增加配置接口;调试人脸识别;验证手机号密码登录和人脸登录功能;

This commit is contained in:
马增飞
2025-12-29 18:41:48 +08:00
parent 6c28260069
commit 17d47a876f
21 changed files with 141 additions and 55 deletions
Generated
+1
View File
@@ -0,0 +1 @@
SmartPlateCabinet
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="17" />
</component>
</project>
@@ -1,5 +1,6 @@
package com.sw.platecabinet package com.sw.platecabinet
import android.util.Log
import com.sw.plate.App import com.sw.plate.App
import com.sw.plate.utils.AppUtil import com.sw.plate.utils.AppUtil
import com.sw.platecabinet.utils.CrashHandler import com.sw.platecabinet.utils.CrashHandler
@@ -24,9 +25,14 @@ class MyApp : App() {
* 初始化全局数据 * 初始化全局数据
*/ */
private fun initGlobalData() { private fun initGlobalData() {
var deviceId = AppUtil.getUDID(this)
deviceId = "be154831-3466-3ba2-a2ea-57652c919fed"
Log.d("MyApp", "initialize: deviceId=$deviceId")
GlobalData.deviceId = deviceId
GlobalData.appVersion = AppUtil.getAppVersionCode(this).toString() GlobalData.appVersion = AppUtil.getAppVersionCode(this).toString()
GlobalData.globalEquipmentCode = "202501171634" GlobalData.globalEquipmentCode = "202501171634"
GlobalData.appBaseUrl = "http://192.168.1.201:14801" // GlobalData.appBaseUrl = "http://192.168.1.201:14801"
} }
} }
@@ -200,10 +200,10 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
private fun disableSystemUICompletely() { private fun disableSystemUICompletely() {
Timber.d("disableSystemUICompletely") Timber.d("disableSystemUICompletely")
// 禁用系统手势(Android 10+ // 禁用系统手势(Android 10+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.insetsController?.systemBarsBehavior = // window.insetsController?.systemBarsBehavior =
WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE // WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
} // }
// 全屏+隐藏导航栏(所有版本通用) // 全屏+隐藏导航栏(所有版本通用)
@@ -330,10 +330,12 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
*/ */
open fun handleLoginSuccess(equipmentUserInfo: EquipmentUserInfo, isAdmin: Boolean) { open fun handleLoginSuccess(equipmentUserInfo: EquipmentUserInfo, isAdmin: Boolean) {
Timber.d("handleLoginSuccess isAdmin = $isAdmin") Timber.d("handleLoginSuccess isAdmin = $isAdmin")
equipmentUserInfo.isIntercept = false
val cardBalance = equipmentUserInfo.cardBalance?:0.toDouble() val cardBalance = equipmentUserInfo.cardBalance?:0.toDouble()
val balanceIsNotEnough = cardBalance <= 0.toDouble() val balanceIsNotEnough = cardBalance <= 0.toDouble()
if (balanceIsNotEnough && equipmentUserInfo.showBalanceNotEnoughDialog) { if (balanceIsNotEnough && equipmentUserInfo.showBalanceNotEnoughDialog) {
//提示余额不足弹窗 //提示余额不足弹窗
equipmentUserInfo.isIntercept = true
BalanceNotEnoughDialog(this).show() BalanceNotEnoughDialog(this).show()
return return
} }
@@ -1,20 +1,22 @@
package com.sw.platecabinet.activity package com.sw.platecabinet.activity
import android.content.Intent import android.content.Intent
import android.util.Log
import androidx.activity.viewModels
import com.sw.plate.utils.AppUtil import com.sw.plate.utils.AppUtil
import com.sw.plate.utils.ToastUtils
import com.sw.platecabinet.GlobalData import com.sw.platecabinet.GlobalData
import com.sw.platecabinet.databinding.ActivityDeviceInitBinding import com.sw.platecabinet.databinding.ActivityDeviceInitBinding
import com.sw.platecabinet.databinding.ItemTitleTimeBinding import com.sw.platecabinet.databinding.ItemTitleTimeBinding
import com.sw.platecabinet.ext.gone import com.sw.platecabinet.ext.gone
import com.sw.platecabinet.ext.invisible import com.sw.platecabinet.ext.invisible
import com.sw.platecabinet.viewmodel.SettingViewModel
/** /**
* 设备初始化界面 * 设备初始化界面
*/ */
class DeviceInitActivity : BaseActivity<ActivityDeviceInitBinding>() { class DeviceInitActivity : BaseActivity<ActivityDeviceInitBinding>() {
// private val deviceViewModel by viewModels<DeviceViewModel>()
override fun inflateViewBinding(): ActivityDeviceInitBinding { override fun inflateViewBinding(): ActivityDeviceInitBinding {
return ActivityDeviceInitBinding.inflate(layoutInflater) return ActivityDeviceInitBinding.inflate(layoutInflater)
} }
@@ -24,9 +26,6 @@ class DeviceInitActivity : BaseActivity<ActivityDeviceInitBinding>() {
} }
override fun initialize() { override fun initialize() {
var deviceId = AppUtil.getUDID(this)
deviceId = "be154831-3466-3ba2-a2ea-57652c919fed"
GlobalData.deviceId = deviceId
// val isSuccess = deviceViewModel.checkEquipmentInfo() // val isSuccess = deviceViewModel.checkEquipmentInfo()
// if (isSuccess) { // if (isSuccess) {
// goLoginActivity() // goLoginActivity()
@@ -41,11 +40,29 @@ class DeviceInitActivity : BaseActivity<ActivityDeviceInitBinding>() {
// binding.btnInit.setOnClickListener { // binding.btnInit.setOnClickListener {
// deviceViewModel.getDeviceToken() // deviceViewModel.getDeviceToken()
// } // }
binding.btnInit.gone() // binding.btnInit.gone()
binding.ivQrCode.invisible() // binding.ivQrCode.invisible()
binding.root.postDelayed({ // binding.root.postDelayed({
goLoginActivity() // goLoginActivity()
}, 1000) // }, 1000)
showWaitingDialog("加载中……")
viewModel.getDeviceConfig { deviceConfig ->
runOnUiThread {
if (deviceConfig == null) {
hideWaitingDialog()
ToastUtils.showToast("获取设备配置数据失败")
return@runOnUiThread
}
GlobalData.appId = deviceConfig.arcsoftAppId ?: ""
GlobalData.sdkKey = deviceConfig.arcsoftSdkKey ?: ""
GlobalData.activeKey = deviceConfig.arcsoftActiveKey ?: ""
binding.root.postDelayed({
hideWaitingDialog()
goLoginActivity()
}, 500)
}
}
// goLoginActivity()
} }
override fun registerDataChange() { override fun registerDataChange() {
@@ -81,7 +81,7 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
openRectInfoDraw = true openRectInfoDraw = true
recognizeViewModel.setDrawRectInfoTextValue(true) recognizeViewModel.setDrawRectInfoTextValue(true)
//viewModel.generateToken() //viewModel.generateToken()
viewModel.activeEngine() // viewModel.activeEngine()
viewModel.getUserFaceCache() viewModel.getUserFaceCache()
binding.llToPwd.setOnClickListener { binding.llToPwd.setOnClickListener {
@@ -173,7 +173,7 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
recognizeViewModel.recognizeUserId.observe(this, Observer { userId: String? -> recognizeViewModel.recognizeUserId.observe(this, Observer { userId: String? ->
Timber.i("recognizeUserId observe userId = $userId") Timber.i("recognizeUserId observe userId = $userId")
viewModel.getUserInfoById(memberId = userId?.toIntOrNull()) viewModel.getUserInfoById(memberId = userId)
}) })
recognizeViewModel.drawRectInfoText.observe(this, Observer { info -> recognizeViewModel.drawRectInfoText.observe(this, Observer { info ->
@@ -53,9 +53,9 @@ class LoginByPwdActivity : BaseActivity<ActivityLoginByPwdBinding>() {
super.handleLoginSuccess(equipmentUserInfo, isAdmin) super.handleLoginSuccess(equipmentUserInfo, isAdmin)
// val cardBalance = equipmentUserInfo.cardBalance?:0.toDouble() // val cardBalance = equipmentUserInfo.cardBalance?:0.toDouble()
// val balanceIsNotEnough = cardBalance <= 0.toDouble() // val balanceIsNotEnough = cardBalance <= 0.toDouble()
// if (balanceIsNotEnough) { if (equipmentUserInfo.isIntercept) {
// return return
// } }
LoginByFaceActivity.goInitActivity() LoginByFaceActivity.goInitActivity()
finish() finish()
} }
@@ -6,11 +6,22 @@ import com.sw.platecabinet.model.response.EquipmentUserInfo
* 错误信息 code == 200 成功 * 错误信息 code == 200 成功
*/ */
data class ErrorInfo( data class ErrorInfo(
val code: Int = 200, val code: String = "200",
val msg: String = "", val msg: String = "",
val equipmentUserInfo: EquipmentUserInfo? = null val equipmentUserInfo: EquipmentUserInfo? = null
) { ) {
fun isSuccess(): Boolean { fun isSuccess(): Boolean {
return code == 200 return code == "00000"
} }
} }
data class DeviceConfig(
var arcsoftAppId: String? = null,
var arcsoftSdkKey: String? = null,
var arcsoftActiveKey: String? = null
)
data class CodeMsg(
val code: String? = "",
val msg: String? = ""
)
@@ -14,7 +14,7 @@ data class BindParam(
/** /**
* 设备Id * 设备Id
*/ */
val equipmentId: Int? = null, val equipmentId: String? = null,
/** /**
* 设备编号 * 设备编号
*/ */
@@ -27,7 +27,7 @@ data class BindParam(
* *
* 会员Id * 会员Id
*/ */
val memberId: Int? = null, val memberId: String? = null,
/** /**
*餐盘编号 *餐盘编号
*/ */
@@ -17,7 +17,7 @@ data class LoginParam(
/** /**
* 会员信息 * 会员信息
*/ */
val memberId: Int? = null, val memberId: String? = null,
/** /**
* 密码 * 密码
*/ */
@@ -1,11 +1,12 @@
package com.sw.platecabinet.model.response package com.sw.platecabinet.model.response
data class ApiResponse<T>( data class ApiResponse<T>(
val code: Int, val code: String,
val success: Boolean? = false, // val success: Boolean? = false,
val msg: String? = "", val msg: String? = "",
val data: T? = null, val data: T? = null,
val result: T? = null, // val result: T? = null,
) { ) {
fun isSuccess(): Boolean = code == 200 // fun isSuccess(): Boolean = code == 200
fun isSuccess(): Boolean = code == "00000"
} }
@@ -14,12 +14,12 @@ data class EquipmentUserInfo(
/** /**
* 主键 * 主键
*/ */
val id: Int? = 0, val id: Long? = 0,
/** /**
* *
* 设备Id * 设备Id
*/ */
val equipmentId: Int? = 0, val equipmentId: String? = "",
/** /**
* 设备编号 * 设备编号
*/ */
@@ -31,7 +31,7 @@ data class EquipmentUserInfo(
/** /**
* 会员Id * 会员Id
*/ */
val memberId: Int? = 0, val memberId: String? = "",
/** /**
* 餐盘编号 * 餐盘编号
*/ */
@@ -62,7 +62,8 @@ data class EquipmentUserInfo(
/** /**
* 用于cardBalance后续拦截判断 * 用于cardBalance后续拦截判断
*/ */
var showBalanceNotEnoughDialog: Boolean = false var showBalanceNotEnoughDialog: Boolean = false,
var isIntercept: Boolean = false
) : Parcelable { ) : Parcelable {
/** /**
@@ -77,7 +77,7 @@ data class SearchResult(
// val rewardBalance: String? = "", // val rewardBalance: String? = "",
// @SerializedName("topUpBalance") // @SerializedName("topUpBalance")
// val topUpBalance: String? = "" // val topUpBalance: String? = ""
val id: Int? = 0, val id: String? = "",
val name: String? = "", val name: String? = "",
val phone: String? = "", val phone: String? = "",
) : Parcelable ) : Parcelable
@@ -1,5 +1,6 @@
package com.sw.platecabinet.network package com.sw.platecabinet.network
import com.sw.platecabinet.GlobalData
import com.sw.platecabinet.MyApp import com.sw.platecabinet.MyApp
import com.sw.platecabinet.network.api.ApiService import com.sw.platecabinet.network.api.ApiService
import com.sw.platecabinet.network.interceptor.RequestInterceptor import com.sw.platecabinet.network.interceptor.RequestInterceptor
@@ -11,10 +12,10 @@ import timber.log.Timber
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
object ApiClient { object ApiClient {
private const val BASE_URL = "https://vip.shuziweidao.com" //private const val BASE_URL = "https://vip.shuziweidao.com"
private const val DEVICE_BASE_URL = "http://device.shuziweidao.com:8889/" // private const val DEVICE_BASE_URL = "http://device.shuziweidao.com:8889/"
private var DEVICE_BASE_URL = GlobalData.appBaseUrl
// private const val BASE_URL = "http://192.168.1.8:9092" // private const val BASE_URL = "http://192.168.1.8:9092"
private const val TIME_OUT = 30L // 超时时间(秒) private const val TIME_OUT = 30L // 超时时间(秒)
val okHttpClient = OkHttpClient.Builder() val okHttpClient = OkHttpClient.Builder()
@@ -1,6 +1,7 @@
package com.sw.platecabinet.network.api package com.sw.platecabinet.network.api
import com.sw.platecabinet.GlobalData import com.sw.platecabinet.GlobalData
import com.sw.platecabinet.model.DeviceConfig
import com.sw.platecabinet.model.request.BindParam import com.sw.platecabinet.model.request.BindParam
import com.sw.platecabinet.model.request.EquipmentParam import com.sw.platecabinet.model.request.EquipmentParam
import com.sw.platecabinet.model.request.LoginParam import com.sw.platecabinet.model.request.LoginParam
@@ -119,7 +120,7 @@ interface ApiService {
@POST @POST
suspend fun plateUnbind( suspend fun plateUnbind(
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/sideboard/app/plateUnbind", @Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/sideboard/app/plateUnbind",
@Field("id") id: Int @Field("id") id: Long
): ApiResponse<Any?> ): ApiResponse<Any?>
/** /**
@@ -167,6 +168,19 @@ interface ApiService {
/** /**
* 获取人脸增量数据 * 获取人脸增量数据
*/ */
@POST("/terminal/neglect/common/app/faceFeature/increment/list") @POST
suspend fun getFaceIncrementList(@Body param:Map<String, Long>): ApiResponse<List<UserFaceModel>?> suspend fun getFaceIncrementList(
@Url url:String = "${GlobalData.appBaseUrl}/terminal/neglect/common/app/faceFeature/increment/list",
@Body param:Map<String, Long>
): ApiResponse<List<UserFaceModel>?>
/**
* 获取设备配置数据
*/
@GET
suspend fun getDeviceConfig(
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/common/app/getYxEquipmentByEquipmentCode"
): ApiResponse<DeviceConfig?>
} }
@@ -17,7 +17,7 @@ class RequestInterceptor : Interceptor {
.header("Content-Type", "application/json") .header("Content-Type", "application/json")
.header("Accept", "application/json") .header("Accept", "application/json")
// .header("Authorization", "Bearer ${getToken()}") // .header("Authorization", "Bearer ${getToken()}")
.header("Authorization", getToken()) // .header("Authorization", getToken())
.header("X-Access-Token", "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjYW50ZWVuSWQiOiJiZTE1NDgzMS0zNDY2LTNiYTItYTJlYS01NzY1MmM5MTlmZWQiLCJ0eXBlIjoiNCIsInVzZXJJZCI6IjAwMDAwMDAwMDAwMDAwMDAwMDEifQ.sN40cOC-O5WQFrF4IDUs8fFlkNdUKLbJt_rHyTsgYYM") .header("X-Access-Token", "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjYW50ZWVuSWQiOiJiZTE1NDgzMS0zNDY2LTNiYTItYTJlYS01NzY1MmM5MTlmZWQiLCJ0eXBlIjoiNCIsInVzZXJJZCI6IjAwMDAwMDAwMDAwMDAwMDAwMDEifQ.sN40cOC-O5WQFrF4IDUs8fFlkNdUKLbJt_rHyTsgYYM")
.header("X-DEVICE-CODE", GlobalData.deviceId) .header("X-DEVICE-CODE", GlobalData.deviceId)
.header("authorization", "57ee87183f2a4fa59683ec9ef41c8f5d") .header("authorization", "57ee87183f2a4fa59683ec9ef41c8f5d")
@@ -1,6 +1,8 @@
package com.sw.platecabinet.repository package com.sw.platecabinet.repository
import com.google.gson.JsonParseException import com.google.gson.JsonParseException
import com.sw.inbound.utils.GsonUtils
import com.sw.platecabinet.model.CodeMsg
import com.sw.platecabinet.model.response.ApiResponse import com.sw.platecabinet.model.response.ApiResponse
import retrofit2.HttpException import retrofit2.HttpException
import timber.log.Timber import timber.log.Timber
@@ -18,31 +20,37 @@ abstract class BaseRepository {
when (e) { when (e) {
is HttpException -> { is HttpException -> {
ApiResponse(code = e.code(), msg = e.message()) val jsonData = e.response()?.errorBody()?.string()
val codeMsg = GsonUtils.fromJson(jsonData, CodeMsg::class.java)
if (codeMsg?.msg.isNullOrBlank()) {
ApiResponse(code = "" + e.code(), msg = e.message())
} else {
ApiResponse(code = codeMsg?.code?:"", msg = codeMsg?.msg?:"")
}
} }
is SocketTimeoutException -> { is SocketTimeoutException -> {
ApiResponse(code = -2, msg = "请求超时: ${e.message}") ApiResponse(code = "-2", msg = "请求超时: ${e.message}")
} }
is ConnectException -> { is ConnectException -> {
ApiResponse(code = -3, msg = "连接失败: ${e.message}") ApiResponse(code = "-3", msg = "连接失败: ${e.message}")
} }
is SSLHandshakeException -> { is SSLHandshakeException -> {
ApiResponse(code = -4, msg = "SSL握手失败: ${e.message}") ApiResponse(code = "-4", msg = "SSL握手失败: ${e.message}")
} }
is JsonParseException -> { is JsonParseException -> {
ApiResponse(code = -5, msg = "JSON解析错误: ${e.message}") ApiResponse(code = "-5", msg = "JSON解析错误: ${e.message}")
} }
is IOException -> { is IOException -> {
ApiResponse(code = -6, msg = "网络IO错误: ${e.message}") ApiResponse(code = "-6", msg = "网络IO错误: ${e.message}")
} }
else -> { else -> {
ApiResponse(code = -1, msg = "未知错误: ${e.message ?: "无错误信息"}") ApiResponse(code = "-1", msg = "未知错误: ${e.message ?: "无错误信息"}")
} }
} }
} }
@@ -1,6 +1,7 @@
package com.sw.platecabinet.repository package com.sw.platecabinet.repository
import com.sw.platecabinet.GlobalData import com.sw.platecabinet.GlobalData
import com.sw.platecabinet.model.DeviceConfig
import com.sw.platecabinet.model.request.BindParam import com.sw.platecabinet.model.request.BindParam
import com.sw.platecabinet.model.request.EquipmentParam import com.sw.platecabinet.model.request.EquipmentParam
import com.sw.platecabinet.model.request.LoginParam import com.sw.platecabinet.model.request.LoginParam
@@ -97,7 +98,7 @@ class RemoteRepository constructor(
return safeApiCall { apiService.plateBind(param = param) } return safeApiCall { apiService.plateBind(param = param) }
} }
suspend fun plateUnbind(id: Int): ApiResponse<Any?> { suspend fun plateUnbind(id: Long ): ApiResponse<Any?> {
return safeApiCall { apiService.plateUnbind(id = id) } return safeApiCall { apiService.plateUnbind(id = id) }
} }
@@ -139,4 +140,8 @@ class RemoteRepository constructor(
) )
} }
} }
suspend fun getDeviceConfig(): ApiResponse<DeviceConfig?> {
return safeApiCall { apiService.getDeviceConfig() }
}
} }
@@ -149,7 +149,7 @@ class SettingViewModel : BaseViewModel() {
// } // }
// } // }
fun unbindPlate(id: Int, block:(Pair<Boolean?, ErrorInfo>)-> Unit) { fun unbindPlate(id: Long, block:(Pair<Boolean?, ErrorInfo>)-> Unit) {
block(null to ErrorInfo()) block(null to ErrorInfo())
launchWithLoading { launchWithLoading {
val response = repository.plateUnbind(id) val response = repository.plateUnbind(id)
@@ -187,7 +187,7 @@ class SettingViewModel : BaseViewModel() {
launchWithLoading { launchWithLoading {
// val bindParam = BindParam(plateNumber = plateNumber) // val bindParam = BindParam(plateNumber = plateNumber)
val response = repository.findByPlateNumber(plateNumber) val response = repository.findByPlateNumber(plateNumber)
if (response.code == 200) { if (response.code == "00000") {
_searchUserInfo.value = response.data _searchUserInfo.value = response.data
} else { } else {
_searchUserInfo.value = EquipmentUserInfo( _searchUserInfo.value = EquipmentUserInfo(
@@ -11,6 +11,7 @@ import com.sw.plate.utils.arcface.facedb.entity.FaceEntity
import com.sw.platecabinet.GlobalData import com.sw.platecabinet.GlobalData
import com.sw.platecabinet.GlobalData.globalEquipmentCode import com.sw.platecabinet.GlobalData.globalEquipmentCode
import com.sw.platecabinet.GlobalKey import com.sw.platecabinet.GlobalKey
import com.sw.platecabinet.model.DeviceConfig
import com.sw.platecabinet.model.request.LoginParam import com.sw.platecabinet.model.request.LoginParam
import com.sw.platecabinet.model.response.EquipmentUserInfo import com.sw.platecabinet.model.response.EquipmentUserInfo
import com.sw.platecabinet.model.response.UserFaceModel import com.sw.platecabinet.model.response.UserFaceModel
@@ -140,7 +141,7 @@ class UserViewModel : BaseViewModel() {
/** /**
* 通过用户id获取用户信息 * 通过用户id获取用户信息
*/ */
fun getUserInfoById(memberId: Int?, action:(EquipmentUserInfo?)->Unit={}) { fun getUserInfoById(memberId: String?, action:(EquipmentUserInfo?)->Unit={}) {
_currentUserInfo.value = null _currentUserInfo.value = null
launchWithLoading { launchWithLoading {
val loginParam = LoginParam(memberId = memberId) val loginParam = LoginParam(memberId = memberId)
@@ -159,5 +160,16 @@ class UserViewModel : BaseViewModel() {
Timber.d("resetUserInfo") Timber.d("resetUserInfo")
_currentUserInfo.value = null _currentUserInfo.value = null
} }
fun getDeviceConfig(block: (DeviceConfig?) -> Unit) {
// Timber.tag(TAG).d("getDeviceConfig")
launch {
val response = repository.getDeviceConfig()
if (parseResponse(response)) {
block(response.data)
} else {
block(null)
}
}
}
} }
@@ -28,7 +28,7 @@
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="22sp" android:textSize="22sp"
android:textStyle="bold" android:textStyle="bold"
android:visibility="visible" android:visibility="invisible"
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
<Space <Space
@@ -43,6 +43,7 @@
android:layout_gravity="center_horizontal|bottom" android:layout_gravity="center_horizontal|bottom"
android:layout_marginBottom="37dp" android:layout_marginBottom="37dp"
tools:ignore="ContentDescription" tools:ignore="ContentDescription"
tools:src="@mipmap/ic_logo512" /> tools:src="@mipmap/ic_logo512"
android:visibility="invisible"/>
</LinearLayout> </LinearLayout>