Skip to content

Commit 6937051

Browse files
committed
Update
1 parent f894e12 commit 6937051

3 files changed

Lines changed: 190 additions & 115 deletions

File tree

app/src/main/java/com/omarea/krscript/config/PageConfigReader.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,9 @@ class PageConfigReader {
372372
"depend-include-hidden" -> {
373373
actionParamInfo.dependIncludeHidden = attrValue == "true" || attrValue == "1"
374374
}
375+
"depend-cascade" -> {
376+
actionParamInfo.dependCascade = !(attrValue == "false" || attrValue == "0")
377+
}
375378
"depend-onchange", "depend-on-change", "depend-callback" -> {
376379
actionParamInfo.dependOnChangeCallback = attrValue
377380
}

app/src/main/java/com/omarea/krscript/model/ActionParamInfo.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,15 @@ class ActionParamInfo {
110110
// (thay vì bỏ qua param ẩn). Hữu ích cho các param ẩn nhưng vẫn cần giá trị.
111111
var dependIncludeHidden: Boolean = false
112112

113+
// ========== TÍNH NĂNG MỚI: CHA ẨN THÌ CON ẨN THEO ==========
114+
// Nếu true (mặc định), khi (bất kỳ) param cha trong depend-on đang bị ẨN (do chính
115+
// depend-on của nó, chuỗi phụ thuộc nhiều cấp...), param này CŨNG BỊ ẨN LUÔN, bất kể
116+
// giá trị hiện tại của cha có khớp depend-value hay không.
117+
// Đặt "false" nếu bạn muốn param con vẫn tự đánh giá theo giá trị của cha ngay cả khi
118+
// hàng chứa param cha đang ẩn khỏi màn hình (hiếm khi cần).
119+
// Ví dụ: depend-on="mode" depend-cascade="false"
120+
var dependCascade: Boolean = true
121+
113122
// ========== TÍNH NĂNG MỚI: LÀMĐIỀU GỌILẠI KHI THAY ĐỔI DEPENDENCY ==========
114123
// Tên shell script/callback để gọi khi param này thay đổi trạng thái ẩn/hiện
115124
// (chỉ gọi khi trạng thái thực sự thay đổi, từ visible -> hidden hoặc ngược lại)

app/src/main/java/com/omarea/krscript/ui/ActionParamsLayoutRender.kt

Lines changed: 178 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import androidx.fragment.app.FragmentActivity
99
import com.omarea.common.model.SelectItem
1010
import com.tool.tree.R
1111
import com.omarea.krscript.model.ActionParamInfo
12+
import com.omarea.krscript.executor.ScriptEnvironmen
1213
import androidx.core.graphics.toColorInt
1314

1415
class ActionParamsLayoutRender(private var linearLayout: LinearLayout, activity: FragmentActivity) {
@@ -176,17 +177,22 @@ class ActionParamsLayoutRender(private var linearLayout: LinearLayout, activity:
176177
for (info in currentParamInfos) {
177178
val name = info.name ?: continue
178179
val initialState = info.dependInitialState.trim().lowercase()
179-
180-
val initialVisibility = when {
181-
initialState == "auto" -> {
182-
// Xác định tự động dựa trên dependDefault
180+
181+
val initialVisibility = when (initialState) {
182+
"hide" -> false
183+
"show" -> true
184+
else -> {
185+
// "auto": xác định tự động dựa trên dependDefault
183186
info.dependDefault.trim().lowercase() != "hide"
184187
}
185-
initialState == "hide" -> false
186-
else -> true // "show"
187188
}
188-
189-
visibilityState[name] = initialVisibility
189+
190+
// CHỈ set View.visibility tức thời để tránh nhấp nháy lúc mở dialog.
191+
// KHÔNG ghi vào visibilityState ở đây: evaluateDependencies() được gọi ngay sau
192+
// initializeDependencyStates() sẽ tính trạng thái thật (có cascade) và mới là nơi
193+
// ghi visibilityState + quyết định gọi depend-onchange. Nếu ghi ở đây, lần đánh giá
194+
// đầu tiên sẽ hiểu nhầm đây là "thay đổi trạng thái" và gọi nhầm callback ngay khi
195+
// dialog vừa mở.
190196
rowViews[name]?.visibility = if (initialVisibility) View.VISIBLE else View.GONE
191197
}
192198
}
@@ -268,146 +274,203 @@ class ActionParamsLayoutRender(private var linearLayout: LinearLayout, activity:
268274
return identifiers
269275
}
270276

271-
// ========== TÍNH NĂNG MỚI: ĐÁNH GIÁ PHỤ THUỘC NÂNG CẤP ==========
277+
// ========== TÍNH NĂNG MỚI: ĐÁNH GIÁ PHỤ THUỘC NÂNG CẤP (có depend-cascade) ==========
272278
private fun evaluateDependencies() {
273-
for (info in currentParamInfos) {
274-
val dependOnRaw = info.dependOn?.trim()
275-
val name = info.name
276-
if (name.isNullOrEmpty()) continue
277-
278-
// Nếu không có dependOn -> hiển thị mặc định
279-
if (dependOnRaw.isNullOrEmpty()) {
280-
val shouldShow = info.dependDefault.trim().lowercase() != "hide"
281-
updateVisibility(name, shouldShow)
282-
continue
279+
// Trạng thái TRƯỚC lượt đánh giá này - dùng để phát hiện thay đổi thật sự và quyết
280+
// định có gọi depend-onchange hay không (so với trạng thái đã áp dụng lần trước, KHÔNG
281+
// so giữa các lượt lặp nội bộ bên dưới).
282+
val previousState = HashMap(visibilityState)
283+
284+
// Bộ nhớ TẠM cho lượt đánh giá này. Lặp nhiều lượt để "cha ẩn thì con ẩn theo"
285+
// (depend-cascade) lan truyền đúng qua các chuỗi phụ thuộc nhiều cấp, bất kể param cha
286+
// được khai báo trước hay sau param con trong file XML. Dừng sớm khi không còn gì
287+
// thay đổi giữa 2 lượt liên tiếp (hầu hết trường hợp chỉ cần 1-2 lượt).
288+
val working = HashMap<String, Boolean>()
289+
val maxPasses = currentParamInfos.size.coerceAtLeast(1).coerceAtMost(20)
290+
291+
for (pass in 0 until maxPasses) {
292+
var changedThisPass = false
293+
294+
for (info in currentParamInfos) {
295+
val name = info.name ?: continue
296+
val shouldShow = computeShouldShow(info, working)
297+
298+
if (working[name] != shouldShow) {
299+
working[name] = shouldShow
300+
changedThisPass = true
301+
}
283302
}
284303

285-
val dependOnList = dependOnRaw.split("|").map { it.trim() }.filter { it.isNotEmpty() }
286-
if (dependOnList.isEmpty()) {
287-
updateVisibility(name, info.dependDefault.trim().lowercase() != "hide")
288-
continue
289-
}
304+
if (!changedThisPass) break
305+
}
306+
307+
// Áp dụng kết quả cuối cùng lên UI, ghi lại visibilityState chính thức, và gọi
308+
// depend-onchange đúng 1 lần cho mỗi param có trạng thái thực sự thay đổi so với
309+
// trước khi vào hàm evaluateDependencies() này.
310+
for (info in currentParamInfos) {
311+
val name = info.name ?: continue
312+
val shouldShow = working[name] ?: continue
313+
applyVisibility(name, shouldShow, previousState[name])
314+
}
315+
}
290316

291-
val dependValueList = (info.dependValue ?: "").split("|")
292-
val dependModeList = info.dependMode.split("|")
317+
// Tính shouldShow cho 1 param dựa trên trạng thái ẩn/hiện của các param cha TRONG LƯỢT
318+
// ĐÁNH GIÁ HIỆN TẠI (map "working"), không phải trạng thái đã chốt từ lần render trước.
319+
private fun computeShouldShow(info: ActionParamInfo, working: HashMap<String, Boolean>): Boolean {
320+
val dependOnRaw = info.dependOn?.trim()
293321

294-
fun evalCondition(i: Int): Pair<Boolean, Boolean>? {
295-
val parentName = dependOnList[i]
296-
val controllerInfo = currentParamInfos.find { it.name == parentName }
297-
val reader = valueReaders[parentName]
298-
if (controllerInfo == null || reader == null) return null
322+
if (dependOnRaw.isNullOrEmpty()) {
323+
return info.dependDefault.trim().lowercase() != "hide"
324+
}
299325

300-
val currentValues = reader().split(controllerInfo.separator)
301-
.map { it.trim() }.filter { it.isNotEmpty() }
302-
val parentOptions = controllerInfo.optionsFromShell ?: controllerInfo.options
326+
val dependOnList = dependOnRaw.split("|").map { it.trim() }.filter { it.isNotEmpty() }
327+
if (dependOnList.isEmpty()) {
328+
return info.dependDefault.trim().lowercase() != "hide"
329+
}
303330

304-
val currentIdentifiers = HashSet<String>()
305-
for (v in currentValues) {
306-
currentIdentifiers.addAll(buildValueIdentifiers(v, parentOptions))
307-
}
331+
// ========== TÍNH NĂNG MỚI: CHA ẨN THÌ CON ẨN THEO (depend-cascade) ==========
332+
// Nếu BẤT KỲ param cha nào trong depend-on đang bị ẩn (đã tính ở lượt trước/pass hiện
333+
// tại), param này ẩn theo luôn, không cần xét tiếp depend-value/depend-logic.
334+
if (info.dependCascade && dependOnList.any { working[it] == false }) {
335+
return false
336+
}
308337

309-
val wantedRaw = dependValueList.getOrNull(i) ?: dependValueList.lastOrNull() ?: ""
310-
val wanted = wantedRaw.split(",").map { it.trim() }.filter { it.isNotEmpty() }
311-
val matched = wanted.isEmpty() || wanted.any { currentIdentifiers.contains(it) }
338+
val dependValueList = (info.dependValue ?: "").split("|")
339+
val dependModeList = info.dependMode.split("|")
312340

313-
val mode = (dependModeList.getOrNull(i) ?: dependModeList.lastOrNull() ?: "show").trim()
314-
val wantShow = if (mode == "hide") !matched else matched
315-
return Pair(matched, wantShow)
341+
fun evalCondition(i: Int): Pair<Boolean, Boolean>? {
342+
val parentName = dependOnList[i]
343+
val controllerInfo = currentParamInfos.find { it.name == parentName }
344+
val reader = valueReaders[parentName]
345+
if (controllerInfo == null || reader == null) return null
346+
347+
val currentValues = reader().split(controllerInfo.separator)
348+
.map { it.trim() }.filter { it.isNotEmpty() }
349+
val parentOptions = controllerInfo.optionsFromShell ?: controllerInfo.options
350+
351+
val currentIdentifiers = HashSet<String>()
352+
for (v in currentValues) {
353+
currentIdentifiers.addAll(buildValueIdentifiers(v, parentOptions))
316354
}
317355

318-
val logic = info.dependLogic.trim().lowercase()
319-
val shouldShow: Boolean = when (logic) {
320-
"priority", "or", "priority-ltr", "or-ltr" -> {
321-
// Ưu tiên trái -> phải
322-
var result = info.dependDefault.trim().lowercase() != "hide"
323-
for (i in dependOnList.indices) {
324-
val (matched, wantShow) = evalCondition(i) ?: continue
325-
if (matched) {
326-
result = wantShow
327-
break
328-
}
356+
val wantedRaw = dependValueList.getOrNull(i) ?: dependValueList.lastOrNull() ?: ""
357+
val wanted = wantedRaw.split(",").map { it.trim() }.filter { it.isNotEmpty() }
358+
val matched = wanted.isEmpty() || wanted.any { currentIdentifiers.contains(it) }
359+
360+
val mode = (dependModeList.getOrNull(i) ?: dependModeList.lastOrNull() ?: "show").trim()
361+
val wantShow = if (mode == "hide") !matched else matched
362+
return Pair(matched, wantShow)
363+
}
364+
365+
val logic = info.dependLogic.trim().lowercase()
366+
return when (logic) {
367+
"priority", "or", "priority-ltr", "or-ltr" -> {
368+
// Ưu tiên trái -> phải
369+
var result = info.dependDefault.trim().lowercase() != "hide"
370+
for (i in dependOnList.indices) {
371+
val (matched, wantShow) = evalCondition(i) ?: continue
372+
if (matched) {
373+
result = wantShow
374+
break
329375
}
330-
result
331376
}
332-
"priority-rtl", "or-rtl" -> {
333-
// Ưu tiên phải -> trái
334-
var result = info.dependDefault.trim().lowercase() != "hide"
335-
for (i in dependOnList.indices.reversed()) {
336-
val (matched, wantShow) = evalCondition(i) ?: continue
337-
if (matched) {
338-
result = wantShow
339-
break
340-
}
377+
result
378+
}
379+
"priority-rtl", "or-rtl" -> {
380+
// Ưu tiên phải -> trái
381+
var result = info.dependDefault.trim().lowercase() != "hide"
382+
for (i in dependOnList.indices.reversed()) {
383+
val (matched, wantShow) = evalCondition(i) ?: continue
384+
if (matched) {
385+
result = wantShow
386+
break
341387
}
342-
result
343388
}
344-
"xor" -> {
345-
// Chỉ ĐÚNG MỘT điều kiện được thỏa
346-
var matchCount = 0
347-
for (i in dependOnList.indices) {
348-
val (matched, _) = evalCondition(i) ?: continue
349-
if (matched) matchCount++
350-
}
351-
(matchCount == 1) != info.dependNegate
389+
result
390+
}
391+
"xor" -> {
392+
// Chỉ ĐÚNG MỘT điều kiện được thỏa
393+
var matchCount = 0
394+
for (i in dependOnList.indices) {
395+
val (matched, _) = evalCondition(i) ?: continue
396+
if (matched) matchCount++
352397
}
353-
"nand" -> {
354-
// Phủ định của AND (KHÔNG phải tất cả điều kiện đều thỏa)
355-
var result = true
356-
for (i in dependOnList.indices) {
357-
val (_, wantShow) = evalCondition(i) ?: continue
358-
if (!wantShow) {
359-
result = false
360-
break
361-
}
398+
(matchCount == 1) != info.dependNegate
399+
}
400+
"nand" -> {
401+
// Phủ định của AND (KHÔNG phải tất cả điều kiện đều thỏa)
402+
var result = true
403+
for (i in dependOnList.indices) {
404+
val (_, wantShow) = evalCondition(i) ?: continue
405+
if (!wantShow) {
406+
result = false
407+
break
362408
}
363-
!result != info.dependNegate
364409
}
365-
else -> {
366-
// "and" (mặc định)
367-
var satisfiedCount = 0
368-
var totalCount = 0
369-
370-
for (i in dependOnList.indices) {
371-
val (_, wantShow) = evalCondition(i) ?: continue
372-
totalCount++
373-
if (wantShow) satisfiedCount++
374-
}
375-
376-
val threshold = if (info.dependThreshold < 0) {
377-
// Mặc định: 100% (tất cả phải thỏa)
378-
totalCount
379-
} else {
380-
// Tính toán % ngưỡng
381-
(totalCount * info.dependThreshold / 100).coerceAtLeast(1)
382-
}
410+
!result != info.dependNegate
411+
}
412+
else -> {
413+
// "and" (mặc định)
414+
var satisfiedCount = 0
415+
var totalCount = 0
416+
417+
for (i in dependOnList.indices) {
418+
val (_, wantShow) = evalCondition(i) ?: continue
419+
totalCount++
420+
if (wantShow) satisfiedCount++
421+
}
383422

384-
val result = satisfiedCount >= threshold
385-
result != info.dependNegate
423+
val threshold = if (info.dependThreshold < 0) {
424+
// Mặc định: 100% (tất cả phải thỏa)
425+
totalCount
426+
} else {
427+
// Tính toán % ngưỡng
428+
(totalCount * info.dependThreshold / 100).coerceAtLeast(1)
386429
}
387-
}
388430

389-
updateVisibility(name, shouldShow)
431+
val result = satisfiedCount >= threshold
432+
result != info.dependNegate
433+
}
390434
}
391435
}
392436

393-
// ========== TÍNH NĂNG MỚI: CẬP NHẬT TRẠNG THÁI VÀ GỌI CALLBACK ==========
394-
private fun updateVisibility(name: String, shouldShow: Boolean) {
395-
val oldState = visibilityState[name]
437+
// ========== TÍNH NĂNG MỚI: ÁP DỤNG TRẠNG THÁI LÊN UI + GỌI CALLBACK ==========
438+
private fun applyVisibility(name: String, shouldShow: Boolean, oldState: Boolean?) {
396439
visibilityState[name] = shouldShow
397-
440+
398441
val view = rowViews[name]
399442
view?.visibility = if (shouldShow) View.VISIBLE else View.GONE
400-
401-
// Gọi callback nếu trạng thái thay đổi
443+
444+
// Chỉ gọi callback khi ĐÃ TỪNG có trạng thái trước đó (oldState != null) VÀ trạng thái
445+
// thực sự đổi - tránh gọi callback ngay khi dialog vừa mở (lần đầu tiên oldState luôn
446+
// null vì initializeDependencyStates() không ghi vào visibilityState).
402447
if (oldState != null && oldState != shouldShow) {
403448
val info = currentParamInfos.find { it.name == name }
404-
if (info != null && !info.dependOnChangeCallback.isNullOrEmpty()) {
405-
// TODO: Implement callback execution (shell script or custom logic)
406-
// Example: executeCallback(info.dependOnChangeCallback, name, shouldShow)
449+
val script = info?.dependOnChangeCallback
450+
if (!script.isNullOrEmpty()) {
451+
executeDependOnChangeCallback(name, shouldShow, script)
407452
}
408453
}
409454
}
410455

456+
// ========== TÍNH NĂNG MỚI: THỰC THI depend-onchange ==========
457+
// Chạy script/callback trên luồng nền (KHÔNG được chạy trực tiếp trên UI thread vì lệnh
458+
// shell root có thể mất nhiều thời gian và sẽ làm treo giao diện). Truyền tên param vừa
459+
// đổi trạng thái + trạng thái mới (1 = đang hiện, 0 = đang ẩn) làm biến môi trường để
460+
// script có thể tự xử lý theo ngữ cảnh.
461+
private fun executeDependOnChangeCallback(paramName: String, visible: Boolean, script: String) {
462+
Thread {
463+
try {
464+
val extraParams = HashMap<String, String>()
465+
extraParams["PARAM_NAME"] = paramName
466+
extraParams["PARAM_VISIBLE"] = if (visible) "1" else "0"
467+
ScriptEnvironmen.executeResultRoot(context, script, null, extraParams)
468+
} catch (ex: Exception) {
469+
// Lỗi khi chạy callback không được làm crash hay ảnh hưởng tới UI chính
470+
}
471+
}.start()
472+
}
473+
411474
private val hideLabelTypes = arrayOf("bool", "checkbox", "switch")
412475
private fun addToLayout(inputView: View, actionParamInfo: ActionParamInfo) {
413476
val layout = LayoutInflater.from(context).inflate(R.layout.kr_param_row, null)

0 commit comments

Comments
 (0)