添加菜品采集
This commit is contained in:
@@ -111,6 +111,9 @@ dependencies {
|
|||||||
val objectboxVersion = "5.0.1"
|
val objectboxVersion = "5.0.1"
|
||||||
debugImplementation("io.objectbox:objectbox-android-objectbrowser:$objectboxVersion")
|
debugImplementation("io.objectbox:objectbox-android-objectbrowser:$objectboxVersion")
|
||||||
// releaseImplementation("io.objectbox:objectbox-android:$objectboxVersion")
|
// releaseImplementation("io.objectbox:objectbox-android:$objectboxVersion")
|
||||||
|
|
||||||
|
implementation(libs.androidx.recyclerview)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apply(plugin = "io.objectbox")
|
apply(plugin = "io.objectbox")
|
||||||
@@ -51,6 +51,21 @@
|
|||||||
android:name=".activity.MainActivity"
|
android:name=".activity.MainActivity"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".activity.FoodCollectionActivity"
|
||||||
|
android:exported="true">
|
||||||
|
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
android:authorities="${applicationId}.fileprovider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/file_paths" />
|
||||||
|
</provider>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -1,7 +1,14 @@
|
|||||||
package com.sw.dualscreen.activity
|
package com.sw.dualscreen.activity
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.Typeface
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
|
import android.view.inputmethod.EditorInfo
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
@@ -13,17 +20,28 @@ import androidx.camera.core.Preview
|
|||||||
import androidx.camera.lifecycle.ProcessCameraProvider
|
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import com.example.utils.FloatBase64Utils
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
import com.sw.dualscreen.R
|
import com.sw.dualscreen.R
|
||||||
import com.sw.dualscreen.adapter.FoodCollectionAdapter
|
import com.sw.dualscreen.adapter.FoodCollectionAdapter
|
||||||
|
import com.sw.dualscreen.adapter.GenericItemAdapter
|
||||||
|
import com.sw.dualscreen.adapter.GridSpacingItemDecoration
|
||||||
|
import com.sw.dualscreen.adapter.dpToPx
|
||||||
import com.sw.dualscreen.databinding.ActivityFoodCollectionBinding
|
import com.sw.dualscreen.databinding.ActivityFoodCollectionBinding
|
||||||
|
import com.sw.dualscreen.databinding.ItemSearchFoodInfoBinding
|
||||||
|
import com.sw.dualscreen.databinding.ItemSearchFoodInfoBinding.inflate
|
||||||
|
import com.sw.dualscreen.model.response.FoodInfo
|
||||||
import com.sw.dualscreen.objbox.Food
|
import com.sw.dualscreen.objbox.Food
|
||||||
import com.sw.dualscreen.objbox.FoodCollectionBean
|
import com.sw.dualscreen.objbox.FoodCollectionBean
|
||||||
import com.sw.dualscreen.objbox.FoodModule
|
import com.sw.dualscreen.objbox.FoodModule
|
||||||
import com.sw.dualscreen.objbox.ObjectBox
|
import com.sw.dualscreen.objbox.ObjectBox
|
||||||
import com.sw.dualscreen.presentation.SecondaryScreenPresentation
|
import com.sw.dualscreen.presentation.SecondaryScreenPresentation
|
||||||
import com.sw.dualscreen.sdk.SensorScaleUtils
|
import com.sw.dualscreen.sdk.SensorScaleUtils
|
||||||
|
import com.sw.dualscreen.utils.CameraHelper
|
||||||
|
import com.sw.dualscreen.utils.Debouncer
|
||||||
|
import com.sw.dualscreen.utils.GlideUtils
|
||||||
import com.sw.dualscreen.utils.ImageUtil
|
import com.sw.dualscreen.utils.ImageUtil
|
||||||
import com.sw.dualscreen.utils.StorageUtils
|
import com.sw.dualscreen.utils.StorageUtils
|
||||||
import com.sw.dualscreen.view.CustomBottomSheetDialog
|
import com.sw.dualscreen.view.CustomBottomSheetDialog
|
||||||
@@ -32,6 +50,7 @@ import com.sw.dualscreen.viewmodel.UserViewModel
|
|||||||
import com.sw.plate.utils.ToastUtils
|
import com.sw.plate.utils.ToastUtils
|
||||||
import io.objectbox.Box
|
import io.objectbox.Box
|
||||||
import io.objectbox.kotlin.boxFor
|
import io.objectbox.kotlin.boxFor
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.pytorch.IValue
|
import org.pytorch.IValue
|
||||||
import org.pytorch.Module
|
import org.pytorch.Module
|
||||||
import org.pytorch.torchvision.TensorImageUtils
|
import org.pytorch.torchvision.TensorImageUtils
|
||||||
@@ -50,6 +69,7 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
|
|||||||
private val executor = Executors.newSingleThreadExecutor()
|
private val executor = Executors.newSingleThreadExecutor()
|
||||||
|
|
||||||
private val viewModel by viewModels<UserViewModel>()
|
private val viewModel by viewModels<UserViewModel>()
|
||||||
|
private var selectedFoodId: String? = ""
|
||||||
private var selectedFoodName: String? = ""
|
private var selectedFoodName: String? = ""
|
||||||
private var bottomSheetDialog: CustomBottomSheetDialog? = null
|
private var bottomSheetDialog: CustomBottomSheetDialog? = null
|
||||||
|
|
||||||
@@ -60,11 +80,17 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
|
|||||||
private var box: Box<Food>? = null
|
private var box: Box<Food>? = null
|
||||||
private val list: MutableList<FoodCollectionBean> = mutableListOf()
|
private val list: MutableList<FoodCollectionBean> = mutableListOf()
|
||||||
|
|
||||||
|
private lateinit var cameraHelper: CameraHelper
|
||||||
|
|
||||||
|
private lateinit var foodAdapter: GenericItemAdapter<FoodInfo, ItemSearchFoodInfoBinding>
|
||||||
|
private val debouncer = Debouncer(2000)
|
||||||
|
|
||||||
private val adapter: FoodCollectionAdapter by lazy {
|
private val adapter: FoodCollectionAdapter by lazy {
|
||||||
FoodCollectionAdapter(list).apply {
|
FoodCollectionAdapter(list).apply {
|
||||||
setOnItemClickListener { _, _, position ->
|
setOnItemClickListener { _, _, position ->
|
||||||
if (list[position].isShowCamera) {
|
if (list[position].isShowCamera) {
|
||||||
takePhoto()
|
// takePhoto()
|
||||||
|
cameraHelper.openCamera()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addOnItemChildClickListener(R.id.ivDelete) { _, _, position ->
|
addOnItemChildClickListener(R.id.ivDelete) { _, _, position ->
|
||||||
@@ -84,7 +110,28 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun initialize() {
|
override fun initialize() {
|
||||||
binding.ivBack.setOnClickListener { finish() }
|
|
||||||
|
// 初始化 CameraHelper
|
||||||
|
cameraHelper = CameraHelper(
|
||||||
|
context = this,
|
||||||
|
caller = this,
|
||||||
|
authority = "${packageName}.fileprovider"
|
||||||
|
) { uri, path ->
|
||||||
|
if (list.size < 6) {
|
||||||
|
val insertIndex = if (list.isEmpty()) 0 else list.size - 1
|
||||||
|
list.add(insertIndex, FoodCollectionBean(imageUri = uri))
|
||||||
|
} else {
|
||||||
|
list[list.size - 1] = FoodCollectionBean(imageUri = uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.ivBack.setOnClickListener {
|
||||||
|
val intent = Intent(this, MainActivity::class.java)
|
||||||
|
startActivity(intent)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
list.add(FoodCollectionBean(isShowCamera = true))
|
list.add(FoodCollectionBean(isShowCamera = true))
|
||||||
binding.rvFoodCollection.let {
|
binding.rvFoodCollection.let {
|
||||||
it.layoutManager = GridLayoutManager(this, 3, GridLayoutManager.VERTICAL, false)
|
it.layoutManager = GridLayoutManager(this, 3, GridLayoutManager.VERTICAL, false)
|
||||||
@@ -92,11 +139,7 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
binding.btnFoodSearch.setOnClickListener {
|
binding.btnFoodSearch.setOnClickListener {
|
||||||
bottomSheetDialog = CustomBottomSheetDialog.newInstance(viewModel) {
|
searchInfo()
|
||||||
selectedFoodName = it.foodName
|
|
||||||
binding.tvSelectedFood.text = selectedFoodName
|
|
||||||
}
|
|
||||||
bottomSheetDialog!!.show(supportFragmentManager, "CustomBottomSheetDialog")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.btnSave.setOnClickListener {
|
binding.btnSave.setOnClickListener {
|
||||||
@@ -110,31 +153,31 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
|
|||||||
}
|
}
|
||||||
vectorThread()
|
vectorThread()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun takePhoto() {
|
binding.editFoodName.setOnEditorActionListener { v, actionId, event ->
|
||||||
StorageUtils.requestCameraPermission(this) {
|
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
|
||||||
val outputFile = File(this.filesDir, "IMG_${System.currentTimeMillis()}.jpg")
|
searchInfo()
|
||||||
outputFileUri =
|
val imm =
|
||||||
FileProvider.getUriForFile(this, "${this.packageName}.fileprovider", outputFile)
|
v.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
takePictureLauncher.launch(outputFileUri!!)
|
imm.hideSoftInputFromWindow(v.windowToken, 0)
|
||||||
}
|
true
|
||||||
}
|
|
||||||
|
|
||||||
private var outputFileUri: Uri? = null
|
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
|
||||||
private val takePictureLauncher =
|
|
||||||
registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
|
|
||||||
if (success) {
|
|
||||||
//Loading.show(this)
|
|
||||||
if (list.size < 6) {
|
|
||||||
list.add(list.size - 1, FoodCollectionBean(imageUri = outputFileUri))
|
|
||||||
} else {
|
} else {
|
||||||
list[list.size - 1] = FoodCollectionBean(imageUri = outputFileUri)
|
false
|
||||||
}
|
}
|
||||||
adapter.notifyDataSetChanged()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foodAdapter = createAdapter()
|
||||||
|
binding.recyclerview.layoutManager = GridLayoutManager(context, 2)
|
||||||
|
// 添加间距装饰(12dp)
|
||||||
|
binding.recyclerview.addItemDecoration(
|
||||||
|
GridSpacingItemDecoration(
|
||||||
|
spanCount = 2,
|
||||||
|
spacing = dpToPx(30),
|
||||||
|
includeEdge = false // 包含边缘间距
|
||||||
|
)
|
||||||
|
)
|
||||||
|
binding.recyclerview.adapter = foodAdapter
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun vectorThread() {
|
private fun vectorThread() {
|
||||||
@@ -142,6 +185,8 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
|
|||||||
showWaitingDialog("加载中……")
|
showWaitingDialog("加载中……")
|
||||||
Thread {
|
Thread {
|
||||||
list.forEachIndexed { index, it ->
|
list.forEachIndexed { index, it ->
|
||||||
|
|
||||||
|
if (!it.isShowCamera)
|
||||||
image2VectorTask(it.imageUri!!, index)
|
image2VectorTask(it.imageUri!!, index)
|
||||||
}
|
}
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
@@ -169,17 +214,76 @@ class FoodCollectionActivity : BaseActivity<ActivityFoodCollectionBinding>() {
|
|||||||
NO_STD_RGB // [0.229, 0.224, 0.225] TORCHVISION_NORM_STD_RGB
|
NO_STD_RGB // [0.229, 0.224, 0.225] TORCHVISION_NORM_STD_RGB
|
||||||
)
|
)
|
||||||
val outputTensor = module!!.forward(IValue.from(inputTensor)).toTensor()
|
val outputTensor = module!!.forward(IValue.from(inputTensor)).toTensor()
|
||||||
|
|
||||||
|
val base64Str = FloatBase64Utils.floatArrayToBase64(outputTensor.dataAsFloatArray)
|
||||||
|
Timber.tag("mzf1").e(base64Str)
|
||||||
val imageVector = outputTensor.dataAsFloatArray
|
val imageVector = outputTensor.dataAsFloatArray
|
||||||
box?.put(Food(name = selectedFoodName, foodIdx = 0, foodVector = imageVector))
|
box?.put(Food(name = selectedFoodName, foodIdx = 0, foodVector = imageVector))
|
||||||
list[position].let {
|
list[position].let {
|
||||||
it.imageVector = imageVector
|
it.imageVector = imageVector
|
||||||
it.isFinish = true
|
it.isFinish = true
|
||||||
|
|
||||||
|
Timber
|
||||||
|
viewModel.postImageData(
|
||||||
|
context,
|
||||||
|
foodId = selectedFoodId.toString(),
|
||||||
|
foodName = selectedFoodName.toString(),
|
||||||
|
foodVector = base64Str,
|
||||||
|
uri = imageUri
|
||||||
|
)
|
||||||
}
|
}
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
adapter.notifyItemChanged(position)
|
adapter.notifyItemChanged(position)
|
||||||
}
|
}
|
||||||
|
Timber.tag("registerDataChange").e("box.all.size=${box?.all?.size}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun searchInfo() {
|
||||||
|
debouncer.debounce {
|
||||||
|
viewModel.searchByFoodName(binding.editFoodName.text.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun registerDataChange() {
|
||||||
|
super.registerDataChange()
|
||||||
|
lifecycleScope.launch {
|
||||||
|
viewModel.searchFoodInfoList.collect {
|
||||||
|
foodAdapter.updateData(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private var checkedItem: FoodInfo? = null
|
||||||
|
private fun createAdapter(): GenericItemAdapter<FoodInfo, ItemSearchFoodInfoBinding> {
|
||||||
|
return GenericItemAdapter(
|
||||||
|
items = emptyList(),
|
||||||
|
bindingInflater = ItemSearchFoodInfoBinding::inflate,
|
||||||
|
bindCallback = { item, position ->
|
||||||
|
this.tvName.text = item.foodName
|
||||||
|
if (item.id != checkedItem?.id) {
|
||||||
|
this.tvName.typeface = Typeface.defaultFromStyle(Typeface.NORMAL)
|
||||||
|
this.tvName.setTextColor(resources.getColor(R.color.search_normal))
|
||||||
|
this.llRoot.setBackgroundResource(R.drawable.grid_search_item_normal)
|
||||||
|
} else {
|
||||||
|
this.tvName.typeface = Typeface.defaultFromStyle(Typeface.BOLD)
|
||||||
|
this.tvName.setTextColor(resources.getColor(R.color.search_checked))
|
||||||
|
this.llRoot.setBackgroundResource(R.drawable.grid_search_item_checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.llRoot.setOnClickListener {
|
||||||
|
Timber.d("itemClick ${item.foodName}, position = $position")
|
||||||
|
checkedItem = item
|
||||||
|
// viewModel.updateCurrentItem(item)
|
||||||
|
// itemClickCallback(item)
|
||||||
|
selectedFoodName = item.foodName
|
||||||
|
selectedFoodId = item.id
|
||||||
|
|
||||||
|
foodAdapter.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.sw.dualscreen.activity
|
package com.sw.dualscreen.activity
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.graphics.Outline
|
import android.graphics.Outline
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.hardware.display.DisplayManager
|
import android.hardware.display.DisplayManager
|
||||||
@@ -43,6 +44,7 @@ import com.sw.dualscreen.utils.ImageUtil
|
|||||||
import com.sw.dualscreen.view.CustomBottomSheetDialog
|
import com.sw.dualscreen.view.CustomBottomSheetDialog
|
||||||
import com.sw.dualscreen.viewmodel.BaseViewModel
|
import com.sw.dualscreen.viewmodel.BaseViewModel
|
||||||
import com.sw.dualscreen.viewmodel.UserViewModel
|
import com.sw.dualscreen.viewmodel.UserViewModel
|
||||||
|
import com.sw.plate.utils.LightManager
|
||||||
import com.sw.plate.utils.ToastUtils
|
import com.sw.plate.utils.ToastUtils
|
||||||
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
|
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -93,6 +95,11 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
|
|
||||||
private fun initView() {
|
private fun initView() {
|
||||||
viewModel.activeEngine()
|
viewModel.activeEngine()
|
||||||
|
binding.foodDisplayLayout.setOnClickListener {
|
||||||
|
val intent = Intent(this, FoodCollectionActivity::class.java)
|
||||||
|
startActivity(intent)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
binding.previewView.outlineProvider = object : ViewOutlineProvider() {
|
binding.previewView.outlineProvider = object : ViewOutlineProvider() {
|
||||||
override fun getOutline(view: View, outline: Outline) {
|
override fun getOutline(view: View, outline: Outline) {
|
||||||
outline.setRoundRect(0, 0, view.width, view.height, 12f.dp)
|
outline.setRoundRect(0, 0, view.width, view.height, 12f.dp)
|
||||||
@@ -202,6 +209,8 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
|
|
||||||
if (weight - lastWeight > 0.1 && presentation?.mealPickupMode == 0) { // 大于100g
|
if (weight - lastWeight > 0.1 && presentation?.mealPickupMode == 0) { // 大于100g
|
||||||
debouncer.debounce {
|
debouncer.debounce {
|
||||||
|
LightManager.openRedLight()
|
||||||
|
LightManager.openGreenLight()
|
||||||
takePhoto { photoUri ->
|
takePhoto { photoUri ->
|
||||||
lastPhotoUri = photoUri
|
lastPhotoUri = photoUri
|
||||||
Timber.d("registerDataChange photoUri = ${photoUri.path}")
|
Timber.d("registerDataChange photoUri = ${photoUri.path}")
|
||||||
@@ -211,7 +220,11 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
|||||||
Timber.d("registerDataChange photoUri 拿到bitmap")
|
Timber.d("registerDataChange photoUri 拿到bitmap")
|
||||||
val bmp = BitmapCropper.cropCenter(bitmap, 1300, 900)
|
val bmp = BitmapCropper.cropCenter(bitmap, 1300, 900)
|
||||||
Timber.d("registerDataChange photoUri bitmap裁剪完成")
|
Timber.d("registerDataChange photoUri bitmap裁剪完成")
|
||||||
val file = BitmapSaver.saveToAppFilesDir(bmp, this, "IMG_CROP_${System.currentTimeMillis()}.jpg")
|
val file = BitmapSaver.saveToAppFilesDir(
|
||||||
|
bmp,
|
||||||
|
this,
|
||||||
|
"IMG_CROP_${System.currentTimeMillis()}.jpg"
|
||||||
|
)
|
||||||
Timber.d("registerDataChange photoUri bitmap保存文件路径:${file?.absolutePath}")
|
Timber.d("registerDataChange photoUri bitmap保存文件路径:${file?.absolutePath}")
|
||||||
val resultList = FoodModule.queryFood(bmp)
|
val resultList = FoodModule.queryFood(bmp)
|
||||||
Timber.d("registerDataChange photoUri 拿到识别数据")
|
Timber.d("registerDataChange photoUri 拿到识别数据")
|
||||||
|
|||||||
@@ -8,10 +8,15 @@ import com.sw.dualscreen.model.response.EquipmentInfo
|
|||||||
import com.sw.dualscreen.model.response.FoodInfo
|
import com.sw.dualscreen.model.response.FoodInfo
|
||||||
import com.sw.dualscreen.model.response.UserFaceInfo
|
import com.sw.dualscreen.model.response.UserFaceInfo
|
||||||
import com.sw.dualscreen.model.response.UserNutritionData
|
import com.sw.dualscreen.model.response.UserNutritionData
|
||||||
|
import okhttp3.MultipartBody
|
||||||
|
import okhttp3.RequestBody
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.Header
|
import retrofit2.http.Header
|
||||||
|
import retrofit2.http.Multipart
|
||||||
import retrofit2.http.POST
|
import retrofit2.http.POST
|
||||||
|
import retrofit2.http.Part
|
||||||
|
import retrofit2.http.PartMap
|
||||||
import retrofit2.http.Query
|
import retrofit2.http.Query
|
||||||
import retrofit2.http.Url
|
import retrofit2.http.Url
|
||||||
|
|
||||||
@@ -109,4 +114,17 @@ interface ApiService {
|
|||||||
@Query("restId") restId: String,
|
@Query("restId") restId: String,
|
||||||
@Query("foodNames") foodName: String,
|
@Query("foodNames") foodName: String,
|
||||||
): ApiResponse<List<FoodInfo>>
|
): ApiResponse<List<FoodInfo>>
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交采集图片数据
|
||||||
|
*/
|
||||||
|
@Multipart
|
||||||
|
@POST
|
||||||
|
suspend fun postImageData(
|
||||||
|
@Url url: String = "http://192.168.1.201:14801/terminal/neglect/dishCollectionVectorData/add",
|
||||||
|
@PartMap params: Map<String, @JvmSuppressWildcards RequestBody>,
|
||||||
|
@Part image: MultipartBody.Part?
|
||||||
|
): ApiResponse<String>
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -32,9 +32,9 @@ object FoodModule {
|
|||||||
// if (box.all.isNotEmpty()) {
|
// if (box.all.isNotEmpty()) {
|
||||||
// box.removeAll()
|
// box.removeAll()
|
||||||
// }
|
// }
|
||||||
if (box.all.isEmpty()) {
|
// if (box.all.isEmpty()) {
|
||||||
initFoodData(context)
|
// initFoodData(context)
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun queryFood(bitmap: Bitmap, queryCount:Int = 15): List<String> {
|
fun queryFood(bitmap: Bitmap, queryCount:Int = 15): List<String> {
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ class SecondaryScreenPresentation(
|
|||||||
Timber.d("step1")
|
Timber.d("step1")
|
||||||
currentStep = 1
|
currentStep = 1
|
||||||
LightManager.closeGreenLight()
|
LightManager.closeGreenLight()
|
||||||
LightManager.openRedLight()
|
LightManager.closeRedLight()
|
||||||
//回复上一次的设置
|
//回复上一次的设置
|
||||||
// lastWeight = 0.0
|
// lastWeight = 0.0
|
||||||
dinnerTypeInfo = null
|
dinnerTypeInfo = null
|
||||||
@@ -188,7 +188,7 @@ class SecondaryScreenPresentation(
|
|||||||
fun step2(foodInfo: FoodInfo) {
|
fun step2(foodInfo: FoodInfo) {
|
||||||
Timber.d("step2")
|
Timber.d("step2")
|
||||||
currentStep = 2
|
currentStep = 2
|
||||||
LightManager.openRedLight()
|
LightManager.closeRedLight()
|
||||||
LightManager.openGreenLight()
|
LightManager.openGreenLight()
|
||||||
dinnerTypeInfo = null
|
dinnerTypeInfo = null
|
||||||
userNutritionData = null
|
userNutritionData = null
|
||||||
@@ -231,6 +231,7 @@ class SecondaryScreenPresentation(
|
|||||||
fun step3() {
|
fun step3() {
|
||||||
Timber.d("step3")
|
Timber.d("step3")
|
||||||
LightManager.closeRedLight()
|
LightManager.closeRedLight()
|
||||||
|
LightManager.openGreenLight()
|
||||||
currentStep = 3
|
currentStep = 3
|
||||||
stepChangeCallback(currentStep)
|
stepChangeCallback(currentStep)
|
||||||
if (currentFood == null) {
|
if (currentFood == null) {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.sw.dualscreen.repository
|
package com.sw.dualscreen.repository
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
import com.sw.dualscreen.GlobalData
|
import com.sw.dualscreen.GlobalData
|
||||||
import com.sw.dualscreen.model.request.UserNutritionParam
|
import com.sw.dualscreen.model.request.UserNutritionParam
|
||||||
import com.sw.dualscreen.model.response.ApiResponse
|
import com.sw.dualscreen.model.response.ApiResponse
|
||||||
@@ -9,6 +11,9 @@ import com.sw.dualscreen.model.response.FoodInfo
|
|||||||
import com.sw.dualscreen.model.response.UserFaceInfo
|
import com.sw.dualscreen.model.response.UserFaceInfo
|
||||||
import com.sw.dualscreen.model.response.UserNutritionData
|
import com.sw.dualscreen.model.response.UserNutritionData
|
||||||
import com.sw.dualscreen.network.api.ApiService
|
import com.sw.dualscreen.network.api.ApiService
|
||||||
|
import com.sw.dualscreen.utils.ImageUtil
|
||||||
|
import okhttp3.RequestBody
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 远程数据处理
|
* 远程数据处理
|
||||||
@@ -115,4 +120,27 @@ class RemoteRepository constructor(
|
|||||||
): ApiResponse<List<FoodInfo>> {
|
): ApiResponse<List<FoodInfo>> {
|
||||||
return safeApiCall { apiService.getFoodInfo(restId = restId, foodName = foodName) }
|
return safeApiCall { apiService.getFoodInfo(restId = restId, foodName = foodName) }
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 获取菜品信息
|
||||||
|
* @param restId 从device服务获取的canteenId字段
|
||||||
|
* @param foodName 菜品名称,多个使用逗号拼接
|
||||||
|
*/
|
||||||
|
suspend fun postImageData(
|
||||||
|
context: Context,
|
||||||
|
restId: String = GlobalData.restId,
|
||||||
|
foodId: String,
|
||||||
|
foodName: String,
|
||||||
|
foodVector: String,
|
||||||
|
uri: Uri,
|
||||||
|
): ApiResponse<String> {
|
||||||
|
|
||||||
|
val params = HashMap<String, RequestBody>()
|
||||||
|
params["placeId"] = restId.toRequestBody()
|
||||||
|
params["foodId"] = foodId.toRequestBody()
|
||||||
|
params["foodName"] = foodName.toRequestBody()
|
||||||
|
params["foodVector"] = foodVector.toRequestBody()
|
||||||
|
val imagePart = ImageUtil.uriToMultipart(context, uri, "foodPic")
|
||||||
|
|
||||||
|
return safeApiCall { apiService.postImageData(params = params, image = imagePart) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package com.sw.dualscreen.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import android.provider.OpenableColumns
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.result.ActivityResultCaller
|
||||||
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简洁相机工具类
|
||||||
|
* 调用系统相机拍照并返回照片路径
|
||||||
|
*/
|
||||||
|
class CameraHelper(
|
||||||
|
private val context: Context,
|
||||||
|
caller: ActivityResultCaller,
|
||||||
|
private val authority: String,
|
||||||
|
private val onResult: (uri: Uri, filePath: String?) -> Unit
|
||||||
|
) {
|
||||||
|
|
||||||
|
private lateinit var imageUri: Uri
|
||||||
|
private val takePictureLauncher: ActivityResultLauncher<Uri>
|
||||||
|
|
||||||
|
init {
|
||||||
|
takePictureLauncher =
|
||||||
|
caller.registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
|
||||||
|
if (success) {
|
||||||
|
val path = getRealPathFromUri(context, imageUri)
|
||||||
|
onResult(imageUri, path)
|
||||||
|
} else {
|
||||||
|
Toast.makeText(context, "拍照取消", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 打开系统相机 */
|
||||||
|
fun openCamera() {
|
||||||
|
// 1. 创建存储图片的文件
|
||||||
|
val photoFile = File(context.externalCacheDir, "IMG_${System.currentTimeMillis()}.jpg")
|
||||||
|
photoFile.parentFile?.mkdirs() // 确保目录存在
|
||||||
|
|
||||||
|
// 2. 生成 content:// Uri
|
||||||
|
imageUri = FileProvider.getUriForFile(context, authority, photoFile)
|
||||||
|
|
||||||
|
// 3. 启动相机
|
||||||
|
takePictureLauncher.launch(imageUri)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 可选:将 content:// 转为实际路径 */
|
||||||
|
private fun getRealPathFromUri(context: Context, uri: Uri): String? {
|
||||||
|
var filePath: String? = null
|
||||||
|
context.contentResolver.query(uri, null, null, null, null)?.use { cursor ->
|
||||||
|
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||||
|
cursor.moveToFirst()
|
||||||
|
val fileName = cursor.getString(nameIndex)
|
||||||
|
val file = File(context.cacheDir, fileName)
|
||||||
|
context.contentResolver.openInputStream(uri)?.use { input ->
|
||||||
|
FileOutputStream(file).use { output -> input.copyTo(output) }
|
||||||
|
}
|
||||||
|
filePath = file.absolutePath
|
||||||
|
}
|
||||||
|
return filePath
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.example.utils
|
||||||
|
|
||||||
|
import android.util.Base64
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.ByteOrder
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工具类:float[] 与 Base64 字符串互转
|
||||||
|
* 无需外部依赖,适用于 Android 原生项目。
|
||||||
|
*/
|
||||||
|
object FloatBase64Utils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* float数组 → Base64字符串
|
||||||
|
* @param values FloatArray
|
||||||
|
* @return Base64字符串(无换行)
|
||||||
|
*/
|
||||||
|
fun floatArrayToBase64(values: FloatArray): String {
|
||||||
|
if (values.isEmpty()) return ""
|
||||||
|
|
||||||
|
val buffer = ByteBuffer.allocate(values.size * 4)
|
||||||
|
buffer.order(ByteOrder.BIG_ENDIAN) // 默认大端序,可改 LITTLE_ENDIAN
|
||||||
|
values.forEach { buffer.putFloat(it) }
|
||||||
|
|
||||||
|
val bytes = buffer.array()
|
||||||
|
return Base64.encodeToString(bytes, Base64.NO_WRAP)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base64字符串 → float数组
|
||||||
|
* @param base64 Base64字符串
|
||||||
|
* @return FloatArray
|
||||||
|
*/
|
||||||
|
fun base64ToFloatArray(base64: String?): FloatArray {
|
||||||
|
if (base64.isNullOrEmpty()) return FloatArray(0)
|
||||||
|
|
||||||
|
val bytes = Base64.decode(base64, Base64.NO_WRAP)
|
||||||
|
val buffer = ByteBuffer.wrap(bytes)
|
||||||
|
buffer.order(ByteOrder.BIG_ENDIAN)
|
||||||
|
|
||||||
|
val result = FloatArray(bytes.size / 4)
|
||||||
|
for (i in result.indices) {
|
||||||
|
result[i] = buffer.getFloat()
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,12 @@ import android.content.Context
|
|||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
|
import okhttp3.MultipartBody
|
||||||
|
import okhttp3.RequestBody
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
object ImageUtil {
|
object ImageUtil {
|
||||||
|
|
||||||
@@ -18,4 +24,24 @@ object ImageUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun uriToMultipart(context: Context, uri: Uri, partName: String): MultipartBody.Part? {
|
||||||
|
try {
|
||||||
|
val contentResolver = context.contentResolver
|
||||||
|
val inputStream: InputStream = contentResolver.openInputStream(uri) ?: return null
|
||||||
|
|
||||||
|
// 临时文件(避免直接访问SAF文件)
|
||||||
|
val tempFile = File.createTempFile("upload_", ".jpg", context.cacheDir)
|
||||||
|
|
||||||
|
val outputStream = FileOutputStream(tempFile)
|
||||||
|
inputStream.copyTo(outputStream)
|
||||||
|
inputStream.close()
|
||||||
|
outputStream.close()
|
||||||
|
|
||||||
|
val requestFile = RequestBody.create("image/*".toMediaTypeOrNull(), tempFile)
|
||||||
|
return MultipartBody.Part.createFormData(partName, tempFile.name, requestFile)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -52,7 +52,10 @@ abstract class BaseViewModel() : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected open fun parseResponse(response: ApiResponse<*>): Boolean {
|
protected open fun parseResponse(response: ApiResponse<*>): Boolean {
|
||||||
if (response.isSuccess()) {
|
if (response.isSuccess()
|
||||||
|
|| response.code == 200
|
||||||
|
|| response.code == 0
|
||||||
|
) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
Timber.d("msg = ${response.message}, code = ${response.code}")
|
Timber.d("msg = ${response.message}, code = ${response.code}")
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.sw.dualscreen.viewmodel
|
package com.sw.dualscreen.viewmodel
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.arcsoft.face.ErrorInfo
|
import com.arcsoft.face.ErrorInfo
|
||||||
import com.sw.dualscreen.GlobalData
|
import com.sw.dualscreen.GlobalData
|
||||||
@@ -217,4 +219,23 @@ class UserViewModel : BaseViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun postImageData(
|
||||||
|
context: Context,
|
||||||
|
foodId: String,
|
||||||
|
foodName: String,
|
||||||
|
foodVector: String,
|
||||||
|
uri: Uri
|
||||||
|
) {
|
||||||
|
Timber.d("postImageData")
|
||||||
|
launchWithLoading {
|
||||||
|
val response = repository.postImageData(
|
||||||
|
context, foodId = foodId, foodName = foodName, foodVector = foodVector,
|
||||||
|
uri = uri
|
||||||
|
)
|
||||||
|
if (parseResponse(response)) {
|
||||||
|
Timber.tag("mzf").d("code=${response.code}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -8,12 +8,13 @@
|
|||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="50dp">
|
android:layout_height="68dp"
|
||||||
|
android:layout_marginTop="24dp">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/ivBack"
|
android:id="@+id/ivBack"
|
||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="50dp"
|
android:layout_height="68dp"
|
||||||
android:padding="10dp"
|
android:padding="10dp"
|
||||||
android:src="@drawable/ic_back" />
|
android:src="@drawable/ic_back" />
|
||||||
|
|
||||||
@@ -23,7 +24,7 @@
|
|||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:text="菜品采集"
|
android:text="菜品采集"
|
||||||
android:textColor="@color/black"
|
android:textColor="@color/black"
|
||||||
android:textSize="18sp" />
|
android:textSize="26sp" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
@@ -31,6 +32,8 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginHorizontal="3dp"
|
android:layout_marginHorizontal="3dp"
|
||||||
|
android:layout_marginStart="24dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||||
app:spanCount="3"
|
app:spanCount="3"
|
||||||
tools:itemCount="6"
|
tools:itemCount="6"
|
||||||
@@ -39,21 +42,24 @@
|
|||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="20dp"
|
android:layout_marginLeft="24dp"
|
||||||
|
android:layout_marginTop="48dp"
|
||||||
|
android:layout_marginRight="24dp"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<TextView
|
<EditText
|
||||||
android:id="@+id/tvSelectedFood"
|
android:id="@+id/editFoodName"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginHorizontal="6dp"
|
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/bg_gray"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
|
android:hint="输入菜品名称"
|
||||||
|
android:imeOptions="actionSearch"
|
||||||
android:paddingStart="5dp"
|
android:paddingStart="5dp"
|
||||||
android:paddingEnd="5dp"
|
android:paddingEnd="5dp"
|
||||||
android:hint="选择菜品名称"
|
android:singleLine="true"
|
||||||
android:background="@drawable/bg_gray"
|
|
||||||
android:textColor="@color/black"
|
android:textColor="@color/black"
|
||||||
android:textSize="18sp" />
|
android:textSize="18sp" />
|
||||||
|
|
||||||
@@ -61,24 +67,28 @@
|
|||||||
android:id="@+id/btnFoodSearch"
|
android:id="@+id/btnFoodSearch"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="6dp"
|
android:layout_marginLeft="12dp"
|
||||||
android:text="菜品检索"
|
android:text="菜品检索"
|
||||||
android:textColor="#ffffff"
|
android:textColor="#ffffff"
|
||||||
android:textSize="16sp" />
|
android:textSize="16sp" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<Space
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerview"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginStart="24dp"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
android:layout_weight="1" />
|
android:layout_weight="1" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/btnSave"
|
android:id="@+id/btnSave"
|
||||||
android:layout_width="260dp"
|
android:layout_width="260dp"
|
||||||
android:layout_height="60dp"
|
android:layout_height="60dp"
|
||||||
android:text="保存"
|
|
||||||
android:layout_marginVertical="20dp"
|
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginVertical="20dp"
|
||||||
|
android:text="保存"
|
||||||
android:textColor="#ffffff"
|
android:textColor="#ffffff"
|
||||||
android:textSize="20sp" />
|
android:textSize="20sp" />
|
||||||
|
|
||||||
|
|||||||
@@ -24,9 +24,9 @@
|
|||||||
|
|
||||||
<Spinner
|
<Spinner
|
||||||
android:id="@+id/spinner"
|
android:id="@+id/spinner"
|
||||||
|
style="@style/customSpinnerStyle"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
style="@style/customSpinnerStyle"
|
|
||||||
android:layout_gravity="end"
|
android:layout_gravity="end"
|
||||||
android:padding="0dp"
|
android:padding="0dp"
|
||||||
android:popupBackground="@drawable/shape_for_custom_spinner" />
|
android:popupBackground="@drawable/shape_for_custom_spinner" />
|
||||||
@@ -43,6 +43,7 @@
|
|||||||
android:text="羊肉臊子荞面饸饹" />
|
android:text="羊肉臊子荞面饸饹" />
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
|
android:id="@+id/foodDisplayLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="531dp"
|
android:layout_height="531dp"
|
||||||
android:layout_marginHorizontal="60dp"
|
android:layout_marginHorizontal="60dp"
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="110dp"
|
android:layout_height="200dp"
|
||||||
android:background="@drawable/bg_gray"
|
|
||||||
android:layout_margin="3dp"
|
android:layout_margin="3dp"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
android:background="@drawable/bg_gray">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/imageView"
|
android:id="@+id/imageView"
|
||||||
@@ -15,8 +15,8 @@
|
|||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/ivDelete"
|
android:id="@+id/ivDelete"
|
||||||
android:layout_width="40dp"
|
android:layout_width="48dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="48dp"
|
||||||
android:layout_gravity="end|top"
|
android:layout_gravity="end|top"
|
||||||
android:paddingTop="10dp"
|
android:paddingTop="10dp"
|
||||||
android:paddingBottom="10dp"
|
android:paddingBottom="10dp"
|
||||||
@@ -24,9 +24,9 @@
|
|||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/ivFinish"
|
android:id="@+id/ivFinish"
|
||||||
android:layout_width="30dp"
|
android:layout_width="48dp"
|
||||||
android:layout_height="30dp"
|
android:layout_height="48dp"
|
||||||
android:padding="5dp"
|
|
||||||
android:layout_gravity="end|bottom"
|
android:layout_gravity="end|bottom"
|
||||||
|
android:padding="5dp"
|
||||||
android:src="@drawable/ic_finish" />
|
android:src="@drawable/ic_finish" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
Reference in New Issue
Block a user