mirror of
https://github.com/google/pebble
synced 2026-10-18 01:04:12 +02:00
547 lines
21 KiB
C
547 lines
21 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 "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 vertical 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 vertical 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;
|
|
}
|
|
}
|