diff --git a/HTTPShortcuts/app/src/main/assets/docs/scripting.html b/HTTPShortcuts/app/src/main/assets/docs/scripting.html index 79b74ed2c..d9de53f53 100644 --- a/HTTPShortcuts/app/src/main/assets/docs/scripting.html +++ b/HTTPShortcuts/app/src/main/assets/docs/scripting.html @@ -81,7 +81,8 @@

changeDescription

With this function you can change the description of a shortcut. Simply pass the name or ID of a shortcut as the first parameter and the new description as the second one. You can also pass an empty string as the first parameter to target the current shortcut.

changeDescription('My Shortcut', 'New Description');
 

A shortcut's description is only visible in categories that use a list layout, not in those that use a grid layout.

changeIcon

With this function you can change the icon of a shortcut. Simply pass the name or ID of a shortcut as the first parameter and the name of the icon as the second one. You can also pass an empty string as the first parameter to target the current shortcut. Use the "Add Code Snippet" button in the app to select an icon. Alternatively, you can check the source code for all the available icons names (look for the prefix "R.drawable.", everything after it is a valid icon name).

changeIcon('My Shortcut', 'bitsies_lightbulb');
 

setShortcutHidden

This function allows you to show or hide individual shortcuts inside the app. Simply pass the name or ID of a shortcut as the first parameter and true or false as the second parameter. You can also pass an empty string as the first parameter to target the current shortcut.

setShortcutHidden('My Shortcut', true);
-

You can make hidden shortcuts visible via an option on the Settings screen.

Control Flow

This section lists some of the options you have to control the execution flow of your script.

wait

The wait function allows you to delay execution by waiting (also called sleeping) for a specified number of milliseconds before continuing with the execution of the script.

wait(3000); // delay execution by 3 seconds
+

You can make hidden shortcuts visible via an option on the Settings screen.

setCategoryHidden

This function allows you to show or hide categories. Simply pass the name or ID of a category as the first parameter and true or false as the second parameter.

setCategoryHidden('My Category', true);
+

There must always be at least one non-hidden category. If you try to hide the last visible category with this, nothing will happen.

Control Flow

This section lists some of the options you have to control the execution flow of your script.

wait

The wait function allows you to delay execution by waiting (also called sleeping) for a specified number of milliseconds before continuing with the execution of the script.

wait(3000); // delay execution by 3 seconds
 

Please note that this is a blocking action, meaning that you will not be able to interact with the app during the waiting time.

abort, abortAll and abortAndTreatAsFailure

With the abort function you can abort the execution of the shortcut.

abort();
 

If the shortcut was called from another shortcut via the executeShortcut function, only the current shortcut will be aborted. If you want to abort also the calling shortcut, you can use abortAll().

As part of the "Run on Success" code block, you can also use the abortAndTreatAsFailure() function, which skips the rest of the "success" steps and instead treats the execution as a failure, meaning that the "Run on Failure" code will be run, as well as any other failure-related steps such as displaying an error message. You can use this in cases where the default behavior of only checking the HTTP status code is not enough to determine whether a request should be considered a success. As an optional parameter, you can pass a string which will be used as the error message.

// Basic example
 abortAndTreatAsFailure();
diff --git a/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/activities/categories/CategoriesViewModel.kt b/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/activities/categories/CategoriesViewModel.kt
index d17a6f23a..4de2d1c73 100644
--- a/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/activities/categories/CategoriesViewModel.kt
+++ b/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/activities/categories/CategoriesViewModel.kt
@@ -103,7 +103,7 @@ constructor(
         val categoryId = activeCategoryId ?: skipAction()
         updateDialogState(null)
         withProgressTracking {
-            categoryRepository.toggleCategoryHidden(categoryId, !visible)
+            categoryRepository.setCategoryHidden(categoryId, !visible)
             hasChanged = true
             showSnackbar(if (visible) R.string.message_category_visible else R.string.message_category_hidden)
         }
diff --git a/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/activities/editor/scripting/codesnippets/usecases/GenerateCodeSnippetItemsUseCase.kt b/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/activities/editor/scripting/codesnippets/usecases/GenerateCodeSnippetItemsUseCase.kt
index 05d7a46a5..6eb5bb337 100644
--- a/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/activities/editor/scripting/codesnippets/usecases/GenerateCodeSnippetItemsUseCase.kt
+++ b/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/activities/editor/scripting/codesnippets/usecases/GenerateCodeSnippetItemsUseCase.kt
@@ -322,6 +322,13 @@ constructor(
                         insertText("setShortcutHidden($shortcutPlaceholder, true", ");\n")
                     }
                 }
+                item(
+                    R.string.action_type_change_category_hidden_title,
+                    docRef = "set-category-hidden",
+                    keywords = setOf("change", "update", "visible", "visibility", "hide", "hidden", "show"),
+                ) {
+                    insertText("setCategoryHidden(\"category name", "\", true);\n")
+                }
             }
             section(R.string.dialog_code_snippet_control_flow, R.drawable.ic_control_flow) {
                 item(
diff --git a/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/activities/main/MainContent.kt b/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/activities/main/MainContent.kt
index 925789147..9481dd290 100644
--- a/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/activities/main/MainContent.kt
+++ b/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/activities/main/MainContent.kt
@@ -56,7 +56,7 @@ fun MainContent(
         if (categoryItems.size > 1) {
             TabBar(
                 categoryItems = categoryItems,
-                activeTabIndex = pagerState.currentPage,
+                activeTabIndex = pagerState.currentPage.coerceAtMost(categoryItems.size - 1),
                 onActiveCategoryIdChanged = onActiveCategoryIdChanged,
             )
         }
diff --git a/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/activities/main/MainViewModel.kt b/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/activities/main/MainViewModel.kt
index 18225abbd..7bf3a50a4 100644
--- a/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/activities/main/MainViewModel.kt
+++ b/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/activities/main/MainViewModel.kt
@@ -47,6 +47,8 @@ import ch.rmy.curlcommand.CurlCommand
 import dagger.hilt.android.lifecycle.HiltViewModel
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.drop
+import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.firstOrNull
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
@@ -90,13 +92,26 @@ constructor(
     private var activeShortcutId: ShortcutId? = null
 
     override suspend fun initialize(data: InitData): MainViewState {
-        this.categories = categoryRepository.getCategories()
+        val categoriesFlow = categoryRepository.getObservableCategories()
+        this.categories = categoriesFlow.first()
 
         viewModelScope.launch(Dispatchers.Default) {
             // Ensure that the VariablePlaceholderProvider is initialized
             variablePlaceholderProvider.applyVariables(variableRepository.getVariables())
         }
 
+        viewModelScope.launch {
+            categoriesFlow.drop(1).collect { categories ->
+                this@MainViewModel.categories = categories
+                updateViewState {
+                    copy(
+                        categoryItems = getCategoryTabItems(),
+                        activeCategoryId = (categories.find { it.id == activeCategoryId && !it.hidden } ?: categories.first { !it.hidden }).id
+                    )
+                }
+            }
+        }
+
         val appLockObservable = appRepository.getObservableLock()
         val appLock = appLockObservable.firstOrNull()
 
diff --git a/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/data/domains/QueryExtensions.kt b/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/data/domains/QueryExtensions.kt
index fb9c1cd33..a9a92943f 100644
--- a/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/data/domains/QueryExtensions.kt
+++ b/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/data/domains/QueryExtensions.kt
@@ -30,6 +30,9 @@ fun RealmContext.getBase(): RealmQuery =
 fun RealmContext.getCategoryById(categoryId: CategoryId): RealmQuery =
     get("${Category.FIELD_ID} == $0", categoryId)
 
+fun RealmContext.getCategoryByNameOrId(categoryNameOrId: String): RealmQuery =
+    get("${Category.FIELD_ID} == $0 OR ${Category.FIELD_NAME} ==[c] $1", categoryNameOrId, categoryNameOrId)
+
 fun RealmContext.getShortcutById(shortcutId: ShortcutId): RealmQuery =
     get("${Shortcut.FIELD_ID} == $0", shortcutId)
 
diff --git a/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/data/domains/categories/CategoryRepository.kt b/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/data/domains/categories/CategoryRepository.kt
index a6fc7f621..a7d84da05 100644
--- a/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/data/domains/categories/CategoryRepository.kt
+++ b/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/data/domains/categories/CategoryRepository.kt
@@ -6,6 +6,7 @@ import ch.rmy.android.framework.extensions.swap
 import ch.rmy.android.framework.utils.UUIDUtils.newUUID
 import ch.rmy.android.http_shortcuts.data.domains.getBase
 import ch.rmy.android.http_shortcuts.data.domains.getCategoryById
+import ch.rmy.android.http_shortcuts.data.domains.getCategoryByNameOrId
 import ch.rmy.android.http_shortcuts.data.enums.CategoryBackgroundType
 import ch.rmy.android.http_shortcuts.data.enums.CategoryLayoutType
 import ch.rmy.android.http_shortcuts.data.enums.ShortcutClickBehavior
@@ -36,6 +37,11 @@ constructor(
             getCategoryById(categoryId)
         }
 
+    suspend fun getCategoryByNameOrId(categoryNameOrId: String): Category =
+        queryItem {
+            this.getCategoryByNameOrId(categoryNameOrId)
+        }
+
     fun getObservableCategory(categoryId: CategoryId): Flow =
         observeItem {
             getCategoryById(categoryId)
@@ -96,8 +102,16 @@ constructor(
         }
     }
 
-    suspend fun toggleCategoryHidden(categoryId: CategoryId, hidden: Boolean) {
+    suspend fun setCategoryHidden(categoryId: CategoryId, hidden: Boolean) {
         commitTransaction {
+            if (hidden) {
+                val categories = getBase().findFirst()!!.categories
+                if (categories.all { it.hidden || it.id == categoryId }) {
+                    // Disallow hiding the last non-hidden category
+                    return@commitTransaction
+                }
+            }
+
             getCategoryById(categoryId)
                 .findFirst()
                 ?.hidden = hidden
diff --git a/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/data/models/Category.kt b/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/data/models/Category.kt
index 7defab03a..2eb305087 100644
--- a/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/data/models/Category.kt
+++ b/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/data/models/Category.kt
@@ -83,6 +83,7 @@ class Category() : RealmObject {
 
     companion object {
         const val FIELD_ID = "id"
+        const val FIELD_NAME = "name"
 
         const val NAME_MAX_LENGTH = 50
     }
diff --git a/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/scripting/actions/ActionFactory.kt b/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/scripting/actions/ActionFactory.kt
index e4ff515a2..b8cfb4c4d 100644
--- a/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/scripting/actions/ActionFactory.kt
+++ b/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/scripting/actions/ActionFactory.kt
@@ -36,6 +36,7 @@ import ch.rmy.android.http_shortcuts.scripting.actions.types.SendIntentActionTyp
 import ch.rmy.android.http_shortcuts.scripting.actions.types.SendMQTTMessagesActionType
 import ch.rmy.android.http_shortcuts.scripting.actions.types.SendTCPPacketActionType
 import ch.rmy.android.http_shortcuts.scripting.actions.types.SendUDPPacketActionType
+import ch.rmy.android.http_shortcuts.scripting.actions.types.SetCategoryHiddenActionType
 import ch.rmy.android.http_shortcuts.scripting.actions.types.SetResultActionType
 import ch.rmy.android.http_shortcuts.scripting.actions.types.SetShortcutHiddenActionType
 import ch.rmy.android.http_shortcuts.scripting.actions.types.SetVariableActionType
@@ -93,6 +94,7 @@ constructor(
     sendMQTTMessagesActionType: SendMQTTMessagesActionType,
     sendTCPPacketActionType: SendTCPPacketActionType,
     sendUDPPacketActionType: SendUDPPacketActionType,
+    setCategoryHiddenActionType: SetCategoryHiddenActionType,
     setShortcutHiddenActionType: SetShortcutHiddenActionType,
     setResultActionType: SetResultActionType,
     setVariableActionType: SetVariableActionType,
@@ -155,6 +157,7 @@ constructor(
             sendMQTTMessagesActionType,
             sendTCPPacketActionType,
             sendUDPPacketActionType,
+            setCategoryHiddenActionType,
             setShortcutHiddenActionType,
             setResultActionType,
             setVariableActionType,
diff --git a/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/scripting/actions/types/SetCategoryHiddenAction.kt b/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/scripting/actions/types/SetCategoryHiddenAction.kt
new file mode 100644
index 000000000..7ae199912
--- /dev/null
+++ b/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/scripting/actions/types/SetCategoryHiddenAction.kt
@@ -0,0 +1,33 @@
+package ch.rmy.android.http_shortcuts.scripting.actions.types
+
+import ch.rmy.android.http_shortcuts.R
+import ch.rmy.android.http_shortcuts.data.domains.categories.CategoryRepository
+import ch.rmy.android.http_shortcuts.exceptions.ActionException
+import ch.rmy.android.http_shortcuts.scripting.ExecutionContext
+import javax.inject.Inject
+
+class SetCategoryHiddenAction
+@Inject
+constructor(
+    private val categoryRepository: CategoryRepository,
+) : Action {
+    override suspend fun Params.execute(executionContext: ExecutionContext) {
+        setHidden(this.categoryNameOrId ?: "")
+    }
+
+    private suspend fun Params.setHidden(categoryNameOrId: String) {
+        val category = try {
+            categoryRepository.getCategoryByNameOrId(categoryNameOrId)
+        } catch (_: NoSuchElementException) {
+            throw ActionException {
+                getString(R.string.error_category_not_found_for_set_visible, categoryNameOrId)
+            }
+        }
+        categoryRepository.setCategoryHidden(category.id, hidden)
+    }
+
+    data class Params(
+        val categoryNameOrId: String?,
+        val hidden: Boolean,
+    )
+}
diff --git a/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/scripting/actions/types/SetCategoryHiddenActionType.kt b/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/scripting/actions/types/SetCategoryHiddenActionType.kt
new file mode 100644
index 000000000..7963de265
--- /dev/null
+++ b/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/scripting/actions/types/SetCategoryHiddenActionType.kt
@@ -0,0 +1,34 @@
+package ch.rmy.android.http_shortcuts.scripting.actions.types
+
+import ch.rmy.android.framework.extensions.takeUnlessEmpty
+import ch.rmy.android.http_shortcuts.scripting.ActionAlias
+import ch.rmy.android.http_shortcuts.scripting.actions.ActionData
+import ch.rmy.android.http_shortcuts.scripting.actions.ActionRunnable
+import javax.inject.Inject
+
+class SetCategoryHiddenActionType
+@Inject
+constructor(
+    private val setCategoryHiddenAction: SetCategoryHiddenAction,
+) : ActionType {
+    override val type = TYPE
+
+    override fun getActionRunnable(actionDTO: ActionData) =
+        ActionRunnable(
+            action = setCategoryHiddenAction,
+            params = SetCategoryHiddenAction.Params(
+                categoryNameOrId = actionDTO.getString(0)?.takeUnlessEmpty(),
+                hidden = actionDTO.getBoolean(1) != false,
+            ),
+        )
+
+    override fun getAlias() = ActionAlias(
+        functionName = FUNCTION_NAME,
+        parameters = 2,
+    )
+
+    companion object {
+        private const val TYPE = "set_category_hidden"
+        private const val FUNCTION_NAME = "setCategoryHidden"
+    }
+}
diff --git a/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/scripting/actions/types/SetShortcutHiddenActionType.kt b/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/scripting/actions/types/SetShortcutHiddenActionType.kt
index 92928006e..81ea24387 100644
--- a/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/scripting/actions/types/SetShortcutHiddenActionType.kt
+++ b/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/scripting/actions/types/SetShortcutHiddenActionType.kt
@@ -18,7 +18,7 @@ constructor(
             action = setShortcutHiddenAction,
             params = SetShortcutHiddenAction.Params(
                 shortcutNameOrId = actionDTO.getString(0)?.takeUnlessEmpty(),
-                hidden = actionDTO.getBoolean(1) ?: true,
+                hidden = actionDTO.getBoolean(1) != false,
             ),
         )
 
diff --git a/HTTPShortcuts/app/src/main/res/values/strings.xml b/HTTPShortcuts/app/src/main/res/values/strings.xml
index c0661646c..005058781 100644
--- a/HTTPShortcuts/app/src/main/res/values/strings.xml
+++ b/HTTPShortcuts/app/src/main/res/values/strings.xml
@@ -774,6 +774,8 @@
     Change Shortcut Description
     
     Show/Hide Shortcut
+    
+    Show/Hide Category
     
     Enqueue Shortcut
     
@@ -1018,6 +1020,8 @@
     Could not rename shortcut \“%1$s\” because it does not exist.
     
     Could not change visibility of shortcut \“%1$s\” because it does not exist.
+    
+    Could not change visibility of category \“%1$s\” because it does not exist.
     
     Could not change icon of shortcut \“%1$s\” because it does not exist.