1061 lines
43 KiB
Kotlin
1061 lines
43 KiB
Kotlin
package com.yingmao.clash.act
|
||
|
||
|
||
import android.Manifest
|
||
import android.annotation.SuppressLint
|
||
import android.content.ClipData
|
||
import android.content.ClipboardManager
|
||
import android.content.Context
|
||
import android.content.Intent
|
||
import android.content.pm.PackageManager
|
||
import android.net.Uri
|
||
import android.os.Build
|
||
import android.os.Bundle
|
||
import android.text.TextUtils
|
||
import android.view.View
|
||
import android.view.WindowManager
|
||
import android.widget.ImageView
|
||
import android.widget.LinearLayout
|
||
import android.widget.ProgressBar
|
||
import android.widget.TextView
|
||
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
|
||
import androidx.appcompat.widget.AppCompatImageView
|
||
import androidx.constraintlayout.widget.ConstraintLayout
|
||
import androidx.core.app.ActivityCompat
|
||
import androidx.core.content.ContextCompat
|
||
import androidx.core.content.FileProvider
|
||
import androidx.core.view.GravityCompat
|
||
import androidx.drawerlayout.widget.DrawerLayout
|
||
import androidx.fragment.app.Fragment
|
||
import androidx.lifecycle.ViewModelProvider
|
||
import androidx.lifecycle.lifecycleScope
|
||
import androidx.viewpager2.widget.ViewPager2
|
||
import com.afollestad.materialdialogs.MaterialDialog
|
||
import com.afollestad.materialdialogs.customview.customView
|
||
import com.afollestad.materialdialogs.customview.getCustomView
|
||
import com.airbnb.lottie.LottieAnimationView
|
||
import com.cncat.vpn.common.log.Log
|
||
import com.cncat.vpn.core.model.LogMessage
|
||
import com.cncat.vpn.service.ClashManager
|
||
import com.cncat.vpn.service.ProfileManager
|
||
import com.cncat.vpn.service.model.Profile
|
||
import com.cncat.vpn.service.remote.IClashManager
|
||
import com.cncat.vpn.service.remote.ILogObserver
|
||
import com.cncat.vpn.service.remote.IProfileManager
|
||
import com.cncat.vpn.service.remote.wrap
|
||
import com.google.gson.Gson
|
||
import com.google.gson.JsonArray
|
||
import com.google.gson.JsonObject
|
||
import com.orhanobut.logger.Logger
|
||
import com.tencent.mmkv.MMKV
|
||
import com.yingmao.clash.BuildConfig
|
||
import com.yingmao.clash.R
|
||
import com.yingmao.clash.app.MainApplication
|
||
import com.yingmao.clash.base.BaseActivity
|
||
import com.yingmao.clash.conf.BZYConstants
|
||
import com.yingmao.clash.conf.BZYConstants.Companion.ScreenFlag
|
||
import com.yingmao.clash.conf.Conf
|
||
import com.yingmao.clash.entity.CheckLoginRsp
|
||
import com.yingmao.clash.fragment.NewAccFragment
|
||
import com.yingmao.clash.fragment.NewAccFragment.Companion.CHECK_NODE_REQUEST_CODE
|
||
import com.yingmao.clash.listener.DownLoadListener
|
||
import com.yingmao.clash.net.http.HttpApi
|
||
import com.yingmao.clash.net.http.HttpReqType
|
||
import com.yingmao.clash.net.http.HttpStatus
|
||
import com.yingmao.clash.net.http.NetService
|
||
import com.yingmao.clash.net.http.entity.ExpensesRecordRsp
|
||
import com.yingmao.clash.net.http.entity.GetActivityRsp
|
||
import com.yingmao.clash.net.http.entity.HeartBeatRsp
|
||
import com.yingmao.clash.net.http.entity.PhDownloadUrlRsp
|
||
import com.yingmao.clash.net.http.entity.ShowNode
|
||
import com.yingmao.clash.net.http.entity.SubscriptionRsp
|
||
import com.yingmao.clash.net.http.entity.VipSignRsp
|
||
import com.yingmao.clash.remote.Remote
|
||
import com.yingmao.clash.util.AESUtil
|
||
import com.yingmao.clash.util.CommonUtils
|
||
import com.yingmao.clash.util.Utils
|
||
import com.yingmao.clash.util.stopClashService
|
||
import com.yingmao.clash.view.RxToast
|
||
import com.yingmao.clash.view.adapter.MainVPAdapter
|
||
import com.yingmao.clash.view.dialog.ActivityViewDialog
|
||
import com.yingmao.clash.view.dialog.ActivityViewDialog.OnDialogClickListener
|
||
import com.yingmao.clash.view.dialog.DialogAppVersion
|
||
import com.yingmao.clash.view.dialog.DialogBandChoose
|
||
import com.yingmao.clash.view.dialog.DialogNotice
|
||
import com.yingmao.clash.view.dialog.RxDialogShapeLoading
|
||
import com.yingmao.clash.vm.MainAtyVM
|
||
import com.yingmao.clash.webview.BZYWebViewKeFu
|
||
import com.yingmao.clash.webview.BZYWebViewNormal
|
||
import com.yingmao.clash.webview.CustomerServiceWebView
|
||
import kotlinx.coroutines.Dispatchers
|
||
import kotlinx.coroutines.GlobalScope
|
||
import kotlinx.coroutines.Job
|
||
import kotlinx.coroutines.async
|
||
import kotlinx.coroutines.delay
|
||
import kotlinx.coroutines.launch
|
||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||
import okhttp3.RequestBody
|
||
import java.io.File
|
||
import java.text.SimpleDateFormat
|
||
import java.util.Date
|
||
import java.util.Locale
|
||
import java.util.Timer
|
||
import java.util.TimerTask
|
||
import java.util.UUID
|
||
|
||
|
||
class NewMainActivity : BaseActivity() {
|
||
|
||
|
||
public val fragments = arrayListOf<Fragment>()
|
||
private lateinit var iv_slider: ImageView
|
||
private lateinit var ll_slider: ConstraintLayout
|
||
lateinit var containerVP: ViewPager2
|
||
val clashRunning: Boolean
|
||
get() = Remote.broadcasts.clashRunning
|
||
|
||
// lateinit var tv_rechar: Button
|
||
lateinit var tv_username: TextView
|
||
lateinit var tv_expire_date: TextView
|
||
private lateinit var drawer: DrawerLayout
|
||
private lateinit var mainAtyVM: MainAtyVM
|
||
private var activityDialog: ActivityViewDialog? = null
|
||
private var timer: Timer? = null
|
||
private var u_timerTask: TimerTask? = null
|
||
private lateinit var ivTor: AppCompatImageView
|
||
private lateinit var tvNetStr:TextView
|
||
private lateinit var tvTemporaryAccount:TextView
|
||
private val mPageInfo = PageInfo()
|
||
private var payResultLoading: RxDialogShapeLoading? = null
|
||
private var timerJob: Job? = null
|
||
private var loadingTimeoutNum = 3
|
||
@SuppressLint("SimpleDateFormat")
|
||
private val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
|
||
|
||
private lateinit var clExit:ConstraintLayout
|
||
companion object {
|
||
//记录心跳失败次数
|
||
var HEART_BEAT_FAILURE_FREQUENCY = 0
|
||
const val REQUEST_CODE_PAY_WEB_VIEW_ACTIVITY = 1000
|
||
var newUuid: String? = null
|
||
var subUrl: String? = null
|
||
var clashBinder: IClashManager? = null
|
||
var profileBinder: IProfileManager? = null
|
||
var newMainActGlo: Boolean = false
|
||
var isUpload: Boolean = false
|
||
var hostsMem: ArrayList<String> = ArrayList()
|
||
suspend fun setSub2(url: String) {
|
||
profileBinder?.let { pm ->
|
||
pm.queryAll().forEach { itemPro ->
|
||
if (itemPro.name != Conf.getClashConfName(MainApplication.getApp()?.applicationContext)) {
|
||
pm.delete(itemPro.uuid)
|
||
}
|
||
}
|
||
val pro = pm.queryAll().find {
|
||
it.name == Conf.getClashConfName(MainApplication.getApp()?.applicationContext)
|
||
}
|
||
pro?.let {
|
||
pm.setActive(it)
|
||
delay(500)
|
||
//判断配置文件source
|
||
if (!TextUtils.isEmpty(pro.source)) {
|
||
Log.d("setSub2 pro.source==>${pro.source}, url==>${url}")
|
||
//订阅链接不同就更换
|
||
if (pro.source != url) {
|
||
pm.patch(
|
||
pro.uuid, pro.name,
|
||
url, 1800000L
|
||
)
|
||
pm.commit(pro.uuid) { status ->
|
||
android.util.Log.d(
|
||
Conf.TAG,
|
||
"new updateStatus: " + Gson().toJson(status.args) + ", pro==>" + status.progress
|
||
)
|
||
}
|
||
}
|
||
}
|
||
pm.update(it.uuid)
|
||
newUuid = pro.uuid.toString()
|
||
} ?: run {
|
||
val uuid: UUID = pm.create(
|
||
Profile.Type.Url,
|
||
Conf.getClashConfName(MainApplication.getApp()?.applicationContext)
|
||
)
|
||
delay(500)
|
||
pm.queryByUUID(uuid)?.let { qPro ->
|
||
pm.setActive(qPro)
|
||
pm.patch(
|
||
qPro.uuid, qPro.name,
|
||
url, 1800000L
|
||
)
|
||
pm.commit(qPro.uuid) { status ->
|
||
android.util.Log.d(
|
||
Conf.TAG,
|
||
"new updateStatus: " + Gson().toJson(status.args) + ", pro==>" + status.progress
|
||
)
|
||
}
|
||
newUuid = qPro.uuid.toString()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
fun addOrSendHost(host: String) {
|
||
Log.d("hostsMem size==>${hostsMem.size}")
|
||
if (!hostsMem.contains(host) && !isApiHost(host)) {
|
||
hostsMem.add(host + "{" + dateFormat.format(Date()))
|
||
}
|
||
}
|
||
|
||
fun isApiHost(host: String): Boolean {
|
||
val mmkv = MMKV.defaultMMKV()
|
||
var isApiHost = false;
|
||
val originalUrl =
|
||
mmkv.decodeString(BZYConstants.MMKV_KEY_CURRENT_HOST, BuildConfig.service_url)
|
||
val routerHost = mmkv.decodeStringSet(BZYConstants.MMKV_KEY_ROUTER_HOST, HashSet())
|
||
// Log.d("originalUrl==>${originalUrl}, host==>${host}")
|
||
if (originalUrl?.contains(host) == true) {
|
||
isApiHost = true
|
||
return isApiHost
|
||
}
|
||
// Log.d("isApiHost==>${isApiHost}")
|
||
routerHost?.forEach { rh ->
|
||
isApiHost = rh.contains(host)
|
||
// Log.d("rh==>${rh}, host==>${host}, isApiHost==>${isApiHost}")
|
||
}
|
||
return isApiHost
|
||
}
|
||
|
||
fun uploadLogHttp() {
|
||
// Log.d("uploadLogHttp isUpload==>${isUpload}, memSize==>${hostsMem.size}")
|
||
if (hostsMem.size <= 0) {
|
||
return
|
||
}
|
||
if (isUpload) {
|
||
return
|
||
}
|
||
isUpload = true
|
||
GlobalScope.async {
|
||
try {
|
||
val hostsJsonArray = JsonArray()
|
||
for (hostItem in hostsMem) {
|
||
val hostAndTime = hostItem.split("{")
|
||
val hostJsonObject = JsonObject()
|
||
hostJsonObject.addProperty(
|
||
"id", MMKV.defaultMMKV().decodeInt(BZYConstants.MMKV_KEY_NOTE_NODE_ID, 0)
|
||
)
|
||
hostJsonObject.addProperty("requestHosts", hostAndTime[0])
|
||
hostJsonObject.addProperty("requestTime", hostAndTime[1])
|
||
hostsJsonArray.add(hostJsonObject)
|
||
}
|
||
val httpApi = NetService.instances.createHttps(HttpApi::class.java)
|
||
val hostsJsonArrayStr = hostsJsonArray.toString()
|
||
val hostsJsonArrayStrObject = JsonObject()
|
||
hostsJsonArrayStrObject.addProperty("requestLogs", hostsJsonArrayStr)
|
||
val phToken = MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_PH_TOKEN, "")
|
||
hostsJsonArrayStrObject.addProperty("phToken", phToken)
|
||
|
||
val encryptHostsJsonArrayStrObject = AESUtil.byteArrayToHexStr(
|
||
AESUtil.encrypt(hostsJsonArrayStrObject.toString())
|
||
)
|
||
// Log.d("encryptHostsJsonArrayStrObject==>$encryptHostsJsonArrayStrObject")
|
||
val requestBody: RequestBody = RequestBody.create(
|
||
"text/plain".toMediaTypeOrNull(), encryptHostsJsonArrayStrObject
|
||
)
|
||
val uploadRequestLogs = httpApi.uploadRequestLogs(requestBody)
|
||
Log.d("uploadRequestLogs==>${uploadRequestLogs}")
|
||
// if ("0" == uploadRequestLogs.code) {
|
||
// }
|
||
//登录失效
|
||
if (uploadRequestLogs.code == "-3") {
|
||
}
|
||
} catch (e: Exception) {
|
||
e.printStackTrace()
|
||
} finally {
|
||
hostsMem.clear()
|
||
isUpload = false
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
private val REQUEST_CODE_WRITE_STORAGE = 1
|
||
override fun onCreate(savedInstanceState: Bundle?) {
|
||
super.onCreate(savedInstanceState)
|
||
setContentView(R.layout.new_main_activity)
|
||
|
||
profileBinder =
|
||
MainApplication.getApp()?.let { ProfileManager(it).wrap() as IProfileManager }
|
||
clashBinder = MainApplication.getApp()?.let { ClashManager(it).wrap() as IClashManager }
|
||
|
||
// 文件权限请求
|
||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||
if (ContextCompat.checkSelfPermission(
|
||
this,
|
||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||
) != PackageManager.PERMISSION_GRANTED
|
||
) {
|
||
ActivityCompat.requestPermissions(
|
||
this,
|
||
arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
|
||
REQUEST_CODE_WRITE_STORAGE
|
||
)
|
||
}
|
||
}
|
||
initViews()
|
||
mainAtyVM = ViewModelProvider(this)[MainAtyVM::class.java]
|
||
observe(this, mainAtyVM)
|
||
|
||
mainAtyVM.startHeartBeat()
|
||
mainAtyVM.getAirNodes()
|
||
mainAtyVM.getExpensesRecord(mPageInfo)
|
||
|
||
val isGlobal = MMKV.defaultMMKV().decodeBool(BZYConstants.MMKV_KEY_GLOBAL_MODEL, false)
|
||
newMainActGlo = isGlobal
|
||
|
||
startLog()
|
||
firstEntry()
|
||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||
val requestPermissionLauncher =
|
||
registerForActivityResult(
|
||
RequestPermission()
|
||
) { isGranted: Boolean ->
|
||
}
|
||
if (ContextCompat.checkSelfPermission(
|
||
this,
|
||
android.Manifest.permission.POST_NOTIFICATIONS
|
||
) != PackageManager.PERMISSION_GRANTED
|
||
) {
|
||
requestPermissionLauncher.launch(android.Manifest.permission.POST_NOTIFICATIONS)
|
||
}
|
||
}
|
||
//判断节点是否可以选择
|
||
mainAtyVM.getSelectNodeInfo()
|
||
}
|
||
|
||
/*
|
||
*活动弹窗
|
||
* */
|
||
private fun activityPopupWindow() {
|
||
mainAtyVM.getActivity()
|
||
}
|
||
|
||
private fun firstEntry() {
|
||
val decodeBool =
|
||
MMKV.defaultMMKV().decodeBool(BZYConstants.INITIALIZATION, false)
|
||
if ((decodeBool)) {
|
||
MaterialDialog(this).show {
|
||
cornerRadius(res = R.dimen.dp_10)
|
||
customView(
|
||
R.layout.first_entry_layout,
|
||
scrollable = false,
|
||
noVerticalPadding = true,
|
||
horizontalPadding = false
|
||
)
|
||
getCustomView().findViewById<TextView>(R.id.determine).setOnClickListener {
|
||
MMKV.defaultMMKV().encode(BZYConstants.INITIALIZATION, false)
|
||
dismiss()
|
||
}
|
||
}
|
||
} else {
|
||
mainAtyVM.getNoticeInfo()
|
||
activityPopupWindow()
|
||
}
|
||
}
|
||
|
||
fun startUploadTimer() {
|
||
stopUploadTimer()
|
||
if (timer == null) {
|
||
timer = Timer()
|
||
u_timerTask = object : TimerTask() {
|
||
override fun run() {
|
||
// Log.d("60s执行上传==>")
|
||
uploadLogHttp()
|
||
}
|
||
}
|
||
}
|
||
timer?.schedule(u_timerTask, 60000, 60000)
|
||
}
|
||
|
||
fun stopUploadTimer() {
|
||
timer?.cancel()
|
||
timer = null
|
||
u_timerTask?.cancel()
|
||
u_timerTask = null
|
||
}
|
||
|
||
private fun startLog() {
|
||
|
||
launch(Dispatchers.IO) {
|
||
// startForegroundServiceCompat(LogcatService::class.intent)
|
||
// val logcat = bindLogcatService()
|
||
// val ticker = ticker(500)
|
||
// var initial = true
|
||
// while (isActive) {
|
||
// select<Unit> {
|
||
// ticker.onReceive {
|
||
// val snapshot = logcat.snapshot(initial) ?: return@onReceive
|
||
// Log.i("startLog meessage==>${snapshot.messages}, removed==>${snapshot.removed}, appended==>${snapshot.appended}")
|
||
// initial = false
|
||
// }
|
||
// }
|
||
// }
|
||
|
||
val observer = object : ILogObserver {
|
||
override fun newItem(log: LogMessage) {
|
||
var msg = log.message
|
||
if (BuildConfig.DEBUG) {
|
||
Log.i("startLog msg==>${msg}")
|
||
}
|
||
var domain = Utils.getDomainNameFromString(msg)
|
||
// if (BuildConfig.DEBUG) {
|
||
// Log.i("startLog meessage==>${domain}")
|
||
// }
|
||
if (!TextUtils.isEmpty(domain)) {
|
||
addOrSendHost(domain!!)
|
||
}
|
||
}
|
||
}
|
||
|
||
clashBinder?.let {
|
||
it.setLogObserver(observer)
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
override fun httpLoading(httpStatus: HttpStatus.Loading) {
|
||
if (httpStatus.reqType == HttpReqType.HEARTBEAT) {
|
||
Logger.d("心跳中")
|
||
} else if (httpStatus.reqType == HttpReqType.CHECK_LOGIN) {
|
||
showLoadingDialog(resources.getString(R.string.QueryingPaymentResults))
|
||
}
|
||
}
|
||
|
||
override fun httpSuccess(httpStatus: HttpStatus.Success) {
|
||
Logger.d("心跳成功")
|
||
if (httpStatus.reqType == HttpReqType.HEARTBEAT) {
|
||
if (httpStatus.rsp is HeartBeatRsp) {
|
||
httpStatus.rsp.let {
|
||
val mmkv = MMKV.defaultMMKV()
|
||
when (it.code) {
|
||
0 -> {
|
||
mmkv.encode(BZYConstants.MMKV_KEY_VPN_EXPIRED, false)
|
||
HEART_BEAT_FAILURE_FREQUENCY = 0
|
||
}
|
||
|
||
-2 -> {
|
||
mmkv.encode(BZYConstants.MMKV_KEY_VPN_EXPIRED, false)
|
||
HEART_BEAT_FAILURE_FREQUENCY = 0
|
||
|
||
cleanUserInfo()
|
||
}
|
||
|
||
-4 -> {
|
||
mmkv.encode(BZYConstants.MMKV_KEY_VPN_EXPIRED, false)
|
||
HEART_BEAT_FAILURE_FREQUENCY = 0
|
||
cleanUserInfo()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} else if (httpStatus.reqType == HttpReqType.PHDOWNLOADURL) {
|
||
(httpStatus.rsp as PhDownloadUrlRsp).let {
|
||
if (it.code == 1000) {
|
||
val lastNotice =
|
||
MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_NOTICE_READ, "")
|
||
val data = it.data
|
||
// if (data?.notice != null) {
|
||
// initMainPageHorseRaceLamp(data.notice)
|
||
// }
|
||
if (data?.notice != null && lastNotice != data.notice) {
|
||
val notice = data.notice
|
||
val dialog = DialogNotice(this@NewMainActivity, notice, data.downloadUrl)
|
||
dialog.setOnReadClickListener(object : DialogNotice.OnReadClickListener {
|
||
override fun onReadClick() {
|
||
MMKV.defaultMMKV()
|
||
.encode(BZYConstants.MMKV_KEY_NOTICE_READ, data.notice)
|
||
MMKV.defaultMMKV().encode(
|
||
BZYConstants.MMKV_KEY_NOTICE_DOWNLOAD_READ, data.downloadUrl
|
||
)
|
||
dialog.dismiss()
|
||
}
|
||
})
|
||
dialog.show()
|
||
}
|
||
}
|
||
}
|
||
} else if (httpStatus.reqType == HttpReqType.GetRuningNotice) {
|
||
(httpStatus.rsp as PhDownloadUrlRsp).let {
|
||
if (it.code == 1000) {
|
||
val data = it.data
|
||
if (data?.notice != null) {
|
||
initMainPageHorseRaceLamp(data.notice)
|
||
}
|
||
}
|
||
}
|
||
} else if (httpStatus.reqType == HttpReqType.CHECK_LOGIN) {
|
||
timerJob?.cancel()
|
||
(httpStatus.rsp as CheckLoginRsp).let { checkLoginRsp ->
|
||
if (checkLoginRsp.code == 0) {
|
||
val mmkv = MMKV.defaultMMKV()
|
||
val oldExpireTime = mmkv.decodeLong(BZYConstants.MMKV_KEY_VPN_VALID_PERIOD, 0)
|
||
val nowExpireTime = checkLoginRsp.data!!.vpnExpireTime
|
||
if (nowExpireTime > oldExpireTime) {
|
||
val expired = checkLoginRsp.data.expired
|
||
mmkv.encode(BZYConstants.MMKV_KEY_VPN_EXPIRED, expired ?: true)
|
||
mmkv.encode(BZYConstants.MMKV_KEY_VPN_VALID_PERIOD, nowExpireTime)
|
||
setExpireDate()
|
||
fragments.filterIsInstance<NewAccFragment>().forEach { it.setExpireDate() }
|
||
checkPayResultDia?.dismiss()
|
||
mainAtyVM.getAirNodes()
|
||
RxToast.info(resources.getString(R.string.SuccessfullyUpdatedUserInformation))
|
||
} else {
|
||
// RxToast.info(resources.getString(R.string.PaymentResultsStr))
|
||
}
|
||
} else {
|
||
RxToast.info(resources.getString(R.string.QueryFailed))
|
||
}
|
||
payResultLoading?.dismiss()
|
||
}
|
||
} else if (httpStatus.reqType == HttpReqType.GET_AIRNODES) {
|
||
val data = httpStatus.rsp as SubscriptionRsp
|
||
if (data.code != "1000") {
|
||
RxToast.error(resources.getString(R.string.NodeAcquisitionFailed) + "${data.msg}")
|
||
}
|
||
data.data.let { url ->
|
||
android.util.Log.e("Data", "机场URL:=${url}")
|
||
// var url2="https://api.ymvpnpro11.cc:8700/7air2vr7ikiqzgvxz/api/v1/client/subscribe?token=5af82930d222840cbc2dec572b21d6c6"
|
||
if (url.isNotEmpty()) {
|
||
//todo 判断订阅地址是否修改过,未修改就用接口返回的地址反之使用本地修改的地址
|
||
val proSubUrl =
|
||
MMKV.defaultMMKV().decodeString(BZYConstants.PROFILE_SUB_URL, "")
|
||
subUrl = if (!TextUtils.isEmpty(proSubUrl)) {
|
||
if (proSubUrl == url) {
|
||
url
|
||
} else {
|
||
proSubUrl
|
||
}
|
||
} else {
|
||
url
|
||
}
|
||
android.util.Log.i("AAAAAAAAAAA","pro_sub_url data:::"+proSubUrl+"::subUrl::"+ subUrl)
|
||
lifecycleScope.launch(Dispatchers.IO) {
|
||
subUrl?.let { setSub2(it) }
|
||
}
|
||
}
|
||
}
|
||
} else if (httpStatus.reqType == HttpReqType.VIPSIGN) {
|
||
val data = httpStatus.rsp as VipSignRsp
|
||
if (data.code == "1000") {
|
||
var gifMains = 30
|
||
data.data?.let {
|
||
if (it.signGiftmins != null) {
|
||
gifMains = it.signGiftmins;
|
||
}
|
||
}
|
||
RxToast.success(
|
||
resources.getString(R.string.SingInGive).replace("%d", "${gifMains}")
|
||
)
|
||
} else {
|
||
RxToast.error(resources.getString(R.string.CheckFailed) + "${data.message}")
|
||
}
|
||
} else if (httpStatus.reqType == HttpReqType.GetActivity) {
|
||
val data = httpStatus.rsp as GetActivityRsp
|
||
if (data.code == 200) {
|
||
if (data.data != null) {
|
||
if (data.data.data != null) {
|
||
if (data.data.data.list.isNotEmpty()) {
|
||
data.data.data.list[0].redirectLink.let {
|
||
if(activityDialog==null){
|
||
activityDialog =
|
||
ActivityViewDialog(this, data.data.data.list[0].redirectLink)
|
||
activityDialog?.setOnDialogClickListener(object :
|
||
OnDialogClickListener {
|
||
override fun onRechargeClickListener() {
|
||
//跳转到支付界面
|
||
goBackupRechargeWeb()
|
||
}
|
||
|
||
override fun onCancelClickListener() {
|
||
activityDialog?.dismiss()
|
||
}
|
||
})
|
||
activityDialog?.show()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}else if(httpStatus.reqType == HttpReqType.EXPENSES_RECORD){
|
||
if(httpStatus.rsp is ExpensesRecordRsp){
|
||
httpStatus.rsp.let { expensesRecordRsp ->
|
||
if(expensesRecordRsp.code == "1000"){
|
||
if(expensesRecordRsp.data != null) {
|
||
val expensesRecordList = expensesRecordRsp.data.expensesRecords
|
||
if(expensesRecordList!=null){
|
||
if(expensesRecordList.size>0){
|
||
tvTemporaryAccount.text=resources.getString(R.string.MemberAccount)
|
||
}else{
|
||
tvTemporaryAccount.text=resources.getString(R.string.TemporaryUser)
|
||
}
|
||
}else{
|
||
tvTemporaryAccount.text=resources.getString(R.string.TemporaryUser)
|
||
}
|
||
}else{
|
||
tvTemporaryAccount.text=resources.getString(R.string.TemporaryUser)
|
||
}
|
||
}else{
|
||
tvTemporaryAccount.text=resources.getString(R.string.TemporaryUser)
|
||
}
|
||
}
|
||
}
|
||
}else if(httpStatus.reqType == HttpReqType.SelectNode){
|
||
(httpStatus.rsp as ShowNode).let {
|
||
if (it.code == 200) {
|
||
val screenshotFlag = it.data?.screenshotFlag
|
||
screenshotFlag.let {
|
||
if (screenshotFlag == true) {
|
||
// 在 Activity 中取消禁止截屏/录屏
|
||
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||
MMKV.defaultMMKV().encode(
|
||
ScreenFlag,screenshotFlag
|
||
)
|
||
}else{
|
||
window.setFlags(
|
||
WindowManager.LayoutParams.FLAG_SECURE,
|
||
WindowManager.LayoutParams.FLAG_SECURE
|
||
)
|
||
MMKV.defaultMMKV().encode(
|
||
ScreenFlag,screenshotFlag!!
|
||
)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
override fun httpFailure(httpStatus: HttpStatus.Failure) {
|
||
if (httpStatus.reqType == HttpReqType.HEARTBEAT) {
|
||
Logger.d("心跳失败")
|
||
if (5 <= HEART_BEAT_FAILURE_FREQUENCY) {
|
||
HEART_BEAT_FAILURE_FREQUENCY = 0
|
||
Logger.d("心跳失败次数超过10次,退出登录")
|
||
cleanUserInfo()
|
||
}
|
||
HEART_BEAT_FAILURE_FREQUENCY++
|
||
} else if (httpStatus.reqType == HttpReqType.CHECK_LOGIN) {
|
||
payResultLoading?.dismiss()
|
||
RxToast.error(resources.getString(R.string.QueryFailed))
|
||
} else if (httpStatus.reqType == HttpReqType.GET_AIRNODES) {
|
||
Logger.e("获取节点异常:${httpStatus.msg}")
|
||
} else if (httpStatus.reqType == HttpReqType.VIPSIGN) {
|
||
RxToast.error(resources.getString(R.string.CheckFailed) + httpStatus.msg)
|
||
}
|
||
}
|
||
|
||
|
||
|
||
private fun initViews() {
|
||
containerVP = findViewById(R.id.container)
|
||
ivTor=findViewById(R.id.ivTor)
|
||
tvNetStr=findViewById(R.id.tvNetStr)
|
||
tvTemporaryAccount=findViewById(R.id.tvTemporaryAccount)
|
||
val selectedName =
|
||
MMKV.defaultMMKV().decodeString(BZYConstants.NODE_SELECTED_NAME_KEY, "自动选择")
|
||
tvNetStr.text=selectedName
|
||
containerVP.offscreenPageLimit = 1
|
||
if (fragments.isNotEmpty()) fragments.clear()
|
||
fragments.add(NewAccFragment())
|
||
// fragments.add(RechargeFragment())
|
||
containerVP.adapter = MainVPAdapter(fragments, supportFragmentManager, lifecycle)
|
||
containerVP.isUserInputEnabled = false
|
||
var aniAct = findViewById<LottieAnimationView>(R.id.lottie_activity)
|
||
aniAct.setAnimation(R.raw.activity)
|
||
aniAct.playAnimation()
|
||
aniAct.setOnClickListener {
|
||
startActivity(Intent(CommonUtils.getApp(), ActivityActivity::class.java))
|
||
}
|
||
findViewById<LinearLayout>(R.id.ll_activity).setOnClickListener {
|
||
startActivity(Intent(CommonUtils.getApp(), ActivityActivity::class.java))
|
||
}
|
||
setSometion()
|
||
}
|
||
|
||
private fun initMainPageHorseRaceLamp(noticeData: String) {
|
||
val mainPageHorseRaceLamp = findViewById<TextView>(R.id.mainPageHorseRaceLamp)
|
||
if (noticeData.isNotEmpty()) {
|
||
mainPageHorseRaceLamp.text = noticeData
|
||
mainPageHorseRaceLamp.requestFocus()
|
||
mainPageHorseRaceLamp.marqueeRepeatLimit = Integer.MAX_VALUE
|
||
mainPageHorseRaceLamp.isFocusable = true
|
||
mainPageHorseRaceLamp.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||
mainPageHorseRaceLamp.setSingleLine()
|
||
mainPageHorseRaceLamp.isFocusableInTouchMode = true
|
||
mainPageHorseRaceLamp.setHorizontallyScrolling(true)
|
||
} else {
|
||
val llNoticeBackground = findViewById<LinearLayout>(R.id.llNoticeBackground)
|
||
llNoticeBackground.visibility = View.GONE
|
||
}
|
||
}
|
||
|
||
private fun cleanUserInfo() {
|
||
MMKV.defaultMMKV().removeValueForKey(BZYConstants.MMKV_KEY_PH_TOKEN)
|
||
MMKV.defaultMMKV().removeValueForKey(BZYConstants.MMKV_KEY_TOKEN)
|
||
MMKV.defaultMMKV().removeValueForKey(BZYConstants.MMKV_KEY_CURRENT_NODE)
|
||
MMKV.defaultMMKV().removeValueForKey(BZYConstants.NODE_SELECTED_NAME_KEY)
|
||
MMKV.defaultMMKV().removeValueForKey(BZYConstants.MMKV_KEY_VPN_UserCommand)
|
||
MMKV.defaultMMKV().removeValueForKey(BZYConstants.NODE_All_ITEM_NAME_KEY)
|
||
MMKV.defaultMMKV().removeValueForKey(BZYConstants.PROFILE_SUB_URL)
|
||
MMKV.defaultMMKV().removeValueForKey(BZYConstants.MMKV_KEY_Switch_Finsh_Set)
|
||
Log.d("cleanUserInfo clashRunning==>${clashRunning}")
|
||
if (clashRunning) {
|
||
stopClashService()
|
||
}
|
||
stopUploadTimer()
|
||
mainAtyVM.stopHeartBeat()
|
||
cleanProfile()
|
||
val intent = Intent(this, LoginActivity::class.java)
|
||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||
startActivity(intent)
|
||
finish()
|
||
}
|
||
|
||
|
||
private fun cleanProfile() {
|
||
Log.d("cleanProfile start")
|
||
GlobalScope.launch(Dispatchers.IO) {
|
||
profileBinder?.let { pm ->
|
||
pm.queryAll().forEach { profile ->
|
||
Log.d("cleanProfile go delete name==>${profile.name}, uuid==>${profile.uuid}")
|
||
pm.delete(profile.uuid)
|
||
}
|
||
} ?: let {
|
||
Log.d("profileBinder is null")
|
||
}
|
||
}
|
||
}
|
||
|
||
fun setSometion() {
|
||
tv_expire_date = findViewById(R.id.tv_expire_date)
|
||
setExpireDate()
|
||
tv_username = findViewById(R.id.tv_username)
|
||
iv_slider = findViewById(R.id.iv_slider)
|
||
ll_slider = findViewById(R.id.ll_slider)
|
||
clExit=findViewById(R.id.clExit)
|
||
clExit.setOnClickListener {
|
||
cleanUserInfo()
|
||
}
|
||
|
||
ivTor.setOnClickListener {
|
||
startActivity(Intent(this@NewMainActivity, PersonalCenter::class.java))
|
||
drawer.close()
|
||
}
|
||
if (MMKV.defaultMMKV().decodeBool(BZYConstants.MMKV_KEY_IS_PERFECT)) {
|
||
tv_username.text = "ID:${
|
||
MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_REMEMBER_USERNAME)
|
||
}"
|
||
} else {
|
||
tv_username.text = "ID:${
|
||
MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_REMEMBER_USERNAME)
|
||
}"
|
||
}
|
||
findViewById<LinearLayout>(R.id.llOpenRechre).setOnClickListener {//充值
|
||
|
||
drawer.close()
|
||
goBackupRechargeWeb()
|
||
}
|
||
|
||
findViewById<ConstraintLayout>(R.id.clMessage).setOnClickListener {//通知
|
||
val notice =
|
||
MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_NOTICE_READ, "暂无新通知")!!
|
||
val downloadUrl =
|
||
MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_NOTICE_DOWNLOAD_READ, "")!!
|
||
val dialog = DialogNotice(this@NewMainActivity, notice, downloadUrl)
|
||
dialog.setOnReadClickListener(object : DialogNotice.OnReadClickListener {
|
||
override fun onReadClick() {
|
||
dialog.dismiss()
|
||
}
|
||
})
|
||
dialog.show()
|
||
drawer.close()
|
||
}
|
||
|
||
tv_username.setOnClickListener {
|
||
val userName = MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_REMEMBER_USERNAME)
|
||
val clipboardManager = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||
val clipData = ClipData.newPlainText("text", userName)
|
||
clipboardManager.setPrimaryClip(clipData)
|
||
RxToast.info(resources.getString(R.string.CopyToClipboard))
|
||
}
|
||
|
||
// tv_rechar.setOnClickListener {
|
||
// goRechargeWeb()
|
||
// }
|
||
|
||
findViewById<ConstraintLayout>(R.id.clLocation).setOnClickListener {
|
||
if (checkExpire()) {
|
||
RxToast.info(resources.getString(R.string.AccountExpired))
|
||
return@setOnClickListener
|
||
}
|
||
val intent = Intent(CommonUtils.getApp(), ChangeNodeActivity::class.java)
|
||
if (!TextUtils.isEmpty(newUuid)) {
|
||
intent.putExtra(BZYConstants.MMKV_KEY_CURRENT_NODE, newUuid)
|
||
intent.putExtra(BZYConstants.EXTRA_KEY_IS_RUNING, clashRunning)
|
||
}
|
||
startActivityForResult(intent, CHECK_NODE_REQUEST_CODE)
|
||
drawer.close()
|
||
}
|
||
findViewById<ConstraintLayout>(R.id.clShareKo).setOnClickListener {//分享
|
||
startActivity(Intent(this@NewMainActivity, ShareActivity::class.java))
|
||
drawer.close()
|
||
}
|
||
findViewById<ConstraintLayout>(R.id.clSetting).setOnClickListener {//设置
|
||
startActivity(Intent(this, SettingActivity::class.java))
|
||
drawer.close()
|
||
}
|
||
findViewById<ConstraintLayout>(R.id.clFire).setOnClickListener {
|
||
startActivity(Intent(this, SettingActivity::class.java))
|
||
drawer.close()
|
||
}
|
||
findViewById<ConstraintLayout>(R.id.clCustomerService).setOnClickListener {//客服
|
||
startActivity(Intent(this, CustomerServiceWebView::class.java))
|
||
drawer.close()
|
||
}
|
||
drawer = findViewById(R.id.drawer)
|
||
iv_slider.setOnClickListener {
|
||
if (containerVP.currentItem != 0) {
|
||
iv_slider.setImageResource(R.mipmap.home_setting)
|
||
containerVP.setCurrentItem(0, true)
|
||
} else {
|
||
drawer.openDrawer(GravityCompat.START)
|
||
}
|
||
}
|
||
}
|
||
|
||
var checkPayResultDia: MaterialDialog? = null
|
||
var checkBindAccountDia: MaterialDialog? = null
|
||
public fun goRechargeWeb() {
|
||
val mmkv = MMKV.defaultMMKV()
|
||
val a = mmkv.decodeString(BZYConstants.MMKV_KEY_REMEMBER_USERNAME, "")
|
||
val b = mmkv.decodeString(BZYConstants.MMKV_KEY_USER_ID, "")
|
||
val c = mmkv.decodeString(BZYConstants.MMKV_KEY_PH_TOKEN, "")
|
||
val intent = Intent(this@NewMainActivity, BZYWebViewNormal::class.java)
|
||
val rcURL = NetService.getCurrentUrl() + "/78zccgy0nsgknh9n4/recharge?a=$a&b=$b&c=$c&d=2"
|
||
// val rcURL = "https://api.gaogaoshot.cc:18001" + "/78zccgy0nsgknh9n4/recharge?a=$a&b=$b&c=$c&d=2"
|
||
Log.d("rcURL==>${rcURL}")
|
||
intent.putExtra("url", rcURL)
|
||
// startActivity(intent)
|
||
startActivityForResult(intent, REQUEST_CODE_PAY_WEB_VIEW_ACTIVITY)
|
||
}
|
||
|
||
fun goBackupRechargeWeb() {
|
||
startActivityForResult(
|
||
Intent(this@NewMainActivity, MemberRechargeActivity::class.java),
|
||
REQUEST_CODE_PAY_WEB_VIEW_ACTIVITY
|
||
)
|
||
}
|
||
|
||
fun goCheckLogin() {
|
||
mainAtyVM.checkLogin()
|
||
}
|
||
private fun showBindAccount() {
|
||
val userAccount =
|
||
MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_REMEMBER_USERNAME, "")!!
|
||
if (Utils.isEmail(userAccount) || Utils.isPhoneNumber(userAccount)) {
|
||
return
|
||
}
|
||
|
||
checkBindAccountDia = MaterialDialog(this).show {
|
||
this.cancelable(false)
|
||
cornerRadius(res = R.dimen.dp_10)
|
||
customView(
|
||
R.layout.bind_account_dialog_layout,
|
||
scrollable = false,
|
||
noVerticalPadding = true,
|
||
horizontalPadding = false
|
||
)
|
||
getCustomView().findViewById<TextView>(R.id.tv_pay_finish).setOnClickListener {
|
||
//跳转到绑定邮箱界面
|
||
showBingDialogChoose()
|
||
dismiss()
|
||
}
|
||
getCustomView().findViewById<TextView>(R.id.tv_pay_cancle).setOnClickListener {
|
||
dismiss()
|
||
}
|
||
}
|
||
}
|
||
|
||
private fun showBingDialogChoose() {
|
||
val dialog = DialogBandChoose(this@NewMainActivity)
|
||
dialog.setOnBandClickListener(object : DialogBandChoose.OnBandClickListener {
|
||
override fun onBandClick(isPhone: Boolean) {
|
||
val intent = Intent(CommonUtils.getApp(), BandPhoneActivity::class.java)
|
||
intent.putExtra("isPhone", isPhone)
|
||
startActivity(intent)
|
||
}
|
||
})
|
||
dialog.show()
|
||
}
|
||
private fun showLoadingDialog(msg: String) {
|
||
payResultLoading = RxDialogShapeLoading(this)
|
||
payResultLoading!!.setMessage(msg)
|
||
payResultLoading!!.show()
|
||
loadingTimeoutNum = 3
|
||
timerJob = launch {
|
||
repeat(4) {
|
||
Log.d("loadingTimeoutNum==>${loadingTimeoutNum}")
|
||
if (loadingTimeoutNum == 0) {
|
||
payResultLoading?.dismiss()
|
||
}
|
||
loadingTimeoutNum--
|
||
delay(1000)
|
||
}
|
||
}
|
||
}
|
||
|
||
override fun onBackPressed() {
|
||
if (containerVP.currentItem != 0) {
|
||
iv_slider.setImageResource(R.drawable.ic_s_menu)
|
||
containerVP.setCurrentItem(0, true)
|
||
} else {
|
||
val backHome = Intent(Intent.ACTION_MAIN)
|
||
backHome.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||
backHome.addCategory(Intent.CATEGORY_HOME)
|
||
startActivity(backHome)
|
||
return
|
||
}
|
||
super.onBackPressed()
|
||
}
|
||
private fun checkExpire(): Boolean {
|
||
val validPeriod = MMKV.defaultMMKV().decodeLong(BZYConstants.MMKV_KEY_VPN_VALID_PERIOD)
|
||
if (validPeriod > 0) {
|
||
val nowTime = System.currentTimeMillis()
|
||
Log.d("validPeriod==>$validPeriod, nowTime==>$nowTime")
|
||
if (validPeriod >= nowTime) {
|
||
return false
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
@SuppressLint("SimpleDateFormat", "SetTextI18n")
|
||
fun setExpireDate() {
|
||
val isExpire = checkExpire()
|
||
var endTimeInfo = "--"
|
||
if (isExpire) {
|
||
endTimeInfo = resources.getString(R.string.AccountExpired)
|
||
} else {
|
||
val validPeriod = MMKV.defaultMMKV().decodeLong(BZYConstants.MMKV_KEY_VPN_VALID_PERIOD)
|
||
|
||
val decodeString = MMKV.defaultMMKV().decodeString(BZYConstants.languageInfo, "")
|
||
if (decodeString.isNullOrEmpty()) {
|
||
//获取当前系统语言
|
||
val config = resources.configuration
|
||
val language = config.locale.language
|
||
if (language.equals("en")) {
|
||
val dateFormat = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||
val locale = Locale.ENGLISH
|
||
SimpleDateFormat("MM/dd/yyyy HH:mm", locale)
|
||
} else {
|
||
val locale = Locale.ENGLISH
|
||
SimpleDateFormat("MM/dd/yyyy HH:mm", locale)
|
||
}
|
||
endTimeInfo = dateFormat.format(validPeriod)
|
||
} else {
|
||
val dateFormat = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||
SimpleDateFormat("yyyy年MM月dd日 HH:mm")
|
||
} else {
|
||
SimpleDateFormat("yyyy年MM月dd日 HH:mm")
|
||
}
|
||
endTimeInfo = dateFormat.format(validPeriod)
|
||
}
|
||
} else {
|
||
if (decodeString == "English") {
|
||
val dateFormat = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||
val locale = Locale.ENGLISH
|
||
SimpleDateFormat("MM/dd/yyyy HH:mm", locale)
|
||
} else {
|
||
val locale = Locale.ENGLISH
|
||
SimpleDateFormat("MM/dd/yyyy HH:mm", locale)
|
||
}
|
||
endTimeInfo = dateFormat.format(validPeriod)
|
||
} else {
|
||
val dateFormat = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||
SimpleDateFormat("yyyy年MM月dd日 HH:mm")
|
||
} else {
|
||
SimpleDateFormat("yyyy年MM月dd日 HH:mm")
|
||
}
|
||
endTimeInfo = dateFormat.format(validPeriod)
|
||
|
||
}
|
||
}
|
||
}
|
||
|
||
tv_expire_date.text = endTimeInfo
|
||
}
|
||
override fun onRequestPermissionsResult(
|
||
requestCode: Int, permissions: Array<out String>, grantResults: IntArray,
|
||
) {
|
||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||
if (requestCode == REQUEST_CODE_WRITE_STORAGE) {
|
||
if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
|
||
// 权限授予,执行下载操作
|
||
|
||
} else {
|
||
// 权限拒绝,处理相应逻辑
|
||
// 提示用户权限被拒绝
|
||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||
if (shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
|
||
// 显示提示信息
|
||
RxToast.info(resources.getString(R.string.giveMyPermission))
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||
super.onActivityResult(requestCode, resultCode, data)
|
||
|
||
if (requestCode == REQUEST_CODE_PAY_WEB_VIEW_ACTIVITY) {
|
||
if (activityDialog != null) {
|
||
activityDialog?.dismiss()
|
||
}
|
||
goCheckLogin()
|
||
showBindAccount()
|
||
}else if(requestCode == CHECK_NODE_REQUEST_CODE){
|
||
fragments.filterIsInstance<NewAccFragment>().forEach { it.nodeChooseBase(data) }
|
||
val selectedName =
|
||
MMKV.defaultMMKV().decodeString(BZYConstants.NODE_SELECTED_NAME_KEY, "自动选择")
|
||
tvNetStr.text=selectedName
|
||
}
|
||
}
|
||
|
||
override fun onDestroy() {
|
||
if (activityDialog != null) {
|
||
if(activityDialog!!.isShowing){
|
||
activityDialog!!.dismiss()
|
||
}
|
||
|
||
}
|
||
super.onDestroy()
|
||
stopUploadTimer()
|
||
mainAtyVM.stopHeartBeat()
|
||
}
|
||
class PageInfo {
|
||
var page = 1
|
||
fun nextPage() {
|
||
page++
|
||
}
|
||
|
||
fun reset() {
|
||
page = 1
|
||
}
|
||
|
||
val isFirstPage: Boolean
|
||
get() = page == 1
|
||
}
|
||
} |