public class ExcitedRetrofitFactory {

    private static final Object object = new Object();
    private static volatile ExcitedRetrofit retrofit;

    public static ExcitedRetrofit getRetrofit(Context context) {
        synchronized (object) {
            if (retrofit == null) {
                retrofit = new ExcitedRetrofit(context);
            }
            return retrofit;
        }
    }
}
 public class LoginActivity extends RxAppCompatActivity implements View.OnClickListener {

    private static final String PHONE = "phone";
    private static final String VERIFY_CODE = "code";
    public static final String ID = "id";
    public static final String TOKEN = "token";
    public static final String RANDOM_UUID = "uuid";

    private EditText phone;
    private EditText verifyCode;
    private Button verifyCodeBtn;
    private TextView tvCountryName;
    private TextView tvCountryNumbers;

    private UserApi userApi;

    private String phoneNumber;
    private String codeNumber;


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        MiuiStatusBarCompat.enableLightStatusBar(getWindow());
        setContentView(R.layout.login_activity);

        final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        toolbar.setNavigationOnClickListener(view -> finish());

        LinearLayout userProtocol = (LinearLayout) findViewById(R.id.user_protocol);
        phone = (EditText) findViewById(R.id.phone);
        verifyCode = (EditText) findViewById(R.id.code);
        verifyCodeBtn = (Button) findViewById(R.id.get_verify_code);
        Button submit = (Button) findViewById(R.id.submit);
        tvCountryName = (TextView) findViewById(R.id.country_name);
        tvCountryNumbers = (TextView) findViewById(R.id.country_numbers);

        userApi = ExcitedRetrofitFactory.getRetrofit(this).createApi(UserApi.class);

        verifyCodeBtn.setOnClickListener(this);
        findViewById(R.id.countries).setOnClickListener(this);
        submit.setOnClickListener(submitListener);
        userProtocol.setOnClickListener(v ->
                CustomTabActivityHelper.openCustomTab(LoginActivity.this, new CustomTabsIntent.Builder()
                                .setToolbarColor(ContextCompat.getColor(LoginActivity.this, R.color.colorPrimary))
                                .addDefaultShareMenuItem()
                                .build(),
                        Uri.parse("https://excited.aja.im/eula")));

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.get_verify_code:
                phoneNumber = phone.getText().toString();
                if (!tvCountryNumbers.getText().toString().equals("+86"))
                    phoneNumber = tvCountryNumbers.getText().toString().substring(1) + phoneNumber;
                if (TextUtils.isEmpty(phoneNumber)) {
                    ToastUtils.shorts(this, getString(R.string.phone_empty));
                } else {
                    HashMap<String, String> body = new HashMap<>();
                    body.put(PHONE, phoneNumber);
                    userApi.getCode(body)
                            .compose(bindToLifecycle())
                            .compose(RxMainThread.mainThread())
                            .subscribe(noticesResponse -> {
                                Notices notices = APIResponse.response(this, noticesResponse);
                                if (notices != null) {
                                    verifyCodeBtn.setEnabled(false);
                                    countDownTime();
                                    verifyCode.requestFocus();
                                } else {
                                    verifyCodeBtn.setEnabled(true);
                                }
                            }, throwable -> ToastUtils.shorts(this, throwable.getMessage()));
                }
                break;
            case R.id.countries:
                startActivityForResult(new Intent(this, SelectCountryActivity.class), 0);
                break;
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == 0) {
            if (data == null) return;
            String number = data.getStringExtra("number");
            String country = data.getStringExtra("country");

            tvCountryName.setText(country);
            tvCountryNumbers.setText(number);
        }
    }

    private void countDownTime() {
        final CountDownTimer timer = new CountDownTimer(TimeUnit.SECONDS.toMillis(30),
                TimeUnit.SECONDS.toMillis(1)) {
            @SuppressLint("DefaultLocale")
            @Override
            public void onTick(long millisUntilFinished) {
                verifyCodeBtn.setText(String.format("%d秒", millisUntilFinished / 1000));
            }

            @Override
            public void onFinish() {
                verifyCodeBtn.setEnabled(true);
                verifyCodeBtn.setText(getResources().getString(R.string.verify_code));
            }
        };
        timer.start();
    }

    private View.OnClickListener submitListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            phoneNumber = phone.getText().toString();
            if (!tvCountryNumbers.getText().toString().equals("+86"))
                phoneNumber = tvCountryNumbers.getText().toString().substring(1) + phoneNumber;
            codeNumber = verifyCode.getText().toString();
            if (TextUtils.isEmpty(codeNumber) && TextUtils.isEmpty(phoneNumber)) {
                ToastUtils.shorts(LoginActivity.this, getString(R.string.code_phone_empty));
            } else {
                HashMap<String, String> body = new HashMap<>();
                body.put(PHONE, phoneNumber);
                body.put(VERIFY_CODE, codeNumber);
                userApi.verifyUser(body)
                        .compose(bindToLifecycle())
                        .compose(RxMainThread.mainThread())
                        .subscribe(userResponse -> {
                            User user = APIResponse.response(LoginActivity.this, userResponse);
                            if (user != null) {
                                saveData(user.data);
                            }
                        }, throwable -> ToastUtils.shorts(LoginActivity.this, throwable.getMessage()));
            }
        }
    };

    private void saveData(Data data) {
        PreferenceManager.putString(this, TOKEN, data.attributes.token);
        PreferenceManager.putString(this, ID, data.id);
        toShareInteresting();
    }

    private void toShareInteresting() {
        Intent intent = new Intent(this, MainActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        startActivity(intent);
        finish();
    }

    @Override
    protected void onResume() {
        super.onResume();
        MobclickAgent.onResume(this);
    }

    @Override
    protected void onPause() {
        super.onPause();
        MobclickAgent.onPause(this);
    }
}
 public class LoginManager {

    public static boolean checkLogin(Context context) {
        String token = PreferenceManager.getValue(context, TOKEN);
        String id = PreferenceManager.getValue(context, ID);

        return !(token == null || id == null);
    }

}
 public final class Register {

    public static void registerItem(MultiTypeAdapter adapter, DataService dataService) {
        registerCommon(adapter, dataService);
    }

    public static void registerChannelItem(MultiTypeAdapter adapter, DataService dataService) {
        adapter.register(Header.class, new HeaderViewProvider());
        registerCommon(adapter, dataService);
    }

    private static void registerCommon(MultiTypeAdapter adapter, DataService dataService) {
        adapter.register(Music.class, new MusicViewProvider(dataService));
        adapter.register(Web.class, new WebViewProvider(dataService));
        adapter.register(Video.class, new VideoViewProvider(dataService));
        adapter.register(Picture.class, new PictureViewProvider(dataService));
        adapter.register(Footer.class, new FooterViewProvider());
        adapter.register(EmptyChannel.class, new ChannelEmptyViewProvider());
        adapter.register(SendingCard.class, new SendingCardHolder());
    }
}
 public interface ImageAPI {

    @Multipart
    @POST("image")
    Observable<Response<ImageResult>> post(@Part MultipartBody.Part body);
} public interface QueryAPI {

    @POST("graphql")
    Observable<NewData> getData(@Query("query") String query);

    @POST("graphql")
    Observable<Response<NewData>> loadData(@Query("query") String query);

    @POST("graphql")
    Observable<NewData> mutation(@Query("query") String query);

} public class DataObserver implements IData.IItems {

    private IItems iItems;

    public DataObserver(IItems iItems) {
        this.iItems = iItems;
    }

    @Override
    public void addData(MultiTypeAdapter adapter, List<Card> cards) {
        Items items = items();
        for (int index = 0; index < cards.size(); index++) {
            final Card card = cards.get(index);
            if (card.type.equals(MUSIC)) {
                items.add(new Music(card, index, adapter, cards, showPlus()));
            } else if (card.type.equals(VIDEO)) {
                items.add(new Video(card, index, adapter, cards, showPlus()));
            } else if (card.type.equals(WEB)) {
                items.add(new Web(card, index, adapter, cards, showPlus()));
            } else if (card.type.equals(COVER_WEB)) {
                items.add(new Web(card, index, adapter, cards, showPlus()));
            } else if (card.type.equals(PICTURE)) {
                items.add(new Picture(card, index, adapter, cards, showPlus()));
            }
        }
        if (cards.size() > 0) {
            items.add(new Footer());
        } else {
            items.add(new EmptyChannel());
        }
        adapter.notifyDataSetChanged();
    }

    @Override
    public Items items() {
        return iItems.items();
    }

    @Override
    public boolean showPlus() {
        return iItems.showPlus();
    }

} public interface IData {

    void addData(MultiTypeAdapter adapter, List<Card> cards);

    interface IItems extends IData {

        Items items();

        /**
         * 是否显示点赞
         * @return true 为显示， false 为不显示
         */
        boolean showPlus();

    }

} public class CardConverter {

    public static Card createCard(Cursor cursor) {
        Card card = new Card();
        card.id = cursor.getString(cursor.getColumnIndex(FavSQLiteHelper.Fav.ID));
        card.link = cursor.getString(cursor.getColumnIndex(FavSQLiteHelper.Fav.LINK));
        card.authorName = cursor.getString(cursor.getColumnIndex(FavSQLiteHelper.Fav.AUTHOR_NAME));
        card.cover = cursor.getString(cursor.getColumnIndex(FavSQLiteHelper.Fav.COVER));
        card.description = cursor.getString(cursor.getColumnIndex(FavSQLiteHelper.Fav.DESCRIPTION));
        card.distance = cursor.getString(cursor.getColumnIndex(FavSQLiteHelper.Fav.DISTANCE));
        card.headIcon = cursor.getString(cursor.getColumnIndex(FavSQLiteHelper.Fav.HEAD_ICON));
        card.headName = cursor.getString(cursor.getColumnIndex(FavSQLiteHelper.Fav.HEAD_NAME));
        card.ratio = cursor.getString(cursor.getColumnIndex(FavSQLiteHelper.Fav.RATIO));
        card.refined = cursor.getInt(cursor.getColumnIndex(FavSQLiteHelper.Fav.REFINED)) == 1;
        card.remains = cursor.getInt(cursor.getColumnIndex(FavSQLiteHelper.Fav.REMAINS));
        card.source = cursor.getString(cursor.getColumnIndex(FavSQLiteHelper.Fav.SOURCE));
        card.timestamp = cursor.getInt(cursor.getColumnIndex(FavSQLiteHelper.Fav.TIMESTAMP));
        card.title = cursor.getString(cursor.getColumnIndex(FavSQLiteHelper.Fav.TITLE));
        card.type = cursor.getString(cursor.getColumnIndex(FavSQLiteHelper.Fav.TYPE));
        card.sum = cursor.getInt(cursor.getColumnIndex(FavSQLiteHelper.Fav.SUM));
        return card;
    }

    public static ContentValues createContentValues(Card card, String userId, String token) {
        ContentValues contentValues = new ContentValues();
        contentValues.put(FavSQLiteHelper.Fav.ID, card.id);
        contentValues.put(FavSQLiteHelper.Fav.USER_ID, userId);
        contentValues.put(FavSQLiteHelper.Fav.TOKEN, token);
        contentValues.put(FavSQLiteHelper.Fav.LINK, card.link);
        contentValues.put(FavSQLiteHelper.Fav.AUTHOR_NAME, card.authorName);
        contentValues.put(FavSQLiteHelper.Fav.COVER, card.cover);
        contentValues.put(FavSQLiteHelper.Fav.DESCRIPTION, card.description);
        contentValues.put(FavSQLiteHelper.Fav.DISTANCE, card.distance);
        contentValues.put(FavSQLiteHelper.Fav.HEAD_ICON, card.headIcon);
        contentValues.put(FavSQLiteHelper.Fav.HEAD_NAME, card.headName);
        contentValues.put(FavSQLiteHelper.Fav.RATIO, card.ratio);
        contentValues.put(FavSQLiteHelper.Fav.REFINED, card.refined);
        contentValues.put(FavSQLiteHelper.Fav.REMAINS, card.remains);
        contentValues.put(FavSQLiteHelper.Fav.SOURCE, card.source);
        contentValues.put(FavSQLiteHelper.Fav.UUID, card.uuid);
        contentValues.put(FavSQLiteHelper.Fav.TIMESTAMP, card.timestamp);
        contentValues.put(FavSQLiteHelper.Fav.TYPE, card.type);
        contentValues.put(FavSQLiteHelper.Fav.TITLE, card.title);
        contentValues.put(FavSQLiteHelper.Fav.SUM, card.sum);
        return contentValues;
    }
} public class FavSQLiteHelper extends SQLiteOpenHelper {
    private static final int VERSION = 1;
    private static final String NAME = "excited.db";

    public FavSQLiteHelper(Context context) {
        super(context, NAME, null, VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(QuerySQL.createFavorite());
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }

    public static final class Fav {
        public static final String TABLE_NAME = "favorite";

        public static final String ID = "id";
        public static final String USER_ID = "user_id";
        public static final String TOKEN = "token";

        public static final String TITLE = "title";
        public static final String COVER = "cover";
        public static final String RATIO = "ratio";
        public static final String HEAD_ICON = "head_icon";
        public static final String HEAD_NAME = "head_name";
        public static final String SOURCE = "source";
        public static final String REMAINS = "remains";
        public static final String SUM = "sum";
        public static final String TIMESTAMP = "timestamp";
        public static final String REFINED = "refined";
        public static final String TYPE = "type";
        public static final String DESCRIPTION = "description";
        public static final String DISTANCE = "distance";
        public static final String UUID = "uuid";
        public static final String LINK = "link";
        public static final String AUTHOR_NAME = "author_name";
    }
} public interface Callback {

    void callback(Card card, String tag);

}
 public class AddCard {
    public Notices.Notice notice;
    public Card card;
} public class Card implements Parcelable {

    public String id;
    public String title;
    public String cover;
    public String ratio;
    @SerializedName("head_icon")
    public String headIcon;
    @SerializedName("head_name")
    public String headName;
    public String source;
    public int remains;
    public int sum;
    public int timestamp;
    public boolean refined;
    public String type;
    public String description;
    public String distance;
    public String uuid;
    public String link;
    @SerializedName("author_name")
    public String authorName;

    public Card() {
    }

    protected Card(Parcel in) {
        id = in.readString();
        title = in.readString();
        cover = in.readString();
        ratio = in.readString();
        headIcon = in.readString();
        headName = in.readString();
        source = in.readString();
        remains = in.readInt();
        sum = in.readInt();
        timestamp = in.readInt();
        type = in.readString();
        description = in.readString();
        distance = in.readString();
        uuid = in.readString();
        link = in.readString();
        authorName = in.readString();
    }

    public static final Creator<Card> CREATOR = new Creator<Card>() {
        @Override
        public Card createFromParcel(Parcel in) {
            return new Card(in);
        }

        @Override
        public Card[] newArray(int size) {
            return new Card[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeString(id);
        parcel.writeString(title);
        parcel.writeString(cover);
        parcel.writeString(ratio);
        parcel.writeString(headIcon);
        parcel.writeString(headName);
        parcel.writeString(source);
        parcel.writeInt(remains);
        parcel.writeInt(sum);
        parcel.writeInt(timestamp);
        parcel.writeString(type);
        parcel.writeString(description);
        parcel.writeString(distance);
        parcel.writeString(uuid);
        parcel.writeString(link);
        parcel.writeString(authorName);
    }
} public class ImageResult {
    public String url;
} public class MusicViewProvider extends ViewProvider<Music, MusicViewProvider.Holder> {

    private boolean isPlus = false;

    public MusicViewProvider(@NonNull DataService dataService) {
        super(dataService);
    }

    @NonNull @Override
    protected Holder onCreateViewHolder(@NonNull LayoutInflater inflater,
                                        @NonNull ViewGroup parent) {
        View view = inflater.inflate(R.layout.item_music, parent, false);
        return new Holder(view);
    }

    @Override
    protected void onBindViewHolder(@NonNull CardHolder cardHolder, @NonNull Music musicItem) {
        Holder holder = (Holder) cardHolder;
        cards = musicItem.cards;
        Card card = musicItem.card;
        multiTypeAdapter = musicItem.adapter;
        holder.index = musicItem.index;
        holder.card = card;
        Glide.with(holder.cover.getContext())
                .load(card.cover)
                .transform(new CircleTransform(holder.cover.getContext()))
                .diskCacheStrategy(DiskCacheStrategy.SOURCE)
                .into(holder.cover);
        holder.musicTitle.setText(card.title);
        holder.singer.setText(card.authorName);
        bindCommonData(card, holder, musicItem.showPlus);

        if (!isPlus) {
            holder.springView.setWidth((float) card.remains / (float) card.sum);
        } else {
            holder.springView.startSpring((float) card.remains / (float) card.sum);
            isPlus = false;
        }
    }

    @Override
    public void updateCard(Card card, int index) {
        isPlus = true;
        updateItemCard(card, index);
    }

    class Holder extends CardHolder implements View.OnClickListener {

        LinearLayout musicContent;
        ImageView cover;
        TextView musicTitle;
        TextView singer;
        int index;
        Card card;

        Holder(View itemView) {
            super(itemView);
            cover = (ImageView) itemView.findViewById(R.id.cover);
            musicTitle = (TextView) itemView.findViewById(R.id.music_title);
            singer = (TextView) itemView.findViewById(R.id.singer);
            plus.setOnClickListener(this);
            musicContent = (LinearLayout) itemView.findViewById(R.id.music_content);
            musicContent.setOnClickListener(this);
            more.setOnClickListener(this);

            more.setOnLongClickListener((view) -> more.performClick());
        }

        @Override
        public void onClick(View v)  {
            onActionClick(v, MusicViewProvider.this, card, index, "music");
        }
    }
}
 public class PictureViewProvider extends
        ViewProvider<Picture, PictureViewProvider.Holder> {

    private static final String RATIO_1_1 = "1:1";
    private static final String RATIO_16_9 = "16:9";
    private boolean isPlus = false;


    public PictureViewProvider(@NonNull DataService dataService) {
        super(dataService);
    }

    @NonNull @Override
    protected Holder onCreateViewHolder(@NonNull LayoutInflater inflater,
                                        @NonNull ViewGroup parent) {
        View view = inflater.inflate(R.layout.item_picture, parent, false);
        return new Holder(view);
    }

    @Override
    protected void onBindViewHolder(@NonNull CardHolder cardHolder, @NonNull Picture pictureItem) {
        Holder holder = (Holder) cardHolder;
        cards = pictureItem.cards;
        Card card = pictureItem.card;
        holder.card = card;
        multiTypeAdapter = pictureItem.adapter;
        holder.index = pictureItem.index;

        setImageDesc(holder, card);

        if (card.ratio.equals(RATIO_1_1)) {
            holder.picture.setOriginalSize(1, 1);
        } else if (card.ratio.equals(RATIO_16_9)){
            holder.picture.setOriginalSize(16, 9);
        }

        Glide.with(holder.picture.getContext())
                .load(card.cover)
                .diskCacheStrategy(DiskCacheStrategy.SOURCE)
                .into(holder.picture);
        bindCommonData(card, holder, pictureItem.showPlus);

        if (!isPlus) {
            holder.springView.setWidth((float) card.remains / (float) card.sum);
        } else {
            holder.springView.startSpring((float) card.remains / (float) card.sum);
            isPlus = false;
        }
    }

    @Override
    public void updateCard(Card card, int index) {
        isPlus = true;
        updateItemCard(card, index);
    }

    private void setImageDesc(Holder holder, Card card) {
        if (!TextUtils.isEmpty(card.description)) {
            holder.pictureDesc.setVisibility(View.VISIBLE);
            holder.pictureDesc.setText(card.description);
        } else {
            holder.pictureDesc.setVisibility(View.GONE);
        }
    }

    class Holder extends CardHolder implements View.OnClickListener {

        RatioImageView picture;
        TextView pictureDesc;
        int index;
        Card card;

        Holder(View itemView) {
            super(itemView);
            picture = (RatioImageView) itemView.findViewById(R.id.picture);
            pictureDesc = (TextView) itemView.findViewById(R.id.picture_desc);
            plus.setOnClickListener(this);
            picture.setOnClickListener(this);
            more.setOnClickListener(this);

            more.setOnLongClickListener((view) -> more.performClick());
        }

        @Override
        public void onClick(View v) {
            onActionClick(v, PictureViewProvider.this, card, index, "image");
        }
    }
}
 public class VideoViewProvider extends ViewProvider<Video, VideoViewProvider.Holder> {

    private boolean isPlus = false;

    public VideoViewProvider(@NonNull DataService dataService) {
        super(dataService);
    }

    @NonNull
    @Override
    protected Holder onCreateViewHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) {
        View view = inflater.inflate(R.layout.item_video, parent, false);
        return new Holder(view);
    }

    @Override
    protected void onBindViewHolder(@NonNull CardHolder cardHolder, @NonNull Video videoItem) {
        Holder holder = (Holder) cardHolder;
        multiTypeAdapter = videoItem.adapter;
        cards = videoItem.cards;
        Card card = videoItem.card;
        holder.card = card;
        holder.index = videoItem.index;

        holder.videoCover.setOriginalSize(16, 9);
        Glide.with(holder.videoCover.getContext())
                .load(card.cover)
                .diskCacheStrategy(DiskCacheStrategy.SOURCE)
                .into(holder.videoCover);
        holder.videoTitle.setText(card.title);
        bindCommonData(card, holder, videoItem.showPlus);

        if (!isPlus) {
            holder.springView.setWidth((float) card.remains / (float) card.sum);
        } else {
            holder.springView.startSpring((float) card.remains / (float) card.sum);
            isPlus = false;
        }
    }

    @Override
    public void updateCard(Card card, int index) {
        isPlus = true;
        updateItemCard(card, index);
    }

    class Holder extends CardHolder implements View.OnClickListener {

        FrameLayout movieContent;
        RatioImageView videoCover;
        ImageButton play;
        TextView videoTitle;
        int index;
        Card card;

        Holder(View itemView) {
            super(itemView);
            videoCover = (RatioImageView) itemView.findViewById(R.id.video_cover);
            play = (ImageButton) itemView.findViewById(R.id.play);
            videoTitle = (TextView) itemView.findViewById(R.id.video_title);
            movieContent = (FrameLayout) itemView.findViewById(R.id.movie_content);
            movieContent.setOnClickListener(this);
            plus.setOnClickListener(this);
            more.setOnClickListener(this);

            more.setOnLongClickListener((view) -> more.performClick());
        }

        @Override
        public void onClick(View v) {
            onActionClick(v, VideoViewProvider.this, card, index, "video");
        }
    }
}
 public class WebViewProvider extends ViewProvider<Web, WebViewProvider.Holder> {

    private boolean isPlus = false;

    public WebViewProvider(@NonNull DataService dataService) {
        super(dataService);
    }

    @NonNull
    @Override
    protected Holder onCreateViewHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) {
        View view = inflater.inflate(R.layout.item_web, parent, false);
        return new Holder(view);
    }

    @Override
    protected void onBindViewHolder(@NonNull CardHolder cardHolder, @NonNull Web webItem) {
        Holder holder = (Holder) cardHolder;
        cards = webItem.cards;
        Card card = webItem.card;
        holder.card = card;
        multiTypeAdapter = webItem.adapter;
        holder.index = webItem.index;

        showTitleAndDesc(holder, card);
        bindCommonData(card, holder, webItem.showPlus);

        if (!isPlus) {
            holder.springView.setWidth((float) card.remains / (float) card.sum);
        } else {
            holder.springView.startSpring(
                    (float) card.remains / (float) card.sum);
            isPlus = false;
        }

        holder.webCover.setOriginalSize(16, 9);
        if (card.cover != null) {
            if (TextUtils.isEmpty(card.cover)) {
                holder.webCover.setVisibility(View.GONE);
            } else {
                holder.webCover.setVisibility(View.VISIBLE);
                Glide.with(holder.webCover.getContext())
                        .load(card.cover)
                        .diskCacheStrategy(DiskCacheStrategy.SOURCE)
                        .into(holder.webCover);
            }
        } else {
            holder.webCover.setVisibility(View.GONE);
        }
    }

    @Override
    public void updateCard(Card card, int index) {
        isPlus = true;
        updateItemCard(card, index);
    }

    private void showTitleAndDesc(Holder holder, Card card) {
        if (card == null)
            return;

        if (TextUtils.isEmpty(card.title)) {
            holder.webTitle.setVisibility(View.GONE);
        } else {
            holder.webTitle.setVisibility(View.VISIBLE);
            holder.webTitle.setText(card.title);
        }

        if (TextUtils.isEmpty(card.description)) {
            holder.webDesc.setVisibility(View.GONE);
        } else {
            holder.webDesc.setVisibility(View.VISIBLE);
            holder.webDesc.setText(card.description);
        }
    }

    class Holder extends CardHolder implements View.OnClickListener {

        LinearLayout webContent;
        RatioImageView webCover;
        TextView webTitle;
        TextView webDesc;
        int index;
        Card card;

        public Holder(View itemView) {
            super(itemView);
            webCover = (RatioImageView) itemView.findViewById(R.id.web_cover);
            webTitle = (TextView) itemView.findViewById(R.id.web_title);
            webDesc = (TextView) itemView.findViewById(R.id.web_desc);
            webContent = (LinearLayout) itemView.findViewById(R.id.web_content);
            plus.setOnClickListener(this);
            webContent.setOnClickListener(this);
            more.setOnClickListener(this);

            more.setOnLongClickListener((view) -> more.performClick());
        }

        @Override
        public void onClick(View v) {
            onActionClick(v, WebViewProvider.this, card, index, "web");
        }
    }
}
 public class DataService {

    private Context context;

    public DataService(@NonNull Context context) {
        this.context = context;
    }

    public void inflateMenu(View actionView, String itemId) {
        PopupMenu actionMenu = new PopupMenu(context, actionView, Gravity.END);
        actionMenu.inflate(R.menu.menu_report);
        actionMenu.setOnMenuItemClickListener(item -> {
            if (item.getItemId() == R.id.report) {
                report(itemId);
            }
            return true;
        });
        actionMenu.show();
    }

    private void report(String itemId) {
        cardApi().report(itemId)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(response -> {
                    if (response.code() >= 200 && response.code() < 300) {
                        ToastUtils.longs(context, R.string.feedback_text);
                    }
                }, throwable -> ToastUtils.shorts(context, throwable.getMessage()));
    }

    //起飞
    public void fly(Card card, String tag) {
        if (tag.contains(PICTURE)) {
            flyToImageActivity(card);
        } else if (tag.contains(WEB)) {
            flyToBrowserActivity(card);
        }
    }

    private void flyToImageActivity(Card card) {
        final Intent pictureIntent = new Intent(context, ImageActivity.class);
        pictureIntent.setData(Uri.parse(card.cover));
        pictureIntent.putExtra("uuid", card.uuid);
        context.startActivity(pictureIntent);
    }

    private void flyToBrowserActivity(Card card) {
        final Intent browserIntent = new Intent(context, BrowserActivity.class);
        browserIntent.setData(Uri.parse(card.source));
        if (card.refined)
            browserIntent.putExtra(BrowserActivity.REFINED_URL, card.link);
        browserIntent.putExtra(BrowserActivity.TITLE, card.title);
        context.startActivity(browserIntent);
    }

    public void vote(ViewProvider provider, String itemId, final int index) {
        queryAPI().mutation(QueryField.vote(itemId))
                .filter(newData -> newData != null)
                .map(newData -> newData.voteCard.card)
                .compose(RxMainThread.mainThread())
                .subscribe(card -> updateCardItem(provider, card, index),
                        throwable -> {
                            if (throwable instanceof HttpException) {
                                HttpException exception = (HttpException) throwable;
                                if (exception.code() == 400) {// 不可以进行重复续命,不对用户做提示}
                                } else {
                                    ToastUtils.shorts(context, throwable.getMessage());
                                }
                                provider.updateCard(null, index);
                            }
                        });
    }

    private void updateCardItem(ViewProvider viewProvider, Card card, int index) {
        viewProvider.updateCard(card, index);
    }

    private CardApi cardApi() {
        return ExcitedRetrofitFactory.getRetrofit(context).createApi(CardApi.class);
    }

    private QueryAPI queryAPI() {
        return ExcitedRetrofitFactory.getRetrofit(context).createApi(QueryAPI.class);
    }

}
 public class Music extends CardItem {

    public Music(@NonNull Card card, int index, @NonNull MultiTypeAdapter adapter,
                 @NonNull List<Card> cards, boolean showPlus) {
        super(card, index, adapter, cards, showPlus);
    }

}
 public class Picture extends CardItem {

    public Picture(@NonNull Card card, int index, @NonNull MultiTypeAdapter adapter,
                   @NonNull List<Card> cards, boolean showPlus) {
        super(card, index, adapter, cards, showPlus);
    }

}
 public class SendingCard implements Parcelable {
    public static final int MUSIC = 0x2;
    public static final int IMAGE = 0x1;
    public static final int WEB = 0x0;
    /**
     * 0表示分享链接，1表示分享图片
     */
    public int type;
    public String title; // null if type = 1
    public String url;
    public String description;
    public String channelId;

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(this.type);
        dest.writeString(this.title);
        dest.writeString(this.url);
        dest.writeString(this.description);
        dest.writeString(this.channelId);
    }

    public SendingCard() {
    }

    protected SendingCard(Parcel in) {
        this.type = in.readInt();
        this.title = in.readString();
        this.url = in.readString();
        this.description = in.readString();
        this.channelId = in.readString();
    }

    public static final Parcelable.Creator<SendingCard> CREATOR = new Parcelable.Creator<SendingCard>() {
        @Override
        public SendingCard createFromParcel(Parcel source) {
            return new SendingCard(source);
        }

        @Override
        public SendingCard[] newArray(int size) {
            return new SendingCard[size];
        }
    };
} public class Video extends CardItem {

    public Video(@NonNull Card card, int index, @NonNull MultiTypeAdapter adapter,
                 @NonNull List<Card> cards, boolean showPlus) {
        super(card, index, adapter, cards, showPlus);
    }

}
 public class Web extends CardItem {

    public Web(@NonNull Card card, int index, @NonNull MultiTypeAdapter adapter,
               @NonNull List<Card> cards, boolean showPlus) {
        super(card, index, adapter, cards, showPlus);
    }

}
 public interface OnItemClickListener {

    void onItemClick(Card card, String tag);

}
 public class RxMainThread {

    public static <T> Observable.Transformer<T, T> mainThread() {
        return tObservable -> tObservable.subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread());
    }

} public class APIResponse {

    public static <T>T response(Context context, Response<T> response) {
        if (response.code() >= 200 && response.code() < 300) {
            return response.body();
        } else if (response.code() >= 400 && response.code() < 500) {
            networkError(context, response.errorBody());
        } else if (response.code() >= 500) {
            serverError(context);
        }
        return null;
    }

    private static void networkError(Context context, ResponseBody errorBody) {
        try {
            Notices notices = new Gson().fromJson(errorBody.string(), Notices.class);
            ToastUtils.shorts(context, notices.notice.text);
        } catch (IOException e) {
            ToastUtils.shorts(context, e.getMessage());
        }
    }

    private static void serverError(Context context) {
        ToastUtils.shorts(context, R.string.server_error);
    }
}
 public class BitmapUtils {
    private static final int MAX_SIZE = 1024;

    public static File compress(String filePath) throws IOException {
        Bitmap bitmap;
        bitmap = BitmapFactory.decodeFile(filePath);
        String outputPath = Environment.getExternalStorageDirectory().getPath()
                + "/Excited/";
        File folder = new File(outputPath);
        if (!folder.exists())
            folder.mkdir();
        File f = new File(outputPath + System.currentTimeMillis() + ".jpg");
        if (f.createNewFile()) {
            FileOutputStream fOut = null;
            try {
                fOut = new FileOutputStream(f);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
            if (fOut == null)
                return null;
            if (Math.max(bitmap.getWidth(), bitmap.getHeight()) > MAX_SIZE
                    && !filePath.endsWith(".gif")) {
                float zoom = 1f * MAX_SIZE / Math.max(bitmap.getWidth(), bitmap.getHeight());

                Matrix matrix = new Matrix();
                matrix.setScale(zoom, zoom);

                bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
            }
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fOut);

            fOut.flush();
            fOut.close();
            return f;
        }
        return null;
    }
} public class PreferenceManager {

    private static final String PREFS_NAME = "aja";

    private static SharedPreferences preferences(Context context) {
        return context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
    }

    private static SharedPreferences.Editor edit(Context context) {
        return preferences(context).edit();
    }

    public static void putString(Context context, String key, String value) {
        edit(context).putString(key, value).apply();
    }

    public static String getValue(Context context, String key) {
        return preferences(context).getString(key, null);
    }

    public static String getValue(Context context, String key, String defaultValue) {
        return preferences(context).getString(key, defaultValue);
    }
}
 public class SnackbarUtils {
    public static void shorts(View container, int resId) {
        shorts(container, resId, null);
    }

    public static void indefinite(View container, int resId) {
        shorts(container, resId, null);
    }

    public static void longs(View container, int resId) {
        shorts(container, resId, null);
    }

    /**
     * To show a SnackBar for a short period of time with a particular padding.
     *
     * @param container  The view to find a parent from.
     * @param resId  The resource id of the string resource to use. Can be formatted text.
     * @param rect The padding(without top) that view need to add, if null then won't add padding.
     */
    public static void shorts(View container, int resId, Rect rect) {
        Snackbar snackbar = Snackbar.make(container, resId, Snackbar.LENGTH_SHORT);
        if (rect != null) {
            snackbar.getView().setPadding(rect.left, 0, rect.right, rect.bottom);
        }
        snackbar.show();
    }

    public static void indefinite(View container, int resId, Rect rect) {
        Snackbar snackbar = Snackbar.make(container, resId, Snackbar.LENGTH_INDEFINITE);
        if (rect != null) {
            snackbar.getView().setPadding(rect.left, 0, rect.right, rect.bottom);
        }
        snackbar.show();
    }

    public static void longs(View container, int resId, Rect rect) {
        Snackbar snackbar = Snackbar.make(container, resId, Snackbar.LENGTH_SHORT);
        if (rect != null) {
            snackbar.getView().setPadding(rect.left, 0, rect.right, rect.bottom);
        }
        snackbar.show();
    }

    public static void shorts(View container, String message, Rect rect) {
        Snackbar snackbar = Snackbar.make(container, message, Snackbar.LENGTH_SHORT);
        if (rect != null) {
            snackbar.getView().setPadding(rect.left, 0, rect.right, rect.bottom);
        }
        snackbar.show();
    }

    public static void indefinite(View container, String message, Rect rect) {
        Snackbar snackbar = Snackbar.make(container, message, Snackbar.LENGTH_INDEFINITE);
        if (rect != null) {
            snackbar.getView().setPadding(rect.left, 0, rect.right, rect.bottom);
        }
        snackbar.show();
    }

    public static void longs(View container, String message, Rect rect) {
        Snackbar snackbar = Snackbar.make(container, message, Snackbar.LENGTH_SHORT);
        if (rect != null) {
            snackbar.getView().setPadding(rect.left, 0, rect.right, rect.bottom);
        }
        snackbar.show();
    }
} public class ToastUtils {

    public static void show(Context context, String message, int duration) {
        Toast.makeText(context, message, duration).show();
    }

    public static void show(Context context, int resId, int duration) {
        Toast.makeText(context, context.getString(resId), duration).show();
    }

    public static void shorts(Context context, int resId) {
        Toast.makeText(context, context.getString(resId), Toast.LENGTH_SHORT).show();
    }

    public static void shorts(Context context, String message) {
        Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
    }

    public static void longs(Context context, int resId) {
        Toast.makeText(context, context.getString(resId), Toast.LENGTH_LONG).show();
    }

    public static void longs(Context context, String message) {
        Toast.makeText(context, message, Toast.LENGTH_LONG).show();
    }
}
 public static final String ID = "id";
 public static final String TOKEN = "token";

import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Rect;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v4.content.ContextCompat;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.widget.Button;
import com.squareup.sqlbrite.BriteDatabase;
import com.squareup.sqlbrite.SqlBrite;
import java.io.File;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import me.drakeet.multitype.Items;
import me.drakeet.multitype.MultiTypeAdapter;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import ooo.oxo.excited.ExcitedRetrofitFactory;
import ooo.oxo.excited.LoginActivity;
import ooo.oxo.excited.LoginManager;
import ooo.oxo.excited.R;
import ooo.oxo.excited.Register;
import ooo.oxo.excited.api.ImageAPI;
import ooo.oxo.excited.api.QueryAPI;
import ooo.oxo.excited.api.QueryField;
import ooo.oxo.excited.data.DataObserver;
import ooo.oxo.excited.data.IData;
import ooo.oxo.excited.database.CardConverter;
import ooo.oxo.excited.database.FavSQLiteHelper;
import ooo.oxo.excited.database.QuerySQL;
import ooo.oxo.excited.fragment.callback.Callback;
import ooo.oxo.excited.model.AddCard;
import ooo.oxo.excited.model.Card;
import ooo.oxo.excited.model.ImageResult;
import ooo.oxo.excited.provider.MusicViewProvider;
import ooo.oxo.excited.provider.PictureViewProvider;
import ooo.oxo.excited.provider.VideoViewProvider;
import ooo.oxo.excited.provider.WebViewProvider;
import ooo.oxo.excited.provider.data.DataService;
import ooo.oxo.excited.provider.item.Music;
import ooo.oxo.excited.provider.item.Picture;
import ooo.oxo.excited.provider.item.SendingCard;
import ooo.oxo.excited.provider.item.Video;
import ooo.oxo.excited.provider.item.Web;
import ooo.oxo.excited.provider.listener.OnItemClickListener;
import ooo.oxo.excited.rx.RxMainThread;
import ooo.oxo.excited.utils.APIResponse;
import ooo.oxo.excited.utils.BitmapUtils;
import ooo.oxo.excited.utils.PreferenceManager;
import ooo.oxo.excited.utils.SnackbarUtils;
import ooo.oxo.excited.utils.ToastUtils;
import rx.Observable;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
import static ooo.oxo.excited.LoginActivity.ID;
import static ooo.oxo.excited.LoginActivity.TOKEN;
                } else {
                    appBarCallback.setExpanded(true, false);
                }
            }
        }
        if (isLogin && containerCallback != null)
            list.setPadding(0, list.getPaddingTop(),
                    0, containerCallback.getWindowInsetsRect().bottom);
        return convertView;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        if (isLogin) {
            if (getArguments() != null)
                sendingCard = getArguments().getParcelable("sending_card");
            if (sendingCard != null) {
                items.add(sendingCard);
                postCard(sendingCard);
            }
            if (adapter == null) {
                adapter = new MultiTypeAdapter(items);

                Register.registerItem(adapter, new DataService(context));
                list.setAdapter(adapter);

                setListener();
            }
        }
    }

    private void postCard(SendingCard sendingCard) {
        if (sendingCard == null)
            return;
        if (sendingCard.type == SendingCard.IMAGE) {
            ImageAPI imageAPI = ExcitedRetrofitFactory
                    .getRetrofit(getContext()).createApi(ImageAPI.class);
            File file = null;
            try {
                file = BitmapUtils.compress(sendingCard.url);
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (file == null) {
                handleError(getString(R.string.file_not_exits));
                return;
            }
            RequestBody requestFile =
                    RequestBody.create(MediaType.parse("image/*"), file);
            MultipartBody.Part body =
                    MultipartBody.Part.createFormData("file", file.getName(), requestFile);

            imageAPI.post(body)
                    .compose(bindToLifecycle())
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(response -> {
                        if (response.isSuccessful()) {
                            ImageResult imageResult = APIResponse
                                    .response(getContext(), response);
                            if (imageResult != null) {
                                sendingCard.url = imageResult.url;
                                createImageCard(QueryField.addImageCard(sendingCard.url,
                                        sendingCard.description, sendingCard.channelId));
                            }
                        } else {
                            handleError(getString(R.string.upload_failed));
                        }
                    }, throwable -> {
                        handleError(throwable.getMessage());
                    });
        } else {
            createWebCard(QueryField.addWebCard(sendingCard.url,
                    sendingCard.title, sendingCard.description, sendingCard.channelId));
        }
    }

    private void createWebCard(String query) {
        queryAPI.getData(query)
                .compose(bindToLifecycle())
                .compose(RxMainThread.mainThread())
                .map(newData -> newData.webCard)
                .subscribe(this::handleAddCard,
                        throwable -> handleError(throwable.getMessage()));

    }

    private void handleError(String errorMessage) {
        Snackbar snackbar = Snackbar.make(refreshLayout, errorMessage, Snackbar.LENGTH_LONG);
        if (containerCallback != null) {
            Rect rect = containerCallback.getWindowInsetsRect();
            snackbar.getView().setPadding(rect.left, 0, rect.right, rect.bottom);
        }
        snackbar.setAction(R.string.retry, (v -> postCard(sendingCard)));
        snackbar.setActionTextColor(ContextCompat.getColor(context, R.color.empty_text_color));
        snackbar.addCallback(new Snackbar.Callback() {
            @Override
            public void onDismissed(Snackbar transientBottomBar, int event) {
                super.onDismissed(transientBottomBar, event);
                if (event != DISMISS_EVENT_ACTION) {
                    getArguments().putParcelable("sending_card", null);
                    sendingCard = null;
                    items.remove(0);
                    adapter.notifyItemRemoved(0);
                    if (items.size() == 0) {
                        inflateEmptyView();
                    }
                }
            }
        });
        snackbar.show();
    }

    private void handleAddCard(AddCard addCard) {
        if (addCard.notice != null) {
            handleError(addCard.notice.text);
        } else if (items.size() > 0 && items.get(0) instanceof SendingCard) {
            items.remove(0);
            List<Card> cards = new ArrayList<>();