com.actinarium.rhythm.sample.RhythmSandbox.java Source code

Java tutorial

Introduction

Here is the source code for com.actinarium.rhythm.sample.RhythmSandbox.java

Source

/*
 * Copyright (C) 2016 Actinarium
 *
 * 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.
 */

package com.actinarium.rhythm.sample;

import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.MultiAutoCompleteTextView;
import com.actinarium.aligned.Utils;
import com.actinarium.rhythm.RhythmDrawable;
import com.actinarium.rhythm.RhythmOverlay;
import com.actinarium.rhythm.RhythmOverlayInflater;
import com.actinarium.rhythm.control.RhythmFrameLayout;
import com.actinarium.rhythm.layer.Columns;
import com.actinarium.rhythm.layer.DimensionsLabel;
import com.actinarium.rhythm.layer.Fill;
import com.actinarium.rhythm.layer.GridLines;
import com.actinarium.rhythm.layer.Inset;
import com.actinarium.rhythm.layer.Keyline;
import com.actinarium.rhythm.layer.RatioKeyline;

/**
 * A presenter for the Rhythm Sandbox card, where you can try the configuration at runtime
 *
 * @author Paul Danyliuk
 */
public class RhythmSandbox {

    /*
     * Words for auto-complete. Not containing words from custom layers
     */
    String[] ALL_CONFIG_WORDS = { Keyline.Factory.LAYER_TYPE, GridLines.Factory.LAYER_TYPE, Fill.Factory.LAYER_TYPE,
            Inset.Factory.LAYER_TYPE, RatioKeyline.Factory.LAYER_TYPE, DimensionsLabel.Factory.LAYER_TYPE,
            Columns.Factory.LAYER_TYPE, "outside", "no-clip", "clip-only", "from=", "distance=", "step=", "ratio=",
            "gravity=", "count=", "top", "bottom", "left", "right", "top=", "bottom=", "left=", "right=", "width=",
            "height=", "color=", "color=#", "text=", "limit=", "offset=", "thickness=", "text-color=",
            "text-size=" };

    private static final String DEFAULT_SANDBOX_CONFIG = "@margin=16dp\n" + "@keyline_thickness=1dp\n"
            + "grid-lines step=8dp from=left\n" + "grid-lines step=4dp from=top\n"
            + "inset left=0dp width=@margin\n" + " fill\n" + "inset right=0dp width=@margin\n" + " fill\n"
            + "keyline distance=@margin from=left\n" + "keyline distance=@margin from=right";

    // -----------------------------------------------------------------------------------------------------------------

    private static final String ARG_RENDER = "com.actinarium.rhythm.sample.arg.RENDER";

    private AppCompatActivity mActivity;
    private RhythmOverlayInflater mOverlayInflater;

    private MultiAutoCompleteTextView mOverlayConfig;
    private RhythmFrameLayout mPreview;
    private boolean mDoRender;

    /**
     * Initialize a presenter for sandbox
     *
     * @param activity        Activity that hosts this sandbox
     * @param rootView        Root view of the sandbox
     * @param overlayInflater Overlay inflater used to inflate rhythm config
     */
    public RhythmSandbox(AppCompatActivity activity, View rootView, RhythmOverlayInflater overlayInflater) {
        mActivity = activity;
        mOverlayInflater = overlayInflater;

        // Find and init preview layout
        mPreview = (RhythmFrameLayout) rootView.findViewById(R.id.preview);
        mPreview.setRhythmDrawable(new RhythmDrawable(null));

        // Find and init overlay config text box
        mOverlayConfig = (MultiAutoCompleteTextView) rootView.findViewById(R.id.config);
        mOverlayConfig.setHorizontallyScrolling(true);

        // Fix config text box metrics
        int i4dp = activity.getResources().getDimensionPixelOffset(R.dimen.i4dp);
        Utils.setExactMetrics(mOverlayConfig, i4dp * 6, i4dp * 5, i4dp * 3);

        // Enable auto-complete for config
        ArrayAdapter<String> adapter = new ArrayAdapter<>(activity, android.R.layout.simple_dropdown_item_1line,
                ALL_CONFIG_WORDS);
        mOverlayConfig.setTokenizer(new ConfigTokenizer());
        mOverlayConfig.setAdapter(adapter);

        // Find and init Apply button
        final Button applyButton = (Button) rootView.findViewById(R.id.apply);
        applyButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                updatePreview();
            }
        });
    }

    public void onSaveInstanceState(Bundle outState) {
        outState.putBoolean(ARG_RENDER, mDoRender);
    }

    public void onRestoreInstanceState(Bundle savedState) {
        if (savedState == null) {
            mOverlayConfig.setText(DEFAULT_SANDBOX_CONFIG);
        } else {
            mDoRender = savedState.getBoolean(ARG_RENDER, false);
            if (mDoRender) {
                updatePreview();
            }
        }
    }

    /**
     * Take the text out of text field and render an overlay into preview frame
     */
    private void updatePreview() {
        String overlayConfig = mOverlayConfig.getText().toString();

        if (validate(overlayConfig)) {
            mDoRender = true;
            final RhythmOverlay overlay = mOverlayInflater.inflateOverlay(overlayConfig);
            mPreview.getRhythmDrawable().setOverlay(overlay);
        }
    }

    /**
     * Perform overlay validation, show dialog if issues encountered
     *
     * @param overlayConfig Overlay config string to validate
     * @return true if validation passed
     */
    private boolean validate(String overlayConfig) {
        String error = null;

        // If config is empty, short-circuit
        if (overlayConfig.trim().length() == 0) {
            error = mActivity.getString(R.string.validation_config_empty);
            InvalidOverlayDialogFragment dialogFragment = InvalidOverlayDialogFragment.newInstance(error);
            dialogFragment.show(mActivity.getSupportFragmentManager(), InvalidOverlayDialogFragment.TAG);
            return false;
        }

        // Heads up: line-by-line validation was removed because of increased complexity after 0.9.5.
        // Thing is, when inflating a raw overlay config file the validation is not really that helpful,
        // therefore not implementing it as a core feature.
        try {
            mOverlayInflater.inflateOverlay(overlayConfig);
        } catch (Exception e) {
            error = e.getMessage();
        }

        if (error == null) {
            return true;
        } else {
            InvalidOverlayDialogFragment dialogFragment = InvalidOverlayDialogFragment.newInstance(error);
            dialogFragment.show(mActivity.getSupportFragmentManager(), InvalidOverlayDialogFragment.TAG);
            return false;
        }
    }

    /**
     * Imperfect tokenizer for configuration auto-complete
     */
    private static class ConfigTokenizer implements MultiAutoCompleteTextView.Tokenizer {
        @Override
        public int findTokenStart(CharSequence text, int cursor) {
            int i = cursor;
            while (i > 0 && text.charAt(i - 1) != ' ' && text.charAt(i - 1) != '=' && text.charAt(i - 1) != '\n') {
                i--;
            }
            return i;
        }

        @Override
        public int findTokenEnd(CharSequence text, int cursor) {
            int i = cursor;
            int len = text.length();
            while (i < len) {
                if (text.charAt(i) == ' ' || text.charAt(i) == '=' || text.charAt(i) == '\n') {
                    return i;
                } else {
                    i++;
                }
            }
            return len;
        }

        @Override
        public CharSequence terminateToken(CharSequence text) {
            int i = text.length();
            if (i > 0 && text.charAt(i - 1) == ' ' || text.charAt(i - 1) == '=' || text.charAt(i - 1) == '\n'
                    || text.charAt(i - 1) == '#') {
                return text;
            } else {
                return text + " ";
            }
        }
    }

    /**
     * Dialog for validation errors
     */
    public static class InvalidOverlayDialogFragment extends DialogFragment {

        public static final String TAG = "InvalidOverlayDialogFragment";

        public static final String ARG_ERROR = "com.actinarium.rhythm.sample.intent.arg.ERROR";

        private Context mContext;

        public static InvalidOverlayDialogFragment newInstance(String error) {
            InvalidOverlayDialogFragment fragment = new InvalidOverlayDialogFragment();
            Bundle args = new Bundle(1);
            args.putString(ARG_ERROR, error);
            fragment.setArguments(args);
            return fragment;
        }

        @Override
        public void onAttach(Activity activity) {
            super.onAttach(activity);
            mContext = activity;
        }

        @Override
        @NonNull
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            final String error = getArguments().getString(ARG_ERROR);

            AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
            builder.setTitle(R.string.validation_config_dialog_title).setMessage(error)
                    .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.cancel();
                        }
                    });
            return builder.create();
        }
    }
}