mirror of
https://github.com/google/pebble.git
synced 2025-11-23 07:50:55 -05:00
189 lines
6.4 KiB
C
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;
|
|
}
|
|
}
|