最新 Android 客户端源代码以及教程,完全免费开源最新版本
free all
This commit is contained in:
@@ -189,11 +189,14 @@ abstract class BaseActivity<D : Design<*>> : AppCompatActivity(),
|
||||
}
|
||||
|
||||
private fun applyDayNight(config: Configuration = resources.configuration) {
|
||||
val dayNight = queryDayNight(config)
|
||||
// val dayNight = theme.applyStyle(R.style.AppThemeLight, true) //默认白天模式
|
||||
|
||||
/*
|
||||
val dayNight = queryDayNight(config)
|
||||
when (dayNight) {
|
||||
DayNight.Night -> theme.applyStyle(R.style.AppThemeDark, true)
|
||||
DayNight.Day -> theme.applyStyle(R.style.AppThemeLight, true)
|
||||
}
|
||||
}*/
|
||||
|
||||
window.isAllowForceDarkCompat = false
|
||||
window.isSystemBarsTranslucentCompat = true
|
||||
@@ -209,7 +212,7 @@ abstract class BaseActivity<D : Design<*>> : AppCompatActivity(),
|
||||
window.isLightNavigationBarCompat = resolveThemedBoolean(android.R.attr.windowLightNavigationBar)
|
||||
}
|
||||
|
||||
this.dayNight = dayNight
|
||||
this.dayNight = DayNight.Night //dayNight
|
||||
}
|
||||
|
||||
enum class Event {
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import com.github.kr328.clash.common.util.intent
|
||||
import com.github.kr328.clash.common.util.ticker
|
||||
import com.github.kr328.clash.design.ConfigOrderDesign
|
||||
import com.github.kr328.clash.design.PreferenceManager
|
||||
import com.github.kr328.clash.design.ProfileDesign
|
||||
import com.github.kr328.clash.network.PlanData
|
||||
import com.github.kr328.clash.utity.LoadingDialog
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.selects.select
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
//ConfigOrderDesign
|
||||
|
||||
class ConfigOrderActivity : BaseActivity<ConfigOrderDesign>() {
|
||||
|
||||
override suspend fun main() {
|
||||
|
||||
// 获取 Intent 并接收传递的 PlanData 对象
|
||||
// 使用 Parcelable 获取对象
|
||||
val planData = intent.getSerializableExtra("planData") as? PlanData
|
||||
|
||||
val design = ConfigOrderDesign(this)
|
||||
setContentDesign(design)
|
||||
design.fillData(planData)
|
||||
|
||||
val ticker = ticker(TimeUnit.SECONDS.toMillis(1))
|
||||
|
||||
while (isActive) {
|
||||
select<Unit> {
|
||||
events.onReceive {
|
||||
println("events : " + it.name)
|
||||
}
|
||||
design.requests.onReceive {
|
||||
println("requests : " + it.name)
|
||||
when (it) {
|
||||
ConfigOrderDesign.Request.SubmitOrder -> {
|
||||
startActivity(SubmitOrderActivity::class.intent.putExtra("trade_no",design.trade_no))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.view.Gravity
|
||||
import android.widget.Button
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.github.kr328.clash.design.BaseListActivity
|
||||
import com.github.kr328.clash.design.PreferenceManager
|
||||
import com.github.kr328.clash.design.adapter.TicketsDataAdapter
|
||||
import com.github.kr328.clash.design.util.showCustomDialog
|
||||
import com.github.kr328.clash.network.ApiClient
|
||||
import com.github.kr328.clash.network.ApiService
|
||||
import com.github.kr328.clash.network.TicketsData
|
||||
import com.github.kr328.clash.network.safeApiRequestCall
|
||||
import com.github.kr328.clash.utity.LoadingDialog
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
||||
class GongdanActivity : BaseListActivity<TicketsData>() {
|
||||
|
||||
override fun createAdapter(): RecyclerView.Adapter<*> {
|
||||
|
||||
return TicketsDataAdapter(onItemClick = { planData ->
|
||||
// 在这里处理 item 点击事件
|
||||
val intent = Intent(this, TicketDetailActivity::class.java)
|
||||
intent.putExtra("ticketid", planData.id ?: 0)
|
||||
startActivity(intent)
|
||||
}, onCloseItemClick = { planData ->
|
||||
showCustomDialog(
|
||||
title = "提示",
|
||||
message = "确定关闭当前工单吗?",
|
||||
positiveButtonText = "确定",
|
||||
negativeButtonText = "取消",
|
||||
onPositiveClick = {
|
||||
// 执行确认操作
|
||||
// startActivity(PlansActivity::class.intent)
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val apiService = ApiClient.retrofit.create(ApiService::class.java)
|
||||
val fieldMap = mutableMapOf<String, Int>()
|
||||
|
||||
fieldMap.put("id",planData.id ?: 0)
|
||||
|
||||
safeApiRequestCall {
|
||||
apiService.closeTicket(
|
||||
PreferenceManager.loginauthData,
|
||||
request = fieldMap
|
||||
)
|
||||
}.let {
|
||||
withContext(Dispatchers.Main) {
|
||||
LoadingDialog.hide()
|
||||
}
|
||||
if (it != null && it.isSuccessful) {
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(this@GongdanActivity, "关闭成功", Toast.LENGTH_SHORT).show()
|
||||
|
||||
}
|
||||
refereshData()
|
||||
|
||||
}else{
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(
|
||||
this@GongdanActivity,
|
||||
"关闭失败",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onNegativeClick = {
|
||||
// 执行取消操作
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
// Create the top-right icon
|
||||
val iconView = Button(this)
|
||||
iconView.setText("新建工单")
|
||||
iconView.layoutParams = FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT,
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT,
|
||||
Gravity.TOP or Gravity.END
|
||||
).apply {
|
||||
marginEnd = 16 // Add margin to the right
|
||||
topMargin = 16 // Add margin to the top
|
||||
}
|
||||
// Set the background tint programmatically
|
||||
/* ViewCompat.setBackgroundTintList(
|
||||
iconView,
|
||||
ContextCompat.getColorStateList(this, R.color.blue)
|
||||
)
|
||||
*/
|
||||
val colorStateList: ColorStateList = ColorStateList(
|
||||
arrayOf<IntArray>(intArrayOf(android.R.attr.state_pressed)), intArrayOf(
|
||||
R.color.blue
|
||||
)
|
||||
)
|
||||
iconView.setBackgroundTintList(colorStateList)
|
||||
iconView.setOnClickListener {
|
||||
val intent = Intent(this@GongdanActivity, NewGongdanActivity::class.java)
|
||||
//startActivity(intent)
|
||||
resultLauncher.launch(intent)
|
||||
|
||||
}
|
||||
mainBinding?.activityBarLayout?.addView(iconView)
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
private val resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
val data = result.data
|
||||
val resultData = data?.getStringExtra("resultKey")
|
||||
// Process the result
|
||||
println("registerForActivityResult : resultData : ${resultData} ")
|
||||
if (resultData == "shuaxin"){
|
||||
|
||||
refereshData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadData(page: Int, callback: (List<TicketsData>?, Throwable?) -> Unit) {
|
||||
|
||||
|
||||
// android:backgroundTint="@color/blue"
|
||||
// android:background="@drawable/rounded_button_background"
|
||||
|
||||
|
||||
|
||||
val apiService = ApiClient.retrofit.create(ApiService::class.java)
|
||||
safeApiRequestCall { apiService.getTickets(PreferenceManager.loginauthData)}.let {
|
||||
if (it != null && it.isSuccessful) {
|
||||
callback(it.body()?.data,null)
|
||||
}else{
|
||||
callback(null, Throwable("请求失败"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.content.Intent
|
||||
import com.github.kr328.clash.design.H5WebViewDesign
|
||||
import com.github.kr328.clash.design.PreferenceManager
|
||||
|
||||
|
||||
class H5WebActivity : BaseActivity<H5WebViewDesign>() {
|
||||
override suspend fun main() {
|
||||
|
||||
val design = H5WebViewDesign(this) {
|
||||
startActivity(Intent(Intent.ACTION_VIEW).setData(it))
|
||||
//Uri.parse(PreferenceManager.getConfigFromPreferences(this@ProfileActivity)?.telegramurl)
|
||||
}
|
||||
|
||||
setContentDesign(design)
|
||||
|
||||
val url = intent.getStringExtra("url")
|
||||
|
||||
if (url != null && url.length > 1){
|
||||
title = url
|
||||
design.refereshApplyFrom()
|
||||
design.breaseURL(url)
|
||||
}else{
|
||||
|
||||
PreferenceManager.getConfigFromPreferences(this@H5WebActivity)?.kefuurl?.let {
|
||||
design.breaseURL(
|
||||
it
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// while (isActive) {
|
||||
// events.receive()
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -6,14 +6,14 @@ import kotlinx.coroutines.isActive
|
||||
|
||||
class HelpActivity : BaseActivity<HelpDesign>() {
|
||||
override suspend fun main() {
|
||||
val design = HelpDesign(this) {
|
||||
startActivity(Intent(Intent.ACTION_VIEW).setData(it))
|
||||
}
|
||||
|
||||
setContentDesign(design)
|
||||
|
||||
while (isActive) {
|
||||
events.receive()
|
||||
}
|
||||
// val design = HelpDesign(this) {
|
||||
// startActivity(Intent(Intent.ACTION_VIEW).setData(it))
|
||||
// }
|
||||
//
|
||||
// setContentDesign(design)
|
||||
//
|
||||
// while (isActive) {
|
||||
// events.receive()
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import com.github.kr328.clash.common.util.intent
|
||||
import com.github.kr328.clash.common.util.ticker
|
||||
import com.github.kr328.clash.design.InvitationDesign
|
||||
import com.github.kr328.clash.design.PreferenceManager
|
||||
import com.github.kr328.clash.design.ProfileDesign
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.selects.select
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class InvitationActivity : BaseActivity<InvitationDesign>() {
|
||||
|
||||
override suspend fun main() {
|
||||
|
||||
PreferenceManager.init(this)
|
||||
|
||||
|
||||
val design = InvitationDesign(this)
|
||||
setContentDesign(design)
|
||||
design.requestdataDisible()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.InputType
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.github.kr328.clash.common.compat.isAllowForceDarkCompat
|
||||
import com.github.kr328.clash.common.compat.isLightNavigationBarCompat
|
||||
import com.github.kr328.clash.common.compat.isLightStatusBarsCompat
|
||||
import com.github.kr328.clash.common.compat.isSystemBarsTranslucentCompat
|
||||
import com.github.kr328.clash.common.util.intent
|
||||
import com.github.kr328.clash.design.PreferenceManager
|
||||
import com.github.kr328.clash.design.databinding.ActivityLoginBinding
|
||||
import com.github.kr328.clash.design.databinding.ActivityProfileBinding
|
||||
import com.github.kr328.clash.design.ui.DayNight
|
||||
import com.github.kr328.clash.design.ui.ToastDuration
|
||||
import com.github.kr328.clash.design.util.layoutInflater
|
||||
import com.github.kr328.clash.design.util.resolveThemedBoolean
|
||||
import com.github.kr328.clash.design.util.resolveThemedColor
|
||||
import com.github.kr328.clash.design.util.root
|
||||
import com.github.kr328.clash.network.ApiClient
|
||||
import com.github.kr328.clash.network.ApiClientConfig
|
||||
import com.github.kr328.clash.network.ApiService
|
||||
import com.github.kr328.clash.network.ConfigResponse
|
||||
import com.github.kr328.clash.network.LoginRequest
|
||||
import com.github.kr328.clash.network.LoginResponse
|
||||
import com.github.kr328.clash.network.safeApiCall
|
||||
import com.github.kr328.clash.network.safeApiRequestCall
|
||||
import com.github.kr328.clash.utity.LoadingDialog
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
|
||||
class LoginActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var apiService: ApiService
|
||||
|
||||
private var dayNight: DayNight = DayNight.Day
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun onBackPressed() {
|
||||
// 禁用返回键
|
||||
// 不调用 super.onBackPressed()
|
||||
}
|
||||
|
||||
|
||||
private var isPasswordVisible = false
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val binding = ActivityLoginBinding
|
||||
.inflate(this.layoutInflater, this.root, false)
|
||||
|
||||
setContentView(binding.root)
|
||||
|
||||
applyDayNight()
|
||||
|
||||
PreferenceManager.init(this)
|
||||
|
||||
binding.togglePasswordVisibility.setOnClickListener {
|
||||
|
||||
isPasswordVisible = !isPasswordVisible
|
||||
|
||||
if (isPasswordVisible) {
|
||||
// 显示明文密码
|
||||
binding.loginPasswordEditText.inputType = InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
||||
binding.togglePasswordVisibility.setImageResource(R.drawable.visibility_24px) // 更改图标为显示状态
|
||||
} else {
|
||||
// 显示星号密码
|
||||
binding.loginPasswordEditText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||
binding.togglePasswordVisibility.setImageResource(R.drawable.visibility_off_24px) // 更改图标为隐藏状态
|
||||
}
|
||||
// 将光标移动到文本的末尾
|
||||
binding.loginPasswordEditText.setSelection(binding.loginPasswordEditText.text.length)
|
||||
|
||||
}
|
||||
binding.loginLoginButton.setOnClickListener {
|
||||
val email = binding.loginEmailEditText.text.toString()
|
||||
val password = binding.loginPasswordEditText.text.toString()
|
||||
val isAgreementChecked = binding.loginAgreementCheckBox.isChecked
|
||||
|
||||
|
||||
if (email.isEmpty() || password.isEmpty()) {
|
||||
Toast.makeText(this, "请输入邮箱和密码", Toast.LENGTH_SHORT).show()
|
||||
} else if (!isAgreementChecked) {
|
||||
Toast.makeText(this, "请同意隐私政策和用户协议", Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
|
||||
// Hide the keyboard
|
||||
val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
val currentFocusView = currentFocus
|
||||
if (currentFocusView != null) {
|
||||
inputMethodManager.hideSoftInputFromWindow(currentFocusView.windowToken, 0)
|
||||
}
|
||||
performLogin(email, password)
|
||||
}
|
||||
}
|
||||
|
||||
binding.loginForgotPassword.setOnClickListener {
|
||||
// Navigate to Forgot Password screen or show dialog
|
||||
}
|
||||
|
||||
binding.loginRegister.setOnClickListener {
|
||||
// Navigate to Register screen
|
||||
//startActivity(RegisterActivity::class::intent)
|
||||
// startActivity(RegisterActivity::class::intent)
|
||||
val intent = Intent(this@LoginActivity, RegisterActivity::class.java)
|
||||
startActivity(intent)
|
||||
// finish() // It
|
||||
|
||||
}
|
||||
|
||||
|
||||
apiService = ApiClientConfig.retrofit.create(ApiService::class.java)
|
||||
|
||||
//获取 Config 数据
|
||||
getconfig()
|
||||
}
|
||||
|
||||
private fun getconfig(){
|
||||
|
||||
LoadingDialog.show(this, "正在初始化...")
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
|
||||
safeApiCall {apiService.getConfig()}.let {
|
||||
if (it != null) {
|
||||
if (it.code == 1 ) {
|
||||
withContext(Dispatchers.Main) {
|
||||
LoadingDialog.hide()
|
||||
}
|
||||
PreferenceManager.saveConfigToPreferences( it)
|
||||
//重新初始化 ApiClint
|
||||
}
|
||||
}else{
|
||||
getconfig()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* withContext(Dispatchers.Main) {
|
||||
* if (response.isSuccessful) {
|
||||
* val userData = response.body()
|
||||
* // 更新 UI
|
||||
* } else {
|
||||
* Toast.makeText(this@MainActivity, "Failed to fetch user data", Toast.LENGTH_SHORT).show()
|
||||
* }
|
||||
* }
|
||||
* */
|
||||
|
||||
private fun applyDayNight(config: Configuration = resources.configuration) {
|
||||
// val dayNight = theme.applyStyle(R.style.AppThemeLight, true) //默认白天模式
|
||||
|
||||
/*
|
||||
val dayNight = queryDayNight(config)
|
||||
when (dayNight) {
|
||||
DayNight.Night -> theme.applyStyle(R.style.AppThemeDark, true)
|
||||
DayNight.Day -> theme.applyStyle(R.style.AppThemeLight, true)
|
||||
}*/
|
||||
|
||||
window.isAllowForceDarkCompat = false
|
||||
window.isSystemBarsTranslucentCompat = true
|
||||
|
||||
window.statusBarColor = resolveThemedColor(android.R.attr.statusBarColor)
|
||||
window.navigationBarColor = resolveThemedColor(android.R.attr.navigationBarColor)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
window.isLightStatusBarsCompat = resolveThemedBoolean(android.R.attr.windowLightStatusBar)
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 27) {
|
||||
window.isLightNavigationBarCompat = resolveThemedBoolean(android.R.attr.windowLightNavigationBar)
|
||||
}
|
||||
|
||||
this.dayNight = DayNight.Night //dayNight
|
||||
}
|
||||
|
||||
private fun performLogin(email: String, password: String) {
|
||||
// Perform login logic here, possibly calling an API
|
||||
|
||||
// Show the loading indicator with a custom message
|
||||
LoadingDialog.show(this, "正在登录...")
|
||||
|
||||
val apiServiceApp = ApiClient.retrofit.create(ApiService::class.java)
|
||||
|
||||
//获取 Config 数据
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
|
||||
safeApiRequestCall {
|
||||
apiServiceApp.loginUser(LoginRequest(email,password))}.let {
|
||||
withContext(Dispatchers.Main) {
|
||||
LoadingDialog.hide()
|
||||
}
|
||||
if (it != null &&
|
||||
it.isSuccessful) {
|
||||
val response: LoginResponse? = it.body()
|
||||
println(response )
|
||||
println(it.errorBody()?.string() ?: "")
|
||||
if (response?.data != null){
|
||||
PreferenceManager.loginemail = email
|
||||
PreferenceManager.loginToken = response.data?.token ?: ""
|
||||
PreferenceManager.loginauthData = response.data?.auth_data ?: ""
|
||||
PreferenceManager.isLoginin = true
|
||||
// saveLoginMailToken(this@LoginActivity,email)
|
||||
// saveLoginToken(this@LoginActivity, response.data.token ?: "")
|
||||
// saveLoginAuthData(this@LoginActivity, response.data.auth_data ?: "")
|
||||
// saveLoginStatus(this@LoginActivity,true)
|
||||
//获取用户信息
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
val intent = Intent(this@LoginActivity, MainActivity::class.java)
|
||||
intent.flags =
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
startActivity(intent)
|
||||
finish() // It
|
||||
}
|
||||
}
|
||||
}else{
|
||||
// Handle error response, even if it's 422
|
||||
try {
|
||||
val errorResponse = it?.errorBody()?.string() ?: ""
|
||||
val errorJson = JSONObject(errorResponse)
|
||||
val message = errorJson.optString("message")
|
||||
// Process the error message or show it to the user
|
||||
if (!message.isNullOrEmpty()) {
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(this@LoginActivity, "登录失败:${message}", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
println( "Message: $message, ")
|
||||
} catch (e: JSONException) {
|
||||
// Handle JSON parsing error
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(this@LoginActivity, "登录失败:${e.message}", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.github.kr328.clash.common.log.Log
|
||||
import com.github.kr328.clash.common.util.intent
|
||||
import com.github.kr328.clash.common.util.setUUID
|
||||
import com.github.kr328.clash.common.util.ticker
|
||||
import com.github.kr328.clash.core.Clash
|
||||
import com.github.kr328.clash.design.MainDesign
|
||||
@@ -15,27 +19,55 @@ import com.github.kr328.clash.util.withClash
|
||||
import com.github.kr328.clash.util.withProfile
|
||||
import com.github.kr328.clash.core.bridge.*
|
||||
import com.github.kr328.clash.core.model.FetchStatus
|
||||
import com.github.kr328.clash.core.model.ProxySort
|
||||
import com.github.kr328.clash.core.model.TunnelState
|
||||
import com.github.kr328.clash.design.NewProfileDesign
|
||||
import com.github.kr328.clash.design.PreferenceLiveData
|
||||
import com.github.kr328.clash.design.PreferenceManager
|
||||
import com.github.kr328.clash.design.R
|
||||
import com.github.kr328.clash.design.component.ProxyMenu
|
||||
import com.github.kr328.clash.design.databinding.ActivitySplashBinding
|
||||
import com.github.kr328.clash.design.dialog.requestModelTextInput
|
||||
import com.github.kr328.clash.design.dialog.withModelProgressBar
|
||||
import com.github.kr328.clash.design.network.APIGlobalObject
|
||||
import com.github.kr328.clash.design.util.ValidatorHttpUrl
|
||||
import com.github.kr328.clash.design.util.root
|
||||
import com.github.kr328.clash.design.util.showCustomDialog
|
||||
import com.github.kr328.clash.design.util.showExceptionToast
|
||||
import com.github.kr328.clash.network.ApiClient
|
||||
import com.github.kr328.clash.network.ApiClientConfig
|
||||
import com.github.kr328.clash.network.ApiService
|
||||
import com.github.kr328.clash.network.ConfigResponse
|
||||
import com.github.kr328.clash.network.LoginRequest
|
||||
import com.github.kr328.clash.network.SubscribeData
|
||||
import com.github.kr328.clash.network.SubscribeResponse
|
||||
import com.github.kr328.clash.network.safeApiCall
|
||||
import com.github.kr328.clash.network.safeApiRequestCall
|
||||
import com.github.kr328.clash.service.ProfileManager
|
||||
import com.github.kr328.clash.service.model.Profile
|
||||
import com.github.kr328.clash.service.remote.IRemoteService
|
||||
import com.github.kr328.clash.service.util.PreferenceManagerServer
|
||||
import com.github.kr328.clash.utity.LoadingDialog
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.selects.select
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.concurrent.timer
|
||||
|
||||
class MainActivity : BaseActivity<MainDesign>() {
|
||||
|
||||
|
||||
private lateinit var apiService: ApiService
|
||||
private var subData: SubscribeData? = null
|
||||
|
||||
|
||||
|
||||
private val selectnodeName: LiveData<String> by lazy {
|
||||
PreferenceLiveData(this)
|
||||
@@ -44,100 +76,363 @@ class MainActivity : BaseActivity<MainDesign>() {
|
||||
override suspend fun main() {
|
||||
val design = MainDesign(this)
|
||||
|
||||
PreferenceManager.init(this)
|
||||
if (PreferenceManager.isLoginin){
|
||||
setContentDesign(design)
|
||||
|
||||
setContentDesign(design)
|
||||
design.fetch()
|
||||
design.startBannsers()
|
||||
|
||||
design.fetch()
|
||||
design.startBannsers()
|
||||
|
||||
selectnodeName.observe(this, androidx.lifecycle.Observer { newValue ->
|
||||
})
|
||||
//1 : 查询 config 信息
|
||||
|
||||
val ticker = ticker(TimeUnit.SECONDS.toMillis(1))
|
||||
apiService = ApiClientConfig.retrofit.create(ApiService::class.java)
|
||||
|
||||
//获取 Config 数据
|
||||
// CoroutineScope(Dispatchers.IO).launch { }
|
||||
|
||||
|
||||
|
||||
val currentTime = System.currentTimeMillis()
|
||||
val cacheExpiryTime = 10 * 60 * 1000 // 10 minutes in milliseconds
|
||||
|
||||
|
||||
// 如果缓存数据存在且未超过 10 分钟,使用缓存数据;否则,重新请求数据
|
||||
if ( PreferenceManager.cached_data != null && (currentTime - PreferenceManager.cache_timestamp) <= cacheExpiryTime) {
|
||||
// 使用缓存数据
|
||||
val gson = Gson()
|
||||
val configResponse = gson.fromJson(PreferenceManager.cached_data, ConfigResponse::class.java)
|
||||
println(configResponse)
|
||||
println("Using cached data: ${PreferenceManager.cached_data}")
|
||||
println("Using cached_userSubscritedata : ${PreferenceManager.cached_userSubscritedata}")
|
||||
if (PreferenceManager.cached_userSubscritedata.length > 0){
|
||||
val subcache = gson.fromJson(PreferenceManager.cached_userSubscritedata, SubscribeResponse::class.java)
|
||||
subData = subcache.data
|
||||
|
||||
//存储到本地
|
||||
val plainid = subcache.data.plan_id ?: 0
|
||||
// 或者检查使用量
|
||||
val guoqi = (subcache.data.u ) + (subcache.data.d) > (subcache.data.transfer_enable ?: 0) && (subcache.data.transfer_enable ?: 0) > 0
|
||||
if ( plainid == 0 || guoqi ) {
|
||||
//提示到期
|
||||
//删除所有节点
|
||||
withProfile {
|
||||
var allProfiles = queryAll()
|
||||
println(allProfiles)
|
||||
//删除所有节点
|
||||
if (allProfiles.isNotEmpty() && allProfiles.count() > 0){
|
||||
allProfiles.forEach {
|
||||
delete(it.uuid)
|
||||
println("Deleted ${it.uuid} ${it.name}: ${it.source}")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//删除所有节点文件
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
showCustomDialog(
|
||||
title = "提示",
|
||||
message = "您的订阅已到期,请续费订阅开启服务",
|
||||
positiveButtonText = "确定",
|
||||
negativeButtonText = "取消",
|
||||
onPositiveClick = {
|
||||
// 执行确认操作
|
||||
startActivity(PlansActivity::class.intent)
|
||||
},
|
||||
onNegativeClick = {
|
||||
// 执行取消操作
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
while (isActive) {
|
||||
select<Unit> {
|
||||
events.onReceive {
|
||||
when (it) {
|
||||
Event.ActivityStart,
|
||||
Event.ServiceRecreated,
|
||||
Event.ClashStop, Event.ClashStart,
|
||||
Event.ProfileLoaded, Event.ProfileChanged -> design.fetch()
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
}
|
||||
design.requests.onReceive {
|
||||
when (it) {
|
||||
MainDesign.Request.ToggleStatus -> {
|
||||
if (clashRunning)
|
||||
stopClashService()
|
||||
else
|
||||
design.startClash()
|
||||
}
|
||||
MainDesign.Request.OpenProxy ->
|
||||
startActivity(ProxyActivity::class.intent)
|
||||
MainDesign.Request.OpenProfiles ->
|
||||
startActivity(ProfilesActivity::class.intent)
|
||||
MainDesign.Request.OpenProviders ->
|
||||
startActivity(ProvidersActivity::class.intent)
|
||||
MainDesign.Request.OpenLogs ->
|
||||
startActivity(LogsActivity::class.intent)
|
||||
MainDesign.Request.OpenSettings ->
|
||||
startActivity(SettingsActivity::class.intent)
|
||||
MainDesign.Request.OpenHelp ->
|
||||
startActivity(HelpActivity::class.intent)
|
||||
MainDesign.Request.OpenAbout ->
|
||||
design.showAbout(queryAppVersionName())
|
||||
MainDesign.Request.OpenSettingsDIY ->
|
||||
startActivity(NetworkSettingsActivity::class.intent)
|
||||
MainDesign.Request.OpenSettingsKEFU ->
|
||||
startActivity(HelpActivity::class.intent)
|
||||
|
||||
MainDesign.Request.OpenModeDirect ->
|
||||
withClash {
|
||||
val o = queryOverride(Clash.OverrideSlot.Session)
|
||||
o.mode = TunnelState.Mode.Direct
|
||||
patchOverride(Clash.OverrideSlot.Session, o)
|
||||
println("subData : ${subData}")
|
||||
|
||||
|
||||
|
||||
}else{
|
||||
// 缓存已过期,重新请求数据
|
||||
println("Cache expired, requesting new data")
|
||||
// 重新请求数据
|
||||
//requestNewData()
|
||||
LoadingDialog.show(this, "正在更新节点数据...")
|
||||
configRequesting()
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
selectnodeName.observe(this, androidx.lifecycle.Observer { newValue ->
|
||||
})
|
||||
|
||||
val ticker = ticker(TimeUnit.SECONDS.toMillis(1))
|
||||
|
||||
while (isActive) {
|
||||
select<Unit> {
|
||||
events.onReceive {
|
||||
when (it) {
|
||||
Event.ActivityStart,
|
||||
Event.ServiceRecreated,
|
||||
Event.ClashStop, Event.ClashStart,
|
||||
Event.ProfileLoaded, Event.ProfileChanged -> design.fetch()
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
design.requests.onReceive {
|
||||
when (it) {
|
||||
MainDesign.Request.ToggleStatus -> {
|
||||
if (clashRunning)
|
||||
stopClashService()
|
||||
else
|
||||
design.startClash()
|
||||
}
|
||||
MainDesign.Request.OpenProxy ->
|
||||
startActivity(ProxyActivity::class.intent)
|
||||
|
||||
MainDesign.Request.OpenProfiles ->
|
||||
startActivity(ProfilesActivity::class.intent)
|
||||
MainDesign.Request.OpenProviders ->
|
||||
startActivity(ProvidersActivity::class.intent)
|
||||
MainDesign.Request.OpenLogs ->
|
||||
startActivity(LogsActivity::class.intent)
|
||||
MainDesign.Request.OpenSettings ->
|
||||
startActivity(SettingsActivity::class.intent)
|
||||
MainDesign.Request.OpenHelp ->
|
||||
startActivity(HelpActivity::class.intent)
|
||||
MainDesign.Request.OpenAbout ->
|
||||
design.showAbout(queryAppVersionName())
|
||||
MainDesign.Request.OpenSettingsDIY ->
|
||||
startActivity(ProfileActivity::class.intent)
|
||||
MainDesign.Request.OpenSettingsKEFU ->
|
||||
startActivity(PlansActivity::class.intent)
|
||||
|
||||
MainDesign.Request.OpenModeDirect ->
|
||||
|
||||
withClash {
|
||||
PreferenceManager.modeName = TunnelState.Mode.Direct.name
|
||||
val override = queryOverride(Clash.OverrideSlot.Session)
|
||||
override.mode = TunnelState.Mode.Direct
|
||||
patchOverride(Clash.OverrideSlot.Session, override)
|
||||
|
||||
}
|
||||
|
||||
MainDesign.Request.OpenModeGlobal -> withClash {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
PreferenceManager.modeName = TunnelState.Mode.Global.name
|
||||
LoadingDialog.show(this@MainActivity, "正在切换中...")
|
||||
|
||||
val names = withClash { queryProxyGroupNames(uiStore.proxyExcludeNotSelectable) }
|
||||
|
||||
withClash {
|
||||
val o = queryOverride(Clash.OverrideSlot.Session)
|
||||
o.mode = TunnelState.Mode.Global
|
||||
patchOverride(Clash.OverrideSlot.Session, o)
|
||||
|
||||
|
||||
}
|
||||
startServer()
|
||||
|
||||
}
|
||||
}
|
||||
MainDesign.Request.OpenModeRule -> withClash {
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
PreferenceManager.modeName = TunnelState.Mode.Rule.name
|
||||
LoadingDialog.show(this@MainActivity, "正在切换中...")
|
||||
withClash {
|
||||
val o = queryOverride(Clash.OverrideSlot.Session)
|
||||
o.mode = TunnelState.Mode.Rule
|
||||
patchOverride(Clash.OverrideSlot.Session, o)
|
||||
|
||||
}
|
||||
startServer()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
MainDesign.Request.OpenModeGlobal -> withClash {
|
||||
withClash {
|
||||
val o = queryOverride(Clash.OverrideSlot.Session)
|
||||
o.mode = TunnelState.Mode.Global
|
||||
patchOverride(Clash.OverrideSlot.Session, o)
|
||||
|
||||
MainDesign.Request.OpenModeMenu -> {
|
||||
design.OpenModeMenu()
|
||||
}
|
||||
}
|
||||
MainDesign.Request.OpenModeRule -> withClash {
|
||||
withClash {
|
||||
val o = queryOverride(Clash.OverrideSlot.Session)
|
||||
o.mode = TunnelState.Mode.Direct
|
||||
patchOverride(Clash.OverrideSlot.Session, o)
|
||||
|
||||
MainDesign.Request.OpenChangeMode -> {
|
||||
design.OpenModeMenu()
|
||||
}
|
||||
|
||||
MainDesign.Request.OpenModeSheet ->{
|
||||
design.OpenModeSheet()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (clashRunning) {
|
||||
ticker.onReceive {
|
||||
design.fetchTraffic()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (clashRunning) {
|
||||
ticker.onReceive {
|
||||
design.fetchTraffic()
|
||||
}
|
||||
|
||||
|
||||
|
||||
}else{
|
||||
val binding = ActivitySplashBinding
|
||||
.inflate(this.layoutInflater, this.root, false)
|
||||
|
||||
setContentView(binding.root)
|
||||
|
||||
Handler(Looper.getMainLooper()).postDelayed({
|
||||
startActivity(LoginActivity::class.intent)
|
||||
|
||||
}, 1000) // Simulating a network delay
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private suspend fun configRequesting(){
|
||||
val gson = Gson()
|
||||
val currentTime = System.currentTimeMillis()
|
||||
/*apiService.getConfig().let {
|
||||
if (it.isSuccessful) {
|
||||
val response: ConfigResponse = it.body() ?: throw Exception("Response body is null")
|
||||
|
||||
if (response.code == 1 ) {
|
||||
PreferenceManager.saveConfigToPreferences(response)
|
||||
//更新缓存数据
|
||||
PreferenceManager.cache_timestamp = currentTime
|
||||
PreferenceManager.cached_data = gson.toJson( it.body())
|
||||
println("API Response: ${gson.toJson( it.body())}")
|
||||
//重新初始化 ApiClint
|
||||
//2 : 获取订阅节点信息
|
||||
performSubscrite()
|
||||
}
|
||||
|
||||
} else {
|
||||
println("API Error: ${it.errorBody()}")
|
||||
configRequesting()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
safeApiCall {apiService.getConfig()}.let {
|
||||
if (it !=null){
|
||||
if (it.code == 1 ) {
|
||||
PreferenceManager.saveConfigToPreferences(it)
|
||||
//更新缓存数据
|
||||
PreferenceManager.cache_timestamp = currentTime
|
||||
PreferenceManager.cached_data = gson.toJson( it)
|
||||
println("API Response: ${it}")
|
||||
//重新初始化 ApiClint
|
||||
//2 : 获取订阅节点信息
|
||||
performSubscrite()
|
||||
}
|
||||
}else{
|
||||
configRequesting()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private fun performSubscrite(){
|
||||
|
||||
|
||||
val apiServiceApp = ApiClient.retrofit.create(ApiService::class.java)
|
||||
|
||||
//获取 Config 数据
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
safeApiRequestCall {
|
||||
apiServiceApp.getSubscribe(PreferenceManager.loginauthData)}.let {
|
||||
withContext(Dispatchers.Main) {
|
||||
LoadingDialog.hide()
|
||||
}
|
||||
if (it != null &&
|
||||
it.isSuccessful) {
|
||||
println(" performSubscrite : ${it.body()?.data}")
|
||||
subData = it.body()?.data
|
||||
|
||||
val gson = Gson()
|
||||
PreferenceManager.cached_userSubscritedata = gson.toJson( it.body())
|
||||
APIGlobalObject.subData = it.body()?.data
|
||||
//存储到本地
|
||||
val plainid = it.body()?.data?.plan_id ?: 0
|
||||
// 或者检查使用量
|
||||
val guoqi = (it.body()?.data?.u ?: 0) + (it.body()?.data?.d ?: 0) > (it.body()?.data?.transfer_enable ?: 0) && (it.body()?.data?.transfer_enable ?: 0) > 0
|
||||
if ( plainid == 0 || guoqi ) {
|
||||
//提示到期
|
||||
//删除所有节点
|
||||
withProfile {
|
||||
var allProfiles = queryAll()
|
||||
println(allProfiles)
|
||||
if (allProfiles.isNotEmpty() && allProfiles.count() > 1){
|
||||
allProfiles.forEach { delete(it.uuid) }
|
||||
//删除所有节点
|
||||
}
|
||||
}
|
||||
|
||||
//删除所有节点文件
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
showCustomDialog(
|
||||
title = "提示",
|
||||
message = "您的订阅已到期,请续费订阅开启服务",
|
||||
positiveButtonText = "确定",
|
||||
negativeButtonText = "取消",
|
||||
onPositiveClick = {
|
||||
// 执行确认操作
|
||||
startActivity(PlansActivity::class.intent)
|
||||
},
|
||||
onNegativeClick = {
|
||||
// 执行取消操作
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
}else{
|
||||
//更新本地节点
|
||||
withProfile {
|
||||
var profileActive = queryActive()
|
||||
if (profileActive != null){
|
||||
try {
|
||||
println(">>>>>>>>> 更新节点订阅地址:${profileActive.uuid} ${profileActive.source}")
|
||||
update(profileActive.uuid)
|
||||
}catch (e: Exception){
|
||||
println(">>>>>>>>> 更新节点订阅地址 catch:${e.message}")
|
||||
}
|
||||
finally {
|
||||
println(">>>>>>>>> 更新节点订阅地址 完成 finally")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private suspend fun MainDesign.fetch() {
|
||||
setClashRunning(clashRunning)
|
||||
|
||||
val state = withClash {
|
||||
queryTunnelState()
|
||||
}
|
||||
|
||||
println("运行时查询系统mode模式: ${state}")
|
||||
|
||||
val providers = withClash {
|
||||
queryProviders()
|
||||
}
|
||||
android.util.Log.i("MainActivity","fetch Info: ${state.mode.name} ${providers.count()}")
|
||||
|
||||
if (providers.isNotEmpty()) {
|
||||
providers.forEach { android.util.Log.i("MainActivity", "The element is ${it.name}") }
|
||||
println("模式providers: ${providers}")
|
||||
providers.forEach { println("The element is ${it.name}") }
|
||||
}
|
||||
|
||||
setMode(state.mode)
|
||||
@@ -145,24 +440,27 @@ class MainActivity : BaseActivity<MainDesign>() {
|
||||
|
||||
withProfile {
|
||||
setProfileName(queryActive()?.name)
|
||||
|
||||
setSelectNodeName(PreferenceManager.selectnodeName)
|
||||
|
||||
setSelectNodeName(PreferenceManager.selectnodeName )
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//查询上传速度和下载速度
|
||||
private suspend fun MainDesign.fetchTraffic() {
|
||||
withClash {
|
||||
setForwarded(queryTrafficTotal())
|
||||
|
||||
// setForwarded(queryTrafficTotal())
|
||||
|
||||
setUploadedSeep(queryTrafficTotal())
|
||||
setDownloadedSeep(queryTrafficTotal())
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun MainDesign.startClash() {
|
||||
|
||||
//删除所有节点
|
||||
withProfile {
|
||||
var allProfiles = queryAll()
|
||||
if (allProfiles.isNotEmpty() && allProfiles.count() > 1){
|
||||
if (allProfiles.isNotEmpty() && allProfiles.count() > 1){ //删除超过多余的 1 个节点之外的节点
|
||||
allProfiles.forEach { delete(it.uuid) }
|
||||
//删除所有节点
|
||||
}
|
||||
@@ -180,26 +478,123 @@ class MainActivity : BaseActivity<MainDesign>() {
|
||||
|
||||
// }
|
||||
val activeProfile = withProfile { queryActive() }
|
||||
println("当前订阅文件:${activeProfile}")
|
||||
if (activeProfile == null ) { //|| !activeProfile.imported
|
||||
|
||||
if (activeProfile == null || !activeProfile.imported) {
|
||||
|
||||
showToast("正在请求节点数据....", ToastDuration.Long) {
|
||||
|
||||
}
|
||||
LoadingDialog.show(this@MainActivity, "正在请求节点数据...")
|
||||
|
||||
// showToast("正在请求节点数据....", ToastDuration.Long)
|
||||
|
||||
//create(Profile.Type.Url, "VPN")
|
||||
withProfile{
|
||||
val uuid: UUID = create(Profile.Type.Url,"VPN","https://api.0009.uk/api/v1/client/subscribe?token=5652e3eec20c553047dbe3fcf8754090")
|
||||
commit(uuid){
|
||||
android.util.Log.i("commit",">> ${it.progress} ${it.action} ${it.args.toString()} ")
|
||||
if (it.action == FetchStatus.Action.Verifying){
|
||||
// 在协程中启动 activeProfile 调用
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
activeProfile()
|
||||
println(">>>>>>>>>${subData}")
|
||||
//https://hongxingdl.com/hxvip?token=bc2f2839ccc608f34d6a6fc90b6e7f15
|
||||
//https://hongxingdl.com/api/v1/client/subscribe?token=bc2f2839ccc608f34d6a6fc90b6e7f15
|
||||
//"https://api.0009.uk/api/v1/client/subscribe?token=5652e3eec20c553047dbe3fcf8754090"
|
||||
if (subData?.subscribe_url != null && subData?.plan?.name != null && subData?.plan?.id != null) {
|
||||
var suburl = subData?.subscribe_url ?: ""
|
||||
var encodeURL = java.net.URLEncoder.encode(suburl, "utf-8")
|
||||
encodeURL = encodeURL.replace("?","%3F")
|
||||
var newULR =suburl
|
||||
|
||||
println(">>>>>>>>> "+suburl)
|
||||
if( ApiClientConfig.ConfigNodeURL.length > 3){
|
||||
newULR = ApiClientConfig.ConfigNodeURL + encodeURL
|
||||
}
|
||||
|
||||
println(">>>>>>>>> "+newULR)
|
||||
|
||||
//val uuid: UUID = create(Profile.Type.Url,subData?.plan?.name ?: "VPN",newULR)
|
||||
|
||||
launch {
|
||||
val uuid2 = withProfile {
|
||||
val type = Profile.Type.Url
|
||||
val name = subData?.plan?.name ?: "VPN"
|
||||
|
||||
create(type, name).also {
|
||||
patch(it, name, newULR, 0)
|
||||
}
|
||||
}
|
||||
|
||||
withProcessing { updateStatus ->
|
||||
withProfile {
|
||||
|
||||
try {
|
||||
commit(uuid2) {
|
||||
|
||||
launch {
|
||||
activeProfile()
|
||||
updateStatus(it)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LoadingDialog.hide()
|
||||
showExceptionToast("初始化节点数据失败,请检查网络重试")
|
||||
|
||||
}
|
||||
coroutineScope {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* coroutineScope {
|
||||
commit(uuid2) {
|
||||
println(">>>>>>>>>> ${it.progress} ${it.action} ${it.args.toString()} ${it} ")
|
||||
if (it.action == FetchStatus.Action.Verifying){
|
||||
// 在协程中启动 activeProfile 调用
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
activeProfile()
|
||||
//自动开启订阅
|
||||
startServer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* commit(uuid){
|
||||
println(">> ${it.progress} ${it.action} ${it.args.toString()} ")
|
||||
if (it.action == FetchStatus.Action.Verifying){
|
||||
// 在协程中启动 activeProfile 调用
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
activeProfile()
|
||||
//自动开启订阅
|
||||
startServer()
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}else{
|
||||
//重新请求网络
|
||||
performSubscrite()
|
||||
}
|
||||
|
||||
/*
|
||||
if (subData == null){
|
||||
withContext(Dispatchers.Main){
|
||||
LoadingDialog.hide()
|
||||
}
|
||||
performSubscrite()
|
||||
}else{
|
||||
|
||||
val plainid =subData?.plan_id ?: 0
|
||||
// 或者检查使用量
|
||||
val guoqi = (subData?.u ?: 0) + (subData?.d ?: 0) > (subData?.transfer_enable ?: 0) && (subData?.transfer_enable ?: 0) > 0
|
||||
if ( plainid == 0 || guoqi ) {
|
||||
//提示到期
|
||||
performSubscrite()
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}else{
|
||||
@@ -209,9 +604,46 @@ class MainActivity : BaseActivity<MainDesign>() {
|
||||
// }
|
||||
// }
|
||||
|
||||
android.util.Log.i("activeProfile",">> ${activeProfile.type.name} ${activeProfile.uuid} ${activeProfile.imported} ")
|
||||
println("当前订阅文件>> ${activeProfile.type.name} ${activeProfile.uuid} ${activeProfile.imported} ")
|
||||
withProfile {
|
||||
activeProfile.uuid
|
||||
}
|
||||
startServer()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
suspend fun withProcessing(executeTask: suspend (suspend (FetchStatus) -> Unit) -> Unit) {
|
||||
try {
|
||||
|
||||
|
||||
executeTask {
|
||||
// applyFrom(it)
|
||||
println("applyFrom ${it.progress} ${it}")
|
||||
if (it.action == FetchStatus.Action.Verifying) {
|
||||
//验证通过
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
//选中节点
|
||||
activeProfile()
|
||||
//自动开启订阅
|
||||
startServer()
|
||||
}
|
||||
}else{
|
||||
//请求中
|
||||
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun startServer(){
|
||||
|
||||
//读取配置文件
|
||||
|
||||
|
||||
val vpnRequest = startClashService()
|
||||
|
||||
@@ -223,13 +655,104 @@ class MainActivity : BaseActivity<MainDesign>() {
|
||||
)
|
||||
|
||||
if (result.resultCode == RESULT_OK)
|
||||
{
|
||||
startClashService()
|
||||
withContext(Dispatchers.Main){
|
||||
LoadingDialog.hide()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
println("缓存配置模式:${PreferenceManager.modeName}")
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
// Delay for 3 seconds
|
||||
//第一次全部使用全局模式连接
|
||||
|
||||
delay(500)
|
||||
withClash {
|
||||
// Query the current mode
|
||||
val override = queryOverride(Clash.OverrideSlot.Session)
|
||||
|
||||
// Toggle between Rule and Global modes
|
||||
if (PreferenceManager.modeName == TunnelState.Mode.Global.name){
|
||||
override.mode = TunnelState.Mode.Global
|
||||
}else{
|
||||
override.mode = TunnelState.Mode.Rule //第一次默认使用智能 模式连接
|
||||
}
|
||||
// Apply the new mode`
|
||||
patchOverride(Clash.OverrideSlot.Session, override)
|
||||
//模式切换完毕
|
||||
// Optional: Log or display the current mode for verification
|
||||
println("模式切换 Clash mode is now set to: ${override.mode}")
|
||||
|
||||
delay(500)
|
||||
//如果切换全局模式,默认 直接主动选择节点 根据缓存来
|
||||
//if (override.mode == TunnelState.Mode.Global){
|
||||
val names = queryProxyGroupNames(uiStore.proxyExcludeNotSelectable)
|
||||
println("模式切换>>>>>>>>>>>>>"+names)
|
||||
if ( names.size > 0){
|
||||
var progxys = queryProxyGroup(names[0], ProxySort.Default)
|
||||
|
||||
if (progxys.proxies.first().title.length > 0){
|
||||
|
||||
|
||||
println("模式切换>>>>>>>>>>>>>: "+progxys.proxies.last())
|
||||
if ( progxys.proxies.size > 2)
|
||||
{
|
||||
|
||||
val zutuolist = progxys.proxies.filter { it.title == "自动选择" }
|
||||
var autoNode = ""
|
||||
if (PreferenceManager.selectnodeName.length > 0){
|
||||
autoNode = PreferenceManager.selectnodeName
|
||||
|
||||
} else{
|
||||
if (zutuolist.size > 0){
|
||||
//说明存在自动选择
|
||||
autoNode = "自动选择"
|
||||
}else{
|
||||
autoNode = progxys.proxies.first().title
|
||||
}
|
||||
PreferenceManager.selectnodeName = autoNode // progxys.proxies.last().title
|
||||
}
|
||||
|
||||
|
||||
patchSelector(names.first(),autoNode) // progxys.proxies.last().title
|
||||
|
||||
withContext(Dispatchers.Main){
|
||||
design?.setSelectNodeName(PreferenceManager.selectnodeName)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// }
|
||||
|
||||
|
||||
withContext(Dispatchers.Main){
|
||||
LoadingDialog.hide()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
} catch (e: Exception) {
|
||||
design?.showToast(R.string.unable_to_start_vpn, ToastDuration.Long)
|
||||
withContext(Dispatchers.Main){
|
||||
LoadingDialog.hide()
|
||||
}
|
||||
//design?.showToast(R.string.unable_to_start_vpn, ToastDuration.Long)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private suspend fun activeProfile(){
|
||||
withProfile {
|
||||
setActive(queryAll().first())
|
||||
@@ -238,7 +761,7 @@ class MainActivity : BaseActivity<MainDesign>() {
|
||||
}
|
||||
private suspend fun queryAppVersionName(): String {
|
||||
return withContext(Dispatchers.IO) {
|
||||
packageManager.getPackageInfo(packageName, 0).versionName + "\n" + Bridge.nativeCoreVersion().replace("_", "-")
|
||||
packageManager.getPackageInfo(packageName, 0).versionName// + "\n" + Bridge.nativeCoreVersion().replace("_", "-")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ import android.content.Context
|
||||
import com.github.kr328.clash.common.Global
|
||||
import com.github.kr328.clash.common.compat.currentProcessName
|
||||
import com.github.kr328.clash.common.log.Log
|
||||
import com.github.kr328.clash.design.PreferenceManager
|
||||
import com.github.kr328.clash.remote.Remote
|
||||
import com.github.kr328.clash.service.util.PreferenceManagerServer
|
||||
import com.github.kr328.clash.service.util.sendServiceRecreated
|
||||
import com.github.kr328.clash.util.clashDir
|
||||
import java.io.File
|
||||
@@ -20,7 +22,11 @@ class MainApplication : Application() {
|
||||
override fun attachBaseContext(base: Context?) {
|
||||
super.attachBaseContext(base)
|
||||
|
||||
PreferenceManagerServer.init(this)
|
||||
PreferenceManager.init(this)
|
||||
|
||||
Global.init(this)
|
||||
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.github.kr328.clash.common.util.intent
|
||||
import com.github.kr328.clash.design.BaseListActivity
|
||||
import com.github.kr328.clash.design.PreferenceManager
|
||||
import com.github.kr328.clash.design.adapter.OrdersDataAdapter
|
||||
import com.github.kr328.clash.design.adapter.PlanDataAdapter
|
||||
import com.github.kr328.clash.network.ApiClient
|
||||
import com.github.kr328.clash.network.ApiService
|
||||
import com.github.kr328.clash.network.OrderData
|
||||
import com.github.kr328.clash.network.PlanData
|
||||
import com.github.kr328.clash.network.safeApiRequestCall
|
||||
|
||||
|
||||
class MyOrdersActivity : BaseListActivity<OrderData>() {
|
||||
|
||||
override fun createAdapter(): RecyclerView.Adapter<*> {
|
||||
return OrdersDataAdapter(){ item ->
|
||||
|
||||
startActivity(SubmitOrderActivity::class.intent.putExtra("trade_no",item.trade_no))
|
||||
}
|
||||
}
|
||||
override fun onCreate() {}
|
||||
override suspend fun loadData(page: Int, callback: (List<OrderData>?, Throwable?) -> Unit) {
|
||||
|
||||
val apiService = ApiClient.retrofit.create(ApiService::class.java)
|
||||
safeApiRequestCall { apiService.getOrders(PreferenceManager.loginauthData)}.let {
|
||||
if (it!= null && it.isSuccessful) {
|
||||
callback(it.body()?.data,null)
|
||||
}else{
|
||||
callback(null, Throwable("请求失败"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+26
-1
@@ -1,16 +1,20 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.content.pm.PackageManager
|
||||
import com.github.kr328.clash.common.util.componentName
|
||||
import com.github.kr328.clash.common.util.intent
|
||||
import com.github.kr328.clash.design.NetworkSettingsDesign
|
||||
import com.github.kr328.clash.design.model.Behavior
|
||||
import com.github.kr328.clash.service.store.ServiceStore
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.selects.select
|
||||
|
||||
class NetworkSettingsActivity : BaseActivity<NetworkSettingsDesign>() {
|
||||
class NetworkSettingsActivity : BaseActivity<NetworkSettingsDesign>(), Behavior {
|
||||
override suspend fun main() {
|
||||
val design = NetworkSettingsDesign(
|
||||
this,
|
||||
uiStore,
|
||||
this,
|
||||
ServiceStore(this),
|
||||
clashRunning,
|
||||
)
|
||||
@@ -36,4 +40,25 @@ class NetworkSettingsActivity : BaseActivity<NetworkSettingsDesign>() {
|
||||
}
|
||||
}
|
||||
|
||||
override var autoRestart: Boolean
|
||||
get() {
|
||||
val status = packageManager.getComponentEnabledSetting(
|
||||
RestartReceiver::class.componentName
|
||||
)
|
||||
|
||||
return status == PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|
||||
}
|
||||
set(value) {
|
||||
val status = if (value)
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|
||||
else
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED
|
||||
|
||||
packageManager.setComponentEnabledSetting(
|
||||
RestartReceiver::class.componentName,
|
||||
status,
|
||||
PackageManager.DONT_KILL_APP,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import com.github.kr328.clash.common.util.ticker
|
||||
import com.github.kr328.clash.design.HelpDesign
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.selects.select
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class NewGongdanActivity : BaseActivity<HelpDesign>() {
|
||||
override suspend fun main() {
|
||||
val design = HelpDesign(this) {
|
||||
startActivity(Intent(Intent.ACTION_VIEW).setData(it))
|
||||
}
|
||||
|
||||
setContentDesign(design)
|
||||
//
|
||||
// while (isActive) {
|
||||
// events.receive()
|
||||
// }
|
||||
|
||||
//val ticker = ticker(TimeUnit.SECONDS.toMillis(1))
|
||||
|
||||
while (isActive) {
|
||||
select<Unit> {
|
||||
events.onReceive {
|
||||
println("events : " + it.name)
|
||||
}
|
||||
design.requests.onReceive {
|
||||
println("requests : " + it.name)
|
||||
when (it) {
|
||||
HelpDesign.Request.CommitSuccess -> {
|
||||
withContext(Dispatchers.Main) {
|
||||
|
||||
|
||||
val resultIntent = Intent()
|
||||
resultIntent.putExtra("resultKey", "shuaxin")
|
||||
setResult(Activity.RESULT_OK, resultIntent)
|
||||
finish()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.content.Intent
|
||||
import android.widget.Toast
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.github.kr328.clash.common.util.intent
|
||||
import com.github.kr328.clash.design.BaseListActivity
|
||||
import com.github.kr328.clash.design.PreferenceManager
|
||||
import com.github.kr328.clash.design.adapter.PlanDataAdapter
|
||||
import com.github.kr328.clash.design.view.ActivityBarLayout
|
||||
|
||||
import com.github.kr328.clash.network.ApiClient
|
||||
import com.github.kr328.clash.network.ApiService
|
||||
import com.github.kr328.clash.network.PlanData
|
||||
import com.github.kr328.clash.network.safeApiRequestCall
|
||||
|
||||
class PlansActivity : BaseListActivity<PlanData>() {
|
||||
|
||||
override fun createAdapter(): RecyclerView.Adapter<*> {
|
||||
return PlanDataAdapter(){ planData ->
|
||||
// 在这里处理 item 点击事件
|
||||
val intent = Intent(this, ConfigOrderActivity::class.java)
|
||||
intent.putExtra("planData", planData) // 使用 Serializable 传递对象
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
//
|
||||
setRightButtonVisible {
|
||||
startActivity(MyOrdersActivity::class.intent)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override suspend fun loadData(page: Int, callback: (List<PlanData>?, Throwable?) -> Unit) {
|
||||
|
||||
val apiService = ApiClient.retrofit.create(ApiService::class.java)
|
||||
safeApiRequestCall { apiService.getplans(PreferenceManager.loginauthData)}.let {
|
||||
if (it != null && it.isSuccessful) {
|
||||
callback(it.body()?.data,null)
|
||||
}else{
|
||||
callback(null, Throwable("请求失败"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package com.github.kr328.clash
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.PersistableBundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.github.kr328.clash.common.util.intent
|
||||
import com.github.kr328.clash.common.util.ticker
|
||||
import com.github.kr328.clash.core.bridge.Bridge
|
||||
import com.github.kr328.clash.design.MainDesign
|
||||
import com.github.kr328.clash.design.PreferenceManager
|
||||
import com.github.kr328.clash.design.ProfileDesign
|
||||
import com.github.kr328.clash.design.ui.ToastDuration
|
||||
import com.github.kr328.clash.design.util.showCustomDialog
|
||||
import com.github.kr328.clash.util.stopClashService
|
||||
import com.github.kr328.clash.util.withProfile
|
||||
import com.github.kr328.clash.utity.LoadingDialog
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.selects.select
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
class ProfileActivity : BaseActivity<ProfileDesign>() {
|
||||
|
||||
private suspend fun queryAppVersionName(): String {
|
||||
return withContext(Dispatchers.IO) {
|
||||
packageManager.getPackageInfo(packageName, 0).versionName //+ "\n" + Bridge.nativeCoreVersion().replace("_", "-")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun main() {
|
||||
|
||||
PreferenceManager.init(this)
|
||||
|
||||
val design = ProfileDesign(this)
|
||||
setContentDesign(design)
|
||||
|
||||
design.requestdataDisible()
|
||||
|
||||
val ticker = ticker(TimeUnit.SECONDS.toMillis(1))
|
||||
design.queryVersion(queryAppVersionName())
|
||||
while (isActive) {
|
||||
select<Unit> {
|
||||
events.onReceive {
|
||||
println("events : " + it.name)
|
||||
}
|
||||
design.requests.onReceive {
|
||||
println("requests : " + it.name)
|
||||
when (it) {
|
||||
|
||||
ProfileDesign.Request.OpenBuyPlan -> startActivity(PlansActivity::class.intent)
|
||||
ProfileDesign.Request.OpenMyOrders -> startActivity(MyOrdersActivity::class.intent)
|
||||
ProfileDesign.Request.OpenMyBlance -> startActivity(SubmitOrderActivity::class.intent)
|
||||
ProfileDesign.Request.OpenMyInvites ->startActivity(InvitationActivity::class.intent)
|
||||
ProfileDesign.Request.OpenCustomView ->{ startActivity(H5WebActivity::class.intent) }
|
||||
/*try {
|
||||
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.addCategory(Intent.CATEGORY_BROWSABLE);
|
||||
intent.setData(Uri.parse(PreferenceManager.getConfigFromPreferences(this@ProfileActivity)?.telegramurl))
|
||||
startActivity(intent)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
design.showToast("当前手机未安装浏览器",ToastDuration.Long)
|
||||
|
||||
} */
|
||||
|
||||
ProfileDesign.Request.OpenLogout -> {
|
||||
showCustomDialog(
|
||||
title = "提示",
|
||||
message = "确定要退出吗?",
|
||||
positiveButtonText = "确定",
|
||||
negativeButtonText = "取消",
|
||||
onPositiveClick = {
|
||||
LoadingDialog.show(this@ProfileActivity, "正在退出...")
|
||||
// 执行确认操作
|
||||
stopClashService()
|
||||
// 清理缓存,清除节点信息,切换界面
|
||||
launch {
|
||||
withProfile {
|
||||
var allProfiles = queryAll()
|
||||
println(allProfiles)
|
||||
if (allProfiles.isNotEmpty() && allProfiles.count() > 0) {
|
||||
allProfiles.forEach {
|
||||
delete(it.uuid)
|
||||
println("删除节点:${it.source}")
|
||||
}
|
||||
//删除所有节点
|
||||
}
|
||||
}
|
||||
}
|
||||
PreferenceManager.clearData()
|
||||
|
||||
//2s 后执行
|
||||
// 在协程作用域中执行
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
delay(2000) // 协程延迟2000毫秒(2秒)
|
||||
println("协程中的延迟执行")
|
||||
LoadingDialog.hide()
|
||||
startActivity(LoginActivity::class.intent)
|
||||
finish()
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
onNegativeClick = {
|
||||
// 执行取消操作
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
ProfileDesign.Request.OpenGSettings -> {
|
||||
//查看是否有更新
|
||||
design.showToast("当前版本是最新版本",ToastDuration.Long)
|
||||
}// startActivity(NetworkSettingsActivity::class.intent)
|
||||
ProfileDesign.Request.OpenMyGongdan ->startActivity(GongdanActivity::class.intent)
|
||||
ProfileDesign.Request.OpenCustomViewIPtest -> startActivity(H5WebActivity::class.intent.putExtra("url","https://ipcelou.com"))
|
||||
ProfileDesign.Request.OpenCustomViewSpeed -> startActivity(H5WebActivity::class.intent.putExtra("url","https://fast.com/"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -95,6 +95,9 @@ class PropertiesActivity : BaseActivity<PropertiesDesign>() {
|
||||
coroutineScope {
|
||||
commit(profile.uuid) {
|
||||
launch {
|
||||
withProfile {
|
||||
setActive(queryAll().first())
|
||||
}
|
||||
updateStatus(it)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import com.github.kr328.clash.common.util.intent
|
||||
import com.github.kr328.clash.core.Clash
|
||||
@@ -8,11 +10,15 @@ import com.github.kr328.clash.design.PreferenceManager
|
||||
import com.github.kr328.clash.design.ProxyDesign
|
||||
import com.github.kr328.clash.design.model.ProxyState
|
||||
import com.github.kr328.clash.util.withClash
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.selects.select
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class ProxyActivity : BaseActivity<ProxyDesign>() {
|
||||
override suspend fun main() {
|
||||
@@ -22,6 +28,8 @@ class ProxyActivity : BaseActivity<ProxyDesign>() {
|
||||
val unorderedStates = names.indices.map { names[it] to states[it] }.toMap()
|
||||
val reloadLock = Semaphore(10)
|
||||
|
||||
setTitle("选择节点")
|
||||
|
||||
val design = ProxyDesign(
|
||||
this,
|
||||
mode,
|
||||
@@ -31,7 +39,11 @@ class ProxyActivity : BaseActivity<ProxyDesign>() {
|
||||
|
||||
setContentDesign(design)
|
||||
|
||||
design.requests.send(ProxyDesign.Request.ReloadAll)
|
||||
|
||||
//刷新所以Groups
|
||||
//design.requests.send(ProxyDesign.Request.ReloadAll)
|
||||
|
||||
design.requests.send(ProxyDesign.Request.Reload(0))
|
||||
|
||||
while (isActive) {
|
||||
select<Unit> {
|
||||
@@ -65,11 +77,52 @@ class ProxyActivity : BaseActivity<ProxyDesign>() {
|
||||
}
|
||||
is ProxyDesign.Request.Reload -> {
|
||||
launch {
|
||||
design.urlTesting = false
|
||||
val group = reloadLock.withPermit {
|
||||
withClash {
|
||||
queryProxyGroup(names[it.index], uiStore.proxySort)
|
||||
}
|
||||
}
|
||||
var state = states[it.index]
|
||||
|
||||
|
||||
println(">>>>>>>>>>>> ${names}")
|
||||
println(">>>>>>>>>>>> ${it.index} group: "+group + " ${group.type.name} ${group.proxies.size}")
|
||||
|
||||
if (group.proxies.first().name == "GLOBAL"){
|
||||
println(">>>>>>>>>>>> GLOBAL ")
|
||||
}
|
||||
|
||||
if (PreferenceManager.selectnodeName.length > 0){
|
||||
state.now = PreferenceManager.selectnodeName //group.now
|
||||
}else{
|
||||
state.now = group.now
|
||||
}
|
||||
|
||||
println(">>>>>>>>>>>>unorderedStates : ${unorderedStates}")
|
||||
println(">>>>>>>>>>>> state: "+state)
|
||||
//PreferenceManager.selectnodeName = state.now //刷新目前使用的节点名称
|
||||
// >>>>>>>>>>>> state: ProxyState(now=?)
|
||||
// >>>>>>>>>>>> 0 group: 距离下次重置剩余:18 天 Selector
|
||||
// >>>>>>>>>>>>{代理=ProxyState(now=距离下次重置剩余:18 天), 电报=ProxyState(now=?), 油管=ProxyState(now=?), 奈飞=ProxyState(now=?), b站=ProxyState(now=?), 规则外路由选择=ProxyState(now=?)}
|
||||
|
||||
|
||||
design.updateGroup(
|
||||
it.index,
|
||||
group.proxies,
|
||||
group.type == Proxy.Type.Selector,
|
||||
state,
|
||||
unorderedStates
|
||||
)
|
||||
|
||||
|
||||
design.finishreferesh()
|
||||
|
||||
/*val group = reloadLock.withPermit {
|
||||
withClash {
|
||||
queryProxyGroup(names[it.index], uiStore.proxySort)
|
||||
}
|
||||
}
|
||||
val state = states[it.index]
|
||||
|
||||
state.now = group.now
|
||||
@@ -80,7 +133,7 @@ class ProxyActivity : BaseActivity<ProxyDesign>() {
|
||||
group.type == Proxy.Type.Selector,
|
||||
state,
|
||||
unorderedStates
|
||||
)
|
||||
) */
|
||||
}
|
||||
}
|
||||
is ProxyDesign.Request.Select -> {
|
||||
@@ -88,9 +141,11 @@ class ProxyActivity : BaseActivity<ProxyDesign>() {
|
||||
patchSelector(names[it.index], it.name)
|
||||
states[it.index].now = it.name
|
||||
}
|
||||
Log.i("selectnodeName","${it.name}")
|
||||
PreferenceManager.selectnodeName = it.name
|
||||
design.requestRedrawVisible()
|
||||
launch {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
is ProxyDesign.Request.UrlTest -> {
|
||||
launch {
|
||||
@@ -102,7 +157,7 @@ class ProxyActivity : BaseActivity<ProxyDesign>() {
|
||||
}
|
||||
}
|
||||
is ProxyDesign.Request.PatchMode -> {
|
||||
design.showModeSwitchTips()
|
||||
// design.showModeSwitchTips()
|
||||
|
||||
withClash {
|
||||
val o = queryOverride(Clash.OverrideSlot.Session)
|
||||
@@ -116,5 +171,21 @@ class ProxyActivity : BaseActivity<ProxyDesign>() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 使用协程延迟 500ms 后执行
|
||||
/* GlobalScope.launch(Dispatchers.Main) {
|
||||
delay(1500) // 延迟 500ms
|
||||
|
||||
launch {
|
||||
withClash {
|
||||
healthCheck(names[0])
|
||||
}
|
||||
design.requests.send(ProxyDesign.Request.Reload(0))
|
||||
}
|
||||
|
||||
}*/
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import com.github.kr328.clash.common.util.intent
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.github.kr328.clash.common.compat.isAllowForceDarkCompat
|
||||
import com.github.kr328.clash.common.compat.isLightNavigationBarCompat
|
||||
import com.github.kr328.clash.common.compat.isLightStatusBarsCompat
|
||||
import com.github.kr328.clash.common.compat.isSystemBarsTranslucentCompat
|
||||
import com.github.kr328.clash.design.PreferenceManager
|
||||
import com.github.kr328.clash.design.databinding.ActivityLoginBinding
|
||||
import com.github.kr328.clash.design.databinding.ActivityProfileBinding
|
||||
import com.github.kr328.clash.design.databinding.ActivityRegisterBinding
|
||||
import com.github.kr328.clash.design.ui.DayNight
|
||||
import com.github.kr328.clash.design.util.layoutInflater
|
||||
import com.github.kr328.clash.design.util.resolveThemedBoolean
|
||||
import com.github.kr328.clash.design.util.resolveThemedColor
|
||||
import com.github.kr328.clash.design.util.root
|
||||
import com.github.kr328.clash.network.ApiClient
|
||||
import com.github.kr328.clash.network.ApiClientConfig
|
||||
import com.github.kr328.clash.network.ApiService
|
||||
import com.github.kr328.clash.network.ConfigResponse
|
||||
import com.github.kr328.clash.network.LoginRequest
|
||||
import com.github.kr328.clash.network.LoginResponse
|
||||
import com.github.kr328.clash.network.safeApiRequestCall
|
||||
import com.github.kr328.clash.utity.LoadingDialog
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
|
||||
class RegisterActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var apiService: ApiService
|
||||
|
||||
private var dayNight: DayNight = DayNight.Day
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val binding = ActivityRegisterBinding
|
||||
.inflate(this.layoutInflater, this.root, false)
|
||||
|
||||
setContentView(binding.root)
|
||||
|
||||
applyDayNight()
|
||||
|
||||
PreferenceManager.init(this)
|
||||
|
||||
binding.loginLoginButton.setOnClickListener {
|
||||
val email = binding.loginEmailEditText.text.toString()
|
||||
val password = binding.loginPasswordEditText.text.toString()
|
||||
val password2 = binding.loginPasswordEditText2.text.toString()
|
||||
val isAgreementChecked = binding.loginAgreementCheckBox.isChecked
|
||||
|
||||
if (email.isEmpty() || password.isEmpty() || password2.isEmpty()) {
|
||||
Toast.makeText(this, "请输入邮箱和密码", Toast.LENGTH_SHORT).show()
|
||||
} else if (!isAgreementChecked) {
|
||||
Toast.makeText(this, "请同意隐私政策和用户协议", Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
|
||||
// Hide the keyboard
|
||||
val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
val currentFocusView = currentFocus
|
||||
if (currentFocusView != null) {
|
||||
inputMethodManager.hideSoftInputFromWindow(currentFocusView.windowToken, 0)
|
||||
}
|
||||
|
||||
performLogin(email, password)
|
||||
}
|
||||
}
|
||||
|
||||
binding.loginForgotPassword.setOnClickListener {
|
||||
// Navigate to Forgot Password screen or show dialog
|
||||
}
|
||||
|
||||
binding.loginRegister.setOnClickListener {
|
||||
// Navigate to Register screen
|
||||
finish()
|
||||
}
|
||||
|
||||
apiService = ApiClientConfig.retrofit.create(ApiService::class.java)
|
||||
|
||||
|
||||
//获取 Config 数据
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
safeApiRequestCall { apiService.getConfig()}.let {
|
||||
if (it != null && it.isSuccessful) {
|
||||
|
||||
val response: ConfigResponse = it.body() ?: throw Exception("Response body is null")
|
||||
if (response.code == 1 ) {
|
||||
PreferenceManager.saveConfigToPreferences( response)
|
||||
//重新初始化 ApiClint
|
||||
}
|
||||
println("API Response: ${it.body()}")
|
||||
} else {
|
||||
println("API Error: 请求失败")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* withContext(Dispatchers.Main) {
|
||||
* if (response.isSuccessful) {
|
||||
* val userData = response.body()
|
||||
* // 更新 UI
|
||||
* } else {
|
||||
* Toast.makeText(this@MainActivity, "Failed to fetch user data", Toast.LENGTH_SHORT).show()
|
||||
* }
|
||||
* }
|
||||
* */
|
||||
|
||||
private fun applyDayNight(config: Configuration = resources.configuration) {
|
||||
//val dayNight = theme.applyStyle(R.style.AppThemeLight, true) //默认白天模式
|
||||
|
||||
/*
|
||||
val dayNight = queryDayNight(config)
|
||||
when (dayNight) {
|
||||
DayNight.Night -> theme.applyStyle(R.style.AppThemeDark, true)
|
||||
DayNight.Day -> theme.applyStyle(R.style.AppThemeLight, true)
|
||||
}*/
|
||||
|
||||
window.isAllowForceDarkCompat = false
|
||||
window.isSystemBarsTranslucentCompat = true
|
||||
|
||||
window.statusBarColor = resolveThemedColor(android.R.attr.statusBarColor)
|
||||
window.navigationBarColor = resolveThemedColor(android.R.attr.navigationBarColor)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
window.isLightStatusBarsCompat = resolveThemedBoolean(android.R.attr.windowLightStatusBar)
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 27) {
|
||||
window.isLightNavigationBarCompat = resolveThemedBoolean(android.R.attr.windowLightNavigationBar)
|
||||
}
|
||||
|
||||
this.dayNight = DayNight.Night //dayNight
|
||||
}
|
||||
|
||||
private fun performLogin(email: String, password: String) {
|
||||
// Perform login logic here, possibly calling an API
|
||||
|
||||
// Show the loading indicator with a custom message
|
||||
LoadingDialog.show(this, "正在注册...")
|
||||
|
||||
val apiServiceApp = ApiClient.retrofit.create(ApiService::class.java)
|
||||
|
||||
//获取 Config 数据
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
safeApiRequestCall {
|
||||
apiServiceApp.registerUser(LoginRequest(email,password))}.let {
|
||||
withContext(Dispatchers.Main) {
|
||||
LoadingDialog.hide()
|
||||
}
|
||||
if (it != null &&
|
||||
it.isSuccessful) {
|
||||
println("API Response: ${it.code()} ${it.body()} ${it.raw().body}")
|
||||
val response: LoginResponse = it.body() ?: throw Exception("Response body is null")
|
||||
if (response.data != null){
|
||||
PreferenceManager.loginemail = email
|
||||
PreferenceManager.loginToken = response.data?.token ?: ""
|
||||
PreferenceManager.loginauthData = response.data?.auth_data ?: ""
|
||||
PreferenceManager.isLoginin = true
|
||||
// saveLoginMailToken(this@LoginActivity,email)
|
||||
// saveLoginToken(this@LoginActivity, response.data.token ?: "")
|
||||
// saveLoginAuthData(this@LoginActivity, response.data.auth_data ?: "")
|
||||
// saveLoginStatus(this@LoginActivity,true)
|
||||
//获取用户信息
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
val intent = Intent(this@RegisterActivity, MainActivity::class.java)
|
||||
intent.flags =
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
startActivity(intent)
|
||||
finish() // It
|
||||
}
|
||||
}
|
||||
}else{
|
||||
// Handle error response, even if it's 422
|
||||
try {
|
||||
val errorResponse = it?.errorBody()?.string() ?: ""
|
||||
val errorJson = JSONObject(errorResponse)
|
||||
val message = errorJson.optString("message")
|
||||
// Process the error message or show it to the user
|
||||
if (!message.isNullOrEmpty()) {
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(this@RegisterActivity, "注册失败:${message}", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
println( "Message: $message, ")
|
||||
} catch (e: JSONException) {
|
||||
// Handle JSON parsing error
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(this@RegisterActivity, "注册失败:${e.message}", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.github.kr328.clash.common.util.intent
|
||||
import com.github.kr328.clash.design.PreferenceManager
|
||||
import com.github.kr328.clash.design.databinding.ActivityLoginBinding
|
||||
import com.github.kr328.clash.design.databinding.ActivitySplashBinding
|
||||
import com.github.kr328.clash.design.util.root
|
||||
import com.github.kr328.clash.network.ApiService
|
||||
|
||||
|
||||
class SplashActivity : AppCompatActivity() {
|
||||
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val binding = ActivitySplashBinding
|
||||
.inflate(this.layoutInflater, this.root, false)
|
||||
|
||||
setContentView(binding.root)
|
||||
PreferenceManager.init(this)
|
||||
|
||||
Handler(Looper.getMainLooper()).postDelayed({
|
||||
// Hide the loading indicator
|
||||
startActivity(LoginActivity::class.intent)
|
||||
finish()
|
||||
|
||||
}, 1000) // Simulating a network delay
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import com.github.kr328.clash.design.SubmitOrderViewDesign
|
||||
|
||||
class SubmitOrderActivity : BaseActivity<SubmitOrderViewDesign>() {
|
||||
|
||||
override suspend fun main() {
|
||||
|
||||
val design = SubmitOrderViewDesign(this)
|
||||
setContentDesign(design)
|
||||
|
||||
//
|
||||
val trade_no = intent.getStringExtra("trade_no")
|
||||
design.fetchTradeInfo(trade_no)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
package com.github.kr328.clash
|
||||
|
||||
import android.graphics.Color
|
||||
import android.graphics.Rect
|
||||
import android.provider.Settings.Global
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.widget.Button
|
||||
import android.widget.EditText
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.github.kr328.clash.design.BaseListActivity
|
||||
import com.github.kr328.clash.design.PreferenceManager
|
||||
import com.github.kr328.clash.design.adapter.TicketDetailAdapter
|
||||
import com.github.kr328.clash.design.adapter.TicketsDataAdapter
|
||||
import com.github.kr328.clash.design.ui.ToastDuration
|
||||
import com.github.kr328.clash.network.ApiClient
|
||||
import com.github.kr328.clash.network.ApiService
|
||||
import com.github.kr328.clash.network.PublicResponse
|
||||
import com.github.kr328.clash.network.TicketMessage
|
||||
import com.github.kr328.clash.network.safeApiRequestCall
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.net.URLEncoder
|
||||
|
||||
class TicketDetailActivity : BaseListActivity<TicketMessage>() {
|
||||
|
||||
override fun createAdapter(): RecyclerView.Adapter<*> {
|
||||
return TicketDetailAdapter(){ planData ->
|
||||
// 在这里处理 item 点击事件
|
||||
}
|
||||
}
|
||||
override fun onCreate() {
|
||||
|
||||
}
|
||||
|
||||
fun encodeString(input: String): String {
|
||||
val encoded = StringBuilder()
|
||||
|
||||
input.forEach { char ->
|
||||
when {
|
||||
char.isWhitespace() -> {
|
||||
// 编码空格为 %20
|
||||
encoded.append("")
|
||||
}
|
||||
char.isLetterOrDigit() || char.toInt() in 0x4E00..0x9FFF -> {
|
||||
// 保留字母、数字和中文字符
|
||||
encoded.append(char)
|
||||
}
|
||||
else -> {
|
||||
// 其他字符使用 URLEncoder 编码
|
||||
encoded.append(URLEncoder.encode(char.toString(), "UTF-8"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return encoded.toString()
|
||||
}
|
||||
|
||||
|
||||
fun onCreateInputView() {
|
||||
|
||||
|
||||
// 将 dp 转换为 px 的实用函数
|
||||
fun dpToPx(dp: Int): Int {
|
||||
return (dp * resources.displayMetrics.density).toInt()
|
||||
}
|
||||
|
||||
// 创建顶部分割线
|
||||
val topDivider = View(this).apply {
|
||||
layoutParams = FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
dpToPx(1) // 设置分割线高度为 1dp
|
||||
) .apply {
|
||||
gravity = Gravity.TOP // 将分割线置于顶部
|
||||
}
|
||||
setBackgroundColor(ContextCompat.getColor(this@TicketDetailActivity, android.R.color.darker_gray)) // 设置分割线颜色
|
||||
}
|
||||
|
||||
val rootLayout = FrameLayout(this).apply {
|
||||
layoutParams = FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
FrameLayout.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
addView(topDivider) // 将分割线添加到根布局
|
||||
|
||||
}
|
||||
|
||||
// Create the input container layout programmatically
|
||||
// Create the input container layout programmatically
|
||||
val inputContainer = LinearLayout(this).apply {
|
||||
orientation = LinearLayout.HORIZONTAL
|
||||
setPadding(8, 8, 8, 8)
|
||||
// setBackgroundColor(ContextCompat.getColor(this@TicketDetailActivity, android.R.color.white))
|
||||
layoutParams = FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT,
|
||||
Gravity.BOTTOM // Aligns the input container to the bottom
|
||||
)
|
||||
setBackgroundColor(ContextCompat.getColor(this@TicketDetailActivity, R.color.white))
|
||||
}
|
||||
|
||||
// Create the EditText for message input
|
||||
val editText = EditText(this).apply {
|
||||
hint = "输入内容回复工单..."
|
||||
setHintTextColor(ContextCompat.getColor(this@TicketDetailActivity, R.color.gray)) // Set the placeholder color
|
||||
// Utility function to convert dp to pixels
|
||||
fun dpToPx2(dp: Int): Int {
|
||||
return (dp * resources.displayMetrics.density).toInt()
|
||||
}
|
||||
textSize = 14f
|
||||
|
||||
// Set other EditText properties
|
||||
setTextColor(Color.BLACK) // Text color
|
||||
inputType = android.text.InputType.TYPE_TEXT_FLAG_MULTI_LINE
|
||||
setTextColor(ContextCompat.getColor(this@TicketDetailActivity, R.color.black))
|
||||
maxLines = 1
|
||||
// background = ContextCompat.getDrawable(this@TicketDetailActivity, R.drawable.rounded_button_background)
|
||||
|
||||
// 设置内边距为 4dp(上下左右)
|
||||
// val padding = dpToPx(8)
|
||||
// setPadding(padding,0 , padding, 0)
|
||||
layoutParams = LinearLayout.LayoutParams(
|
||||
0,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT,
|
||||
1f // Weight to make it fill available width
|
||||
)
|
||||
}
|
||||
|
||||
// Create the Send Button
|
||||
|
||||
val sendButton = Button(this).apply {
|
||||
text = "回复"
|
||||
setTextColor(ContextCompat.getColor(this@TicketDetailActivity, android.R.color.white)) // Set text color for better contrast
|
||||
|
||||
layoutParams = LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
}
|
||||
|
||||
// Set the background tint programmatically
|
||||
ViewCompat.setBackgroundTintList(
|
||||
sendButton,
|
||||
ContextCompat.getColorStateList(this, R.color.blue)
|
||||
)
|
||||
|
||||
|
||||
// Add EditText and Button to the input container
|
||||
inputContainer.addView(editText)
|
||||
inputContainer.addView(sendButton)
|
||||
|
||||
|
||||
sendButton.setOnClickListener {
|
||||
val message = editText.text.toString()
|
||||
if (message.isNullOrEmpty()){
|
||||
|
||||
}else{
|
||||
val fieldMap = mutableMapOf<String, String>()
|
||||
val messageProperty1 = encodeString(message)
|
||||
val ticketid = intent.getIntExtra("ticketid",0)
|
||||
fieldMap.put("message",""+messageProperty1)
|
||||
fieldMap.put("id",""+ticketid)
|
||||
|
||||
GlobalScope.launch {
|
||||
|
||||
val apiService = ApiClient.retrofit.create(ApiService::class.java)
|
||||
safeApiRequestCall { apiService.replyTicket(PreferenceManager.loginauthData, fieldMap)}.let {
|
||||
if (it != null && it.isSuccessful) {
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(this@TicketDetailActivity, "回复成功", Toast.LENGTH_SHORT).show()
|
||||
editText.setText("")
|
||||
}
|
||||
refereshData()
|
||||
|
||||
}else{
|
||||
val errorinfo = it?.errorBody()?.string()
|
||||
|
||||
if (errorinfo != null){
|
||||
//解析错错误信息
|
||||
|
||||
val gson = Gson()
|
||||
val submitResponse = gson.fromJson(errorinfo, PublicResponse::class.java)
|
||||
|
||||
if (submitResponse?.message != null){
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(this@TicketDetailActivity, "回复失败: ${submitResponse.message}", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}else{
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(this@TicketDetailActivity, "回复失败", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 将分割线添加到输入容器顶部
|
||||
|
||||
|
||||
rootLayout.addView(inputContainer)
|
||||
// rootLayout.addView(topDivider) // 将分割线添加到根布局
|
||||
// Add the input container to the root layout
|
||||
mainBinding?.mainview?.addView(rootLayout)
|
||||
|
||||
rootLayout.viewTreeObserver.addOnGlobalLayoutListener {
|
||||
val rect = Rect()
|
||||
rootLayout.getWindowVisibleDisplayFrame(rect)
|
||||
val screenHeight = rootLayout.rootView.height
|
||||
val keypadHeight = screenHeight - rect.bottom
|
||||
|
||||
val params = inputContainer.layoutParams as FrameLayout.LayoutParams
|
||||
if (keypadHeight > screenHeight * 0.15) {
|
||||
// Keyboard is visible; adjust inputContainer to stay above it
|
||||
params.bottomMargin = keypadHeight
|
||||
} else {
|
||||
// Keyboard is hidden; reset inputContainer to bottom
|
||||
params.bottomMargin = 0
|
||||
}
|
||||
inputContainer.layoutParams = params
|
||||
}
|
||||
|
||||
}
|
||||
override suspend fun loadData(page: Int, callback: (List<TicketMessage>?, Throwable?) -> Unit) {
|
||||
val ticketid = intent.getIntExtra("ticketid",0)
|
||||
val apiService = ApiClient.retrofit.create(ApiService::class.java)
|
||||
safeApiRequestCall { apiService.getTicketsByTID(PreferenceManager.loginauthData,ticketid)}.let {
|
||||
if (it != null && it.isSuccessful) {
|
||||
withContext(Dispatchers.Main) {
|
||||
|
||||
referTitle(it.body()?.data?.subject ?: "")
|
||||
}
|
||||
if(it.body()?.data?.status == 0){
|
||||
withContext(Dispatchers.Main) {
|
||||
onCreateInputView()
|
||||
}
|
||||
}
|
||||
callback(it.body()?.data?.message,null)
|
||||
|
||||
}else{
|
||||
callback(null, Throwable("请求失败"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,7 +8,7 @@ object SystemLogcat {
|
||||
"Go",
|
||||
"DEBUG",
|
||||
"AndroidRuntime",
|
||||
"ClashMetaForAndroid",
|
||||
"UUVPN",
|
||||
"LwIP",
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user