最新 Android 客户端源代码以及教程,完全免费开源最新版本
free all
@@ -9,11 +9,14 @@
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
|
||||
<!-- 网络权限 -->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
tools:ignore="QueryAllPackagesPermission" />
|
||||
@@ -21,6 +24,7 @@
|
||||
<application
|
||||
android:name=".MainApplication"
|
||||
android:allowBackup="true"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:banner="@mipmap/ic_launcher"
|
||||
android:fullBackupContent="@xml/full_backup_content"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
@@ -38,12 +42,14 @@
|
||||
android:name="releaseCode"
|
||||
android:value="@integer/release_code" />
|
||||
|
||||
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="uiMode"
|
||||
android:exported="true"
|
||||
android:label="@string/launch_name"
|
||||
android:launchMode="singleTop">
|
||||
android:launchMode="singleTop" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
@@ -66,9 +72,9 @@
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="clash"/>
|
||||
<data android:scheme="clashmeta"/>
|
||||
<data android:host="install-config"/>
|
||||
<data android:scheme="clash-----------"/>
|
||||
<data android:scheme="clashmeta-----------"/>
|
||||
<data android:host="install-config-----------"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="com.github.metacubex.clash.meta.action.START_CLASH" />
|
||||
@@ -83,6 +89,9 @@
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
|
||||
|
||||
<activity
|
||||
android:name=".ApkBrokenActivity"
|
||||
android:configChanges="uiMode"
|
||||
@@ -170,14 +179,109 @@
|
||||
android:exported="false"
|
||||
android:label="@string/files" />
|
||||
|
||||
<activity
|
||||
android:name=".ProfileActivity"
|
||||
android:configChanges="uiMode"
|
||||
android:exported="false"
|
||||
android:label="设置中心"
|
||||
/>
|
||||
|
||||
<activity
|
||||
android:name=".LoginActivity"
|
||||
android:configChanges="uiMode"
|
||||
android:exported="false"
|
||||
/>
|
||||
|
||||
<activity
|
||||
android:name=".SplashActivity"
|
||||
android:configChanges="uiMode"
|
||||
android:exported="true" />
|
||||
|
||||
<activity
|
||||
android:name=".PlansActivity"
|
||||
android:configChanges="uiMode"
|
||||
android:exported="false"
|
||||
android:label="购买订阅"
|
||||
/>
|
||||
|
||||
|
||||
<activity
|
||||
android:name=".MyOrdersActivity"
|
||||
android:configChanges="uiMode"
|
||||
android:exported="false"
|
||||
android:label="我的订单"
|
||||
/>
|
||||
|
||||
<activity
|
||||
android:name=".ConfigOrderActivity"
|
||||
android:configChanges="uiMode"
|
||||
android:exported="false"
|
||||
android:label="配置订阅"
|
||||
/>
|
||||
|
||||
<activity
|
||||
android:name=".SubmitOrderActivity"
|
||||
android:configChanges="uiMode"
|
||||
android:exported="false"
|
||||
android:label="订单详情"
|
||||
/>
|
||||
|
||||
<activity
|
||||
android:name=".GongdanActivity"
|
||||
android:configChanges="uiMode"
|
||||
android:exported="false"
|
||||
android:label="我的工单"
|
||||
/>
|
||||
|
||||
<activity
|
||||
android:name=".NewGongdanActivity"
|
||||
android:configChanges="uiMode"
|
||||
android:exported="false"
|
||||
android:label="新建工单"
|
||||
/>
|
||||
|
||||
|
||||
|
||||
|
||||
<activity
|
||||
android:name=".H5WebActivity"
|
||||
android:configChanges="uiMode"
|
||||
android:exported="false"
|
||||
/>
|
||||
<activity
|
||||
android:name=".TicketDetailActivity"
|
||||
android:configChanges="uiMode"
|
||||
android:exported="false"
|
||||
android:label=""
|
||||
/>
|
||||
|
||||
|
||||
|
||||
<activity
|
||||
android:name=".RegisterActivity"
|
||||
android:configChanges="uiMode"
|
||||
android:exported="false"
|
||||
android:label="注册"
|
||||
/>
|
||||
|
||||
|
||||
<activity
|
||||
android:name=".InvitationActivity"
|
||||
android:configChanges="uiMode"
|
||||
android:exported="false"
|
||||
android:label="我的邀请"
|
||||
/>
|
||||
|
||||
<service
|
||||
android:name=".LogcatService"
|
||||
android:exported="false"
|
||||
android:label="@string/clash_logcat" />
|
||||
|
||||
<service
|
||||
android:name=".TileService"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/ic_logo_service"
|
||||
android:foregroundServiceType="connectedDevice"
|
||||
android:label="@string/launch_name"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
<intent-filter>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"v":"5.9.3","fr":29.9700012207031,"ip":0,"op":60.0000024438501,"w":1920,"h":1080,"nm":"1_Loading_Line","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Blue Line","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[960,539,0],"ix":2,"l":2},"a":{"a":0,"k":[0,-1,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[800,800],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.212477396049,0.540471813725,0.898039215686,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":80,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-1],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.9],"y":[0]},"t":15,"s":[0]},{"t":60.0000024438501,"s":[99.9]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.1],"y":[1]},"o":{"x":[0.9],"y":[0]},"t":0,"s":[0.01]},{"t":45.0000018328876,"s":[100]}],"ix":2},"o":{"a":0,"k":-1,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":150.000006109625,"st":0,"ct":1,"bm":0}],"markers":[{"tm":60.0000024438501,"cm":"END (No Loop)","dr":0}]}
|
||||
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 16 KiB |
@@ -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("请求失败"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="#1e1f22"
|
||||
android:pathData="M480,640Q555,640 607.5,587.5Q660,535 660,460Q660,385 607.5,332.5Q555,280 480,280Q405,280 352.5,332.5Q300,385 300,460Q300,535 352.5,587.5Q405,640 480,640ZM480,568Q435,568 403.5,536.5Q372,505 372,460Q372,415 403.5,383.5Q435,352 480,352Q525,352 556.5,383.5Q588,415 588,460Q588,505 556.5,536.5Q525,568 480,568ZM480,760Q334,760 214,678.5Q94,597 40,460Q94,323 214,241.5Q334,160 480,160Q626,160 746,241.5Q866,323 920,460Q866,597 746,678.5Q626,760 480,760ZM480,460Q480,460 480,460Q480,460 480,460Q480,460 480,460Q480,460 480,460Q480,460 480,460Q480,460 480,460Q480,460 480,460Q480,460 480,460ZM480,680Q593,680 687.5,620.5Q782,561 832,460Q782,359 687.5,299.5Q593,240 480,240Q367,240 272.5,299.5Q178,359 128,460Q178,561 272.5,620.5Q367,680 480,680Z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="#1e1f22"
|
||||
android:pathData="M644,532L586,474Q595,427 559,386Q523,345 466,354L408,296Q425,288 442.5,284Q460,280 480,280Q555,280 607.5,332.5Q660,385 660,460Q660,480 656,497.5Q652,515 644,532ZM772,658L714,602Q752,573 781.5,538.5Q811,504 832,460Q782,359 688.5,299.5Q595,240 480,240Q451,240 423,244Q395,248 368,256L306,194Q347,177 390,168.5Q433,160 480,160Q631,160 749,243.5Q867,327 920,460Q897,519 859.5,569.5Q822,620 772,658ZM792,904L624,738Q589,749 553.5,754.5Q518,760 480,760Q329,760 211,676.5Q93,593 40,460Q61,407 93,361.5Q125,316 166,280L56,168L112,112L848,848L792,904ZM222,336Q193,362 169,393Q145,424 128,460Q178,561 271.5,620.5Q365,680 480,680Q500,680 519,677.5Q538,675 558,672L522,634Q511,637 501,638.5Q491,640 480,640Q405,640 352.5,587.5Q300,535 300,460Q300,449 301.5,439Q303,429 306,418L222,336ZM541,429L541,429Q541,429 541,429Q541,429 541,429Q541,429 541,429Q541,429 541,429Q541,429 541,429Q541,429 541,429ZM390,504Q390,504 390,504Q390,504 390,504L390,504Q390,504 390,504Q390,504 390,504Q390,504 390,504Q390,504 390,504Z"/>
|
||||
</vector>
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/appicon"/>
|
||||
<monochrome android:drawable="@drawable/appicon" />
|
||||
</adaptive-icon>
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/appicon"/>
|
||||
<monochrome android:drawable="@drawable/appicon" />
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 16 KiB |