mirror of
https://github.com/google/pebble.git
synced 2025-07-04 22:00:38 -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
204
src/fw/applib/voice/dictation_session.c
Normal file
204
src/fw/applib/voice/dictation_session.c
Normal file
|
@ -0,0 +1,204 @@
|
|||
/*
|
||||
* 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 "dictation_session.h"
|
||||
#include "dictation_session_private.h"
|
||||
#include "voice_window_private.h"
|
||||
|
||||
#include "applib/voice/voice_window.h"
|
||||
#include "applib/applib_malloc.auto.h"
|
||||
#include "process_management/app_install_manager.h"
|
||||
#include "syscall/syscall.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#if CAPABILITY_HAS_MICROPHONE
|
||||
|
||||
static void prv_handle_transcription_result(PebbleEvent *e, void *context) {
|
||||
PBL_ASSERTN(context);
|
||||
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Exiting with status code: %"PRId8, e->dictation.result);
|
||||
DictationSession *session = context;
|
||||
|
||||
session->callback(session, e->dictation.result, e->dictation.text, session->context);
|
||||
voice_window_reset(session->voice_window);
|
||||
session->in_progress = false;
|
||||
|
||||
if (session->destroy_pending) {
|
||||
dictation_session_destroy(session);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_app_focus_handler(PebbleEvent *e, void *context) {
|
||||
DictationSession *session = context;
|
||||
if (e->app_focus.in_focus) {
|
||||
event_service_client_subscribe(&session->dictation_result_sub);
|
||||
voice_window_regain_focus(session->voice_window);
|
||||
} else {
|
||||
event_service_client_unsubscribe(&session->dictation_result_sub);
|
||||
voice_window_lose_focus(session->voice_window);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_stop_session(DictationSession *session) {
|
||||
session->in_progress = false;
|
||||
event_service_client_unsubscribe(&session->dictation_result_sub);
|
||||
if (pebble_task_get_current() == PebbleTask_App) {
|
||||
event_service_client_unsubscribe(&session->app_focus_sub);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
DictationSession *dictation_session_create(uint32_t buffer_size,
|
||||
DictationSessionStatusCallback callback, void *context) {
|
||||
#if CAPABILITY_HAS_MICROPHONE
|
||||
if (!callback) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Old versions of the Android app (<3.5) will allow voice replies (which also use this code-path)
|
||||
// but don't set the capability flag, so we don't want to block all requests here, just those from
|
||||
// apps. This will result in apps not being able to use the voice APIs unless the phone has the
|
||||
// capability flag set, which is what we want.
|
||||
bool from_app = (pebble_task_get_current() == PebbleTask_App) &&
|
||||
!app_install_id_from_system(sys_process_manager_get_current_process_id());
|
||||
if (from_app && !sys_system_pp_has_capability(CommSessionVoiceApiSupport)) {
|
||||
PBL_LOG(LOG_LEVEL_INFO, "No phone connected or phone app does not support app-initiated "
|
||||
"dictation sessions");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DictationSession *session = applib_type_malloc(DictationSession);
|
||||
if (!session) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *buffer = NULL;
|
||||
if (buffer_size > 0) {
|
||||
buffer = applib_malloc(buffer_size);
|
||||
if (!buffer) {
|
||||
applib_free(session);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
VoiceWindow *voice_window = voice_window_create(buffer, buffer_size,
|
||||
VoiceEndpointSessionTypeDictation);
|
||||
if (!voice_window) {
|
||||
applib_free(buffer);
|
||||
applib_free(session);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*session = (DictationSession) {
|
||||
.callback = callback,
|
||||
.context = context,
|
||||
.voice_window = voice_window,
|
||||
.dictation_result_sub = (EventServiceInfo) {
|
||||
.type = PEBBLE_DICTATION_EVENT,
|
||||
.handler = prv_handle_transcription_result,
|
||||
.context = session
|
||||
}
|
||||
};
|
||||
|
||||
if (pebble_task_get_current() == PebbleTask_App) {
|
||||
session->app_focus_sub = (EventServiceInfo) {
|
||||
.type = PEBBLE_APP_DID_CHANGE_FOCUS_EVENT,
|
||||
.handler = prv_app_focus_handler,
|
||||
.context = session
|
||||
};
|
||||
}
|
||||
#else
|
||||
DictationSession *session = NULL;
|
||||
#endif
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
void dictation_session_destroy(DictationSession *session) {
|
||||
#if CAPABILITY_HAS_MICROPHONE
|
||||
if (!session) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (session->in_progress) {
|
||||
// we can't destroy a session while it is in progress,
|
||||
// so we mark it as destroy pending and we'll destroy it later
|
||||
session->destroy_pending = true;
|
||||
return;
|
||||
}
|
||||
|
||||
prv_stop_session(session);
|
||||
voice_window_destroy(session->voice_window);
|
||||
applib_free(session);
|
||||
#endif
|
||||
}
|
||||
|
||||
void dictation_session_enable_confirmation(DictationSession *session, bool is_enabled) {
|
||||
#if CAPABILITY_HAS_MICROPHONE
|
||||
if (!session || session->in_progress) {
|
||||
return;
|
||||
}
|
||||
voice_window_set_confirmation_enabled(session->voice_window, is_enabled);
|
||||
#endif
|
||||
}
|
||||
|
||||
void dictation_session_enable_error_dialogs(DictationSession *session, bool is_enabled) {
|
||||
#if CAPABILITY_HAS_MICROPHONE
|
||||
if (!session || session->in_progress) {
|
||||
return;
|
||||
}
|
||||
voice_window_set_error_enabled(session->voice_window, is_enabled);
|
||||
#endif
|
||||
}
|
||||
|
||||
DictationSessionStatus dictation_session_start(DictationSession *session) {
|
||||
#if CAPABILITY_HAS_MICROPHONE
|
||||
if (!session || session->in_progress) {
|
||||
return DictationSessionStatusFailureInternalError;
|
||||
}
|
||||
|
||||
DictationSessionStatus result = voice_window_push(session->voice_window);
|
||||
if (result != DictationSessionStatusSuccess) {
|
||||
return result;
|
||||
}
|
||||
|
||||
session->in_progress = true;
|
||||
event_service_client_subscribe(&session->dictation_result_sub);
|
||||
if (pebble_task_get_current() == PebbleTask_App) {
|
||||
event_service_client_subscribe(&session->app_focus_sub);
|
||||
}
|
||||
return result;
|
||||
#else
|
||||
return DictationSessionStatusFailureInternalError;
|
||||
#endif
|
||||
}
|
||||
|
||||
DictationSessionStatus dictation_session_stop(DictationSession *session) {
|
||||
#if CAPABILITY_HAS_MICROPHONE
|
||||
if (!session || !session->in_progress) {
|
||||
return DictationSessionStatusFailureInternalError;
|
||||
}
|
||||
prv_stop_session(session);
|
||||
voice_window_pop(session->voice_window);
|
||||
return DictationSessionStatusSuccess;
|
||||
#else
|
||||
return DictationSessionStatusFailureInternalError;
|
||||
#endif
|
||||
}
|
150
src/fw/applib/voice/dictation_session.h
Normal file
150
src/fw/applib/voice/dictation_session.h
Normal file
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
//! @file voice/dictation_session.h
|
||||
//! Defines the interface to the dictation session API
|
||||
//! @addtogroup Microphone
|
||||
//! @{
|
||||
//! @addtogroup DictationSession Dictation Session
|
||||
//! \brief A dictation session allows the retrieval of a voice transcription from the Pebble
|
||||
//! smartwatch's speech recognition provider via the same user interface used by the Pebble OS for
|
||||
//! notifications.
|
||||
//!
|
||||
//! Starting a session will spawn the UI and upon user confirmation (unless this is disabled), the
|
||||
//! result of the session as well as the transcription text will be returned via callback. If user
|
||||
//! confirmation is disabled the first transcription result will be passed back via the callback.
|
||||
//!
|
||||
//! A dictation session must be created before use (see \ref dictation_session_create) and can
|
||||
//! be reused for however many dictations are required, using \ref dictation_session_start. A
|
||||
//! session can be aborted mid-flow by calling \ref dictation_session_stop.
|
||||
//!
|
||||
//! If these calls are made on a platform that does not support voice dictation,
|
||||
//! \ref dictation_session_create will return NULL and the other calls will do nothing.
|
||||
|
||||
typedef struct DictationSession DictationSession;
|
||||
|
||||
// convenient macros to distinguish between mic and no mic.
|
||||
// TODO: PBL-21978 remove redundant comments as a workaround around for SDK generator
|
||||
#if defined(PBL_MICROPHONE)
|
||||
|
||||
//! Convenience macro to switch between two expressions depending on mic support.
|
||||
//! On platforms with a mic the first expression will be chosen, the second otherwise.
|
||||
#define PBL_IF_MICROPHONE_ELSE(if_true, if_false) (if_true)
|
||||
|
||||
#else
|
||||
|
||||
//! Convenience macro to switch between two expressions depending on mic support.
|
||||
//! On platforms with a mic the first expression will be chosen, the second otherwise.
|
||||
#define PBL_IF_MICROPHONE_ELSE(if_true, if_false) (if_false)
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
//! Transcription successful, with a valid result
|
||||
DictationSessionStatusSuccess,
|
||||
|
||||
//! User rejected transcription and exited UI
|
||||
DictationSessionStatusFailureTranscriptionRejected,
|
||||
|
||||
//! User exited UI after transcription error
|
||||
DictationSessionStatusFailureTranscriptionRejectedWithError,
|
||||
|
||||
//! Too many errors occurred during transcription and the UI exited
|
||||
DictationSessionStatusFailureSystemAborted,
|
||||
|
||||
//! No speech was detected and UI exited
|
||||
DictationSessionStatusFailureNoSpeechDetected,
|
||||
|
||||
//! No BT or internet connection
|
||||
DictationSessionStatusFailureConnectivityError,
|
||||
|
||||
//! Voice transcription disabled for this user
|
||||
DictationSessionStatusFailureDisabled,
|
||||
|
||||
//! Voice transcription failed due to internal error
|
||||
DictationSessionStatusFailureInternalError,
|
||||
|
||||
//! Cloud recognizer failed to transcribe speech (only possible if error dialogs disabled)
|
||||
DictationSessionStatusFailureRecognizerError,
|
||||
} DictationSessionStatus;
|
||||
|
||||
//! Dictation status callback. Indicates success or failure of the dictation session and, if
|
||||
//! successful, passes the transcribed string to the user of the dictation session. The transcribed
|
||||
//! string will be freed after this call returns, so the string should be copied if it needs to be
|
||||
//! retained afterwards.
|
||||
//! @param session dictation session from which the status was received
|
||||
//! @param status dictation status
|
||||
//! @param transcription transcribed string
|
||||
//! @param context callback context specified when starting the session
|
||||
typedef void (*DictationSessionStatusCallback)(DictationSession *session,
|
||||
DictationSessionStatus status, char *transcription,
|
||||
void *context);
|
||||
|
||||
//! Create a dictation session. The session object can be used more than once to get a
|
||||
//! transcription. When a transcription is received a buffer will be allocated to store the text in
|
||||
//! with a maximum size specified by \ref buffer_size. When a transcription and accepted by the user
|
||||
//! or a failure of some sort occurs, the callback specified will be called with the status and the
|
||||
//! transcription if one was accepted.
|
||||
//! @param buffer_size size of buffer to allocate for the transcription text; text will be
|
||||
//! truncated if it is longer than the maximum size specified; a size of 0
|
||||
//! will allow the session to allocate as much as it needs and text will
|
||||
//! not be truncated
|
||||
//! @param callback dictation session status handler (must be valid)
|
||||
//! @param callback_context context pointer for status handler
|
||||
//! @return handle to the dictation session or NULL if the phone app is not connected or does not
|
||||
//! support voice dictation, if this is called on a platform that doesn't support voice dictation,
|
||||
//! or if an internal error occurs.
|
||||
DictationSession *dictation_session_create(uint32_t buffer_size,
|
||||
DictationSessionStatusCallback callback,
|
||||
void *callback_context);
|
||||
|
||||
//! Destroy the dictation session and free its memory. Will terminate a session in progress.
|
||||
//! @param session dictation session to be destroyed
|
||||
void dictation_session_destroy(DictationSession *session);
|
||||
|
||||
//! Enable or disable user confirmation of transcribed text, which allows the user to accept or
|
||||
//! reject (and restart) the transcription. Must be called before the session is started.
|
||||
//! @param session dictation session to modify
|
||||
//! @param is_enabled set to true to enable user confirmation of transcriptions (default), false
|
||||
//! to disable
|
||||
void dictation_session_enable_confirmation(DictationSession *session, bool is_enabled);
|
||||
|
||||
//! Enable or disable error dialogs when transcription fails. Must be called before the session
|
||||
//! is started. Disabling error dialogs will also disable automatic retries if transcription fails.
|
||||
//! @param session dictation session to modify
|
||||
//! @param is_enabled set to true to enable error dialogs (default), false to disable
|
||||
void dictation_session_enable_error_dialogs(DictationSession *session, bool is_enabled);
|
||||
|
||||
//! Start the dictation session. The dictation UI will be shown. When the user accepts a
|
||||
//! transcription or exits the UI, or, when the confirmation dialog is disabled and a status is
|
||||
//! received, the status callback will be called. Can only be called when no session is in progress.
|
||||
//! The session can be restarted multiple times after the UI is exited or the session is stopped.
|
||||
//! @param session dictation session to start or restart
|
||||
//! @return true if session was started, false if session is already in progress or is invalid.
|
||||
DictationSessionStatus dictation_session_start(DictationSession *session);
|
||||
|
||||
//! Stop the current dictation session. The UI will be hidden and no status callbacks will be
|
||||
//! received after the session is stopped.
|
||||
//! @param session dictation session to stop
|
||||
//! @return true if session was stopped, false if session was not started or is invalid
|
||||
DictationSessionStatus dictation_session_stop(DictationSession *session);
|
||||
|
||||
//! @} // end addtogroup DictationSession
|
||||
//! @} // end addtogroup Microphone
|
34
src/fw/applib/voice/dictation_session_private.h
Normal file
34
src/fw/applib/voice/dictation_session_private.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "voice_window.h"
|
||||
#include "dictation_session.h"
|
||||
#include "applib/event_service_client.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
struct DictationSession {
|
||||
VoiceWindow *voice_window;
|
||||
DictationSessionStatusCallback callback;
|
||||
void *context;
|
||||
bool in_progress;
|
||||
bool destroy_pending;
|
||||
EventServiceInfo dictation_result_sub;
|
||||
EventServiceInfo app_focus_sub;
|
||||
};
|
100
src/fw/applib/voice/loading_layer.c
Normal file
100
src/fw/applib/voice/loading_layer.c
Normal file
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* 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 "loading_layer.h"
|
||||
|
||||
#include "applib/graphics/gtypes.h"
|
||||
#include "applib/ui/property_animation.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
void loading_layer_init(LoadingLayer *loading_layer, const GRect *frame) {
|
||||
*loading_layer = (LoadingLayer) {
|
||||
.full_frame = *frame
|
||||
};
|
||||
|
||||
ProgressLayer *progress_layer = &loading_layer->progress_layer;
|
||||
progress_layer_init(progress_layer, frame);
|
||||
progress_layer_set_corner_radius(progress_layer, PROGRESS_SUGGESTED_CORNER_RADIUS);
|
||||
}
|
||||
|
||||
void loading_layer_deinit(LoadingLayer *loading_layer) {
|
||||
loading_layer_pause(loading_layer);
|
||||
progress_layer_deinit(&loading_layer->progress_layer);
|
||||
}
|
||||
|
||||
void loading_layer_shrink(LoadingLayer *loading_layer, uint32_t delay, uint32_t duration,
|
||||
AnimationStoppedHandler stopped_handler, void *context) {
|
||||
loading_layer_pause(loading_layer);
|
||||
|
||||
layer_set_frame((Layer *)loading_layer, &loading_layer->full_frame);
|
||||
GRect *start = &loading_layer->full_frame;
|
||||
GRect stop = *start;
|
||||
|
||||
stop.origin.x += stop.size.w;
|
||||
stop.size.w = 0;
|
||||
|
||||
PropertyAnimation *prop_anim = property_animation_create_layer_frame(
|
||||
(Layer *)loading_layer, start, &stop);
|
||||
if (!prop_anim) {
|
||||
return;
|
||||
}
|
||||
|
||||
Animation *animation = property_animation_get_animation(prop_anim);
|
||||
// If we failed, pause on the screen for a little.
|
||||
animation_set_delay(animation, delay);
|
||||
animation_set_duration(animation, duration);
|
||||
animation_set_curve(animation, AnimationCurveEaseOut);
|
||||
animation_set_handlers(animation, (AnimationHandlers) {
|
||||
.stopped = stopped_handler
|
||||
}, context);
|
||||
|
||||
loading_layer->animation = animation;
|
||||
animation_schedule(animation);
|
||||
}
|
||||
|
||||
void loading_layer_pause(LoadingLayer *loading_layer) {
|
||||
if (animation_is_scheduled(loading_layer->animation)) {
|
||||
animation_unschedule(loading_layer->animation);
|
||||
}
|
||||
}
|
||||
|
||||
void loading_layer_grow(LoadingLayer *loading_layer, uint32_t delay, uint32_t duration) {
|
||||
loading_layer_pause(loading_layer);
|
||||
|
||||
if (duration == 0) {
|
||||
layer_set_frame((Layer *)loading_layer, &loading_layer->full_frame);
|
||||
return;
|
||||
}
|
||||
GRect start = loading_layer->full_frame;
|
||||
start.size.w = 0;
|
||||
layer_set_frame((Layer *)loading_layer, &start);
|
||||
|
||||
PropertyAnimation *prop_anim = property_animation_create_layer_frame(
|
||||
(Layer *)loading_layer, &start, &loading_layer->full_frame);
|
||||
if (!prop_anim) {
|
||||
return;
|
||||
}
|
||||
|
||||
Animation *animation = property_animation_get_animation(prop_anim);
|
||||
|
||||
animation_set_delay(animation, delay);
|
||||
animation_set_duration(animation, duration);
|
||||
animation_set_curve(animation, AnimationCurveEaseOut);
|
||||
|
||||
loading_layer->animation = animation;
|
||||
animation_schedule(animation);
|
||||
}
|
44
src/fw/applib/voice/loading_layer.h
Normal file
44
src/fw/applib/voice/loading_layer.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "applib/ui/animation.h"
|
||||
#include "applib/ui/progress_layer.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define LOADING_LAYER_DEFAULT_SIZE { 79, PROGRESS_SUGGESTED_HEIGHT }
|
||||
|
||||
typedef void (*LoadingLayerAnimCompleteCb)(void *context);
|
||||
|
||||
typedef struct {
|
||||
ProgressLayer progress_layer;
|
||||
Animation *animation;
|
||||
GRect full_frame;
|
||||
} LoadingLayer;
|
||||
|
||||
void loading_layer_init(LoadingLayer *loading_layer, const GRect *frame);
|
||||
|
||||
void loading_layer_deinit(LoadingLayer *loading_layer);
|
||||
|
||||
void loading_layer_shrink(LoadingLayer *loading_layer, uint32_t delay, uint32_t duration,
|
||||
AnimationStoppedHandler stopped_handler, void *context);
|
||||
|
||||
void loading_layer_grow(LoadingLayer *loading_layer, uint32_t duration, uint32_t delay);
|
||||
|
||||
void loading_layer_pause(LoadingLayer *loading_layer);
|
277
src/fw/applib/voice/transcription_dialog.c
Normal file
277
src/fw/applib/voice/transcription_dialog.c
Normal file
|
@ -0,0 +1,277 @@
|
|||
/*
|
||||
* 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 "transcription_dialog.h"
|
||||
|
||||
#include "applib/applib_malloc.auto.h"
|
||||
#include "applib/graphics/gtypes.h"
|
||||
#include "applib/graphics/text_render.h"
|
||||
#include "applib/graphics/utf8.h"
|
||||
#include "applib/ui/action_bar_layer.h"
|
||||
#include "applib/ui/animation.h"
|
||||
#include "applib/ui/animation_interpolate.h"
|
||||
#include "applib/ui/dialogs/dialog_private.h"
|
||||
#include "applib/ui/scroll_layer.h"
|
||||
#include "kernel/ui/kernel_ui.h"
|
||||
#include "resource/resource_ids.auto.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define SCROLL_ANIMATION_DURATION (300)
|
||||
#define POP_WINDOW_DELAY (400)
|
||||
#define CHARACTER_DELAY (20)
|
||||
#define TEXT_OFFSET_VERTICAL (6)
|
||||
|
||||
static void prv_show_next_character(TranscriptionDialog *transcription_dialog, int16_t to_idx) {
|
||||
// TODO: at the beginning of a word, check whether it's going to wrap when it's finished
|
||||
// type and break to the next line before it starts typing.
|
||||
|
||||
Dialog *dialog = expandable_dialog_get_dialog((ExpandableDialog *)transcription_dialog);
|
||||
|
||||
// Find the current index
|
||||
utf8_t *cursor = (utf8_t *)dialog->buffer;
|
||||
int16_t current_idx = 0;
|
||||
while (cursor < (utf8_t *)transcription_dialog->zero) {
|
||||
cursor = utf8_get_next(cursor);
|
||||
current_idx++;
|
||||
}
|
||||
PBL_ASSERTN(cursor == (utf8_t *)transcription_dialog->zero);
|
||||
PBL_ASSERTN(current_idx <= to_idx);
|
||||
|
||||
// Restore the missing character, then get the start of the next codepoint.
|
||||
*transcription_dialog->zero = transcription_dialog->missing;
|
||||
while (current_idx++ < to_idx) {
|
||||
cursor = utf8_get_next(cursor);
|
||||
}
|
||||
|
||||
char *next = (char *)cursor;
|
||||
if (next == dialog->buffer + transcription_dialog->buffer_len) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Move the zero terminator.
|
||||
transcription_dialog->missing = *next;
|
||||
*next = '\0';
|
||||
transcription_dialog->zero = next;
|
||||
}
|
||||
|
||||
static void prv_set_char_index(void *subject, int16_t index) {
|
||||
TranscriptionDialog *transcription_dialog = subject;
|
||||
prv_show_next_character(transcription_dialog, index);
|
||||
|
||||
Dialog *dialog = expandable_dialog_get_dialog((ExpandableDialog *)transcription_dialog);
|
||||
ScrollLayer *scroll_layer = &((ExpandableDialog *) transcription_dialog)->scroll_layer;
|
||||
|
||||
TextLayer *text_layer = &dialog->text_layer;
|
||||
const GSize size = text_layer_get_content_size(graphics_context_get_current_context(),
|
||||
text_layer);
|
||||
const uint16_t font_height = fonts_get_font_height(text_layer->font);
|
||||
|
||||
text_layer_set_size(text_layer, (GSize) { text_layer->layer.frame.size.w, size.h + font_height });
|
||||
|
||||
const GSize scroll_size = scroll_layer_get_content_size(scroll_layer);
|
||||
const int16_t new_height = size.h + TEXT_OFFSET_VERTICAL;
|
||||
if (scroll_size.h != new_height) {
|
||||
const GRect *bounds = &scroll_layer_get_layer(scroll_layer)->bounds;
|
||||
GPoint offset = { .y = bounds->size.h - new_height };
|
||||
#if PBL_ROUND
|
||||
// do paging on round display
|
||||
offset.y = ROUND_TO_MOD_CEIL(offset.y, scroll_layer->layer.frame.size.h);
|
||||
#endif
|
||||
scroll_layer_set_content_size(scroll_layer,
|
||||
(GSize) { scroll_layer->layer.frame.size.w, new_height });
|
||||
scroll_layer_set_content_offset(scroll_layer, offset, true /* animated */);
|
||||
animation_set_duration(property_animation_get_animation(scroll_layer->animation),
|
||||
SCROLL_ANIMATION_DURATION);
|
||||
}
|
||||
|
||||
layer_mark_dirty((Layer *)text_layer);
|
||||
}
|
||||
|
||||
static void prv_start_text_animation(TranscriptionDialog *transcription_dialog) {
|
||||
static const PropertyAnimationImplementation animated_text_len = {
|
||||
.base = {
|
||||
.update = (AnimationUpdateImplementation) property_animation_update_int16,
|
||||
},
|
||||
.accessors = {
|
||||
.setter = { .int16 = prv_set_char_index }
|
||||
}
|
||||
};
|
||||
|
||||
Dialog *dialog = expandable_dialog_get_dialog((ExpandableDialog *)transcription_dialog);
|
||||
|
||||
// Count the number of codepoints in the message
|
||||
*transcription_dialog->zero = transcription_dialog->missing;
|
||||
|
||||
int16_t count = 0;
|
||||
int16_t begin = 0;
|
||||
utf8_t *cursor = (utf8_t *)dialog->buffer;
|
||||
while ((cursor = utf8_get_next(cursor))) {
|
||||
count++;
|
||||
if (cursor == (utf8_t *)transcription_dialog->zero) {
|
||||
begin = count;
|
||||
}
|
||||
}
|
||||
|
||||
transcription_dialog->animation = property_animation_create(&animated_text_len,
|
||||
transcription_dialog, NULL, NULL);
|
||||
if (!transcription_dialog->animation) {
|
||||
return;
|
||||
}
|
||||
property_animation_set_from_int16(transcription_dialog->animation, &begin);
|
||||
property_animation_set_to_int16(transcription_dialog->animation, &count);
|
||||
|
||||
Animation *anim = property_animation_get_animation(transcription_dialog->animation);
|
||||
|
||||
animation_set_duration(anim, (count - begin) * CHARACTER_DELAY);
|
||||
animation_set_curve(anim, AnimationCurveEaseInOut);
|
||||
|
||||
// Text is shown if creating the property animation fails
|
||||
*transcription_dialog->zero = '\0';
|
||||
|
||||
animation_set_curve(anim, AnimationCurveLinear);
|
||||
animation_schedule(anim);
|
||||
}
|
||||
|
||||
static void prv_stop_text_animation(TranscriptionDialog *transcription_dialog) {
|
||||
Dialog *dialog = expandable_dialog_get_dialog((ExpandableDialog *)transcription_dialog);
|
||||
animation_unschedule(property_animation_get_animation(transcription_dialog->animation));
|
||||
*transcription_dialog->zero = transcription_dialog->missing;
|
||||
transcription_dialog->zero = dialog->buffer + transcription_dialog->buffer_len;
|
||||
transcription_dialog->missing = '\0';
|
||||
layer_mark_dirty((Layer *)&dialog->text_layer);
|
||||
}
|
||||
|
||||
static void prv_transcription_dialog_unload(void *context) {
|
||||
TranscriptionDialog *transcription_dialog = context;
|
||||
app_timer_cancel(transcription_dialog->pop_timer);
|
||||
prv_stop_text_animation(transcription_dialog);
|
||||
}
|
||||
|
||||
static void prv_transcription_dialog_load(void *context) {
|
||||
TranscriptionDialog *transcription_dialog = context;
|
||||
transcription_dialog->was_pushed = true;
|
||||
if (transcription_dialog->buffer_len > 0) {
|
||||
Dialog *dialog = expandable_dialog_get_dialog((ExpandableDialog *)transcription_dialog);
|
||||
transcription_dialog->zero = dialog->buffer;
|
||||
transcription_dialog->missing = dialog->buffer[0];
|
||||
prv_start_text_animation(context);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_transcription_dialog_select_cb(void *context) {
|
||||
TranscriptionDialog *transcription_dialog = context;
|
||||
if (transcription_dialog->keep_alive_on_select) {
|
||||
action_bar_layer_clear_icon(&transcription_dialog->e_dialog.action_bar, BUTTON_ID_SELECT);
|
||||
} else {
|
||||
transcription_dialog_pop(transcription_dialog);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_transcription_dialog_select_handler(ClickRecognizerRef recognizer, void *context) {
|
||||
TranscriptionDialog *transcription_dialog = context;
|
||||
if (transcription_dialog->select_pressed) {
|
||||
// We are waiting to pop the window, don't run the callback again
|
||||
return;
|
||||
}
|
||||
transcription_dialog->select_pressed = true;
|
||||
|
||||
prv_stop_text_animation(transcription_dialog);
|
||||
|
||||
if (transcription_dialog->callback) {
|
||||
if (transcription_dialog->callback_context) {
|
||||
transcription_dialog->callback(transcription_dialog->callback_context);
|
||||
} else {
|
||||
transcription_dialog->callback(transcription_dialog);
|
||||
}
|
||||
}
|
||||
|
||||
transcription_dialog->pop_timer = app_timer_register(POP_WINDOW_DELAY,
|
||||
prv_transcription_dialog_select_cb, transcription_dialog);
|
||||
}
|
||||
|
||||
void transcription_dialog_update_text(TranscriptionDialog *transcription_dialog,
|
||||
char *buffer, uint16_t buffer_len) {
|
||||
Dialog *dialog = expandable_dialog_get_dialog((ExpandableDialog *)transcription_dialog);
|
||||
|
||||
transcription_dialog->buffer_len = buffer_len;
|
||||
dialog_set_text_buffer(dialog, buffer, false /* take_ownership */);
|
||||
|
||||
if (transcription_dialog->was_pushed) {
|
||||
prv_stop_text_animation(transcription_dialog);
|
||||
prv_start_text_animation(transcription_dialog);
|
||||
}
|
||||
}
|
||||
|
||||
void transcription_dialog_push(TranscriptionDialog *transcription_dialog,
|
||||
WindowStack *window_stack) {
|
||||
PBL_ASSERTN(transcription_dialog);
|
||||
expandable_dialog_push((ExpandableDialog *)transcription_dialog, window_stack);
|
||||
}
|
||||
|
||||
void app_transcription_dialog_push(TranscriptionDialog *transcription_dialog) {
|
||||
PBL_ASSERTN(transcription_dialog);
|
||||
app_expandable_dialog_push((ExpandableDialog *)transcription_dialog);
|
||||
}
|
||||
|
||||
void transcription_dialog_pop(TranscriptionDialog *transcription_dialog) {
|
||||
PBL_ASSERTN(transcription_dialog);
|
||||
expandable_dialog_pop((ExpandableDialog *)transcription_dialog);
|
||||
}
|
||||
|
||||
void transcription_dialog_set_callback(TranscriptionDialog *transcription_dialog,
|
||||
TranscriptionConfirmationCallback callback,
|
||||
void *callback_context) {
|
||||
PBL_ASSERTN(transcription_dialog);
|
||||
transcription_dialog->callback = callback;
|
||||
transcription_dialog->callback_context = callback_context;
|
||||
}
|
||||
|
||||
void transcription_dialog_keep_alive_on_select(TranscriptionDialog *transcription_dialog,
|
||||
bool keep_alive_on_select) {
|
||||
PBL_ASSERTN(transcription_dialog);
|
||||
transcription_dialog->keep_alive_on_select = keep_alive_on_select;
|
||||
}
|
||||
|
||||
TranscriptionDialog *transcription_dialog_create(void) {
|
||||
TranscriptionDialog *transcription_dialog = applib_type_malloc(TranscriptionDialog);
|
||||
if (!transcription_dialog) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
transcription_dialog_init(transcription_dialog);
|
||||
return transcription_dialog;
|
||||
}
|
||||
|
||||
void transcription_dialog_init(TranscriptionDialog *transcription_dialog) {
|
||||
*transcription_dialog = (TranscriptionDialog){};
|
||||
|
||||
expandable_dialog_init((ExpandableDialog *)transcription_dialog, "Transcription Dialog");
|
||||
expandable_dialog_set_select_action((ExpandableDialog *)transcription_dialog,
|
||||
RESOURCE_ID_ACTION_BAR_ICON_CHECK, prv_transcription_dialog_select_handler);
|
||||
|
||||
Dialog *dialog = expandable_dialog_get_dialog((ExpandableDialog *)transcription_dialog);
|
||||
dialog_set_callbacks(dialog, &(DialogCallbacks) {
|
||||
.unload = prv_transcription_dialog_unload,
|
||||
.load = prv_transcription_dialog_load
|
||||
}, transcription_dialog);
|
||||
dialog_show_status_bar_layer(dialog, true /* show status bar */);
|
||||
dialog_set_timeout(dialog, DIALOG_TIMEOUT_INFINITE);
|
||||
|
||||
status_bar_layer_set_colors(&dialog->status_layer, GColorLightGray, GColorBlack);
|
||||
}
|
98
src/fw/applib/voice/transcription_dialog.h
Normal file
98
src/fw/applib/voice/transcription_dialog.h
Normal file
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "applib/app_timer.h"
|
||||
#include "applib/ui/property_animation.h"
|
||||
#include "applib/ui/dialogs/expandable_dialog.h"
|
||||
#include "applib/ui/window_stack.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
//! Callback from the dialog
|
||||
typedef void(*TranscriptionConfirmationCallback)(void *callback_context);
|
||||
|
||||
typedef struct TranscriptionDialog {
|
||||
ExpandableDialog e_dialog;
|
||||
AppTimer *pop_timer;
|
||||
|
||||
TranscriptionConfirmationCallback callback;
|
||||
void *callback_context;
|
||||
|
||||
char *zero;
|
||||
char missing;
|
||||
bool was_pushed;
|
||||
bool select_pressed;
|
||||
bool keep_alive_on_select;
|
||||
PropertyAnimation *animation;
|
||||
|
||||
// We cache this value so that we don't have to recompute it
|
||||
// in our animation helpers.
|
||||
uint32_t buffer_len;
|
||||
} TranscriptionDialog;
|
||||
|
||||
//! Creates a TranscriptionDialog on the heap.
|
||||
//! @return \ref TranscriptionDialog
|
||||
TranscriptionDialog *transcription_dialog_create(void);
|
||||
|
||||
//! Initialize a transcription dialog that was already allocated
|
||||
void transcription_dialog_init(TranscriptionDialog *transcription_dialog);
|
||||
|
||||
//! Pushes a TranscriptionDialog onto the app window stack if running on
|
||||
//! the app task, otherwise the modal window stack.
|
||||
//! @param transcription_dialog Pointer to the \ref TranscriptionDialog to push
|
||||
//! @param window_stack Pointer to the \ref WindowStack to push to
|
||||
void transcription_dialog_push(TranscriptionDialog *transcription_dialog,
|
||||
WindowStack *window_stack);
|
||||
|
||||
//! Pushes a \ref TranscriptionDialog to the app's window stack
|
||||
//! @param transcription_dialog Pointer to the \ref TranscriptionDialog to push
|
||||
//! @note: Put a better comment here before exporting
|
||||
void app_transcription_dialog_push(TranscriptionDialog *transcription_dialog);
|
||||
|
||||
//! Pops a \ref TranscriptionDialog from the app window stack or modal window
|
||||
//! stack depending on the current task.
|
||||
//! @param transcription_dialog Pointer to the \ref TranscriptionDialog to pop
|
||||
void transcription_dialog_pop(TranscriptionDialog *transcription_dialog);
|
||||
|
||||
//! Updates the text in a \ref TranscriptionDialog. This causes the dialog to
|
||||
//! re-render and animate its contents.
|
||||
//! @param transcription_dialog Pointer to the \ref TranscriptionDialog text to set
|
||||
//! @param transcription The text to display
|
||||
//! @param transcription_len The length of the text in the transcription
|
||||
void transcription_dialog_update_text(TranscriptionDialog *transcription_dialog,
|
||||
char *transcription, uint16_t transcription_len);
|
||||
|
||||
//! Sets the callback that is called if the user confirms that the text
|
||||
//! being displayed is what they intended.
|
||||
//! @param transcription_dialog Pointer to the \ref TranscriptionDialog to set
|
||||
//! @param callback The \ref TranscriptionConfirmationCallback to call if the user confirms text
|
||||
//! @param callback_context The \ref callback_context to pass to the confirmation handler
|
||||
//! @note If the callback_context is NULL, then the \ref TranscriptionDialog will be passed
|
||||
//! the callback handler.
|
||||
void transcription_dialog_set_callback(TranscriptionDialog *transcription_dialog,
|
||||
TranscriptionConfirmationCallback callback,
|
||||
void *callback_context);
|
||||
|
||||
//! @internal
|
||||
//! Control whether the dialog closes when the select button is pressed
|
||||
//! @param transcription_dialog Pointer to the \ref TranscriptionDialog to set
|
||||
//! @param keep_alive_on_select If True the window will NOT close when select is pressed
|
||||
//! @note The default is false (window will close when the selection has been made)
|
||||
void transcription_dialog_keep_alive_on_select(TranscriptionDialog *transcription_dialog,
|
||||
bool keep_alive_on_select);
|
1554
src/fw/applib/voice/voice_window.c
Normal file
1554
src/fw/applib/voice/voice_window.c
Normal file
File diff suppressed because it is too large
Load diff
43
src/fw/applib/voice/voice_window.h
Normal file
43
src/fw/applib/voice/voice_window.h
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "util/uuid.h"
|
||||
#include "applib/voice/dictation_session.h"
|
||||
#include "services/normal/voice_endpoint.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct VoiceUiData VoiceWindow;
|
||||
|
||||
VoiceWindow *voice_window_create(char *buffer, size_t buffer_size,
|
||||
VoiceEndpointSessionType session_type);
|
||||
|
||||
void voice_window_destroy(VoiceWindow *voice_window);
|
||||
|
||||
// Push the voice window from App task or Main task
|
||||
DictationSessionStatus voice_window_push(VoiceWindow *voice_window);
|
||||
|
||||
void voice_window_pop(VoiceWindow *voice_window);
|
||||
|
||||
void voice_window_set_confirmation_enabled(VoiceWindow *voice_window, bool enabled);
|
||||
|
||||
void voice_window_set_error_enabled(VoiceWindow *voice_window, bool enabled);
|
||||
|
||||
void voice_window_reset(VoiceWindow *voice_window);
|
110
src/fw/applib/voice/voice_window_private.h
Normal file
110
src/fw/applib/voice/voice_window_private.h
Normal file
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "loading_layer.h"
|
||||
#include "transcription_dialog.h"
|
||||
|
||||
#include "applib/app_timer.h"
|
||||
#include "applib/event_service_client.h"
|
||||
#include "applib/ui/animation.h"
|
||||
#include "applib/ui/layer.h"
|
||||
#include "applib/ui/property_animation.h"
|
||||
#include "applib/ui/text_layer.h"
|
||||
#include "applib/ui/status_bar_layer.h"
|
||||
#include "applib/ui/window.h"
|
||||
#include "applib/ui/dialogs/bt_conn_dialog.h"
|
||||
#include "applib/ui/dialogs/simple_dialog.h"
|
||||
#include "applib/ui/dialogs/expandable_dialog.h"
|
||||
#include "applib/ui/kino/kino_layer.h"
|
||||
#include "applib/voice/dictation_session.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef enum {
|
||||
StateStart, // Start state. Nothing happens
|
||||
StateStartWaitForReady, // Dot flies in
|
||||
StateWaitForReady, // Progress bar shows and animates, dot pulses
|
||||
StateStopWaitForReady, // Progress bar shrinks, dot continues to animate
|
||||
StateRecording, // Microphone unfolds and text appears
|
||||
StateStopRecording, // Microphone folds up again and text disappears
|
||||
StateWaitForResponse, // Dot pulses, progress bar shown
|
||||
StateStopWaitForResponse, // Progress bar shrinks
|
||||
StateTransitionToText, // Dot flies out, text window pushed
|
||||
StateError,
|
||||
StateFinished,
|
||||
StateExiting,
|
||||
} VoiceUiState;
|
||||
|
||||
typedef struct VoiceUiData {
|
||||
struct {
|
||||
Window window;
|
||||
KinoLayer icon_layer;
|
||||
Animation *mic_dot_anim;
|
||||
Layer mic_dot_layer;
|
||||
int16_t mic_dot_radius;
|
||||
TextLayer text_layer;
|
||||
char text_buffer[20]; // Larger than needed because i18n
|
||||
StatusBarLayer status_bar;
|
||||
LoadingLayer progress_bar;
|
||||
PropertyAnimation *progress_anim;
|
||||
PropertyAnimation *fly_anim;
|
||||
} mic_window;
|
||||
|
||||
union{
|
||||
TranscriptionDialog transcription_dialog;
|
||||
ExpandableDialog long_error_dialog;
|
||||
SimpleDialog short_error_dialog;
|
||||
BtConnDialog bt_dialog;
|
||||
Dialog dialog;
|
||||
};
|
||||
|
||||
VoiceUiState state;
|
||||
bool speech_detected;
|
||||
bool transcription_dialog_keep_alive_on_select;
|
||||
char *message;
|
||||
size_t message_len;
|
||||
time_t timestamp;
|
||||
uint8_t error_count;
|
||||
bool last_session_successful;
|
||||
uint8_t num_sessions;
|
||||
AppTimer *dictation_timeout;
|
||||
EventServiceInfo voice_event_sub;
|
||||
DictationSessionStatus error_exit_status;
|
||||
|
||||
char error_text_buffer[150];
|
||||
|
||||
// For API access
|
||||
size_t buffer_size;
|
||||
bool show_confirmation_dialog;
|
||||
bool show_error_dialog;
|
||||
|
||||
// Used to keep track of total elapsed time of transcriptions
|
||||
uint64_t start_ms;
|
||||
uint64_t elapsed_ms;
|
||||
|
||||
VoiceSessionId session_id;
|
||||
VoiceEndpointSessionType session_type;
|
||||
} VoiceUiData;
|
||||
|
||||
void voice_window_lose_focus(VoiceWindow *voice_window);
|
||||
|
||||
void voice_window_regain_focus(VoiceWindow *voice_window);
|
||||
|
||||
void voice_window_transcription_dialog_keep_alive_on_select(VoiceWindow *voice_window,
|
||||
bool keep_alive_on_select);
|
Loading…
Add table
Add a link
Reference in a new issue