Search code examples
google-glassgoogle-gdk

Grace period slider in a high-frequency live card?


Is there any way to use a Slider.GracePeriod from the GDK in a high-frequency live card? I have the menu option "send message," and after you tap it and input your message, I'd like to display a "sending message..." alert dialog (or something akin to it) with a grace period slider. However, I haven't had success getting the grace period slider to appear in a dialog (i.e. using the same view that was being passed to Dialog.setContentView() as the argument to Slider.from()), the live card menu activity, or anywhere else. Any ideas?

(This most likely isn't related, but I did notice that Google's GDK reference mentions that a grace period slider animates during the given grace period in "timeInMs." This sounds like an argument to a function or constructor, but if so I couldn't find one. Maybe grace period sliders don't work if you don't set this?)

Note: I am aware of this existing question, but it hasn't been answered yet and it concerns a different kind of both slider and livecard, anyway, so another question seemed appropriate.


Solution

  • Something like the following should work for you; the following class wraps up grace period/confirmation dialog logic to provide the same experience as built-in Glassware.

    public class GracePeriodDialog extends Dialog {
        private static final long COMPLETION_MESSAGE_TIMEOUT_MILLIS = 1000;
    
        private final DialogInterface.OnClickListener mOnClickListener;
        private final AudioManager mAudioManager;
        private final GestureDetector mGestureDetector;
        private final CharSequence mCompletedText;
    
        private View mContentView;
        private Slider.GracePeriod mGracePeriod;
    
        /** Handles the tap gesture to complete the grace period early. */
        private final GestureDetector.BaseListener mBaseListener =
                new GestureDetector.BaseListener() {
            @Override
            public boolean onGesture(Gesture gesture) {
                if (gesture == Gesture.TAP) {
                    // Cancel the grace period if the user tapped early so that the completion event
                    // doesn't fire twice.
                    mGracePeriod.cancel();
                    showCompletion();
                    return true;
                }
                return false;
            }
        };
    
        /** Called when the grace period has ended to show the completion message. */
        private final Slider.GracePeriod.Listener mGracePeriodListener =
                new Slider.GracePeriod.Listener() {
            @Override
            public void onGracePeriodEnd() {
                showCompletion();
            }
    
            @Override
            public void onGracePeriodCancel() {}
        };
    
        /**
         * Called after a 1-second delay when the grace period has completed (or the user tapped).
         * Forwards the successful completion as an {@code onClick} event on the dialog.
         */
        private final Runnable mDoneRunnable = new Runnable() {
            @Override
            public void run() {
                if (mOnClickListener != null) {
                    // Since Glass dialogs do not have buttons, the index passed to onClick is always 0.
                    mOnClickListener.onClick(GracePeriodDialog.this, 0);
                }
                dismiss();
            }
        };
    
        /**
         * Creates a new {@code GracePeriodDialog}. The dialog can show a progress message and icon
         * while the grace period is running, which changes to a completion message when the grace
         * period ends (for example, "Sending" -> "Sent".
         */
        public GracePeriodDialog(Context context,
                           int progressIconId, CharSequence progressText,
                           CharSequence completedText,
                           DialogInterface.OnClickListener onClickListener) {
            super(context);
    
            mOnClickListener = onClickListener;
            mAudioManager =
                    (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
            mGestureDetector =
                    new GestureDetector(context).setBaseListener(mBaseListener);
            mCompletedText = completedText;
    
            updateContentView(progressText, progressIconId);
        }
    
        @Override
        public void onAttachedToWindow() {
            super.onAttachedToWindow();
            mGracePeriod = Slider.from(mContentView).startGracePeriod(mGracePeriodListener);
        }
    
        @Override
        public void onBackPressed() {
            mGracePeriod.cancel();
            super.onBackPressed();
        }
    
        /** Overridden to let the gesture detector handle a possible tap event. */
        @Override
        public boolean onGenericMotionEvent(MotionEvent event) {
            return mGestureDetector.onMotionEvent(event)
                    || super.onGenericMotionEvent(event);
        }
    
        private void showCompletion() {
            mAudioManager.playSoundEffect(Sounds.SUCCESS);
            updateContentView(mCompletedText, R.drawable.ic_done_50);
            mContentView.postDelayed(mDoneRunnable, COMPLETION_MESSAGE_TIMEOUT_MILLIS);
        }
    
        private void updateContentView(CharSequence text, int iconResId) {
            mContentView = new CardBuilder(getContext(), CardBuilder.Layout.MENU)
                    .setText(text)
                    .setIcon(iconResId)
                    .getView();
            setContentView(mContentView);
        }
    }
    

    One possible gotcha to watch out for: make sure that your menu activity isn't being finished (in onOptionsMenuClosed, for example) before you display the dialog or start the grace period.