1.map和直播页增加用户和宠物虚线设置

2.优化帮助页面
3.支付页面去掉税的计算
4.GPS: Off 改为GPS: Sleep;
5.map页增加位置更新时间格式:当更新滞后超过2倍的上报间隔时,才显示
(2h 35m ago)”,显示休眠中时,不显示“(2h 35m ago)
6.运动数据改为查看30天数据
7.历史轨迹限制只能看30天数据
This commit is contained in:
yezhiqiu
2026-05-07 12:02:55 +08:00
parent 587697954d
commit 5be446af72
32 changed files with 433 additions and 147 deletions

View File

@@ -28,9 +28,9 @@ android {
applicationId "com.abbidot.tracker"
minSdkVersion 23
targetSdkVersion 35
versionCode 2203
// versionName "2.2.3"
versionName "2.2.3-Beta1"
versionCode 2204
// versionName "2.2.4"
versionName "2.2.4-Beta1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View File

@@ -2,19 +2,50 @@
<body>
<div>
<h3>ABBIDOT Tracker Subscription Terms and Conditions
</h3>
<p>1.Subscription Plans Users can choose between Basic and Prime plans. Basic plan subscribers can upgrade to Prime at any time. Prime plan subscribers can't change to Basic plan.
<p>2.Refund Policy Automatic Refund (Within 48 Hours): Full refund available if canceled within 48 hours of subscription. Processed automatically without manual review. Manual Refund (After 48 Hours): For subscriptions older than 48 hours but within 1 month, refunds require manual approval. Eligibility: Only applicable to annual (1-year) subscriptions. Refunds may be prorated based on usage. No Refund After 1 Month: Subscriptions active for over 1 month are non-refundable.</p>
<p>3.Pausing Subscription & Number Retention Users may pause their subscription. Number Retention: Paused numbers can be retained for up to 1 year. A small fee will apply for number retention during the pause period. Failure to reactivate within 1 year may result in number release.</p>
<p>4.Modifications ABBIDOT reserves the right to update these terms. Users will be notified of changes. By subscribing, you agree to these terms.</p>
<p>For support contact:</p>
<p>support@abbidot.com</p>
<h2>ABBIDOT Tracker Subscription Terms and Conditions</h2>
<hr>
<h3>1. Subscription Plans</h3>
<p>ABBIDOT offers three subscription plans:</p>
<ul>
<li>3-Month Plan</li>
<li>1-Year Plan</li>
<li>2-Year Plan</li>
</ul>
<p>All subscriptions are automatically renewed at the end of each billing cycle unless canceled by the user.</p>
<p>Users may cancel their subscription at any time. After cancellation, the subscription will remain active until the end of the current billing period, and no further charges will be applied.</p>
<p>The device will continue to function normally during the active subscription period.</p>
<hr>
<h3>2. Refund Policy</h3>
<h4>2.1 Automatic Refund (Within 48 Hours)</h4>
<p>All subscription purchases are eligible for a <strong>full automatic refund within 48 hours</strong> of payment. Refunds will be processed automatically without manual review.</p>
<hr>
<h4>2.2 Standard Refund Window (30 Days)</h4>
<p>After the initial 48-hour period, users may request a refund within <strong>30 days of purchase.</strong></p>
<ul>
<li>All subscription plans (3-Month, 1-Year, 2-Year) are eligible</li>
<li>Refund requests are subject to review and approval</li>
<li>Refunds may be partially adjusted based on usage and service consumption</li>
</ul>
<hr>
<h4>2.3 No Refund After 30 Days</h4>
<p>Subscriptions older than 30 days are non-refundable.</p>
<hr>
<h3>3. Subscription Cancellation</h3>
<p>Users may cancel their subscription at any time.</p>
<ul>
<li>Cancellation stops future billing</li>
<li>The current subscription remains active until the end of the billing period</li>
<li>No partial refund is issued after cancellation unless within the refund window</li>
</ul>
<hr>
<h3>4. Policy Updates</h3>
<p>ABBIDOT reserves the right to update or modify these Terms and Conditions at any time. Users will be notified of significant changes.</p>
<p>By subscribing to ABBIDOT services, you agree to these terms.</p>
<hr>
<h3>5. Customer Support</h3>
<p>If you encounter any difficulties, please contact us at:</p>
<p><a title="Mail to subscription@abbidot.com" href="subscription@abbidot.com">subscription@abbidot.com</a></p>
<p>Our support team will assist you as soon as possible.</p>
</div>

View File

@@ -2,8 +2,10 @@ package com.abbidot.tracker.dialog
import android.content.Context
import android.widget.CompoundButton
import com.abbidot.baselibrary.constant.MMKVKey
import com.abbidot.baselibrary.list.BaseRecyclerAdapter
import com.abbidot.baselibrary.util.AppUtils
import com.abbidot.baselibrary.util.MMKVUtil
import com.abbidot.tracker.base.BaseDialog
import com.abbidot.tracker.bean.DataBean
import com.abbidot.tracker.databinding.DialogSelectMapTypeLayoutBinding
@@ -18,12 +20,14 @@ import com.abbidot.tracker.util.ViewUtil
class SelectMapTypeDialog(
context: Context,
adapter: BaseRecyclerAdapter<*>,
checkedChangeListener: CompoundButton.OnCheckedChangeListener? = null
fenceCheckedChangeListener: CompoundButton.OnCheckedChangeListener? = null,
dashedCheckedChangeListener: CompoundButton.OnCheckedChangeListener? = null
) : BaseDialog<DialogSelectMapTypeLayoutBinding>(
DialogSelectMapTypeLayoutBinding::inflate, context
) {
private var mAdapter = adapter
private val mCheckedChangeListener = checkedChangeListener
private val mFenceCheckedChangeListener = fenceCheckedChangeListener
private val mDashedCheckedChangeListener = dashedCheckedChangeListener
override fun initView() {
mViewBinding.apply {
@@ -31,7 +35,10 @@ class SelectMapTypeDialog(
context, rvShowMapTypeList, mAdapter, right = AppUtils.dpToPx(58)
)
cbDialogMapFencesSwitch.isChecked = Util.getShowFenceSp()
cbDialogMapFencesSwitch.setOnCheckedChangeListener(mCheckedChangeListener)
cbDialogMapDashedLineSwitch.isChecked =
MMKVUtil.getBoolean(MMKVKey.ShowDashedLine, true)
cbDialogMapFencesSwitch.setOnCheckedChangeListener(mFenceCheckedChangeListener)
cbDialogMapDashedLineSwitch.setOnCheckedChangeListener(mDashedCheckedChangeListener)
}
}
@@ -58,4 +65,8 @@ class SelectMapTypeDialog(
fun setFencesSwitch(checked: Boolean) {
mViewBinding.cbDialogMapFencesSwitch.isChecked = checked
}
fun setDashedSwitch(checked: Boolean) {
mViewBinding.cbDialogMapDashedLineSwitch.isChecked = checked
}
}

View File

@@ -21,6 +21,7 @@ import java.util.Calendar
* @link
* @description:显示日历弹窗
* @param canSelectFutureDate 平时正常日期true不能选择未来时间
* @param minLastDayRange 最小天数能选最近多少天0表示没有限制
*/
class ShowCalenderAndTimeDialog(
context: Context,
@@ -29,7 +30,8 @@ class ShowCalenderAndTimeDialog(
format: String = Utils.DATE_FORMAT_PATTERN_EN1,
canSelectFutureDate: Boolean = false,
calenderShowTimestamp: Long = 0,
minYear: Int = 2023
minYear: Int = 2023,
minLastDayRange: Int = 0
) : BaseDialog<DialogCalenderAndTimeLayoutBinding>(
DialogCalenderAndTimeLayoutBinding::inflate, context
), OnValueChangeListener {
@@ -44,7 +46,8 @@ class ShowCalenderAndTimeDialog(
private val mOkListener = okListener
private val mMinYear = minYear
private val mCalenderShowTimestamp = calenderShowTimestamp
private var mCalenderShowTimestamp = calenderShowTimestamp
private val mMinLastDayRange = minLastDayRange
override fun initView() {
mMonths = context.resources.getStringArray(R.array.array_month)
@@ -58,7 +61,23 @@ class ShowCalenderAndTimeDialog(
val cYear = calendar.get(Calendar.YEAR)
val month = calendar.get(Calendar.MONTH) + 1
val day = calendar.get(Calendar.DAY_OF_MONTH)
it.setRange(mMinYear, 1, 1, cYear, month, day)
if (mMinLastDayRange > 0) {
val minTimestamp = Utils.getBeforeHowTimestamp(
calendar.timeInMillis, mMinLastDayRange.toLong()
)
val minDate =
Utils.formatTime(minTimestamp, Utils.DATE_FORMAT_PATTERN_CN).split("-")
it.setRange(
minDate[0].toInt(),
minDate[1].toInt(),
minDate[2].toInt(),
cYear,
month,
day
)
} else {
it.setRange(mMinYear, 1, 1, cYear, month, day)
}
}
updateMonthYear()
@@ -104,8 +123,6 @@ class ShowCalenderAndTimeDialog(
)
}
//根据传入的时间戳进行还原显示
setSelectDate(mCalenderShowTimestamp)
setOnClickListenerViews(
llDialogCalenderLayout.ivSelectCalendarMonthLeft,
@@ -117,6 +134,12 @@ class ShowCalenderAndTimeDialog(
}
}
override fun onStart() {
super.onStart()
//根据传入的时间戳进行还原显示
setSelectDate(mCalenderShowTimestamp)
}
/**
* 根据传入的时间戳进行还原显示
* @param showTimestamp 13位时间戳
@@ -174,11 +197,11 @@ class ShowCalenderAndTimeDialog(
val nowTimestamp = System.currentTimeMillis()
if (timesTamp > nowTimestamp) {
//时间戳还原
setSelectDate(nowTimestamp)
mCalenderShowTimestamp = nowTimestamp
mShowCalenderTextView.text = Utils.formatTime(nowTimestamp, mDateFormat)
mOkListener?.onSelectClick(this@ShowCalenderAndTimeDialog, nowTimestamp)
} else {
mCalenderShowTimestamp = timesTamp
mShowCalenderTextView.text = Utils.stringToDate(
selectMonthYear, Utils.DATE_FORMAT_PATTERN_CN2, mDateFormat
)

View File

@@ -100,7 +100,7 @@ class ShowCalenderDialog(
* 根据传入的时间戳进行还原显示
* @param showTimestamp 13位时间戳
*/
fun setSelectDate(showTimestamp: Long) {
private fun setSelectDate(showTimestamp: Long) {
if (showTimestamp > 0) {
val calendar = java.util.Calendar.getInstance().apply {
timeInMillis = showTimestamp

View File

@@ -324,7 +324,7 @@ class MoreActivityActivity :
)
}
}
}, calenderShowTimestamp = cTimestamp, minLastDayRange = 364)
}, calenderShowTimestamp = cTimestamp, minLastDayRange = 29)
calenderDialog.show()
}

View File

@@ -201,7 +201,7 @@ class MoreSleepActivity :
)
}
}
}, calenderShowTimestamp = cTimestamp, minLastDayRange = 364)
}, calenderShowTimestamp = cTimestamp, minLastDayRange = 29)
calenderDialog.show()
}

View File

@@ -25,24 +25,39 @@ class HelpCreatePetFenceActivity :
ViewUtil.instance.addMenuBean(
menuList, "", imageResId = R.drawable.create_fence_help1, menuType = MultipleEntity.IMG
)
ViewUtil.instance.addMenuBean(
menuList, getString(R.string.txt_set_fence_type)
)
ViewUtil.instance.addMenuBean(
menuList, getString(R.string.txt_fence_type_help), menuType = MultipleEntity.IMG_IMG
)
ViewUtil.instance.addMenuBean(
menuList, getString(R.string.txt_set_boundary)
)
ViewUtil.instance.addMenuBean(
menuList, getString(R.string.txt_create_fence_tip2), menuType = MultipleEntity.IMG_IMG
)
ViewUtil.instance.addMenuBean(
menuList, "", imageResId = R.drawable.create_fence_help2, menuType = MultipleEntity.IMG
)
ViewUtil.instance.addMenuBean(
menuList, getString(R.string.txt_enable_set_notify)
)
ViewUtil.instance.addMenuBean(
menuList, getString(R.string.txt_create_fence_tip3), menuType = MultipleEntity.IMG_IMG
)
ViewUtil.instance.addMenuBean(
menuList, "", imageResId = R.drawable.create_fence_help3, menuType = MultipleEntity.IMG
)
ViewUtil.instance.addMenuBean(
menuList, getString(R.string.txt_create_fence_tip4), menuType = MultipleEntity.IMG_IMG
)
ViewUtil.instance.addMenuBean(
menuList, "", imageResId = R.drawable.create_fence_help4, menuType = MultipleEntity.IMG
)
ViewUtil.instance.addMenuBean(
menuList, getString(R.string.txt_edit_delete_fence)
)
// ViewUtil.instance.addMenuBean(
// menuList, getString(R.string.txt_create_fence_tip4), menuType = MultipleEntity.IMG_IMG
// )
// ViewUtil.instance.addMenuBean(
// menuList, "", imageResId = R.drawable.create_fence_help4, menuType = MultipleEntity.IMG
// )
val helpCreateFenceAdapter = HelpTextImageTypeAdapter(menuList)
ViewUtil.instance.setRecyclerViewVerticalLinearLayout(

View File

@@ -1,18 +1,53 @@
package com.abbidot.tracker.ui.activity.help
import android.view.View
import com.abbidot.tracker.R
import com.abbidot.tracker.base.BaseActivity
import com.abbidot.tracker.constant.ConstantInt
import com.abbidot.tracker.constant.ConstantString
import com.abbidot.tracker.databinding.ActivityHelpTrackerStatusBinding
class HelpTrackerStatusActivity :
BaseActivity<ActivityHelpTrackerStatusBinding>(ActivityHelpTrackerStatusBinding::inflate) {
private var mType = ConstantInt.Type0
override fun getTopBar() = mViewBinding.ilHelpTrackerStatusBar.titleTopBar
override fun initData() {
super.initData()
setTopBarTitle(R.string.txt_check_tracker_status)
setLeftBackImage(R.drawable.icon_white_back_svg)
mViewBinding.ilHelpTrackerStatusTitle1.tvHelpTitleOne.setText(R.string.txt_check_tracker_status_top_tip)
intent.extras?.apply {
mType = getInt(ConstantString.Type, ConstantInt.Type0)
}
mViewBinding.apply {
when (mType) {
0 -> {
setTopBarTitle(R.string.txt_check_tracker_status)
setLeftBackImage(R.drawable.icon_white_back_svg)
ilHelpTrackerStatusTitle1.tvHelpTitleOne.setText(R.string.txt_check_tracker_status_top_tip)
}
1 -> {
setTopBarTitle(R.string.txt_sleep_mode)
setLeftBackImage(R.drawable.icon_white_back_svg)
ilHelpTrackerStatusTitle1.tvHelpTitleOne.setText(R.string.txt_sleep_mode_help)
ivHelpTrackerStatusImage.visibility = View.GONE
}
2 -> {
setTopBarTitle(R.string.txt_wifi_zone_home)
setLeftBackImage(R.drawable.icon_white_back_svg)
ilHelpTrackerStatusTitle1.tvHelpTitleOne.setText(R.string.txt_wifi_zone_home_help)
ivHelpTrackerStatusImage.visibility = View.GONE
}
3 -> {
setTopBarTitle(R.string.tracker_manage_set_duration)
setLeftBackImage(R.drawable.icon_white_back_svg)
ilHelpTrackerStatusTitle1.tvHelpTitleOne.setText(R.string.txt_gps_update_help)
ivHelpTrackerStatusImage.visibility = View.GONE
}
}
}
}
}

View File

@@ -7,6 +7,8 @@ import com.abbidot.tracker.R
import com.abbidot.tracker.adapter.HowWorkAdapter
import com.abbidot.tracker.base.BaseActivity
import com.abbidot.tracker.bean.MenuTxtBean
import com.abbidot.tracker.constant.ConstantInt
import com.abbidot.tracker.constant.ConstantString
import com.abbidot.tracker.databinding.ActivityHowWorkBinding
import com.abbidot.tracker.util.ViewUtil
import com.qmuiteam.qmui.util.QMUIDisplayHelper
@@ -26,7 +28,10 @@ class HowWorkActivity : BaseActivity<ActivityHowWorkBinding>(ActivityHowWorkBind
ViewUtil.instance.addMenuBean(menuList, getString(R.string.txt_power_on_off))
ViewUtil.instance.addMenuBean(menuList, getString(R.string.txt_active_tracker))
ViewUtil.instance.addMenuBean(menuList, getString(R.string.txt_check_tracker_status))
ViewUtil.instance.addMenuBean(menuList, getString(R.string.txt_tracker_battery_life))
ViewUtil.instance.addMenuBean(menuList, getString(R.string.txt_sleep_mode))
ViewUtil.instance.addMenuBean(menuList, getString(R.string.txt_wifi_zone_home))
ViewUtil.instance.addMenuBean(menuList, getString(R.string.tracker_manage_set_duration))
// ViewUtil.instance.addMenuBean(menuList, getString(R.string.txt_tracker_battery_life))
ViewUtil.instance.addMenuBean(menuList, getString(R.string.txt_create_pet_fence))
mHowWorkAdapter = HowWorkAdapter(this, menuList).apply {
setOnItemClickListener(object : BaseRecyclerAdapter.OnItemClickListener {
@@ -35,8 +40,26 @@ class HowWorkActivity : BaseActivity<ActivityHowWorkBinding>(ActivityHowWorkBind
0 -> startActivity(Intent(mContext, HelpPowerOnOffActivity::class.java))
1 -> startActivity(Intent(mContext, HelpActiveTrackerActivity::class.java))
2 -> startActivity(Intent(mContext, HelpTrackerStatusActivity::class.java))
3 -> startActivity(Intent(mContext, HelpTrackerBatteryActivity::class.java))
4 -> startActivity(Intent(mContext, HelpCreatePetFenceActivity::class.java))
3 -> {
Intent(mContext, HelpTrackerStatusActivity::class.java).let {
it.putExtra(ConstantString.Type, ConstantInt.Type1)
startActivity(it)
}
}
4 -> {
Intent(mContext, HelpTrackerStatusActivity::class.java).let {
it.putExtra(ConstantString.Type, ConstantInt.Type2)
startActivity(it)
}
}
5 -> {
Intent(mContext, HelpTrackerStatusActivity::class.java).let {
it.putExtra(ConstantString.Type, ConstantInt.Type3)
startActivity(it)
}
}
6 -> startActivity(Intent(mContext, HelpCreatePetFenceActivity::class.java))
}
}
})

View File

@@ -91,6 +91,9 @@ class LiveActivityV3 : BaseActivity<ActivityLiveV3Binding>(ActivityLiveV3Binding
//是否显示围栏
private var isShowFence = true
//是否显示虚线
private var isShowDashed = true
//地图类型,标准和卫星地图
private var mMapType = ConstantInt.Type0
private var mShowCenterLocation = ConstantInt.PetLocationType
@@ -138,6 +141,7 @@ class LiveActivityV3 : BaseActivity<ActivityLiveV3Binding>(ActivityLiveV3Binding
}
isShowFence = Util.getShowFenceSp()
isShowDashed = MMKVUtil.getBoolean(MMKVKey.ShowDashedLine, true)
mViewBinding.apply {
ViewUtil.instance.viewRotationAnimator(
@@ -775,7 +779,8 @@ class LiveActivityV3 : BaseActivity<ActivityLiveV3Binding>(ActivityLiveV3Binding
private fun showMapTypeDialog() {
if (null == mSelectMapTypeDialog) {
mSelectMapTypeDialog = ViewUtil.instance.getMapTypeDialog(
mContext, object : BaseRecyclerAdapter.OnItemClickListener {
mContext,
object : BaseRecyclerAdapter.OnItemClickListener {
override fun onItemClick(itemView: View?, pos: Int) {
mMapType = Util.getMapTypeSp()
if (pos == mMapType) {
@@ -783,8 +788,8 @@ class LiveActivityV3 : BaseActivity<ActivityLiveV3Binding>(ActivityLiveV3Binding
}
mHomeMapCommon.switchSatelliteAndNormalMapType()
}
}) { v, isChecked ->
if (v.id == R.id.cb_dialog_map_fences_switch) {
},
{ _, isChecked ->
isShowFence = isChecked
MMKVUtil.putBoolean(MMKVKey.ShowFence, isChecked)
if (isChecked) {
@@ -794,8 +799,16 @@ class LiveActivityV3 : BaseActivity<ActivityLiveV3Binding>(ActivityLiveV3Binding
} else {
mFencesMapViewModel.setFencesData(mContext, null, mFragment)
}
}
}
},
{ _, isChecked ->
isShowDashed = isChecked
MMKVUtil.putBoolean(MMKVKey.ShowDashedLine, isChecked)
if (isChecked) {
mHomeMapCommon.addUserAndPetLine()
} else {
mHomeMapCommon.removeUserAndPetLine()
}
})
} else {
mSelectMapTypeDialog!!.mapTypeSpToUpdate()
}
@@ -990,6 +1003,9 @@ class LiveActivityV3 : BaseActivity<ActivityLiveV3Binding>(ActivityLiveV3Binding
}
}
}
if (isShowDashed) {
mHomeMapCommon.addUserAndPetLine()
}
}
}
}

View File

@@ -62,6 +62,7 @@ class SureSubscriptionPlanActivity :
//升级套餐还剩下多少差价
private var mResidualMoney = 0.0
//设备类型
private var mType = ConstantInt.Type1
@@ -253,7 +254,7 @@ class SureSubscriptionPlanActivity :
ViewUtil.instance.addMenuBean(
mSummaryAdapter.getData(),
p.planName,
"$price",
Utils.formatDecimal(p.planPrice, 2),
colorRedId = R.color.data_black_color
)
mTotalMoney += price
@@ -289,12 +290,12 @@ class SureSubscriptionPlanActivity :
isSwitch = true
)
}
ViewUtil.instance.addMenuBean(
mSummaryAdapter.getData(),
getString(R.string.txt_sales_tax),
"0",
colorRedId = R.color.data_black_color
)
// ViewUtil.instance.addMenuBean(
// mSummaryAdapter.getData(),
// getString(R.string.txt_sales_tax),
// "0",
// colorRedId = R.color.data_black_color
// )
}
//判断套餐是否过期 或者套餐没退款
@@ -417,9 +418,10 @@ class SureSubscriptionPlanActivity :
private fun updateMoney() {
mViewBinding.apply {
val list = mSummaryAdapter.getData()
val taxMoney = abs(Utils.formatDecimal(mTaxRate * mTotalMoney, 2).toDouble())
// val taxMoney = abs(Utils.formatDecimal(mTaxRate * mTotalMoney, 2).toDouble())
val taxMoney = 0.0
mOrderBean?.tax = taxMoney
list[list.size - 1].menuValue = taxMoney.toString()
// list[list.size - 1].menuValue = taxMoney.toString()
mTotalWithTaxMoney = taxMoney + mTotalMoney
mTotalWithTaxMoney = abs(mTotalWithTaxMoney)
ilSubscribePlanSummary.ilSureSubscribePlanTotalLayout.tvSubscribeSummaryItemMoney.text =

View File

@@ -10,7 +10,6 @@ import com.abbidot.tracker.base.BaseMapCommon
import com.abbidot.tracker.bean.HistoryDataBean
import com.abbidot.tracker.bean.MapDeviceBean
import com.abbidot.tracker.constant.ConstantInt
import com.abbidot.tracker.constant.LinkMapCallback
import com.abbidot.tracker.databinding.LayoutPetLocationInfoBinding
import com.abbidot.tracker.ui.fragment.map.baidumap.HomeMapBaiduMapFragment
import com.abbidot.tracker.ui.fragment.map.googlemap.HomeMapGoogleMapFragmentV3
@@ -199,6 +198,14 @@ class HomeMapCommonV3 @Inject constructor() : BaseMapCommon() {
return mHomeMapGoogleMapFragment?.mUserLatLng
}
fun addUserAndPetLine() {
mHomeMapGoogleMapFragment?.addUserAndPetLine()
}
fun removeUserAndPetLine() {
mHomeMapGoogleMapFragment?.removeUserAndPetLine()
}
// fun getAddressShowMarkerInfoWindow(historyDataBean: HistoryDataBean) {
// if (null != mHomeMapBaiduMapFragment) {
// mHomeMapBaiduMapFragment!!.showMarkerInfoWindow(historyDataBean)

View File

@@ -643,7 +643,8 @@ class RouteV3Fragment : BaseFragment<FragmentRouteV3Binding>(FragmentRouteV3Bind
}
},
calenderShowTimestamp = mFromTimestamp,
format = Utils.DATE_FORMAT_PATTERN_EN13
format = Utils.DATE_FORMAT_PATTERN_EN13,
minLastDayRange = 29
)
}
mFromCalenderDialog?.show()
@@ -672,7 +673,8 @@ class RouteV3Fragment : BaseFragment<FragmentRouteV3Binding>(FragmentRouteV3Bind
}
},
calenderShowTimestamp = mToTimestamp,
format = Utils.DATE_FORMAT_PATTERN_EN13
format = Utils.DATE_FORMAT_PATTERN_EN13,
minLastDayRange = 29
)
}
mToCalenderDialog?.show()

View File

@@ -503,7 +503,7 @@ class HomeTrackFragment :
ViewUtil.instance.addMenuBean(
mTrackStateList,
getString(R.string.tracker_manage_set_gps),
getString(R.string.tracker_manage_set_led_off),
getString(R.string.txt_sleep),
imageResId = R.drawable.icon_map_gps
)
ViewUtil.instance.addMenuBean(
@@ -669,7 +669,7 @@ class HomeTrackFragment :
it.menuValue =
if (isTimeoutReport || powerSwitch == ConstantInt.Type0 || powerSwitch == ConstantInt.Type2 || powerSwitch == ConstantInt.Type3 || inWifiZone == ConstantInt.Type1) {
it.colorRedId = R.color.orange_color3
getString(R.string.tracker_manage_set_led_off)
getString(R.string.txt_sleep)
} else if (gpsSignal > ConstantInt.WeakSignal) getString(R.string.txt_strong_signal)
else if (gpsSignal == ConstantInt.NoSignal) {
it.colorRedId = R.color.orange_color3

View File

@@ -9,6 +9,8 @@ import android.location.LocationManager
import android.provider.Settings
import android.view.Gravity
import android.view.View
import android.widget.CompoundButton
import android.widget.CompoundButton.OnCheckedChangeListener
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import androidx.core.view.WindowInsetsCompat
@@ -63,7 +65,8 @@ import javax.inject.Inject
* create an instance of this fragment.
*/
@AndroidEntryPoint
class MapV3Fragment : BaseFragment<FragmentMapV3Binding>(FragmentMapV3Binding::inflate) {
class MapV3Fragment : BaseFragment<FragmentMapV3Binding>(FragmentMapV3Binding::inflate),
OnCheckedChangeListener {
private val mFencesMapViewModel: FencesMapViewModel by viewModels()
private val mMapViewModel: MapViewModel by viewModels()
@@ -93,6 +96,9 @@ class MapV3Fragment : BaseFragment<FragmentMapV3Binding>(FragmentMapV3Binding::i
//是否显示围栏
private var isShowFence = true
//是否显示虚线
private var isShowDashed = true
//地图类型,标准和卫星地图
private var mMapType = ConstantInt.Type0
@@ -120,6 +126,7 @@ class MapV3Fragment : BaseFragment<FragmentMapV3Binding>(FragmentMapV3Binding::i
}
isShowFence = Util.getShowFenceSp()
isShowDashed = MMKVUtil.getBoolean(MMKVKey.ShowDashedLine, true)
mViewBinding.apply {
getHomeV2Activity()?.edgeToEdgeAdapterBars(
@@ -208,6 +215,7 @@ class MapV3Fragment : BaseFragment<FragmentMapV3Binding>(FragmentMapV3Binding::i
}
val showFence = Util.getShowFenceSp()
val showDashed = MMKVUtil.getBoolean(MMKVKey.ShowDashedLine, true)
//检测直播页面有没有修改围栏显示
if (isShowFence != showFence) {
if (null == mSelectMapTypeDialog) {
@@ -216,6 +224,13 @@ class MapV3Fragment : BaseFragment<FragmentMapV3Binding>(FragmentMapV3Binding::i
mSelectMapTypeDialog?.setFencesSwitch(showFence)
}
}
if (isShowDashed != showDashed) {
if (null == mSelectMapTypeDialog) {
setDashedShow(showDashed)
} else {
mSelectMapTypeDialog?.setDashedSwitch(showDashed)
}
}
}
private fun getHomeV2Activity(): HomeV2Activity? {
@@ -548,11 +563,8 @@ class MapV3Fragment : BaseFragment<FragmentMapV3Binding>(FragmentMapV3Binding::i
}
mHomeMapCommon.switchSatelliteAndNormalMapType()
}
}) { v, isChecked ->
if (v.id == R.id.cb_dialog_map_fences_switch) {
setFencesShow(isChecked)
}
}
}, this, this
)
else {
mSelectMapTypeDialog!!.mapTypeSpToUpdate()
}
@@ -574,6 +586,19 @@ class MapV3Fragment : BaseFragment<FragmentMapV3Binding>(FragmentMapV3Binding::i
}
}
/**
* 设置虚线显示
*/
private fun setDashedShow(isShow: Boolean) {
isShowDashed = isShow
MMKVUtil.putBoolean(MMKVKey.ShowDashedLine, isShow)
if (isShow) {
mHomeMapCommon.addUserAndPetLine()
} else {
mHomeMapCommon.removeUserAndPetLine()
}
}
/**
* 设置需要更新的标识
*/
@@ -678,6 +703,9 @@ class MapV3Fragment : BaseFragment<FragmentMapV3Binding>(FragmentMapV3Binding::i
mFencesMapViewModel.setFencesData(mContext!!, fences, mFragment)
}
}
if (isShowDashed) {
mHomeMapCommon.addUserAndPetLine()
}
}
}
@@ -929,6 +957,12 @@ class MapV3Fragment : BaseFragment<FragmentMapV3Binding>(FragmentMapV3Binding::i
// }
// }
override fun onCheckedChanged(v: CompoundButton, isChecked: Boolean) {
when (v.id) {
R.id.cb_dialog_map_fences_switch -> setFencesShow(isChecked)
R.id.cb_dialog_map_dashed_line_switch -> setDashedShow(isChecked)
}
}
override fun onClick(v: View?) {
mViewBinding.apply {
@@ -1008,5 +1042,4 @@ class MapV3Fragment : BaseFragment<FragmentMapV3Binding>(FragmentMapV3Binding::i
}
}
}
}

View File

@@ -406,14 +406,15 @@ abstract class BaseGoogleMapFragment :
if (MMKVUtil.getBoolean(MMKVKey.isGpsToGCJ02)) {
mUserLatLng?.let {
if (null == mUserMarker) {
mUserMarker = addImageMarker(it, R.drawable.icon_user_location_image)
mUserMarker = addImageMarker(it, R.drawable.icon_user_location_image,false)
} else {
if (it.latitude == mUserMarker!!.position.latitude && it.longitude == mUserMarker!!.position.longitude) {
} else {
mUserMarker?.remove()
mUserMarker = addImageMarker(it, R.drawable.icon_user_location_image)
mUserMarker = addImageMarker(it, R.drawable.icon_user_location_image,false)
}
}
mUserMarker?.setAnchor(0.5f,0.5f)
}
}
// else {
@@ -791,7 +792,7 @@ abstract class BaseGoogleMapFragment :
/**
* 获取地图画线的PolylineOptions
*/
private fun getPolylineOptions(widthDp: Float, lineColorRes: Int): PolylineOptions {
fun getPolylineOptions(widthDp: Float, lineColorRes: Int): PolylineOptions {
return PolylineOptions().clickable(false).width(AppUtils.dpToPx(widthDp))
.color(ContextCompat.getColor(mContext!!, lineColorRes)).geodesic(false)
}

View File

@@ -25,7 +25,6 @@ import com.google.android.gms.maps.model.CircleOptions
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.Marker
import com.google.android.gms.maps.model.Polyline
import com.google.android.gms.maps.model.PolylineOptions
import kotlin.math.pow
/**
@@ -47,7 +46,7 @@ class HomeMapGoogleMapFragmentV3 : BaseGoogleMapFragment() {
//启动动画移动地图摄像机
private var isMoveCamera = true
private var mPolyline: Polyline? = null
private var mUserAndPetLine: Polyline? = null
private var mRippleCircle: Circle? = null
private var mValueAnimator: ValueAnimator? = null
@@ -111,6 +110,19 @@ class HomeMapGoogleMapFragmentV3 : BaseGoogleMapFragment() {
mPetLocationLayoutBinding.let { layout ->
layout.tvPetLocationReverseGeocode.text = address
layout.tvPetLocationUpdateTime.text = timeString
layout.tvPetLocationUpdateTimeFormat.visibility =
if (powerSwitch == ConstantInt.Type2) View.GONE
else {
if (Util.isTimeoutReport(updateTime, gnssInterval)) {
layout.tvPetLocationUpdateTimeFormat.text = String.format(
getString(R.string.txt_location_ago),
Utils.getTimeDifference(
updateTime * 1000, System.currentTimeMillis()
)
)
View.VISIBLE
} else View.GONE
}
}
}
}
@@ -207,27 +219,26 @@ class HomeMapGoogleMapFragmentV3 : BaseGoogleMapFragment() {
/**
* 画用户和宠物之间的虚线
*/
private fun addUserAndPetLine(petLatLng: LatLng) {
mUserLatLng?.apply {
mGoogleMap?.let {
if (null == mPolyline) {
val polylineOptions =
PolylineOptions().clickable(false).width(AppUtils.dpToPx(1.5f))
.color(ContextCompat.getColor(mContext!!, R.color.blue_color9))
.geodesic(false)
mPolyline = it.addPolyline(polylineOptions)
mPolyline?.pattern = getDashedPatternStyle(18f)
}
mPolyline?.let { pLine ->
val latList = mutableListOf<LatLng>()
latList.add(petLatLng)
latList.add(this)
pLine.points = latList
fun addUserAndPetLine() {
mGoogleMap?.apply {
mUserLatLng?.let { u ->
mPetLatLng?.let { p ->
mUserAndPetLine?.remove()
getPolylineOptions(1.5f, R.color.blue_color9).let {
it.add(toGCJ02LatLon(p))
it.add(toGCJ02LatLon(u))
mUserAndPetLine = addPolyline(it)
}
mUserAndPetLine?.pattern = getDashedPatternStyle(18f)
}
}
}
}
fun removeUserAndPetLine() {
mUserAndPetLine?.remove()
}
/**
* 初始化并添加水波纹圆
*/

View File

@@ -878,7 +878,8 @@ class ViewUtil private constructor() {
fun getMapTypeDialog(
context: Context,
onItemClickListener: BaseRecyclerAdapter.OnItemClickListener? = null,
checkedChangeListener: CompoundButton.OnCheckedChangeListener? = null
fenceCheckedChangeListener: CompoundButton.OnCheckedChangeListener? = null,
dashedCheckedChangeListener: CompoundButton.OnCheckedChangeListener? = null
): SelectMapTypeDialog {
val mapType = Util.getMapTypeSp()
val typeList = mutableListOf<DataBean>()
@@ -902,7 +903,9 @@ class ViewUtil private constructor() {
}
})
}
return SelectMapTypeDialog(context, mapTypeAdapter, checkedChangeListener)
return SelectMapTypeDialog(
context, mapTypeAdapter, fenceCheckedChangeListener, dashedCheckedChangeListener
)
}
/**

View File

@@ -35,7 +35,6 @@ import com.abbidot.tracker.widget.TypefaceButton
import com.abbidot.tracker.widget.TypefaceTextView
import com.clj.fastble.BleManager
import kotlinx.coroutines.launch
import java.util.concurrent.TimeUnit
class MapViewModel : ViewModel() {
@@ -114,7 +113,7 @@ class MapViewModel : ViewModel() {
menuType = ConstantInt.Close
name = context.getString(R.string.tracker_manage_set_gps)
value =
context.getString(R.string.tracker_manage_set_gps) + "" + context.getString(R.string.tracker_manage_set_led_off)
context.getString(R.string.tracker_manage_set_gps) + "" + context.getString(R.string.txt_sleep)
deviceStateList.add(this)
}
DataBean().apply {
@@ -302,7 +301,7 @@ class MapViewModel : ViewModel() {
deviceStateList[1].apply {
menuType = ConstantInt.Close
value =
context.getString(R.string.tracker_manage_set_gps) + "" + context.getString(R.string.tracker_manage_set_led_off)
context.getString(R.string.tracker_manage_set_gps) + "" + context.getString(R.string.txt_sleep)
}
deviceStateList[2].apply {
menuType = ConstantInt.Close
@@ -344,13 +343,12 @@ class MapViewModel : ViewModel() {
val gpsValue =
if (isTimeoutReport || it.powerSwitch == ConstantInt.Type0 || it.powerSwitch == ConstantInt.Type2 || it.powerSwitch == ConstantInt.Type3 || it.inWifiZone == ConstantInt.Type1) {
menuType = ConstantInt.Close
context.getString(R.string.tracker_manage_set_led_off)
context.getString(R.string.txt_sleep)
} else if (it.gpsSignal > ConstantInt.WeakSignal) context.getString(R.string.txt_strong_signal)
else if (it.gpsSignal == ConstantInt.NoSignal) {
menuType = ConstantInt.Close
context.getString(R.string.txt_no_signal)
}
else context.getString(R.string.txt_weak_signal)
} else context.getString(R.string.txt_weak_signal)
value = context.getString(R.string.tracker_manage_set_gps) + "$gpsValue"
}
deviceStateList[2].apply {
@@ -476,7 +474,7 @@ class MapViewModel : ViewModel() {
)
it.text = String.format(
context.getString(R.string.txt_fell_asleep),
getTimeDifference(updateTime * 1000, System.currentTimeMillis())
Utils.getTimeDifference(updateTime * 1000, System.currentTimeMillis())
)
ViewUtil.instance.viewShow(closeBtn)
} else if (powerSwitch == ConstantInt.Type0) {
@@ -517,37 +515,6 @@ class MapViewModel : ViewModel() {
}
}
private fun getTimeDifference(startMillis: Long, endMillis: Long): String {
var diff = endMillis - startMillis
val days = TimeUnit.MILLISECONDS.toDays(diff)
diff -= TimeUnit.DAYS.toMillis(days)
val hours = TimeUnit.MILLISECONDS.toHours(diff)
diff -= TimeUnit.HOURS.toMillis(hours)
val minutes = TimeUnit.MILLISECONDS.toMinutes(diff)
var timeStr: String
if (days > 0) {
timeStr = if (days > 1) {
"$days days"
} else {
"$days day"
}
if (hours > 0) {
timeStr += " ${hours}h"
}
} else {
if (hours > 0) {
timeStr = "${hours}h"
if (minutes > 0) timeStr += " ${minutes}m"
} else {
timeStr = if (minutes > 0) "${minutes}m"
else "1m"
}
}
return timeStr
}
fun setPetLocationReverseGeocode(
context: Context,
mapDeviceBean: MapDeviceBean,

View File

@@ -159,7 +159,8 @@ public class HorizontalCalenderViewV2 extends FrameLayout {
//获取当月的日期
// List<DateItem> items = getItems();
//获取最近365天的日期
List<DateItem> items = getLast365DaysItems();
// List<DateItem> items = getLast365DaysItems();
List<DateItem> items = getLast30DaysItems();
adapter = new DayV2Adapter(context, items, dayTextColorSelected, dayTextColorNormal,
daySelectionColor, weekTextColor, pressShapeSelectorId, todayPointColor);
adapter.setItemClick((year, month, day, week) -> {

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -18,13 +19,22 @@
android:layout_marginVertical="@dimen/dp_18"
android:orientation="vertical">
<com.abbidot.tracker.widget.TypefaceTextView
style="@style/my_TextView_style_v2"
android:layout_marginHorizontal="@dimen/dp_22"
android:gravity="start"
android:text="@string/txt_first_use"
android:textSize="@dimen/textSize16"
android:textStyle="bold"
app:lineHeight="@dimen/textSize20" />
<include
android:id="@+id/il_help_power_on_off_title"
layout="@layout/layout_help_title1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/dp_22" />
android:layout_marginHorizontal="@dimen/dp_22"
android:layout_marginTop="@dimen/dp_6" />
<androidx.appcompat.widget.AppCompatImageView
@@ -38,13 +48,24 @@
android:paddingBottom="@dimen/dp_12"
android:scaleType="fitCenter" />
<com.abbidot.tracker.widget.TypefaceTextView
style="@style/my_TextView_style_v2"
android:layout_marginHorizontal="@dimen/dp_22"
android:layout_marginTop="@dimen/dp_26"
android:gravity="start"
android:text="@string/txt_daily_use"
android:textSize="@dimen/textSize16"
android:textStyle="bold"
app:lineHeight="@dimen/textSize20" />
<include
android:id="@+id/il_help_shake_title"
layout="@layout/layout_help_title1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/dp_22"
android:layout_marginVertical="@dimen/dp_26" />
android:layout_marginTop="@dimen/dp_6"
android:layout_marginBottom="@dimen/dp_26" />
<androidx.appcompat.widget.AppCompatImageView

View File

@@ -32,9 +32,11 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/dp_22"
android:layout_marginTop="@dimen/dp_20" />
android:layout_marginTop="@dimen/dp_20"
android:visibility="gone" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_help_tracker_status_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"

View File

@@ -41,8 +41,9 @@
android:checked="true" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/ll_map_type_show_accuracy_switch"
android:id="@+id/ll_map_type_show_dashed_line_switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/ll_map_type_display_fences_switch"
@@ -50,7 +51,6 @@
android:background="@drawable/shape8_gray_border_transparent_bg"
android:gravity="center_vertical"
android:minHeight="@dimen/dp_47"
android:visibility="gone"
android:orientation="horizontal"
android:paddingHorizontal="@dimen/dp_16">
@@ -58,12 +58,14 @@
style="@style/my_TextView_style_v2"
android:layout_weight="1"
android:gravity="start"
android:text="@string/txt_show_accuracy"
android:text="@string/txt_display_dashed_line"
android:textSize="@dimen/textSize14"
app:typeface="@string/roboto_bold_font" />
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/cb_dialog_map_dashed_line_switch"
style="@style/my_checkbox_switch_style"
android:layout_width="@dimen/dp_47" />
android:layout_width="@dimen/dp_47"
android:checked="true" />
</androidx.appcompat.widget.LinearLayoutCompat>
</RelativeLayout>

View File

@@ -10,7 +10,7 @@
<com.abbidot.tracker.widget.TypefaceTextView
android:id="@+id/tv_subscribe_summary_item_name"
style="@style/my_TextView_style_v2"
android:text="@string/txt_total_tax"
android:text="@string/txt_total"
android:textSize="@dimen/textSize14"
app:typeface="@string/roboto_regular_font" />

View File

@@ -30,6 +30,15 @@
android:textSize="@dimen/textSize12"
app:typeface="@string/roboto_regular_font" />
<com.abbidot.tracker.widget.TypefaceTextView
android:id="@+id/tv_pet_location_update_time_format"
style="@style/my_TextView_style_v2"
android:layout_alignBaseline="@id/tv_pet_location_update_time"
android:gravity="start"
android:textColor="@color/orange_color5"
android:textSize="@dimen/textSize10"
app:typeface="@string/roboto_regular_font" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_pet_location_navigation_btn"
android:layout_width="wrap_content"

View File

@@ -967,8 +967,8 @@
<string name="txt_load_more_pull_text">Ziehen zum Laden weiterer Inhalte</string>
<string name="txt_load_more_release_text">Loslassen zum Laden</string>
<string name="txt_fully_charged">Voll aufgeladen</string>
<string name="txt_fell_asleep">Vor %@ eingeschlafen</string>
<string name="txt_move_wake_up">Bewegen Sie %@ zum Aufwecken</string>
<string name="txt_fell_asleep">Vor %s eingeschlafen</string>
<string name="txt_move_wake_up">Bewegen Sie %s zum Aufwecken</string>
<string name="txt_time_line">Zeitleiste</string>
<string name="txt_phone_close_device">Halten Sie das Telefon nah ans Gerät</string>
<string name="txt_auto_renew_cancel">Auto-Verlängerung: Kündigen</string>
@@ -979,5 +979,14 @@
<string name="txt_fence_saved">espeichert. Aktiv, wenn online.</string>
<string name="txt_renewal_years">Verlängerung: $%s / %s Jahre am %s</string>
<string name="txt_renewal_months">Verlängerung: $%s / %s Monate am %s</string>
<string name ="txt_refund_expired">Die Rückerstattungsfrist ist abgelaufen</string>
<string name ="txt_refund_policy">Rückerstattungsrichtlinie\n</string>
<string name ="txt_automatic_refund">Automatische Rückerstattung (innerhalb von 48 Stunden)</string>
<string name ="txt_automatic_refund_dec">Alle Abonnementkäufe sind innerhalb von 48 Stunden nach Zahlung für eine vollständige automatische Rückerstattung berechtigt. Die Rückerstattungen werden automatisch ohne manuelle Prüfung bearbeitet.\n</string>
<string name ="txt_standard_refund">Standard-Rückerstattungszeitraum (30 Tage)</string>
<string name ="txt_standard_refund_dec">Nach der ersten 48-Stunden-Frist können Benutzer innerhalb von 30 Tagen nach dem Kauf eine Rückerstattung beantragen.\n\t1. Alle Abonnementpläne (3 Monate, 1 Jahr, 2 Jahre) sind berechtigt\n\t2. Rückerstattungsanträge unterliegen der Prüfung und Genehmigung\n\t3. Rückerstattungen können je nach Nutzung und Serviceverbrauch teilweise angepasst werden\n</string>
<string name ="txt_no_refund_dec">Abonnements, die älter als 30 Tage sind, können nicht erstattet werden.</string>
<string name ="txt_no_refund">Keine Rückerstattung nach 30 Tagen</string>
<string name ="txt_display_dashed_line">Gestrichelte Linie anzeigen</string>
</resources>

View File

@@ -1023,5 +1023,14 @@
<string name="txt_fence_saved">已保存,上线时生效。</string>
<string name="txt_renewal_years">续订:$%s / %s 年,于 %s</string>
<string name="txt_renewal_months">续订:$%s / %s 月,于 %s</string>
<string name="txt_refund_expired">退款期限已过</string>
<string name="txt_refund_policy">退款政策\n</string>
<string name="txt_automatic_refund">自动退款48小时内</string>
<string name="txt_automatic_refund_dec">所有订阅购买在付款后48小时内均可享受全额自动退款。退款将自动处理无需人工审核。\n</string>
<string name="txt_standard_refund">标准退款窗口30天</string>
<string name="txt_standard_refund_dec">在最初的48小时之后用户可在购买后30天内申请退款。\n\t1. 所有订阅计划3个月、1年、2年均符合条件\n\t2. 退款申请需经过审核和批准\n\t3. 退款可能会根据使用情况和服务消耗进行部分调整\n</string>
<string name="txt_no_refund_dec">超过30天的订阅不可退款。</string>
<string name="txt_no_refund">30天后不退款</string>
<string name="txt_display_dashed_line">显示虚线</string>
</resources>

View File

@@ -379,4 +379,5 @@
<color name="light_yellow_color">#F9FFE8</color>
<color name="grey_color_91">#B0B0B0</color>
<color name="grey_color_92">#758E94</color>
<color name="orange_color5">#FF996E</color>
</resources>

View File

@@ -847,8 +847,8 @@
<string name="txt_reminder">Reminder</string>
<string name="txt_yesterday">Yesterday</string>
<string name="txt_power_on_off">Power On/Off</string>
<string name="txt_power_on_off_tip">Fully charge the tracker, then remove the cable. When the green light flashes, the tracker is powered on.</string>
<string name="txt_active_tracker_tip">Select and complete your subscription, then click to activate the tracker on the app. Once activated, the tracker can be used for GPS tracking and cellular network connectivity.</string>
<string name="txt_power_on_off_tip">Charge and unplug to turn on Flashing green light = on</string>
<string name="txt_active_tracker_tip">Choose a subscription Tap to activate the tracker Enable GPS and cellular tracking</string>
<string name="txt_active_tracker">Active Tracker</string>
<string name="txt_check_tracker_status_tip">(The GPS and cellular network conditions can vary depending on the environment. Before placing it on your pet, check various locations around you to evaluate the GPS and network status.)</string>
<string name="txt_check_tracker_status">Check Tracker Status</string>
@@ -859,11 +859,11 @@
<string name="txt_tracker_battery_tip5">(Shorter network intervals drain the Tracker\'s battery faster. By default, the network reconnects every 15 minutes.)</string>
<string name="txt_tracker_battery_tip6">When Live mode is activated, the Tracker enters a rapid positioning and networking state, usually within 1520 seconds. This mode drains the battery the quickest, so it\'s best reserved for emergency situations.</string>
<string name="txt_tracker_battery_life">Tracker Battery Life</string>
<string name="txt_create_fence_tip1">1.Fence Settings: Select "Pet" > "My Pet Fence". Click "Manage" > "+ Add new" to create or edit a fence for Safe Zone or No-go Zone.</string>
<string name="txt_create_fence_tip2">2.Define Boundary: Choose a location on the map, adjust the fence\'s radius or shape.</string>
<string name="txt_create_fence_tip3">3.Save Settings: Save to activate the geofence.</string>
<string name="txt_create_fence_tip1">Go to Pet > My Pet Fence > Manage > + Add New</string>
<string name="txt_create_fence_tip2">Choose location and adjust radius or shapeSave fence to activate it</string>
<string name="txt_create_fence_tip3">Alerts when pet enters or leaves the zone</string>
<string name="txt_create_fence_tip4">4.Turn on Notifications: Go to \'Account\' > \'Settings\'. Click \'Notifications\' and turn on</string>
<string name="txt_create_pet_fence">Creating My Pet Fence</string>
<string name="txt_create_pet_fence">Create a Pet Fence</string>
<string name="txt_tracker_sleeping_mode">Tracker in Sleeping Mode</string>
<string name="txt_low_power">(Super Low power usage)</string>
<string name="txt_in_wifi_zone">Tracker in WiFi Zone</string>
@@ -1010,8 +1010,8 @@
<string name="txt_sure_delete_card">Sure to delete this card?</string>
<string name="txt_account_exists_log_in">Account exists. Log in now?</string>
<string name="txt_time_out_of_range">Selected date is out of range</string>
<string name="txt_power_on_off_install_tip">Shake to wake your tracker next time.</string>
<string name="txt_check_tracker_status_top_tip">Turn on the tracker, open the app, go to \'Pet\' > \'Tracker\' on the dashboard, then tap \'Network\', \'GPS\', \'Battery\', \'WiFi zone\', \'Bluetooth\', or \'Light\' to check connectivity, signal, or battery status.</string>
<string name="txt_power_on_off_install_tip">Shake to turn on Flashing green light confirms on</string>
<string name="txt_check_tracker_status_top_tip">Go to Pet > Tracker Check Network GPS Battery WiFi Zone Bluetooth LED GPS and cellular signals may vary by location Test in different locations before use</string>
<string name="txt_on_charging">Tracker is on charging</string>
<string name="txt_active_subscribed">Subscribed</string>
<string name="txt_send">Send</string>
@@ -1090,5 +1090,20 @@
<string name="txt_standard_refund_dec">After the initial 48-hour period, users may request a refund within 30 days of purchase.\n\t1.All subscription plans (3-Month, 1-Year, 2-Year) are eligible\n\t2.Refund requests are subject to review and approval\n\t3.Refunds may be partially adjusted based on usage and service consumption\n</string>
<string name="txt_no_refund_dec">Subscriptions older than 30 days are non-refundable.</string>
<string name="txt_no_refund">No Refund After 30 Days</string>
<string name="txt_display_dashed_line">Display Dashed Line</string>
<string name="txt_first_use">First use</string>
<string name="txt_daily_use">Daily use</string>
<string name="txt_sleep_mode">Sleep Mode</string>
<string name="txt_sleep_mode_help">15 minutes no movement → sleep mode Auto wake on movement Battery updates every 24 hours</string>
<string name="txt_wifi_zone_home">WiFi ZoneHome)</string>
<string name="txt_wifi_zone_home_help">Connect to home WiFi GPS off → save battery Update every ~60 minutes</string>
<string name="txt_gps_update_help">Pet > Tracker > GPS Update Select 315 min Short interval = higher battery use Default: 5 min</string>
<string name="txt_set_fence_type">Set Fence type</string>
<string name="txt_fence_type_help">Safe Zone or No-go Zone</string>
<string name="txt_set_boundary">Set boundary</string>
<string name="txt_enable_set_notify">Enable alerts in Account > Settings > Notifications</string>
<string name="txt_edit_delete_fence">Edit or delete fence in Manage</string>
<string name="txt_total">Total</string>
<string name="txt_location_ago">(%s ago)</string>
</resources>

View File

@@ -37,6 +37,7 @@ import androidx.annotation.StringDef
MMKVKey.isGpsToGCJ02,
MMKVKey.MapType,
MMKVKey.ShowFence,
MMKVKey.ShowDashedLine,
MMKVKey.isCrash,
MMKVKey.AvailableOrder,
MMKVKey.isFirstCheckBleOpen,
@@ -92,6 +93,7 @@ annotation class MMKVKey {
//map页是否显示围栏
const val ShowFence = "isShowFence"
const val ShowDashedLine = "isShowDashedLine"
//是首次打开APP
const val FirstOpen = "firstOpen"

View File

@@ -11,6 +11,7 @@ import java.util.Calendar
import java.util.Date
import java.util.Locale
import java.util.TimeZone
import java.util.concurrent.TimeUnit
import java.util.regex.Pattern
import kotlin.math.atan2
import kotlin.math.cos
@@ -217,6 +218,40 @@ class Utils {
return "${fill2Digits(hour)}:${fill2Digits(minutes)}:${fill2Digits(second)}"
}
/**
* 格式化2个时间相差多少
*/
fun getTimeDifference(startMillis: Long, endMillis: Long): String {
var diff = endMillis - startMillis
val days = TimeUnit.MILLISECONDS.toDays(diff)
diff -= TimeUnit.DAYS.toMillis(days)
val hours = TimeUnit.MILLISECONDS.toHours(diff)
diff -= TimeUnit.HOURS.toMillis(hours)
val minutes = TimeUnit.MILLISECONDS.toMinutes(diff)
var timeStr: String
if (days > 0) {
timeStr = if (days > 1) {
"$days days"
} else {
"$days day"
}
if (hours > 0) {
timeStr += " ${hours}h"
}
} else {
if (hours > 0) {
timeStr = "${hours}h"
if (minutes > 0) timeStr += " ${minutes}m"
} else {
timeStr = if (minutes > 0) "${minutes}m"
else "1m"
}
}
return timeStr
}
/**
* 把秒转换为 day hour min
*/