mirror of
https://github.com/google/pebble
synced 2026-10-18 09:14:11 +02:00
Import of the watch repository from Pebble
This commit is contained in:
@@ -0,0 +1,546 @@
|
||||
/*
|
||||
* 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 "health_detail_card.h"
|
||||
|
||||
#include "applib/pbl_std/pbl_std.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "resource/resource_ids.auto.h"
|
||||
#include "services/common/i18n/i18n.h"
|
||||
#include "services/normal/activity/health_util.h"
|
||||
#include "util/size.h"
|
||||
|
||||
#include "system/logging.h"
|
||||
|
||||
#define CORNER_RADIUS (3)
|
||||
|
||||
static void prv_draw_headings(HealthDetailCard *detail_card, GContext *ctx, const Layer *layer) {
|
||||
const int16_t rect_padding = PBL_IF_RECT_ELSE(5, 22);
|
||||
const int16_t rect_height = 35;
|
||||
|
||||
for (int i = 0; i < detail_card->num_headings; i++) {
|
||||
HealthDetailHeading *heading = &detail_card->headings[i];
|
||||
|
||||
if (!heading->primary_label) {
|
||||
continue;
|
||||
}
|
||||
|
||||
#if PBL_ROUND
|
||||
int16_t header_y_origin = ((detail_card->num_headings > 1) ? 22 : 32) + (i * (rect_height + 5));
|
||||
#endif
|
||||
|
||||
GRect header_rect = grect_inset(layer->bounds, GEdgeInsets(rect_padding));
|
||||
header_rect.origin.y += PBL_IF_RECT_ELSE(detail_card->y_origin, header_y_origin);
|
||||
header_rect.size.h = rect_height;
|
||||
|
||||
detail_card->y_origin += rect_height + rect_padding;
|
||||
|
||||
#if PBL_BW
|
||||
const GRect inner_rect = grect_inset(header_rect, GEdgeInsets(1));
|
||||
const uint16_t inner_corner_radius = CORNER_RADIUS - 1;
|
||||
graphics_context_set_stroke_color(ctx, heading->outline_color);
|
||||
graphics_draw_round_rect(ctx, &inner_rect, inner_corner_radius);
|
||||
graphics_draw_round_rect(ctx, &header_rect, CORNER_RADIUS);
|
||||
#else
|
||||
graphics_context_set_fill_color(ctx, heading->fill_color);
|
||||
graphics_fill_round_rect(ctx, &header_rect, CORNER_RADIUS, GCornersAll);
|
||||
#endif
|
||||
|
||||
const bool has_secondary_heading = heading->secondary_label;
|
||||
|
||||
GRect label_rect = header_rect;
|
||||
if (has_secondary_heading) {
|
||||
label_rect.size.w /= 2;
|
||||
}
|
||||
label_rect.size.h = 12; // Restrict to a single line
|
||||
|
||||
graphics_context_set_text_color(ctx, gcolor_legible_over(heading->fill_color));
|
||||
|
||||
graphics_draw_text(ctx, heading->primary_label, detail_card->heading_label_font,
|
||||
label_rect, GTextOverflowModeWordWrap, GTextAlignmentCenter, NULL);
|
||||
|
||||
const int16_t value_rect_y_padding = 12;
|
||||
|
||||
GRect value_rect = label_rect;
|
||||
value_rect.origin.y += value_rect_y_padding;
|
||||
|
||||
graphics_draw_text(ctx, heading->primary_value, detail_card->heading_value_font,
|
||||
value_rect, GTextOverflowModeWordWrap, GTextAlignmentCenter, NULL);
|
||||
|
||||
if (!heading->secondary_label) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const int separator_padding = 5;
|
||||
|
||||
GPoint separator_top_point = GPoint(header_rect.origin.x + (header_rect.size.w / 2) - 1,
|
||||
header_rect.origin.y + separator_padding);
|
||||
GPoint separator_bot_point = GPoint(separator_top_point.x, separator_top_point.y +
|
||||
header_rect.size.h - (separator_padding * 2) - 1);
|
||||
|
||||
graphics_draw_line(ctx, separator_top_point, separator_bot_point);
|
||||
|
||||
// draw another line to make the width 2px
|
||||
separator_top_point.x++;
|
||||
separator_bot_point.x++;
|
||||
graphics_draw_line(ctx, separator_top_point, separator_bot_point);
|
||||
|
||||
label_rect.origin.x += label_rect.size.w;
|
||||
value_rect.origin.x += value_rect.size.w;
|
||||
|
||||
graphics_draw_text(ctx, heading->secondary_label, detail_card->heading_label_font,
|
||||
label_rect, GTextOverflowModeWordWrap, GTextAlignmentCenter, NULL);
|
||||
|
||||
graphics_draw_text(ctx, heading->secondary_value, detail_card->heading_value_font,
|
||||
value_rect, GTextOverflowModeWordWrap, GTextAlignmentCenter, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_draw_subtitles(HealthDetailCard *detail_card, GContext *ctx, const Layer *layer) {
|
||||
const int16_t rect_padding = PBL_IF_RECT_ELSE(5, 0);
|
||||
const int16_t rect_height = PBL_IF_RECT_ELSE(23, 36);
|
||||
|
||||
for (int i = 0; i < detail_card->num_subtitles; i++) {
|
||||
HealthDetailSubtitle *subtitle = &detail_card->subtitles[i];
|
||||
|
||||
if (!subtitle->label) {
|
||||
continue;
|
||||
}
|
||||
|
||||
GRect subtitle_rect = grect_inset(layer->bounds, GEdgeInsets(rect_padding));
|
||||
subtitle_rect.origin.y += PBL_IF_RECT_ELSE(detail_card->y_origin, 125);
|
||||
subtitle_rect.size.h = rect_height;
|
||||
|
||||
detail_card->y_origin += rect_height + rect_padding;
|
||||
|
||||
graphics_context_set_fill_color(ctx, subtitle->fill_color);
|
||||
graphics_fill_round_rect(ctx, &subtitle_rect, CORNER_RADIUS, GCornersAll);
|
||||
|
||||
if (!gcolor_equal(subtitle->outline_color, GColorClear)) {
|
||||
graphics_context_set_stroke_color(ctx, subtitle->outline_color);
|
||||
graphics_draw_round_rect(ctx, &subtitle_rect, CORNER_RADIUS);
|
||||
}
|
||||
|
||||
// font offset
|
||||
subtitle_rect.origin.y -= PBL_IF_RECT_ELSE(1, 3);
|
||||
|
||||
graphics_context_set_text_color(ctx, gcolor_legible_over(subtitle->fill_color));
|
||||
graphics_draw_text(ctx, subtitle->label, detail_card->subtitle_font, subtitle_rect,
|
||||
GTextOverflowModeWordWrap, GTextAlignmentCenter, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_draw_progress_bar(GContext *ctx, HealthProgressBar *progress_bar, GColor bg_color,
|
||||
GColor fill_color, int current_progress, int typical_progress, int max_progress,
|
||||
bool hide_typical) {
|
||||
const GColor typical_color = PBL_IF_COLOR_ELSE(GColorYellow, GColorBlack);
|
||||
const GColor outline_color = PBL_IF_COLOR_ELSE(GColorClear, GColorBlack);
|
||||
|
||||
health_progress_bar_fill(ctx, progress_bar, bg_color, 0, HEALTH_PROGRESS_BAR_MAX_VALUE);
|
||||
|
||||
if (max_progress > 0) {
|
||||
const int current_fill = (current_progress * HEALTH_PROGRESS_BAR_MAX_VALUE) / max_progress;
|
||||
|
||||
health_progress_bar_fill(ctx, progress_bar, fill_color, 0, current_fill);
|
||||
|
||||
if (typical_progress > 0) {
|
||||
const int typical_fill = (typical_progress * HEALTH_PROGRESS_BAR_MAX_VALUE) / max_progress;
|
||||
health_progress_bar_mark(ctx, progress_bar, typical_color, typical_fill);
|
||||
}
|
||||
}
|
||||
|
||||
health_progress_bar_outline(ctx, progress_bar, outline_color);
|
||||
}
|
||||
|
||||
#if PBL_RECT
|
||||
static void prv_draw_progress_bar_in_zone(GContext *ctx, const GRect *zone_rect, GColor fill_color,
|
||||
int current_progress, int typical_progress, int max_progress, bool hide_typical) {
|
||||
const int16_t progress_bar_x = zone_rect->origin.x + PBL_IF_BW_ELSE(0, -1);
|
||||
const int16_t progress_bar_y = zone_rect->origin.y + 22;
|
||||
const int16_t progress_bar_width = zone_rect->size.w + PBL_IF_BW_ELSE(-2, 1);
|
||||
const int16_t progress_bar_height = 10 + PBL_IF_BW_ELSE(-1, 0);
|
||||
|
||||
HealthProgressSegment segments[] = {
|
||||
{
|
||||
// Left side vertical line (needed for the draw outline function to draw the verticle lines)
|
||||
.type = HealthProgressSegmentType_Corner,
|
||||
.points = {
|
||||
{progress_bar_x, progress_bar_y},
|
||||
{progress_bar_x, progress_bar_y + progress_bar_height},
|
||||
{progress_bar_x, progress_bar_y + progress_bar_height},
|
||||
{progress_bar_x, progress_bar_y},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Right side vertical line (needed for the draw outline function to draw the verticle lines)
|
||||
.type = HealthProgressSegmentType_Corner,
|
||||
.points = {
|
||||
{progress_bar_x + progress_bar_width, progress_bar_y},
|
||||
{progress_bar_x + progress_bar_width, progress_bar_y + progress_bar_height},
|
||||
{progress_bar_x + progress_bar_width, progress_bar_y + progress_bar_height},
|
||||
{progress_bar_x + progress_bar_width, progress_bar_y},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Horizontal bar from left line to right line
|
||||
.type = HealthProgressSegmentType_Horizontal,
|
||||
.amount_of_total = HEALTH_PROGRESS_BAR_MAX_VALUE,
|
||||
.mark_width = 124, // Arbitrarily chosen through trial and error
|
||||
.points = {
|
||||
{progress_bar_x, progress_bar_y + progress_bar_height},
|
||||
{progress_bar_x + progress_bar_width, progress_bar_y + progress_bar_height},
|
||||
{progress_bar_x + progress_bar_width, progress_bar_y},
|
||||
{progress_bar_x, progress_bar_y},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
HealthProgressBar progress_bar = {
|
||||
.num_segments = ARRAY_LENGTH(segments),
|
||||
.segments = segments,
|
||||
};
|
||||
|
||||
const GColor bg_color = PBL_IF_COLOR_ELSE(GColorDarkGray, GColorWhite);
|
||||
|
||||
prv_draw_progress_bar(ctx, &progress_bar, bg_color, fill_color, current_progress,
|
||||
typical_progress, max_progress, hide_typical);
|
||||
}
|
||||
|
||||
static void prv_draw_zones(HealthDetailCard *detail_card, GContext *ctx) {
|
||||
if (detail_card->num_zones <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int16_t rect_padding = 5;
|
||||
const int16_t rect_height = 33;
|
||||
|
||||
GRect zone_rect = grect_inset(detail_card->window.layer.bounds, GEdgeInsets(rect_padding));
|
||||
zone_rect.origin.y += detail_card->y_origin;
|
||||
zone_rect.size.h = rect_height;
|
||||
|
||||
for (int i = 0; i < detail_card->num_zones; i++) {
|
||||
HealthDetailZone *zone = &detail_card->zones[i];
|
||||
|
||||
graphics_context_set_text_color(ctx, PBL_IF_COLOR_ELSE(GColorWhite, GColorBlack));
|
||||
graphics_draw_text(ctx, zone->label, detail_card->subtitle_font, zone_rect,
|
||||
GTextOverflowModeWordWrap, GTextAlignmentLeft, NULL);
|
||||
|
||||
if (zone->show_crown) {
|
||||
const GSize label_size = app_graphics_text_layout_get_content_size(zone->label,
|
||||
detail_card->subtitle_font, zone_rect, GTextOverflowModeWordWrap, GTextAlignmentLeft);
|
||||
GPoint icon_offset = zone_rect.origin;
|
||||
icon_offset.x += label_size.w + 4;
|
||||
#if PBL_BW
|
||||
icon_offset.y += 2;
|
||||
#endif
|
||||
gdraw_command_image_draw(ctx, detail_card->icon_crown, icon_offset);
|
||||
}
|
||||
|
||||
prv_draw_progress_bar_in_zone(ctx, &zone_rect, zone->fill_color, zone->progress,
|
||||
detail_card->daily_avg, detail_card->max_progress, zone->hide_typical);
|
||||
|
||||
zone_rect.origin.y += rect_height + rect_padding;
|
||||
detail_card->y_origin += rect_height + rect_padding;
|
||||
}
|
||||
|
||||
detail_card->y_origin += rect_padding;
|
||||
}
|
||||
#endif // PBL_RECT
|
||||
|
||||
#if PBL_ROUND
|
||||
static uint16_t prv_get_num_rows_callback(MenuLayer *menu_layer,
|
||||
uint16_t section_index, void *context) {
|
||||
HealthDetailCard *detail_card = (HealthDetailCard *)context;
|
||||
return detail_card->num_zones + 1;
|
||||
}
|
||||
|
||||
static void prv_draw_row_callback(GContext *ctx, const Layer *cell_layer,
|
||||
MenuIndex *cell_index, void *context) {
|
||||
HealthDetailCard *detail_card = (HealthDetailCard *)context;
|
||||
|
||||
MenuIndex selected_index = menu_layer_get_selected_index(&detail_card->menu_layer);
|
||||
|
||||
if (cell_index->row == 0) {
|
||||
graphics_context_set_fill_color(ctx, detail_card->bg_color);
|
||||
graphics_fill_rect(ctx, &cell_layer->bounds);
|
||||
|
||||
prv_draw_headings(detail_card, ctx, cell_layer);
|
||||
prv_draw_subtitles(detail_card, ctx, cell_layer);
|
||||
return;
|
||||
}
|
||||
|
||||
HealthDetailZone *zone = &detail_card->zones[cell_index->row - 1];
|
||||
|
||||
const int16_t rect_padding = 5;
|
||||
|
||||
GRect label_rect = grect_inset(cell_layer->bounds, GEdgeInsets(rect_padding));
|
||||
|
||||
if (!menu_layer_is_index_selected(&detail_card->menu_layer, cell_index)) {
|
||||
label_rect.origin.y = (cell_index->row < selected_index.row) ? 3 : 22;
|
||||
|
||||
graphics_context_set_text_color(ctx, GColorWhite);
|
||||
graphics_draw_text(ctx, zone->label, detail_card->subtitle_font, label_rect,
|
||||
GTextOverflowModeWordWrap, GTextAlignmentCenter, NULL);
|
||||
} else {
|
||||
const GRect cell_bounds = grect_inset(cell_layer->bounds, GEdgeInsets(0, -1));
|
||||
|
||||
HealthProgressSegment segments[] = {
|
||||
{
|
||||
// Horizontal bar from left line to right line
|
||||
.type = HealthProgressSegmentType_Horizontal,
|
||||
.amount_of_total = HEALTH_PROGRESS_BAR_MAX_VALUE,
|
||||
.mark_width = 100, // Arbitrarily chosen through trial and error
|
||||
.points = {
|
||||
{cell_bounds.origin.x, cell_bounds.size.h},
|
||||
{cell_bounds.size.w, cell_bounds.size.h},
|
||||
{cell_bounds.size.w, cell_bounds.origin.y},
|
||||
{cell_bounds.origin.x, cell_bounds.origin.y},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
HealthProgressBar progress_bar = {
|
||||
.num_segments = ARRAY_LENGTH(segments),
|
||||
.segments = segments,
|
||||
};
|
||||
|
||||
prv_draw_progress_bar(ctx, &progress_bar, GColorLightGray, zone->fill_color, zone->progress,
|
||||
detail_card->daily_avg, detail_card->max_progress, zone->hide_typical);
|
||||
|
||||
label_rect.origin.y += 3;
|
||||
|
||||
if (zone->show_crown) {
|
||||
const GSize icon_size = gdraw_command_image_get_bounds_size(detail_card->icon_crown);
|
||||
GPoint icon_offset = GPoint((cell_layer->bounds.size.w / 2) - (icon_size.w / 2), 4);
|
||||
gdraw_command_image_draw(ctx, detail_card->icon_crown, icon_offset);
|
||||
|
||||
label_rect.origin.y += 8;
|
||||
}
|
||||
|
||||
graphics_context_set_text_color(ctx, GColorBlack);
|
||||
graphics_draw_text(ctx, zone->label, fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD), label_rect,
|
||||
GTextOverflowModeWordWrap, GTextAlignmentCenter, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static int16_t prv_get_cell_height_callback(MenuLayer *menu_layer,
|
||||
MenuIndex *cell_index, void *context) {
|
||||
if (cell_index->row == 0) {
|
||||
return menu_layer_is_index_selected(menu_layer, cell_index) ? DISP_ROWS : 0;
|
||||
}
|
||||
|
||||
return menu_layer_is_index_selected(menu_layer, cell_index) ? 50 : 54;
|
||||
}
|
||||
|
||||
static void prv_refresh_content_indicators(HealthDetailCard *detail_card) {
|
||||
const bool is_up_visible = (menu_layer_get_selected_index(&detail_card->menu_layer).row > 0);
|
||||
const bool is_down_visible = (menu_layer_get_selected_index(&detail_card->menu_layer).row <
|
||||
prv_get_num_rows_callback(&detail_card->menu_layer, 0, detail_card) - 1);
|
||||
|
||||
content_indicator_set_content_available(&detail_card->up_indicator,
|
||||
ContentIndicatorDirectionUp,
|
||||
is_up_visible);
|
||||
|
||||
content_indicator_set_content_available(&detail_card->down_indicator,
|
||||
ContentIndicatorDirectionDown,
|
||||
is_down_visible);
|
||||
}
|
||||
|
||||
static void prv_selection_changed_callback(struct MenuLayer *menu_layer, MenuIndex new_index,
|
||||
MenuIndex old_index, void *context) {
|
||||
HealthDetailCard *detail_card = (HealthDetailCard *)context;
|
||||
prv_refresh_content_indicators(detail_card);
|
||||
}
|
||||
#else // PBL_RECT
|
||||
static void prv_health_detail_scroll_layer_update_proc(Layer *layer, GContext *ctx) {
|
||||
ScrollLayer *scroll_layer = (ScrollLayer *)layer->parent;
|
||||
HealthDetailCard *detail_card = (HealthDetailCard *)scroll_layer->context;
|
||||
|
||||
detail_card->y_origin = 0;
|
||||
|
||||
prv_draw_headings(detail_card, ctx, &detail_card->window.layer);
|
||||
prv_draw_subtitles(detail_card, ctx, &detail_card->window.layer);
|
||||
prv_draw_zones(detail_card, ctx);
|
||||
|
||||
scroll_layer_set_content_size(&detail_card->scroll_layer,
|
||||
GSize(layer->bounds.size.w, detail_card->y_origin));
|
||||
}
|
||||
#endif // PBL_RECT
|
||||
|
||||
HealthDetailCard *health_detail_card_create(const HealthDetailCardConfig *config) {
|
||||
HealthDetailCard *detail_card = app_zalloc_check(sizeof(HealthDetailCard));
|
||||
window_init(&detail_card->window, WINDOW_NAME("Health Detail Card"));
|
||||
health_detail_card_configure(detail_card, config);
|
||||
GRect window_frame = detail_card->window.layer.frame;
|
||||
#if PBL_ROUND
|
||||
// setup menu layer
|
||||
MenuLayer *menu_layer = &detail_card->menu_layer;
|
||||
menu_layer_init(menu_layer, &window_frame);
|
||||
menu_layer_set_callbacks(menu_layer, detail_card, &(MenuLayerCallbacks) {
|
||||
.get_num_rows = prv_get_num_rows_callback,
|
||||
.get_cell_height = prv_get_cell_height_callback,
|
||||
.draw_row = prv_draw_row_callback,
|
||||
.selection_changed = prv_selection_changed_callback,
|
||||
});
|
||||
menu_layer_set_normal_colors(menu_layer, detail_card->bg_color, GColorWhite);
|
||||
menu_layer_set_highlight_colors(menu_layer, detail_card->bg_color, GColorBlack);
|
||||
menu_layer_set_click_config_onto_window(menu_layer, &detail_card->window);
|
||||
layer_add_child(&detail_card->window.layer, menu_layer_get_layer(menu_layer));
|
||||
|
||||
// setup content indicators
|
||||
const int content_indicator_height = 15;
|
||||
const GRect down_arrow_layer_frame = grect_inset(window_frame,
|
||||
GEdgeInsets(window_frame.size.h - content_indicator_height, 0, 0));
|
||||
layer_init(&detail_card->down_arrow_layer, &down_arrow_layer_frame);
|
||||
layer_add_child(&detail_card->window.layer, &detail_card->down_arrow_layer);
|
||||
content_indicator_init(&detail_card->down_indicator);
|
||||
|
||||
const GRect up_arrow_layer_frame = grect_inset(window_frame,
|
||||
GEdgeInsets(0, 0, window_frame.size.h - content_indicator_height));
|
||||
layer_init(&detail_card->up_arrow_layer, &up_arrow_layer_frame);
|
||||
layer_add_child(&detail_card->window.layer, &detail_card->up_arrow_layer);
|
||||
content_indicator_init(&detail_card->up_indicator);
|
||||
|
||||
ContentIndicatorConfig content_indicator_config = (ContentIndicatorConfig) {
|
||||
.layer = &detail_card->up_arrow_layer,
|
||||
.colors.foreground = gcolor_legible_over(detail_card->bg_color),
|
||||
.colors.background = detail_card->bg_color,
|
||||
};
|
||||
content_indicator_configure_direction(&detail_card->up_indicator, ContentIndicatorDirectionUp,
|
||||
&content_indicator_config);
|
||||
content_indicator_config.layer = &detail_card->down_arrow_layer;
|
||||
content_indicator_configure_direction(&detail_card->down_indicator, ContentIndicatorDirectionDown,
|
||||
&content_indicator_config);
|
||||
prv_refresh_content_indicators(detail_card);
|
||||
#else // PBL_RECT
|
||||
// setup scroll layer
|
||||
scroll_layer_init(&detail_card->scroll_layer, &window_frame);
|
||||
scroll_layer_set_click_config_onto_window(&detail_card->scroll_layer, &detail_card->window);
|
||||
scroll_layer_set_context(&detail_card->scroll_layer, detail_card);
|
||||
scroll_layer_set_shadow_hidden(&detail_card->scroll_layer, true);
|
||||
layer_add_child(&detail_card->window.layer, (Layer *)&detail_card->scroll_layer);
|
||||
layer_set_update_proc(&detail_card->scroll_layer.content_sublayer,
|
||||
prv_health_detail_scroll_layer_update_proc);
|
||||
#endif // PBL_RECT
|
||||
detail_card->icon_crown = gdraw_command_image_create_with_resource(RESOURCE_ID_HEALTH_APP_CROWN);
|
||||
detail_card->heading_label_font = fonts_get_system_font(FONT_KEY_GOTHIC_14_BOLD);
|
||||
detail_card->heading_value_font = fonts_get_system_font(FONT_KEY_GOTHIC_18_BOLD);
|
||||
detail_card->subtitle_font = fonts_get_system_font(FONT_KEY_GOTHIC_18_BOLD);
|
||||
return detail_card;
|
||||
}
|
||||
|
||||
void health_detail_card_destroy(HealthDetailCard *detail_card) {
|
||||
if (!detail_card) {
|
||||
return;
|
||||
}
|
||||
gdraw_command_image_destroy(detail_card->icon_crown);
|
||||
#if PBL_ROUND
|
||||
menu_layer_deinit(&detail_card->menu_layer);
|
||||
content_indicator_deinit(&detail_card->down_indicator);
|
||||
content_indicator_deinit(&detail_card->up_indicator);
|
||||
#else
|
||||
scroll_layer_deinit(&detail_card->scroll_layer);
|
||||
#endif
|
||||
i18n_free_all(detail_card);
|
||||
app_free(detail_card);
|
||||
}
|
||||
|
||||
void health_detail_card_configure(HealthDetailCard *detail_card,
|
||||
const HealthDetailCardConfig *config) {
|
||||
if (!detail_card || !config) {
|
||||
return;
|
||||
}
|
||||
detail_card->bg_color = config->bg_color;
|
||||
window_set_background_color(&detail_card->window, detail_card->bg_color);
|
||||
if (config->num_headings) {
|
||||
detail_card->num_headings = config->num_headings;
|
||||
detail_card->headings = config->headings;
|
||||
}
|
||||
if (config->num_subtitles) {
|
||||
detail_card->num_subtitles = config->num_subtitles;
|
||||
detail_card->subtitles = config->subtitles;
|
||||
}
|
||||
detail_card->daily_avg = config->daily_avg;
|
||||
detail_card->max_progress = MAX(config->weekly_max, detail_card->daily_avg);
|
||||
detail_card->max_progress = detail_card->max_progress * 11 / 10; // add 10%;
|
||||
if (config->num_zones) {
|
||||
detail_card->num_zones = config->num_zones;
|
||||
detail_card->zones = config->zones;
|
||||
}
|
||||
if (config->data) {
|
||||
detail_card->data = config->data;
|
||||
}
|
||||
}
|
||||
|
||||
void health_detail_card_set_render_day_zones(HealthDetailZone *zones, int16_t *num_zones,
|
||||
int32_t *weekly_max, bool format_hours_and_minutes, bool show_crown, GColor fill_color,
|
||||
GColor today_fill_color, int32_t *day_data, void *i18n_owner) {
|
||||
time_t time_utc = rtc_get_time();
|
||||
struct tm time_tm;
|
||||
|
||||
int max_data = 0;
|
||||
int crown_index = 0;
|
||||
|
||||
*num_zones = DAYS_PER_WEEK;
|
||||
|
||||
for (int i = 0; i < *num_zones; i++) {
|
||||
localtime_r(&time_utc, &time_tm);
|
||||
|
||||
const bool is_today = (i == 0);
|
||||
|
||||
const size_t buffer_size = 32;
|
||||
zones[i] = (HealthDetailZone) {
|
||||
.label = app_zalloc_check(buffer_size),
|
||||
.progress = day_data[i],
|
||||
.fill_color = is_today ? PBL_IF_ROUND_ELSE(fill_color, today_fill_color) : fill_color,
|
||||
.hide_typical = is_today,
|
||||
};
|
||||
|
||||
char *label_ptr = zones[i].label;
|
||||
|
||||
int pos = 0;
|
||||
|
||||
if (i == 0) {
|
||||
pos += snprintf(label_ptr, buffer_size, "%s ", i18n_get("Today", i18n_owner));
|
||||
} else {
|
||||
pos += strftime(label_ptr, buffer_size, "%a ", &time_tm);
|
||||
}
|
||||
|
||||
if (day_data[i] > 0) {
|
||||
if (format_hours_and_minutes) {
|
||||
health_util_format_hours_and_minutes(label_ptr + pos, buffer_size - pos, day_data[i],
|
||||
i18n_owner);
|
||||
} else {
|
||||
snprintf(label_ptr + pos, buffer_size - pos, "%"PRId32, day_data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (day_data[i] > *weekly_max) {
|
||||
*weekly_max = day_data[i];
|
||||
}
|
||||
|
||||
if (day_data[i] > max_data) {
|
||||
max_data = day_data[i];
|
||||
crown_index = i;
|
||||
}
|
||||
|
||||
time_utc -= SECONDS_PER_DAY;
|
||||
}
|
||||
|
||||
if (crown_index && show_crown) {
|
||||
zones[crown_index].show_crown = true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user