|
| 1 | +/* |
| 2 | + * Copyright 2025 Google LLC |
| 3 | + * |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | + * you may not use this file except in compliance with the License. |
| 6 | + * You may obtain a copy of the License at |
| 7 | + * |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | + * |
| 10 | + * Unless required by applicable law or agreed to in writing, software |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | + * See the License for the specific language governing permissions and |
| 14 | + * limitations under the License. |
| 15 | + */ |
| 16 | + |
| 17 | +#include "feedback_window.h" |
| 18 | +#include "../util/vector_sequence_layer.h" |
| 19 | +#include "../util/result_window.h" |
| 20 | +#include "../util/style.h" |
| 21 | +#include "../alarms/manager.h" |
| 22 | +#include "../version/version.h" |
| 23 | +#include <pebble.h> |
| 24 | +#include <pebble-events/pebble-events.h> |
| 25 | + |
| 26 | +typedef struct { |
| 27 | + DictationSession *dict_session; |
| 28 | + ScrollLayer *scroll_layer; |
| 29 | + TextLayer *text_layer; |
| 30 | + GBitmap *select_indicator; |
| 31 | + BitmapLayer *select_indicator_layer; |
| 32 | + char *blurb; |
| 33 | + EventHandle event_handle; |
| 34 | + GDrawCommandSequence *loading_sequence; |
| 35 | + VectorSequenceLayer *loading_layer; |
| 36 | + Layer *scroll_indicator_down; |
| 37 | + StatusBarLayer *status_bar_layer; |
| 38 | +} FeedbackWindowData; |
| 39 | + |
| 40 | +static void prv_window_load(Window *window); |
| 41 | +static void prv_window_unload(Window *window); |
| 42 | +static void prv_dictation_status_callback(DictationSession *session, DictationSessionStatus status, char *transcription, void *context); |
| 43 | +static void prv_click_config_provider(); |
| 44 | +static void prv_select_clicked(ClickRecognizerRef recognizer, void *context); |
| 45 | +static void prv_app_message_received(DictionaryIterator *iterator, void *context); |
| 46 | + |
| 47 | +void feedback_window_push() { |
| 48 | + Window *window = window_create(); |
| 49 | + FeedbackWindowData *data = malloc(sizeof(FeedbackWindowData)); |
| 50 | + window_set_user_data(window, data); |
| 51 | + window_set_window_handlers(window, (WindowHandlers) { |
| 52 | + .load = prv_window_load, |
| 53 | + .unload = prv_window_unload, |
| 54 | + }); |
| 55 | + window_stack_push(window, true); |
| 56 | +} |
| 57 | + |
| 58 | +static void prv_window_load(Window *window) { |
| 59 | + FeedbackWindowData *data = window_get_user_data(window); |
| 60 | + GRect bounds = layer_get_bounds(window_get_root_layer(window)); |
| 61 | + Layer *layer = window_get_root_layer(window); |
| 62 | + |
| 63 | + data->status_bar_layer = status_bar_layer_create(); |
| 64 | + layer_add_child(layer, status_bar_layer_get_layer(data->status_bar_layer)); |
| 65 | + bobby_status_bar_config(data->status_bar_layer); |
| 66 | + |
| 67 | + data->scroll_layer = scroll_layer_create(GRect(0, STATUS_BAR_LAYER_HEIGHT, bounds.size.w, bounds.size.h - STATUS_BAR_LAYER_HEIGHT)); |
| 68 | + scroll_layer_set_callbacks(data->scroll_layer, (ScrollLayerCallbacks) { |
| 69 | + .click_config_provider = prv_click_config_provider, |
| 70 | + }); |
| 71 | + scroll_layer_set_shadow_hidden(data->scroll_layer, true); |
| 72 | + scroll_layer_set_context(data->scroll_layer, window); |
| 73 | + scroll_layer_set_click_config_onto_window(data->scroll_layer, window); |
| 74 | + layer_add_child(layer, scroll_layer_get_layer(data->scroll_layer)); |
| 75 | + data->scroll_indicator_down = layer_create(GRect(0, bounds.size.h - STATUS_BAR_LAYER_HEIGHT, bounds.size.w, STATUS_BAR_LAYER_HEIGHT)); |
| 76 | + layer_add_child(layer, data->scroll_indicator_down); |
| 77 | + ContentIndicator* indicator = scroll_layer_get_content_indicator(data->scroll_layer); |
| 78 | + const ContentIndicatorConfig up_config = (ContentIndicatorConfig) { |
| 79 | + .layer = status_bar_layer_get_layer(data->status_bar_layer), |
| 80 | + .times_out = true, |
| 81 | + .alignment = GAlignCenter, |
| 82 | + .colors = { |
| 83 | + .foreground = GColorBlack, |
| 84 | + .background = GColorWhite, |
| 85 | + } |
| 86 | + }; |
| 87 | + content_indicator_configure_direction(indicator, ContentIndicatorDirectionUp, &up_config); |
| 88 | + const ContentIndicatorConfig down_config = (ContentIndicatorConfig) { |
| 89 | + .layer = data->scroll_indicator_down, |
| 90 | + .times_out = true, |
| 91 | + .alignment = GAlignCenter, |
| 92 | + .colors = { |
| 93 | + .foreground = GColorBlack, |
| 94 | + .background = GColorWhite, |
| 95 | + }, |
| 96 | + }; |
| 97 | + content_indicator_configure_direction(indicator, ContentIndicatorDirectionDown, &down_config); |
| 98 | + |
| 99 | + ResHandle blurb_handle = resource_get_handle(RESOURCE_ID_FEEDBACK_BLURB); |
| 100 | + size_t blurb_length = resource_size(blurb_handle); |
| 101 | + data->blurb = malloc(blurb_length + 1); |
| 102 | + resource_load(blurb_handle, (uint8_t *)data->blurb, blurb_length); |
| 103 | + data->blurb[blurb_length] = '\0'; |
| 104 | + |
| 105 | + data->text_layer = text_layer_create(GRect(5, 5, bounds.size.w - 10, 2000)); |
| 106 | + text_layer_set_text(data->text_layer, data->blurb); |
| 107 | + text_layer_set_font(data->text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD)); |
| 108 | + text_layer_set_overflow_mode(data->text_layer, GTextOverflowModeWordWrap); |
| 109 | + GSize text_size = text_layer_get_content_size(data->text_layer); |
| 110 | + layer_set_frame(text_layer_get_layer(data->text_layer), GRect(5, 5, bounds.size.w - 10, text_size.h)); |
| 111 | + scroll_layer_add_child(data->scroll_layer, text_layer_get_layer(data->text_layer)); |
| 112 | + scroll_layer_set_content_size(data->scroll_layer, GSize(bounds.size.w, text_size.h + 10)); |
| 113 | + |
| 114 | + data->select_indicator = gbitmap_create_with_resource(RESOURCE_ID_BUTTON_INDICATOR); |
| 115 | + data->select_indicator_layer = bitmap_layer_create(GRect(bounds.size.w - 5, bounds.size.h / 2 - 10, 5, 20)); |
| 116 | + layer_add_child(layer, bitmap_layer_get_layer(data->select_indicator_layer)); |
| 117 | + bitmap_layer_set_bitmap(data->select_indicator_layer, data->select_indicator); |
| 118 | + bitmap_layer_set_compositing_mode(data->select_indicator_layer, GCompOpSet); |
| 119 | + |
| 120 | + data->loading_sequence = gdraw_command_sequence_create_with_resource(RESOURCE_ID_RUNNING_PONY); |
| 121 | + GSize pony_size = gdraw_command_sequence_get_bounds_size(data->loading_sequence); |
| 122 | + data->loading_layer = vector_sequence_layer_create(GRect(bounds.size.w / 2 - pony_size.w / 2, bounds.size.h / 2 - pony_size.h / 2, pony_size.w, pony_size.h)); |
| 123 | + vector_sequence_layer_set_sequence(data->loading_layer, data->loading_sequence); |
| 124 | + |
| 125 | + data->dict_session = dictation_session_create(0, prv_dictation_status_callback, window); |
| 126 | + dictation_session_enable_error_dialogs(data->dict_session, true); |
| 127 | + dictation_session_enable_confirmation(data->dict_session, true); |
| 128 | + |
| 129 | + data->event_handle = events_app_message_register_inbox_received(prv_app_message_received, window); |
| 130 | +} |
| 131 | + |
| 132 | +static void prv_window_unload(Window *window) { |
| 133 | + APP_LOG(APP_LOG_LEVEL_DEBUG, "Window unloading"); |
| 134 | + FeedbackWindowData *data = window_get_user_data(window); |
| 135 | + dictation_session_destroy(data->dict_session); |
| 136 | + text_layer_destroy(data->text_layer); |
| 137 | + scroll_layer_destroy(data->scroll_layer); |
| 138 | + gbitmap_destroy(data->select_indicator); |
| 139 | + bitmap_layer_destroy(data->select_indicator_layer); |
| 140 | + gdraw_command_sequence_destroy(data->loading_sequence); |
| 141 | + vector_sequence_layer_destroy(data->loading_layer); |
| 142 | + layer_destroy(data->scroll_indicator_down); |
| 143 | + status_bar_layer_destroy(data->status_bar_layer); |
| 144 | + events_app_message_unsubscribe(data->event_handle); |
| 145 | + free(data->blurb); |
| 146 | + free(data); |
| 147 | + window_destroy(window); |
| 148 | + APP_LOG(APP_LOG_LEVEL_DEBUG, "Window unloaded"); |
| 149 | +} |
| 150 | + |
| 151 | +static void prv_click_config_provider() { |
| 152 | + APP_LOG(APP_LOG_LEVEL_INFO, "Click menu configuration"); |
| 153 | + window_single_click_subscribe(BUTTON_ID_SELECT, prv_select_clicked); |
| 154 | +} |
| 155 | + |
| 156 | +static void prv_select_clicked(ClickRecognizerRef recognizer, void *context) { |
| 157 | + APP_LOG(APP_LOG_LEVEL_INFO, "Click menu selection"); |
| 158 | + Window *window = context; |
| 159 | + FeedbackWindowData *data = window_get_user_data(window); |
| 160 | + dictation_session_start(data->dict_session); |
| 161 | +} |
| 162 | + |
| 163 | +static void prv_dictation_status_callback(DictationSession *session, DictationSessionStatus status, char *transcription, void *context) { |
| 164 | + Window *window = context; |
| 165 | + FeedbackWindowData *data = window_get_user_data(window); |
| 166 | + if (status != DictationSessionStatusSuccess) { |
| 167 | + return; |
| 168 | + } |
| 169 | + layer_remove_from_parent(scroll_layer_get_layer(data->scroll_layer)); |
| 170 | + layer_add_child(window_get_root_layer(window), vector_sequence_layer_get_layer(data->loading_layer)); |
| 171 | + vector_sequence_layer_play(data->loading_layer); |
| 172 | + DictionaryIterator *iter; |
| 173 | + app_message_outbox_begin(&iter); |
| 174 | + dict_write_cstring(iter, MESSAGE_KEY_FEEDBACK_TEXT, transcription); |
| 175 | + VersionInfo version = version_get_current(); |
| 176 | + dict_write_int8(iter, MESSAGE_KEY_FEEDBACK_APP_MAJOR, version.major); |
| 177 | + dict_write_int8(iter, MESSAGE_KEY_FEEDBACK_APP_MINOR, version.minor); |
| 178 | + dict_write_int8(iter, MESSAGE_KEY_FEEDBACK_ALARM_COUNT, alarm_manager_get_alarm_count()); |
| 179 | + app_message_outbox_send(); |
| 180 | +} |
| 181 | + |
| 182 | +static void prv_app_message_received(DictionaryIterator *iter, void *context) { |
| 183 | + Window *window = context; |
| 184 | + Tuple *tuple = dict_find(iter, MESSAGE_KEY_FEEDBACK_SEND_RESULT); |
| 185 | + if (!tuple) { |
| 186 | + return; |
| 187 | + } |
| 188 | + int result = tuple->value->int32; |
| 189 | + if (result == 0) { |
| 190 | + GDrawCommandImage *image = gdraw_command_image_create_with_resource(RESOURCE_ID_SENT_IMAGE); |
| 191 | + result_window_push("Sent", "Thank you!", image, BRANDED_BACKGROUND_COLOUR); |
| 192 | + } else { |
| 193 | + GDrawCommandImage *image = gdraw_command_image_create_with_resource(RESOURCE_ID_FAILED_PONY); |
| 194 | + result_window_push("Error", "There was a problem 🙁", image, COLOR_FALLBACK(GColorSunsetOrange, GColorWhite)); |
| 195 | + } |
| 196 | + window_stack_remove(window, false); |
| 197 | +} |
0 commit comments