task manager 读取

This commit is contained in:
renhaoting 2025-11-20 17:45:17 +08:00
parent 9ce2bda2bc
commit 1424f72256
8 changed files with 478 additions and 0 deletions

View File

@ -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"
}
]
}
]
}
}

View File

@ -1,10 +1,23 @@
package com.gamedog.vididin package com.gamedog.vididin
import com.ama.core.architecture.BaseApp import com.ama.core.architecture.BaseApp
import com.gamedog.vididin.manager.TaskManager
import dagger.hilt.android.HiltAndroidApp import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp @HiltAndroidApp
class VidiDinApp : BaseApp() { class VidiDinApp : BaseApp() {
init {
}
override fun onCreate() {
super.onCreate()
initManagers()
}
private fun initManagers() {
TaskManager.instance()
}
} }

View File

@ -6,14 +6,18 @@ import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import com.ama.core.architecture.appBase.AppViewsFragment import com.ama.core.architecture.appBase.AppViewsFragment
import com.ama.core.architecture.appBase.OnFragmentBackgroundListener import com.ama.core.architecture.appBase.OnFragmentBackgroundListener
import com.ama.core.architecture.util.SpUtil import com.ama.core.architecture.util.SpUtil
import com.ama.core.architecture.util.setOnClickBatch import com.ama.core.architecture.util.setOnClickBatch
import com.ama.core.architecture.util.setStatusBarDarkFont import com.ama.core.architecture.util.setStatusBarDarkFont
import com.gamedog.vididin.main.fragments.task.DailySignDialog 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.main.interfaces.OnTabClickAgainListener
import com.gamedog.vididin.manager.TaskManager
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import kotlin.getValue import kotlin.getValue
import com.gamedog.vididin.databinding.VididinappFeatureMessageFragmentMessageBinding as ViewBinding import com.gamedog.vididin.databinding.VididinappFeatureMessageFragmentMessageBinding as ViewBinding
import com.gamedog.vididin.main.fragments.home.YoutubeViewModel as ViewModel 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 @AndroidEntryPoint
class TasksFragment : AppViewsFragment<ViewBinding, UiState, ViewModel>(), OnTabClickAgainListener, OnFragmentBackgroundListener { class TasksFragment : AppViewsFragment<ViewBinding, UiState, ViewModel>(), OnTabClickAgainListener, OnFragmentBackgroundListener {
private var mTaskConfig: TaskBean? = null
override val mViewModel: ViewModel by viewModels() override val mViewModel: ViewModel by viewModels()
override var isBackgroundBright: Boolean = true override var isBackgroundBright: Boolean = true
@ -42,6 +47,7 @@ class TasksFragment : AppViewsFragment<ViewBinding, UiState, ViewModel>(), OnTab
} }
} }
override fun ViewBinding.initViews() { override fun ViewBinding.initViews() {
with(binding) { with(binding) {
setOnClickBatch(ivGotoDailySign) { setOnClickBatch(ivGotoDailySign) {
@ -52,6 +58,10 @@ class TasksFragment : AppViewsFragment<ViewBinding, UiState, ViewModel>(), OnTab
} }
} }
} }
lifecycleScope.launch {
mTaskConfig = TaskManager.instance().getTaskConfig()
}
} }
override fun ViewBinding.initListeners() { override fun ViewBinding.initListeners() {

View File

@ -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<TaskCategory>
)
data class BasicInfo(
val currency_display: List<String>,
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<Task>,
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<RewardDetail>,
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
)

View File

@ -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
}
}
}

View File

@ -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<String, Job>()
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
fun <T> 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 <T> 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)
}
}
}
}
*/

View File

@ -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<String> {
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()
}
}
}
}

View File

@ -0,0 +1,11 @@
package com.ama.core.architecture.util
class ResUtil private constructor() {
companion object {
}
}