From 014d4ae16caece76979f37787d6b7e96f4989467 Mon Sep 17 00:00:00 2001 From: pan <1029559041@qq.com> Date: Thu, 13 May 2021 08:00:07 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=A4=BE=E5=9B=A2=E6=B3=A8?= =?UTF-8?q?=E5=86=8C=20=E4=BC=98=E5=8C=96=E8=83=8C=E6=99=AF=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=20=E9=80=9A=E7=94=A8=E6=96=87=E6=9C=AC=E6=A1=86?= =?UTF-8?q?=E3=80=81=E5=BA=95=E9=83=A8=E6=8F=90=E7=A4=BA=E5=B0=81=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/gyf/csams/TestPreview.kt | 192 +++++++++++++ app/src/main/AndroidManifest.xml | 10 +- app/src/main/java/com/gyf/csams/APP.kt | 81 ++++++ .../main/java/com/gyf/csams/InitActivity.kt | 6 +- .../gyf/csams/account/ui/AccountActivity.kt | 11 +- .../model/RegAssociationViewModel.kt | 45 +++ .../association/ui/RegAssociationActivity.kt | 258 ++++++++++++++++++ .../model/MainViewModel.kt} | 112 +++++--- .../gyf/csams/{ => main}/ui/MainActivity.kt | 210 +++++--------- .../csams/{ui/Base.kt => uikit/BaseView.kt} | 148 +++++++--- .../java/com/gyf/csams/uikit/ViewModel.kt | 41 +++ .../gyf/csams/{ui => uikit}/theme/Color.kt | 2 +- .../gyf/csams/{ui => uikit}/theme/Shape.kt | 2 +- .../gyf/csams/{ui => uikit}/theme/Theme.kt | 2 +- .../com/gyf/csams/{ui => uikit}/theme/Type.kt | 2 +- .../main/res/drawable/ic_exchange_rate.xml | 9 + .../java/com/gyf/csams/ExampleUnitTest.kt | 7 - 17 files changed, 905 insertions(+), 233 deletions(-) create mode 100644 app/src/androidTest/java/com/gyf/csams/TestPreview.kt create mode 100644 app/src/main/java/com/gyf/csams/association/model/RegAssociationViewModel.kt create mode 100644 app/src/main/java/com/gyf/csams/association/ui/RegAssociationActivity.kt rename app/src/main/java/com/gyf/csams/{ui/model/ViewModel.kt => main/model/MainViewModel.kt} (65%) rename app/src/main/java/com/gyf/csams/{ => main}/ui/MainActivity.kt (64%) rename app/src/main/java/com/gyf/csams/{ui/Base.kt => uikit/BaseView.kt} (63%) create mode 100644 app/src/main/java/com/gyf/csams/uikit/ViewModel.kt rename app/src/main/java/com/gyf/csams/{ui => uikit}/theme/Color.kt (69%) rename app/src/main/java/com/gyf/csams/{ui => uikit}/theme/Shape.kt (88%) rename app/src/main/java/com/gyf/csams/{ui => uikit}/theme/Theme.kt (96%) rename app/src/main/java/com/gyf/csams/{ui => uikit}/theme/Type.kt (95%) create mode 100644 app/src/main/res/drawable/ic_exchange_rate.xml diff --git a/app/src/androidTest/java/com/gyf/csams/TestPreview.kt b/app/src/androidTest/java/com/gyf/csams/TestPreview.kt new file mode 100644 index 0000000..aa63651 --- /dev/null +++ b/app/src/androidTest/java/com/gyf/csams/TestPreview.kt @@ -0,0 +1,192 @@ +package com.gyf.csams + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.rotate +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.rotate +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +@Composable +fun MyBox(modifier: Modifier, canvasSize:Dp, canvasD:Float, content: @Composable BoxScope.() -> Unit){ + Box(modifier = modifier,contentAlignment = Alignment.Center){ + Canvas(modifier = Modifier.size(canvasSize)) { + rotate(canvasD){ + drawRect(color = Color.Cyan) + } + } + content() + } +} + +@Composable +fun BoxSetSize(degrees:Float=0F, content: @Composable BoxScope.() -> Unit){ + MyBox(modifier = Modifier + .height(300.dp) + .fillMaxWidth() + .background(Color.Gray) + .rotate(degrees = degrees), canvasSize = 200.dp, canvasD = 45F,content = content + ) +} + +@Composable +fun BoxFillSize(degrees:Float=0F, content: @Composable BoxScope.() -> Unit){ + MyBox( + modifier = Modifier + .fillMaxSize() + .background(Color.LightGray), canvasSize = 100.dp, canvasD = 80F,content = content + ) +} + +//@Preview +@Composable +fun TestPreview(){ + Column(modifier = Modifier.fillMaxSize()){ + BoxSetSize { + Text(buildAnnotatedString { + withStyle(style = SpanStyle(fontSize = 30.sp)){ + append("前置子布局固定尺寸") + } + }) + } + + BoxFillSize { + Text(buildAnnotatedString { + withStyle(style = SpanStyle(fontSize = 30.sp)){ + append("后置子布局使用") + } + withStyle(style = SpanStyle(color= Color.Red,fontSize = 30.sp)){ + append("fillMaxSize修饰符") + } + withStyle(style = SpanStyle(fontSize = 30.sp)){ + append("填充父项允许的所有可用空间") + } + }) + } + } +} + +//@Preview +@Composable +fun TestPreview2(){ + Column(modifier = Modifier.fillMaxSize()){ + Text(buildAnnotatedString { + withStyle(style = SpanStyle(fontSize = 30.sp)){ + append("如果使用示例1方法是") + } + withStyle(style = SpanStyle(color= Color.Red,fontSize = 30.sp)){ + append("无法实现前置子布局填充父项所有可用空间,后置子布局固定尺寸") + } + withStyle(style = SpanStyle(fontSize = 30.sp)){ + append("因为前置布局已经填充父项允许的所有可用空间,后置子布局没有剩余空间可用") + } + + }) + + BoxFillSize { + Text(buildAnnotatedString { + withStyle(style = SpanStyle(fontSize = 30.sp)){ + append("前置子布局填充父项允许的所有可用空间") + } + }) + } + + BoxSetSize { + Text(buildAnnotatedString { + withStyle(style = SpanStyle(fontSize = 30.sp)){ + append("后置子布局固定尺寸") + } + }) + } + } +} + +//@Preview +@Composable +fun TestPreview3(){ + Column(modifier = Modifier + .fillMaxSize()) { + Text(buildAnnotatedString { + withStyle(style = SpanStyle(fontSize = 30.sp)){ + append("折衷方案是通过父项旋转180°实现,子项分别旋转180°复位可") + } + withStyle(style = SpanStyle(color= Color.Red,fontSize = 30.sp)){ + append("实现前置子布局填充父项所有可用空间,后置子布局固定尺寸") + } + withStyle(style = SpanStyle(fontSize = 30.sp)){ + append(",除了旋转,应该有更好的实现方式?") + } + }) + Column(modifier = Modifier.rotate(180F)){ + BoxSetSize(degrees = 180F) { + Text(buildAnnotatedString { + withStyle(style = SpanStyle(fontSize = 30.sp)){ + append("前置子布局固定尺寸,通过旋转和后置子布局对调位置") + } + }) + } + BoxFillSize(degrees = 180F) { + Text(buildAnnotatedString { + + withStyle(style = SpanStyle(fontSize = 30.sp)){ + append("后置子布局使用") + } + withStyle(style = SpanStyle(color= Color.Red,fontSize = 30.sp)){ + append("fillMaxSize修饰符") + } + withStyle(style = SpanStyle(fontSize = 30.sp)){ + append("填充父项允许的所有可用空间,通过旋转和前置子布局对调位置") + } + }) + } + } + } + +} + +//@Preview +@Composable +fun TestPreview4(){ + Column(modifier = Modifier + .fillMaxSize()) { + + + MyBox(modifier = Modifier + .fillMaxWidth() + .fillMaxHeight() + .background(Color.Gray), canvasSize = 100.dp, canvasD = 80F){ + Text(text = "Box1") + } + + MyBox(modifier = Modifier + .height(300.dp) + .fillMaxWidth() + .background(Color.Gray), canvasSize = 200.dp, canvasD = 45F){ + Text(text = "Box2") + } + } +} + +@Preview +@Composable +fun TestPreview5(){ + + Column { + Row { + + } + + } +} + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 34d063a..3af8a99 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,6 +3,10 @@ package="com.gyf.csams"> + + + + + + diff --git a/app/src/main/java/com/gyf/csams/APP.kt b/app/src/main/java/com/gyf/csams/APP.kt index 5c41d89..8f1b314 100644 --- a/app/src/main/java/com/gyf/csams/APP.kt +++ b/app/src/main/java/com/gyf/csams/APP.kt @@ -1,19 +1,100 @@ package com.gyf.csams import android.app.Application +import android.content.res.Resources +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.util.LruCache +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.asImageBitmap +import com.gyf.csams.uikit.BackgroundImage import com.orhanobut.logger.AndroidLogAdapter import com.orhanobut.logger.DiskLogAdapter import com.orhanobut.logger.Logger class APP : Application() { + private lateinit var memoryCache: LruCache + // Get max available VM memory, exceeding this amount will throw an + // OutOfMemory exception. Stored in kilobytes as LruCache takes an + // int in its constructor. + private val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt() + // Use 1/8th of the available memory for this memory cache. + val cacheSize = maxMemory / 8 + + fun getImage(image: BackgroundImage, reqWidth: Int, reqHeight: Int): ImageBitmap { + val bitmap=memoryCache.get(image) + return if(bitmap==null){ + Logger.i("reqWidth=$reqWidth,reqHeight=$reqHeight") + val cacheValue= decodeSampledBitmapFromResource(res = resources,image.id,reqWidth=reqWidth,reqHeight=reqHeight) + memoryCache.put(image, cacheValue) + Logger.i("添加缓存:${image}") + cacheValue.asImageBitmap() + }else{ + Logger.i("从缓存读取:${image}") + bitmap.asImageBitmap() + } + } + + private fun decodeSampledBitmapFromResource( + res: Resources, + resId: Int, + reqWidth: Int, + reqHeight: Int + ): Bitmap { + // First decode with inJustDecodeBounds=true to check dimensions + return BitmapFactory.Options().run { + inJustDecodeBounds = true + BitmapFactory.decodeResource(res, resId, this) + + // Calculate inSampleSize + inSampleSize = calculateInSampleSize(this, reqWidth, reqHeight) + + // Decode bitmap with inSampleSize set + inJustDecodeBounds = false + + BitmapFactory.decodeResource(res, resId, this) + } + } + + private fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int { + // Raw height and width of image + val (height: Int, width: Int) = options.run { outHeight to outWidth } + var inSampleSize = 1 + + if (height > reqHeight || width > reqWidth) { + + val halfHeight: Int = height / 2 + val halfWidth: Int = width / 2 + + // Calculate the largest inSampleSize value that is a power of 2 and keeps both + // height and width larger than the requested height and width. + while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) { + inSampleSize *= 2 + } + } + + return inSampleSize + } override fun onCreate() { super.onCreate() + + memoryCache = object : LruCache(cacheSize) { + + override fun sizeOf(key: BackgroundImage, bitmap: Bitmap): Int { + // The cache size will be measured in kilobytes rather than + // number of items. + return bitmap.byteCount / 1024 + } + } + //初始化日志 Logger.addLogAdapter(AndroidLogAdapter()) Logger.addLogAdapter(DiskLogAdapter()) Logger.i("${BuildConfig.APP_NAME}启动") + + } } \ No newline at end of file diff --git a/app/src/main/java/com/gyf/csams/InitActivity.kt b/app/src/main/java/com/gyf/csams/InitActivity.kt index e1899a5..11a85bb 100644 --- a/app/src/main/java/com/gyf/csams/InitActivity.kt +++ b/app/src/main/java/com/gyf/csams/InitActivity.kt @@ -10,8 +10,8 @@ import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.viewmodel.compose.viewModel import com.gyf.csams.account.ui.AccountActivity -import com.gyf.csams.ui.AnimationText -import com.gyf.csams.ui.MainActivity +import com.gyf.csams.main.ui.MainActivity +import com.gyf.csams.uikit.AnimationText import com.orhanobut.logger.Logger class InitActivity : ComponentActivity() { @@ -50,7 +50,7 @@ private fun Init(initViewModel:InitViewModel= viewModel()){ when (isValid) { false -> context.startActivity(Intent(context, AccountActivity::class.java)) - true -> context.startActivity(Intent(context,MainActivity::class.java)) + true -> context.startActivity(Intent(context, MainActivity::class.java)) } } \ No newline at end of file diff --git a/app/src/main/java/com/gyf/csams/account/ui/AccountActivity.kt b/app/src/main/java/com/gyf/csams/account/ui/AccountActivity.kt index 9804cd0..2775a8f 100644 --- a/app/src/main/java/com/gyf/csams/account/ui/AccountActivity.kt +++ b/app/src/main/java/com/gyf/csams/account/ui/AccountActivity.kt @@ -31,8 +31,8 @@ import androidx.navigation.compose.rememberNavController import com.gyf.csams.BuildConfig import com.gyf.csams.account.model.AccountViewModel import com.gyf.csams.account.model.DialogMessage -import com.gyf.csams.ui.AnimationText -import com.gyf.csams.ui.theme.CSAMSTheme +import com.gyf.csams.uikit.AnimationText +import com.gyf.csams.uikit.theme.CSAMSTheme enum class AccountRoute{ @@ -178,7 +178,7 @@ private fun Account(accountViewModel: AccountViewModel = viewModel(), /** * 学号 - * + *TODO 需要把逻辑封装到[com.gyf.csams.uikit.BaseTextField] * @param accountViewModel */ @Composable @@ -297,9 +297,10 @@ private fun PasswordDialog(accountViewModel: AccountViewModel = viewModel(), mes } + /** * 姓名文本框 - * + * TODO 需要把逻辑封装到[com.gyf.csams.uikit.BaseTextField] * @param name 姓名 * @param accountViewModel */ @@ -328,7 +329,7 @@ private fun Name(name:String, accountViewModel: AccountViewModel = viewModel()){ /** * 密码框 - * + *TODO 需要把逻辑封装到[com.gyf.csams.uikit.BaseTextField] * @param accountViewModel */ @Composable diff --git a/app/src/main/java/com/gyf/csams/association/model/RegAssociationViewModel.kt b/app/src/main/java/com/gyf/csams/association/model/RegAssociationViewModel.kt new file mode 100644 index 0000000..87dd954 --- /dev/null +++ b/app/src/main/java/com/gyf/csams/association/model/RegAssociationViewModel.kt @@ -0,0 +1,45 @@ +package com.gyf.csams.association.model + +import android.net.Uri +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.gyf.csams.uikit.StringForm + + +data class Image(val uri:Uri,val createTime:Long,val size:Long) + +class RegAssociationViewModel : ViewModel() { + + val frameDesc="社团注册资料" + + val name= StringForm(formDesc = "社团名称",textLength = 5) + val desc = StringForm(formDesc = "社团简介",textLength = 30) + + + val _picture=MutableLiveData() + val picture:LiveData =_picture + + val piciurePlaceHolder="请上传图片" + + val errorPicture="图片加载失败,请联系管理员" + + val deninedPermission="拒绝授权" + + val register="注册" + val back="返回" + + fun setPicture(uri: Uri){ + _picture.value=uri + } + + /** + * TODO 注册社团 + * + * @param callback + */ + fun register(callback: (value: String) -> Unit){ + callback("功能尚未实现,敬请期待") + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/gyf/csams/association/ui/RegAssociationActivity.kt b/app/src/main/java/com/gyf/csams/association/ui/RegAssociationActivity.kt new file mode 100644 index 0000000..87bbccb --- /dev/null +++ b/app/src/main/java/com/gyf/csams/association/ui/RegAssociationActivity.kt @@ -0,0 +1,258 @@ +package com.gyf.csams.association.ui + +import android.Manifest +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.BitmapFactory +import android.net.Uri +import android.os.Bundle +import android.provider.MediaStore +import androidx.activity.ComponentActivity +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.compose.setContent +import androidx.activity.result.ActivityResultLauncher +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.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.core.content.ContextCompat +import androidx.lifecycle.viewmodel.compose.viewModel +import com.gyf.csams.R +import com.gyf.csams.association.model.RegAssociationViewModel +import com.gyf.csams.uikit.* +import com.gyf.csams.uikit.theme.CSAMSTheme +import com.orhanobut.logger.Logger + + +/** + * 注册社团 + * + */ +class RegAssociationActivity: ComponentActivity(){ + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + CSAMSTheme { + Body() + } + } + } + +} + +@Composable +fun Body(model:RegAssociationViewModel= viewModel()){ + val scaffoldState = rememberScaffoldState() + Scaffold(scaffoldState = scaffoldState) { + Surface(color = MaterialTheme.colors.background) { + MainFrame(background = { Background(BackgroundImage.reg_association,alpha = 0.7F) }) { + Spacer( + modifier = Modifier + .weight(0.1F) + ) + Title(model = model) + Name(model = model) + Desc( + model = model, modifier = Modifier + .weight(0.1F) + .fillMaxWidth() + ) + Spacer(modifier = Modifier.weight(0.05F)) + Logo( + model = model, 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) + } + } + } +} + + +//@Composable +fun PermissionHandle(context: Context, launcher: ActivityResultLauncher,onGranted:()->Unit){ + // Check permission + when (PackageManager.PERMISSION_GRANTED) { + ContextCompat.checkSelfPermission( + context, + Manifest.permission.READ_EXTERNAL_STORAGE + ) -> { + // Some works that require permission + onGranted() + } + else -> { + // Asking for permission + launcher.launch(Manifest.permission.READ_EXTERNAL_STORAGE) + } + } +} + +/** + * 社团Logo + * + * @param modifier + */ +@Composable +fun Logo(model:RegAssociationViewModel= 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() + + val resultLauncher=rememberLauncherForActivityResult(contract = ActivityResultContracts.StartActivityForResult()) { + when(it.resultCode){ + Activity.RESULT_OK->{ + Logger.i("uri=${it.data?.data}") + it.data?.data?.let { it1 -> model.setPicture(it1) } + } + } + } + + val loadPicture={ + //model.loadPicture(context) + resultLauncher.launch(photoIntent) + } + + val launcher = rememberLauncherForActivityResult( + ActivityResultContracts.RequestPermission() + ) { isGranted: Boolean -> + if (isGranted) { + // Permission Accepted: Do something + loadPicture() + } else { + // Permission Denied: Do something + Logger.w(model.deninedPermission) + } + } + + + + val context= LocalContext.current + + Box(contentAlignment = Alignment.Center,modifier = modifier) { + Row(verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center,modifier = Modifier + .fillMaxSize() + .border(width = 1.dp, color = Color.Black)) { + + if (uri == null) { + OutlinedButton(onClick = { + when (PackageManager.PERMISSION_GRANTED) { + ContextCompat.checkSelfPermission( + context, + Manifest.permission.READ_EXTERNAL_STORAGE + ) -> { + // Some works that require permission + loadPicture() + } + else -> { + // Asking for permission + launcher.launch(Manifest.permission.READ_EXTERNAL_STORAGE) + } + } + }) { + Text(text = model.piciurePlaceHolder) + } + } else { + uri.let { + if(it!=null){ + Row { + Image(bitmap = BitmapFactory.decodeStream(context.contentResolver.openInputStream(it)) + .asImageBitmap(), contentDescription = null) + IconButton(onClick = { + loadPicture() + }) { + Image(painter = painterResource(id = R.drawable.ic_exchange_rate), contentDescription = null) + } + } + + + }else{ + Text(text = model.errorPicture) + } + } + } + + + } + + } + + +} + +@Composable +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(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) + } + } + +} + +/** + * 菜单标题 + * + */ +@Composable +fun Title(model:RegAssociationViewModel= viewModel()){ + Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.Center) { + Text(text = model.frameDesc,style = MaterialTheme.typography.h4) + } +} + +/** + * 社团名称 + * @param model + */ +@Composable +fun Name(model:RegAssociationViewModel= viewModel()){ + BaseTextField(form = model.name,singeLine = true,modifier = Modifier.fillMaxWidth()) +} + +/** + * 社团简介 + * @param model + */ +@Composable +fun Desc(model:RegAssociationViewModel= viewModel(),modifier:Modifier){ + BaseTextField(form = model.desc,modifier = modifier) +} + + +@Preview +@Composable +fun NamePreview(){ + val model=RegAssociationViewModel() + Body(model=model) + +} \ No newline at end of file diff --git a/app/src/main/java/com/gyf/csams/ui/model/ViewModel.kt b/app/src/main/java/com/gyf/csams/main/model/MainViewModel.kt similarity index 65% rename from app/src/main/java/com/gyf/csams/ui/model/ViewModel.kt rename to app/src/main/java/com/gyf/csams/main/model/MainViewModel.kt index d8314e9..8174c8f 100644 --- a/app/src/main/java/com/gyf/csams/ui/model/ViewModel.kt +++ b/app/src/main/java/com/gyf/csams/main/model/MainViewModel.kt @@ -1,10 +1,11 @@ -package com.gyf.csams.ui.model +package com.gyf.csams.main.model import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.gyf.csams.R +import com.gyf.csams.uikit.StringForm import com.orhanobut.logger.Logger import kotlinx.coroutines.Job import kotlinx.coroutines.delay @@ -20,7 +21,7 @@ class MarqueeViewModel:ViewModel() { private val _marqueeIndex=MutableLiveData(0) var marqueeIndex:LiveData = _marqueeIndex - var marqueeJob:Job? = null + private var marqueeJob:Job? = null @@ -38,6 +39,10 @@ class MarqueeViewModel:ViewModel() { } } +/** + * 海报轮播 + * + */ class CarouselViewModel:ViewModel(){ val imageList= listOf(R.drawable.ic_launcher_foreground,R.drawable.ic_account_fill,R.drawable.ic_all_fill,R.drawable.ic_home_fill) @@ -45,13 +50,13 @@ class CarouselViewModel:ViewModel(){ val index:LiveData = _index - var job:Job? = null + private var job:Job? = null init { start() } - fun start(){ + private fun start(){ job = viewModelScope.launch { do{ _index.postValue(if (_index.value==imageList.size-1) 0 else _index.value?.plus(1)) @@ -60,50 +65,68 @@ class CarouselViewModel:ViewModel(){ } } - fun stop(){ - println("停止更新") - job?.cancel() - } - } - +/** + * 社团 + * + * @property name 社团名称 + */ data class AssociationDto(val name:String) +/** + * 主页 + * + */ +class MainViewModel:ViewModel(){ + /** + * TODO 发送留言 + * + */ + fun sendMessage(callback: (value: String) -> Unit){ + callback("功能尚未实现,敬请期待") + } + + /** + * TODO 打开通知 + * + * @param callback + */ + fun openNotification(callback: (value: String) -> Unit){ + callback("功能尚未实现,敬请期待") + } +} + +/** + * 社团列表 + * + */ class ListViewModel:ViewModel(){ - private val _name=MutableLiveData("") - val name:LiveData = _name - val nameDesc="社团名称" - val namePlaceholder="请输入$nameDesc" + val name = StringForm(formDesc = "社团名称",textLength = 5) - private val _desc=MutableLiveData("") - val desc:LiveData = _desc - val descDesc="社团简介" - val descPlaceholder="请输入$descDesc" + val desc = StringForm(formDesc = "社团简介",textLength = 10) - //注册请求响应信息 - private val _snackBarMsg=MutableLiveData() - val snackBarMsg:LiveData = _snackBarMsg + //社团列表加载数量 + val associationListSize=10 //社团列表 private val _associationList=MutableLiveData>(mutableListOf()) val associationDto:LiveData> = _associationList + val searchDesc="搜索" + init { loadAssociation() } - fun onChangeName(name:String){ - _name.value=name - } - - fun onChangeDesc(desc:String){ - _desc.value=desc - } - - fun search(){ - Logger.i("使用社团名称:${_name.value},社团简介:${_desc.value} 搜索社团") - _snackBarMsg.value="搜索失败,请联系管理员" + /** + * TODO 社团检索 + * + * @param callback + */ + fun search(callback: (value: String) -> Unit){ + Logger.i("搜索条件[社团名称:${name.formValue.value},社团简介:${desc.formValue.value}]") + callback("功能尚未实现,敬请期待") } /** @@ -131,7 +154,7 @@ class ListViewModel:ViewModel(){ * 加载更多社团列表 * */ - fun addMore(){ + fun addMore(callback:(message:String) -> Unit){ viewModelScope.launch { val c = _associationList.value @@ -147,15 +170,26 @@ class ListViewModel:ViewModel(){ Logger.i("t.size=${t.size}") _associationList.postValue(t) Logger.i("加载更多社团size=${_associationList.value?.size}") - _snackBarMsg.value="成功加载更多社团" + callback("成功加载更多社团") } - - } } +} - fun reset(){ - _snackBarMsg.value="" +/** + * 个人中心 + * + */ +class CenterViewModel:ViewModel(){ + val myAssociationDesc="我的社团" + + /** + * TODO 打开我的社团 + * + * @param callback + */ + fun openMyAssociation(callback: (value: String) -> Unit){ + callback("功能尚未实现,敬请期待") } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/gyf/csams/ui/MainActivity.kt b/app/src/main/java/com/gyf/csams/main/ui/MainActivity.kt similarity index 64% rename from app/src/main/java/com/gyf/csams/ui/MainActivity.kt rename to app/src/main/java/com/gyf/csams/main/ui/MainActivity.kt index 34b23e6..0d6127d 100644 --- a/app/src/main/java/com/gyf/csams/ui/MainActivity.kt +++ b/app/src/main/java/com/gyf/csams/main/ui/MainActivity.kt @@ -1,28 +1,25 @@ -package com.gyf.csams.ui +package com.gyf.csams.main.ui +import android.content.Intent import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.Image import androidx.compose.foundation.border +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.shape.RoundedCornerShape -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.* import androidx.compose.runtime.Composable 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.graphics.Color -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -32,13 +29,10 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import com.gyf.csams.R -import com.gyf.csams.ui.model.AssociationDto -import com.gyf.csams.ui.model.CarouselViewModel -import com.gyf.csams.ui.model.ListViewModel -import com.gyf.csams.ui.model.MarqueeViewModel -import com.gyf.csams.ui.theme.CSAMSTheme -import com.orhanobut.logger.Logger -import kotlinx.coroutines.launch +import com.gyf.csams.association.ui.RegAssociationActivity +import com.gyf.csams.main.model.* +import com.gyf.csams.uikit.* +import com.gyf.csams.uikit.theme.CSAMSTheme /** @@ -48,11 +42,14 @@ import kotlinx.coroutines.launch class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + setContent { CSAMSTheme { Body() } } + + } } @@ -67,25 +64,15 @@ fun Body() { NavHost(navController, startDestination = MainMenu.Main.name) { composable(MainMenu.Main.name) { Main(navController = navController) + ShowSnackbar(scaffoldState = scaffoldState) } composable(MainMenu.List.name) { AssociationList(navController = navController) - - val model:ListViewModel= viewModel() - val snackBarMsg:String by model.snackBarMsg.observeAsState("") - - if(snackBarMsg!=""){ - val scope= rememberCoroutineScope() - scope.launch { - scaffoldState.snackbarHostState.showSnackbar( - message = snackBarMsg - ) - model.reset() - } - } + ShowSnackbar(scaffoldState = scaffoldState) } composable(MainMenu.Center.name) { Center(navController = navController) + ShowSnackbar(scaffoldState = scaffoldState) } } } @@ -98,16 +85,20 @@ fun Body() { * */ @Composable -fun Center(navController: NavController){ - MainFrame(background = { CenterBackground() }, mainMenu = MainMenu.Center, nav = navController) { +fun Center(model:CenterViewModel= viewModel(), scaffoldModel: ScaffoldModel= viewModel(), navController: NavController){ + MainFrame(background = { Background(image = BackgroundImage.center,alpha = 0.5F) }, mainMenu = MainMenu.Center, nav = navController) { Column(modifier = Modifier .weight(0.33F) .fillMaxWidth(),verticalArrangement = Arrangement.Bottom) { - Card(backgroundColor = Color.White) { - Row(modifier = Modifier.fillMaxWidth(),verticalAlignment = Alignment.CenterVertically) { + Card(backgroundColor = MaterialTheme.colors.background) { + Row(modifier = Modifier + .fillMaxWidth() + .clickable(onClick = { + model.openMyAssociation { scaffoldModel.update(it) } + }),verticalAlignment = Alignment.CenterVertically) { Spacer(modifier = Modifier.weight(0.33F)) Row(modifier = Modifier.weight(0.33F),horizontalArrangement = Arrangement.Center) { - Text(text = "我的社团") + Text(text = model.myAssociationDesc) } Row(modifier = Modifier.weight(0.33F),horizontalArrangement = Arrangement.End) { Icon( @@ -126,27 +117,12 @@ fun Center(navController: NavController){ } } -/** - * 个人中心背景 - * - */ -@Composable -fun CenterBackground(){ - Image( - painter = painterResource(id = R.drawable.mb_bg_fb_28), - contentDescription = null, - contentScale = ContentScale.FillHeight, - alpha = 0.5F, - modifier = Modifier.fillMaxSize() - ) -} - /** * 主界面 */ @Composable fun Main(navController: NavController) { - MainFrame(background = { MainBackground() }, mainMenu = MainMenu.Main, nav = navController) { + MainFrame(background = { Background(image = BackgroundImage.main) }, mainMenu = MainMenu.Main, nav = navController) { Column(modifier = Modifier.weight(0.33F)) { Notification() MessageBoard() @@ -160,19 +136,6 @@ fun Main(navController: NavController) { } } -/** - * 主界面背景 - * - */ -@Composable -fun MainBackground() { - Image( - painter = painterResource(id = R.drawable.mb_bg_fb_08), - contentDescription = null, - contentScale = ContentScale.FillHeight, - modifier = Modifier.fillMaxSize() - ) -} /** * 社团列表 @@ -182,9 +145,7 @@ fun MainBackground() { @Composable fun AssociationList(navController: NavController) { MainFrame( - background = { - AssociationListBackground() - }, + background = { Background(image = BackgroundImage.list) }, mainMenu = MainMenu.List, nav = navController ) { @@ -195,45 +156,36 @@ fun AssociationList(navController: NavController) { } /** - * 添加社团按钮 + * 注册社团按钮 * */ @Composable fun RegisterAssociation() { + val context= LocalContext.current Row( horizontalArrangement = Arrangement.End, modifier = Modifier .fillMaxWidth() .padding(10.dp) ) { - Icon( - painter = painterResource(id = R.drawable.ic_add_fill), - contentDescription = null, - modifier = Modifier.size(50.dp) - ) + IconButton(onClick = { + context.startActivity(Intent(context, RegAssociationActivity::class.java)) + }) { + Icon( + painter = painterResource(id = R.drawable.ic_add_fill), + contentDescription = null, + modifier = Modifier.size(50.dp), + ) + } } } -/** - * 社团列表背景 - * - */ -@Composable -fun AssociationListBackground() { - Image( - painter = painterResource(id = R.drawable.mb_bg_fb_07), - contentDescription = null, - contentScale = ContentScale.FillHeight, - modifier = Modifier.fillMaxSize() - ) -} - /** * 社团列表 * */ @Composable -fun AssociationListBody(model: ListViewModel = viewModel()) { +fun AssociationListBody(model: ListViewModel = viewModel(),scaffoldModel: ScaffoldModel= viewModel()) { val associationList: MutableList? by model.associationDto.observeAsState() val listState = rememberLazyListState() @@ -253,7 +205,7 @@ fun AssociationListBody(model: ListViewModel = viewModel()) { }else{ Box(modifier = Modifier .weight(0.35F) - .border(width = 1.dp, color = Color.Black)) + .border(width = 1.dp, color = MaterialTheme.colors.onBackground)) } Spacer(modifier = Modifier.weight(0.1F)) } @@ -263,15 +215,9 @@ fun AssociationListBody(model: ListViewModel = viewModel()) { } } } - Logger.i("totalItemsCount=${listState.layoutInfo.totalItemsCount}" + - ",firstVisibleItemIndex=${listState.firstVisibleItemIndex}," + - "firstVisibleItemScrollOffset=${listState.firstVisibleItemScrollOffset}," + - "viewportStartOffset=${listState.layoutInfo.viewportStartOffset}" + - "viewportEndOffset=${listState.layoutInfo.viewportEndOffset}") - - if(listState.layoutInfo.totalItemsCount-listState.firstVisibleItemIndex==4){ - model.addMore() + if(listState.layoutInfo.totalItemsCount-listState.firstVisibleItemIndex==model.associationListSize/2-1){ + model.addMore { scaffoldModel.update(it) } } } @@ -296,48 +242,23 @@ fun Association(associationDto: AssociationDto) { * */ @Composable -fun AssociationSearch(model: ListViewModel = viewModel()) { - val name: String by model.name.observeAsState("") +fun AssociationSearch(model: ListViewModel = viewModel(),scaffoldModel: ScaffoldModel= viewModel()) { + Card(modifier = Modifier.padding(horizontal = 50.dp, vertical = 10.dp)) { Column { - Row { - val focusManager = LocalFocusManager.current - Spacer(modifier = Modifier.weight(0.05F)) - OutlinedTextField( - modifier = Modifier.weight(0.4F), value = name, - onValueChange = { model.onChangeName(it) }, - singleLine = true, - label = { Text(text = model.nameDesc) }, - placeholder = { Text(text = model.namePlaceholder) }, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done) - ) - Spacer(modifier = Modifier.weight(0.1F)) - OutlinedTextField( - modifier = Modifier.weight(0.4F), value = name, - onValueChange = { model.onChangeDesc(it) }, - singleLine = true, - label = { Text(text = model.descDesc) }, - placeholder = { Text(text = model.descPlaceholder) }, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done) - ) - Spacer(modifier = Modifier.weight(0.05F)) - } + + BaseTextField(form = model.name,singeLine = true,modifier = Modifier.padding(horizontal = 10.dp)) + BaseTextField(form = model.desc,singeLine = true,modifier = Modifier.padding(horizontal = 10.dp)) Spacer( - modifier = Modifier - .fillMaxWidth() - .height(10.dp) + modifier = Modifier.height(10.dp) ) Row(horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth()) { - OutlinedButton(onClick = { model.search() }, modifier = Modifier.width(100.dp)) { - Text(text = "搜索") + OutlinedButton(onClick = { model.search { scaffoldModel.update(it) } }, modifier = Modifier.width(100.dp)) { + Text(text = model.searchDesc) } } Spacer( - modifier = Modifier - .fillMaxWidth() - .height(10.dp) + modifier = Modifier.height(10.dp) ) } @@ -350,18 +271,22 @@ fun AssociationSearch(model: ListViewModel = viewModel()) { * */ @Composable -fun Notification() { +fun Notification(mainViewModel: MainViewModel= viewModel(),scaffoldModel: ScaffoldModel= viewModel()) { Row( horizontalArrangement = Arrangement.End, modifier = Modifier .fillMaxWidth() .padding(10.dp) ) { - Icon( - painter = painterResource(id = R.drawable.ic_notification), - contentDescription = null, - modifier = Modifier.size(50.dp) - ) + IconButton(onClick = { + mainViewModel.openNotification { scaffoldModel.update(it) } + }) { + Icon( + painter = painterResource(id = R.drawable.ic_notification), + contentDescription = null + ) + } + } } @@ -376,7 +301,7 @@ fun MyBorder(content: @Composable BoxScope.() -> Unit) { modifier = Modifier .border( width = 1.dp, - color = Color.Black, + color = MaterialTheme.colors.onBackground, shape = RoundedCornerShape(size = 20.dp) ), ) { @@ -390,18 +315,19 @@ fun MyBorder(content: @Composable BoxScope.() -> Unit) { * */ @Composable -fun MessageBoard(model: MarqueeViewModel = viewModel()) { +fun MessageBoard(model: MarqueeViewModel = viewModel(),mainViewModel:MainViewModel= viewModel(),scaffoldModel: ScaffoldModel= viewModel()) { MyBorder { Row( verticalAlignment = Alignment.CenterVertically ) { - Icon( - painter = painterResource(id = R.drawable.ic_comments), - contentDescription = null, - modifier = Modifier - .size(50.dp) - ) + + IconButton(onClick = { mainViewModel.sendMessage { scaffoldModel.update(it) } }) { + Icon( + painter = painterResource(id = R.drawable.ic_comments), + contentDescription = null, + ) + } Row( horizontalArrangement = Arrangement.Center, modifier = Modifier diff --git a/app/src/main/java/com/gyf/csams/ui/Base.kt b/app/src/main/java/com/gyf/csams/uikit/BaseView.kt similarity index 63% rename from app/src/main/java/com/gyf/csams/ui/Base.kt rename to app/src/main/java/com/gyf/csams/uikit/BaseView.kt index 4d662f5..68a6d4f 100644 --- a/app/src/main/java/com/gyf/csams/ui/Base.kt +++ b/app/src/main/java/com/gyf/csams/uikit/BaseView.kt @@ -1,4 +1,4 @@ -package com.gyf.csams.ui +package com.gyf.csams.uikit import androidx.annotation.DrawableRes import androidx.compose.animation.Crossfade @@ -6,14 +6,21 @@ import androidx.compose.animation.animateColor import androidx.compose.animation.core.* import androidx.compose.foundation.Image import androidx.compose.foundation.layout.* +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.* import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.DefaultAlpha +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -21,10 +28,13 @@ import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController import androidx.navigation.compose.navigate import androidx.navigation.compose.rememberNavController +import com.gyf.csams.APP import com.gyf.csams.R -import com.gyf.csams.ui.model.CarouselViewModel -import com.gyf.csams.ui.model.MarqueeViewModel -import com.gyf.csams.ui.theme.CSAMSTheme +import com.gyf.csams.main.model.CarouselViewModel +import com.gyf.csams.main.model.MarqueeViewModel +import com.gyf.csams.uikit.theme.CSAMSTheme +import com.orhanobut.logger.Logger +import kotlinx.coroutines.launch /** * 淡入淡出并且颜色变化文本 @@ -35,8 +45,8 @@ import com.gyf.csams.ui.theme.CSAMSTheme fun AnimationText(text:String){ val infiniteTransition = rememberInfiniteTransition() val color by infiniteTransition.animateColor( - initialValue = Color.Red, - targetValue = Color.Green, + initialValue = MaterialTheme.colors.primary, + targetValue = MaterialTheme.colors.onPrimary, animationSpec = infiniteRepeatable( animation = tween(1000, easing = LinearEasing), repeatMode = RepeatMode.Reverse @@ -94,7 +104,7 @@ fun MenuIconButton(_menu: MainMenu,menu: MainMenu,modifier: Modifier,onClick: () */ @Composable fun MainBottomAppBar(menu:MainMenu, nav: NavController, modifier: Modifier=Modifier){ - BottomAppBar(backgroundColor = Color.White,modifier=modifier) { + BottomAppBar(backgroundColor = MaterialTheme.colors.background,modifier=modifier) { //图标宽度平等分 val weight=1/(MainMenu.values().size*1.0f) @@ -146,7 +156,12 @@ fun Marquee(model: MarqueeViewModel = viewModel(), content: @Composable BoxScope } } - +/** + * 跑马灯布局 + * + * @param model + * @param offset + */ @Composable fun MarqueeText(model: MarqueeViewModel = viewModel(), offset: State) { val poetryIndex: Int by model.marqueeIndex.observeAsState(0) @@ -163,7 +178,7 @@ fun MarqueeText(model: MarqueeViewModel = viewModel(), offset: State) { } /** - * 界面框架 + * 导航界面框架 * * @param background 背景 * @param mainMenu 菜单 @@ -184,6 +199,20 @@ fun MainFrame( background:@Composable ()->Unit,mainMenu: MainMenu,nav: NavContro } } +/** + * 界面框架 + * + * @param background + * @param body + */ +@Composable +fun MainFrame(background:@Composable ()->Unit,body:@Composable ColumnScope.()->Unit){ + Box(modifier = Modifier.fillMaxSize()) { + background() + Column(content = body) + } +} + /** * 图片轮播 * @@ -200,34 +229,89 @@ fun Carousel( } } -@Preview +/** + * 通用文本输入框 + * + * @param T + * @param modifier + * @param form + * @param singeLine + */ @Composable -fun CarouselPreview() { - val model = CarouselViewModel() -// ClubActivitiesImage(model = model) - - - Carousel(model = model) { - Card( - modifier = Modifier - .height(300.dp) - .fillMaxWidth() -// .rotate(180F) - ) { - Image( - painter = painterResource(id = R.drawable.hot_activity_background), - contentDescription = null - ) +fun BaseTextField(modifier:Modifier=Modifier,form:T, singeLine:Boolean=false){ + val name: String by form.formValue.observeAsState("") + val focusManager = LocalFocusManager.current + OutlinedTextField( + modifier = modifier, + value = name, + onValueChange = {form.onChange(it)}, + label={ Text(text = form.formDesc)}, + placeholder = { Text(text = form.formPlaceholder)}, + singleLine = singeLine, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done), + trailingIcon={ Text(text = "${name.length}/${form.textLength}") }) +} - Image( - painter = painterResource(id = it), - contentDescription = null - ) +/** + * 底部提示 + * + * @param model + * @param scaffoldState + */ +@Composable +fun ShowSnackbar(model:ScaffoldModel= viewModel(),scaffoldState: ScaffoldState){ + val message:String? by model.message.observeAsState() + message?.let { + Logger.i("message=$it") + LaunchedEffect(scaffoldState){ + launch { + scaffoldState.snackbarHostState.showSnackbar( + message = it + ) + model.update() + } } } } +/** + * 界面背景 + * + * @property id 资源id + */ +enum class BackgroundImage(@DrawableRes val id:Int){ + //主页 + main(R.drawable.mb_bg_fb_08), + //社团列表 + list(R.drawable.mb_bg_fb_07), + //个人中心 + center(R.drawable.mb_bg_fb_28), + //注册社团 + reg_association(R.drawable.mb_bg_fb_07) +} + +/** + * 界面背景图 + * + * @param image + * @param alpha + */ +@Composable +fun Background(image: BackgroundImage, alpha:Float= DefaultAlpha){ + val app= LocalContext.current.applicationContext as APP + + BoxWithConstraints { + Image( + bitmap = app.getImage(image = image,reqHeight = maxHeight.value.toInt()/2,reqWidth = maxWidth.value.toInt()/2), + contentDescription = null, + contentScale = ContentScale.FillHeight, + alpha = alpha, + modifier = Modifier.fillMaxSize() + ) + } +} @Preview @Composable @@ -235,7 +319,7 @@ fun AnimationTextPreview(){ AnimationText(text = "6666") } -@Preview +//@Preview @Composable fun MyBottomAppBarPreview(){ val nav= rememberNavController() diff --git a/app/src/main/java/com/gyf/csams/uikit/ViewModel.kt b/app/src/main/java/com/gyf/csams/uikit/ViewModel.kt new file mode 100644 index 0000000..5a9843b --- /dev/null +++ b/app/src/main/java/com/gyf/csams/uikit/ViewModel.kt @@ -0,0 +1,41 @@ +package com.gyf.csams.uikit + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.orhanobut.logger.Logger + +interface FormLength{ + val nameLengthError:String +} + +abstract class FormName(val formDesc:String){ + protected val _formValue= MutableLiveData() + val formValue: LiveData = _formValue + val formPlaceholder="请输入$formDesc" + + abstract fun onChange(value:T) +} + +open class StringForm(formDesc: String, val textLength: Int) : FormName(formDesc = formDesc), + FormLength { + override val nameLengthError="${formDesc}不能超过最大长度$textLength" + + override fun onChange(value: String) { + if(value.length>textLength){ + _formValue.value=value.slice(IntRange(0,textLength-1)) + }else{ + _formValue.value=value + } + Logger.i("${formDesc}更新值:${_formValue.value}") + } +} + +class ScaffoldModel:ViewModel(){ + private val _message=MutableLiveData() + val message:LiveData = _message + + fun update(message:String?=null){ + _message.value=message + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gyf/csams/ui/theme/Color.kt b/app/src/main/java/com/gyf/csams/uikit/theme/Color.kt similarity index 69% rename from app/src/main/java/com/gyf/csams/ui/theme/Color.kt rename to app/src/main/java/com/gyf/csams/uikit/theme/Color.kt index 1ea6684..cbec799 100644 --- a/app/src/main/java/com/gyf/csams/ui/theme/Color.kt +++ b/app/src/main/java/com/gyf/csams/uikit/theme/Color.kt @@ -1,4 +1,4 @@ -package com.gyf.csams.ui.theme +package com.gyf.csams.uikit.theme import androidx.compose.ui.graphics.Color diff --git a/app/src/main/java/com/gyf/csams/ui/theme/Shape.kt b/app/src/main/java/com/gyf/csams/uikit/theme/Shape.kt similarity index 88% rename from app/src/main/java/com/gyf/csams/ui/theme/Shape.kt rename to app/src/main/java/com/gyf/csams/uikit/theme/Shape.kt index 79508e4..e2f4bfb 100644 --- a/app/src/main/java/com/gyf/csams/ui/theme/Shape.kt +++ b/app/src/main/java/com/gyf/csams/uikit/theme/Shape.kt @@ -1,4 +1,4 @@ -package com.gyf.csams.ui.theme +package com.gyf.csams.uikit.theme import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Shapes diff --git a/app/src/main/java/com/gyf/csams/ui/theme/Theme.kt b/app/src/main/java/com/gyf/csams/uikit/theme/Theme.kt similarity index 96% rename from app/src/main/java/com/gyf/csams/ui/theme/Theme.kt rename to app/src/main/java/com/gyf/csams/uikit/theme/Theme.kt index 1ea718e..042773b 100644 --- a/app/src/main/java/com/gyf/csams/ui/theme/Theme.kt +++ b/app/src/main/java/com/gyf/csams/uikit/theme/Theme.kt @@ -1,4 +1,4 @@ -package com.gyf.csams.ui.theme +package com.gyf.csams.uikit.theme import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material.MaterialTheme diff --git a/app/src/main/java/com/gyf/csams/ui/theme/Type.kt b/app/src/main/java/com/gyf/csams/uikit/theme/Type.kt similarity index 95% rename from app/src/main/java/com/gyf/csams/ui/theme/Type.kt rename to app/src/main/java/com/gyf/csams/uikit/theme/Type.kt index f01e380..5dcc466 100644 --- a/app/src/main/java/com/gyf/csams/ui/theme/Type.kt +++ b/app/src/main/java/com/gyf/csams/uikit/theme/Type.kt @@ -1,4 +1,4 @@ -package com.gyf.csams.ui.theme +package com.gyf.csams.uikit.theme import androidx.compose.material.Typography import androidx.compose.ui.text.TextStyle diff --git a/app/src/main/res/drawable/ic_exchange_rate.xml b/app/src/main/res/drawable/ic_exchange_rate.xml new file mode 100644 index 0000000..eee489a --- /dev/null +++ b/app/src/main/res/drawable/ic_exchange_rate.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/test/java/com/gyf/csams/ExampleUnitTest.kt b/app/src/test/java/com/gyf/csams/ExampleUnitTest.kt index 479a3ca..070132b 100644 --- a/app/src/test/java/com/gyf/csams/ExampleUnitTest.kt +++ b/app/src/test/java/com/gyf/csams/ExampleUnitTest.kt @@ -39,12 +39,5 @@ class ExampleUnitTest { println( LocalDateTime.now().get(ChronoField.YEAR)) } - @Test - fun testChunked(){ - IntRange(0,13).chunked(3){ - println(it) - } - } - }