diff --git a/app/src/main/assets/taskConfig.json b/app/src/main/assets/taskConfig.json new file mode 100644 index 0000000..488bc60 --- /dev/null +++ b/app/src/main/assets/taskConfig.json @@ -0,0 +1,130 @@ +{ + "task_module_config": { + "basic_info": { + "module_name": "任务中心", + "page_path": "底部第二个页签", + "layout_style": "上下滑动长页面", + "currency_display": ["金币账户", "巴西雷亚尔现金账户"] + }, + "task_categories": [ + { + "category_id": "newbie_task", + "category_name": "新手任务(Missão Para Iniciantes)", + "valid_period": "注册后7天内", + "display_priority": 1, + "tasks": [ + { + "task_id": "newbie_first_withdraw", + "task_name": "完成第一次提现", + "task_desc": "完成首次提现操作(0.1 BRL门槛),即可获得100金币奖励", + "target_action": "用户完成首次提现流程(含绑定Pix账户、观看广告)", + "reward_type": "金币", + "reward_value": 100, + "is_one_time": true, + "status": "active" + }, + { + "task_id": "newbie_push_notify", + "task_name": "开启消息推送", + "task_desc": "启用APP消息推送权限,实时获取任务更新与奖励通知", + "target_action": "用户授权APP系统推送通知", + "reward_type": "金币", + "reward_value": 300, + "is_one_time": true, + "status": "active" + }, + { + "task_id": "newbie_join_discord", + "task_name": "加入Discord社区", + "task_desc": "通过APP内链接加入官方Discord社区,获取专属活动与客服支持", + "target_action": "用户通过指定链接成功加入Discord社区", + "reward_type": "金币", + "reward_value": 200, + "is_one_time": true, + "status": "active" + }, + { + "task_id": "newbie_7day_checkin", + "task_name": "7日连续签到", + "task_desc": "连续7天完成签到,奖励逐级递增,第7天最高可得800金币", + "target_action": "用户每日完成签到操作(支持广告补签)", + "reward_type": "金币", + "reward_details": [ + {"day": 1, "value": 100}, + {"day": 2, "value": 300}, + {"day": 3, "value": 300}, + {"day": 4, "value": 500}, + {"day": 5, "value": 300}, + {"day": 6, "value": 300}, + {"day": 7, "value": 800} + ], + "support_makeup": true, + "makeup_method": "观看15-30秒激励视频", + "is_one_time": true, + "status": "active" + } + ] + }, + { + "category_id": "daily_task", + "category_name": "日常任务(Missão Diária)", + "valid_period": "每日凌晨00:00重置", + "display_priority": 2, + "tasks": [ + { + "task_id": "daily_checkin", + "task_name": "每日签到", + "task_desc": "每日签到可获金币,7天为一周期,奖励循环递增(支持双倍广告奖励)", + "target_action": "用户当日完成签到操作", + "reward_type": "金币", + "reward_details": [ + {"day": 1, "value": 100}, + {"day": 2, "value": 300}, + {"day": 3, "value": 300}, + {"day": 4, "value": 500}, + {"day": 5, "value": 300}, + {"day": 6, "value": 300}, + {"day": 7, "value": 500} + ], + "double_reward_method": "观看15-30秒激励视频", + "support_makeup": true, + "makeup_method": "观看15-30秒激励视频", + "is_one_time": false, + "status": "active" + }, + { + "task_id": "daily_video_ladder", + "task_name": "阶梯观看视频", + "task_desc": "累计观看指定数量短视频,分3档领取奖励(与基础观看收益叠加)", + "target_action": "用户累计观看短视频", + "reward_type": "金币", + "reward_details": [ + {"target_count": 10, "value": 100}, + {"target_count": 20, "value": 150}, + {"target_count": 30, "value": 200} + ], + "reward_stackable": true, + "is_one_time": false, + "status": "active" + }, + { + "task_id": "daily_ad_ladder", + "task_name": "阶梯观看激励视频", + "task_desc": "累计观看指定数量激励视频,分3档领取奖励(每日上限10次)", + "target_action": "用户累计观看激励视频", + "reward_type": "金币", + "reward_details": [ + {"target_count": 1, "value": 100}, + {"target_count": 5, "value": 150}, + {"target_count": 10, "value": 200} + ], + "daily_limit": 10, + "reward_stackable": true, + "is_one_time": false, + "status": "active" + } + ] + } + ] + } +} diff --git a/app/src/main/java/com/gamedog/vididin/VidiDinApp.kt b/app/src/main/java/com/gamedog/vididin/VidiDinApp.kt index bc3a357..e9e9b4d 100644 --- a/app/src/main/java/com/gamedog/vididin/VidiDinApp.kt +++ b/app/src/main/java/com/gamedog/vididin/VidiDinApp.kt @@ -1,10 +1,23 @@ package com.gamedog.vididin import com.ama.core.architecture.BaseApp +import com.gamedog.vididin.manager.TaskManager import dagger.hilt.android.HiltAndroidApp @HiltAndroidApp class VidiDinApp : BaseApp() { + init { + + } + + override fun onCreate() { + super.onCreate() + initManagers() + } + + private fun initManagers() { + TaskManager.instance() + } } \ No newline at end of file diff --git a/app/src/main/java/com/gamedog/vididin/main/fragments/TasksFragment.kt b/app/src/main/java/com/gamedog/vididin/main/fragments/TasksFragment.kt index ffbf95c..c98bfae 100644 --- a/app/src/main/java/com/gamedog/vididin/main/fragments/TasksFragment.kt +++ b/app/src/main/java/com/gamedog/vididin/main/fragments/TasksFragment.kt @@ -6,14 +6,18 @@ import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.updatePadding import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope import com.ama.core.architecture.appBase.AppViewsFragment import com.ama.core.architecture.appBase.OnFragmentBackgroundListener import com.ama.core.architecture.util.SpUtil import com.ama.core.architecture.util.setOnClickBatch import com.ama.core.architecture.util.setStatusBarDarkFont import com.gamedog.vididin.main.fragments.task.DailySignDialog +import com.gamedog.vididin.main.fragments.task.TaskBean import com.gamedog.vididin.main.interfaces.OnTabClickAgainListener +import com.gamedog.vididin.manager.TaskManager import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch import kotlin.getValue import com.gamedog.vididin.databinding.VididinappFeatureMessageFragmentMessageBinding as ViewBinding import com.gamedog.vididin.main.fragments.home.YoutubeViewModel as ViewModel @@ -24,6 +28,7 @@ import com.gamedog.vididin.main.fragments.home.YoutubeUiState as UiState @AndroidEntryPoint class TasksFragment : AppViewsFragment(), OnTabClickAgainListener, OnFragmentBackgroundListener { + private var mTaskConfig: TaskBean? = null override val mViewModel: ViewModel by viewModels() override var isBackgroundBright: Boolean = true @@ -42,6 +47,7 @@ class TasksFragment : AppViewsFragment(), OnTab } } + override fun ViewBinding.initViews() { with(binding) { setOnClickBatch(ivGotoDailySign) { @@ -52,6 +58,10 @@ class TasksFragment : AppViewsFragment(), OnTab } } } + + lifecycleScope.launch { + mTaskConfig = TaskManager.instance().getTaskConfig() + } } override fun ViewBinding.initListeners() { diff --git a/app/src/main/java/com/gamedog/vididin/main/fragments/task/TaskBean.kt b/app/src/main/java/com/gamedog/vididin/main/fragments/task/TaskBean.kt new file mode 100644 index 0000000..cdbcf57 --- /dev/null +++ b/app/src/main/java/com/gamedog/vididin/main/fragments/task/TaskBean.kt @@ -0,0 +1,48 @@ +package com.gamedog.vididin.main.fragments.task + +data class TaskBean( + val task_module_config: TaskModuleConfig +) + +data class TaskModuleConfig( + val basic_info: BasicInfo, + val task_categories: List +) + +data class BasicInfo( + val currency_display: List, + val layout_style: String, + val module_name: String, + val page_path: String +) + +data class TaskCategory( + val category_id: String, + val category_name: String, + val display_priority: Int, + val tasks: List, + val valid_period: String +) + +data class Task( + val daily_limit: Int, + val double_reward_method: String, + val is_one_time: Boolean, + val makeup_method: String, + val reward_details: List, + val reward_stackable: Boolean, + val reward_type: String, + val reward_value: Int, + val status: String, + val support_makeup: Boolean, + val target_action: String, + val task_desc: String, + val task_id: String, + val task_name: String +) + +data class RewardDetail( + val day: Int, + val target_count: Int, + val value: Int +) \ No newline at end of file diff --git a/app/src/main/java/com/gamedog/vididin/manager/TaskManager.kt b/app/src/main/java/com/gamedog/vididin/manager/TaskManager.kt new file mode 100644 index 0000000..ba2d174 --- /dev/null +++ b/app/src/main/java/com/gamedog/vididin/manager/TaskManager.kt @@ -0,0 +1,63 @@ +package com.gamedog.vididin.manager + +import com.ama.core.architecture.util.FileUtil +import com.gamedog.vididin.main.fragments.task.TaskBean +import com.google.gson.GsonBuilder +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.* + +class TaskManager private constructor() { + companion object { + @Volatile + private var instance: TaskManager? = null + + fun instance(): TaskManager { + return instance ?: synchronized(this) { + instance ?: TaskManager().also { + instance = it + } + } + } + } + + private val mutex = Mutex() + + @Volatile + private var initialized = false + private var mTaskConfigBean: TaskBean? = null + private val mGson = GsonBuilder().create() + + + init { + loadTaskConfigAsync() + } + + + suspend fun getTaskConfig(): TaskBean? = mutex.withLock { + if (!initialized) { + loadTaskConfigFromAsset() + } + return mTaskConfigBean + } + + private fun loadTaskConfigAsync() { + CoroutineScope(Dispatchers.IO).launch { + mutex.withLock { + if (!initialized) { + loadTaskConfigFromAsset() + } + } + } + } + + private suspend fun loadTaskConfigFromAsset() { + return withContext(Dispatchers.IO) { + val configStr = FileUtil.readAssetsFile("taskConfig.json") + mTaskConfigBean = mGson.fromJson(configStr, TaskBean::class.java) + initialized = true + } + } +} + + diff --git a/core/architecture/src/main/java/com/ama/core/architecture/util/AsyncUtil.kt b/core/architecture/src/main/java/com/ama/core/architecture/util/AsyncUtil.kt new file mode 100644 index 0000000..6084fae --- /dev/null +++ b/core/architecture/src/main/java/com/ama/core/architecture/util/AsyncUtil.kt @@ -0,0 +1,144 @@ +package com.ama.core.architecture.util + +import android.util.Log +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.LifecycleOwner +import kotlinx.coroutines.* +import java.util.concurrent.ConcurrentHashMap + +object AsyncUtil { + private val jobMap = ConcurrentHashMap() + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) + + + fun doGlobalAsync( + backgroundWork: suspend () -> T, + onStart: (() -> Unit)? = null, + onComplete: ((T) -> Unit)? = null, + onError: ((Exception) -> Unit)? = null + ): Job { + return scope.launch { + onStart?.invoke() + try { + val result = withContext(Dispatchers.IO) { backgroundWork() } + onComplete?.invoke(result) + } catch (e: Exception) { + onError?.invoke(e) + } + }.also { job -> + job.invokeOnCompletion { cause -> + if (cause != null) Log.d("AsyncTaskUtil", "Global task completed with error: $cause") + } + } + } + + /** + * 绑定组件生命周期的异步任务 + */ + fun doLifecycleAwareAsync( + lifecycleOwner: LifecycleOwner, + backgroundWork: suspend () -> T, + onStart: (() -> Unit)? = null, + onComplete: ((T) -> Unit)? = null, + onError: ((Exception) -> Unit)? = null + ) { + val key = lifecycleOwner::class.java.simpleName + System.identityHashCode(lifecycleOwner) + + // 清理现有任务 + jobMap[key]?.cancel() + + val job = scope.launch { + onStart?.invoke() + try { + val result = withContext(Dispatchers.IO) { backgroundWork() } + if (isActive) onComplete?.invoke(result) + } catch (e: Exception) { + if (e is CancellationException) throw e + if (isActive) onError?.invoke(e) + } + }.also { jobMap[key] = it } + + // 绑定生命周期 + lifecycleOwner.lifecycle.addObserver(object : LifecycleEventObserver { + override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { + when (event) { + Lifecycle.Event.ON_DESTROY -> { + job.cancel() + jobMap.remove(key) + lifecycleOwner.lifecycle.removeObserver(this) + Log.d("AsyncTaskUtil", "Lifecycle task cleaned up: $key") + } + else -> {} + } + } + }) + } + + /** + * 取消所有任务(用于应用退出) + */ + fun shutdown() { + scope.cancel() + jobMap.clear() + } +} + + + +/* + +// 1 全局任务 ---------------------- +val job = AsyncTaskUtil.doGlobalAsync( + backgroundWork = { + // 在IO线程执行 + simulateNetworkRequest() + }, + onStart = { showProgressBar() }, + onComplete = { result -> + // 在主线程执行 + updateUI(result) + }, + onError = { e -> + showError(e.message ?: "Unknown error") + } +) + +// 手动取消(如果需要) +job.cancel() + + + + +// 2 生命周期绑定任务 ---------------------- +class MainActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // 任务自动绑定到Activity生命周期 + AsyncTaskUtil.doLifecycleAwareAsync( + lifecycleOwner = this, + backgroundWork = { loadUserData() }, + onComplete = { data -> updateUserInterface(data) }, + onError = { e -> showErrorMessage(e) } + ) + } +} + + +// 3 ViewModel中 ---------------------- +class MyViewModel : ViewModel() { + fun loadData() { + // 使用viewModelScope而非全局Scope + viewModelScope.launch { + try { + val data = withContext(Dispatchers.IO) { fetchData() } + _uiState.value = SuccessState(data) + } catch (e: Exception) { + _uiState.value = ErrorState(e.message) + } + } + } +} + + */ \ No newline at end of file diff --git a/core/architecture/src/main/java/com/ama/core/architecture/util/FileUtil.kt b/core/architecture/src/main/java/com/ama/core/architecture/util/FileUtil.kt new file mode 100644 index 0000000..54e0bec --- /dev/null +++ b/core/architecture/src/main/java/com/ama/core/architecture/util/FileUtil.kt @@ -0,0 +1,59 @@ +package com.ama.core.architecture.util + +import android.content.Context +import com.ama.core.architecture.BaseApp +import java.io.BufferedReader +import java.io.IOException +import java.io.InputStreamReader + + +class FileUtil private constructor() { + companion object { + + + fun readAssetsFile(fileName: String, encoding: String = "UTF-8"): String? { + return try { + // 使用use关键字自动关闭资源 + BaseApp.appContext().assets.open(fileName).use { inputStream -> + InputStreamReader(inputStream, encoding).use { reader -> + val buffer = CharArray(1024) + val result = StringBuilder() + while (true) { + val bytesRead = reader.read(buffer) + if (bytesRead == -1) break + result.append(buffer, 0, bytesRead) + } + result.toString() + } + } + } catch (e: IOException) { + e.printStackTrace() + null + } + } + + /** + * 逐行读取文本文件(适合处理大文件或需要按行处理的场景) + */ + fun readTextFileLineByLine(context: Context, fileName: String, encoding: String = "UTF-8"): List { + return try { + context.assets.open(fileName).use { inputStream -> + InputStreamReader(inputStream, encoding).use { reader -> + BufferedReader(reader).useLines { lines -> + lines.toList() + } + } + } + } catch (e: IOException) { + e.printStackTrace() + emptyList() + } + } + + + + + } +} + + diff --git a/core/architecture/src/main/java/com/ama/core/architecture/util/ResUtil.kt b/core/architecture/src/main/java/com/ama/core/architecture/util/ResUtil.kt new file mode 100644 index 0000000..4a42587 --- /dev/null +++ b/core/architecture/src/main/java/com/ama/core/architecture/util/ResUtil.kt @@ -0,0 +1,11 @@ +package com.ama.core.architecture.util + +class ResUtil private constructor() { + companion object { + + + + + } +} +