mirror of
https://github.com/google/pebble.git
synced 2025-07-19 12:34:48 -04:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
390
src/fw/apps/system_apps/settings/settings_notifications.c
Normal file
390
src/fw/apps/system_apps/settings/settings_notifications.c
Normal file
|
@ -0,0 +1,390 @@
|
|||
/*
|
||||
* 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 "settings_notifications_private.h"
|
||||
#include "settings_menu.h"
|
||||
#include "settings_option_menu.h"
|
||||
#include "settings_window.h"
|
||||
|
||||
#include "applib/event_service_client.h"
|
||||
#include "applib/fonts/fonts.h"
|
||||
#include "applib/ui/action_menu_window_private.h"
|
||||
#include "applib/ui/app_window_stack.h"
|
||||
#include "applib/ui/option_menu_window.h"
|
||||
#include "applib/ui/ui.h"
|
||||
#include "drivers/battery.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "popups/notifications/notification_window.h"
|
||||
#include "services/common/analytics/analytics.h"
|
||||
#include "services/common/i18n/i18n.h"
|
||||
#include "services/normal/notifications/alerts_preferences_private.h"
|
||||
#include "services/normal/notifications/alerts_private.h"
|
||||
#include "services/normal/vibes/vibe_intensity.h"
|
||||
#include "shell/prefs.h"
|
||||
#include "shell/system_theme.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/size.h"
|
||||
#include "util/time/time.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
// Offset between vibe intensity menu item index and vibe intensity enum values
|
||||
#define INTENSITY_ROW_OFFSET 1
|
||||
|
||||
typedef struct {
|
||||
SettingsCallbacks callbacks;
|
||||
EventServiceInfo battery_connection_event_info;
|
||||
} SettingsNotificationsData;
|
||||
|
||||
enum NotificationsItem {
|
||||
NotificationsItemFilter,
|
||||
#if !CAPABILITY_HAS_VIBE_SCORES
|
||||
NotificationsItemVibration,
|
||||
#endif
|
||||
NotificationsItemTextSize,
|
||||
NotificationsItemWindowTimeout,
|
||||
NotificationsItem_Count,
|
||||
};
|
||||
|
||||
// Filter Alerts
|
||||
//////////////////////////
|
||||
|
||||
#define NUM_ALERT_MODES_IN_LIST 3
|
||||
|
||||
// These aren't all of the values of AlertMask, so to add extra ones you have to update both of
|
||||
// these arrays
|
||||
|
||||
static const AlertMask s_alert_mode_values[NUM_ALERT_MODES_IN_LIST] = {
|
||||
AlertMaskAllOn,
|
||||
AlertMaskPhoneCalls,
|
||||
AlertMaskAllOff,
|
||||
};
|
||||
|
||||
static const char *s_alert_mode_labels[NUM_ALERT_MODES_IN_LIST] = {
|
||||
i18n_noop("Allow All Notifications"),
|
||||
i18n_noop("Allow Phone Calls Only"),
|
||||
i18n_noop("Mute All Notifications"),
|
||||
};
|
||||
|
||||
static const char *prv_alert_mask_to_label(AlertMask mask) {
|
||||
for (uint32_t i = 0; i < NUM_ALERT_MODES_IN_LIST; i++) {
|
||||
if (s_alert_mode_values[i] == mask) {
|
||||
return s_alert_mode_labels[i];
|
||||
}
|
||||
}
|
||||
return "???";
|
||||
}
|
||||
|
||||
static void prv_filter_menu_select(OptionMenu *option_menu, int selection, void *context) {
|
||||
alerts_set_mask(s_alert_mode_values[selection]);
|
||||
app_window_stack_remove(&option_menu->window, true /* animated */);
|
||||
}
|
||||
|
||||
static void prv_filter_menu_push(SettingsNotificationsData *data) {
|
||||
AlertMask mask = alerts_get_mask();
|
||||
size_t cycle_len = ARRAY_LENGTH(s_alert_mode_values);
|
||||
size_t index = 0;
|
||||
// TODO PBL-24306: update once AlertMask logic is made safer
|
||||
for (size_t i = 0; i < cycle_len; i++) {
|
||||
if (s_alert_mode_values[i] == mask) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const OptionMenuCallbacks callbacks = {
|
||||
.select = prv_filter_menu_select,
|
||||
};
|
||||
/// The option in the Settings app for filtering notifications by type.
|
||||
const char *title = i18n_noop("Filter");
|
||||
settings_option_menu_push(
|
||||
title, OptionMenuContentType_DoubleLine, index, &callbacks, cycle_len,
|
||||
true /* icons_enabled */, s_alert_mode_labels, data);
|
||||
}
|
||||
|
||||
// Vibe Settings (If vibes scores disabled for this model)
|
||||
//////////////////////////
|
||||
#if !CAPABILITY_HAS_VIBE_SCORES
|
||||
static const char *strings_for_vibe_intensities[] = {
|
||||
i18n_ctx_noop("NotifVibe", "Disabled"),
|
||||
i18n_ctx_noop("NotifVibe", "Low"),
|
||||
i18n_ctx_noop("NotifVibe", "Medium"),
|
||||
i18n_ctx_noop("NotifVibe", "High")
|
||||
};
|
||||
|
||||
static void prv_vibe_menu_select(OptionMenu *option_menu, int selection, void *context) {
|
||||
const bool enable_vibration = (selection != 0);
|
||||
const VibeIntensity new_vibe_intensity = enable_vibration ? (selection - INTENSITY_ROW_OFFSET) :
|
||||
DEFAULT_VIBE_INTENSITY;
|
||||
|
||||
alerts_set_vibrate(enable_vibration);
|
||||
alerts_preferences_set_vibe_intensity(new_vibe_intensity);
|
||||
vibe_intensity_set(new_vibe_intensity);
|
||||
|
||||
if (enable_vibration) {
|
||||
vibes_short_pulse();
|
||||
}
|
||||
|
||||
app_window_stack_remove(&option_menu->window, true /* animated */);
|
||||
}
|
||||
|
||||
static void prv_vibe_menu_push(SettingsNotificationsData *data) {
|
||||
const OptionMenuCallbacks callbacks = {
|
||||
.select = prv_vibe_menu_select,
|
||||
};
|
||||
/// The option in the Settings app for choosing a vibration intensity for notifications.
|
||||
const char *title = i18n_noop("Vibration");
|
||||
uint32_t selected = vibe_intensity_get() + INTENSITY_ROW_OFFSET;
|
||||
if (!alerts_get_vibrate()) {
|
||||
selected = 0;
|
||||
}
|
||||
settings_option_menu_push(
|
||||
title, OptionMenuContentType_SingleLine, selected, &callbacks,
|
||||
ARRAY_LENGTH(strings_for_vibe_intensities), true /* icons_enabled */,
|
||||
strings_for_vibe_intensities, data);
|
||||
}
|
||||
#endif /* !CAPABILITY_HAS_VIBE_SCORES */
|
||||
|
||||
// Text Size
|
||||
////////////////////////
|
||||
|
||||
static const char *s_text_size_names[] = {
|
||||
[SettingsContentSize_Small] = i18n_noop("Smaller"),
|
||||
[SettingsContentSize_Default] = i18n_noop("Default"),
|
||||
[SettingsContentSize_Large] = i18n_noop("Larger"),
|
||||
};
|
||||
|
||||
static void prv_text_size_menu_select(OptionMenu *option_menu, int selection, void *context) {
|
||||
system_theme_set_content_size(settings_content_size_to_preferred_size(selection));
|
||||
app_window_stack_remove(&option_menu->window, true /* animated */);
|
||||
}
|
||||
|
||||
static void prv_text_size_menu_push(SettingsNotificationsData *data) {
|
||||
const OptionMenuCallbacks callbacks = {
|
||||
.select = prv_text_size_menu_select,
|
||||
};
|
||||
/// The option in the Settings app for choosing the text size of notifications.
|
||||
const char *title = i18n_noop("Text Size");
|
||||
const SettingsContentSize index =
|
||||
settings_content_size_from_preferred_size(system_theme_get_content_size());
|
||||
settings_option_menu_push(
|
||||
title, OptionMenuContentType_SingleLine, index, &callbacks, SettingsContentSizeCount,
|
||||
true /* icons_enabled */, s_text_size_names, data);
|
||||
}
|
||||
|
||||
// Text Size
|
||||
////////////////////////
|
||||
|
||||
// NOTE: Keep the following two arrays in sync and with the same size.
|
||||
static const uint32_t s_window_timeouts_ms[] = {
|
||||
15 * MS_PER_SECOND,
|
||||
1 * MS_PER_MINUTE,
|
||||
NOTIF_WINDOW_TIMEOUT_DEFAULT,
|
||||
10 * MS_PER_MINUTE,
|
||||
NOTIF_WINDOW_TIMEOUT_INFINITE
|
||||
};
|
||||
|
||||
static const char *s_window_timeouts_labels[] = {
|
||||
/// 15 Second Notification Window Timeout
|
||||
i18n_noop("15 Seconds"),
|
||||
/// 1 Minute Notification Window Timeout
|
||||
i18n_noop("1 Minute"),
|
||||
/// 3 Minute Notification Window Timeout
|
||||
i18n_noop("3 Minutes"),
|
||||
/// 10 Minute Notification Window Timeout
|
||||
i18n_noop("10 Minutes"),
|
||||
/// No Notification Window Timeout
|
||||
i18n_noop("None"),
|
||||
};
|
||||
|
||||
_Static_assert(ARRAY_LENGTH(s_window_timeouts_ms) == ARRAY_LENGTH(s_window_timeouts_labels), "");
|
||||
|
||||
static int prv_window_timeout_get_selection_index(void) {
|
||||
const int DEFAULT_IDX = 2;
|
||||
// Double check no one has fudged with the order and the fallback/default
|
||||
PBL_ASSERTN(s_window_timeouts_ms[DEFAULT_IDX] == NOTIF_WINDOW_TIMEOUT_DEFAULT);
|
||||
|
||||
const uint32_t timeout_ms = alerts_preferences_get_notification_window_timeout_ms();
|
||||
for (size_t i = 0; i < ARRAY_LENGTH(s_window_timeouts_ms); i++) {
|
||||
if (s_window_timeouts_ms[i] == timeout_ms) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
// Should never happen (only should happen if we remove a timeout and don't migrate the user
|
||||
// to a new setting automatically
|
||||
return DEFAULT_IDX;
|
||||
}
|
||||
|
||||
static void prv_window_timeout_menu_select(OptionMenu *option_menu, int selection, void *context) {
|
||||
alerts_preferences_set_notification_window_timeout_ms(s_window_timeouts_ms[selection]);
|
||||
app_window_stack_remove(&option_menu->window, true /* animated */);
|
||||
}
|
||||
|
||||
static void prv_window_timeout_menu_push(SettingsNotificationsData *data) {
|
||||
const int index = prv_window_timeout_get_selection_index();
|
||||
const OptionMenuCallbacks callbacks = {
|
||||
.select = prv_window_timeout_menu_select,
|
||||
};
|
||||
/// Status bar title for the Notification Window Timeout settings screen
|
||||
const char *title = i18n_noop("Timeout");
|
||||
settings_option_menu_push(
|
||||
title, OptionMenuContentType_SingleLine, index, &callbacks,
|
||||
ARRAY_LENGTH(s_window_timeouts_labels), true /* icons_enabled */, s_window_timeouts_labels,
|
||||
data);
|
||||
}
|
||||
|
||||
// Menu Layer Callbacks
|
||||
////////////////////////
|
||||
|
||||
static uint16_t prv_num_rows_cb(SettingsCallbacks *context) {
|
||||
return NotificationsItem_Count;
|
||||
}
|
||||
|
||||
static void prv_draw_row_cb(SettingsCallbacks *context, GContext *ctx,
|
||||
const Layer *cell_layer, uint16_t row, bool selected) {
|
||||
SettingsNotificationsData *data = ((SettingsOptionMenuData *)context)->context;
|
||||
const char *subtitle = NULL;
|
||||
const char *title = NULL;
|
||||
|
||||
switch (row) {
|
||||
case NotificationsItemFilter:
|
||||
title = i18n_noop("Filter");
|
||||
subtitle = prv_alert_mask_to_label(alerts_get_mask());
|
||||
break;
|
||||
#if !CAPABILITY_HAS_VIBE_SCORES
|
||||
case NotificationsItemVibration:
|
||||
title = i18n_noop("Vibration");
|
||||
if (battery_is_usb_connected()) {
|
||||
subtitle = i18n_noop("Disabled (Plugged In)");
|
||||
} else if (alerts_get_vibrate()) {
|
||||
subtitle = strings_for_vibe_intensities[vibe_intensity_get() + INTENSITY_ROW_OFFSET];
|
||||
} else {
|
||||
subtitle = strings_for_vibe_intensities[0];
|
||||
}
|
||||
break;
|
||||
#endif /* !CAPABILITY_HAS_VIBE_SCORES */
|
||||
case NotificationsItemTextSize: {
|
||||
/// String within Settings->Notifications that describes the text font size
|
||||
title = i18n_noop("Text Size");
|
||||
const SettingsContentSize index =
|
||||
settings_content_size_from_preferred_size(system_theme_get_content_size());
|
||||
subtitle = (index < SettingsContentSizeCount) ? s_text_size_names[index] : "";
|
||||
break;
|
||||
}
|
||||
case NotificationsItemWindowTimeout: {
|
||||
/// String within Settings->Notifications that describes the window timeout setting
|
||||
title = i18n_noop("Timeout");
|
||||
subtitle = s_window_timeouts_labels[prv_window_timeout_get_selection_index()];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
WTF;
|
||||
}
|
||||
|
||||
menu_cell_basic_draw(ctx, cell_layer, i18n_get(title, data), i18n_get(subtitle, data), NULL);
|
||||
}
|
||||
|
||||
static void prv_deinit_cb(SettingsCallbacks *context) {
|
||||
SettingsNotificationsData *data = (SettingsNotificationsData *)context;
|
||||
i18n_free_all(data);
|
||||
app_free(data);
|
||||
}
|
||||
|
||||
static void prv_select_click_cb(SettingsCallbacks *context, uint16_t row) {
|
||||
SettingsNotificationsData *data = (SettingsNotificationsData *) context;
|
||||
|
||||
switch (row) {
|
||||
case NotificationsItemFilter:
|
||||
prv_filter_menu_push(data);
|
||||
break;
|
||||
#if !CAPABILITY_HAS_VIBE_SCORES
|
||||
case NotificationsItemVibration:
|
||||
if (battery_is_usb_connected()) {
|
||||
return;
|
||||
}
|
||||
prv_vibe_menu_push(data);
|
||||
break;
|
||||
#endif /* !CAPABILITY_HAS_VIBE_SCORES */
|
||||
case NotificationsItemTextSize:
|
||||
prv_text_size_menu_push(data);
|
||||
break;
|
||||
case NotificationsItemWindowTimeout:
|
||||
prv_window_timeout_menu_push(data);
|
||||
break;
|
||||
default:
|
||||
WTF;
|
||||
}
|
||||
settings_menu_reload_data(SettingsMenuItemNotifications);
|
||||
}
|
||||
|
||||
static void prv_settings_notifications_event_handler(PebbleEvent *event, void *context) {
|
||||
switch (event->type) {
|
||||
case PEBBLE_BATTERY_CONNECTION_EVENT:
|
||||
// Redraw the menu so that the Vibration status will be re-rendered.
|
||||
settings_menu_mark_dirty(SettingsMenuItemNotifications);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_expand_cb(SettingsCallbacks *context) {
|
||||
SettingsNotificationsData *data = (SettingsNotificationsData *) context;
|
||||
|
||||
data->battery_connection_event_info = (EventServiceInfo) {
|
||||
.type = PEBBLE_BATTERY_CONNECTION_EVENT,
|
||||
.handler = prv_settings_notifications_event_handler,
|
||||
};
|
||||
event_service_client_subscribe(&data->battery_connection_event_info);
|
||||
|
||||
}
|
||||
|
||||
static void prv_hide_cb(SettingsCallbacks *context) {
|
||||
SettingsNotificationsData *data = (SettingsNotificationsData *) context;
|
||||
|
||||
event_service_client_unsubscribe(&data->battery_connection_event_info);
|
||||
}
|
||||
|
||||
static Window *prv_init(void) {
|
||||
SettingsNotificationsData* data = app_malloc_check(sizeof(*data));
|
||||
*data = (SettingsNotificationsData){};
|
||||
|
||||
data->callbacks = (SettingsCallbacks) {
|
||||
.deinit = prv_deinit_cb,
|
||||
.draw_row = prv_draw_row_cb,
|
||||
.select_click = prv_select_click_cb,
|
||||
.num_rows = prv_num_rows_cb,
|
||||
.expand = prv_expand_cb,
|
||||
.hide = prv_hide_cb,
|
||||
};
|
||||
|
||||
return settings_window_create(SettingsMenuItemNotifications, &data->callbacks);
|
||||
}
|
||||
|
||||
const SettingsModuleMetadata *settings_notifications_get_info(void) {
|
||||
static const SettingsModuleMetadata s_module_info = {
|
||||
.name = i18n_noop("Notifications"),
|
||||
.init = prv_init,
|
||||
};
|
||||
|
||||
return &s_module_info;
|
||||
}
|
||||
|
||||
void analytics_external_collect_notification_settings(void) {
|
||||
const uint8_t strength = get_strength_for_intensity(vibe_intensity_get());
|
||||
analytics_set(ANALYTICS_DEVICE_METRIC_SETTING_VIBRATION_STRENGTH,
|
||||
strength, AnalyticsClient_System);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue