Building your own UI

Build Your Own Interface

Glance provides the ability to create your own layouts so your app doesn’t need to have different UI when using our SDK.

You can customize the default interface colors, text, and graphics to match your company’s branding.

To build your own custom user interface the default UI needs to be disabled:

Visitor.defaultUI(false);
Warning

By using this method, you’ll be disabling all default UIs (and until you provide custom ones, they won’t be shown for the user), so pay attention to it.

Custom Agent Viewer

A custom agent viewer can show the agent video in a custom view that your application controls.

The custom agent viewer requires a view that is present in all of the activity layouts with a consistent view ID.

The view type must be a subclass of net.glance.android.SessionView. The custom agent viewer can be customized through the subclassing of net.glance.android.SessionView to present the agent in a circle shape or other specific design.

An example custom agent viewer class that presents the agent within a circle:

public class RoundedAgentViewer extends net.glance.android.SessionView {

    private final String CORNER_RADIUS_ATTRIBUTE = "corner_radius";

    private float mRadius;

    private Path mPath = new Path();

    private RectF mRect = new RectF();

    public RoundedAgentViewer(Context context) {
        super(context);
    }

    public RoundedAgentViewer(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.mRadius = attrs.getAttributeFloatValue(null, CORNER_RADIUS_ATTRIBUTE, 0f);
    }

    public RoundedAgentViewer(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        this.mRadius = attrs.getAttributeFloatValue(null, CORNER_RADIUS_ATTRIBUTE, 0f);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        int savedState = canvas.save();
        float w = getWidth();
        float h = getHeight();

        mPath.reset();
        mRect.set(0, 0, w, h);
        mPath.addRoundRect(mRect, mRadius, mRadius, CCW);
        mPath.close();

        super.onDraw(canvas);
        canvas.restoreToCount(savedState);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        // compute the mPath
        float centerX = w / 2f; // calculating half width
        float centerY = h / 2f; // calculating half height
        this.mRadius = centerX;

        mPath.reset();
        mPath.addCircle(centerX, centerY, Math.min(centerX, centerY), Path.Direction.CW);
        mPath.close();
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        int save = canvas.save();
        canvas.clipPath(mPath);
        super.dispatchDraw(canvas);
        canvas.restoreToCount(save);
    }
}

Define the view layout and ensure it is present in every activity layout.

Here’s the custom agent viewer with id rounded_agent_viewer layout using the RoundedAgentViewer defined above:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/custom_glance_agent_viewer_parent"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:visibility="gone"
    android:elevation="2dp">

    <net.glance.defaultuidemo_java.views.RoundedAgentViewer
        android:id="@+id/custom_glance_agent_viewer"
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:layout_marginStart="10dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        tools:layout_editor_absoluteX="0dp"
        tools:layout_editor_absoluteY="0dp"/>

    <!--  THIS IS OPTIONAL SO YOU CAN DOUBLE CONFIRM WITH THE USERS WHETHER THEY REALLY WANT TO END THE SESSION  -->
    <!--  ALSO, SHOULD BE PLACED HERE SO YOU CAN MOVE THIS VIEW TOGETHER WITH THE AGENT VIDEO, JUST LIKE THE GLANCE'S DEFAULT ONE  -->
    <ImageButton
        android:id="@+id/bt_stop_agent_video"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_below="@+id/rounded_glance_agent_viewer"
        android:layout_alignStart="@+id/rounded_glance_agent_viewer"
        android:layout_alignEnd="@+id/rounded_glance_agent_viewer"
        android:background="@android:color/transparent"
        android:src="@drawable/ic_glance_exit" />

</RelativeLayout>

The custom agent viewer layout defined above now needs to be included within each layout. It is recommended to define the layout in its own file like the one above, called rounded_agent_viewer.xml, and include it within every activity layout:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MyActivity">
    
    ...

    <include layout="@layout/rounded_agent_viewer" />

</androidx.constraintlayout.widget.ConstraintLayout>

Pass the view ID custom_glance_agent_viewer to enable the customer agent viewer within the Visitor API:

Visitor.setCustomSessionViewId(R.id.custom_glance_agent_viewer);

To remove the custom agent viewer behavior:

Visitor.clearCustomSessionViewId();

NOTE: since it is an entire custom view, you need to provide the drag gesture implementation yourself. So check this demo’s implementation as an example:

private void showAgentVideo(int visibility) {
    rlCustomGlanceAgentViewerParent.setVisibility(visibility);

    boolean isVisible = visibility == View.VISIBLE;
    rlCustomGlanceAgentViewerParent.setClickable(isVisible);
    rlCustomGlanceAgentViewerParent.setFocusable(isVisible);

    if (isVisible) {
        // setting the touch listener for the entire view, so you can include the end session button and other views as part of it as well
        rlCustomGlanceAgentViewerParent.setOnTouchListener((view, motionEvent) -> {
            switch (motionEvent.getActionMasked()) {
                case MotionEvent.ACTION_DOWN:
                    dXCustomAgentViewer = view.getX() - motionEvent.getRawX();
                    dYCustomAgentViewer = view.getY() - motionEvent.getRawY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    view.setY(motionEvent.getRawY() + dYCustomAgentViewer);
                    view.setX(motionEvent.getRawX() + dXCustomAgentViewer);
                    break;
                default:
                    return false;
            }
            return true;
        });
    } else {
        rlCustomGlanceAgentViewerParent.setOnTouchListener(null);
    }
}

VisitorListener

When customizing the UI, you will need to handle almost all views’ states inside the VisitorListener events changes

private VisitorListener defaultVisitorListener = new VisitorListener() {
        @Override
        public void onGlanceVisitorEvent(Event event) {
            if(event.getCode() == EventCode.EventConnectedToSession) {
                // Show any UI to indicate the session has started
                // If not using a known session key, you can get the random key here
                // to display to the user to read to the agent
                final String sessionKey = event.GetValue("sessionkey");
        
                runOnUiThread(() -> {
                    swDefaultUi.setEnabled(false);
                    tvSessionKey.setText(getString(R.string.session_key,sessionKey));
                    btStartSession.setEnabled(false);
                    btEndSession.setVisibility(View.VISIBLE);
                    pbLoading.setVisibility(View.GONE);
                });

            } else if(event.getCode() == EventCode.EventGuestCountChange) {
                runOnUiThread(() -> flSSIndicationBorder.setVisibility(View.VISIBLE));
            } else if(event.getCode() == EventCode.EventChildSessionStarted) {
                // The agent started a video session

                runOnUiThread(() -> {
                    // remember to change the view class type in case you change the view too
                    SquaredAgentViewer agentViewer=findViewById(R.id.custom_glance_agent_viewer);
                    AppCompatImageView agentImageView=(AppCompatImageView)agentViewer.getChildAt(0);
                    agentImageView.setId(View.generateViewId());
        
                    agentImageView.postDelayed(() -> {
                        showAgentVideo(View.VISIBLE);
                    }, 2500); // just to make sure that the button won't appear before the video
                });
            } else if(event.getCode() == EventCode.EventChildSessionEnded) {
                runOnUiThread(() -> showAgentVideo(View.GONE));
            }
        }
}        

DefaultUI.DefaultUIListener

In order to show proper views relying on the event, you’ll also need to implement the DefaultUI.DefaultUIListener. Please check the demo implementation below:

private DefaultUI.DefaultUIListener defaultUIListener = new DefaultUI.DefaultUIListener() {
        @Override
        public void defaultUIVoiceDidAuthenticate(String s) {
            Log.i(GLANCE_TAG, "defaultUIVoiceDidAuthenticate");
        }

        @Override
        public void defaultUIDidHangUp() {
            Log.i(GLANCE_TAG, "defaultUIDidHangUp");
        }

        @Override
        public void defaultUIDidMute() {
            Log.i(GLANCE_TAG, "defaultUIDidMute");
        }

        @Override
        public void defaultUIDidUnmute() {
            Log.i(GLANCE_TAG, "defaultUIDidUnmute");
        }

        @Override
        public void defaultUIDidTurnSpeakerphoneOn() {
            Log.i(GLANCE_TAG, "defaultUIDidTurnSpeakerphoneOn");
        }

        @Override
        public void defaultUIDidTurnSpeakerphoneOff() {
            Log.i(GLANCE_TAG, "defaultUIDidTurnSpeakerphoneOff");
        }

        @Override
        public void defaultUIDidClose() {
            // The user clicked on the Cancel button on the Session Key circled dialog
            pbLoading.setVisibility(View.GONE);
            Log.i(GLANCE_TAG, "defaultUIDidClose");
        }

        @Override
        public void glanceDefaultUIVoiceAuthenticationRequired() {
            Log.i(GLANCE_TAG, "glanceDefaultUIVoiceAuthenticationRequired");
        }

        @Override
        public void glanceDefaultUIVoiceAuthenticationFailed() {
            Log.i(GLANCE_TAG, "glanceDefaultUIVoiceAuthenticationFailed");
        }

        public void glanceDefaultUIDialogAccepted() {
            Log.i(GLANCE_TAG, "glanceDefaultUIDialogAccepted");
        }

        public void glanceDefaultUIDialogCancelled() {
            runOnUiThread(() -> {
                pbLoading.setVisibility(View.GONE);
            });
            Log.i(GLANCE_TAG, "glanceDefaultUIDialogCancelled");
        }
        
        // ATTENTION: only available from version 5.2.0.0 or higher
        public void glanceVideoDefaultUIDialogAccepted() {
            Log.i(GLANCE_TAG, "glanceVideoDefaultUIDialogAccepted");
        }

        // ATTENTION: only available from version 5.2.0.0 or higher        
        public void glanceVideoDefaultUIDialogCancelled() {
            Log.i(GLANCE_TAG, "glanceVideoDefaultUIDialogCancelled");
        }
};

Video UI

For now, you can only change colors, strings, and some images icons on the video UI. Find more details below:

  1. To set various colors used by the default UI, add them to your res/values/colors.xml file, using constants found in the images below.

  2. To modify the UI text, update your application’s res/values/strings.xml file, using constants found in the images below.

  3. To override images, add your custom images (SVG format) to the res/drawable folder by right-clicking on drawable and selecting New > Vector Asset. Name the image using the constants in the image below:

VideoUI VideoUI

VideoUI VideoUI

Voice UI

As well as for the video UI, you can only change colors, strings, and some images icons on the video UI. The steps are the same.

Also, to integrate the Voice capability on your app, please check the Voice demos.

Presence UI

For presence, it will be only possible to customize the requests confirmation and show Terms and Conditions UI. The URL to a Terms web page is configured in the agent-side CRM system.

Q&A

  1. I’ve already had called Visitor.defaultUI(false); and Visitor.setCustomSessionViewId(R.id.glance_agent_viewer); but I’m still not able to see my custom Agent Viewer layout.
    A.: check if you haven’t forgotten to add the <include layout="@layout/agent_viewer"/> line in your MainActivity layout.

  2. I’ve set all configurations needed to customize the AgentViewer view, but the view is not draggable.
    A.: you need to implement the View.OnTouchListener for this to work. Check our sample of how to do it.

  3. I’ve implemented a different shape for my custom AgentViewer view, but it is being shown in a square shape.
    A.: check your view class implementation. Using Canvas, Path and Paint in Android is always challenging.

  4. I’ve overridden the default UI and video UI dialog button colors values, but they are not changing.
    A.: check your defined themes are extending any Material Themes or other similar, because they may prevent the buttons to use our defined color keys.