pebble/src/fw/applib/ui/action_toggle.c
2025-01-27 11:38:16 -08:00

202 lines
6.8 KiB
C

/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "action_toggle.h"
#include "applib/app_launch_button.h"
#include "applib/app_launch_reason.h"
#include "applib/applib_malloc.auto.h"
#include "applib/ui/dialogs/actionable_dialog.h"
#include "applib/ui/dialogs/dialog.h"
#include "applib/ui/dialogs/simple_dialog.h"
#include "applib/ui/vibes.h"
#include "applib/ui/window_manager.h"
#include "kernel/ui/modals/modal_manager.h"
#include "process_state/app_state/app_state.h"
#include "resource/resource_ids.auto.h"
#include "services/common/i18n/i18n.h"
#include "system/passert.h"
typedef struct ActionToggleDialogConfig {
const char *window_name;
const char *message;
ResourceId icon;
GColor text_color;
GColor background_color;
unsigned int timeout_ms;
} ActionToggleDialogConfig;
typedef struct ActionToggleContext {
ActionToggleConfig config;
bool enabled;
bool defer_destroy;
} ActionToggleContext;
static void prv_action_toggle_dialog_unload(void *context);
static bool prv_should_prompt(const ActionToggleConfig *config);
static ActionToggleState prv_get_toggled_state_index(ActionToggleContext *ctx) {
return ctx->enabled ? ActionToggleState_Disabled : ActionToggleState_Enabled;
}
static void prv_setup_state_config(ActionToggleContext *ctx, ActionToggleDialogConfig *config,
ActionToggleDialogType dialog_type) {
if (!config->window_name) {
config->window_name = ctx->config.impl->window_name;
}
if (!config->icon) {
config->icon = ctx->config.impl->icons[dialog_type];
}
if (!config->timeout_ms) {
// Set the default prompt or result dialog timeout
config->timeout_ms = prv_should_prompt(&ctx->config) ? 4500 : 1800;
}
if (!config->text_color.argb) {
config->text_color = GColorBlack;
}
if (!config->background_color.argb) {
config->background_color = !ctx->enabled ? GColorMediumAquamarine : GColorMelon;
}
}
static void prv_setup_dialog(Dialog *dialog, const ActionToggleDialogConfig *config,
void *context) {
const char *msg = i18n_get(config->message, dialog);
dialog_set_text(dialog, msg);
i18n_free(msg, dialog);
dialog_set_icon(dialog, config->icon);
dialog_set_text_color(dialog, config->text_color);
dialog_set_background_color(dialog, config->background_color);
dialog_set_timeout(dialog, config->timeout_ms);
dialog_set_callbacks(dialog, &(DialogCallbacks) {
.unload = prv_action_toggle_dialog_unload,
}, context);
}
static void prv_vibe(const bool enabled) {
if (enabled) {
vibes_short_pulse();
} else {
vibes_double_pulse();
}
}
static WindowStack *prv_get_window_stack(void) {
return window_manager_get_window_stack(ModalPriorityNotification);
}
static void prv_push_result_dialog(ActionToggleContext *ctx) {
ActionToggleDialogConfig config = {
.message = ctx->config.impl->result_messages[prv_get_toggled_state_index(ctx)],
};
prv_setup_state_config(ctx, &config, ActionToggleDialogType_Result);
SimpleDialog *simple_dialog = simple_dialog_create(config.window_name);
prv_setup_dialog(simple_dialog_get_dialog(simple_dialog), &config, (void *)ctx);
simple_dialog_set_icon_animated(simple_dialog, !ctx->config.impl->result_icon_static);
simple_dialog_push(simple_dialog, prv_get_window_stack());
}
static bool prv_call_get_state_callback(ActionToggleContext *ctx) {
if (ctx->config.impl->callbacks.get_state) {
ctx->enabled = ctx->config.impl->callbacks.get_state(ctx->config.context);
}
return ctx->enabled;
}
static void prv_call_set_state_callback(ActionToggleContext *ctx) {
if (!ctx->config.impl->callbacks.set_state) {
return;
}
const bool next_state = !ctx->enabled;
ctx->config.impl->callbacks.set_state(next_state, ctx->config.context);
ctx->enabled = next_state;
if (ctx->config.set_exit_reason) {
app_exit_reason_set(APP_EXIT_ACTION_PERFORMED_SUCCESSFULLY);
}
prv_vibe(next_state);
}
static void prv_handle_prompt_confirm(ClickRecognizerRef recognizer, void *context) {
ActionableDialog *actionable_dialog = context;
ActionToggleContext *ctx = actionable_dialog->dialog.callback_context;
prv_push_result_dialog(ctx);
// Don't destroy the context since it is being reused for the result dialog
ctx->defer_destroy = true;
actionable_dialog_pop(actionable_dialog);
prv_call_set_state_callback(ctx);
}
static void prv_action_toggle_dialog_unload(void *context) {
ActionToggleContext *ctx = context;
if (!ctx) {
return;
}
if (ctx->defer_destroy) {
ctx->defer_destroy = false;
return;
}
applib_free(ctx);
}
static void prv_prompt_click_config_provider(void *context) {
window_single_click_subscribe(BUTTON_ID_SELECT, prv_handle_prompt_confirm);
}
static void prv_push_prompt_dialog(ActionToggleContext *ctx) {
ActionToggleDialogConfig config = {
.message = ctx->config.impl->prompt_messages[prv_get_toggled_state_index(ctx)],
};
prv_setup_state_config(ctx, &config, ActionToggleDialogType_Prompt);
ActionableDialog *actionable_dialog = actionable_dialog_create(config.window_name);
actionable_dialog_set_action_bar_type(actionable_dialog, DialogActionBarConfirm, NULL);
actionable_dialog_set_click_config_provider(actionable_dialog, prv_prompt_click_config_provider);
prv_setup_dialog(actionable_dialog_get_dialog(actionable_dialog), &config, (void *)ctx);
actionable_dialog_push(actionable_dialog, prv_get_window_stack());
}
static bool prv_should_prompt(const ActionToggleConfig *config) {
switch (config->prompt) {
case ActionTogglePrompt_Auto:
#if PLATFORM_SPALDING
return ((pebble_task_get_current() == PebbleTask_App) &&
(app_launch_reason() == APP_LAUNCH_QUICK_LAUNCH) &&
(app_launch_button() == BUTTON_ID_BACK));
#else
return false;
#endif
case ActionTogglePrompt_NoPrompt:
return false;
case ActionTogglePrompt_Prompt:
return true;
}
return false;
}
void action_toggle_push(const ActionToggleConfig *config) {
ActionToggleContext *context = applib_zalloc(sizeof(ActionToggleContext));
PBL_ASSERTN(context);
*context = (ActionToggleContext) {
.config = *config,
};
prv_call_get_state_callback(context);
if (prv_should_prompt(config)) {
prv_push_prompt_dialog(context);
} else {
prv_push_result_dialog(context);
prv_call_set_state_callback(context);
}
}