public class BlockEvent {
    private boolean isBullet;
    private boolean isQuote;

    public BlockEvent() {}

    public BlockEvent(boolean isBullet, boolean isQuote) {
        this.isBullet = isBullet;
        this.isQuote = isQuote;
    }

    public boolean isBullet() {
        return isBullet;
    }

    public void setBullet(boolean isBullet) {
        this.isBullet = isBullet;
    }

    public boolean isQuote() {
        return isQuote;
    }

    public void setQuote(boolean isQuote) {
        this.isQuote = isQuote;
    }
} public class BoldEvent {} public class BulletEvent {} public class DotsEvent {} public class ImageEvent {
    private Bitmap bitmap;

    public ImageEvent(@NonNull Bitmap bitmap) {
        this.bitmap = bitmap;
    }

    public Bitmap getBitmap() {
        return bitmap;
    }
} public class InsertEvent {
    private int type;
    private int position;
    private Spanned prefix;
    private Spanned suffix;
    private Bitmap bitmap;

    public InsertEvent(@Type.TypeValue int type, int position, @Nullable Spanned prefix, @Nullable Spanned suffix, @Nullable Bitmap bitmap) {
        this.type = type;
        this.position = position;
        this.prefix = prefix != null ? prefix : new SpannableString("");
        this.suffix = suffix != null ? suffix : new SpannableString("");
        this.bitmap = bitmap;
    }

    @Type.TypeValue
    public int getType() {
        return type;
    }

    public int getPosition() {
        return position;
    }

    @NonNull
    public Spanned getPrefix() {
        return prefix;
    }

    @NonNull
    public Spanned getSuffix() {
        return suffix;
    }

    @Nullable
    public Bitmap getBitmap() {
        return bitmap;
    }
} public class FormatEvent {
    private boolean isBold;
    private boolean isItalic;
    private boolean isUnderline;
    private boolean isStrikethrough;
    private boolean isLink;

    public FormatEvent() {}

    public FormatEvent(boolean isBold, boolean isItalic, boolean isUnderline, boolean isStrikethrough, boolean isLink) {
        this.isBold = isBold;
        this.isItalic = isItalic;
        this.isUnderline = isUnderline;
        this.isStrikethrough = isStrikethrough;
        this.isLink = isLink;
    }

    public boolean isBold() {
        return isBold;
    }

    public void setBold(boolean isBold) {
        this.isBold = isBold;
    }

    public boolean isItalic() {
        return isItalic;
    }

    public void setItalic(boolean isItalic) {
        this.isItalic = isItalic;
    }

    public boolean isUnderline() {
        return isUnderline;
    }

    public void setUnderline(boolean isUnderline) {
        this.isUnderline = isUnderline;
    }

    public boolean isStrikethrough() {
        return isStrikethrough;
    }

    public void setStrikethrough(boolean isStrikethrough) {
        this.isStrikethrough = isStrikethrough;
    }

    public boolean isLink() {
        return isLink;
    }

    public void setLink(boolean isLink) {
        this.isLink = isLink;
    }
} public class ItalicEvent {} public class QuoteEvent {} public class StrikethroughEvent {} public class UnderlineEvent {} public class RxBus {
    private static final RxBus sRxBus = new RxBus();

    public static RxBus getInstance() {
        return sRxBus;
    }

    private Subject<Object, Object> mSubject = new SerializedSubject<>(PublishSubject.create());

    private RxBus() {}

    public void post(Object object) {
        mSubject.onNext(object);
    }

    public <T> Observable<T> toObservable(Class<T> eventType) {
        return mSubject.ofType(eventType);
    }

    public boolean hasObservers() {
        return mSubject.hasObservers();
    }
} public class Type {
    public static final int TYPE_BLOCK = 0x100;
    public static final int TYPE_DOTS = 0x101;
    public static final int TYPE_IMAGE = 0x102;
    public static final int TYPE_TITLE = 0x103;

    @IntDef({TYPE_BLOCK, TYPE_DOTS, TYPE_IMAGE, TYPE_TITLE})
    @Retention(RetentionPolicy.SOURCE)
    public @interface TypeValue {}

    private int type;

    public Type(@TypeValue int type) {
        this.type = type;
    }

    @TypeValue
    public final int getType() {
        return type;
    }
} public class TypeBlock extends Type {
    private Spanned content;
    private boolean isBullet;
    private boolean isQuote;

    public TypeBlock(@Nullable Spanned content) {
        super(TYPE_BLOCK);
        setContent(content);
    }

    @NonNull
    public Spanned getContent() {
        return content;
    }

    public void setContent(@Nullable Spanned content) {
        this.content = content != null ? content : new SpannableString("");
    }

    public boolean isBullet() {
        return isBullet;
    }

    public void setBullet(boolean isBullet) {
        this.isBullet = isBullet;
    }

    public boolean isQuote() {
        return isQuote;
    }

    public void setQuote(boolean isQuote) {
        this.isQuote = isQuote;
    }
} public class KnifeText extends AppCompatEditText {
    public static final int FORMAT_BOLD = 0x01;
    public static final int FORMAT_ITALIC = 0x02;
    public static final int FORMAT_UNDERLINE = 0x03;
    public static final int FORMAT_STRIKETHROUGH = 0x04;
    public static final int FORMAT_LINK = 0x05;

    @IntDef({FORMAT_BOLD, FORMAT_ITALIC, FORMAT_UNDERLINE, FORMAT_STRIKETHROUGH, FORMAT_LINK})
    @Retention(RetentionPolicy.SOURCE)
    public @interface FormatValue {}

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

    public KnifeText(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public KnifeText(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void bold(boolean valid) {
        if (valid) {
            styleValid(Typeface.BOLD, getSelectionStart(), getSelectionEnd());
        } else {
            styleInvalid(Typeface.BOLD, getSelectionStart(), getSelectionEnd());
        }
    }

    public void italic(boolean valid) {
        if (valid) {
            styleValid(Typeface.ITALIC, getSelectionStart(), getSelectionEnd());
        } else {
            styleInvalid(Typeface.ITALIC, getSelectionStart(), getSelectionEnd());
        }
    }

    private void styleValid(int style, int start, int end) {
        switch (style) {
            case Typeface.NORMAL:
            case Typeface.BOLD:
            case Typeface.ITALIC:
            case Typeface.BOLD_ITALIC:
                break;
            default:
                return;
        }

        if (start < end) {
            getEditableText().setSpan(new StyleSpan(style), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
    }

    private void styleInvalid(int style, int start, int end) {
        switch (style) {
            case Typeface.NORMAL:
            case Typeface.BOLD:
            case Typeface.ITALIC:
            case Typeface.BOLD_ITALIC:
                break;
            default:
                return;
        }

        if (start >= end) {
            return;
        }

        StyleSpan[] spans = getEditableText().getSpans(start, end, StyleSpan.class);
        List<KnifePart> list = new ArrayList<>();

        for (StyleSpan span : spans) {
            if (span.getStyle() == style) {
                list.add(new KnifePart(getEditableText().getSpanStart(span), getEditableText().getSpanEnd(span)));
                getEditableText().removeSpan(span);
            }
        }

        for (KnifePart part : list) {
            if (part.isValid()) {
                if (part.getStart() < start) {
                    styleValid(style, part.getStart(), start);
                }

                if (part.getEnd() > end) {
                    styleValid(style, end, part.getEnd());
                }
            }
        }
    }

    private boolean containStyle(int style, int start, int end) {
        switch (style) {
            case Typeface.NORMAL:
            case Typeface.BOLD:
            case Typeface.ITALIC:
            case Typeface.BOLD_ITALIC:
                break;
            default:
                return false;
        }

        if (start > end) {
            return false;
        }

        if (start == end) {
            if (start - 1 < 0 || start + 1 > getEditableText().length()) {
                return false;
            } else {
                StyleSpan[] before = getEditableText().getSpans(start - 1, start, StyleSpan.class);
                StyleSpan[] after = getEditableText().getSpans(start, start + 1, StyleSpan.class);
                return before.length > 0 && after.length > 0 && before[0].getStyle() == style && after[0].getStyle() == style;
            }
        } else {
            StringBuilder builder = new StringBuilder();

            // Make sure no duplicate characters be added
            for (int i = start; i < end; i++) {
                StyleSpan[] spans = getEditableText().getSpans(i, i + 1, StyleSpan.class);
                for (StyleSpan span : spans) {
                    if (span.getStyle() == style) {
                        builder.append(getEditableText().subSequence(i, i + 1).toString());
                        break;
                    }
                }
            }

            return getEditableText().subSequence(start, end).toString().equals(builder.toString());
        }
    }

    public void underline(boolean valid) {
        if (valid) {
            underlineValid(getSelectionStart(), getSelectionEnd());
        } else {
            underlineInvalid(getSelectionStart(), getSelectionEnd());
        }
    }

    private void underlineValid(int start, int end) {
        if (start < end) {
            getEditableText().setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
    }

    private void underlineInvalid(int start, int end) {
        if (start >= end) {
            return;
        }

        UnderlineSpan[] spans = getEditableText().getSpans(start, end, UnderlineSpan.class);
        List<KnifePart> list = new ArrayList<>();

        for (UnderlineSpan span : spans) {
            list.add(new KnifePart(getEditableText().getSpanStart(span), getEditableText().getSpanEnd(span)));
            getEditableText().removeSpan(span);
        }

        for (KnifePart part : list) {
            if (part.isValid()) {
                if (part.getStart() < start) {
                    underlineValid(part.getStart(), start);
                }

                if (part.getEnd() > end) {
                    underlineValid(end, part.getEnd());
                }
            }
        }
    }

    private boolean containUnderline(int start, int end) {
        if (start > end) {
            return false;
        }

        if (start == end) {
            if (start - 1 < 0 || start + 1 > getEditableText().length()) {
                return false;
            } else {
                UnderlineSpan[] before = getEditableText().getSpans(start - 1, start, UnderlineSpan.class);
                UnderlineSpan[] after = getEditableText().getSpans(start, start + 1, UnderlineSpan.class);
                return before.length > 0 && after.length > 0;
            }
        } else {
            StringBuilder builder = new StringBuilder();

            for (int i = start; i < end; i++) {
                if (getEditableText().getSpans(i, i + 1, UnderlineSpan.class).length > 0) {
                    builder.append(getEditableText().subSequence(i, i + 1).toString());
                }
            }

            return getEditableText().subSequence(start, end).toString().equals(builder.toString());
        }
    }

    public void strikethrough(boolean valid) {
        if (valid) {
            strikethroughValid(getSelectionStart(), getSelectionEnd());
        } else {
            strikethroughInvalid(getSelectionStart(), getSelectionEnd());
        }
    }

    private void strikethroughValid(int start, int end) {
        if (start < end) {
            getEditableText().setSpan(new StrikethroughSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
    }

    private void strikethroughInvalid(int start, int end) {
        if (start >= end) {
            return;
        }

        StrikethroughSpan[] spans = getEditableText().getSpans(start, end, StrikethroughSpan.class);
        List<KnifePart> list = new ArrayList<>();

        for (StrikethroughSpan span : spans) {
            list.add(new KnifePart(getEditableText().getSpanStart(span), getEditableText().getSpanEnd(span)));
            getEditableText().removeSpan(span);
        }

        for (KnifePart part : list) {
            if (part.isValid()) {
                if (part.getStart() < start) {
                    strikethroughValid(part.getStart(), start);
                }

                if (part.getEnd() > end) {
                    strikethroughValid(end, part.getEnd());
                }
            }
        }
    }

    private boolean containStrikethrough(int start, int end) {
        if (start > end) {
            return false;
        }

        if (start == end) {
            if (start - 1 < 0 || start + 1 > getEditableText().length()) {
                return false;
            } else {
                StrikethroughSpan[] before = getEditableText().getSpans(start - 1, start, StrikethroughSpan.class);
                StrikethroughSpan[] after = getEditableText().getSpans(start, start + 1, StrikethroughSpan.class);
                return before.length > 0 && after.length > 0;
            }
        } else {
            StringBuilder builder = new StringBuilder();

            for (int i = start; i < end; i++) {
                if (getEditableText().getSpans(i, i + 1, StrikethroughSpan.class).length > 0) {
                    builder.append(getEditableText().subSequence(i, i + 1).toString());
                }
            }

            return getEditableText().subSequence(start, end).toString().equals(builder.toString());
        }
    }

    // TODO link

    public boolean contains(@FormatValue int format) {
        switch (format) {
            case FORMAT_BOLD:
                return containStyle(Typeface.BOLD, getSelectionStart(), getSelectionEnd());
            case FORMAT_ITALIC:
                return containStyle(Typeface.ITALIC, getSelectionStart(), getSelectionEnd());
            case FORMAT_UNDERLINE:
                return containUnderline(getSelectionStart(), getSelectionEnd());
            case FORMAT_STRIKETHROUGH:
                return containStrikethrough(getSelectionStart(), getSelectionEnd());
            case FORMAT_LINK:
                // TODO
                return false;
            default:
                return false;
        }
    }

    public void clearFormats() {
        setText(getEditableText().toString());
        setSelection(getEditableText().length());
    }

    @Override
    protected void onSelectionChanged(int start, int end) {
        if (start >= end) {
            return;
        }

        FormatEvent event = new FormatEvent();
        event.setBold(contains(FORMAT_BOLD));
        event.setItalic(contains(FORMAT_ITALIC));
        event.setUnderline(contains(FORMAT_UNDERLINE));
        event.setStrikethrough(contains(FORMAT_STRIKETHROUGH));
        event.setLink(contains(FORMAT_LINK));
        RxBus.getInstance().post(event);
    }
}
import android.graphics.Bitmap;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.text.Editable;
import android.text.SpannableString;
import android.text.Spanned;
import android.view.KeyEvent;
import android.view.View;
import com.jakewharton.rxbinding.view.RxView;
import com.jakewharton.rxbinding.widget.RxTextView;
import com.jakewharton.rxbinding.widget.TextViewAfterTextChangeEvent;
import io.github.mthli.type.R;
import io.github.mthli.type.event.BlockEvent;
import io.github.mthli.type.event.BoldEvent;
import io.github.mthli.type.event.BulletEvent;
import io.github.mthli.type.event.DotsEvent;
import io.github.mthli.type.event.ImageEvent;
import io.github.mthli.type.event.InsertEvent;
import io.github.mthli.type.event.FormatEvent;
import io.github.mthli.type.event.ItalicEvent;
import io.github.mthli.type.event.QuoteEvent;
import io.github.mthli.type.event.StrikethroughEvent;
import io.github.mthli.type.event.UnderlineEvent;
import io.github.mthli.type.util.RxBus;
import io.github.mthli.type.widget.model.Type;
import io.github.mthli.type.widget.model.TypeBlock;
import io.github.mthli.type.widget.text.KnifeText;
import rx.functions.Action1;
/*
 * Copyright 2016 Matthew Lee
 *
 * 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 io.github.mthli.type.widget.holder;




public class TypeBlockHolder extends RecyclerView.ViewHolder {
    private View quote;
    private View bullet;
    private KnifeText content;