Skip to content

Commit

Permalink
Allow programmatically showing/hiding categories
Browse files Browse the repository at this point in the history
  • Loading branch information
Waboodoo committed Oct 5, 2024
1 parent e29148e commit dadec2e
Show file tree
Hide file tree
Showing 13 changed files with 121 additions and 6 deletions.
3 changes: 2 additions & 1 deletion HTTPShortcuts/app/src/main/assets/docs/scripting.html
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@
</code></pre><p><a name="change-description"></a></p><h3>changeDescription</h3><p>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.</p><pre><code class="language-js">changeDescription('My Shortcut', 'New Description');
</code></pre><blockquote><p>A shortcut's description is only visible in categories that use a list layout, not in those that use a grid layout.</p></blockquote><p><a name="change-icon"></a></p><h3>changeIcon</h3><p>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 <em>&quot;Add Code Snippet&quot;</em> button in the app to select an icon. Alternatively, you can check the <a href="https://github.com/Waboodoo/HTTP-Shortcuts/blob/develop/HTTPShortcuts/app/src/main/kotlin/ch/rmy/android/http_shortcuts/icons/Icons.kt">source code</a> for all the available icons names (look for the prefix &quot;R.drawable.&quot;, everything after it is a valid icon name).</p><pre><code class="language-js">changeIcon('My Shortcut', 'bitsies_lightbulb');
</code></pre><p><a name="set-shortcut-hidden"></a></p><h3>setShortcutHidden</h3><p>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 <code>true</code> or <code>false</code> as the second parameter. You can also pass an empty string as the first parameter to target the current shortcut.</p><pre><code class="language-js">setShortcutHidden('My Shortcut', true);
</code></pre><blockquote><p>You can make hidden shortcuts visible via an option on the Settings screen.</p></blockquote><p><a name="control-flow"></a></p><h2>Control Flow</h2><p>This section lists some of the options you have to control the execution flow of your script.</p><p><a name="wait"></a></p><h3>wait</h3><p>The <code>wait</code> 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.</p><pre><code class="language-js">wait(3000); // delay execution by 3 seconds
</code></pre><blockquote><p>You can make hidden shortcuts visible via an option on the Settings screen.</p></blockquote><p><a name="set-category-hidden"></a></p><h3>setCategoryHidden</h3><p>This function allows you to show or hide categories. Simply pass the name or ID of a category as the first parameter and <code>true</code> or <code>false</code> as the second parameter.</p><pre><code class="language-js">setCategoryHidden('My Category', true);
</code></pre><blockquote><p>There must always be at least one non-hidden category. If you try to hide the last visible category with this, nothing will happen.</p></blockquote><p><a name="control-flow"></a></p><h2>Control Flow</h2><p>This section lists some of the options you have to control the execution flow of your script.</p><p><a name="wait"></a></p><h3>wait</h3><p>The <code>wait</code> 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.</p><pre><code class="language-js">wait(3000); // delay execution by 3 seconds
</code></pre><blockquote><p>Please note that this is a blocking action, meaning that you will not be able to interact with the app during the waiting time.</p></blockquote><p><a name="abort"></a></p><h3>abort, abortAll and abortAndTreatAsFailure</h3><p>With the <code>abort</code> function you can abort the execution of the shortcut.</p><pre><code class="language-js">abort();
</code></pre><p>If the shortcut was called from another shortcut via the <a href="#execute-shortcut">executeShortcut</a> function, only the current shortcut will be aborted. If you want to abort also the calling shortcut, you can use <code>abortAll()</code>.</p><p>As part of the &quot;Run on Success&quot; code block, you can also use the <code>abortAndTreatAsFailure()</code> function, which skips the rest of the &quot;success&quot; steps and instead treats the execution as a failure, meaning that the &quot;Run on Failure&quot; 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.</p><pre><code class="language-js">// Basic example
abortAndTreatAsFailure();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ fun RealmContext.getBase(): RealmQuery<Base> =
fun RealmContext.getCategoryById(categoryId: CategoryId): RealmQuery<Category> =
get("${Category.FIELD_ID} == $0", categoryId)

fun RealmContext.getCategoryByNameOrId(categoryNameOrId: String): RealmQuery<Category> =
get("${Category.FIELD_ID} == $0 OR ${Category.FIELD_NAME} ==[c] $1", categoryNameOrId, categoryNameOrId)

fun RealmContext.getShortcutById(shortcutId: ShortcutId): RealmQuery<Shortcut> =
get("${Shortcut.FIELD_ID} == $0", shortcutId)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -36,6 +37,11 @@ constructor(
getCategoryById(categoryId)
}

suspend fun getCategoryByNameOrId(categoryNameOrId: String): Category =
queryItem {
this.getCategoryByNameOrId(categoryNameOrId)
}

fun getObservableCategory(categoryId: CategoryId): Flow<Category> =
observeItem {
getCategoryById(categoryId)
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -93,6 +94,7 @@ constructor(
sendMQTTMessagesActionType: SendMQTTMessagesActionType,
sendTCPPacketActionType: SendTCPPacketActionType,
sendUDPPacketActionType: SendUDPPacketActionType,
setCategoryHiddenActionType: SetCategoryHiddenActionType,
setShortcutHiddenActionType: SetShortcutHiddenActionType,
setResultActionType: SetResultActionType,
setVariableActionType: SetVariableActionType,
Expand Down Expand Up @@ -155,6 +157,7 @@ constructor(
sendMQTTMessagesActionType,
sendTCPPacketActionType,
sendUDPPacketActionType,
setCategoryHiddenActionType,
setShortcutHiddenActionType,
setResultActionType,
setVariableActionType,
Expand Down
Original file line number Diff line number Diff line change
@@ -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<SetCategoryHiddenAction.Params> {
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,
)
}
Original file line number Diff line number Diff line change
@@ -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"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
),
)

Expand Down
4 changes: 4 additions & 0 deletions HTTPShortcuts/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,8 @@
<string name="action_type_change_icon_description">Change Shortcut Description</string>
<!-- Shortcut Action Title: Change Shortcut Visibility -->
<string name="action_type_change_shortcut_hidden_title">Show/Hide Shortcut</string>
<!-- Shortcut Action Title: Change Category Visibility -->
<string name="action_type_change_category_hidden_title">Show/Hide Category</string>
<!-- Shortcut Action Title: Enqueue a shortcut, i.e., execute it after the current one finishes -->
<string name="action_type_trigger_shortcut_title">Enqueue Shortcut</string>
<!-- Shortcut Action Title: Execute another shortcut immediately and return its result -->
Expand Down Expand Up @@ -1018,6 +1020,8 @@
<string name="error_shortcut_not_found_for_renaming">Could not rename shortcut \“%1$s\” because it does not exist.</string>
<!-- Error message: Shown in toast when "Set Shortcut Hidden" action cannot find the specified shortcut. %1$s is a placeholder for the shortcut's name -->
<string name="error_shortcut_not_found_for_set_visible">Could not change visibility of shortcut \“%1$s\” because it does not exist.</string>
<!-- Error message: Shown in toast when "Set Category Hidden" action cannot find the specified category. %1$s is a placeholder for the category's name -->
<string name="error_category_not_found_for_set_visible">Could not change visibility of category \“%1$s\” because it does not exist.</string>
<!-- Error message: Shown in toast when "Change Icon" action cannot find the specified shortcut. %1$s is a placeholder for the shortcut's name -->
<string name="error_shortcut_not_found_for_changing_icon">Could not change icon of shortcut \“%1$s\” because it does not exist.</string>
<!-- Error message: Shown in toast when "Change Description" action cannot find the specified shortcut. %1$s is a placeholder for the shortcut's name -->
Expand Down

0 comments on commit dadec2e

Please sign in to comment.