Commit 239af647 authored by wanglei's avatar wanglei

...ui

parent 801e9303
plugins {
alias(libs.plugins.androidApplication)
alias(libs.plugins.jetbrainsKotlinAndroid)
id 'com.google.gms.google-services'
id 'com.google.firebase.crashlytics'
}
android {
......@@ -30,6 +32,10 @@ android {
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.realse
// 设置是否要自动上传
firebaseCrashlytics {
mappingFileUploadEnabled true
}
}
}
compileOptions {
......@@ -45,6 +51,13 @@ android {
aidl true
}
}
gradle.taskGraph.whenReady {
tasks.each { task ->
if (task.name.contains("uploadCrashlyticsMappingFile")) {
task.enabled = false
}
}
}
dependencies {
......@@ -66,4 +79,19 @@ dependencies {
//网络
implementation group: 'com.google.code.gson', name: 'gson', version: '2.10.1'
//facebook
implementation("com.facebook.android:facebook-android-sdk:[8,9)")
//广告
implementation("com.google.android.gms:play-services-ads:23.1.0")
implementation 'com.google.ads.mediation:applovin:12.4.3.0'
implementation 'com.google.ads.mediation:facebook:6.17.0.0'
implementation 'com.google.ads.mediation:mintegral:16.7.21.0'
implementation 'com.google.ads.mediation:pangle:5.9.0.4.0'
//firebase
implementation platform('com.google.firebase:firebase-bom:32.3.1')
implementation 'com.google.firebase:firebase-analytics:21.6.2'
implementation("com.google.firebase:firebase-messaging")
}
\ No newline at end of file
{
"project_info": {
"project_number": "1032027169037",
"project_id": "easycleanerjunk",
"storage_bucket": "easycleanerjunk.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:1032027169037:android:c81c9c37c0534a2b2a9044",
"android_client_info": {
"package_name": "com.base.datarecovery"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyCqV4IqJ0QYV7TCMBMBEPepvP0JD6Lj_5k"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" >
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
......@@ -8,6 +8,7 @@
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<application
android:name=".MyApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
......@@ -16,25 +17,65 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.DataRecovery"
tools:targetApi="31" >
tools:targetApi="31">
<activity
android:name=".activity.FileScanResultActivity"
android:exported="false" />
<activity
android:name=".activity.FileRecoveryActivity"
android:exported="false" />
<activity
android:name=".activity.FileScanActivity"
android:exported="false" />
<activity
android:name=".MainActivity"
android:exported="true" >
android:name=".activity.SplashActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".activity.MainActivity"
android:exported="false"
android:launchMode="singleTop"
android:screenOrientation="portrait"
tools:ignore="DiscouragedApi,LockedOrientationActivity" />
<activity
android:name=".activity.FileRecoveredActivity"
android:exported="false"
android:launchMode="singleTop"
android:screenOrientation="portrait"
tools:ignore="DiscouragedApi,LockedOrientationActivity" />
<activity
android:name=".activity.FileScanResultActivity"
android:exported="false"
android:launchMode="singleTop"
android:screenOrientation="portrait"
tools:ignore="DiscouragedApi,LockedOrientationActivity" />
<activity
android:name=".activity.FileRecoveryActivity"
android:exported="false"
android:launchMode="singleTop"
android:screenOrientation="portrait"
tools:ignore="DiscouragedApi,LockedOrientationActivity" />
<activity
android:name=".activity.FileScanActivity"
android:exported="false"
android:launchMode="singleTop"
android:screenOrientation="portrait"
tools:ignore="DiscouragedApi,LockedOrientationActivity" />
<meta-data
android:name="com.google.android.gms.ads.flag.OPTIMIZE_INITIALIZATION"
android:value="true" />
<meta-data
android:name="com.google.android.gms.ads.flag.OPTIMIZE_AD_LOADING"
android:value="true" />
<meta-data
android:name="com.google.android.gms.ads.flag.NATIVE_AD_DEBUGGER_ENABLED"
android:value="false" />
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-3940256099942544~3347511713" />
<meta-data
android:name="com.facebook.sdk.ApplicationId"
android:value="@string/facebook_app_id" />
</application>
</manifest>
\ No newline at end of file
package com.base.datarecovery
import android.annotation.SuppressLint
import android.app.Activity
import android.os.Bundle
import com.base.datarecovery.bean.ConstObject.ifAgreePrivacy
import com.base.datarecovery.help.BaseApplication
import com.base.datarecovery.help.ConfigHelper
import com.base.datarecovery.utils.ActivityManagerUtils
import com.google.android.gms.ads.MobileAds
class MyApplication : BaseApplication() {
companion object {
@JvmField
var PAUSED_VALUE = 0
}
override fun init() {
initApp()
}
private fun initApp() {
if (ifAgreePrivacy) {
MobileAds.initialize(this) { initializationStatus ->
}
}
}
private var lastTimePause = 0L
private var lastTimeResume = 0L
private fun isHotLaunch(): Boolean {
if ((lastTimeResume - lastTimePause) > 1000) {
return true
}
return false
}
@SuppressLint("UnspecifiedRegisterReceiverFlag")
private fun initLifeListener() {
registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
private var count = 0
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {}
override fun onActivityStarted(activity: Activity) {
count++
lastTimeResume = System.currentTimeMillis()
if (count == 1 && isHotLaunch()) {
val topActivity: Activity? = ActivityManagerUtils.getInstance().topActivity
val flag = if (topActivity == null) {
true
} else {
ConfigHelper.noLoadingActivities.all { !topActivity.localClassName.contains(it, true) }
}
if (flag) {
// if (AdmobUtils.isOpenAdLoaded()) {
// AdmobUtils.showAppOpenAd(activity)
// } else {
// topActivity?.startActivity(
// Intent(
// topActivity,
// NewSplashActivity::class.java
// ).apply {
// putExtra("isHotLaunch", true)
// putExtra("type", -1)
// })
// }
}
}
}
override fun onActivityResumed(activity: Activity) {
PAUSED_VALUE = 1
}
override fun onActivityPaused(activity: Activity) {
PAUSED_VALUE = 2
lastTimePause = System.currentTimeMillis()
}
override fun onActivityStopped(activity: Activity) {
count--
}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
override fun onActivityDestroyed(activity: Activity) {}
})
}
}
\ No newline at end of file
package com.base.datarecovery.activity
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.base.datarecovery.R
import com.base.datarecovery.databinding.ActivityFileRecoveredBinding
import com.base.datarecovery.help.BaseActivity
class FileRecoveredActivity : BaseActivity<ActivityFileRecoveredBinding>() {
override val binding: ActivityFileRecoveredBinding by lazy {
ActivityFileRecoveredBinding.inflate(layoutInflater)
}
override fun initView() {
}
}
\ No newline at end of file
package com.base.datarecovery.activity
import android.annotation.SuppressLint
import android.content.Intent
import android.graphics.BitmapFactory
import android.graphics.Color
import android.os.Environment
import android.view.View
import android.widget.Toast
import androidx.activity.addCallback
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.base.datarecovery.R
import com.base.datarecovery.adapter.FileMediaColumnsAdapter
import com.base.datarecovery.adapter.RecoveryFilterAdapter
import com.base.datarecovery.bean.ConstObject
import com.base.datarecovery.bean.ConstObject.SCAN_DOCUMENTS
import com.base.datarecovery.bean.ConstObject.SCAN_PHOTOS
import com.base.datarecovery.bean.ConstObject.SCAN_VIDEOS
import com.base.datarecovery.bean.FolderBean
import com.base.datarecovery.bean.RecoveryBean
import com.base.datarecovery.bean.RecoveryFilterBean
......@@ -21,6 +27,7 @@ import com.base.datarecovery.utils.BarUtils
import com.base.datarecovery.utils.TimeUtils.isWithinOneMonth
import com.base.datarecovery.utils.TimeUtils.isWithinSixMonths
import com.base.datarecovery.utils.TimeUtils.isWithinTwentyFourMonths
import com.base.datarecovery.view.DialogViews.showRecoveringDialog
import com.google.gson.Gson
import java.io.File
......@@ -66,7 +73,7 @@ class FileRecoveryActivity : BaseActivity<ActivityFileRecoveryBinding>() {
folderBean = Gson().fromJson(json, FolderBean::class.java)
when (scanType) {
ConstObject.SCAN_PHOTOS -> {
SCAN_PHOTOS -> {
var size = 0
folderBean?.recoveryList?.forEach {
val bitmap = BitmapFactory.decodeFile(it.path)
......@@ -78,11 +85,11 @@ class FileRecoveryActivity : BaseActivity<ActivityFileRecoveryBinding>() {
binding.tvThumbnails.text = "Hide thumbnails (${size})"
}
ConstObject.SCAN_DOCUMENTS -> {
SCAN_DOCUMENTS -> {
binding.clThumbnails.visibility = View.GONE
}
ConstObject.SCAN_VIDEOS -> {
SCAN_VIDEOS -> {
binding.clThumbnails.visibility = View.GONE
}
......@@ -131,7 +138,31 @@ class FileRecoveryActivity : BaseActivity<ActivityFileRecoveryBinding>() {
binding.llLayout.setOnClickListener {
showFilter(it.id)
}
binding.tvRecover.setOnClickListener {
var dirType = ""
when (scanType) {
SCAN_PHOTOS -> dirType = "Photo"
SCAN_DOCUMENTS -> dirType = "Document"
SCAN_VIDEOS -> dirType = "Video"
}
val appName = this.resources.getString(R.string.app_name)
val appDir = File(Environment.getExternalStorageDirectory(), appName)
val dir = File(appDir, dirType)
if (!dir.exists()) {
dir.mkdirs()
}
beginRecover(dir)
}
}
private fun beginRecover(dir: File) {
val list = adapter.getSelectData().map { it.path }
showRecoveringDialog(lifecycleScope, list, dir, copyProgressAction = {
}, finish = {
adapter.toggleAllSelect(false)
startActivity(Intent(this, FileRecoveredActivity::class.java))
})
}
private fun showFilter(filter: Int) {
......
package com.base.datarecovery
package com.base.datarecovery.activity
import android.graphics.Color
import android.os.Bundle
......
package com.base.datarecovery.activity
import android.annotation.SuppressLint
import android.content.Intent
import android.graphics.Color
import android.net.Uri
import android.text.SpannableString
import android.text.Spanned
import android.text.style.UnderlineSpan
import android.view.View
import androidx.lifecycle.lifecycleScope
import com.base.datarecovery.ads.AdmobNativeUtils
import com.base.datarecovery.databinding.ActivitySplashBinding
import com.base.datarecovery.help.BaseActivity
import com.base.datarecovery.help.ConfigHelper
import com.base.datarecovery.utils.BarUtils
import com.base.datarecovery.utils.SPUtils
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlin.random.Random
@SuppressLint("CustomSplashScreen")
class SplashActivity : BaseActivity<ActivitySplashBinding>() {
private var job: Job? = null
private val progress = MutableSharedFlow<Int>()
private val progressFlow: SharedFlow<Int> = progress
private var oneClickStart: Boolean = false
var ifAgreePrivacy = false
get() {
return SPUtils.getInstance().getBoolean("ifAgreePrivacy", field)
}
set(value) {
field = value
SPUtils.getInstance().put("ifAgreePrivacy", value, true)
}
override val binding: ActivitySplashBinding by lazy {
ActivitySplashBinding.inflate(layoutInflater)
}
override fun initView() {
BarUtils.setStatusBarLightMode(this, true)
BarUtils.setStatusBarColor(this, Color.TRANSPARENT)
jumpNext()
if (ifAgreePrivacy) {
startProgress()
binding.llStart.visibility = View.GONE
binding.llProgress.visibility = View.VISIBLE
} else {
binding.llStart.visibility = View.VISIBLE
binding.llProgress.visibility = View.GONE
}
val spannableString = SpannableString("Privacy Policy")
spannableString.setSpan(
UnderlineSpan(),
0,
spannableString.length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
binding.idTvPrivacyPolicy.text = spannableString
binding.idTvPrivacyPolicy.setOnClickListener {
val intent = Intent(
Intent.ACTION_VIEW,
Uri.parse(ConfigHelper.privacyPolicy)
)
startActivity(intent)
}
AdmobNativeUtils.loadNativeAd()
}
private fun jumpNext() {
lifecycleScope.launch {
progressFlow.collectLatest {
if (it >= 100) {
job?.cancel()
startActivity(Intent(this@SplashActivity, MainActivity::class.java))
finish()
}
}
}
}
override fun initListener() {
binding.idTvStart.setOnClickListener {
if (oneClickStart) {
return@setOnClickListener
}
oneClickStart = true
ifAgreePrivacy = true
binding.llStart.visibility = View.GONE
binding.llProgress.visibility = View.VISIBLE
startProgress()
}
}
fun startProgress() = lifecycleScope.launch {
while (isActive) {
delay(Random.nextLong(50, 150))
val value = binding.pb.progress + Random.nextInt(3, 5)
binding.pb.setProgress(value, true)
progress.emit(value)
}
}
override fun onResume() {
super.onResume()
if (ifAgreePrivacy) {
job = startProgress()
}
}
override fun onPause() {
super.onPause()
job?.cancel()
}
}
\ No newline at end of file
......@@ -49,28 +49,27 @@ class FileFolderAdapter(
when (scanType) {
SCAN_PHOTOS, SCAN_VIDEOS -> {
LogEx.logDebug(TAG, "scanType=SCAN_PHOTOS or SCAN_VIDEOS")
val binding = ItemFolderRecoveryBinding.bind(holder.itemView)
runCatching {
val options = RequestOptions().transform(CenterCrop(), RoundedCorners(context.dpToPx(10)))
val image1 = bean.recoveryList[0].path
val request1 = Glide.with(context).load(image1).apply(options)
if (scanType == SCAN_VIDEOS) {
request1.error(R.drawable.videotu)
request1.error(R.mipmap.videotu)
}
request1.into(binding.iv1)
val image2 = bean.recoveryList[1].path
val request2 = Glide.with(context).load(image2).apply(options)
if (scanType == SCAN_VIDEOS) {
request2.error(R.drawable.videotu)
request2.error(R.mipmap.videotu)
}
request2.into(binding.iv2)
val image3 = bean.recoveryList[2].path
val request3 = Glide.with(context).load(image3).apply(options)
if (scanType == SCAN_VIDEOS) {
request3.error(R.drawable.videotu)
request3.error(R.mipmap.videotu)
}
request3.into(binding.iv3)
}
......
......@@ -175,10 +175,18 @@ class FileMediaColumnsAdapter(
it.recoveryList.forEach { bean -> bean.isSelect = selected }
}
notifyDataSetChanged()
val all = beanList.all { it.isSelect }
val size = beanList.sumOf { it.recoveryList.filter { bean -> bean.isSelect }.size }
select.invoke(all, size)
}
@SuppressLint("NotifyDataSetChanged")
fun changeColumns(columns: Int) {
this.columns = columns
notifyDataSetChanged()
}
fun getSelectData(): List<RecoveryBean> {
return beanList.flatMap { it.recoveryList }.filter { it.isSelect }
}
}
\ No newline at end of file
package com.base.datarecovery.ads;
import android.content.SharedPreferences;
import com.base.datarecovery.help.BaseApplication;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
public class AdDisplayUtils {
private static final int DEFAULT_MAX_AD_DISPLAY_COUNT = 45; // 总广告展示次数限制默认值
private static final int DEFAULT_MAX_AD_CLICK_COUNT = 10; // 单个广告点击次数限制默认值
private static final String AD_PREFS_NAME = "ad_prefs"; // SharedPreferences 名称
private static final String AD_DISPLAY_COUNT_KEY = "ad_display_count"; // 广告展示次数的键
private static final String AD_CLICK_COUNT_KEY = "ad_click_count"; // 广告点击次数的键
private static final String MAX_AD_DISPLAY_COUNT_KEY = "max_ad_display_count"; // 总广告展示次数限制的键
private static final String MAX_AD_CLICK_COUNT_KEY = "max_ad_click_count"; // 单个广告点击次数限制的键
private static AdDisplayUtils instance; // 单例对象
private int adDisplayCount = 0; // 当前广告展示次数
private int adClickCount = 0; // 当前广告点击次数
private int maxAdDisplayCount; // 总广告展示次数限制
private int maxAdClickCount; // 单个广告点击次数限制
private String currentDate; // 当前日期
private AdDisplayUtils() {
currentDate = getCurrentDate();
SharedPreferences prefs = BaseApplication.context.getSharedPreferences(AD_PREFS_NAME, 0);
maxAdDisplayCount = prefs.getInt(MAX_AD_DISPLAY_COUNT_KEY, DEFAULT_MAX_AD_DISPLAY_COUNT);
maxAdClickCount = prefs.getInt(MAX_AD_CLICK_COUNT_KEY, DEFAULT_MAX_AD_CLICK_COUNT);
adDisplayCount = prefs.getInt(getAdDisplayCountKey(), 0);
adClickCount = prefs.getInt(getAdClickCountKey(), 0);
}
public static synchronized AdDisplayUtils getInstance() {
if (instance == null) {
instance = new AdDisplayUtils();
}
return instance;
}
public boolean shouldDisplayAd() {
return adDisplayCount < getMaxAdDisplayCount();
}
public boolean shouldIncrementClickCount() {
return adClickCount < getMaxAdClickCount();
}
public boolean shouldShowAd() {
return shouldDisplayAd() || shouldIncrementClickCount();
}
public void incrementAdDisplayCount() {
if (!currentDate.equals(getCurrentDate())) {
currentDate = getCurrentDate();
adDisplayCount = 0;
}
adDisplayCount++;
saveAdDisplayCount();
}
public void incrementAdClickCount() {
if (!currentDate.equals(getCurrentDate())) {
currentDate = getCurrentDate();
adClickCount = 0;
}
adClickCount++;
saveAdClickCount();
}
public void setAdClickCount(int s) {
if (!currentDate.equals(getCurrentDate())) {
currentDate = getCurrentDate();
adClickCount = 0;
}
adClickCount = s;
saveAdClickCount();
}
private String getCurrentDate() {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
Date currentDate = Calendar.getInstance().getTime();
return dateFormat.format(currentDate);
}
private String getAdDisplayCountKey() {
return AD_DISPLAY_COUNT_KEY + "_" + currentDate;
}
private String getAdClickCountKey() {
return AD_CLICK_COUNT_KEY + "_" + currentDate;
}
private void saveAdDisplayCount() {
SharedPreferences prefs = BaseApplication.context.getSharedPreferences(AD_PREFS_NAME, 0);
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(getAdDisplayCountKey(), adDisplayCount);
editor.apply();
}
private void saveAdClickCount() {
SharedPreferences prefs = BaseApplication.context.getSharedPreferences(AD_PREFS_NAME, 0);
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(getAdClickCountKey(), adClickCount);
editor.apply();
}
private int getMaxAdDisplayCount() {
return maxAdDisplayCount;
}
public void setMaxAdDisplayCount(int maxAdDisplayCount) {
this.maxAdDisplayCount = maxAdDisplayCount;
saveMaxAdDisplayCount();
}
public int getMaxAdClickCount() {
return maxAdClickCount;
}
public void setMaxAdClickCount(int maxAdClickCount) {
this.maxAdClickCount = maxAdClickCount;
saveMaxAdClickCount();
}
private void saveMaxAdDisplayCount() {
SharedPreferences prefs = BaseApplication.context.getSharedPreferences(AD_PREFS_NAME, 0);
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(MAX_AD_DISPLAY_COUNT_KEY, maxAdDisplayCount);
editor.apply();
}
private void saveMaxAdClickCount() {
SharedPreferences prefs = BaseApplication.context.getSharedPreferences(AD_PREFS_NAME, 0);
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(MAX_AD_CLICK_COUNT_KEY, maxAdClickCount);
editor.apply();
}
}
package com.base.datarecovery.ads
import com.base.datarecovery.utils.ActivityManagerUtils
object AdmobCommonUtils {
private var lastAd: Any? = null
private var maxMultiClick = 4
private var multiClick = 0
fun isMultiClick(currentAd: Any?) {
if (currentAd == null) {
return
}
if (lastAd == currentAd) {
multiClick++
if (multiClick >= maxMultiClick) {
AdDisplayUtils.getInstance()
.setAdClickCount(AdDisplayUtils.getInstance().maxAdClickCount)
ActivityManagerUtils.getInstance().finishAllActivity()
return
}
} else {
multiClick = 0
}
lastAd = currentAd
}
}
\ No newline at end of file
This diff is collapsed.
package com.base.datarecovery.ads
import android.app.Activity
import android.view.ViewGroup
import androidx.core.view.isVisible
import com.base.datarecovery.ads.AdmobCommonUtils.isMultiClick
import com.base.datarecovery.ads.AdmobEvent.clickAd
import com.base.datarecovery.ads.AdmobEvent.pullAd
import com.base.datarecovery.ads.AdmobEvent.showAd
import com.base.datarecovery.help.BaseApplication
import com.base.datarecovery.help.ConfigHelper
import com.google.android.gms.ads.AdListener
import com.google.android.gms.ads.AdLoader
import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.LoadAdError
import com.google.android.gms.ads.nativead.NativeAd
import org.json.JSONObject
import java.util.UUID
object AdmobNativeUtils {
private var nativeAd: NativeAd? = null
private var isLoading = false
private var nativeLoadTime = Long.MAX_VALUE
private var loadingListener: (() -> Unit)? = null
private val mRequest = AdRequest.Builder().build()
fun loadNativeAd() {
if (nativeAd != null) {
return
}
if (isLoading) {
return
}
isLoading = true
if (!AdDisplayUtils.getInstance().shouldShowAd()) {
return
}
val reqId = UUID.randomUUID().toString()
val obj = JSONObject()
obj.put("req_id", reqId)
obj.put("ad_type", "nativeAd")
val adLoader = AdLoader.Builder(
BaseApplication.context, ConfigHelper.nativeAdmobId
).forNativeAd {
nativeLoadTime = System.currentTimeMillis()
nativeAd = it
isLoading = false
loadingListener?.invoke()
pullAd(it.responseInfo, "nativeAd", reqId = reqId)
it.setOnPaidEventListener(AdmobEvent.EventOnPaidEventListener(it))
}.withAdListener(object : AdListener() {
override fun onAdClicked() {
clickAd(nativeAd?.responseInfo, "nativeAd")
isMultiClick(nativeAd)
}
override fun onAdFailedToLoad(p0: LoadAdError) {
nativeAd = null
isLoading = false
pullAd(p0.responseInfo, "nativeAd", p0.message, reqId = reqId)
// Log.e("MXL", "NativeAdFailedToLoad: " + p0.message)
}
}).build()
adLoader.loadAd(mRequest)
}
fun showNativeAd(activity: Activity?, parent: ViewGroup) {
val obj = JSONObject()
obj.put("ad_unit", "NativeAd")
// EventUtils.event("ad_prepare_show", ext = obj)
if (!AdDisplayUtils.getInstance().shouldShowAd()) {
return
}
loadingListener = {
if (System.currentTimeMillis() - nativeLoadTime <= 1000 * 60 * 60) {
nativeAd?.let {
NativeView(parent.context).run {
parent.removeAllViews()
setNativeAd(it)
parent.addView(this)
parent.isVisible = true
showAd(nativeAd?.responseInfo, "nativeAd", activity)
}
}
}
nativeAd = null
loadingListener = null
loadNativeAd()
}
if (nativeAd == null) {
loadNativeAd()
val obj = JSONObject()
obj.put("reason", "no_ad")
obj.put("ad_unit", "nativeAd")
// EventUtils.event("ad_show_error", ext = obj)
} else {
loadingListener?.invoke()
}
}
}
\ No newline at end of file
package com.base.datarecovery.ads
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import com.base.datarecovery.R
import com.google.android.gms.ads.nativead.NativeAd
import com.google.android.gms.ads.nativead.NativeAdView
class NativeView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : FrameLayout(context, attrs) {
init {
layoutParams = LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
}
fun setNativeAd(nativeAd: NativeAd?) {
nativeAd ?: return
val adView = LayoutInflater.from(context)
.inflate(R.layout.layout_native, this, false) as NativeAdView
adView.mediaView = adView.findViewById(R.id.ad_media)
adView.headlineView = adView.findViewById(R.id.ad_headline)
adView.bodyView = adView.findViewById(R.id.ad_body)
adView.callToActionView = adView.findViewById(R.id.ad_call_to_action)
adView.iconView = adView.findViewById(R.id.ad_app_icon)
(adView.headlineView as TextView?)?.text = nativeAd.headline
adView.mediaView!!.mediaContent = nativeAd.mediaContent
if (nativeAd.body == null) {
adView.bodyView!!.visibility = View.INVISIBLE
} else {
adView.bodyView!!.visibility = View.VISIBLE
(adView.bodyView as TextView?)?.text = nativeAd.body
}
if (nativeAd.callToAction == null) {
adView.callToActionView!!.visibility = View.INVISIBLE
} else {
adView.callToActionView!!.visibility = View.VISIBLE
(adView.callToActionView as Button?)?.text = nativeAd.callToAction
}
if (nativeAd.icon == null) {
adView.iconView!!.visibility = View.GONE
} else {
(adView.iconView as ImageView?)?.setImageDrawable(
nativeAd.icon!!.drawable
)
adView.iconView!!.visibility = View.VISIBLE
}
adView.setNativeAd(nativeAd)
removeAllViews()
addView(adView)
}
}
\ No newline at end of file
package com.base.datarecovery.bean
import com.base.datarecovery.utils.SPUtils
object ConstObject {
const val SCAN_PHOTOS = 1
const val SCAN_DOCUMENTS = 2
const val SCAN_VIDEOS = 3
var ifAgreePrivacy = false
get() {
return SPUtils.getInstance().getBoolean("ifAgreePrivacy", field)
}
set(value) {
field = value
SPUtils.getInstance().put("ifAgreePrivacy", value, true)
}
}
\ No newline at end of file
package com.base.datarecovery.fragment
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.content.Intent
import android.view.View
import android.view.animation.AccelerateDecelerateInterpolator
import com.base.datarecovery.activity.FileScanActivity
import com.base.datarecovery.ads.AdmobNativeUtils
import com.base.datarecovery.bean.ConstObject.SCAN_DOCUMENTS
import com.base.datarecovery.bean.ConstObject.SCAN_PHOTOS
import com.base.datarecovery.bean.ConstObject.SCAN_VIDEOS
......@@ -13,28 +19,68 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
override val binding: FragmentHomeBinding by lazy {
FragmentHomeBinding.inflate(layoutInflater)
}
private lateinit var animatorSet: AnimatorSet
override fun setView() {
animatorSet = createHeartbeatAnimation(binding.flScan)
AdmobNativeUtils.showNativeAd(requireActivity(), binding.flAd)
}
override fun setListener() {
binding.llRyPhotos.setOnClickListener {
binding.flRyPhoto.setOnClickListener {
startActivity(Intent(requireContext(), FileScanActivity::class.java).apply {
putExtra("Type", SCAN_PHOTOS)
})
}
binding.llRyDocuments.setOnClickListener {
binding.flRyVideo.setOnClickListener {
startActivity(Intent(requireContext(), FileScanActivity::class.java).apply {
putExtra("Type", SCAN_DOCUMENTS)
putExtra("Type", SCAN_VIDEOS)
})
}
binding.llRyVideos.setOnClickListener {
binding.cardRyDocument.setOnClickListener {
startActivity(Intent(requireContext(), FileScanActivity::class.java).apply {
putExtra("Type", SCAN_VIDEOS)
putExtra("Type", SCAN_DOCUMENTS)
})
}
}
@SuppressLint("Recycle")
fun createHeartbeatAnimation(view: View): AnimatorSet {
// 创建放大动画
val scaleUpAnimator = ObjectAnimator.ofFloat(view, "scaleX", 1f, 1.1f)
scaleUpAnimator.setDuration(500)
scaleUpAnimator.interpolator = AccelerateDecelerateInterpolator()
scaleUpAnimator.repeatCount = ObjectAnimator.INFINITE
scaleUpAnimator.repeatMode = ObjectAnimator.REVERSE
// 创建缩小动画
val scaleDownAnimator = ObjectAnimator.ofFloat(view, "scaleY", 1.1f, 1f)
scaleDownAnimator.setDuration(500)
scaleDownAnimator.interpolator = AccelerateDecelerateInterpolator()
scaleDownAnimator.setStartDelay(500) // 延迟开始,以便与放大动画同步结束
scaleDownAnimator.repeatCount = ObjectAnimator.INFINITE
scaleDownAnimator.repeatMode = ObjectAnimator.REVERSE
// 组合放大和缩小动画
val animatorSet = AnimatorSet()
animatorSet.play(scaleUpAnimator).with(scaleDownAnimator)
// 启动动画
animatorSet.start()
return animatorSet
}
override fun onPause() {
super.onPause()
animatorSet.pause()
}
override fun onResume() {
super.onResume()
animatorSet.resume()
}
}
\ No newline at end of file
......@@ -5,9 +5,8 @@ import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.viewbinding.ViewBinding
import com.base.datarecovery.MainActivity
import com.base.datarecovery.activity.MainActivity
import com.base.datarecovery.utils.ActivityManagerUtils
import com.base.datarecovery.utils.BarUtils
abstract class BaseActivity<T : ViewBinding> : AppCompatActivity() {
......
package com.base.datarecovery.help
import android.app.Application
abstract class BaseApplication : Application() {
companion object {
lateinit var context: BaseApplication
}
final override fun onCreate() {
super.onCreate()
context = this
init()
}
abstract fun init()
}
\ No newline at end of file
package com.base.datarecovery.help
import com.base.datarecovery.activity.SplashActivity
import com.base.datarecovery.utils.SPUtils
object ConfigHelper {
const val privacyPolicy = "https://sites.google.com/view/easy-cleannow/easy-clean"
// 域名
const val eventUrl = "https://rp.easyfilemanager.xyz"
// const val apiUrl = "https://api.easyfilemanager.xyz"
// admob广告id
const val openAdmobId = "/6499/example/app-open"
const val interAdmobId = "ca-app-pub-3940256099942544/1033173712"
const val nativeAdmobId = "ca-app-pub-3940256099942544/2247696110"
// 正式包名
const val packageName = "com.kk.junkcleaner.easy.zxkk"
val noLoadingActivities = listOf(
"full", // 过滤全屏广告
"adActivity",
SplashActivity::class.java.simpleName
// 返回前台时不跳转启动页的 activity
)
}
\ No newline at end of file
This diff is collapsed.
......@@ -6,11 +6,29 @@ import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.view.LayoutInflater
import android.view.View
import androidx.lifecycle.LifecycleCoroutineScope
import com.base.datarecovery.R
import com.base.datarecovery.databinding.DialogPermissonOpenBinding
import com.base.datarecovery.databinding.DialogRecoveringBinding
import com.base.datarecovery.utils.LogEx
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import java.io.File
import kotlin.random.Random
object DialogViews {
private val TAG = "DialogViews"
@SuppressLint("SetTextI18n")
fun Context.showGerPermission(
tittle: String? = null,
......@@ -42,4 +60,79 @@ object DialogViews {
dialog.show()
return dialog
}
@SuppressLint("SetTextI18n")
fun Context.showRecoveringDialog(
lifecycleScope: LifecycleCoroutineScope,
list: List<String>,
dir: File,
copyProgressAction: () -> Unit,
finish: () -> Unit
) {
val dialog = BottomSheetDialog(this)
val binding = DialogRecoveringBinding.inflate(LayoutInflater.from(this))
dialog.setContentView(binding.root)
dialog.setCanceledOnTouchOutside(false)
val mutableSharedFlow = MutableSharedFlow<Int>(
replay = 5,//当新的订阅者Collect时,发送几个已经发送过的数据给它
extraBufferCapacity = 5,//减去replay,MutableSharedFlow还缓存多少数据,缓冲池容量 = replay + extraBufferCapacity
onBufferOverflow = BufferOverflow.SUSPEND//缓存策略,三种 丢掉最新值、丢掉最旧值和挂起
)
val sharedFlow: SharedFlow<Int> = mutableSharedFlow
val parentView = binding.root.parent as View
val behavior = BottomSheetBehavior.from(parentView)
//展开
behavior.state = BottomSheetBehavior.STATE_EXPANDED
// 设置禁止通过拖动来隐藏
behavior.isHideable = false
// 禁止点击外部区域关闭
dialog.setOnCancelListener { dialogInterface ->
dialogInterface.cancel() // 这里可以处理点击外部区域的逻辑
}
lifecycleScope.launch(Dispatchers.IO) {
val arrayList = arrayListOf<String>().apply {
addAll(list)
}
var index = 0
while (arrayList.isNotEmpty()) {
val path = arrayList[0]
arrayList.removeAt(0)
val file = File(path)
runCatching {
val recoveryFile = File(dir, file.name)
file.copyTo(recoveryFile, true)
}
mutableSharedFlow.emit(index + 1)
index++
delay(Random.nextLong(500, 1500))
}
mutableSharedFlow.emit(-1)
}
lifecycleScope.launch(Dispatchers.Main) {
sharedFlow.collectLatest {
LogEx.logDebug(TAG, "Flow $it")
if (it == -1) {
finish.invoke()
dialog.dismiss()
cancel()
} else {
val process = it.toFloat() / list.size.toFloat()
if (process > 0.3) {
copyProgressAction.invoke()
}
binding.tvNumber.text = "${it + 1}/${list.size}"
}
}
}
dialog.show()
}
}
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#577DFD" />
<corners
android:topLeftRadius="20dp"
android:topRightRadius="20dp" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<stroke
android:width="1px"
android:color="#FF3835" />
<corners android:radius="6dp" />
</shape>
</item>
<item
android:id="@android:id/progress"
android:bottom="3dp"
android:end="3dp"
android:start="3dp"
android:top="3dp">
<scale android:scaleWidth="100%">
<shape>
<corners android:radius="5dp" />
<solid android:color="#FF3835" />
</shape>
</scale>
</item>
</layer-list>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/white"/>
<!-- <item android:drawable="@drawable/splash_bg" />-->
<item
android:top="130dp"
android:gravity="top|center_horizontal">
<bitmap
android:src="@mipmap/qdylogo" />
</item>
</layer-list>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activity.FileRecoveredActivity">
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
......@@ -178,7 +178,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:src="@drawable/b_circle_selector"
android:src="@drawable/bg_circle_selector"
tools:ignore="ContentDescription" />
<TextView
......
......@@ -5,7 +5,7 @@
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
tools:context=".activity.MainActivity">
<fragment
android:id="@+id/fragment"
......
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/splash_bp"
android:gravity="center_horizontal"
android:orientation="vertical">
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="2" />
<LinearLayout
android:id="@+id/ll_progress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="49dp"
android:gravity="center_horizontal"
android:orientation="vertical"
android:visibility="gone">
<ProgressBar
android:id="@+id/pb"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="15dp"
android:layout_marginHorizontal="32dp"
android:layout_marginTop="5dp"
android:max="100"
android:progressDrawable="@drawable/shape_splash_s"
tools:progress="50" />
<TextView
android:id="@+id/tv_load"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="18dp"
android:text="Loading..."
android:textColor="#000000"
android:textSize="15sp"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/tv_ad_des"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="42dp"
android:text="This process may involve ad."
android:textColor="#000000"
android:textSize="15sp"
tools:ignore="HardcodedText" />
</LinearLayout>
<LinearLayout
android:id="@+id/ll_start"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="4dp"
android:text="By continuing you are agreeing to the"
android:textColor="#676767"
android:textSize="14sp"
tools:ignore="HardcodedText" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=" &amp; "
android:visibility="gone"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/id_tv_privacy_policy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Privacy Policy"
android:textColor="#676767"
android:textSize="14sp"
tools:ignore="HardcodedText" />
</LinearLayout>
<TextView
android:id="@+id/id_tv_start"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginHorizontal="40dp"
android:layout_marginBottom="49dp"
android:background="#577CFB"
android:gravity="center"
android:text="START TO USE"
android:textColor="@color/white"
android:textSize="18sp"
android:textStyle="bold"
android:visibility="visible"
tools:ignore="HardcodedText" />
</LinearLayout>
</androidx.appcompat.widget.LinearLayoutCompat>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_ss"
android:orientation="vertical">
<TextView
android:id="@+id/tv_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="18dp"
android:layout_marginTop="40dp"
android:textColor="@color/white"
android:textSize="45sp"
android:textStyle="bold"
tools:text="0/1" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="18dp"
android:layout_marginTop="25dp"
android:text="Recovering..."
android:textColor="@color/white"
android:textSize="25sp"
tools:ignore="HardcodedText" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="18dp"
android:layout_marginTop="15dp"
android:text="It may take a few seconds to recover the file(s), please wait.."
android:textColor="@color/white"
android:textSize="15sp"
tools:ignore="HardcodedText" />
<FrameLayout
android:id="@+id/fl_ad"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="26dp" />
</LinearLayout>
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
......@@ -38,7 +38,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginEnd="5dp"
android:src="@drawable/b_circle_selector"
android:src="@drawable/bg_circle_selector"
tools:ignore="ContentDescription" />
<TextView
......
......@@ -23,7 +23,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:src="@drawable/b_circle_border_selector"
android:src="@drawable/bg_circle_border_selector"
tools:ignore="ContentDescription" />
</FrameLayout>
......
......@@ -23,7 +23,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:src="@drawable/b_circle_border_selector"
android:src="@drawable/bg_circle_border_selector"
tools:ignore="ContentDescription" />
</FrameLayout>
......
......@@ -23,7 +23,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:src="@drawable/b_circle_border_selector"
android:src="@drawable/bg_circle_border_selector"
tools:ignore="ContentDescription" />
</FrameLayout>
......
<com.google.android.gms.ads.nativead.NativeAdView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="#D9D9D9"
android:minHeight="50dp"
android:paddingHorizontal="15dp"
android:paddingVertical="10dp"
tools:ignore="DisableBaselineAlignment">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/ad_headline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="2"
android:textColor="@color/black"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/ad_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:layout_marginRight="10dp"
android:ellipsize="end"
android:lines="2"
android:textColor="@color/black"
android:textSize="12sp"
tools:ignore="RtlHardcoded" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/ad_app_icon"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center_vertical"
tools:ignore="ContentDescription" />
<Button
android:id="@+id/ad_call_to_action"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginHorizontal="5dp"
android:backgroundTint="@color/black"
android:gravity="center"
android:textColor="@color/white"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="match_parent">
<com.google.android.gms.ads.nativead.MediaView
android:id="@+id/ad_media"
android:layout_width="160dp"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:background="@color/black"
android:paddingStart="3dp"
android:paddingTop="2dp"
android:paddingEnd="3dp"
android:paddingBottom="2dp"
android:text="Ad"
android:textColor="@color/white"
android:textSize="12sp"
tools:ignore="HardcodedText" />
</FrameLayout>
</LinearLayout>
</com.google.android.gms.ads.nativead.NativeAdView>
\ No newline at end of file
......@@ -2,4 +2,5 @@
<string name="app_name">Data Recovery</string>
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string>
<string name="facebook_app_id">11</string>
</resources>
\ No newline at end of file
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
dependencies {
classpath 'com.google.gms:google-services:4.3.15'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.5'
}
}
plugins {
alias(libs.plugins.androidApplication) apply false
alias(libs.plugins.androidApplication) apply false
alias(libs.plugins.jetbrainsKotlinAndroid) apply false
}
\ No newline at end of file
......@@ -16,6 +16,11 @@ dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url 'https://jitpack.io' }
maven { url "https://android-sdk.is.com" }
maven { url "https://dl-maven-android.mintegral.com/repository/mbridge_android_sdk_oversea" }
maven { url "https://artifact.bytedance.com/repository/pangle" }
maven { url "https://s01.oss.sonatype.org/content/groups/public" }
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment