Files
pebble/src/fw/shell/normal/battery_ui.c
2025-01-27 11:38:16 -08:00

189 lines
6.4 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 "battery_ui.h"
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "applib/ui/dialogs/dialog_private.h"
#include "applib/ui/dialogs/simple_dialog.h"
#include "applib/ui/window_stack.h"
#include "applib/ui/ui.h"
#include "kernel/event_loop.h"
#include "kernel/pbl_malloc.h"
#include "kernel/ui/kernel_ui.h"
#include "kernel/ui/modals/modal_manager.h"
#include "process_management/app_manager.h"
#include "resource/resource_ids.auto.h"
#include "services/common/battery/battery_curve.h"
#include "services/common/clock.h"
#include "services/common/i18n/i18n.h"
#include "services/common/light.h"
#include "system/logging.h"
#include "util/time/time.h"
typedef void (*DialogUpdateFn)(Dialog *, void *);
static Dialog *s_dialog = NULL;
typedef struct {
uint32_t percent;
GColor background_color;
ResourceId warning_icon;
} BatteryWarningDisplayData;
// UI Callbacks
///////////////////////
static const GColor s_warning_color[] = {
{ .argb = GColorLightGrayARGB8 },
{ .argb = GColorRedARGB8 },
};
static const ResourceId s_warning_icon[] = {
RESOURCE_ID_BATTERY_ICON_LOW_LARGE,
RESOURCE_ID_BATTERY_ICON_VERY_LOW_LARGE
};
static void prv_update_ui_fully_charged(Dialog *dialog, void *ignored) {
dialog_set_text(dialog, i18n_get("Fully Charged", dialog));
dialog_set_background_color(dialog, GColorKellyGreen);
dialog_set_icon(dialog, RESOURCE_ID_BATTERY_ICON_FULL_LARGE);
}
static void prv_update_ui_charging(Dialog *dialog, void *ignored) {
dialog_set_text(dialog, i18n_get("Charging", dialog));
dialog_set_background_color(dialog, GColorLightGray);
dialog_set_icon(dialog, RESOURCE_ID_BATTERY_ICON_CHARGING_LARGE);
}
static void prv_update_ui_warning(Dialog *dialog, void *context) {
const BatteryWarningDisplayData *data = context;
const uint32_t percent = data->percent;
dialog_set_background_color(dialog, data->background_color);
const size_t warning_length = 64;
char buffer[warning_length];
const uint32_t battery_hours_left = battery_curve_get_hours_remaining(percent);
const char *message = clock_get_relative_daypart_string(rtc_get_time(), battery_hours_left);
if (message) {
snprintf(buffer, warning_length, i18n_get("Powered 'til %s", dialog),
i18n_get(message, dialog));
dialog_set_text(dialog, buffer);
}
dialog_set_icon(dialog, data->warning_icon);
}
static void prv_dialog_on_unload(void *context) {
Dialog *dialog = context;
i18n_free_all(dialog);
if (dialog == s_dialog) {
s_dialog = NULL;
}
}
static void prv_display_modal(WindowStack *stack, DialogUpdateFn update_fn, void *data) {
if (s_dialog) {
update_fn(s_dialog, data);
return;
}
SimpleDialog *new_simple_dialog = simple_dialog_create(
WINDOW_NAME("Battery Status"));
Dialog *new_dialog = simple_dialog_get_dialog(new_simple_dialog);
dialog_set_callbacks(new_dialog, &(DialogCallbacks) {
.unload = prv_dialog_on_unload,
}, NULL);
update_fn(new_dialog, data);
Dialog *old_dialog = s_dialog;
s_dialog = new_dialog;
simple_dialog_push(new_simple_dialog, stack);
#if PBL_ROUND
// For circular display, to fit some battery_ui messages requires 3 lines
// Simple dialog only allows up to 2 lines, so adjust here
// This has to occur after the dialog push has been called
TextLayer *text_layer = &new_simple_dialog->dialog.text_layer;
GContext *ctx = graphics_context_get_current_context();
const int font_height = fonts_get_font_height(text_layer->font);
const int text_cap_height = fonts_get_font_cap_offset(text_layer->font);
const int max_text_height = 2 * font_height + text_cap_height;
const int32_t text_height = text_layer_get_content_size(ctx, text_layer).h;
if (text_height > max_text_height) {
// Values used below were to improve visual aesthetics and were reviewed by design
const int num_lines = 3;
const int line_spacing_delta = -4;
const int text_shift_y = -2;
const int text_box_height = (font_height + text_cap_height) * num_lines +
line_spacing_delta * (num_lines - 1);
const int text_flow_inset = 6; // Modify to allow longer central lines
text_layer_enable_screen_text_flow_and_paging(text_layer, text_flow_inset);
text_layer_set_size(text_layer, GSize(DISP_COLS, text_box_height));
text_layer->layer.frame.origin.y += text_shift_y;
text_layer_set_line_spacing_delta(text_layer, line_spacing_delta);
}
#endif
if (old_dialog) {
dialog_pop(old_dialog);
}
}
// Public API
////////////////////
void battery_ui_display_plugged(void) {
// If we're plugged in for charging, we want to alert the user of this,
// but we don't want to overlay ourselves over anything they may have
// on the screen at the moment.
WindowStack *stack = modal_manager_get_window_stack(ModalPriorityGeneric);
prv_display_modal(stack, prv_update_ui_charging, NULL);
}
void battery_ui_display_fully_charged(void) {
// If we're plugged in (charged), we want to alert the user of this,
// but we don't want to overlay ourselves over anything they may have
// on the screen at the moment.
WindowStack *stack = modal_manager_get_window_stack(ModalPriorityGeneric);
prv_display_modal(stack, prv_update_ui_fully_charged, NULL);
}
void battery_ui_display_warning(uint32_t percent, BatteryUIWarningLevel warning_level) {
// If we're not plugged in, that means we hit a critical power notification,
// so we want to alert the user, subverting any non-critical windows they
// have on the screen.
WindowStack *stack = modal_manager_get_window_stack(ModalPriorityAlert);
BatteryWarningDisplayData display_data = {
.percent = percent,
.background_color = s_warning_color[warning_level],
.warning_icon = s_warning_icon[warning_level],
};
prv_display_modal(stack, prv_update_ui_warning, &display_data);
}
void battery_ui_dismiss_modal(void) {
if (s_dialog) {
dialog_pop(s_dialog);
s_dialog = NULL;
}
}