描述:

新增报错日志抓取 并上传至阿里云oss
This commit is contained in:
level 2024-09-30 10:26:42 +08:00
parent 0fdf222a78
commit 2b8635e45a
12 changed files with 578 additions and 6 deletions

View File

@ -103,7 +103,7 @@ dependencies {
// https://mvnrepository.com/artifact/com.github.bumptech.glide/glide // https://mvnrepository.com/artifact/com.github.bumptech.glide/glide
implementation("com.github.bumptech.glide:glide:4.13.2") implementation("com.github.bumptech.glide:glide:4.13.2")
// implementation("com.luozm.captcha:captcha:1.1.2") // implementation("com.luozm.captcha:captcha:1.1.2")
implementation("com.aliyun.dpa:oss-android-sdk:2.9.19")
} }
tasks.getByName("clean", type = Delete::class) { tasks.getByName("clean", type = Delete::class) {

View File

@ -16,6 +16,7 @@ import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.ProgressBar import android.widget.ProgressBar
import android.widget.TextView import android.widget.TextView
import android.widget.Toast
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.core.view.GravityCompat import androidx.core.view.GravityCompat
import androidx.drawerlayout.widget.DrawerLayout import androidx.drawerlayout.widget.DrawerLayout
@ -27,6 +28,15 @@ import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.customview.customView import com.afollestad.materialdialogs.customview.customView
import com.afollestad.materialdialogs.customview.getCustomView import com.afollestad.materialdialogs.customview.getCustomView
import com.airbnb.lottie.LottieAnimationView import com.airbnb.lottie.LottieAnimationView
import com.alibaba.sdk.android.oss.ClientException
import com.alibaba.sdk.android.oss.OSSClient
import com.alibaba.sdk.android.oss.ServiceException
import com.alibaba.sdk.android.oss.callback.OSSCompletedCallback
import com.alibaba.sdk.android.oss.callback.OSSProgressCallback
import com.alibaba.sdk.android.oss.common.auth.OSSStsTokenCredentialProvider
import com.alibaba.sdk.android.oss.internal.OSSAsyncTask
import com.alibaba.sdk.android.oss.model.PutObjectRequest
import com.alibaba.sdk.android.oss.model.PutObjectResult
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.JsonArray import com.google.gson.JsonArray
import com.google.gson.JsonObject import com.google.gson.JsonObject
@ -52,8 +62,10 @@ import com.yingmao.clash.net.http.NetService
import com.yingmao.clash.net.http.entity.AppVersionCheckRsp import com.yingmao.clash.net.http.entity.AppVersionCheckRsp
import com.yingmao.clash.net.http.entity.GetActivityRsp import com.yingmao.clash.net.http.entity.GetActivityRsp
import com.yingmao.clash.net.http.entity.HeartBeatRsp import com.yingmao.clash.net.http.entity.HeartBeatRsp
import com.yingmao.clash.net.http.entity.ParamData
import com.yingmao.clash.net.http.entity.PhDownloadUrlRsp import com.yingmao.clash.net.http.entity.PhDownloadUrlRsp
import com.yingmao.clash.net.http.entity.SubscriptionRsp import com.yingmao.clash.net.http.entity.SubscriptionRsp
import com.yingmao.clash.net.http.entity.UploadParamData
import com.yingmao.clash.net.http.entity.VipSignRsp import com.yingmao.clash.net.http.entity.VipSignRsp
import com.yingmao.clash.remote.Remote import com.yingmao.clash.remote.Remote
import com.yingmao.clash.service.ClashManager import com.yingmao.clash.service.ClashManager
@ -65,6 +77,8 @@ import com.yingmao.clash.service.remote.IProfileManager
import com.yingmao.clash.service.remote.wrap import com.yingmao.clash.service.remote.wrap
import com.yingmao.clash.util.AESUtil import com.yingmao.clash.util.AESUtil
import com.yingmao.clash.util.CommonUtils import com.yingmao.clash.util.CommonUtils
import com.yingmao.clash.util.DataUtils
import com.yingmao.clash.util.DataUtils.getAllFilePath
import com.yingmao.clash.util.Utils import com.yingmao.clash.util.Utils
import com.yingmao.clash.view.RxToast import com.yingmao.clash.view.RxToast
import com.yingmao.clash.view.adapter.MainVPAdapter import com.yingmao.clash.view.adapter.MainVPAdapter
@ -74,6 +88,7 @@ import com.yingmao.clash.view.dialog.DialogAppVersion
import com.yingmao.clash.view.dialog.DialogBandChoose import com.yingmao.clash.view.dialog.DialogBandChoose
import com.yingmao.clash.view.dialog.DialogNotice import com.yingmao.clash.view.dialog.DialogNotice
import com.yingmao.clash.view.dialog.RxDialogShapeLoading import com.yingmao.clash.view.dialog.RxDialogShapeLoading
import com.yingmao.clash.view.permissions.PermissionX
import com.yingmao.clash.vm.MainAtyVM import com.yingmao.clash.vm.MainAtyVM
import com.yingmao.clash.webview.BZYWebViewKeFu import com.yingmao.clash.webview.BZYWebViewKeFu
import com.yingmao.clash.webview.BZYWebViewNormal import com.yingmao.clash.webview.BZYWebViewNormal
@ -87,6 +102,7 @@ import okhttp3.RequestBody
import java.io.File import java.io.File
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
import java.util.Locale
import java.util.Timer import java.util.Timer
import java.util.TimerTask import java.util.TimerTask
import java.util.UUID import java.util.UUID
@ -249,7 +265,7 @@ class NewMainActivity : BaseActivity() {
startLog() startLog()
// showBindAccount() // showBindAccount()
firstEntry() firstEntry()
checkPermission()
} }
/* /*
@ -543,7 +559,32 @@ class NewMainActivity : BaseActivity() {
} }
} }
} }
android.util.Log.i("levelData", "data:::${data}") } else if (httpStatus.reqType == HttpReqType.UploadParameters) {
val upLoadData = httpStatus.rsp as UploadParamData
if (upLoadData.code == 1000) {
val directory: File? = this.getExternalFilesDir("CrashLog")
//多文件上传
if (directory != null) {
val allFilePath = getAllFilePath(directory.path)
if (upLoadData.data != null) {
if (allFilePath.isNotEmpty()) {
val username =
MMKV.defaultMMKV()
.decodeString(BZYConstants.MMKV_KEY_REMEMBER_USERNAME, "")!!
val nowTime = SimpleDateFormat(
"yyyyMMddkkmmss",
Locale.getDefault()
).format(Date())
uploadForAliOss(
upLoadData.data,
allFilePath,
"app/errlog/" + username + "_" + nowTime + ".txt"
)
}
}
}
}
} }
} }
@ -1086,4 +1127,89 @@ class NewMainActivity : BaseActivity() {
super.onDestroy() super.onDestroy()
stopUploadTimer() stopUploadTimer()
} }
private fun uploadForAliOss(data: ParamData, filePath: List<String>, dirStr: String) {
val endpoint = "oss-cn-hongkong.aliyuncs.com"
val accessKeyId = data.credentials?.accessKeyId
val accessKeySecret = data.credentials?.accessKeySecret
// 从STS服务获取的安全令牌SecurityToken
val securityToken = data.credentials?.securityToken
val credentialProvider =
OSSStsTokenCredentialProvider(accessKeyId, accessKeySecret, securityToken)
val oss = OSSClient(applicationContext, endpoint, credentialProvider)
android.util.Log.i("uploadForAliOss", "filePath::::${filePath[0]}")
val put = PutObjectRequest("yingmao", dirStr, filePath[0])
// 异步上传时可以设置进度回调。
put.progressCallback =
OSSProgressCallback { request, currentSize, totalSize ->
android.util.Log.d(
"PutObject",
"currentSize: $currentSize totalSize: $totalSize"
)
}
val task: OSSAsyncTask<*> = oss.asyncPutObject(put,
object : OSSCompletedCallback<PutObjectRequest?, PutObjectResult> {
override fun onSuccess(request: PutObjectRequest?, result: PutObjectResult) {
if (result.statusCode == 200) {
//删除文件
DataUtils.deleteFile(filePath[0])
}
android.util.Log.d("PutObjectRequest", "$request")
android.util.Log.d("PutObjectResult", "$result")
android.util.Log.d("PutObject", "UploadSuccess")
android.util.Log.d("ETag", result.eTag)
android.util.Log.d("RequestId", result.requestId)
}
override fun onFailure(
request: PutObjectRequest?,
clientExcepion: ClientException,
serviceException: ServiceException
) {
// 请求异常。
if (clientExcepion != null) {
// 本地异常,如网络异常等。
clientExcepion.printStackTrace()
}
if (serviceException != null) {
// 服务异常。
android.util.Log.e("ErrorCode", serviceException.errorCode)
android.util.Log.e("RequestId", serviceException.requestId)
android.util.Log.e("HostId", serviceException.hostId)
android.util.Log.e("RawMessage", serviceException.rawMessage)
}
}
})
}
private fun checkPermission() {
PermissionX.request(
this,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE,
) { allGranted, deniedList ->
if (allGranted) {
android.util.Log.i("checkPermission", "checkPermission::所有申请的权限都已通过")
val directory: File? = this.getExternalFilesDir("CrashLog")
if (directory != null) {
val allFilePath = getAllFilePath(directory.path)
if (allFilePath.isNotEmpty()) {
mainAtyVM.getUploadParameters()
} else {
android.util.Log.i("checkPermission", "allFilePath is empty")
}
} else {
Toast.makeText(this, "You denied $deniedList", Toast.LENGTH_SHORT).show()
}
}
}
}
} }

View File

@ -19,6 +19,7 @@ import com.yingmao.clash.common.compat.currentProcessName
import com.yingmao.clash.common.log.Log import com.yingmao.clash.common.log.Log
import com.yingmao.clash.entity.CheckLoginRsp import com.yingmao.clash.entity.CheckLoginRsp
import com.yingmao.clash.entity.UserData import com.yingmao.clash.entity.UserData
import com.yingmao.clash.util.CrashCatchHandler
import com.yingmao.clash.util.clashDir import com.yingmao.clash.util.clashDir
import java.io.File import java.io.File
@ -68,6 +69,11 @@ class MainApplication : Application() {
val MMKVRootDir = MMKV.initialize(this) val MMKVRootDir = MMKV.initialize(this)
Logger.i("MMKV_ROOT_DIR:$MMKVRootDir") Logger.i("MMKV_ROOT_DIR:$MMKVRootDir")
updateNotificationChannels() updateNotificationChannels()
/*
* 错误日志上传捕获
* */
val crashCatchHandler: CrashCatchHandler = CrashCatchHandler.getInstance() //获得单例
crashCatchHandler.init(applicationContext) //初始化,传入context
} }
private fun extractGeoFiles() { private fun extractGeoFiles() {

View File

@ -25,6 +25,7 @@ import com.yingmao.clash.net.http.entity.AppVersionCheckRsp
import com.yingmao.clash.net.http.entity.GetActivityRsp import com.yingmao.clash.net.http.entity.GetActivityRsp
import com.yingmao.clash.net.http.entity.LoginRsp2 import com.yingmao.clash.net.http.entity.LoginRsp2
import com.yingmao.clash.net.http.entity.SendSmsRsp import com.yingmao.clash.net.http.entity.SendSmsRsp
import com.yingmao.clash.net.http.entity.UploadParamData
import com.yingmao.clash.net.http.entity.VipSignRsp import com.yingmao.clash.net.http.entity.VipSignRsp
import okhttp3.RequestBody import okhttp3.RequestBody
import retrofit2.Call import retrofit2.Call
@ -489,4 +490,16 @@ interface HttpApi {
BZYConstants.MMKV_KEY_TOKEN BZYConstants.MMKV_KEY_TOKEN
).toString() ).toString()
): GetActivityRsp ): GetActivityRsp
//获取阿里云上传数据
@Headers(
"User-Agent: Octopus_Android"
)
@POST("/netbarcloud/vpn/getOssAssumeRoleToken")
suspend fun getUploadParameters(
@QueryMap params: Map<String, String>,
@Header("token") string: String = MMKV.defaultMMKV().decodeString(
BZYConstants.MMKV_KEY_TOKEN
).toString()
): UploadParamData
} }

View File

@ -46,6 +46,6 @@ enum class HttpReqType(type: Int) {
Get_Node_Other(42), Get_Node_Other(42),
GetActivity(43), GetActivity(43),
GetRuningNotice(44), GetRuningNotice(44),
unionPay(45) unionPay(45),
UploadParameters(46)
} }

View File

@ -0,0 +1,24 @@
package com.yingmao.clash.net.http.entity
import com.google.gson.annotations.SerializedName
data class UploadParamData (
@SerializedName("code") val code: Int,
@SerializedName("message") val message: String,
@SerializedName("data") val data: ParamData?
)
data class ParamData(
val requestId:String?,
val credentials:Credentials?,
val assumedRoleUser:AssumedRoleUser?
)
data class AssumedRoleUser(
val arn:String,
val assumedRoleId:String
)
data class Credentials(
val securityToken:String,
val accessKeySecret:String,
val accessKeyId:String
)

View File

@ -0,0 +1,206 @@
package com.yingmao.clash.util;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
public class CrashCatchHandler implements UncaughtExceptionHandler {
private final static String FILE_TYPE = "CrashLog";
public static final String TAG = "CrashHandler";
@SuppressLint("StaticFieldLeak")
private static final CrashCatchHandler INSTANCE = new CrashCatchHandler();// 单例模式
private Context context;
private UncaughtExceptionHandler defaultHandler;// 系统默认的UncaughtException处理类
private final Map<String, String> infoMap = new HashMap<String, String>(); // 用来存储设备信息和异常信息
/**
* 私有构造方法保证只有一个CrashHandler实例
*/
private CrashCatchHandler() {
}
/**
* 获取CrashHandler单例模式
*/
public static CrashCatchHandler getInstance() {
return INSTANCE;
}
/**
* 初始化
*/
public void init(Context context) {
this.context = context;
defaultHandler = Thread.getDefaultUncaughtExceptionHandler();// 获取系统默认的UncaughtException处理器
Thread.setDefaultUncaughtExceptionHandler(this);// 设置当前CrashHandler为程序的默认处理器
}
/**
* 当UncaughtException发生时会转入该函数来处理
*/
@Override
public void uncaughtException(@NonNull Thread thread, @NonNull Throwable ex) {
try {
if (!handleException(ex) && defaultHandler != null) {
// 如果用户没有处理则让系统默认的异常处理器来处理
defaultHandler.uncaughtException(thread, ex);
} else {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
Log.e(TAG, "exception : ", e);
e.printStackTrace();
}
// 杀死进程
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(1);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.
*
* @return 如果处理了该异常信息, 返回true;否则返回false.
*/
private boolean handleException(Throwable ex) throws IOException {
if (ex == null) {
return false;
}
// 使用Toast显示异常信息
new Thread() {
public void run() {
Looper.prepare();
Toast.makeText(context, "程序异常停止,请稍后重启应用!", Toast.LENGTH_SHORT).show();
Looper.loop();
}
}.start();
collectDeviceInfo(context);// 收集设备参数信息
saveCrashInfoToFile(ex);// 保存日志文件
//上传错误文件
return true;
}
/**
* 收集设备信息
*/
public void collectDeviceInfo(Context context) {
// 使用包管理器获取信息
PackageManager pm = context.getPackageManager();
SimpleDateFormat nowTime = new SimpleDateFormat("yyyy年MM月dd日HH时mm分ss秒E", Locale.getDefault());
String nowTimeStr = nowTime.format(System.currentTimeMillis());
try {
PackageInfo pi = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES);
if (pi != null) {
// TODO 在这里得到包的信息
String versionName = pi.versionName == null ? "" : pi.versionName;// 版本名;若versionName==null="null"否则=versionName
String versionCode = pi.versionCode + "";// 版本号
infoMap.put("versionName", versionName);
infoMap.put("versionCode", versionCode);
infoMap.put("当前时间", String.valueOf(nowTimeStr));
}
} catch (NameNotFoundException e) {
Log.e(TAG, "an NameNotFoundException occured when collect package info");
}
// 使用反射获取获取系统的硬件信息
Field[] fields = Build.class.getDeclaredFields();// 获得某个类的所有申明的字段即包括publicprivate和proteced
for (Field field : fields) {
field.setAccessible(true);// 暴力反射 ,获取私有的信息;类中的成员变量为private,故必须进行此操作
try {
infoMap.put(field.getName(), Objects.requireNonNull(field.get(null)).toString());
Log.d(TAG, field.getName() + " : " + field.get(null));
} catch (IllegalArgumentException e) {
Log.e(TAG, "an IllegalArgumentException occured when collect reflect field info", e);
} catch (IllegalAccessException e) {
Log.e(TAG, "an IllegalAccessException occured when collect reflect field info", e);
}
}
}
/**
* 保存错误信息到文件中
*/
private void saveCrashInfoToFile(Throwable ex) throws IOException {
// 字符串流
StringBuilder stringBuffer = new StringBuilder();
// 获得设备信息
for (Map.Entry<String, String> entry : infoMap.entrySet()) {// 遍历map中的值
String key = entry.getKey();
String value = entry.getValue();
stringBuffer.append(key).append("=").append(value).append("\n");
}
// 获得错误信息
Writer writer = new StringWriter();// 这个writer下面还会用到所以需要它的实例
PrintWriter printWriter = new PrintWriter(writer);// 输出错误栈信息需要用到PrintWriter
ex.printStackTrace(printWriter);
Throwable cause = ex.getCause();
while (cause != null) {// 循环把所有的cause都输出到printWriter中
cause.printStackTrace(printWriter);
cause = ex.getCause();
}
printWriter.close();
String result = writer.toString();
stringBuffer.append(result);
stringBuffer.append("\n");
stringBuffer.append("==============================================================================================================");
File directory = context.getExternalFilesDir(FILE_TYPE);
if (directory != null) {
if (!directory.isDirectory()) {
directory.delete();
}
if (!directory.exists()) {
directory.mkdirs();
}
}
File file = new File(directory, new SimpleDateFormat("yyyyMMdd", Locale.getDefault()).format(new Date()) + ".txt");
if (!file.isFile()) {
file.delete();
}
if (!file.exists()) {
file.createNewFile();
}
try {
FileOutputStream fos = new FileOutputStream(file, true);
fos.write(stringBuffer.toString().getBytes());
fos.close();
} catch (FileNotFoundException e) {
Log.e(TAG, "an FileNotFoundException when write to sdcard", e);
} catch (IOException e) {
Log.e(TAG, "an IOException when write to sdcard", e);
}
file.getPath();
}
}

View File

@ -0,0 +1,136 @@
package com.yingmao.clash.util
import android.util.Log
import android.widget.Toast
import com.alibaba.sdk.android.oss.ClientException
import com.alibaba.sdk.android.oss.OSSClient
import com.alibaba.sdk.android.oss.ServiceException
import com.alibaba.sdk.android.oss.callback.OSSCompletedCallback
import com.alibaba.sdk.android.oss.common.auth.OSSStsTokenCredentialProvider
import com.alibaba.sdk.android.oss.internal.OSSAsyncTask
import com.alibaba.sdk.android.oss.model.PutObjectRequest
import com.alibaba.sdk.android.oss.model.PutObjectResult
import com.yingmao.clash.net.http.entity.ParamData
import java.io.File
object DataUtils {
/** 删除文件可以是文件或文件夹
* @param delFile 要删除的文件夹或文件名
* @return 删除成功返回true否则返回false
*/
fun deleteFile(delFile: String): Boolean {
val file = File(delFile)
if (!file.exists()) {
Log.e(
"--Method--",
"删除文件失败:\" + delFile + \"不存在!"
)
return false
} else {
return if (file.isFile) deleteSingleFile(delFile)
else deleteDirectory(delFile)
}
}
/** 删除单个文件
* @param 要删除的文件的文件名
* @return 单个文件删除成功返回true否则返回false
*/
private fun deleteSingleFile(filePath: String): Boolean {
val file = File(filePath)
// 如果文件路径所对应的文件存在,并且是一个文件,则直接删除
if (file.exists() && file.isFile) {
if (file.delete()) {
Log.e(
"--Method--",
"Copy_Delete.deleteSingleFile: 删除单个文件" + `filePath` + "成功!"
)
return true
} else {
Log.e(
"--Method--",
"删除单个文件" + `filePath` + "失败!",
)
return false
}
} else {
Log.e(
"--Method--",
"删除单个文件失败:" + `filePath` + "不存在!",
)
return false
}
}
/** 删除目录及目录下的文件
* @param filePath 要删除的目录的文件路径
* @return 目录删除成功返回true否则返回false
*/
private fun deleteDirectory(filePath: String): Boolean {
// 如果dir不以文件分隔符结尾自动添加文件分隔符
var filePath = filePath
if (!filePath.endsWith(File.separator)) filePath = filePath + File.separator
val dirFile = File(filePath)
// 如果dir对应的文件不存在或者不是一个目录则退出
if ((!dirFile.exists()) || (!dirFile.isDirectory)) {
Log.e(
"--Method--",
"删除目录失败:" + filePath + "不存在!"
)
return false
}
var flag = true
// 删除文件夹中的所有文件包括子目录
val files = dirFile.listFiles()
for (file in files) {
// 删除子文件
if (file.isFile) {
flag = deleteSingleFile(file.absolutePath)
if (!flag) break
} else if (file.isDirectory) {
flag = deleteDirectory(
file
.absolutePath
)
if (!flag) break
}
}
if (!flag) {
Log.e(
"--Method--",
"删除目录失败"
)
return false
}
// 删除当前目录
if (dirFile.delete()) {
Log.e("--Method--", "Copy_Delete.deleteDirectory: 删除目录" + filePath + "成功!")
return true
} else {
Log.e(
"--Method--",
"删除目录:" + filePath + "失败!"
)
return false
}
}
fun getAllFilePath(folderPath: String): MutableList<String> {
val fileList =mutableListOf<String>()
val file = File(folderPath)
val tempList = file.listFiles()
if (tempList != null) {
for (i in tempList.indices) {
if (tempList[i].isFile) {
Log.i("dirpath","文 件:" + tempList[i].name)
val fileName = folderPath+"/"+tempList[i].name
Log.i("getAllFilePath","fileName" +fileName)
if (fileName.endsWith(".txt")) { // 根据自己的需要进行类型筛选
fileList.add(fileName)
}
}
}
}
return fileList
}
}

View File

@ -0,0 +1,28 @@
package com.yingmao.clash.view.permissions
import android.content.pm.PackageManager
import androidx.fragment.app.Fragment
typealias PermissionCallback = (Boolean, List<String>) -> Unit
class InvisibleFragment : Fragment() {
private var callback: ((Boolean, List<String>) -> Unit)? = null
fun requestNow(cb: (Boolean, List<String>) -> Unit, vararg permissions: String) {
callback = cb
requestPermissions(permissions, 1)
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>, grantResults: IntArray
) {
if (requestCode == 1) {
val deniedList = ArrayList<String>()
for ((index, result) in grantResults.withIndex()) {
if (result != PackageManager.PERMISSION_GRANTED) {
deniedList.add(permissions[index])
}
}
val allGranted = deniedList.isEmpty()
callback?.let { it(allGranted, deniedList) }
}
}
}

View File

@ -0,0 +1,20 @@
package com.yingmao.clash.view.permissions
import androidx.fragment.app.FragmentActivity
object PermissionX {
private const val TAG = "InvisibleFragment"
fun request(activity: FragmentActivity, vararg permissions: String, callback:
PermissionCallback) {
val fragmentManager = activity.supportFragmentManager
val existedFragment = fragmentManager.findFragmentByTag(TAG)
val fragment = if (existedFragment != null) {
existedFragment as InvisibleFragment
} else {
val invisibleFragment = InvisibleFragment()
fragmentManager.beginTransaction().add(invisibleFragment, TAG).commitNow()
invisibleFragment
}
fragment.requestNow(callback, *permissions)
}
}

View File

@ -179,4 +179,16 @@ class MainAtyVM : PublicViewMode() {
) )
doAsync(HttpReqType.GetActivity) { httpApi.getActivity(params) } doAsync(HttpReqType.GetActivity) { httpApi.getActivity(params) }
} }
fun getUploadParameters() {
val httpApi = NetService.instances.createHttps(HttpApi::class.java)
val username =
MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_REMEMBER_USERNAME, "")!!
val phToken = MMKV.defaultMMKV().decodeString(BZYConstants.MMKV_KEY_PH_TOKEN, "")!!
val params = hashMapOf(
"phoneNumber" to username,
"phToken" to phToken,
)
doAsync(HttpReqType.UploadParameters) { httpApi.getUploadParameters(params) }
}
} }

View File

@ -19,7 +19,8 @@
<androidx.viewpager2.widget.ViewPager2 <androidx.viewpager2.widget.ViewPager2
android:id="@+id/container" android:id="@+id/container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" >
</androidx.viewpager2.widget.ViewPager2>
<ImageView <ImageView
android:id="@+id/iv_slider" android:id="@+id/iv_slider"