diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e1da272..f9f3b2c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -20,11 +20,21 @@ android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" + android:localeConfig="@xml/locales_config" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:usesCleartextTraffic="true" android:theme="@style/Theme.KotMvpProject"> + + + + diff --git a/app/src/main/java/com/juhabi/birds/MyApplication.kt b/app/src/main/java/com/juhabi/birds/MyApplication.kt index 0968d2b..c09f031 100644 --- a/app/src/main/java/com/juhabi/birds/MyApplication.kt +++ b/app/src/main/java/com/juhabi/birds/MyApplication.kt @@ -29,7 +29,9 @@ class MyApplication : Application(), Application.ActivityLifecycleCallbacks { instance = this - if (!isMainProcess()) return + if (!isMainProcess()) { + return + } registerActivityLifecycleCallbacks(this) initialize() diff --git a/app/src/main/java/com/juhabi/birds/base/BaseActivity.kt b/app/src/main/java/com/juhabi/birds/base/BaseActivity.kt index e041a30..ba54f1b 100644 --- a/app/src/main/java/com/juhabi/birds/base/BaseActivity.kt +++ b/app/src/main/java/com/juhabi/birds/base/BaseActivity.kt @@ -105,8 +105,10 @@ abstract class BaseActivity> : } /** 显示加载弹窗;按返回键取消时关闭当前 Activity。 */ - fun showLoadingDialog(message: String = "加载中...") { - if (isFinishing || isDestroyed || loadingDialog?.isShowing == true) return + fun showLoadingDialog(message: String = mContext.getString(R.string.i18n_043)) { //加载中... + if (isFinishing || isDestroyed || loadingDialog?.isShowing == true) { + return + } val dialog = LoadingDialog(this, message).apply { setCanceledOnTouchOutside(false) setCancelable(true) @@ -210,7 +212,12 @@ abstract class BaseActivity> : /** 使用默认说明文案申请一个或多个权限。 */ protected open fun requestPermission(vararg permissions: String) { - getMorePermissionRequst(permissions.toList().toTypedArray(), "需要权限", "请允许应用使用相关权限", 0) + getMorePermissionRequst( + permissions.toList().toTypedArray(), + mContext.getString(R.string.i18n_068), + mContext.getString(R.string.i18n_069), + 0 + ) //需要权限 / 请允许应用使用相关权限 } /** 权限被拒绝后的兼容回调,子类可重写。 */ @@ -224,10 +231,10 @@ abstract class BaseActivity> : protected open fun onPermissionsDenied(permissions: Array, permanentlyDenied: Boolean) { noAllowPermission() if (permanentlyDenied && !isFinishing) { - AlertDialog.Builder(this).setTitle("权限被拒绝") - .setMessage("请在应用设置中开启所需权限") - .setNegativeButton("取消", null) - .setPositiveButton("去设置") { _, _ -> openAppPermissionSettings() } + AlertDialog.Builder(this).setTitle(mContext.getString(R.string.i18n_070)) //权限被拒绝 + .setMessage(mContext.getString(R.string.i18n_071)) //请在应用设置中开启所需权限 + .setNegativeButton(mContext.getString(R.string.i18n_041), null) //取消 + .setPositiveButton(mContext.getString(R.string.i18n_072)) { _, _ -> openAppPermissionSettings() } //去设置 .show() } } @@ -252,8 +259,8 @@ abstract class BaseActivity> : fun showDialogtext( title: String, content: String, - okText: String = "确定", - cancelText: String = "取消", + okText: String = mContext.getString(R.string.i18n_027), //确定 + cancelText: String = mContext.getString(R.string.i18n_041), //取消 click: OnDialogClick? = null, touchOutsideCancel: Boolean = true ) { @@ -298,8 +305,8 @@ abstract class BaseActivity> : tip: String = "", content: String = "", touchOutsideCancel: Boolean = true, - cancelText: String = "取消", - confirmText: String = "确定", + cancelText: String = mContext.getString(R.string.i18n_041), //取消 + confirmText: String = mContext.getString(R.string.i18n_027), //确定 click: OnDialogClick? = null ) { val view = layoutInflater.inflate(R.layout.dialog_input_text, null) @@ -324,7 +331,7 @@ abstract class BaseActivity> : setOnClickListener { val value = input.text.toString().trim() if (value.isEmpty()) { - input.error = hint.ifBlank { "请输入内容" } + input.error = hint.ifBlank { mContext.getString(R.string.i18n_073) } //请输入内容 return@setOnClickListener } dialog.dismiss() @@ -377,8 +384,14 @@ abstract class BaseActivity> : fun showPermissionDialog(icon: Drawable?, showIcon: Boolean, title: String, content: String) { showPermissionDialog(title, content) permissionDialog?.findViewById(R.id.permissionIcon)?.apply { - visibility = if (showIcon) android.view.View.VISIBLE else android.view.View.GONE - if (icon != null) setImageDrawable(icon) + visibility = if (showIcon) { + android.view.View.VISIBLE + } else { + android.view.View.GONE + } + if (icon != null) { + setImageDrawable(icon) + } } } @@ -388,8 +401,14 @@ abstract class BaseActivity> : view.findViewById(R.id.permissionTitle).text = title view.findViewById(R.id.permissionContent).text = content val dialog = AlertDialog.Builder(this).setView(view).create() - view.findViewById(R.id.permissionCancel).setOnClickListener { dialog.dismiss(); onCancel() } - view.findViewById(R.id.permissionAllow).setOnClickListener { dialog.dismiss(); onAllow() } + view.findViewById(R.id.permissionCancel).setOnClickListener { + dialog.dismiss() + onCancel() + } + view.findViewById(R.id.permissionAllow).setOnClickListener { + dialog.dismiss() + onAllow() + } dialog.setOnShowListener { dialog.window?.apply { // 动态权限确认弹窗固定左右各 50dp,避免使用 AlertDialog 默认宽度。 @@ -426,3 +445,4 @@ abstract class BaseActivity> : super.onDestroy() } } + diff --git a/app/src/main/java/com/juhabi/birds/base/BaseFragment.kt b/app/src/main/java/com/juhabi/birds/base/BaseFragment.kt index b092c14..98d59f5 100644 --- a/app/src/main/java/com/juhabi/birds/base/BaseFragment.kt +++ b/app/src/main/java/com/juhabi/birds/base/BaseFragment.kt @@ -99,9 +99,11 @@ abstract class BaseFragment> : Fragment(), I } /** 在 Fragment 已附加时显示加载弹窗。 */ - fun showLoadingDialog(message: String = "加载中...") { + fun showLoadingDialog(message: String = mContext.getString(R.string.i18n_043)) { //加载中... val currentContext = context ?: return - if (!isAdded || loadingDialog?.isShowing == true) return + if (!isAdded || loadingDialog?.isShowing == true) { + return + } val dialog = LoadingDialog(currentContext, message).apply { setCanceledOnTouchOutside(false) setCancelable(true) @@ -210,7 +212,12 @@ abstract class BaseFragment> : Fragment(), I /** 使用默认说明文案申请一个或多个权限。 */ protected open fun requestPermission(vararg permissions: String) { - getMorePermissionRequst(permissions.toList().toTypedArray(), "需要权限", "请允许应用使用相关权限", 0) + getMorePermissionRequst( + permissions.toList().toTypedArray(), + mContext.getString(R.string.i18n_068), + mContext.getString(R.string.i18n_069), + 0 + ) //需要权限 / 请允许应用使用相关权限 } /** 权限被拒绝后的兼容回调,子类可重写。 */ @@ -224,10 +231,10 @@ abstract class BaseFragment> : Fragment(), I protected open fun onPermissionsDenied(permissions: Array, permanentlyDenied: Boolean) { noAllowPermission() if (permanentlyDenied && isAdded) { - AlertDialog.Builder(requireContext()).setTitle("权限被拒绝") - .setMessage("请在应用设置中开启所需权限") - .setNegativeButton("取消", null) - .setPositiveButton("去设置") { _, _ -> openAppPermissionSettings() } + AlertDialog.Builder(requireContext()).setTitle(mContext.getString(R.string.i18n_070)) //权限被拒绝 + .setMessage(mContext.getString(R.string.i18n_071)) //请在应用设置中开启所需权限 + .setNegativeButton(mContext.getString(R.string.i18n_041), null) //取消 + .setPositiveButton(mContext.getString(R.string.i18n_072)) { _, _ -> openAppPermissionSettings() } //去设置 .show() } } @@ -252,8 +259,8 @@ abstract class BaseFragment> : Fragment(), I fun showDialogtext( title: String, content: String, - okText: String = "确定", - cancelText: String = "取消", + okText: String = mContext.getString(R.string.i18n_027), //确定 + cancelText: String = mContext.getString(R.string.i18n_041), //取消 click: OnDialogClick? = null, touchOutsideCancel: Boolean = true ) { @@ -298,8 +305,8 @@ abstract class BaseFragment> : Fragment(), I tip: String = "", content: String = "", touchOutsideCancel: Boolean = true, - cancelText: String = "取消", - confirmText: String = "确定", + cancelText: String = mContext.getString(R.string.i18n_041), //取消 + confirmText: String = mContext.getString(R.string.i18n_027), //确定 click: OnDialogClick? = null ) { val view = layoutInflater.inflate(R.layout.dialog_input_text, null) @@ -324,7 +331,7 @@ abstract class BaseFragment> : Fragment(), I setOnClickListener { val value = input.text.toString().trim() if (value.isEmpty()) { - input.error = hint.ifBlank { "请输入内容" } + input.error = hint.ifBlank { mContext.getString(R.string.i18n_073) } //请输入内容 return@setOnClickListener } dialog.dismiss() @@ -376,8 +383,14 @@ abstract class BaseFragment> : Fragment(), I fun showPermissionDialog(icon: Drawable?, showIcon: Boolean, title: String, content: String) { showPermissionDialog(title, content) permissionDialog?.findViewById(R.id.permissionIcon)?.apply { - visibility = if (showIcon) View.VISIBLE else View.GONE - if (icon != null) setImageDrawable(icon) + visibility = if (showIcon) { + View.VISIBLE + } else { + View.GONE + } + if (icon != null) { + setImageDrawable(icon) + } } } /** 在系统权限申请框弹出前,显示自定义权限说明弹窗。 */ @@ -386,8 +399,14 @@ abstract class BaseFragment> : Fragment(), I view.findViewById(R.id.permissionTitle).text = title view.findViewById(R.id.permissionContent).text = content val dialog = AlertDialog.Builder(requireContext()).setView(view).create() - view.findViewById(R.id.permissionCancel).setOnClickListener { dialog.dismiss(); onCancel() } - view.findViewById(R.id.permissionAllow).setOnClickListener { dialog.dismiss(); onAllow() } + view.findViewById(R.id.permissionCancel).setOnClickListener { + dialog.dismiss() + onCancel() + } + view.findViewById(R.id.permissionAllow).setOnClickListener { + dialog.dismiss() + onAllow() + } dialog.setOnShowListener { dialog.window?.apply { // 动态权限确认弹窗固定左右各 50dp,避免使用 AlertDialog 默认宽度。 @@ -421,3 +440,4 @@ abstract class BaseFragment> : Fragment(), I super.onDestroyView() } } + diff --git a/app/src/main/java/com/juhabi/birds/component/picker/DateTimePicker.kt b/app/src/main/java/com/juhabi/birds/component/picker/DateTimePicker.kt index 9346cad..63b2bb9 100644 --- a/app/src/main/java/com/juhabi/birds/component/picker/DateTimePicker.kt +++ b/app/src/main/java/com/juhabi/birds/component/picker/DateTimePicker.kt @@ -37,10 +37,17 @@ class DateTimePicker private constructor( } .setType(mode.visibleItems) .setDate(selectedTime) - .setTitleText(mode.title) - .setSubmitText("确定") - .setCancelText("取消") - .setLabel("年", "月", "日", "时", "分", "秒") + .setTitleText(context.getString(mode.titleRes)) + .setSubmitText(context.getString(com.juhabi.birds.R.string.i18n_027)) //确定 + .setCancelText(context.getString(com.juhabi.birds.R.string.i18n_041)) //取消 + .setLabel( + context.getString(com.juhabi.birds.R.string.i18n_198), + context.getString(com.juhabi.birds.R.string.i18n_199), + context.getString(com.juhabi.birds.R.string.i18n_200), + context.getString(com.juhabi.birds.R.string.i18n_201), + context.getString(com.juhabi.birds.R.string.i18n_202), + context.getString(com.juhabi.birds.R.string.i18n_203) + ) //年 / 月 / 日 / 时 / 分 / 秒 .setSubmitColor(ContextCompat.getColor(context, R.color.bottom_nav_selected)) .setCancelColor(ContextCompat.getColor(context, R.color.bottom_nav_unselected)) .setTextColorCenter(ContextCompat.getColor(context, R.color.bottom_nav_selected)) @@ -57,22 +64,22 @@ class DateTimePicker private constructor( enum class Mode( internal val visibleItems: BooleanArray, internal val pattern: String, - internal val title: String + internal val titleRes: Int ) { DATE( booleanArrayOf(true, true, true, false, false, false), "yyyy-MM-dd", - "选择日期" + R.string.picker_select_date //选择日期 ), TIME( booleanArrayOf(false, false, false, true, true, true), "HH:mm:ss", - "选择时间" + R.string.picker_select_time //选择时间 ), DATE_TIME( booleanArrayOf(true, true, true, true, true, true), "yyyy-MM-dd HH:mm:ss", - "选择日期时间" + R.string.picker_select_date_time //选择日期时间 ); internal fun format(date: Date): String { @@ -113,7 +120,7 @@ class DateTimePicker private constructor( fun setTimeRange(startTimeInMillis: Long, endTimeInMillis: Long): Builder { return apply { - require(startTimeInMillis <= endTimeInMillis) { "开始时间不能晚于结束时间" } + require(startTimeInMillis <= endTimeInMillis) { context.getString(com.juhabi.birds.R.string.i18n_204) } //开始时间不能晚于结束时间 startTime = Calendar.getInstance().apply { timeInMillis = startTimeInMillis } endTime = Calendar.getInstance().apply { timeInMillis = endTimeInMillis } } @@ -126,13 +133,17 @@ class DateTimePicker private constructor( } fun build(): DateTimePicker { - val listener = requireNotNull(onSelected) { "必须设置选择结果监听器" } + val listener = requireNotNull(onSelected) { context.getString(com.juhabi.birds.R.string.i18n_205) } //必须设置选择结果监听器 val effectiveSelectedTime = selectedTime.clone() as Calendar startTime?.let { start -> - if (effectiveSelectedTime.before(start)) effectiveSelectedTime.timeInMillis = start.timeInMillis + if (effectiveSelectedTime.before(start)) { + effectiveSelectedTime.timeInMillis = start.timeInMillis + } } endTime?.let { end -> - if (effectiveSelectedTime.after(end)) effectiveSelectedTime.timeInMillis = end.timeInMillis + if (effectiveSelectedTime.after(end)) { + effectiveSelectedTime.timeInMillis = end.timeInMillis + } } return DateTimePicker( context = context, @@ -145,3 +156,4 @@ class DateTimePicker private constructor( } } } + diff --git a/app/src/main/java/com/juhabi/birds/custom/CustomRefreshHeader.java b/app/src/main/java/com/juhabi/birds/custom/CustomRefreshHeader.java index da37f73..dd28515 100644 --- a/app/src/main/java/com/juhabi/birds/custom/CustomRefreshHeader.java +++ b/app/src/main/java/com/juhabi/birds/custom/CustomRefreshHeader.java @@ -96,7 +96,9 @@ public class CustomRefreshHeader extends RelativeLayout implements RefreshHeader * 平滑动画过渡回正常大小 */ private void animateToNormalScale() { - if (ivRefresh == null) return; + if (ivRefresh == null) { + return; + } float startScale = mCurrentScale; ValueAnimator scaleAnimator = ValueAnimator.ofFloat(startScale, 1f); @@ -175,4 +177,4 @@ public class CustomRefreshHeader extends RelativeLayout implements RefreshHeader animateToNormalScale(); } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/juhabi/birds/demo/constraint/ConstraintLayoutDemoActivity.kt b/app/src/main/java/com/juhabi/birds/demo/constraint/ConstraintLayoutDemoActivity.kt index 3d178be..6e97ec8 100644 --- a/app/src/main/java/com/juhabi/birds/demo/constraint/ConstraintLayoutDemoActivity.kt +++ b/app/src/main/java/com/juhabi/birds/demo/constraint/ConstraintLayoutDemoActivity.kt @@ -1,5 +1,6 @@ package com.juhabi.birds.demo.constraint +import com.juhabi.birds.R import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -22,8 +23,17 @@ class ConstraintLayoutDemoActivity : BaseActivityNoPresenter val item = adapter.data[position] val message = when (view.id) { - R.id.titleTextView -> "点击了标题:${item.title}" - R.id.idTextView -> "点击了编号:${item.id}" - R.id.statusTextView -> "点击了状态:${item.status}" - R.id.timeTextView -> "点击了更新时间:${item.updateTime}" + R.id.titleTextView -> getString(R.string.demo_clicked_title, item.title) //点击了标题:%1$s + R.id.idTextView -> getString(R.string.demo_clicked_id, item.id) //点击了编号:%1$d + R.id.statusTextView -> getString(R.string.demo_clicked_status, item.status) //点击了状态:%1$s + R.id.timeTextView -> getString(R.string.demo_clicked_update_time, item.updateTime) //点击了更新时间:%1$s else -> return@setOnItemChildClickListener } android.widget.Toast.makeText(this, message, android.widget.Toast.LENGTH_SHORT).show() @@ -42,18 +42,19 @@ class RecyclerViewDemoActivity : BaseActivityNoPresenter { return listOf( - RecyclerDemoItem(1001, "设备巡检记录", "已完成", "2026-07-23 09:30:00"), - RecyclerDemoItem(1002, "自动浇水计划", "执行中", "2026-07-23 10:15:20"), - RecyclerDemoItem(1003, "传感器数据同步", "待处理", "2026-07-23 11:08:45"), - RecyclerDemoItem(1004, "系统配置备份", "已完成", "2026-07-22 18:42:10"), - RecyclerDemoItem(1005, "固件升级任务", "失败", "2026-07-22 16:20:35"), - RecyclerDemoItem(1006, "用户操作日志", "已完成", "2026-07-22 14:05:00") + RecyclerDemoItem(1001, mContext.getString(R.string.i18n_188), mContext.getString(R.string.i18n_189), "2026-07-23 09:30:00"), //设备巡检记录 / 已完成 + RecyclerDemoItem(1002, mContext.getString(R.string.i18n_190), mContext.getString(R.string.i18n_191), "2026-07-23 10:15:20"), //自动浇水计划 / 执行中 + RecyclerDemoItem(1003, mContext.getString(R.string.i18n_192), mContext.getString(R.string.i18n_193), "2026-07-23 11:08:45"), //传感器数据同步 / 待处理 + RecyclerDemoItem(1004, mContext.getString(R.string.i18n_194), mContext.getString(R.string.i18n_189), "2026-07-22 18:42:10"), //系统配置备份 / 已完成 + RecyclerDemoItem(1005, mContext.getString(R.string.i18n_195), mContext.getString(R.string.i18n_196), "2026-07-22 16:20:35"), //固件升级任务 / 失败 + RecyclerDemoItem(1006, mContext.getString(R.string.i18n_197), mContext.getString(R.string.i18n_189), "2026-07-22 14:05:00") //用户操作日志 / 已完成 ) } } + diff --git a/app/src/main/java/com/juhabi/birds/demo/recycler/adapter/RecyclerDemoAdapter.java b/app/src/main/java/com/juhabi/birds/demo/recycler/adapter/RecyclerDemoAdapter.java index 0073ba7..eaade8e 100644 --- a/app/src/main/java/com/juhabi/birds/demo/recycler/adapter/RecyclerDemoAdapter.java +++ b/app/src/main/java/com/juhabi/birds/demo/recycler/adapter/RecyclerDemoAdapter.java @@ -33,11 +33,11 @@ public class RecyclerDemoAdapter extends BaseQuickAdapter } override fun createPresenter(): DevicePresenter? { - return DevicePresenter(DeviceImpl()) + return DevicePresenter(DeviceImpl(mContext), mContext) } override fun initWindows() { @@ -35,7 +35,7 @@ class BindDeviceActivity: BaseActivity } override fun initView(savedInstanceState: Bundle?) { - binding.includedTitle.tvTitle.setText(R.string.bind_device) + binding.includedTitle.tvTitle.setText(R.string.bind_device) //绑定设备 listOf( binding.tvBind, binding.includedTitle.imgBack, @@ -67,11 +67,11 @@ class BindDeviceActivity: BaseActivity ?.trim() .orEmpty() if (imei.isBlank()) { - showMessage("请输入设备编码") + showMessage(mContext.getString(R.string.i18n_022)) //请输入设备编码 return } if (name.isBlank()) { - showMessage("请输入设备名称") + showMessage(mContext.getString(R.string.i18n_024)) //请输入设备名称 return } presenter?.bindDevice(imei, name) @@ -92,3 +92,5 @@ class BindDeviceActivity: BaseActivity } + + diff --git a/app/src/main/java/com/juhabi/birds/device/DeviceDetailActivity.kt b/app/src/main/java/com/juhabi/birds/device/DeviceDetailActivity.kt index 34e3c4f..f66b162 100644 --- a/app/src/main/java/com/juhabi/birds/device/DeviceDetailActivity.kt +++ b/app/src/main/java/com/juhabi/birds/device/DeviceDetailActivity.kt @@ -33,7 +33,7 @@ class DeviceDetailActivity : } override fun createPresenter(): DevicePresenter? { - return DevicePresenter(DeviceImpl()) + return DevicePresenter(DeviceImpl(mContext), mContext) } override fun initWindows() { @@ -48,10 +48,10 @@ class DeviceDetailActivity : override fun initView(savedInstanceState: Bundle?) { device = intent.getSerializableExtra(EXTRA_DEVICE) as? DeviceEntity - binding.includeTitle.tvTitle.text = device?.name ?: "设备详情" + binding.includeTitle.tvTitle.text = device?.name ?: mContext.getString(R.string.i18n_074) //设备详情 val deviceNo = device?.deviceNo if (deviceNo.isNullOrBlank()) { - showError("设备信息无效") + showError(mContext.getString(R.string.i18n_075)) //设备信息无效 } else { presenter?.getDeviceDetail(deviceNo) } @@ -75,7 +75,7 @@ class DeviceDetailActivity : R.id.img_switch_ctrl ->{ val currentDevice = device ?: return if (currentDevice.status == "0") { - showMessage("设备已经离线") + showMessage(mContext.getString(R.string.i18n_076)) //设备已经离线 return } @@ -102,7 +102,7 @@ class DeviceDetailActivity : override fun getDeviceDetailSuccess(device: DeviceEntity) { this.device = device binding.includeTitle.tvTitle.text = device.name - binding.tvBind.text = "设备编号:${device.deviceNo}" + binding.tvBind.text = getString(R.string.device_number_value, device.deviceNo) //设备编号:%1$s updateCoverControlState(device.hoodCoverOn == HOOD_COVER_ON) } @@ -124,7 +124,11 @@ class DeviceDetailActivity : private fun updateCoverControlState(coverOn: Boolean) { binding.switchCtrl.isChecked = coverOn binding.imgSwitchCtrl.setImageResource( - if (coverOn) R.mipmap.icon_switch_on else R.mipmap.icon_switch_off + if (coverOn) { + R.mipmap.icon_switch_on + } else { + R.mipmap.icon_switch_off + } ) } @@ -134,3 +138,5 @@ class DeviceDetailActivity : } + + diff --git a/app/src/main/java/com/juhabi/birds/device/DeviceFragment.kt b/app/src/main/java/com/juhabi/birds/device/DeviceFragment.kt index 0c93de4..df6b896 100644 --- a/app/src/main/java/com/juhabi/birds/device/DeviceFragment.kt +++ b/app/src/main/java/com/juhabi/birds/device/DeviceFragment.kt @@ -35,7 +35,7 @@ class DeviceFragment : BaseFragment(), } override fun createPresenter(): DevicePresenter { - return DevicePresenter(DeviceImpl()) + return DevicePresenter(DeviceImpl(mContext), mContext) } override fun initView(savedInstanceState: Bundle?) { @@ -59,11 +59,11 @@ class DeviceFragment : BaseFragment(), view, onRename = { showEditDialog( - title = "重命名设备", - label = "设备名称", + title = mContext.getString(R.string.i18n_077), //重命名设备 + label = mContext.getString(R.string.i18n_023), //设备名称 hint = device.name, - tip = "最多支持20个字符,支持中英文、数字和字符。", - confirmText = "确认修改", + tip = mContext.getString(R.string.i18n_078), //最多支持20个字符,支持中英文、数字和字符。 + confirmText = mContext.getString(R.string.i18n_042), //确认修改 click = object : OnDialogClick { override fun onCancelClick() { } @@ -76,10 +76,10 @@ class DeviceFragment : BaseFragment(), }, onUnbind = {//解绑设备 showDialogtext( - "提示", - "确定要解绑该设备?", - "确定", - "取消", + mContext.getString(R.string.i18n_063), //提示 + mContext.getString(R.string.i18n_079), //确定要解绑该设备? + mContext.getString(R.string.i18n_027), //确定 + mContext.getString(R.string.i18n_041), //取消 object : OnDialogClick { override fun onCancelClick() { } @@ -98,7 +98,7 @@ class DeviceFragment : BaseFragment(), val device = deviceAdapter.data[position] // 没有经纬度时只展示空态,不要把空坐标传给地图 if (device.lat.isNullOrBlank() || device.lng.isNullOrBlank()) { - showMessage("暂无定位") + showMessage(mContext.getString(R.string.i18n_080)) //暂无定位 return@setOnItemChildClickListener } // 地图页后续接入,这里先预留点击入口 @@ -136,8 +136,8 @@ class DeviceFragment : BaseFragment(), // 扫一扫:先申请相机权限,授权后再打开扫码页 getMorePermissionRequst( scanPermissions(), - "相机权限", - "扫一扫功能需要使用相机权限", + mContext.getString(R.string.i18n_081), //相机权限 + mContext.getString(R.string.i18n_082), //扫一扫功能需要使用相机权限 REQUEST_CAMERA_AND_ALBUM ) } @@ -157,7 +157,11 @@ class DeviceFragment : BaseFragment(), binding.refresh.finishLoadMore() binding.refresh.setNoMoreData(devices.size < PAGE_SIZE) binding.relNoMore.visibility = - if (deviceAdapter.data.isEmpty()) View.VISIBLE else View.GONE + if (deviceAdapter.data.isEmpty()) { + View.VISIBLE + } else { + View.GONE + } } override fun bindDeviceSuccess(message: String) { @@ -180,11 +184,17 @@ class DeviceFragment : BaseFragment(), deviceAdapter.data.removeAt(removedPosition) deviceAdapter.notifyItemRemoved(removedPosition) binding.relNoMore.visibility = - if (deviceAdapter.data.isEmpty()) View.VISIBLE else View.GONE + if (deviceAdapter.data.isEmpty()) { + View.VISIBLE + } else { + View.GONE + } } // 等待 RecyclerView 删除动画完成后再刷新,避免刷新结果立即覆盖动画。 binding.root.postDelayed({ - if (!isAdded) return@postDelayed + if (!isAdded) { + return@postDelayed + } currentPage = FIRST_PAGE requestDeviceList() }, REMOVE_ANIMATION_DELAY_MS) @@ -265,7 +275,7 @@ class DeviceFragment : BaseFragment(), ?.joinToString("\n") .orEmpty() if (content.isBlank()) { - ToastUtil.showShort(mContext, "未识别到内容") + ToastUtil.showShort(mContext, mContext.getString(R.string.i18n_083)) //未识别到内容 } else { ToastUtil.showShort(mContext, content) } @@ -276,7 +286,7 @@ class DeviceFragment : BaseFragment(), ) ToastUtil.showShort( mContext, - resultError.takeUnless { it.isNullOrBlank() } ?: "扫码失败" + resultError.takeUnless { it.isNullOrBlank() } ?: mContext.getString(R.string.i18n_084) //扫码失败 ) } else -> { @@ -288,3 +298,5 @@ class DeviceFragment : BaseFragment(), } } + + diff --git a/app/src/main/java/com/juhabi/birds/device/adapter/DeviceAdapter.kt b/app/src/main/java/com/juhabi/birds/device/adapter/DeviceAdapter.kt index b22917f..1b5e98a 100644 --- a/app/src/main/java/com/juhabi/birds/device/adapter/DeviceAdapter.kt +++ b/app/src/main/java/com/juhabi/birds/device/adapter/DeviceAdapter.kt @@ -2,6 +2,8 @@ package com.juhabi.birds.device.adapter import android.widget.ProgressBar import androidx.core.content.ContextCompat +import android.graphics.drawable.Drawable +import android.graphics.drawable.ClipDrawable import com.chad.library.adapter.base.BaseQuickAdapter import com.chad.library.adapter.base.viewholder.BaseViewHolder import com.juhabi.birds.R @@ -9,18 +11,20 @@ import com.juhabi.birds.device.entity.DeviceEntity /** 临时设备列表适配器,后续可根据设计稿替换 item 展示。 */ class DeviceAdapter : BaseQuickAdapter(R.layout.device_item) { + private val progressDrawables = mutableMapOf() + init { addChildClickViewIds(R.id.img_more) } override fun convert(holder: BaseViewHolder, item: DeviceEntity) { //设备名字 - holder.setText(R.id.tv_dev_name, item.name.ifBlank { "未命名设备" }) + holder.setText(R.id.tv_dev_name, item.name.ifBlank { context.getString(com.juhabi.birds.R.string.i18n_170) }) //未命名设备 //在线状态 if (item.status == "1") { - holder.setText(R.id.tv_dev_online_status, "在线") + holder.setText(R.id.tv_dev_online_status, context.getString(com.juhabi.birds.R.string.i18n_030)) //在线 } else { - holder.setText(R.id.tv_dev_online_status, "离线") + holder.setText(R.id.tv_dev_online_status, context.getString(com.juhabi.birds.R.string.i18n_171)) //离线 } val statusRes = if (item.status == "1") { R.drawable.shape_device_online_bg @@ -41,19 +45,19 @@ class DeviceAdapter : BaseQuickAdapter(R.layout.de val wifiTextColor: Int if (item.signalLevel == 0) { imgResource = R.mipmap.icon_dev_wifi_offline - signalText = "离线" + signalText = context.getString(com.juhabi.birds.R.string.i18n_171) //离线 wifiTextColor = R.color.color_999999 } else if (item.signalLevel == 1) { imgResource = R.mipmap.icon_dev_wifi_low - signalText = "弱" + signalText = context.getString(com.juhabi.birds.R.string.i18n_172) //弱 wifiTextColor = R.color.color_e34d59 } else if (item.signalLevel == 2) { imgResource = R.mipmap.icon_dev_wifi_mid - signalText = "一般" + signalText = context.getString(com.juhabi.birds.R.string.i18n_173) //一般 wifiTextColor = R.color.color_ff942a } else { imgResource = R.mipmap.icon_dev_wifi_hight - signalText = "强" + signalText = context.getString(com.juhabi.birds.R.string.i18n_033) //强 wifiTextColor = R.color.color_zhu } @@ -62,15 +66,23 @@ class DeviceAdapter : BaseQuickAdapter(R.layout.de holder.setTextColor(R.id.tv_signal_text,wifiTextColor) holder.setText( R.id.tv_head_foot_connect_status, - if (item.hoodConnected == "0") "断开" else "已连接" + if (item.hoodConnected == "0") { + context.getString(com.juhabi.birds.R.string.i18n_174) + } else { + context.getString(com.juhabi.birds.R.string.i18n_035) + } //断开 / 已连接 ) }else{ holder.setImageResource(R.id.img_signal, R.mipmap.icon_dev_wifi_offline) - holder.setText(R.id.tv_signal_text, "离线") + holder.setText(R.id.tv_signal_text, context.getString(com.juhabi.birds.R.string.i18n_171)) //离线 holder.setTextColor(R.id.tv_signal_text, R.color.color_999999) holder.setText( R.id.tv_head_foot_connect_status, - if (item.hoodConnected == "0") "断开" else "已连接" + if (item.hoodConnected == "0") { + context.getString(com.juhabi.birds.R.string.i18n_174) + } else { + context.getString(com.juhabi.birds.R.string.i18n_035) + } //断开 / 已连接 ) } @@ -123,20 +135,15 @@ class DeviceAdapter : BaseQuickAdapter(R.layout.de } else { R.drawable.progress_offline_bg } - // mutate 避免两条进度条共用同一份离线 drawable 状态 - progressHead.progressDrawable = ContextCompat.getDrawable( - context, - headProgressRes - )?.mutate() - progressFoot.progressDrawable = ContextCompat.getDrawable( - context, - footProgressRes - )?.mutate() - // 替换 drawable 后进度会被重置,需要重新写入 + // 每个进度条使用独立 drawable 状态,避免 RecyclerView 复用时 clip 进度互相影响。 + progressHead.progressDrawable = getProgressDrawable(headProgressRes) + progressFoot.progressDrawable = getProgressDrawable(footProgressRes) progressHead.max = 100 progressHead.progress = headBattery progressFoot.max = 100 progressFoot.progress = footBattery + setProgressDrawableLevel(progressHead, headBattery) + setProgressDrawableLevel(progressFoot, footBattery) //tv_location_detail 显示地址 holder.setText(R.id.tv_location_detail, item.address) @@ -158,8 +165,37 @@ class DeviceAdapter : BaseQuickAdapter(R.layout.de } } + private fun getProgressDrawable(resourceId: Int): Drawable? { + val constantState = progressDrawables.getOrPut(resourceId) { + ContextCompat.getDrawable(context, resourceId)?.constantState + } + return constantState?.newDrawable(context.resources)?.mutate() + } + + private fun setProgressDrawableLevel(progressBar: ProgressBar, progress: Int) { + val drawable = progressBar.progressDrawable ?: return + val clipDrawable = drawable.findClipDrawable() ?: return + clipDrawable.level = (progress * MAX_DRAWABLE_LEVEL / progressBar.max) + .coerceIn(0, MAX_DRAWABLE_LEVEL) + drawable.invalidateSelf() + } + + private fun Drawable.findClipDrawable(): ClipDrawable? { + if (this is ClipDrawable) { + return this + } + if (this is android.graphics.drawable.LayerDrawable) { + for (index in 0 until numberOfLayers) { + getDrawable(index).findClipDrawable()?.let { return it } + } + } + return null + } + private companion object { const val BATTERY_LOW_MAX = 30 const val BATTERY_MID_MAX = 70 + const val MAX_DRAWABLE_LEVEL = 10_000 } -} \ No newline at end of file +} + diff --git a/app/src/main/java/com/juhabi/birds/device/mvp/DeviceImpl.kt b/app/src/main/java/com/juhabi/birds/device/mvp/DeviceImpl.kt index e17c390..0e78ed7 100644 --- a/app/src/main/java/com/juhabi/birds/device/mvp/DeviceImpl.kt +++ b/app/src/main/java/com/juhabi/birds/device/mvp/DeviceImpl.kt @@ -1,7 +1,9 @@ package com.juhabi.birds.device.mvp +import android.content.Context import android.os.Handler import android.os.Looper +import com.juhabi.birds.R import com.juhabi.birds.device.entity.DeviceEntity import com.juhabi.birds.network.ApiClient import com.juhabi.birds.network.BaseBean @@ -11,7 +13,7 @@ import retrofit2.Call import retrofit2.Callback import retrofit2.Response -class DeviceImpl : DeviceContract.DataModel { +class DeviceImpl(private val mContext: Context) : DeviceContract.DataModel { private val mainHandler = Handler(Looper.getMainLooper()) private var deviceListCall: Call>>? = null private var deviceDetailCall: Call>? = null @@ -42,7 +44,7 @@ class DeviceImpl : DeviceContract.DataModel { callback.success(devices) } else { callback.fail( - bean?.message?.ifBlank { null } ?: "获取设备列表失败" + bean?.message?.ifBlank { null } ?: mContext.getString(R.string.i18n_175) //获取设备列表失败 ) } } @@ -54,7 +56,7 @@ class DeviceImpl : DeviceContract.DataModel { ) { if (!call.isCanceled) { mainHandler.post { - callback.fail(error.message ?: "网络请求失败") + callback.fail(error.message ?: mContext.getString(R.string.i18n_150)) //网络请求失败 } } } @@ -86,7 +88,7 @@ class DeviceImpl : DeviceContract.DataModel { callback.success(device) } else { callback.fail( - bean?.message?.ifBlank { null } ?: "获取设备详情失败" + bean?.message?.ifBlank { null } ?: mContext.getString(R.string.i18n_176) //获取设备详情失败 ) } } @@ -98,7 +100,7 @@ class DeviceImpl : DeviceContract.DataModel { ) { if (!call.isCanceled) { mainHandler.post { - callback.fail(error.message ?: "网络请求失败") + callback.fail(error.message ?: mContext.getString(R.string.i18n_150)) //网络请求失败 } } } @@ -130,7 +132,7 @@ class DeviceImpl : DeviceContract.DataModel { if (response.isSuccessful && (bean?.code == 0 || bean?.code == 200)) { callback.success(bean) } else { - callback.fail(bean?.message?.ifBlank { null } ?: "控制眼罩失败") + callback.fail(bean?.message?.ifBlank { null } ?: mContext.getString(R.string.i18n_177)) //控制眼罩失败 } } } @@ -138,7 +140,7 @@ class DeviceImpl : DeviceContract.DataModel { override fun onFailure(call: Call>, error: Throwable) { if (!call.isCanceled) { mainHandler.post { - callback.fail(error.message ?: "网络请求失败") + callback.fail(error.message ?: mContext.getString(R.string.i18n_150)) //网络请求失败 } } } @@ -169,7 +171,7 @@ class DeviceImpl : DeviceContract.DataModel { callback.success(bean) } else { callback.fail( - bean?.message?.ifBlank { null } ?: "绑定设备失败" + bean?.message?.ifBlank { null } ?: mContext.getString(R.string.i18n_178) //绑定设备失败 ) } } @@ -178,7 +180,7 @@ class DeviceImpl : DeviceContract.DataModel { override fun onFailure(call: Call>, error: Throwable) { if (!call.isCanceled) { mainHandler.post { - callback.fail(error.message ?: "网络请求失败") + callback.fail(error.message ?: mContext.getString(R.string.i18n_150)) //网络请求失败 } } } @@ -206,7 +208,7 @@ class DeviceImpl : DeviceContract.DataModel { if (response.isSuccessful && (bean?.code == 0 || bean?.code == 200)) { callback.success(bean) } else { - callback.fail(bean?.message?.ifBlank { null } ?: "设备重命名失败") + callback.fail(bean?.message?.ifBlank { null } ?: mContext.getString(R.string.i18n_179)) //设备重命名失败 } } } @@ -214,7 +216,7 @@ class DeviceImpl : DeviceContract.DataModel { override fun onFailure(call: Call>, error: Throwable) { if (!call.isCanceled) { mainHandler.post { - callback.fail(error.message ?: "网络请求失败") + callback.fail(error.message ?: mContext.getString(R.string.i18n_150)) //网络请求失败 } } } @@ -241,7 +243,7 @@ class DeviceImpl : DeviceContract.DataModel { if (response.isSuccessful && (bean?.code == 0 || bean?.code == 200)) { callback.success(bean) } else { - callback.fail(bean?.message?.ifBlank { null } ?: "设备解绑失败") + callback.fail(bean?.message?.ifBlank { null } ?: mContext.getString(R.string.i18n_180)) //设备解绑失败 } } } @@ -249,7 +251,7 @@ class DeviceImpl : DeviceContract.DataModel { override fun onFailure(call: Call>, error: Throwable) { if (!call.isCanceled) { mainHandler.post { - callback.fail(error.message ?: "网络请求失败") + callback.fail(error.message ?: mContext.getString(R.string.i18n_150)) //网络请求失败 } } } @@ -274,3 +276,4 @@ class DeviceImpl : DeviceContract.DataModel { mainHandler.removeCallbacksAndMessages(null) } } + diff --git a/app/src/main/java/com/juhabi/birds/device/mvp/DevicePresenter.kt b/app/src/main/java/com/juhabi/birds/device/mvp/DevicePresenter.kt index e8fc9f1..e3c4ae0 100644 --- a/app/src/main/java/com/juhabi/birds/device/mvp/DevicePresenter.kt +++ b/app/src/main/java/com/juhabi/birds/device/mvp/DevicePresenter.kt @@ -1,12 +1,15 @@ package com.juhabi.birds.device.mvp +import android.content.Context +import com.juhabi.birds.R import com.juhabi.birds.base.BasePresenter import com.juhabi.birds.device.entity.DeviceEntity import com.juhabi.birds.network.BaseBean import org.json.JSONObject class DevicePresenter( - private val deviceImpl: DeviceImpl + private val deviceImpl: DeviceImpl, + private val mContext: Context ) : BasePresenter() { /** 获取当前账号已经绑定的设备列表。 */ @@ -65,7 +68,7 @@ class DevicePresenter( override fun success(data: BaseBean) { view?.apply { hideLoading() - controlEyeSuccess(data.message.ifBlank { "眼罩状态已更新" }) + controlEyeSuccess(data.message.ifBlank { mContext.getString(R.string.i18n_181) }) //眼罩状态已更新 } } @@ -90,7 +93,7 @@ class DevicePresenter( override fun success(data: BaseBean) { view?.apply { hideLoading() - bindDeviceSuccess(data.message.ifBlank { "设备绑定成功" }) + bindDeviceSuccess(data.message.ifBlank { mContext.getString(R.string.i18n_182) }) //设备绑定成功 } } @@ -108,7 +111,7 @@ class DevicePresenter( val currentView = view ?: return val trimmedName = name.trim() if (deviceNo.isBlank() || trimmedName.isBlank()) { - currentView.showError("设备编号和名称不能为空") + currentView.showError(mContext.getString(R.string.i18n_183)) //设备编号和名称不能为空 return } currentView.showLoading() @@ -120,7 +123,7 @@ class DevicePresenter( override fun success(data: BaseBean) { view?.apply { hideLoading() - renameDeviceSuccess(data.message.ifBlank { "设备重命名成功" }) + renameDeviceSuccess(data.message.ifBlank { mContext.getString(R.string.i18n_184) }) //设备重命名成功 } } @@ -137,7 +140,7 @@ class DevicePresenter( fun unbindDevice(deviceNo: String) { val currentView = view ?: return if (deviceNo.isBlank()) { - currentView.showError("设备编号不能为空") + currentView.showError(mContext.getString(R.string.i18n_185)) //设备编号不能为空 return } currentView.showLoading() @@ -145,7 +148,7 @@ class DevicePresenter( override fun success(data: BaseBean) { view?.apply { hideLoading() - unbindDeviceSuccess(data.message.ifBlank { "设备解绑成功" }) + unbindDeviceSuccess(data.message.ifBlank { mContext.getString(R.string.i18n_186) }) //设备解绑成功 } } @@ -164,3 +167,4 @@ class DevicePresenter( super.onDestroy() } } + diff --git a/app/src/main/java/com/juhabi/birds/dialog/LoadingDialog.kt b/app/src/main/java/com/juhabi/birds/dialog/LoadingDialog.kt index adb555b..7768a3a 100644 --- a/app/src/main/java/com/juhabi/birds/dialog/LoadingDialog.kt +++ b/app/src/main/java/com/juhabi/birds/dialog/LoadingDialog.kt @@ -13,7 +13,7 @@ import com.juhabi.birds.R class LoadingDialog( context: Context, - private val message: String = "加载中..." + private val message: String = context.getString(com.juhabi.birds.R.string.i18n_043) //加载中... ) : Dialog(context, R.style.LoadingDialogTheme) { override fun onCreate(savedInstanceState: Bundle?) { @@ -39,3 +39,4 @@ class LoadingDialog( } } } + diff --git a/app/src/main/java/com/juhabi/birds/login/ForgetPsdActivity.kt b/app/src/main/java/com/juhabi/birds/login/ForgetPsdActivity.kt index 6bb35e4..0a9e77c 100644 --- a/app/src/main/java/com/juhabi/birds/login/ForgetPsdActivity.kt +++ b/app/src/main/java/com/juhabi/birds/login/ForgetPsdActivity.kt @@ -33,7 +33,7 @@ class ForgetPsdActivity : BaseActivity() } override fun createPresenter(): LoginPresenter? { - return LoginPresenter(LoginImpl()) + return LoginPresenter(LoginImpl(mContext), mContext) } override fun initWindows() { @@ -46,7 +46,7 @@ class ForgetPsdActivity : BaseActivity() } override fun initView(savedInstanceState: Bundle?) { - binding.includedTitle.tvTitle.setText(R.string.forget_password_title) + binding.includedTitle.tvTitle.setText(R.string.forget_password_title) //忘记密码 listOf( binding.includedTitle.imgBack, binding.getCodeButton, @@ -67,14 +67,30 @@ class ForgetPsdActivity : BaseActivity() R.id.tv_change_login_type ->{ resetByPhone = !resetByPhone - binding.linearCountry.visibility = if (resetByPhone) View.VISIBLE else View.GONE + binding.linearCountry.visibility = if (resetByPhone) { + View.VISIBLE + } else { + View.GONE + } binding.imgLoginType.setImageResource( - if (resetByPhone) R.mipmap.icon_login_phone else R.mipmap.icon_login_email + if (resetByPhone) { + R.mipmap.icon_login_phone + } else { + R.mipmap.icon_login_email + } ) - binding.tvChangeLoginType.text = if (resetByPhone) "邮箱" else "手机" + binding.tvChangeLoginType.text = if (resetByPhone) { + mContext.getString(R.string.i18n_018) + } else { + mContext.getString(R.string.i18n_085) + } //邮箱 / 手机 binding.phoneEditText.apply { text?.clear() - hint = if (resetByPhone) "请输入手机号" else "请输入邮箱" + hint = if (resetByPhone) { + mContext.getString(R.string.i18n_044) + } else { + mContext.getString(R.string.i18n_086) + } //请输入手机号 / 请输入邮箱 inputType = if (resetByPhone) { InputType.TYPE_CLASS_PHONE } else { @@ -86,7 +102,12 @@ class ForgetPsdActivity : BaseActivity() R.id.getCodeButton -> {//获取验证码 val account = binding.phoneEditText.text.toString().trim() if (account.isBlank()) { - ToastUtil.showShort(mContext, if (resetByPhone) "手机号不能为空" else "邮箱不能为空") + val accountError = if (resetByPhone) { + mContext.getString(R.string.i18n_087) + } else { + mContext.getString(R.string.i18n_088) + } //手机号不能为空 / 邮箱不能为空 + ToastUtil.showShort(mContext, accountError) return } if (resetByPhone) { @@ -113,29 +134,34 @@ class ForgetPsdActivity : BaseActivity() val confirmPassword = binding.confirmPasswordEditText.text.toString() if (account.isBlank()) { - ToastUtil.showShort(mContext, if (resetByPhone) "手机号不能为空" else "邮箱不能为空") + val accountError = if (resetByPhone) { + mContext.getString(R.string.i18n_087) + } else { + mContext.getString(R.string.i18n_088) + } //手机号不能为空 / 邮箱不能为空 + ToastUtil.showShort(mContext, accountError) return } if (code.isBlank()) { - ToastUtil.showShort(mContext, "验证码不能为空") + ToastUtil.showShort(mContext, mContext.getString(R.string.i18n_089)) //验证码不能为空 return } if (password.isBlank()) { - ToastUtil.showShort(mContext, "密码不能为空") + ToastUtil.showShort(mContext, mContext.getString(R.string.i18n_090)) //密码不能为空 return } if (confirmPassword.isBlank()) { - ToastUtil.showShort(mContext, "请再次输入密码") + ToastUtil.showShort(mContext, mContext.getString(R.string.i18n_091)) //请再次输入密码 return } if (password != confirmPassword) { - ToastUtil.showShort(mContext, "两次输入的密码不一致") + ToastUtil.showShort(mContext, mContext.getString(R.string.i18n_092)) //两次输入的密码不一致 return } if (!isValidPassword(password)) { ToastUtil.showShort( mContext, - "密码需为8-20位,且包含大小写字母和数字" + mContext.getString(R.string.i18n_093) //密码需为8-20位,且包含大小写字母和数字 ) return } @@ -173,13 +199,13 @@ class ForgetPsdActivity : BaseActivity() isEnabled = false isClickable = false setBackgroundResource(R.drawable.shape_register_code_disabled_bg) - text = getString(R.string.register_code_countdown, CODE_COUNTDOWN_SECONDS) + text = getString(R.string.register_code_countdown, CODE_COUNTDOWN_SECONDS) //%1$d秒后重新获取 } codeCountDownTimer = object : CountDownTimer(CODE_COUNTDOWN_MILLIS, 1_000L) { override fun onTick(millisUntilFinished: Long) { val secondsRemaining = (millisUntilFinished + 999L) / 1_000L binding.getCodeButton.text = getString( - R.string.register_code_countdown, + R.string.register_code_countdown, //%1$d秒后重新获取 secondsRemaining ) } @@ -190,7 +216,7 @@ class ForgetPsdActivity : BaseActivity() isEnabled = true isClickable = true setBackgroundResource(R.drawable.shape_register_code_bg) - setText(R.string.register_get_code) + setText(R.string.register_get_code) //获取验证码 } } }.start() @@ -201,7 +227,7 @@ class ForgetPsdActivity : BaseActivity() override fun onForgetPsdSuccess(message: String) { hideLoading() - ToastUtil.showShort(mContext, message.ifBlank { "密码修改成功" }) + ToastUtil.showShort(mContext, message.ifBlank { mContext.getString(R.string.i18n_094) }) //密码修改成功 finish() } @@ -243,3 +269,5 @@ class ForgetPsdActivity : BaseActivity() const val PASSWORD_MAX_LENGTH = 20 } } + + diff --git a/app/src/main/java/com/juhabi/birds/login/LoginActivity.kt b/app/src/main/java/com/juhabi/birds/login/LoginActivity.kt index 3240712..2a1538b 100644 --- a/app/src/main/java/com/juhabi/birds/login/LoginActivity.kt +++ b/app/src/main/java/com/juhabi/birds/login/LoginActivity.kt @@ -3,9 +3,16 @@ package com.juhabi.birds.login import android.content.Intent import android.os.Bundle import android.os.CountDownTimer +import android.graphics.Color import android.text.InputType +import android.text.SpannableString +import android.text.Spanned +import android.text.TextPaint +import android.text.method.LinkMovementMethod +import android.text.style.ClickableSpan import android.view.LayoutInflater import android.view.View +import androidx.core.content.ContextCompat import com.gyf.immersionbar.ImmersionBar import com.juhabi.birds.AppConfig import com.juhabi.birds.R @@ -16,7 +23,7 @@ import com.juhabi.birds.login.mvp.LoginContract import com.juhabi.birds.login.mvp.LoginImpl import com.juhabi.birds.login.mvp.LoginPresenter import com.juhabi.birds.main.MainActivity -import com.juhabi.birds.mine.MineCenterActivity +import com.juhabi.birds.mine.WebviewActivity import com.juhabi.birds.network.TokenManager import com.juhabi.birds.util.SharedPreferencesUtil import com.juhabi.birds.util.ToastUtil @@ -39,7 +46,7 @@ class LoginActivity : BaseActivity(), } override fun createPresenter(): LoginPresenter { - return LoginPresenter(LoginImpl()) + return LoginPresenter(LoginImpl(mContext), mContext) } override fun initWindows() { @@ -61,6 +68,7 @@ class LoginActivity : BaseActivity(), ).forEach { button -> button.setOnClickListener(this) } + setupAgreementText() updateLoginModeText() } @@ -84,13 +92,25 @@ class LoginActivity : BaseActivity(), // 切换手机号和邮箱登录,并同步输入框图标、提示和输入类型。 loginByPhone = !loginByPhone binding.imgLoginType.setImageResource( - if (loginByPhone) R.mipmap.icon_login_phone else R.mipmap.icon_login_email + if (loginByPhone) { + R.mipmap.icon_login_phone + } else { + R.mipmap.icon_login_email + } ) - binding.tvChangeLoginType.text = if (loginByPhone) "邮箱" else "手机" + binding.tvChangeLoginType.text = if (loginByPhone) { + mContext.getString(R.string.i18n_018) + } else { + mContext.getString(R.string.i18n_085) + } //邮箱 / 手机 // 账号类型切换后,同步更新密码/验证码登录切换提示。 updateLoginModeText() binding.accountEditText.apply { - hint = if (loginByPhone) "请输入手机号" else "请输入邮箱" + hint = if (loginByPhone) { + mContext.getString(R.string.i18n_044) + } else { + mContext.getString(R.string.i18n_086) + } //请输入手机号 / 请输入邮箱 inputType = if (loginByPhone) { InputType.TYPE_CLASS_PHONE } else { @@ -104,7 +124,12 @@ class LoginActivity : BaseActivity(), // 根据当前账号类型选择手机或邮箱验证码接口。 val account = binding.accountEditText.text.toString().trim() if (account.isBlank()) { - ToastUtil.showShort(mContext, if (loginByPhone) "手机号不能为空" else "邮箱不能为空") + val accountError = if (loginByPhone) { + mContext.getString(R.string.i18n_087) + } else { + mContext.getString(R.string.i18n_088) + } //手机号不能为空 / 邮箱不能为空 + ToastUtil.showShort(mContext, accountError) return } if (loginByPhone) { @@ -127,17 +152,21 @@ class LoginActivity : BaseActivity(), } R.id.loginButton -> {//登录 + if (!binding.checkboxRead.isChecked) { + ToastUtil.showShort(mContext, mContext.getString(R.string.i18n_095)) //请先阅读并同意用户协议和隐私政策 + return + } // 账号类型和登录方式组合成四种登录请求。 val account = binding.accountEditText.text.toString().trim() if (account.isBlank()) { - ToastUtil.showShort(mContext, "账号不能为空") + ToastUtil.showShort(mContext, mContext.getString(R.string.i18n_096)) //账号不能为空 return } if (loginByPassword) { val password = binding.passwordEditText.text.toString() if (password.isBlank()) { - ToastUtil.showShort(mContext, "密码不能为空") + ToastUtil.showShort(mContext, mContext.getString(R.string.i18n_090)) //密码不能为空 return } if (loginByPhone) { @@ -148,7 +177,7 @@ class LoginActivity : BaseActivity(), } else { val code = binding.codeEditText.text.toString().trim() if (code.isBlank()) { - ToastUtil.showShort(mContext, "验证码不能为空") + ToastUtil.showShort(mContext, mContext.getString(R.string.i18n_089)) //验证码不能为空 return } if (loginByPhone) { @@ -174,12 +203,66 @@ class LoginActivity : BaseActivity(), binding.passwordEditText.typeface = binding.accountEditText.typeface } + private fun setupAgreementText() { + val content = getString(R.string.agree_user_policy) //我已阅读并同意《用户协议》《隐私政策》 + val text = SpannableString(content) + addClickableSpan(text, content, getString(R.string.user_agreement)) { //《用户协议》 + openAgreementPage(WebviewActivity.PAGE_AGREEMENT, AppConfig.USER_AGREEMENT_URL) + } + addClickableSpan(text, content, getString(R.string.privacy_policy)) { //《隐私政策》 + openAgreementPage(WebviewActivity.PAGE_PRIVACY, AppConfig.PRIVACY_POLICY_URL) + } + binding.tvAgreementFull.apply { + this.text = text + movementMethod = LinkMovementMethod.getInstance() + highlightColor = Color.TRANSPARENT + } + binding.realtiveReadFinish.setOnClickListener { + binding.checkboxRead.isChecked = !binding.checkboxRead.isChecked + } + } + + private fun addClickableSpan( + spannable: SpannableString, + content: String, + target: String, + action: () -> Unit + ) { + val start = content.indexOf(target) + if (start < 0) { + return + } + spannable.setSpan(object : ClickableSpan() { + override fun onClick(widget: View) = action() + + override fun updateDrawState(ds: TextPaint) { + ds.color = ContextCompat.getColor(this@LoginActivity, R.color.color_white) + ds.isUnderlineText = false + } + }, start, start + target.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + } + + private fun openAgreementPage(page: Int, url: String) { + goTo(WebviewActivity::class.java, Bundle().apply { + putInt(WebviewActivity.EXTRA_PAGE, page) + putString(WebviewActivity.EXTRA_URL, url) + }) + } + /** 根据账号类型和当前登录方式,更新登录方式切换按钮文案。 */ private fun updateLoginModeText() { binding.tvChangeCode.text = if (loginByPassword) { - if (loginByPhone) "切换手机号验证码登录" else "切换邮箱验证码登录" + if (loginByPhone) { + getString(R.string.register_change_code_login) + } else { + mContext.getString(R.string.i18n_097) + } //切换手机号验证码登录 / 切换邮箱验证码登录 } else { - if (loginByPhone) "切换手机号密码登录" else "切换邮箱密码登录" + if (loginByPhone) { + mContext.getString(R.string.i18n_098) + } else { + mContext.getString(R.string.i18n_099) + } //切换手机号密码登录 / 切换邮箱密码登录 } } @@ -191,13 +274,13 @@ class LoginActivity : BaseActivity(), isEnabled = false isClickable = false setBackgroundResource(R.drawable.shape_register_code_disabled_bg) - text = getString(R.string.register_code_countdown, CODE_COUNTDOWN_SECONDS) + text = getString(R.string.register_code_countdown, CODE_COUNTDOWN_SECONDS) //%1$d秒后重新获取 } codeCountDownTimer = object : CountDownTimer(CODE_COUNTDOWN_MILLIS, 1_000L) { override fun onTick(millisUntilFinished: Long) { val secondsRemaining = (millisUntilFinished + 999L) / 1_000L binding.getCodeButton.text = getString( - R.string.register_code_countdown, + R.string.register_code_countdown, //%1$d秒后重新获取 secondsRemaining ) } @@ -208,7 +291,7 @@ class LoginActivity : BaseActivity(), isEnabled = true isClickable = true setBackgroundResource(R.drawable.shape_register_code_bg) - setText(R.string.register_get_code) + setText(R.string.register_get_code) //获取验证码 } } }.start() @@ -238,7 +321,7 @@ class LoginActivity : BaseActivity(), AppConfig.USER_LOGIN = token.isNotBlank() SharedPreferencesUtil.putBoolean(this, KEY_USER_LOGIN, AppConfig.USER_LOGIN) - ToastUtil.showShort(mContext, "登录成功") + ToastUtil.showShort(mContext, mContext.getString(R.string.i18n_100)) //登录成功 startActivity(Intent(this, MainActivity::class.java)) finish() } @@ -260,3 +343,5 @@ class LoginActivity : BaseActivity(), const val KEY_USER_LOGIN = "user_login" } } + + diff --git a/app/src/main/java/com/juhabi/birds/login/RegisterActivity.kt b/app/src/main/java/com/juhabi/birds/login/RegisterActivity.kt index 7a104f0..01f0207 100644 --- a/app/src/main/java/com/juhabi/birds/login/RegisterActivity.kt +++ b/app/src/main/java/com/juhabi/birds/login/RegisterActivity.kt @@ -38,7 +38,7 @@ class RegisterActivity : BaseActivity(), } override fun createPresenter(): LoginPresenter? { - return LoginPresenter(LoginImpl()) + return LoginPresenter(LoginImpl(mContext), mContext) } override fun initWindows() { @@ -51,7 +51,7 @@ class RegisterActivity : BaseActivity(), } override fun initView(savedInstanceState: Bundle?) { - binding.includedTitle.tvTitle.setText(R.string.login_register) + binding.includedTitle.tvTitle.setText(R.string.login_register) //手机注册 listOf( binding.passwordVisibilityButton, binding.getCodeButton, @@ -73,14 +73,30 @@ class RegisterActivity : BaseActivity(), R.id.tv_change_login_type ->{ registerByPhone = !registerByPhone - binding.linearCountry.visibility = if (registerByPhone) View.VISIBLE else View.GONE + binding.linearCountry.visibility = if (registerByPhone) { + View.VISIBLE + } else { + View.GONE + } binding.imgLoginType.setImageResource( - if (registerByPhone) R.mipmap.icon_login_phone else R.mipmap.icon_login_email + if (registerByPhone) { + R.mipmap.icon_login_phone + } else { + R.mipmap.icon_login_email + } ) - binding.tvChangeLoginType.text = if (registerByPhone) "邮箱" else "手机" + binding.tvChangeLoginType.text = if (registerByPhone) { + mContext.getString(R.string.i18n_018) + } else { + mContext.getString(R.string.i18n_085) + } //邮箱 / 手机 binding.phoneEditText.apply { text?.clear() - hint = if (registerByPhone) "请输入手机号" else "请输入邮箱" + hint = if (registerByPhone) { + mContext.getString(R.string.i18n_044) + } else { + mContext.getString(R.string.i18n_086) + } //请输入手机号 / 请输入邮箱 inputType = if (registerByPhone) { InputType.TYPE_CLASS_PHONE } else { @@ -94,7 +110,12 @@ class RegisterActivity : BaseActivity(), R.id.getCodeButton -> {//获取验证码 val account = binding.phoneEditText.text.toString().trim() if (account.isBlank()) { - ToastUtil.showShort(mContext, if (registerByPhone) "手机号不能为空" else "邮箱不能为空") + val accountError = if (registerByPhone) { + mContext.getString(R.string.i18n_087) + } else { + mContext.getString(R.string.i18n_088) + } //手机号不能为空 / 邮箱不能为空 + ToastUtil.showShort(mContext, accountError) return } if (registerByPhone) { @@ -111,29 +132,34 @@ class RegisterActivity : BaseActivity(), val confirmPassword = binding.confirmPasswordEditText.text.toString() if (account.isBlank()) { - ToastUtil.showShort(mContext, if (registerByPhone) "手机号不能为空" else "邮箱不能为空") + val accountError = if (registerByPhone) { + mContext.getString(R.string.i18n_087) + } else { + mContext.getString(R.string.i18n_088) + } //手机号不能为空 / 邮箱不能为空 + ToastUtil.showShort(mContext, accountError) return } if (code.isBlank()) { - ToastUtil.showShort(mContext, "验证码不能为空") + ToastUtil.showShort(mContext, mContext.getString(R.string.i18n_089)) //验证码不能为空 return } if (password.isBlank()) { - ToastUtil.showShort(mContext, "密码不能为空") + ToastUtil.showShort(mContext, mContext.getString(R.string.i18n_090)) //密码不能为空 return } if (confirmPassword.isBlank()) { - ToastUtil.showShort(mContext, "请再次输入密码") + ToastUtil.showShort(mContext, mContext.getString(R.string.i18n_091)) //请再次输入密码 return } if (password != confirmPassword) { - ToastUtil.showShort(mContext, "两次输入的密码不一致") + ToastUtil.showShort(mContext, mContext.getString(R.string.i18n_092)) //两次输入的密码不一致 return } if (!isValidPassword(password)) { ToastUtil.showShort( mContext, - "密码需为8-20位,且包含大小写字母和数字" + mContext.getString(R.string.i18n_093) //密码需为8-20位,且包含大小写字母和数字 ) return } @@ -198,13 +224,13 @@ class RegisterActivity : BaseActivity(), isEnabled = false isClickable = false setBackgroundResource(R.drawable.shape_register_code_disabled_bg) - text = getString(R.string.register_code_countdown, CODE_COUNTDOWN_SECONDS) + text = getString(R.string.register_code_countdown, CODE_COUNTDOWN_SECONDS) //%1$d秒后重新获取 } codeCountDownTimer = object : CountDownTimer(CODE_COUNTDOWN_MILLIS, 1_000L) { override fun onTick(millisUntilFinished: Long) { val secondsRemaining = (millisUntilFinished + 999L) / 1_000L binding.getCodeButton.text = getString( - R.string.register_code_countdown, + R.string.register_code_countdown, //%1$d秒后重新获取 secondsRemaining ) } @@ -215,7 +241,7 @@ class RegisterActivity : BaseActivity(), isEnabled = true isClickable = true setBackgroundResource(R.drawable.shape_register_code_bg) - setText(R.string.register_get_code) + setText(R.string.register_get_code) //获取验证码 } } }.start() @@ -223,7 +249,7 @@ class RegisterActivity : BaseActivity(), override fun onRegisterSuccess(message: String) { hideLoading() - ToastUtil.showShort(mContext, message.ifBlank { "注册成功" }) + ToastUtil.showShort(mContext, message.ifBlank { mContext.getString(R.string.i18n_101) }) //注册成功 finish() } @@ -266,3 +292,5 @@ class RegisterActivity : BaseActivity(), const val PASSWORD_MAX_LENGTH = 20 } } + + diff --git a/app/src/main/java/com/juhabi/birds/login/mvp/LoginImpl.kt b/app/src/main/java/com/juhabi/birds/login/mvp/LoginImpl.kt index ca87619..e57f188 100644 --- a/app/src/main/java/com/juhabi/birds/login/mvp/LoginImpl.kt +++ b/app/src/main/java/com/juhabi/birds/login/mvp/LoginImpl.kt @@ -1,7 +1,9 @@ package com.juhabi.birds.login.mvp +import android.content.Context import android.os.Handler import android.os.Looper +import com.juhabi.birds.R import com.juhabi.birds.login.model.UserEntity import com.juhabi.birds.login.model.SendCodeEntity import com.juhabi.birds.network.ApiClient @@ -12,7 +14,7 @@ import retrofit2.Call import retrofit2.Callback import retrofit2.Response -class LoginImpl : LoginContract.DataModel { +class LoginImpl(private val mContext: Context) : LoginContract.DataModel { private val mainHandler = Handler(Looper.getMainLooper()) private var loginCall: Call>? = null @@ -63,7 +65,7 @@ class LoginImpl : LoginContract.DataModel { if (response.isSuccessful && bean != null) { callback.success(bean) } else { - callback.fail("接口响应异常:${response.code()}") + callback.fail(mContext.getString(R.string.api_response_error, response.code())) //接口响应异常:%1$d } } } @@ -75,7 +77,7 @@ class LoginImpl : LoginContract.DataModel { calls -= call if (!call.isCanceled) { mainHandler.post { - callback.fail(error.message ?: "网络请求失败") + callback.fail(error.message ?: mContext.getString(R.string.i18n_150)) //网络请求失败 } } } @@ -142,7 +144,7 @@ class LoginImpl : LoginContract.DataModel { if (response.isSuccessful && bean != null) { callback.success(bean) } else { - callback.fail("接口响应异常:${response.code()}") + callback.fail(mContext.getString(R.string.api_response_error, response.code())) //接口响应异常:%1$d } } } @@ -151,7 +153,7 @@ class LoginImpl : LoginContract.DataModel { calls -= call if (!call.isCanceled) { mainHandler.post { - callback.fail(error.message ?: "网络请求失败") + callback.fail(error.message ?: mContext.getString(R.string.i18n_150)) //网络请求失败 } } } @@ -178,7 +180,7 @@ class LoginImpl : LoginContract.DataModel { if (response.isSuccessful && bean != null) { callback.success(bean) } else { - callback.fail("接口响应异常:${response.code()}") + callback.fail(mContext.getString(R.string.api_response_error, response.code())) //接口响应异常:%1$d } } } @@ -187,7 +189,7 @@ class LoginImpl : LoginContract.DataModel { calls -= call if (!call.isCanceled) { mainHandler.post { - callback.fail(error.message ?: "网络请求失败") + callback.fail(error.message ?: mContext.getString(R.string.i18n_150)) //网络请求失败 } } } @@ -214,7 +216,7 @@ class LoginImpl : LoginContract.DataModel { if (response.isSuccessful && bean != null) { callback.success(bean) } else { - callback.fail("接口响应异常:${response.code()}") + callback.fail(mContext.getString(R.string.api_response_error, response.code())) //接口响应异常:%1$d } } } @@ -223,7 +225,7 @@ class LoginImpl : LoginContract.DataModel { calls -= call if (!call.isCanceled) { mainHandler.post { - callback.fail(error.message ?: "网络请求失败") + callback.fail(error.message ?: mContext.getString(R.string.i18n_150)) //网络请求失败 } } } @@ -250,7 +252,7 @@ class LoginImpl : LoginContract.DataModel { if (response.isSuccessful && bean != null) { callback.success(bean) } else { - callback.fail("接口响应异常:${response.code()}") + callback.fail(mContext.getString(R.string.api_response_error, response.code())) //接口响应异常:%1$d } } } @@ -259,7 +261,7 @@ class LoginImpl : LoginContract.DataModel { calls -= call if (!call.isCanceled) { mainHandler.post { - callback.fail(error.message ?: "网络请求失败") + callback.fail(error.message ?: mContext.getString(R.string.i18n_150)) //网络请求失败 } } } @@ -286,7 +288,7 @@ class LoginImpl : LoginContract.DataModel { if (response.isSuccessful && bean != null) { callback.success(bean) } else { - callback.fail("接口响应异常:${response.code()}") + callback.fail(mContext.getString(R.string.api_response_error, response.code())) //接口响应异常:%1$d } } } @@ -295,7 +297,7 @@ class LoginImpl : LoginContract.DataModel { calls -= call if (!call.isCanceled) { mainHandler.post { - callback.fail(error.message ?: "网络请求失败") + callback.fail(error.message ?: mContext.getString(R.string.i18n_150)) //网络请求失败 } } } @@ -318,7 +320,7 @@ class LoginImpl : LoginContract.DataModel { if (response.isSuccessful && bean != null) { callback.success(bean) } else { - callback.fail("接口响应异常:${response.code()}") + callback.fail(mContext.getString(R.string.api_response_error, response.code())) //接口响应异常:%1$d } } } @@ -326,7 +328,7 @@ class LoginImpl : LoginContract.DataModel { override fun onFailure(call: Call>, error: Throwable) { if (!call.isCanceled) { mainHandler.post { - callback.fail(error.message ?: "网络请求失败") + callback.fail(error.message ?: mContext.getString(R.string.i18n_150)) //网络请求失败 } } } @@ -341,3 +343,4 @@ class LoginImpl : LoginContract.DataModel { mainHandler.removeCallbacksAndMessages(null) } } + diff --git a/app/src/main/java/com/juhabi/birds/login/mvp/LoginPresenter.kt b/app/src/main/java/com/juhabi/birds/login/mvp/LoginPresenter.kt index b723bc9..f541ab0 100644 --- a/app/src/main/java/com/juhabi/birds/login/mvp/LoginPresenter.kt +++ b/app/src/main/java/com/juhabi/birds/login/mvp/LoginPresenter.kt @@ -1,12 +1,17 @@ package com.juhabi.birds.login.mvp +import android.content.Context +import com.juhabi.birds.R import com.juhabi.birds.base.BasePresenter import com.juhabi.birds.login.model.UserEntity import com.juhabi.birds.login.model.SendCodeEntity import com.juhabi.birds.network.BaseBean import org.json.JSONObject -class LoginPresenter(private val loginImpl: LoginImpl) : BasePresenter() { +class LoginPresenter( + private val loginImpl: LoginImpl, + private val mContext: Context +) : BasePresenter() { fun getPhoneSmsCode(phoneNumber: String, sendCodeSceneRegister: String) { @@ -22,10 +27,10 @@ class LoginPresenter(private val loginImpl: LoginImpl) : BasePresenter { getSinglePermissionRequst( Manifest.permission.ACCESS_FINE_LOCATION, - "定位权限", - "定位功能需要获取您的位置信息", + mContext.getString(R.string.i18n_104), //定位权限 + mContext.getString(R.string.i18n_105), //定位功能需要获取您的位置信息 REQUEST_LOCATION ) } R.id.normalDialogButton -> { showDialogtext( - "操作确认", - "确定要执行这项操作吗?", - "确定", - "取消", + mContext.getString(R.string.i18n_106), //操作确认 + mContext.getString(R.string.i18n_107), //确定要执行这项操作吗? + mContext.getString(R.string.i18n_027), //确定 + mContext.getString(R.string.i18n_041), //取消 object : OnDialogClick { override fun onCancelClick() { - showMessage("已取消") + showMessage(mContext.getString(R.string.i18n_108)) //已取消 } override fun onConfirmClick() { - showMessage("已点击确定") + showMessage(mContext.getString(R.string.i18n_109)) //已点击确定 } } ) @@ -123,14 +123,14 @@ class MineCenterActivity : BaseActivity { showEditDialog( - title = "请输入内容", - hint = "请输入测试内容", + title = mContext.getString(R.string.i18n_073), //请输入内容 + hint = mContext.getString(R.string.i18n_110), //请输入测试内容 click = object : OnDialogClick { override fun onCancelClick() { - showMessage("已取消输入") + showMessage(mContext.getString(R.string.i18n_111)) //已取消输入 } override fun onConfirmClick(content: String) { - showMessage("输入内容:$content") + showMessage(getString(R.string.input_content_result, content)) //输入内容:%1$s } } ) @@ -179,7 +179,7 @@ class MineCenterActivity : BaseActivity selectedDateTime = result.timestamp binding.dateTimeResultTextView.text = result.formattedText - showMessage("已选择:${result.formattedText}") + showMessage(getString(R.string.selected_result, result.formattedText)) //已选择:%1$s } .build() .show() @@ -190,12 +190,27 @@ class MineCenterActivity : BaseActivity) { - showMessage("权限获取成功:${permissions.joinToString { it.substringAfterLast('.') }}") + showMessage(getString( + R.string.permission_granted_value, //权限获取成功:%1$s + permissions.joinToString { it.substringAfterLast('.') } + )) } override fun onPermissionsDenied(permissions: Array, permanentlyDenied: Boolean) { - showMessage("权限被拒绝:${permissions.joinToString { it.substringAfterLast('.') }}") + showMessage(getString( + R.string.permission_denied_value, //权限被拒绝:%1$s + permissions.joinToString { it.substringAfterLast('.') } + )) super.onPermissionsDenied(permissions, permanentlyDenied) } @@ -243,4 +264,6 @@ class MineCenterActivity : BaseActivity(), } override fun createPresenter(): MinePresenter { - return MinePresenter(MineImpl()) + return MinePresenter(MineImpl(mContext), mContext) } override fun onResume() { @@ -78,10 +82,10 @@ class MineFragment : BaseFragment(), R.id.linear_qchc -> { showDialogtext( - "提示", - "清除当前应用缓存?", - "确定", - "取消", + mContext.getString(R.string.i18n_063), //提示 + mContext.getString(R.string.i18n_113), //清除当前应用缓存? + mContext.getString(R.string.i18n_027), //确定 + mContext.getString(R.string.i18n_041), //取消 object : OnDialogClick { override fun onCancelClick() { } @@ -93,15 +97,20 @@ class MineFragment : BaseFragment(), ) } + R.id.linear_qhyy -> { + showLanguageDialog() + } + + //注销账号 R.id.linear_zxzh -> { showDialogtext( - "确认注销账号?", - "注销账号后,账号将被删除,所有设备" + - "解绑、个人信息被清除,无法恢复,请谨慎操作?", - "确认注销", - "取消", + mContext.getString(R.string.i18n_114), //确认注销账号? + mContext.getString(R.string.i18n_115) + //注销账号后,账号将被删除,所有设备 + mContext.getString(R.string.i18n_116), //解绑、个人信息被清除,无法恢复,请谨慎操作? + mContext.getString(R.string.i18n_117), //确认注销 + mContext.getString(R.string.i18n_041), //取消 object : OnDialogClick { override fun onCancelClick() { } @@ -118,10 +127,10 @@ class MineFragment : BaseFragment(), //退出登录 R.id.tv_logout -> { showDialogtext( - "退出登录", - "确定要退出当前账号吗?", - "确定", - "取消", + mContext.getString(R.string.i18n_061), //退出登录 + mContext.getString(R.string.i18n_118), //确定要退出当前账号吗? + mContext.getString(R.string.i18n_027), //确定 + mContext.getString(R.string.i18n_041), //取消 object : OnDialogClick { override fun onCancelClick() { } @@ -138,6 +147,50 @@ class MineFragment : BaseFragment(), } } + private fun showLanguageDialog() { + val view = layoutInflater.inflate(R.layout.dialog_language, null) + val dialog = AlertDialog.Builder(requireContext(), R.style.LanguageDialogTheme) + .setView(view) + .create() + + view.findViewById(R.id.languageChinese).setOnClickListener { + dialog.dismiss() + setAppLanguage("zh-CN") + } + view.findViewById(R.id.languageEnglish).setOnClickListener { + dialog.dismiss() + setAppLanguage("en") + } + view.findViewById(R.id.languageArabic).setOnClickListener { + dialog.dismiss() + setAppLanguage("ar") + } + + dialog.setCanceledOnTouchOutside(true) + dialog.window?.apply { + setBackgroundDrawableResource(android.R.color.transparent) + setGravity(android.view.Gravity.BOTTOM) + attributes = attributes.apply { + height = WindowManager.LayoutParams.WRAP_CONTENT + windowAnimations = R.style.LanguageDialogAnimation + } + } + dialog.show() + // AlertDialog.show() 会按主题重新计算默认宽度,因此必须在 show 后覆盖为屏幕宽度。 + dialog.window?.setLayout( + WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.WRAP_CONTENT + ) + } + + private fun setAppLanguage(languageTag: String) { + val currentTag = AppCompatDelegate.getApplicationLocales().toLanguageTags() + if (currentTag == languageTag) { + return + } + AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(languageTag)) + } + private fun openAgreementPage(page: Int, url: String) { goTo( WebviewActivity::class.java, @@ -150,13 +203,17 @@ class MineFragment : BaseFragment(), /** 将个人信息请求结果更新到个人中心页面。 */ override fun getUserInfoSuccess(userInfo: MineUserInfo) { - val displayName = userInfo.userName.ifBlank { "未设置用户名" } + val displayName = userInfo.userName.ifBlank { mContext.getString(R.string.i18n_119) } //未设置用户名 val displayAccount = userInfo.nickName ?.ifBlank { null } - ?: "未绑定手机号" + ?: mContext.getString(R.string.i18n_120) //未绑定手机号 binding.tvUserName.text = displayName binding.tvUserAccount.text = displayAccount - binding.tvChangeBindAccount.text = if (userInfo.phoneBound == true) "换绑手机号" else "换绑邮箱" + binding.tvChangeBindAccount.text = if (userInfo.phoneBound == true) { + mContext.getString(R.string.i18n_121) + } else { + mContext.getString(R.string.i18n_122) + } //换绑手机号 / 换绑邮箱 if (userInfo.avatar.isNullOrBlank()) { binding.imgAvatar.setImageResource(R.mipmap.icon_mine_user_head_defaul) @@ -226,7 +283,7 @@ class MineFragment : BaseFragment(), activity?.runOnUiThread { if (isAdded && view != null) { binding.tvTotalCache.text = formatCacheSize(0L) - showMessage("缓存清理成功") + showMessage(mContext.getString(R.string.i18n_123)) //缓存清理成功 } } }.start() @@ -263,3 +320,5 @@ class MineFragment : BaseFragment(), } } } + + diff --git a/app/src/main/java/com/juhabi/birds/mine/ModifyPsdActivity.kt b/app/src/main/java/com/juhabi/birds/mine/ModifyPsdActivity.kt index a2eb081..2c419c3 100644 --- a/app/src/main/java/com/juhabi/birds/mine/ModifyPsdActivity.kt +++ b/app/src/main/java/com/juhabi/birds/mine/ModifyPsdActivity.kt @@ -25,7 +25,7 @@ class ModifyPsdActivity : BaseActivity(), } override fun createPresenter(): MinePresenter? { - return MinePresenter(MineImpl()) + return MinePresenter(MineImpl(mContext), mContext) } override fun initWindows() { @@ -39,7 +39,7 @@ class ModifyPsdActivity : BaseActivity(), override fun initView(savedInstanceState: Bundle?) { - binding.includedTitle.tvTitle.text = "修改密码" + binding.includedTitle.tvTitle.setText(R.string.modify_password) //修改密码 listOf( binding.oldPsdVisibilityButton, binding.passwordVisibilityButton, @@ -64,27 +64,27 @@ class ModifyPsdActivity : BaseActivity(), // 按顺序校验输入项,避免无效数据提交到服务端。 if (oldPassword.isBlank()) { - ToastUtil.showShort(mContext, "当前密码不能为空") + ToastUtil.showShort(mContext, mContext.getString(R.string.i18n_124)) //当前密码不能为空 return } if (newPassword.isBlank()) { - ToastUtil.showShort(mContext, "新密码不能为空") + ToastUtil.showShort(mContext, mContext.getString(R.string.i18n_125)) //新密码不能为空 return } if (confirmPassword.isBlank()) { - ToastUtil.showShort(mContext, "请再次输入新密码") + ToastUtil.showShort(mContext, mContext.getString(R.string.i18n_046)) //请再次输入新密码 return } if (oldPassword == newPassword) { - ToastUtil.showShort(mContext, "新密码不能与当前密码相同") + ToastUtil.showShort(mContext, mContext.getString(R.string.i18n_126)) //新密码不能与当前密码相同 return } if (newPassword != confirmPassword) { - ToastUtil.showShort(mContext, "两次输入的新密码不一致") + ToastUtil.showShort(mContext, mContext.getString(R.string.i18n_127)) //两次输入的新密码不一致 return } if (!isValidPassword(newPassword)) { - ToastUtil.showShort(mContext, "密码需为8-20位,且包含大小写字母和数字") + ToastUtil.showShort(mContext, mContext.getString(R.string.i18n_093)) //密码需为8-20位,且包含大小写字母和数字 return } @@ -104,13 +104,15 @@ class ModifyPsdActivity : BaseActivity(), override fun onModifyPasswordSuccess(message: String) { hideLoading() - ToastUtil.showShort(mContext, message.ifBlank { "密码修改成功" }) + ToastUtil.showShort(mContext, message.ifBlank { mContext.getString(R.string.i18n_094) }) //密码修改成功 finish() } /** 8-20 位,且同时包含大写、小写字母和数字。 */ private fun isValidPassword(password: String): Boolean { - if (password.length !in PASSWORD_MIN_LENGTH..PASSWORD_MAX_LENGTH) return false + if (password.length !in PASSWORD_MIN_LENGTH..PASSWORD_MAX_LENGTH) { + return false + } return password.any { it.isUpperCase() } && password.any { it.isLowerCase() } && password.any { it.isDigit() } @@ -129,3 +131,5 @@ class ModifyPsdActivity : BaseActivity(), } + + diff --git a/app/src/main/java/com/juhabi/birds/mine/UpdateUserAccount.kt b/app/src/main/java/com/juhabi/birds/mine/UpdateUserAccount.kt index 47c4801..22f1d83 100644 --- a/app/src/main/java/com/juhabi/birds/mine/UpdateUserAccount.kt +++ b/app/src/main/java/com/juhabi/birds/mine/UpdateUserAccount.kt @@ -31,7 +31,7 @@ class UpdateUserAccount : BaseActivity{ - if (userLoginByPhone) presenter?.getCurrentPhoneCode() + if (userLoginByPhone) { + presenter?.getCurrentPhoneCode() + } else presenter?.getCurrentEmailCode() } @@ -79,12 +85,14 @@ class UpdateUserAccount : BaseActivity "隐私政策" to "privacy_policy.html" - PAGE_AGREEMENT -> "用户协议" to "user_agreement.html" + PAGE_PRIVACY -> mContext.getString(R.string.i18n_134) to "privacy_policy.html" //隐私政策 + PAGE_AGREEMENT -> mContext.getString(R.string.i18n_058) to "user_agreement.html" //用户协议 else -> null } if (page == null) { - showMessage("页面参数无效") + showMessage(mContext.getString(R.string.i18n_135)) //页面参数无效 finish() return } @@ -108,13 +109,15 @@ class WebviewActivity : BaseActivity>? = null private var versionCall: Call>? = null @@ -39,7 +41,7 @@ class MineImpl : MineContract.DataModel { ) { mainHandler.post { response.body()?.let(callback::success) - ?: callback.fail("验证码发送失败") + ?: callback.fail(mContext.getString(R.string.i18n_149)) //验证码发送失败 } } @@ -48,7 +50,7 @@ class MineImpl : MineContract.DataModel { error: Throwable ) { if (!call.isCanceled) { - mainHandler.post { callback.fail(error.message ?: "网络请求失败") } + mainHandler.post { callback.fail(error.message ?: mContext.getString(R.string.i18n_150)) } //网络请求失败 } } }) @@ -58,10 +60,12 @@ class MineImpl : MineContract.DataModel { override fun getCurrentEmailCode(callback: MineContract.Callback>) { ApiClient.apiService.getCurrentEmailCode(NetworkManager.getToken()).enqueue(object : Callback> { override fun onResponse(call: Call>, response: Response>) { - mainHandler.post { response.body()?.let(callback::success) ?: callback.fail("验证码发送失败") } + mainHandler.post { response.body()?.let(callback::success) ?: callback.fail(mContext.getString(R.string.i18n_149)) } //验证码发送失败 } override fun onFailure(call: Call>, error: Throwable) { - if (!call.isCanceled) mainHandler.post { callback.fail(error.message ?: "网络请求失败") } + if (!call.isCanceled) { + mainHandler.post { callback.fail(error.message ?: mContext.getString(R.string.i18n_150)) } + } //网络请求失败 } }) } @@ -81,7 +85,7 @@ class MineImpl : MineContract.DataModel { ) { mainHandler.post { response.body()?.let(callback::success) - ?: callback.fail("验证码发送失败") + ?: callback.fail(mContext.getString(R.string.i18n_149)) //验证码发送失败 } } @@ -90,7 +94,7 @@ class MineImpl : MineContract.DataModel { error: Throwable ) { if (!call.isCanceled) { - mainHandler.post { callback.fail(error.message ?: "网络请求失败") } + mainHandler.post { callback.fail(error.message ?: mContext.getString(R.string.i18n_150)) } //网络请求失败 } } }) @@ -98,12 +102,17 @@ class MineImpl : MineContract.DataModel { } override fun sendEmailCode(request: JSONObject, callback: MineContract.Callback>) { - ApiClient.apiService.sendEmailCode(NetworkManager.getToken(), NetworkManager.toRequestBody(request)).enqueue(object : Callback> { + ApiClient.apiService.sendEmailCode( + NetworkManager.getToken(), + NetworkManager.toRequestBody(request) + ).enqueue(object : Callback> { override fun onResponse(call: Call>, response: Response>) { - mainHandler.post { response.body()?.let(callback::success) ?: callback.fail("验证码发送失败") } + mainHandler.post { response.body()?.let(callback::success) ?: callback.fail(mContext.getString(R.string.i18n_149)) } //验证码发送失败 } override fun onFailure(call: Call>, error: Throwable) { - if (!call.isCanceled) mainHandler.post { callback.fail(error.message ?: "网络请求失败") } + if (!call.isCanceled) { + mainHandler.post { callback.fail(error.message ?: mContext.getString(R.string.i18n_150)) } + } //网络请求失败 } }) } @@ -126,14 +135,14 @@ class MineImpl : MineContract.DataModel { ) { mainHandler.post { response.body()?.let(callback::success) - ?: callback.fail("当前手机号验证失败") + ?: callback.fail(mContext.getString(R.string.i18n_151)) //当前手机号验证失败 } } override fun onFailure(call: Call>, error: Throwable) { if (!call.isCanceled) { mainHandler.post { - callback.fail(error.message ?: "网络请求失败") + callback.fail(error.message ?: mContext.getString(R.string.i18n_150)) //网络请求失败 } } } @@ -145,10 +154,12 @@ class MineImpl : MineContract.DataModel { val body = JSONObject().apply { put("oldCode", oldCode.trim()) } ApiClient.apiService.verifyOldEmail(NetworkManager.getToken(), NetworkManager.toRequestBody(body)).enqueue(object : Callback> { override fun onResponse(call: Call>, response: Response>) { - mainHandler.post { response.body()?.let(callback::success) ?: callback.fail("当前邮箱验证失败") } + mainHandler.post { response.body()?.let(callback::success) ?: callback.fail(mContext.getString(R.string.i18n_152)) } //当前邮箱验证失败 } override fun onFailure(call: Call>, error: Throwable) { - if (!call.isCanceled) mainHandler.post { callback.fail(error.message ?: "网络请求失败") } + if (!call.isCanceled) { + mainHandler.post { callback.fail(error.message ?: mContext.getString(R.string.i18n_150)) } + } //网络请求失败 } }) } @@ -176,14 +187,14 @@ class MineImpl : MineContract.DataModel { ) { mainHandler.post { response.body()?.let(callback::success) - ?: callback.fail("手机号更换失败") + ?: callback.fail(mContext.getString(R.string.i18n_153)) //手机号更换失败 } } override fun onFailure(call: Call>, error: Throwable) { if (!call.isCanceled) { mainHandler.post { - callback.fail(error.message ?: "网络请求失败") + callback.fail(error.message ?: mContext.getString(R.string.i18n_150)) //网络请求失败 } } } @@ -192,13 +203,21 @@ class MineImpl : MineContract.DataModel { } override fun updateBindNewEmail(newEmail: String, code: String, callback: MineContract.Callback>) { - val body = JSONObject().apply { put("newEmail", newEmail.trim()); put("code", code.trim()) } - ApiClient.apiService.updateBindNewEmail(NetworkManager.getToken(), NetworkManager.toRequestBody(body)).enqueue(object : Callback> { + val body = JSONObject().apply { + put("newEmail", newEmail.trim()) + put("code", code.trim()) + } + ApiClient.apiService.updateBindNewEmail( + NetworkManager.getToken(), + NetworkManager.toRequestBody(body) + ).enqueue(object : Callback> { override fun onResponse(call: Call>, response: Response>) { - mainHandler.post { response.body()?.let(callback::success) ?: callback.fail("邮箱更换失败") } + mainHandler.post { response.body()?.let(callback::success) ?: callback.fail(mContext.getString(R.string.i18n_154)) } //邮箱更换失败 } override fun onFailure(call: Call>, error: Throwable) { - if (!call.isCanceled) mainHandler.post { callback.fail(error.message ?: "网络请求失败") } + if (!call.isCanceled) { + mainHandler.post { callback.fail(error.message ?: mContext.getString(R.string.i18n_150)) } + } //网络请求失败 } }) } @@ -215,14 +234,14 @@ class MineImpl : MineContract.DataModel { ) { mainHandler.post { response.body()?.let(callback::success) - ?: callback.fail("退出登录失败") + ?: callback.fail(mContext.getString(R.string.i18n_155)) //退出登录失败 } } override fun onFailure(call: Call>, error: Throwable) { if (!call.isCanceled) { mainHandler.post { - callback.fail(error.message ?: "网络请求失败") + callback.fail(error.message ?: mContext.getString(R.string.i18n_150)) //网络请求失败 } } } @@ -242,14 +261,14 @@ class MineImpl : MineContract.DataModel { ) { mainHandler.post { response.body()?.let(callback::success) - ?: callback.fail("注销账号失败") + ?: callback.fail(mContext.getString(R.string.i18n_156)) //注销账号失败 } } override fun onFailure(call: Call>, error: Throwable) { if (!call.isCanceled) { mainHandler.post { - callback.fail(error.message ?: "网络请求失败") + callback.fail(error.message ?: mContext.getString(R.string.i18n_150)) //网络请求失败 } } } @@ -276,7 +295,7 @@ class MineImpl : MineContract.DataModel { if (response.isSuccessful && bean?.code == 200 && data != null) { callback.success(data) } else { - callback.fail(bean?.message?.ifBlank { null } ?: "获取个人信息失败") + callback.fail(bean?.message?.ifBlank { null } ?: mContext.getString(R.string.i18n_157)) //获取个人信息失败 } } } @@ -284,7 +303,7 @@ class MineImpl : MineContract.DataModel { override fun onFailure(call: Call>, error: Throwable) { if (!call.isCanceled) { mainHandler.post { - callback.fail(error.message ?: "网络请求失败") + callback.fail(error.message ?: mContext.getString(R.string.i18n_150)) //网络请求失败 } } } @@ -315,7 +334,7 @@ class MineImpl : MineContract.DataModel { } else { callback.fail( bean?.message?.ifBlank { null } - ?: "版本检测失败" + ?: mContext.getString(R.string.i18n_158) //版本检测失败 ) } } @@ -328,7 +347,7 @@ class MineImpl : MineContract.DataModel { if (!call.isCanceled) { mainHandler.post { callback.fail( - error.message ?: "版本检测失败" + error.message ?: mContext.getString(R.string.i18n_158) //版本检测失败 ) } } @@ -363,14 +382,14 @@ class MineImpl : MineContract.DataModel { if (response.isSuccessful && bean?.code == 200) { callback.success(bean.message.orEmpty()) } else { - callback.fail(bean?.message?.ifBlank { null } ?: "密码修改失败") + callback.fail(bean?.message?.ifBlank { null } ?: mContext.getString(R.string.i18n_159)) //密码修改失败 } } } override fun onFailure(call: Call>, error: Throwable) { if (!call.isCanceled) { - mainHandler.post { callback.fail(error.message ?: "网络请求失败") } + mainHandler.post { callback.fail(error.message ?: mContext.getString(R.string.i18n_150)) } //网络请求失败 } } }) @@ -399,3 +418,4 @@ class MineImpl : MineContract.DataModel { mainHandler.removeCallbacksAndMessages(null) } } + diff --git a/app/src/main/java/com/juhabi/birds/mine/mvp/MinePresenter.kt b/app/src/main/java/com/juhabi/birds/mine/mvp/MinePresenter.kt index ad1c49f..a20996d 100644 --- a/app/src/main/java/com/juhabi/birds/mine/mvp/MinePresenter.kt +++ b/app/src/main/java/com/juhabi/birds/mine/mvp/MinePresenter.kt @@ -1,5 +1,7 @@ package com.juhabi.birds.mine.mvp +import android.content.Context +import com.juhabi.birds.R import com.juhabi.birds.base.BasePresenter import com.juhabi.birds.main.model.AppVersionInfo import com.juhabi.birds.mine.model.MineUserInfo @@ -7,7 +9,10 @@ import com.juhabi.birds.login.model.SendCodeEntity import com.juhabi.birds.network.BaseBean import org.json.JSONObject -class MinePresenter(private val mineImpl: MineImpl) : BasePresenter() { +class MinePresenter( + private val mineImpl: MineImpl, + private val mContext: Context +) : BasePresenter() { /** 通过登录态向当前绑定的手机号发送验证码,无需额外请求参数。 */ fun getCurrentPhoneCode() { @@ -18,9 +23,9 @@ class MinePresenter(private val mineImpl: MineImpl) : BasePresenter return false } - if (!cardNumber.take(17).all(Char::isDigit)) return false - if (cardNumber.last() != 'X' && !cardNumber.last().isDigit()) return false - if (cardNumber.substring(0, 2) !in areaCodes) return false - if (!hasValidBirthDate(cardNumber.substring(6, 14))) return false + if (!cardNumber.take(17).all(Char::isDigit)) { + return false + } + if (cardNumber.last() != 'X' && !cardNumber.last().isDigit()) { + return false + } + if (cardNumber.substring(0, 2) !in areaCodes) { + return false + } + if (!hasValidBirthDate(cardNumber.substring(6, 14))) { + return false + } return cardNumber.last() == calculateVerifyCode(cardNumber) } private fun convertToNewCardNumber(oldCardNumber: String): String? { - if (!oldCardNumber.all(Char::isDigit)) return null + if (!oldCardNumber.all(Char::isDigit)) { + return null + } val firstSeventeen = buildString(17) { append(oldCardNumber, 0, 6) append("19") diff --git a/app/src/main/java/com/juhabi/birds/util/file/PreviewFileUtil.kt b/app/src/main/java/com/juhabi/birds/util/file/PreviewFileUtil.kt index a7c0b21..ab6e622 100644 --- a/app/src/main/java/com/juhabi/birds/util/file/PreviewFileUtil.kt +++ b/app/src/main/java/com/juhabi/birds/util/file/PreviewFileUtil.kt @@ -12,7 +12,9 @@ import java.io.File object PreviewFileUtil { @JvmStatic fun previewFiles(context: Context, suffix: String?, fileUrl: String?): Boolean { - if (fileUrl.isNullOrBlank()) return false + if (fileUrl.isNullOrBlank()) { + return false + } val uri = when { fileUrl.startsWith("content://", ignoreCase = true) -> Uri.parse(fileUrl) fileUrl.startsWith("http://", ignoreCase = true) || @@ -31,11 +33,15 @@ object PreviewFileUtil { val intent = Intent(Intent.ACTION_VIEW) .setDataAndType(uri, resolvedMimeType) .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - if (context !is Activity) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + if (context !is Activity) { + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } return try { context.startActivity(Intent.createChooser(intent, null).apply { - if (context !is Activity) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + if (context !is Activity) { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } }) true } catch (_: ActivityNotFoundException) { @@ -44,7 +50,9 @@ object PreviewFileUtil { } private fun fileToContentUri(context: Context, file: File): Uri? { - if (!file.exists() || !file.isFile) return null + if (!file.exists() || !file.isFile) { + return null + } return try { FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", file) } catch (_: IllegalArgumentException) { diff --git a/app/src/main/java/com/juhabi/birds/util/manager/ActivityManager.kt b/app/src/main/java/com/juhabi/birds/util/manager/ActivityManager.kt index 1ec63cb..f4fb17c 100644 --- a/app/src/main/java/com/juhabi/birds/util/manager/ActivityManager.kt +++ b/app/src/main/java/com/juhabi/birds/util/manager/ActivityManager.kt @@ -40,7 +40,9 @@ object ActivityManager { @JvmStatic fun removeActivityByTag(tag: String) { - if (tag.isBlank()) return + if (tag.isBlank()) { + return + } finishAndRemove { key, _ -> key.contains(tag) } } diff --git a/app/src/main/java/com/juhabi/birds/util/manager/UserInfoManager.kt b/app/src/main/java/com/juhabi/birds/util/manager/UserInfoManager.kt index 4896049..b0507e5 100644 --- a/app/src/main/java/com/juhabi/birds/util/manager/UserInfoManager.kt +++ b/app/src/main/java/com/juhabi/birds/util/manager/UserInfoManager.kt @@ -41,9 +41,19 @@ object UserInfoManager { @JvmStatic fun setUserInfo(userInfo: MineUserInfo) { val loginByPhone = userInfo.email.isNullOrBlank() + val normalizedEmail = if (loginByPhone) { + "" + } else { + userInfo.email + } + val normalizedPhoneNumber = if (loginByPhone) { + userInfo.phoneNumber + } else { + "" + } val normalizedUserInfo = userInfo.copy( - email = if (loginByPhone) "" else userInfo.email, - phoneNumber = if (loginByPhone) userInfo.phoneNumber else "", + email = normalizedEmail, + phoneNumber = normalizedPhoneNumber, userLoginByPhone = loginByPhone ) setUserInfo(MyApplication.appContext, normalizedUserInfo) diff --git a/app/src/main/java/com/juhabi/birds/utils/AppUpdateManager.kt b/app/src/main/java/com/juhabi/birds/utils/AppUpdateManager.kt index 38c4f80..d96b73f 100644 --- a/app/src/main/java/com/juhabi/birds/utils/AppUpdateManager.kt +++ b/app/src/main/java/com/juhabi/birds/utils/AppUpdateManager.kt @@ -33,7 +33,7 @@ class AppUpdateManager( versionInfo.versionName } if (!isNewerVersion(currentVersion, latestVersion)) { - messageCallback("当前已是最新版本") + messageCallback(activity.getString(com.juhabi.birds.R.string.i18n_140)) //当前已是最新版本 return } showUpdateDialog(latestVersion, versionInfo) @@ -43,7 +43,9 @@ class AppUpdateManager( requestCode: Int, grantResults: IntArray ): Boolean { - if (requestCode != REQUEST_NOTIFICATIONS) return false + if (requestCode != REQUEST_NOTIFICATIONS) { + return false + } val downloadUrl = pendingDownloadUrl pendingDownloadUrl = null @@ -52,7 +54,7 @@ class AppUpdateManager( if (permissionGranted && !downloadUrl.isNullOrBlank()) { downloadApk(downloadUrl) } else { - messageCallback("需要通知权限才能显示下载进度") + messageCallback(activity.getString(com.juhabi.birds.R.string.i18n_141)) //需要通知权限才能显示下载进度 } return true } @@ -62,14 +64,14 @@ class AppUpdateManager( versionInfo: AppVersionInfo ) { AlertDialog.Builder(activity) - .setTitle("发现新版本 $latestVersion") + .setTitle(activity.getString(com.juhabi.birds.R.string.update_new_version_title, latestVersion)) //发现新版本 %1$s .setMessage( versionInfo.releaseNotes.ifBlank { - "是否立即下载更新?" + activity.getString(com.juhabi.birds.R.string.i18n_142) //是否立即下载更新? } ) - .setNegativeButton("取消", null) - .setPositiveButton("下载") { _, _ -> + .setNegativeButton(activity.getString(com.juhabi.birds.R.string.i18n_041), null) //取消 + .setPositiveButton(activity.getString(com.juhabi.birds.R.string.i18n_143)) { _, _ -> //下载 downloadApk(versionInfo.downloadUrl) } .setCancelable(!versionInfo.forceUpdate) @@ -102,7 +104,7 @@ class AppUpdateManager( private fun downloadApk(url: String) { if (url.isBlank()) { - messageCallback("下载地址为空") + messageCallback(activity.getString(com.juhabi.birds.R.string.i18n_144)) //下载地址为空 return } if (!hasNotificationPermission()) { @@ -128,7 +130,7 @@ class AppUpdateManager( } }.onFailure { error -> activity.runOnUiThread { - messageCallback("下载失败:${error.message}") + messageCallback(activity.getString(com.juhabi.birds.R.string.update_download_failed, error.message)) //下载失败:%1$s } } } @@ -142,12 +144,14 @@ class AppUpdateManager( } private fun createDownloadChannel(manager: NotificationManager) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + return + } manager.createNotificationChannel( NotificationChannel( DOWNLOAD_CHANNEL_ID, - "应用更新", + activity.getString(com.juhabi.birds.R.string.i18n_145), //应用更新 NotificationManager.IMPORTANCE_LOW ) ) @@ -160,9 +164,11 @@ class AppUpdateManager( val response = OkHttpClient().newCall( Request.Builder().url(url).build() ).execute() - if (!response.isSuccessful) error("HTTP ${response.code}") + if (!response.isSuccessful) { + error("HTTP ${response.code}") + } - val body = response.body ?: error("下载响应为空") + val body = response.body ?: error(activity.getString(com.juhabi.birds.R.string.i18n_146)) //下载响应为空 val apkFile = File( activity.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), UPDATE_APK_NAME @@ -209,7 +215,7 @@ class AppUpdateManager( DOWNLOAD_CHANNEL_ID ) .setSmallIcon(android.R.drawable.stat_sys_download) - .setContentTitle("正在下载更新") + .setContentTitle(activity.getString(com.juhabi.birds.R.string.i18n_147)) //正在下载更新 .setOnlyAlertOnce(true) .setProgress(100, progress, false) .build() @@ -222,7 +228,7 @@ class AppUpdateManager( DOWNLOAD_CHANNEL_ID ) .setSmallIcon(android.R.drawable.stat_sys_download_done) - .setContentTitle("下载完成") + .setContentTitle(activity.getString(com.juhabi.birds.R.string.i18n_148)) //下载完成 .build() manager.notify(DOWNLOAD_NOTIFICATION_ID, notification) } @@ -248,3 +254,4 @@ class AppUpdateManager( const val APK_MIME_TYPE = "application/vnd.android.package-archive" } } + diff --git a/app/src/main/res/drawable/select_radio.xml b/app/src/main/res/drawable/select_radio.xml new file mode 100644 index 0000000..ebff77f --- /dev/null +++ b/app/src/main/res/drawable/select_radio.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/layout/activity_constraint_layout_demo.xml b/app/src/main/res/layout/activity_constraint_layout_demo.xml index fcad020..0ee6253 100644 --- a/app/src/main/res/layout/activity_constraint_layout_demo.xml +++ b/app/src/main/res/layout/activity_constraint_layout_demo.xml @@ -11,9 +11,9 @@ android:layout_width="48dp" android:layout_height="48dp" android:background="?attr/selectableItemBackgroundBorderless" - android:contentDescription="返回" + android:contentDescription="@string/back_button_description" android:padding="14dp" - android:src="@drawable/img_back_black" + android:src="@drawable/img_back_black_auto_mirrored" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -57,7 +57,7 @@ android:id="@+id/introText" android:layout_width="0dp" android:layout_height="wrap_content" - android:text="下面用一个项目概览页演示常用约束。可以结合 XML 注释观察效果。" + android:text="@string/i18n_001" android:textColor="#667085" android:textSize="14sp" app:layout_constraintEnd_toEndOf="parent" @@ -101,7 +101,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="14dp" - android:text="智能灌溉升级项目" + android:text="@string/i18n_002" android:textColor="#18212F" android:textSize="17sp" android:textStyle="bold" @@ -116,7 +116,7 @@ android:background="@drawable/bg_constraint_tag" android:gravity="center" android:paddingHorizontal="8dp" - android:text="进行中" + android:text="@string/i18n_003" android:textColor="#00866A" android:textSize="12sp" app:layout_constraintBaseline_toBaselineOf="@id/nameText" @@ -129,7 +129,7 @@ android:layout_height="wrap_content" android:layout_marginStart="14dp" android:layout_marginTop="8dp" - android:text="负责人:Android 学习者" + android:text="@string/i18n_004" android:textColor="#667085" android:textSize="13sp" app:layout_constraintEnd_toEndOf="parent" @@ -152,7 +152,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" - android:text="完成进度" + android:text="@string/i18n_005" android:textColor="#667085" android:textSize="12sp" app:layout_constraintStart_toStartOf="parent" @@ -175,7 +175,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="24dp" - android:text="项目详情" + android:text="@string/i18n_006" android:textColor="#18212F" android:textSize="18sp" android:textStyle="bold" @@ -186,7 +186,7 @@ android:id="@+id/toggleDetailsButton" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="隐藏详情" + android:text="@string/i18n_007" android:textColor="#00866A" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="@+id/detailTitle"/> @@ -196,7 +196,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="18dp" - android:text="开始时间" + android:text="@string/i18n_008" android:textColor="#667085" android:textSize="14sp" app:layout_constraintStart_toStartOf="parent" @@ -207,7 +207,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="14dp" - android:text="责任部门很长" + android:text="@string/i18n_009" android:textColor="#667085" android:textSize="14sp" app:layout_constraintStart_toStartOf="parent" @@ -238,7 +238,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="20dp" - android:text="移动研发组" + android:text="@string/i18n_010" android:textColor="#344054" android:textSize="14sp" app:layout_constraintBaseline_toBaselineOf="@id/labelOwner" @@ -257,7 +257,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="26dp" - android:text="本周数据" + android:text="@string/i18n_011" android:textColor="#18212F" android:textSize="18sp" android:textStyle="bold" @@ -272,7 +272,7 @@ android:layout_marginTop="14dp" android:background="#FFFFFF" android:gravity="center" - android:text="12\n任务" + android:text="@string/i18n_012" android:textColor="#344054" android:textSize="14sp" app:layout_constraintEnd_toStartOf="@id/statTwo" @@ -287,7 +287,7 @@ android:layout_marginStart="8dp" android:background="#FFFFFF" android:gravity="center" - android:text="9\n完成" + android:text="@string/i18n_013" android:textColor="#344054" android:textSize="14sp" app:layout_constraintEnd_toStartOf="@id/statThree" @@ -301,7 +301,7 @@ android:layout_marginStart="8dp" android:background="#FFFFFF" android:gravity="center" - android:text="3\n待处理" + android:text="@string/i18n_014" android:textColor="#344054" android:textSize="14sp" app:layout_constraintEnd_toEndOf="parent" @@ -313,7 +313,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="24dp" - android:text="16:9 比例区域" + android:text="@string/i18n_015" android:textColor="#18212F" android:textSize="16sp" android:textStyle="bold" @@ -329,7 +329,7 @@ android:layout_marginTop="12dp" android:background="#DDEFEA" android:gravity="center" - android:text="宽度由两侧约束\n高度按 16:9 自动计算" + android:text="@string/i18n_016" android:textColor="#26705F" android:textSize="14sp" app:layout_constraintDimensionRatio="16:9" @@ -344,7 +344,7 @@ android:layout_marginBottom="24dp" android:background="#FFF7E6" android:padding="14dp" - android:text="核心区别:LinearLayout 强调排列,RelativeLayout 强调相对规则;ConstraintLayout 用双向约束与辅助工具描述关系,复杂页面通常能减少嵌套层级。" + android:text="@string/i18n_017" android:textColor="#7A4E00" android:textSize="13sp" app:layout_constraintBottom_toBottomOf="parent" diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index c26492a..fcfc090 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -80,7 +80,7 @@ android:layout_height="match_parent" android:layout_gravity="end" android:gravity="center" - android:text="邮箱" + android:text="@string/i18n_018" android:textColor="@color/color_zhu" android:textSize="13sp" /> @@ -149,7 +149,7 @@ android:layout_weight="1" android:layout_marginStart="8dp" android:background="@null" - android:hint="请输入验证码" + android:hint="@string/i18n_019" android:inputType="number" android:maxLines="1" android:textColorHint="@color/color_999999" @@ -163,7 +163,7 @@ android:backgroundTint="@null" android:stateListAnimator="@null" android:gravity="center" - android:text="获取验证码" + android:text="@string/register_get_code" android:textAllCaps="false" android:textColor="@color/color_white" android:textSize="12sp" /> @@ -215,15 +215,37 @@ android:textColor="@color/color_zhu" android:textSize="16sp" /> - + android:layout_marginTop="30dp" + android:layout_marginStart="20dp"> + + + + + + android:text="@string/i18n_020" /> diff --git a/app/src/main/res/layout/bind_device_layout.xml b/app/src/main/res/layout/bind_device_layout.xml index 5d8c060..41491f6 100644 --- a/app/src/main/res/layout/bind_device_layout.xml +++ b/app/src/main/res/layout/bind_device_layout.xml @@ -30,7 +30,7 @@ android:layout_height="wrap_content" android:textSize="15sp" android:textColor="@color/color_333333" - android:text="绑定"/> + android:text="@string/i18n_021"/> + android:hint="@string/i18n_022"/> + android:text="@string/i18n_023"/> + android:hint="@string/i18n_024"/> + android:text="@string/i18n_025"/> @@ -112,7 +112,7 @@ android:layout_weight="7" android:textSize="15sp" android:textColor="@color/color_333333" - android:text="IMEI码可在设备机身上的二维码标签旁边找到"/> + android:text="@string/i18n_026"/> diff --git a/app/src/main/res/layout/device_detail_layout.xml b/app/src/main/res/layout/device_detail_layout.xml index 7e548aa..1f922a9 100644 --- a/app/src/main/res/layout/device_detail_layout.xml +++ b/app/src/main/res/layout/device_detail_layout.xml @@ -103,7 +103,7 @@ android:layout_marginStart="10dp" android:textSize="13sp" android:textColor="@color/color_333333" - android:text="眼罩开关"/> + android:text="@string/i18n_028"/> + android:text="@string/i18n_029"/> + android:text="@string/i18n_031"/> + android:text="@string/i18n_032"/> + android:text="@string/i18n_033"/> @@ -147,7 +147,7 @@ android:layout_height="wrap_content" android:textSize="13sp" android:textColor="@color/color_black1" - android:text="头套连接状态"/> + android:text="@string/i18n_034"/> + android:text="@string/i18n_035"/> @@ -185,7 +185,7 @@ android:layout_height="wrap_content" android:textSize="13sp" android:textColor="@color/color_black1" - android:text="头套设备电量"/> + android:text="@string/i18n_036"/> + android:text="@string/i18n_037"/> + android:text="@string/i18n_038"/> + android:text="@string/i18n_039"/> + android:text="@string/i18n_040"/> - \ No newline at end of file + diff --git a/app/src/main/res/layout/dialog_input_text.xml b/app/src/main/res/layout/dialog_input_text.xml index efdcca0..3a41fd4 100644 --- a/app/src/main/res/layout/dialog_input_text.xml +++ b/app/src/main/res/layout/dialog_input_text.xml @@ -74,7 +74,7 @@ android:layout_height="match_parent" android:layout_weight="1" android:gravity="center" - android:text="取消" + android:text="@string/i18n_041" android:textColor="@color/color_333333" android:textSize="16sp" /> @@ -89,7 +89,7 @@ android:layout_height="match_parent" android:layout_weight="1" android:gravity="center" - android:text="确认修改" + android:text="@string/i18n_042" android:textColor="@color/color_zhu" android:textSize="16sp" android:textStyle="bold" /> diff --git a/app/src/main/res/layout/dialog_loading.xml b/app/src/main/res/layout/dialog_loading.xml index 97784e1..fc0187b 100644 --- a/app/src/main/res/layout/dialog_loading.xml +++ b/app/src/main/res/layout/dialog_loading.xml @@ -22,7 +22,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="6dp" - android:text="加载中..." + android:text="@string/i18n_043" android:textColor="@color/bottom_nav_selected" android:textSize="12sp" /> diff --git a/app/src/main/res/layout/forget_psd_layout.xml b/app/src/main/res/layout/forget_psd_layout.xml index 44b87e3..a62af71 100644 --- a/app/src/main/res/layout/forget_psd_layout.xml +++ b/app/src/main/res/layout/forget_psd_layout.xml @@ -76,7 +76,7 @@ android:layout_height="match_parent" android:layout_weight="1" android:background="@null" - android:hint="请输入手机号" + android:hint="@string/i18n_044" android:inputType="phone" android:layout_marginStart="8dp" android:maxLines="1" @@ -89,7 +89,7 @@ android:layout_gravity="end" android:layout_marginEnd="20dp" android:gravity="center" - android:text="邮箱" + android:text="@string/i18n_018" android:textColor="@color/color_zhu" android:textSize="13sp" /> @@ -117,7 +117,7 @@ android:layout_weight="1" android:layout_marginStart="8dp" android:background="@null" - android:hint="请输入验证码" + android:hint="@string/i18n_019" android:inputType="number" android:maxLines="1" android:textColorHint="@color/color_999999" @@ -131,7 +131,7 @@ android:backgroundTint="@null" android:stateListAnimator="@null" android:gravity="center" - android:text="获取验证码" + android:text="@string/register_get_code" android:textAllCaps="false" android:textColor="@color/color_white" android:textSize="12sp" /> @@ -152,7 +152,7 @@ android:layout_height="match_parent" android:layout_weight="1" android:background="@null" - android:hint="请输入8-20位密码" + android:hint="@string/i18n_045" android:inputType="textPassword" android:textColorHint="@color/color_999999" android:textSize="16sp" /> @@ -162,7 +162,7 @@ android:layout_width="24dp" android:layout_height="24dp" android:src="@mipmap/icon_login_show" - android:contentDescription="显示密码" /> + android:contentDescription="@string/register_show_password" /> @@ -190,7 +190,7 @@ android:layout_width="24dp" android:layout_height="24dp" android:src="@mipmap/icon_login_show" - android:contentDescription="显示密码" /> + android:contentDescription="@string/register_show_password" /> @@ -243,7 +243,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="6dp" - android:text="包含大小写字母和数字" + android:text="@string/i18n_049" android:textColor="@color/white" android:textSize="14sp" /> @@ -262,7 +262,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="6dp" - android:text="不能与当前密码相同" + android:text="@string/i18n_050" android:textColor="@color/white" android:textSize="14sp" /> @@ -280,7 +280,7 @@ android:background="@drawable/shape_login_field_bg" android:backgroundTint="@null" android:stateListAnimator="@null" - android:text="确定" + android:text="@string/i18n_027" android:textAllCaps="false" android:textColor="@color/color_zhu" android:textSize="17sp" /> diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 3350226..4b0b26e 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -52,7 +52,8 @@ + android:layout_height="wrap_content" + > diff --git a/app/src/main/res/layout/fragment_plan.xml b/app/src/main/res/layout/fragment_plan.xml index 65f4afb..9acf498 100644 --- a/app/src/main/res/layout/fragment_plan.xml +++ b/app/src/main/res/layout/fragment_plan.xml @@ -19,7 +19,7 @@ android:layout_marginTop="8dp" android:contentDescription="@string/back_button_description" android:padding="12dp" - android:src="@drawable/img_back_black" /> + android:src="@drawable/img_back_black_auto_mirrored" /> - \ No newline at end of file + diff --git a/app/src/main/res/layout/include_activity_back_greenbg_layout.xml b/app/src/main/res/layout/include_activity_back_greenbg_layout.xml index 1e945ac..d4eb7b7 100644 --- a/app/src/main/res/layout/include_activity_back_greenbg_layout.xml +++ b/app/src/main/res/layout/include_activity_back_greenbg_layout.xml @@ -12,8 +12,8 @@ android:layout_width="22dp" android:layout_height="match_parent" android:layout_centerVertical="true" - android:src="@drawable/img_back_black" - android:layout_marginLeft="10dp"/> + android:src="@drawable/img_back_black_auto_mirrored" + android:layout_marginStart="10dp"/> diff --git a/app/src/main/res/layout/include_activity_back_whitebg_layout.xml b/app/src/main/res/layout/include_activity_back_whitebg_layout.xml index 88d1815..c37b2b4 100644 --- a/app/src/main/res/layout/include_activity_back_whitebg_layout.xml +++ b/app/src/main/res/layout/include_activity_back_whitebg_layout.xml @@ -12,8 +12,8 @@ android:layout_width="22dp" android:layout_height="match_parent" android:layout_centerVertical="true" - android:src="@drawable/img_back_black" - android:layout_marginLeft="10dp"/> + android:src="@drawable/img_back_black_auto_mirrored" + android:layout_marginStart="10dp"/> diff --git a/app/src/main/res/layout/include_title_history_layout.xml b/app/src/main/res/layout/include_title_history_layout.xml index 372da75..ba45356 100644 --- a/app/src/main/res/layout/include_title_history_layout.xml +++ b/app/src/main/res/layout/include_title_history_layout.xml @@ -11,7 +11,7 @@ android:layout_alignParentStart="true" android:layout_centerVertical="true" android:layout_marginStart="10dp" - android:src="@drawable/img_back_black" /> + android:src="@drawable/img_back_black_auto_mirrored" /> + android:src="@drawable/img_back_black_auto_mirrored" /> + android:src="@drawable/img_back_black_auto_mirrored" /> + android:src="@drawable/img_back_black_auto_mirrored" /> + android:text="@string/i18n_053"/> @@ -83,13 +83,13 @@ android:textStyle="bold" android:textSize="16sp" android:textColor="@color/white" - android:text="修改密码"/> + android:text="@string/modify_password"/> + android:src="@drawable/icon_mine_point_auto_mirrored"/> + android:text="@string/i18n_054"/> + android:src="@drawable/icon_mine_point_auto_mirrored"/> + android:text="@string/i18n_055"/> + android:src="@drawable/icon_mine_point_auto_mirrored"/> + android:text="@string/i18n_056"/> + android:src="@drawable/icon_mine_point_auto_mirrored"/> + android:text="@string/i18n_057"/> + android:src="@drawable/icon_mine_point_auto_mirrored"/> + android:text="@string/i18n_058"/> + android:src="@drawable/icon_mine_point_auto_mirrored"/> + android:text="@string/i18n_059"/> + android:src="@drawable/icon_mine_point_auto_mirrored"/> + android:text="@string/i18n_060"/> + android:src="@drawable/icon_mine_point_auto_mirrored"/> - \ No newline at end of file + diff --git a/app/src/main/res/layout/modify_psd_layout.xml b/app/src/main/res/layout/modify_psd_layout.xml index 1f6f406..b5b077a 100644 --- a/app/src/main/res/layout/modify_psd_layout.xml +++ b/app/src/main/res/layout/modify_psd_layout.xml @@ -39,16 +39,20 @@ android:layout_marginTop="20dp" android:gravity="center_vertical" android:background="@drawable/shape_login_field_bg" - android:paddingStart="44dp" + android:paddingStart="20dp" android:paddingEnd="16dp"> - + @@ -66,16 +70,20 @@ android:layout_marginTop="20dp" android:gravity="center_vertical" android:background="@drawable/shape_login_field_bg" - android:paddingStart="44dp" + android:paddingStart="20dp" android:paddingEnd="16dp"> - + @@ -85,7 +93,7 @@ android:layout_width="24dp" android:layout_height="24dp" android:src="@mipmap/icon_login_show" - android:contentDescription="显示密码" /> + android:contentDescription="@string/register_show_password" /> - + @@ -113,7 +125,7 @@ android:layout_width="24dp" android:layout_height="24dp" android:src="@mipmap/icon_login_show" - android:contentDescription="显示密码" /> + android:contentDescription="@string/register_show_password" /> @@ -166,7 +178,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="6dp" - android:text="包含大小写字母和数字" + android:text="@string/i18n_049" android:textColor="@color/white" android:textSize="14sp" /> @@ -185,7 +197,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="6dp" - android:text="不能与当前密码相同" + android:text="@string/i18n_050" android:textColor="@color/white" android:textSize="14sp" /> @@ -203,7 +215,7 @@ android:background="@drawable/shape_login_field_bg" android:backgroundTint="@null" android:stateListAnimator="@null" - android:text="确定" + android:text="@string/i18n_027" android:textAllCaps="false" android:textColor="@color/color_zhu" android:textSize="17sp" /> diff --git a/app/src/main/res/layout/permission_confirm_dialog.xml b/app/src/main/res/layout/permission_confirm_dialog.xml index 78a9190..06df714 100644 --- a/app/src/main/res/layout/permission_confirm_dialog.xml +++ b/app/src/main/res/layout/permission_confirm_dialog.xml @@ -13,7 +13,7 @@ android:layout_marginTop="28dp" android:layout_marginHorizontal="24dp" android:gravity="center" - android:text="提示" + android:text="@string/i18n_063" android:textColor="@color/color_333333" android:textSize="18sp" android:textStyle="bold" /> @@ -27,7 +27,7 @@ android:layout_marginHorizontal="24dp" android:gravity="center" android:lineSpacingExtra="4dp" - android:text="内容内容" + android:text="@string/i18n_064" android:textColor="@color/color_999999" android:textSize="15sp" /> @@ -47,7 +47,7 @@ android:layout_height="match_parent" android:layout_weight="1" android:gravity="center" - android:text="取消" + android:text="@string/i18n_041" android:textColor="@color/color_333333" android:textSize="16sp" /> @@ -62,7 +62,7 @@ android:layout_height="match_parent" android:layout_weight="1" android:gravity="center" - android:text="确定" + android:text="@string/i18n_027" android:textColor="@color/color_zhu" android:textSize="16sp" android:textStyle="bold" /> diff --git a/app/src/main/res/layout/popup_add_device.xml b/app/src/main/res/layout/popup_add_device.xml index e4d959c..d212876 100644 --- a/app/src/main/res/layout/popup_add_device.xml +++ b/app/src/main/res/layout/popup_add_device.xml @@ -19,7 +19,7 @@ @@ -28,7 +28,7 @@ android:layout_height="wrap_content" android:layout_marginStart="10dp" android:duplicateParentState="true" - android:text="手动添加" + android:text="@string/i18n_065" android:textColor="@color/popup_add_item_text" android:textSize="16sp" /> @@ -47,7 +47,7 @@ @@ -56,7 +56,7 @@ android:layout_height="wrap_content" android:layout_marginStart="10dp" android:duplicateParentState="true" - android:text="扫一扫" + android:text="@string/i18n_066" android:textColor="@color/popup_add_item_text" android:textSize="16sp" /> diff --git a/app/src/main/res/layout/popup_device_action.xml b/app/src/main/res/layout/popup_device_action.xml index 9b50f95..a891dd8 100644 --- a/app/src/main/res/layout/popup_device_action.xml +++ b/app/src/main/res/layout/popup_device_action.xml @@ -23,7 +23,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="10dp" - android:text="重命名" + android:text="@string/device_action_rename" android:textColor="#FFFFFF" android:textSize="16sp" /> @@ -46,7 +46,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="10dp" - android:text="解绑设备" + android:text="@string/device_action_unbind" android:textColor="#FFFFFF" android:textSize="16sp" /> diff --git a/app/src/main/res/layout/register_layout.xml b/app/src/main/res/layout/register_layout.xml index ac092a3..0bb0b18 100644 --- a/app/src/main/res/layout/register_layout.xml +++ b/app/src/main/res/layout/register_layout.xml @@ -76,7 +76,7 @@ android:layout_height="match_parent" android:layout_weight="2" android:background="@null" - android:hint="请输入手机号" + android:hint="@string/i18n_044" android:inputType="phone" android:layout_marginStart="8dp" android:maxLines="1" @@ -90,7 +90,7 @@ android:layout_gravity="end" android:layout_marginEnd="20dp" android:gravity="center" - android:text="邮箱" + android:text="@string/i18n_018" android:textColor="@color/color_zhu" android:textSize="13sp" /> @@ -118,7 +118,7 @@ android:layout_weight="1" android:layout_marginStart="8dp" android:background="@null" - android:hint="请输入验证码" + android:hint="@string/i18n_019" android:inputType="number" android:maxLines="1" android:textColorHint="@color/color_999999" @@ -153,7 +153,7 @@ android:layout_height="match_parent" android:layout_weight="1" android:background="@null" - android:hint="请输入8-20位密码" + android:hint="@string/i18n_045" android:inputType="textPassword" android:textColorHint="@color/color_999999" android:textSize="16sp" /> @@ -163,7 +163,7 @@ android:layout_width="24dp" android:layout_height="24dp" android:src="@mipmap/icon_login_show" - android:contentDescription="显示密码" /> + android:contentDescription="@string/register_show_password" /> @@ -191,7 +191,7 @@ android:layout_width="24dp" android:layout_height="24dp" android:src="@mipmap/icon_login_show" - android:contentDescription="显示密码" /> + android:contentDescription="@string/register_show_password" /> @@ -244,7 +244,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="6dp" - android:text="包含大小写字母和数字" + android:text="@string/i18n_049" android:textColor="@color/white" android:textSize="14sp" /> @@ -262,7 +262,7 @@ android:background="@drawable/shape_login_field_bg" android:backgroundTint="@null" android:stateListAnimator="@null" - android:text="确定" + android:text="@string/i18n_027" android:textAllCaps="false" android:textColor="@color/color_zhu" android:textSize="17sp" /> diff --git a/app/src/main/res/layout/update_user_phone_layout.xml b/app/src/main/res/layout/update_user_phone_layout.xml index cd258c9..1e6eaca 100644 --- a/app/src/main/res/layout/update_user_phone_layout.xml +++ b/app/src/main/res/layout/update_user_phone_layout.xml @@ -40,7 +40,7 @@ android:layout_height="wrap_content" android:textSize="13sp" android:textColor="@color/color_black1" - android:text="当前绑定账号"/> + android:text="@string/i18n_067"/> @@ -148,7 +148,7 @@ android:layout_height="match_parent" android:layout_weight="1" android:background="@null" - android:hint="请输入手机号" + android:hint="@string/i18n_044" android:inputType="phone" android:layout_marginStart="8dp" android:maxLines="1" @@ -181,7 +181,7 @@ android:layout_weight="1" android:layout_marginStart="8dp" android:background="@null" - android:hint="请输入验证码" + android:hint="@string/i18n_019" android:inputType="number" android:maxLines="1" android:textColorHint="@color/color_999999" @@ -216,7 +216,7 @@ android:background="@drawable/shape_login_field_bg" android:backgroundTint="@null" android:stateListAnimator="@null" - android:text="确认修改" + android:text="@string/i18n_042" android:textAllCaps="false" android:textColor="@color/color_zhu" android:textSize="17sp" /> diff --git a/app/src/main/res/mipmap-xhdpi/icon_language_china.png b/app/src/main/res/mipmap-xhdpi/icon_language_china.png new file mode 100644 index 0000000..f68e913 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/icon_language_china.png differ diff --git a/app/src/main/res/mipmap-xhdpi/icon_language_en.png b/app/src/main/res/mipmap-xhdpi/icon_language_en.png new file mode 100644 index 0000000..cb639c5 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/icon_language_en.png differ diff --git a/app/src/main/res/mipmap-xhdpi/icon_language_st.png b/app/src/main/res/mipmap-xhdpi/icon_language_st.png new file mode 100644 index 0000000..627651c Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/icon_language_st.png differ diff --git a/app/src/main/res/mipmap-xhdpi/icon_login_user_uncheck.png b/app/src/main/res/mipmap-xhdpi/icon_login_user_uncheck.png new file mode 100644 index 0000000..5db7d8c Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/icon_login_user_uncheck.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/icon_login_user_uncheck.png b/app/src/main/res/mipmap-xxhdpi/icon_login_user_uncheck.png new file mode 100644 index 0000000..0f6ac2c Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/icon_login_user_uncheck.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/icon_login_user_uncheck.png b/app/src/main/res/mipmap-xxxhdpi/icon_login_user_uncheck.png new file mode 100644 index 0000000..16ce703 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/icon_login_user_uncheck.png differ diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml new file mode 100644 index 0000000..081e62c --- /dev/null +++ b/app/src/main/res/values-ar/strings.xml @@ -0,0 +1,292 @@ + + + JUMHABI Saqr GPS Smart Falcon Hood + JUMHABI Saqr GPS Smart Falcon Hood + تسجيل الدخول + أدخل الحساب وكلمة المرور + رقم الهاتف + كلمة المرور + تسجيل الدخول + التسجيل برقم الهاتف + ربط الجهاز + أدخل الحساب + أدخل كلمة المرور + إظهار كلمة المرور + نسيت كلمة المرور؟ + نسيت كلمة المرور + التبديل إلى الدخول برمز الهاتف + التبديل إلى الدخول بكلمة المرور + لقد قرأت ووافقت على اتفاقية المستخدم وسياسة الخصوصية وشروط التخصيص + لقد قرأت ووافقت على اتفاقية المستخدم وسياسة الخصوصية + اتفاقية المستخدم + سياسة الخصوصية + ليس لديك حساب؟ سجل الآن + إرسال الرمز + إعادة الإرسال بعد %1$d ثانية + لا مزيد من البيانات + أجهزتي + يتوفر إصدار جديد: %1$s + فشل التنزيل: %1$s + تم النقر على العنوان: %1$s + تم النقر على المعرف: %1$d + تم النقر على الحالة: %1$s + تم النقر على وقت التحديث: %1$s + تم النقر على: %1$s + النص المدخل: %1$s + المحدد: %1$s + الهاتف: %1$s + البريد الإلكتروني: %1$s + الحساب: %1$s + نوع المستخدم: %1$s + وقت التسجيل: %1$s + تم منح الإذن: %1$s + تم رفض الإذن: %1$s + معرف الجهاز: %1$s + خطأ في استجابة API: %1$d + غير مرتبط + غير متوفر + تعرض هذه الصفحة نظرة عامة على المشروع لتوضيح قيود التخطيط الشائعة. راجع تعليقات XML للتفاصيل. + ترقية الري الذكي + قيد التنفيذ + المسؤول: متعلم Android + التقدم + تفاصيل المشروع + إخفاء التفاصيل + وقت البدء + القسم المسؤول + فريق تطوير الجوال + هذا الأسبوع + 12 مهمة + 9 مكتملة + 3 معلقة + منطقة بنسبة 16:9 + العرض مقيد من الجانبين يحسب الارتفاع تلقائيا بنسبة 16:9 + الفرق الأساسي: يركز LinearLayout على الترتيب، ويركز RelativeLayout على القواعد النسبية، بينما يستخدم ConstraintLayout قيودا ثنائية الاتجاه وأدوات مساعدة لوصف العلاقات، مما يقلل عادة تداخل التخطيطات في الشاشات المعقدة. + البريد الإلكتروني + أدخل رمز التحقق + مثال ConstraintLayout + ربط + أدخل رمز الجهاز + اسم الجهاز + أدخل اسم الجهاز + حتى 20 حرفا + يوجد رمز IMEI بجوار ملصق رمز QR على الجهاز. + موافق + مفتاح غطاء الصقر + الجهاز A + متصل + آخر تحديث + قوة الإشارة + قوية + اتصال الغطاء + متصل + بطارية الغطاء + بطارية حلقة الساق + الموقع الحالي + فوق متنزه طريق تيانموشان الوطني للغابات، حي شيهو، هانغتشو، تشجيانغ، على ارتفاع 236 مترا. + عرض الخريطة + إلغاء + تأكيد التغييرات + جار التحميل... + أدخل رقم الهاتف + أدخل كلمة مرور من 8 إلى 20 حرفا + أعد إدخال كلمة المرور الجديدة + متطلبات كلمة المرور + من 8 إلى 20 حرفا + تتضمن أحرفا كبيرة وصغيرة وأرقاما + يجب أن تختلف عن كلمة المرور الحالية + (الوقت المتبقي) + العنوان + اسم المستخدم + إعادة الربط + مسح ذاكرة التخزين المؤقت + معلومات الإصدار + اتفاقية الخصوصية + اتفاقية المستخدم + اللغة + حذف الحساب + تسجيل الخروج + أدخل كلمة المرور الحالية + تنبيه + المحتوى + إضافة يدوية + مسح رمز QR + الحساب المرتبط حاليا + الإذن مطلوب + اسمح للتطبيق باستخدام الأذونات المطلوبة + تم رفض الإذن + فعل الأذونات المطلوبة من الإعدادات + الانتقال إلى الإعدادات + أدخل المحتوى + تفاصيل الجهاز + معلومات الجهاز غير صالحة + الجهاز غير متصل + إعادة تسمية الجهاز + حتى 20 حرفا؛ يدعم الحروف والأرقام والرموز. + هل تريد إلغاء ربط هذا الجهاز؟ + لا يتوفر موقع + إذن الكاميرا + يتطلب المسح إذن الكاميرا + لم يتم التعرف على محتوى + فشل المسح + الهاتف + أدخل البريد الإلكتروني + رقم الهاتف مطلوب + البريد الإلكتروني مطلوب + رمز التحقق مطلوب + كلمة المرور مطلوبة + أعد إدخال كلمة المرور + كلمتا المرور غير متطابقتين + يجب أن تتكون كلمة المرور من 8 إلى 20 حرفا وأن تتضمن أحرفا كبيرة وصغيرة وأرقاما + تم تغيير كلمة المرور + اقرأ ووافق على اتفاقية المستخدم وسياسة الخصوصية أولا + الحساب مطلوب + التبديل إلى الدخول برمز البريد الإلكتروني + التبديل إلى الدخول بالهاتف وكلمة المرور + التبديل إلى الدخول بالبريد وكلمة المرور + تم تسجيل الدخول + تم إنشاء الحساب + إذنا الكاميرا والميكروفون + يتطلب التقاط الصور وتسجيل الصوت إذني الكاميرا والميكروفون + إذن الموقع + تتطلب ميزات الموقع الوصول إلى موقعك + تأكيد الإجراء + هل تريد بالتأكيد تنفيذ هذا الإجراء؟ + تم الإلغاء + تم التأكيد + أدخل نص الاختبار + تم إلغاء الإدخال + لم يتم تعيين اسم مستعار + هل تريد مسح ذاكرة التخزين المؤقت للتطبيق؟ + هل تريد حذف حسابك؟ + بعد الحذف، سيحذف حسابك وتلغى روابط جميع الأجهزة + وتمسح بياناتك الشخصية. لا يمكن التراجع عن ذلك، لذا تابع بحذر. + تأكيد الحذف + هل تريد تسجيل الخروج من الحساب الحالي؟ + لم يتم تعيين اسم المستخدم + رقم الهاتف غير مرتبط + تغيير الهاتف المرتبط + تغيير البريد المرتبط + تم مسح ذاكرة التخزين المؤقت + كلمة المرور الحالية مطلوبة + كلمة المرور الجديدة مطلوبة + يجب أن تختلف كلمة المرور الجديدة عن الحالية + كلمتا المرور الجديدتان غير متطابقتين + تغيير الهاتف المرتبط + تغيير البريد المرتبط + رقم الهاتف الجديد مطلوب + البريد الإلكتروني الجديد مطلوب + أدخل رقم الهاتف الجديد + أدخل البريد الإلكتروني الجديد + سياسة الخصوصية + معلمات الصفحة غير صالحة + تعذر فتح هذا الرابط + فشل تحميل الصفحة. حاول لاحقا. + ملف الصورة غير موجود + فشل ضغط الصورة + لديك أحدث إصدار + يلزم إذن الإشعارات لإظهار تقدم التنزيل + هل تريد تنزيل التحديث الآن؟ + تنزيل + رابط التنزيل فارغ + تحديث التطبيق + استجابة التنزيل فارغة + جار تنزيل التحديث + اكتمل التنزيل + فشل إرسال رمز التحقق + فشل طلب الشبكة + فشل التحقق من الهاتف + فشل التحقق من البريد الإلكتروني + فشل تغيير رقم الهاتف + فشل تغيير البريد الإلكتروني + فشل تسجيل الخروج + فشل حذف الحساب + فشل تحميل الملف الشخصي + فشل التحقق من التحديثات + فشل تغيير كلمة المرور + تم إرسال رمز التحقق + تم التحقق من الهاتف + تم التحقق من البريد الإلكتروني + تم تغيير رقم الهاتف + تم تغيير البريد الإلكتروني + تم تسجيل الخروج + تم حذف الحساب + خطأ في الخدمة + فشل تسجيل الدخول + فشل إنشاء الحساب + جهاز بلا اسم + غير متصل + ضعيفة + متوسطة + مفصول + فشل تحميل قائمة الأجهزة + فشل تحميل تفاصيل الجهاز + فشل التحكم في الغطاء + فشل ربط الجهاز + فشل تغيير اسم الجهاز + فشل إلغاء ربط الجهاز + تم تحديث حالة الغطاء + تم ربط الجهاز + معرف الجهاز واسمه مطلوبان + تم تغيير اسم الجهاز + معرف الجهاز مطلوب + تم إلغاء ربط الجهاز + إظهار التفاصيل + سجلات فحص الجهاز + مكتمل + خطة الري التلقائي + قيد التشغيل + مزامنة بيانات المستشعر + معلق + نسخ إعدادات النظام احتياطيا + مهمة ترقية البرنامج الثابت + فشل + سجل نشاط المستخدم + السنة + الشهر + اليوم + الساعة + الدقيقة + الثانية + لا يمكن أن يكون وقت البدء بعد وقت الانتهاء + يجب تعيين مستمع لنتيجة الاختيار + 中文 + English + العربية + العربية غير متاحة حاليا + سيحفظ الرمز المميز تلقائيا بعد تسجيل الدخول + الانتقال إلى تسجيل الدخول + صورة رأس صفحة الخطط + رجوع + الملف الشخصي + إعادة التحميل + التحقق من التحديثات + اختبار إذني الكاميرا والميكروفون + اختبار إذن الموقع + اختبار مربع الحوار + اختبار مربع الإدخال + قائمة الأجهزة + الخطط + السجل + حسابي + الرئيسية + إعادة التسمية + إلغاء ربط الجهاز + إلغاء المسح + الخطط + السجل + حسابي + فتح مركز المكونات + تغيير كلمة المرور + اختر الدولة أو المنطقة + اختيار التاريخ والوقت + اختر التاريخ + اختر الوقت + اختر التاريخ والوقت + لم يتم الاختيار + مثال قائمة RecyclerView + مثال مكون القائمة + المعرف: %1$d + الحالة: %1$s + آخر تحديث: %1$s + diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml new file mode 100644 index 0000000..48f1deb --- /dev/null +++ b/app/src/main/res/values-en/strings.xml @@ -0,0 +1,292 @@ + + + JUMHABI Saqr GPS Smart Falcon Hood + JUMHABI Saqr GPS Smart Falcon Hood + Sign In + Enter your account and password + Phone Number + Password + Sign In + Sign Up with Phone + Bind Device + Enter account + Enter password + Show password + Forgot password? + Forgot Password + Switch to phone verification login + Switch to password login + I have read and agree to the User Agreement, Privacy Policy, and Customization Terms + I have read and agree to the User Agreement and Privacy Policy + User Agreement + Privacy Policy + No account? Sign up now + Get Code + Get again in %1$d seconds + No more data + My Devices + New version found: %1$s + Download failed: %1$s + Title clicked: %1$s + ID clicked: %1$d + Status clicked: %1$s + Update time clicked: %1$s + Clicked: %1$s + Entered: %1$s + Selected: %1$s + Phone: %1$s + Email: %1$s + Account: %1$s + User type: %1$s + Registration time: %1$s + Permission granted: %1$s + Permission denied: %1$s + Device ID: %1$s + API response error: %1$d + Not bound + N/A + This project overview demonstrates common layout constraints. See the XML comments for details. + Smart Irrigation Upgrade + In Progress + Owner: Android Learner + Progress + Project Details + Hide Details + Start Time + Responsible Department + Mobile Development Team + This Week + 12 Tasks + 9 Completed + 3 Pending + 16:9 Area + Width constrained by both sides Height calculated automatically at 16:9 + Key difference: LinearLayout focuses on arrangement, RelativeLayout on relative rules, and ConstraintLayout uses bidirectional constraints and helper tools to define relationships. Complex screens usually require fewer nested layouts. + Email + Enter verification code + ConstraintLayout Demo + Bind + Enter device code + Device Name + Enter device name + Up to 20 characters + The IMEI is next to the QR code label on the device. + OK + Hood Switch + Device A + Online + Updated + Signal Strength + Strong + Hood Connection + Connected + Hood Battery + Leg Band Battery + Current Location + Above Tianmushan Road National Forest Park, Xihu District, Hangzhou, Zhejiang, altitude 236 m. + View Map + Cancel + Confirm Changes + Loading... + Enter phone number + Enter an 8-20 character password + Re-enter the new password + Password Requirements + 8-20 characters + Includes uppercase, lowercase, and numbers + Must differ from the current password + (Time remaining) + Title + Username + Rebind + Clear Cache + Version Info + Privacy Agreement + User Agreement + Language + Delete Account + Sign Out + Enter current password + Notice + Content + Add Manually + Scan QR Code + Currently Bound Account + Permission Required + Allow the app to use the required permissions + Permission Denied + Enable the required permissions in Settings + Go to Settings + Enter content + Device Details + Invalid device information + Device is offline + Rename Device + Up to 20 characters; letters, numbers, and symbols supported. + Unbind this device? + No location available + Camera Permission + Scanning requires camera permission + No content recognized + Scan failed + Phone + Enter email + Phone number is required + Email is required + Verification code is required + Password is required + Re-enter password + Passwords do not match + Password must be 8-20 characters and include uppercase, lowercase, and numbers + Password changed successfully + Please read and agree to the User Agreement and Privacy Policy first + Account is required + Switch to email verification login + Switch to phone password login + Switch to email password login + Signed in successfully + Signed up successfully + Camera and Microphone Permissions + Taking photos and recording audio require camera and microphone permissions + Location Permission + Location features require access to your location + Confirm Action + Are you sure you want to perform this action? + Canceled + Confirmed + Enter test content + Input canceled + Nickname not set + Clear the app cache? + Delete your account? + After deletion, your account will be deleted and all devices + unbound and personal data cleared. This cannot be undone. Proceed with caution. + Confirm Deletion + Sign out of the current account? + Username not set + Phone number not bound + Change Bound Phone + Change Bound Email + Cache cleared + Current password is required + New password is required + New password must differ from the current password + New passwords do not match + Change Bound Phone + Change Bound Email + New phone number is required + New email is required + Enter new phone number + Enter new email + Privacy Policy + Invalid page parameters + Unable to open this link + Page failed to load. Try again later. + Image file not found + Image compression failed + You have the latest version + Notification permission is required to show download progress + Download the update now? + Download + Download URL is empty + App Update + Empty download response + Downloading update + Download complete + Failed to send verification code + Network request failed + Phone verification failed + Email verification failed + Failed to change phone number + Failed to change email + Failed to sign out + Failed to delete account + Failed to get profile + Failed to check for updates + Failed to change password + Verification code sent + Phone verified + Email verified + Phone number changed + Email changed + Signed out successfully + Account deleted + Service error + Sign-in failed + Sign-up failed + Unnamed Device + Offline + Weak + Fair + Disconnected + Failed to get device list + Failed to get device details + Failed to control hood + Failed to bind device + Failed to rename device + Failed to unbind device + Hood status updated + Device bound successfully + Device ID and name are required + Device renamed successfully + Device ID is required + Device unbound successfully + Show Details + Device Inspection Records + Completed + Automatic Watering Plan + Running + Sensor Data Sync + Pending + System Configuration Backup + Firmware Upgrade Task + Failed + User Activity Log + Year + Month + Day + Hour + Minute + Second + Start time cannot be later than end time + A selection listener must be set + 中文 + English + العربية + Arabic is not available yet + Token will be saved automatically after sign-in + Go to Sign In + Plan page header image + Back + Profile + Reload + Check for Updates + Test camera and microphone permissions + Test location permission + Test Standard Dialog + Test Input Dialog + Device List + Plans + History + Profile + Home + Rename + Unbind Device + Cancel Scan + Plans + History + Profile + Open Component Center + Change Password + Select Country or Region + Date & Time Picker + Select Date + Select Time + Select Date and Time + Not selected + RecyclerView List Demo + List Component Demo + ID: %1$d + Status: %1$s + Updated: %1$s + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f61d6f2..3a437fc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -16,19 +16,252 @@ 切换手机号验证码登录 切换密码登录 我已阅读并同意用户协议和隐私政策以及定制条款 + 我已阅读并同意《用户协议》《隐私政策》 + 《用户协议》 + 《隐私政策》 没有账号?立即注册 获取验证码 %1$d秒后重新获取 已经到底了 我的设备 + + 发现新版本 %1$s + 下载失败:%1$s + 点击了标题:%1$s + 点击了编号:%1$d + 点击了状态:%1$s + 点击了更新时间:%1$s + 点击了:%1$s + 输入内容:%1$s + 已选择:%1$s + 手机号:%1$s + 邮箱:%1$s + 账号:%1$s + 用户类型:%1$s + 注册时间:%1$s + 权限获取成功:%1$s + 权限被拒绝:%1$s + 设备编号:%1$s + 接口响应异常:%1$d + 未绑定 + 暂无 + 下面用一个项目概览页演示常用约束。可以结合 XML 注释观察效果。 + 智能灌溉升级项目 + 进行中 + 负责人:Android 学习者 + 完成进度 + 项目详情 + 隐藏详情 + 开始时间 + 责任部门很长 + 移动研发组 + 本周数据 + 12\n任务 + 9\n完成 + 3\n待处理 + 16:9 比例区域 + 宽度由两侧约束\n高度按 16:9 自动计算 + 核心区别:LinearLayout 强调排列,RelativeLayout 强调相对规则;ConstraintLayout 用双向约束与辅助工具描述关系,复杂页面通常能减少嵌套层级。 + 邮箱 + 请输入验证码 + ConstraintLayout 综合示例 + 绑定 + 请输入设备编码 + 设备名称 + 请输入设备名称 + 最多支持20个字符 + IMEI码可在设备机身上的二维码标签旁边找到 + 确定 + 眼罩开关 + 设备A + 在线 + 更新时间 + 设备信号强度 + 强 + 头套连接状态 + 已连接 + 头套设备电量 + 脚环设备电量 + 当前位置 + 浙江省杭州市西湖区天目山路国家森林公园上空,海拔236米。 + 查看地图 + 取消 + 确认修改 + 加载中... + 请输入手机号 + 请输入8-20位密码 + 请再次输入新密码 + 密码规则 + 长度在8-20个字符之间 + 包含大小写字母和数字 + 不能与当前密码相同 + (剩余时间) + 标题名称 + 用户名 + 换绑 + 清除缓存 + 版本信息 + 隐私协议 + 用户协议 + 切换语言 + 注销账号 + 退出登录 + 请输入当前密码 + 提示 + 内容内容 + 手动添加 + 扫一扫 + 当前绑定账号 + 需要权限 + 请允许应用使用相关权限 + 权限被拒绝 + 请在应用设置中开启所需权限 + 去设置 + 请输入内容 + 设备详情 + 设备信息无效 + 设备已经离线 + 重命名设备 + 最多支持20个字符,支持中英文、数字和字符。 + 确定要解绑该设备? + 暂无定位 + 相机权限 + 扫一扫功能需要使用相机权限 + 未识别到内容 + 扫码失败 + 手机 + 请输入邮箱 + 手机号不能为空 + 邮箱不能为空 + 验证码不能为空 + 密码不能为空 + 请再次输入密码 + 两次输入的密码不一致 + 密码需为8-20位,且包含大小写字母和数字 + 密码修改成功 + 请先阅读并同意用户协议和隐私政策 + 账号不能为空 + 切换邮箱验证码登录 + 切换手机号密码登录 + 切换邮箱密码登录 + 登录成功 + 注册成功 + 相机和麦克风权限 + 拍照和录音功能需要使用相机及麦克风权限 + 定位权限 + 定位功能需要获取您的位置信息 + 操作确认 + 确定要执行这项操作吗? + 已取消 + 已点击确定 + 请输入测试内容 + 已取消输入 + 未设置昵称 + 清除当前应用缓存? + 确认注销账号? + 注销账号后,账号将被删除,所有设备 + 解绑、个人信息被清除,无法恢复,请谨慎操作? + 确认注销 + 确定要退出当前账号吗? + 未设置用户名 + 未绑定手机号 + 换绑手机号 + 换绑邮箱 + 缓存清理成功 + 当前密码不能为空 + 新密码不能为空 + 新密码不能与当前密码相同 + 两次输入的新密码不一致 + 更换绑定手机号 + 更换绑定邮箱 + 新手机号不能为空 + 新邮箱不能为空 + 请输入新手机号 + 请输入新邮箱 + 隐私政策 + 页面参数无效 + 无法打开此链接 + 页面加载失败,请稍后重试 + 图片文件不存在 + 图片压缩失败 + 当前已是最新版本 + 需要通知权限才能显示下载进度 + 是否立即下载更新? + 下载 + 下载地址为空 + 应用更新 + 下载响应为空 + 正在下载更新 + 下载完成 + 验证码发送失败 + 网络请求失败 + 当前手机号验证失败 + 当前邮箱验证失败 + 手机号更换失败 + 邮箱更换失败 + 退出登录失败 + 注销账号失败 + 获取个人信息失败 + 版本检测失败 + 密码修改失败 + 验证码已发送 + 当前手机号验证成功 + 当前邮箱验证成功 + 手机号更换成功 + 邮箱更换成功 + 退出登录成功 + 账号注销成功 + 服务异常 + 登录失败 + 注册失败 + 未命名设备 + 离线 + 弱 + 一般 + 断开 + 获取设备列表失败 + 获取设备详情失败 + 控制眼罩失败 + 绑定设备失败 + 设备重命名失败 + 设备解绑失败 + 眼罩状态已更新 + 设备绑定成功 + 设备编号和名称不能为空 + 设备重命名成功 + 设备编号不能为空 + 设备解绑成功 + 显示详情 + 设备巡检记录 + 已完成 + 自动浇水计划 + 执行中 + 传感器数据同步 + 待处理 + 系统配置备份 + 固件升级任务 + 失败 + 用户操作日志 + 年 + 月 + 日 + 时 + 分 + 秒 + 开始时间不能晚于结束时间 + 必须设置选择结果监听器 + 中文 + English + العربية + 阿拉伯语暂未开放 - + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index febfdb5..7f862a7 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -11,4 +11,18 @@ true @android:style/Animation.Dialog + + + +