package com.hulab.debugkit; import android.animation.ValueAnimator; import android.app.Fragment; import android.os.Build; import android.os.Bundle; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.animation.AccelerateInterpolator; import android.view.animation.Animation; import android.view.animation.RotateAnimation; import android.widget.Button; import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.TextView; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import java.util.Locale; /** * Created by Nebneb on 26/10/2016 at 11:27. */ public class DevToolFragment extends Fragment { private static final int MINIFY_WIDTH = 132; private int CONSOLE_HEIGHT = 110; private int CONSOLE_WIDTH = 250; private int CONSOLE_TEXT_SIZE = 12; private View mRootView; private LayoutInflater mInflater; private List<DebugFunction> mFunctions = new ArrayList<>(); private TextView mConsole; private ScrollView mConsoleContainer; private View mPanel; private View mMinifyButton; private float dX; private float dY; private float startX = 0; private float startY = 0; private DevToolTheme mTheme = DevToolTheme.DARK; public DevToolFragment() { } void displayAt(float x, float y) { this.startX = x; this.startY = y; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { mInflater = inflater; mRootView = inflater.inflate(R.layout.debugkit_fragment_dev_tools, container, false); return mRootView; } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); LinearLayout mButtonContainer = (LinearLayout) mRootView.findViewById(R.id.debugkit_button_container); for (int i = 0; i < mFunctions.size(); i++) { Button button = (Button) mInflater .inflate(mTheme == DevToolTheme.DARK ? R.layout.debugkit_function_button_dark : R.layout.debugkit_function_button_light, mButtonContainer, false); final DebugFunction function = mFunctions.get(i); final String title = function.title == null ? "F" + (i + 1) : function.title; if (function.title != null) { ViewGroup.LayoutParams params = button.getLayoutParams(); params.width = LinearLayout.LayoutParams.WRAP_CONTENT; button.setLayoutParams(params); } button.setText(title); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { try { if (function != null) { String result = function.call(); if (result != null) log(title + ": " + result); } else log(title + " is undefined"); } catch (Exception e) { log("Error: see logcat for more details"); e.printStackTrace(); } } }); mButtonContainer.addView(button); } mConsole = (TextView) mRootView.findViewById(R.id.debugkit_console); mConsoleContainer = (ScrollView) mRootView.findViewById(R.id.debugkit_console_scroll_view); mMinifyButton = mRootView.findViewById(R.id.debugkit_tools_minify); mPanel = mRootView.findViewById(R.id.debugkit_tools_panel); mRootView.findViewById(R.id.debugkit_tools_close_button).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (isAdded()) { try { getActivity().getFragmentManager() .beginTransaction() .remove(DevToolFragment.this) .commit(); } catch (Exception e) { e.printStackTrace(); } } } }); mRootView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return DevToolFragment.this.onTouch(v, event); } }); ViewGroup.LayoutParams layoutParams = mConsoleContainer.getLayoutParams(); layoutParams.height = dpTopX(CONSOLE_HEIGHT); mConsoleContainer.setLayoutParams(layoutParams); layoutParams = mConsole.getLayoutParams(); layoutParams.height = dpTopX(CONSOLE_HEIGHT); layoutParams.width = dpTopX(CONSOLE_WIDTH); mConsole.setLayoutParams(layoutParams); mConsole.setMinimumHeight(dpTopX(CONSOLE_HEIGHT)); view.setX(startX); view.setY(startY); mMinifyButton.setTag(mMinifyButton.getId(), false); mMinifyButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { switchMinify(); } }); applyTheme(); softLog("ready."); } /** * Switch the tool to minify mode. */ private void switchMinify() { RotateAnimation rotateAnimation; ValueAnimator heightValueAnimator; ValueAnimator widthValueAnimator; if ((boolean) mMinifyButton.getTag(mMinifyButton.getId())) { rotateAnimation = new RotateAnimation(180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); heightValueAnimator = ValueAnimator.ofInt(0, dpTopX(CONSOLE_HEIGHT)); widthValueAnimator = ValueAnimator.ofInt(dpTopX(MINIFY_WIDTH), dpTopX(CONSOLE_WIDTH)); mMinifyButton.setTag(mMinifyButton.getId(), false); } else { rotateAnimation = new RotateAnimation(0, 180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); heightValueAnimator = ValueAnimator.ofInt(dpTopX(CONSOLE_HEIGHT), 0); widthValueAnimator = ValueAnimator.ofInt(dpTopX(CONSOLE_WIDTH), dpTopX(MINIFY_WIDTH)); mMinifyButton.setTag(mMinifyButton.getId(), true); } heightValueAnimator.setDuration(200); heightValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { public void onAnimationUpdate(ValueAnimator animation) { Integer value = (Integer) animation.getAnimatedValue(); mConsoleContainer.getLayoutParams().height = value.intValue(); mConsoleContainer.requestLayout(); } }); widthValueAnimator.setDuration(200); widthValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { public void onAnimationUpdate(ValueAnimator animation) { Integer value = (Integer) animation.getAnimatedValue(); mConsole.getLayoutParams().width = value.intValue(); mConsole.requestLayout(); } }); rotateAnimation.setDuration(200); rotateAnimation.setFillAfter(true); mMinifyButton.startAnimation(rotateAnimation); heightValueAnimator.setInterpolator(new AccelerateInterpolator()); heightValueAnimator.start(); widthValueAnimator.setInterpolator(new AccelerateInterpolator()); widthValueAnimator.start(); } private boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: dX = v.getX() - event.getRawX(); dY = v.getY() - event.getRawY(); break; case MotionEvent.ACTION_MOVE: v.setX(event.getRawX() + dX); v.setY(event.getRawY() + dY); break; case MotionEvent.ACTION_UP: break; default: return false; } return true; } /** * Call this function at runtime if you want to log something in the console. * * @param string the message that will be logged to to the console. * <p> * string will be logged in the console on a new line as following: * <br> * {@code HH:mm:ss > string} */ public void log(final String string) { final StringBuilder sb = new StringBuilder(mConsole.getText()); sb.append("\n"); sb.append(getCurrentTime()).append(" > "); sb.append(string); write(sb.toString()); } /** * Call this function at runtime if you want to clear the console. */ public void clear() { mConsole.setText(""); softLog("ready."); } private void softLog(String string) { final StringBuilder sb = new StringBuilder(mConsole.getText()); sb.append(getCurrentTime()).append(" > "); sb.append(string); write(sb.toString()); } private void write(final String string) { mConsole.setText(string); mConsole.post(new Runnable() { @Override public void run() { mConsole.requestLayout(); if (mConsoleContainer != null) { mConsoleContainer.post(new Runnable() { @Override public void run() { mConsoleContainer.fullScroll(ScrollView.FOCUS_DOWN); mConsoleContainer.requestLayout(); } }); } } }); } /** * Add a function to the list. This will add a button as well when calling {@code build()} * * @param function must implement {@link DebugFunction}. */ public void addFunction(DebugFunction function) { this.mFunctions.add(function); } /** * Set the function list. This will corresponding buttons when calling {@code build()} * * @param functions must be a List of {@link DebugFunction}. */ public void setFunctionList(List<DebugFunction> functions) { this.mFunctions = functions; } /** * Set the console text size. Must be called after having called build() * * @param sp the size of the text in sp. */ public void changeConsoleTextSize(final int sp) { mConsole.post(new Runnable() { @Override public void run() { mConsole.setTextSize(TypedValue.COMPLEX_UNIT_SP, sp); } }); } /** * Set the console text size. The size will be applied on build. * * @param sp the size of the text in sp. */ public void setConsoleTextSize(final int sp) { CONSOLE_TEXT_SIZE = sp; } /** * Set the theme of the debug tool * * @param theme can be {@code DevToolTheme.LIGHT} or {@code DevToolTheme.DARK}. * The default theme is {@code DevToolTheme.DARK} */ public void setTheme(DevToolTheme theme) { this.mTheme = theme; } /** * This method will be called on build. You can call this method if you want to change the * theme of the console theme at runtime. */ public void applyTheme() { switch (mTheme) { case LIGHT: mConsole.setBackgroundColor(getColor(R.color.debug_kit_primary_light)); mConsole.setTextColor(getColor(R.color.debug_kit_background_black_light)); break; default: mConsole.setBackgroundColor(getColor(R.color.debug_kit_background_black)); mConsole.setTextColor(getColor(R.color.debug_kit_primary)); } mConsole.setTextSize(TypedValue.COMPLEX_UNIT_SP, CONSOLE_TEXT_SIZE); } private int getColor(int resId) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { return getResources().getColor(resId, null); } else { return getResources().getColor(resId); } } private int dpTopX(int dp) { return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics())); } private String getCurrentTime() { SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss", Locale.getDefault()); return df.format(Calendar.getInstance().getTime()); } /** * Set the console height. * * @param consoleHeight represents the console height in dp. */ public void setConsoleHeight(int consoleHeight) { this.CONSOLE_HEIGHT = consoleHeight; } /** * Set the console width. * * @param consoleWidth represents the console width in dp. */ public void setConsoleWidth(int consoleWidth) { this.CONSOLE_WIDTH = consoleWidth; } /** * Enum, theme choices for the debug tool. */ public enum DevToolTheme { DARK, LIGHT } }