@Singleton
@Component(
        modules = {
                AppModule.class,
                NetworkModule.class
        }
)
public interface AppInjectionComponent {

    void inject(@NonNull MainActivity activity);
    void inject(@NonNull DetailsActivity activity);
    void inject(@NonNull SearchActivity activity);
    void inject(@NonNull VideoPlayerActivity activity);

    void inject(@NonNull BrowseFragPresenter presenter);
    void inject(@NonNull VideoPlayerFragPresenter presenter);
    void inject(@NonNull PlaybackOverlayFragPresenter presenter);
    void inject(@NonNull PlaybackOverlayFragment presenter);
    void inject(@NonNull SearchFragPresenter presenter);
    void inject(@NonNull DetailsFragPresenter presenter);

    void inject(@NonNull OkHttpGlideModule module);

    void inject(@NonNull RecommendationContentProvider contentProvider);
    void inject(@NonNull RecommendationService service);

    final class Initializer {
        private Initializer() {
            /* No instances. */
        }

        public static AppInjectionComponent init(TVNLApplication application) {
            return DaggerAppInjectionComponent
                    .builder()
                    .appModule(new AppModule(application))
                    .networkModule(new NetworkModule())
                    .build();
        }
    }

} public class EventBus extends Bus {
    private final Handler mainThread = new Handler(Looper.getMainLooper());

    public EventBus() {
    }

    public void postOnMain(final Object event) {
        if(Looper.myLooper() == Looper.getMainLooper()) {
            super.post(event);
        } else {
            this.mainThread.post(new Runnable() {
                public void run() {
                    EventBus.this.post(event);
                }
            });
        }

    }
} public class UpdateBackgroundEvent {

    private Drawable drawable;
    private String url;
    @DrawableRes
    private Integer res = -1;

    public UpdateBackgroundEvent() {
        drawable = null;
        url = null;
    }

    public UpdateBackgroundEvent(Drawable drawable) {
        this.drawable = drawable;
    }

    public UpdateBackgroundEvent(String url) {
        this.url = url;
    }

    public UpdateBackgroundEvent(@DrawableRes Integer res) {
        this.res = res;
    }

    public Drawable getDrawable() {
        return drawable;
    }

    public String getUrl() {
        return url;
    }

    public Integer getResource() {
        return res;
    }

    public Boolean isDrawable() {
        return drawable != null;
    }

    public Boolean isUrl() {
        return url != null;
    }

    public Boolean isResource() {
        return res != -1;
    }

} public class Broadcast implements Parcelable {

    private String id;
    private Boolean rerun;
    @SerializedName("starts_at")
    private Long startsAt;
    @SerializedName("ends_at")
    private Long endsAt;
    private Long duration;
    private Episode episode;

    public String getId() {
        return id;
    }

    public Boolean getRerun() {
        return rerun;
    }

    public Date getStartsAt() {
        return new Date(startsAt * 1000);
    }

    public Date getEndsAt() {
        return new Date(endsAt * 1000);
    }

    public Long getDuration() {
        return duration;
    }

    public Episode getEpisode() {
        return episode;
    }

    public OverviewGridItem<Broadcast> toOverviewGridItem() {
        return new OverviewGridItem<>(this, episode.getName(), episode.getBroadcasters().toString(), episode.getImage());
    }

    protected Broadcast(Parcel in) {
        id = in.readString();
        byte rerunVal = in.readByte();
        rerun = rerunVal == 0x02 ? null : rerunVal != 0x00;
        startsAt = in.readByte() == 0x00 ? null : in.readLong();
        endsAt = in.readByte() == 0x00 ? null : in.readLong();
        duration = in.readByte() == 0x00 ? null : in.readLong();
        episode = (Episode) in.readValue(Episode.class.getClassLoader());
    }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(id);
        if (rerun == null) {
            dest.writeByte((byte) (0x02));
        } else {
            dest.writeByte((byte) (rerun ? 0x01 : 0x00));
        }
        if (startsAt == null) {
            dest.writeByte((byte) (0x00));
        } else {
            dest.writeByte((byte) (0x01));
            dest.writeLong(startsAt);
        }
        if (endsAt == null) {
            dest.writeByte((byte) (0x00));
        } else {
            dest.writeByte((byte) (0x01));
            dest.writeLong(endsAt);
        }
        if (duration == null) {
            dest.writeByte((byte) (0x00));
        } else {
            dest.writeByte((byte) (0x01));
            dest.writeLong(duration);
        }
        dest.writeValue(episode);
    }

    @SuppressWarnings("unused")
    public static final Parcelable.Creator<Broadcast> CREATOR = new Parcelable.Creator<Broadcast>() {
        @Override
        public Broadcast createFromParcel(Parcel in) {
            return new Broadcast(in);
        }

        @Override
        public Broadcast[] newArray(int size) {
            return new Broadcast[size];
        }
    };
} public class Channel {

    private String id;
    private String title;
    private String subtitle;
    private int drawable;
    private int progress;
    private String image;
    private int color;
    private Boolean isRadio;

    public Channel(String id, String title, String subtitle, String image, int progress, @DrawableRes int drawable, @ColorRes int color) {
        this(id, title, subtitle, image, progress, drawable, color, false);
    }

    public Channel(String id, String title, String subtitle, String image, int progress, @DrawableRes int drawable, @ColorRes int color, boolean isRadio) {
        this.id = id;
        this.title = title;
        this.subtitle = subtitle;
        this.drawable = drawable;
        this.progress = progress;
        this.image = image;
        this.color = color;
        this.isRadio = isRadio;
    }

    public String getId() {
        return id;
    }

    @ColorRes
    public int getColor() {
        return color;
    }

    public String getTitle() {
        return title;
    }

    public String getSubtitle() {
        return subtitle;
    }

    @DrawableRes
    public int getDrawable() {
        return drawable;
    }

    public String getImage() {
        return image;
    }

    public int getProgress() {
        return progress;
    }

    public Boolean isRadio() {
        return isRadio;
    }

} public class EncryptedStreamData {

    private String data;
    private String iv;
    private Boolean tt888;

    public String getData() {
        return data;
    }

    public String getIv() {
        return iv;
    }

    public String getUrl() {
        return SecurityUtil.decrypt(KeyProvider.data(), getIv(), getData());
    }

    public Boolean getTt888() {
        return tt888;
    }

} public class Episode implements Parcelable {

    @SerializedName("whatson_id")
    private String whatsOnId;
    private String mid;
    @SerializedName("nebo_id")
    private String neboId;
    private String name;
    private String description;
    private List<String> broadcasters;
    private List<String> genres;
    private List<Still> stills;
    private String image;
    private Long duration;
    private Boolean revoked;
    private List<String> video;
    private Integer views;
    private List<String> advisories;
    private Boolean active;
    @SerializedName("broadcasted_at")
    private Long broadcastedAt;
    private Restrictions restrictions;
    private Series series;

    public String getWhatsOnId() {
        return whatsOnId;
    }

    public String getId() {
        return mid;
    }

    public String getNeboId() {
        return neboId;
    }

    public String getName() {
        if(name == null || name.isEmpty()) {
            return getSeries().getName();
        }
        return name;
    }

    public String getDescription() {
        return description;
    }

    public List<String> getBroadcasters() {
        return broadcasters;
    }

    public List<String> getGenres() {
        return genres;
    }

    public List<Still> getStills() {
        return stills;
    }

    public String getImage() {
        if(image == null && stills != null && stills.size() > 0) {
            return stills.get(0).getUrl();
        }
        return image;
    }

    public Long getDuration() {
        return duration;
    }

    public Boolean getRevoked() {
        return revoked;
    }

    public List<String> getVideo() {
        return video;
    }

    public Integer getViews() {
        return views;
    }

    public List<String> getAdvisories() {
        return advisories;
    }

    public Boolean getActive() {
        return active;
    }

    public Date getBroadcastedAt() {
        return new Date(broadcastedAt * 1000);
    }

    public String getBroadcastedAtAsStr() {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("E d MMM • H:mm", Locale.getDefault());
        return simpleDateFormat.format(getBroadcastedAt());
    }

    public Restrictions getRestrictions() {
        return restrictions;
    }

    public Series getSeries() {
        return series;
    }

    public void setSeries(Series series) {
        this.series = series;
    }

    public OverviewGridItem<Episode> toOverviewGridItem() {
        return new OverviewGridItem<>(this, getName(), TextUtils.join(", ", getBroadcasters()), getImage());
    }


    protected Episode(Parcel in) {
        whatsOnId = in.readString();
        mid = in.readString();
        neboId = in.readString();
        name = in.readString();
        description = in.readString();
        if (in.readByte() == 0x01) {
            broadcasters = new ArrayList<String>();
            in.readList(broadcasters, String.class.getClassLoader());
        } else {
            broadcasters = null;
        }
        if (in.readByte() == 0x01) {
            genres = new ArrayList<String>();
            in.readList(genres, String.class.getClassLoader());
        } else {
            genres = null;
        }
        if (in.readByte() == 0x01) {
            stills = new ArrayList<Still>();
            in.readList(stills, Still.class.getClassLoader());
        } else {
            stills = null;
        }
        image = in.readString();
        duration = in.readByte() == 0x00 ? null : in.readLong();
        byte revokedVal = in.readByte();
        revoked = revokedVal == 0x02 ? null : revokedVal != 0x00;
        if (in.readByte() == 0x01) {
            video = new ArrayList<String>();
            in.readList(video, String.class.getClassLoader());
        } else {
            video = null;
        }
        views = in.readByte() == 0x00 ? null : in.readInt();
        if (in.readByte() == 0x01) {
            advisories = new ArrayList<String>();
            in.readList(advisories, String.class.getClassLoader());
        } else {
            advisories = null;
        }
        byte activeVal = in.readByte();
        active = activeVal == 0x02 ? null : activeVal != 0x00;
        broadcastedAt = in.readByte() == 0x00 ? null : in.readLong();
        restrictions = (Restrictions) in.readValue(Restrictions.class.getClassLoader());
        series = (Series) in.readValue(Series.class.getClassLoader());
    }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(whatsOnId);
        dest.writeString(mid);
        dest.writeString(neboId);
        dest.writeString(name);
        dest.writeString(description);
        if (broadcasters == null) {
            dest.writeByte((byte) (0x00));
        } else {
            dest.writeByte((byte) (0x01));
            dest.writeList(broadcasters);
        }
        if (genres == null) {
            dest.writeByte((byte) (0x00));
        } else {
            dest.writeByte((byte) (0x01));
            dest.writeList(genres);
        }
        if (stills == null) {
            dest.writeByte((byte) (0x00));
        } else {
            dest.writeByte((byte) (0x01));
            dest.writeList(stills);
        }
        dest.writeString(image);
        if (duration == null) {
            dest.writeByte((byte) (0x00));
        } else {
            dest.writeByte((byte) (0x01));
            dest.writeLong(duration);
        }
        if (revoked == null) {
            dest.writeByte((byte) (0x02));
        } else {
            dest.writeByte((byte) (revoked ? 0x01 : 0x00));
        }
        if (video == null) {
            dest.writeByte((byte) (0x00));
        } else {
            dest.writeByte((byte) (0x01));
            dest.writeList(video);
        }
        if (views == null) {
            dest.writeByte((byte) (0x00));
        } else {
            dest.writeByte((byte) (0x01));
            dest.writeInt(views);
        }
        if (advisories == null) {
            dest.writeByte((byte) (0x00));
        } else {
            dest.writeByte((byte) (0x01));
            dest.writeList(advisories);
        }
        if (active == null) {
            dest.writeByte((byte) (0x02));
        } else {
            dest.writeByte((byte) (active ? 0x01 : 0x00));
        }
        if (broadcastedAt == null) {
            dest.writeByte((byte) (0x00));
        } else {
            dest.writeByte((byte) (0x01));
            dest.writeLong(broadcastedAt);
        }
        dest.writeValue(restrictions);
        dest.writeValue(series);
    }

    @SuppressWarnings("unused")
    public static final Parcelable.Creator<Episode> CREATOR = new Parcelable.Creator<Episode>() {
        @Override
        public Episode createFromParcel(Parcel in) {
            return new Episode(in);
        }

        @Override
        public Episode[] newArray(int size) {
            return new Episode[size];
        }
    };
} public class Extension {
    public static final String SMOOTH_STREAMING = "Manifest";
    public static final String HLS = "m3u8";
    public static final String HDS = "f4m";
    // DASH appears to exist, since the Android application uses it for the livestreams, but the API (UGApiService.getLiveDataByChannel) says it doesn't. We don't have to care, since we can use Smooth Streaming, thus it's here for documentation purposes.
    public static final String DASH = "mpd";
} public class MainChannelsGuide {

    private String date;
    private List<Broadcast> ned1;
    private List<Broadcast> ned2;
    private List<Broadcast> ned3;

    public String getDate() {
        return date;
    }

    public List<Broadcast> getNPO1() {
        return ned1;
    }

    public List<Broadcast> getNPO2() {
        return ned2;
    }

    public List<Broadcast> getNPO3() {
        return ned3;
    }

} public class OdiData {

    private Integer errorcode;
    private String family;
    private String path;
    private String protocol;
    private String server;
    private Integer wait;
    private String url;

    public Integer getErrorcode() {
        return errorcode;
    }

    public String getFamily() {
        return family;
    }

    public String getPath() {
        return path;
    }

    public String getProtocol() {
        return protocol;
    }

    public String getServer() {
        return server;
    }

    public Integer getWait() {
        return wait;
    }

    public String getUrl() {
        return url;
    }

} public class OverviewGridItem<T> implements Parcelable {

    private T object;
    private String title;
    private String subtitle;
    private String image;

    public OverviewGridItem(T object, String title, String subtitle, String image) {
        this.object = object;
        this.title = title;
        this.subtitle = subtitle;
        this.image = image;
    }

    public OverviewGridItem(T object) {
        this.object = object;
    }

    public T getObject() {
        return object;
    }

    public String getTitle() {
        return title;
    }

    public String getSubtitle() {
        return subtitle;
    }

    public String getImage() {
        return image;
    }

    public void setSubtitle(String subtitle) {
        this.subtitle = subtitle;
    }

    protected OverviewGridItem(Parcel in) {
        Class type = (Class) in.readSerializable();
        object = (T) in.readValue(type.getClassLoader());
        title = in.readString();
        subtitle = in.readString();
        image = in.readString();
    }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeSerializable(object.getClass());
        dest.writeValue(object);
        dest.writeString(title);
        dest.writeString(subtitle);
        dest.writeString(image);
    }

    @SuppressWarnings("unused")
    public static final Parcelable.Creator<OverviewGridItem> CREATOR = new Parcelable.Creator<OverviewGridItem>() {
        @Override
        public OverviewGridItem createFromParcel(Parcel in) {
            return new OverviewGridItem(in);
        }

        @Override
        public OverviewGridItem[] newArray(int size) {
            return new OverviewGridItem[size];
        }
    };
} public class Promo implements Parcelable {

    private String mid;
    private String name;
    @SerializedName("episode_mid")
    private String episodeMid;
    @SerializedName("online_at")
    private Long onlineAt;
    @SerializedName("offline_at")
    private Long offlineAt;
    private List<VideoLocation> locations;
    private Episode episode;

    public String getId() {
        return mid;
    }

    public String getName() {
        return name;
    }

    public String getEpisodeId() {
        return episodeMid;
    }

    public Long getOnlineAt() {
        return onlineAt;
    }

    public Long getOfflineAt() {
        return offlineAt;
    }

    public List<VideoLocation> getLocations() {
        return locations;
    }

    public Episode getEpisode() {
        return episode;
    }

    public OverviewGridItem<Promo> toOverviewGridItem() {
        return new OverviewGridItem<>(this, name, episode.getBroadcasters().toString(), episode.getImage());
    }


    protected Promo(Parcel in) {
        mid = in.readString();
        name = in.readString();
        episodeMid = in.readString();
        onlineAt = in.readByte() == 0x00 ? null : in.readLong();
        offlineAt = in.readByte() == 0x00 ? null : in.readLong();
        if (in.readByte() == 0x01) {
            locations = new ArrayList<>();
            in.readList(locations, VideoLocation.class.getClassLoader());
        } else {
            locations = null;
        }
        episode = (Episode) in.readValue(Episode.class.getClassLoader());
    }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mid);
        dest.writeString(name);
        dest.writeString(episodeMid);
        if (onlineAt == null) {
            dest.writeByte((byte) (0x00));
        } else {
            dest.writeByte((byte) (0x01));
            dest.writeLong(onlineAt);
        }
        if (offlineAt == null) {
            dest.writeByte((byte) (0x00));
        } else {
            dest.writeByte((byte) (0x01));
            dest.writeLong(offlineAt);
        }
        if (locations == null) {
            dest.writeByte((byte) (0x00));
        } else {
            dest.writeByte((byte) (0x01));
            dest.writeList(locations);
        }
        dest.writeValue(episode);
    }

    @SuppressWarnings("unused")
    public static final Parcelable.Creator<Promo> CREATOR = new Parcelable.Creator<Promo>() {
        @Override
        public Promo createFromParcel(Parcel in) {
            return new Promo(in);
        }

        @Override
        public Promo[] newArray(int size) {
            return new Promo[size];
        }
    };
} public class RadioboxBroadcast {

    private Long id;
    @SerializedName("startdatetime")
    private Date startTime;
    @SerializedName("stopdatetime")
    private Date stopTime;
    private String name;
    private String subname;
    private String description;
    @SerializedName("PRID")
    private String prId;
    private RadioboxImage image;

    public Long getId() {
        return id;
    }

    public Date getStartTime() {
        return startTime;
    }

    public Date getStopTime() {
        return stopTime;
    }

    public String getName() {
        return name;
    }

    public String getSubname() {
        return subname;
    }

    public String getDescription() {
        return description;
    }

    public String getPrId() {
        return prId;
    }

    public RadioboxImage getImage() {
        return image;
    }

} public class RadioboxChannel {

    private Long id;
    private String name;
    private List<RadioboxVideostream> videostream;

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public List<RadioboxVideostream> getVideostream() {
        return videostream;
    }
} public class RadioboxSearch<T> {

    @SerializedName("page-index")
    private Integer pageIndex;
    @SerializedName("max-results")
    private Integer maxResults;
    @SerializedName("total-pages")
    private Integer totalPages;
    @SerializedName("total-results")
    private Integer totalResults;
    private List<T> results;

    public Integer getPageIndex() {
        return pageIndex;
    }

    public Integer getMaxResults() {
        return maxResults;
    }

    public Integer getTotalPages() {
        return totalPages;
    }

    public Integer getTotalResults() {
        return totalResults;
    }

    public List<T> getResults() {
        return results;
    }

} public class RadioboxVideostream {

    private Long id;
    private String url;
    private String name;

    public Long getId() {
        return id;
    }

    public String getUrl() {
        return url;
    }

    public String getName() {
        return name;
    }

} public class Series implements Parcelable {

    private String mid;
    private String name;
    private String image;
    @SerializedName("nebo_id")
    private String neboId;
    private List<String> genres;
    private List<String> broadcasters;
    private Long episodeCount;
    private List<Episode> episodes;
    private Long code;
    @SerializedName("starts_with")
    private String startsWith;

    public String getId() {
        return mid;
    }

    public String getName() {
        return name;
    }

    public String getNeboId() {
        return neboId;
    }

    public List<String> getGenres() {
        return genres;
    }

    public Long getEpisodeCount() {
        return episodeCount;
    }

    public List<Episode> getEpisodes() {
        return episodes;
    }

    public Long getCode() {
        return code;
    }

    public String getStartsWith() {
        return startsWith;
    }

    public List<String> getBroadcasters() {
        return broadcasters;
    }

    public String getImage() {
        return image;
    }

    public OverviewGridItem<Series> toOverviewGridItem() {
        return new OverviewGridItem<>(this, name, TextUtils.join(", ", broadcasters), getImage());
    }
    
    public Series getWithoutEpisodes() {
        return new Series(this);
    }

    private Series(Series series) {
        this.mid = series.mid;
        this.name = series.name;
        this.image = series.image;
        this.neboId = series.neboId;
        this.genres = series.genres;
        this.broadcasters = series.broadcasters;
        this.episodeCount = series.episodeCount;
        this.code = series.code;
        this.startsWith = series.startsWith;
    }

    protected Series(Parcel in) {
        mid = in.readString();
        name = in.readString();
        image = in.readString();
        neboId = in.readString();
        if (in.readByte() == 0x01) {
            genres = new ArrayList<>();
            in.readList(genres, String.class.getClassLoader());
        } else {
            genres = null;
        }
        if (in.readByte() == 0x01) {
            broadcasters = new ArrayList<>();
            in.readList(broadcasters, String.class.getClassLoader());
        } else {
            broadcasters = null;
        }
        episodeCount = in.readByte() == 0x00 ? null : in.readLong();
        if (in.readByte() == 0x01) {
            episodes = new ArrayList<>();
            in.readList(episodes, Episode.class.getClassLoader());
        } else {
            episodes = null;
        }
        code = in.readByte() == 0x00 ? null : in.readLong();
        startsWith = in.readString();
    }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mid);
        dest.writeString(name);
        dest.writeString(image);
        dest.writeString(neboId);
        if (genres == null) {
            dest.writeByte((byte) (0x00));
        } else {
            dest.writeByte((byte) (0x01));
            dest.writeList(genres);
        }
        if (broadcasters == null) {
            dest.writeByte((byte) (0x00));
        } else {
            dest.writeByte((byte) (0x01));
            dest.writeList(broadcasters);
        }
        if (episodeCount == null) {
            dest.writeByte((byte) (0x00));
        } else {
            dest.writeByte((byte) (0x01));
            dest.writeLong(episodeCount);
        }
        if (episodes == null) {
            dest.writeByte((byte) (0x00));
        } else {
            dest.writeByte((byte) (0x01));
            dest.writeList(episodes);
        }
        if (code == null) {
            dest.writeByte((byte) (0x00));
        } else {
            dest.writeByte((byte) (0x01));
            dest.writeLong(code);
        }
        dest.writeString(startsWith);
    }

    @SuppressWarnings("unused")
    public static final Parcelable.Creator<Series> CREATOR = new Parcelable.Creator<Series>() {
        @Override
        public Series createFromParcel(Parcel in) {
            return new Series(in);
        }

        @Override
        public Series[] newArray(int size) {
            return new Series[size];
        }
    };
} public class StreamInfo implements Parcelable {

    private String id;
    private String videoLocation;
    private String title;
    private String subtitle;
    private String image;
    private Boolean isLive;
    private Long startPosition = 0L;

    public StreamInfo(String id, String videoLocation, String title, String subtitle, String image) {
        this(id, videoLocation, title, subtitle, image, false);
    }

    public StreamInfo(String id, String videoLocation, String title, String subtitle, String image, Long startPosition) {
        this(id, videoLocation, title, subtitle, image, false);
        this.startPosition = startPosition;
    }

    public StreamInfo(String id, String videoLocation, String title, String subtitle, String image, Boolean isLive) {
        this.id = id;
        this.videoLocation = videoLocation;
        this.title = title;
        this.subtitle = subtitle;
        this.image = image;
        this.isLive = isLive;
    }

    public String getId() {
        return id;
    }

    public String getVideoLocation() {
        return videoLocation;
    }

    public String getTitle() {
        return title;
    }

    public String getSubtitle() {
        return subtitle;
    }

    public String getImage() {
        return image;
    }

    public Boolean isLive() {
        return isLive;
    }

    public Long getStartPosition() {
        return startPosition;
    }

    protected StreamInfo(Parcel in) {
        id = in.readString();
        videoLocation = in.readString();
        title = in.readString();
        subtitle = in.readString();
        image = in.readString();
        isLive = in.readInt() == 1;
        startPosition = in.readLong();
    }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(id);
        dest.writeString(videoLocation);
        dest.writeString(title);
        dest.writeString(subtitle);
        dest.writeString(image);
        dest.writeInt(isLive ? 1 : 0);
        dest.writeLong(startPosition);
    }

    @SuppressWarnings("unused")
    public static final Parcelable.Creator<StreamInfo> CREATOR = new Parcelable.Creator<StreamInfo>() {
        @Override
        public StreamInfo createFromParcel(Parcel in) {
            return new StreamInfo(in);
        }

        @Override
        public StreamInfo[] newArray(int size) {
            return new StreamInfo[size];
        }
    };
} public class ThemeChannelsGuide {

    private String date;
    @SerializedName("cult")
    private List<Broadcast> cult;
    @SerializedName("hilv")
    private List<Broadcast> best;
    @SerializedName("holl")
    private List<Broadcast> doc;
    @SerializedName("humo")
    private List<Broadcast> humor;
    @SerializedName("nosj")
    private List<Broadcast> news;
    @SerializedName("opvo")
    private List<Broadcast> zapp;
    @SerializedName("po24")
    private List<Broadcast> pol;
    @SerializedName("_101_")
    private List<Broadcast> _101_;

    public String getDate() {
        return date;
    }

    public List<Broadcast> getCult() {
        return cult;
    }

    public List<Broadcast> getBest() {
        return best;
    }

    public List<Broadcast> getDoc() {
        return doc;
    }

    public List<Broadcast> getHumor() {
        return humor;
    }

    public List<Broadcast> getNews() {
        return news;
    }

    public List<Broadcast> getZapp() {
        return zapp;
    }

    public List<Broadcast> getPol() {
        return pol;
    }

    public List<Broadcast> get101() {
        return _101_;
    }

} public class Timeline implements Parcelable {

    private String mid;
    private String image;
    private String kind;
    private String label;
    private String subtitle;
    private String title;
    @SerializedName("title_suffix")
    private String titleSuffix;
    private String description;
    @SerializedName("unpublish_at")
    private Long unpublishAt;

    public String getId() {
        return mid;
    }

    public String getImage() {
        return image;
    }

    public String getLabel() {
        if(kind.equals("segment")) {
            return "kort";
        } else if(kind.equals("program") && label.equals("most_watched")) {
            return "populair";
        } else if(kind.equals("program") && label.equals("tip")) {
            return "kijktip"; //
        } else if(kind.equals("promo")) {
            return "preview"; // -> play directly
        } else if(kind.equals("tv") && label.equals("tip")) {
            return "kijk live"; // -> play directly
        } else if(kind.equals("radio") && label.equals("tip")) {
            return "luister live"; // -> play directly
        }
        return "kijktip";
    }

    public boolean isLive() {
        return kind.equals("tv") || kind.equals("radio");
    }

    public String getSubtitle() {
        return subtitle;
    }

    public String getTitle() {
        return title;
    }

    public String getTitleSuffix() {
        return titleSuffix;
    }

    public String getDescription() {
        return description;
    }

    public String getKind() {
        return kind;
    }

    public Long getUnpublishAt() {
        return unpublishAt;
    }

    public OverviewGridItem<Timeline> toOverviewGridItem() {
        return new OverviewGridItem<>(this, title, getLabel(), image);
    }


    protected Timeline(Parcel in) {
        mid = in.readString();
        image = in.readString();
        kind = in.readString();
        label = in.readString();
        subtitle = in.readString();
        title = in.readString();
        titleSuffix = in.readString();
        description = in.readString();
        unpublishAt = in.readByte() == 0x00 ? null : in.readLong();
    }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mid);
        dest.writeString(image);
        dest.writeString(kind);
        dest.writeString(label);
        dest.writeString(subtitle);
        dest.writeString(title);
        dest.writeString(titleSuffix);
        dest.writeString(description);
        if (unpublishAt == null) {
            dest.writeByte((byte) (0x00));
        } else {
            dest.writeByte((byte) (0x01));
            dest.writeLong(unpublishAt);
        }
    }

    @SuppressWarnings("unused")
    public static final Parcelable.Creator<Timeline> CREATOR = new Parcelable.Creator<Timeline>() {
        @Override
        public Timeline createFromParcel(Parcel in) {
            return new Timeline(in);
        }

        @Override
        public Timeline[] newArray(int size) {
            return new Timeline[size];
        }
    };
} public class VideoFragment implements Parcelable {

    private String mid;
    private String name;
    private String description;
    @SerializedName("starts_at")
    private Long startsAt;
    @SerializedName("ends_at")
    private Long endsAt;
    private Long duration;
    private Episode episode;
    private List<Still> stills;

    public String getId() {
        return mid;
    }

    public String getName() {
        return name;
    }

    public String getDescription() {
        return description;
    }

    public Long getStartsAt() {
        return startsAt;
    }

    public Long getEndsAt() {
        return endsAt;
    }

    public Long getDuration() {
        return duration;
    }

    public Episode getEpisode() {
        return episode;
    }

    public List<Still> getStills() {
        return stills;
    }

    public OverviewGridItem<VideoFragment> toOverviewGridItem() {
        String subtitle = String.format(Locale.getDefault(), "%s • Uit: %s", DurationUtil.convert(duration), episode.getSeries().getName());

        if(stills == null)
            return new OverviewGridItem<>(this, name, subtitle, null);
        return new OverviewGridItem<>(this, name, subtitle, stills.get(0).getUrl());
    }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(this.mid);
        dest.writeString(this.name);
        dest.writeString(this.description);
        dest.writeValue(this.startsAt);
        dest.writeValue(this.endsAt);
        dest.writeValue(this.duration);
        dest.writeParcelable(this.episode, 0);
        dest.writeTypedList(stills);
    }

    public VideoFragment() {
    }

    protected VideoFragment(Parcel in) {
        this.mid = in.readString();
        this.name = in.readString();
        this.description = in.readString();
        this.startsAt = (Long) in.readValue(Long.class.getClassLoader());
        this.endsAt = (Long) in.readValue(Long.class.getClassLoader());
        this.duration = (Long) in.readValue(Long.class.getClassLoader());
        this.episode = in.readParcelable(Episode.class.getClassLoader());
        this.stills = in.createTypedArrayList(Still.CREATOR);
    }

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

        public VideoFragment[] newArray(int size) {
            return new VideoFragment[size];
        }
    };

} public class RxSntpClient {

    public static Single<Long> getCurrentTime(final String host, final int timeout) {
        return Single.create(new Single.OnSubscribe<Long>() {
            @Override
            public void call(SingleSubscriber<? super Long> subscriber) {
                try {
                    SntpClient client = new SntpClient();
                    if (client.requestTime(host, timeout)) {
                        long now = client.getNtpTime() + SystemClock.elapsedRealtime() - client.getNtpTimeReference();
                        subscriber.onSuccess(now);
                    }
                } catch (IOException e) {
                    subscriber.onError(e);
                }
            }
        });
    }

} public interface LivestreamsApiService {

    @GET
    Single<String> getRealUrl(@Url String url);

} public interface OdiApiService {

    @GET
    Single<OdiData> getData(@Url String url, @Query("extension") String extension);

    @GET
    Single<OdiData> getData(@Url String url);

} public interface RadioboxApiService {

    @GET("broadcast/search.json")
    Single<RadioboxSearch<RadioboxBroadcast>> broadcastSearch(@Query("q") String query, @Query("max-results") Integer limit, @Query("order") String order);

    @GET("track/search.json")
    Single<RadioboxSearch<RadioboxTrack>> trackSearch(@Query("q") String query, @Query("max-results") Integer limit, @Query("order") String order);

    @GET("videostream/search.json")
    Single<RadioboxSearch<RadioboxVideostream>> videostreamSearch(@Query("q") String query, @Query("max-results") Integer limit, @Query("order") String order);

    @GET("audiofragment/search.json")
    Single<RadioboxSearch<RadioboxAudiofragment>> audiofragmentSearch(@Query("q") String query, @Query("max-results") Integer limit, @Query("order") String order);

    @GET("data/radiobox2/nowonair/{channel}.json")
    Single<Object> getNowOnAirByChannel(@Path("channel") Integer channel);

    @GET("channel/rest/{channel}.json")
    Single<RadioboxChannel> getChannelData(@Path("channel") Integer channel);

    class QueryBuilder {

        private StringBuilder stringBuilder;

        public QueryBuilder() {
            stringBuilder = new StringBuilder();
            stringBuilder.append(' ');
        }

        public QueryBuilder equals(String key, Object value) {
            stringBuilder.append(key);
            stringBuilder.append(":");
            stringBuilder.append('\'');
            stringBuilder.append(value);
            stringBuilder.append('\'');
            stringBuilder.append(' ');
            return this;
        }

        public QueryBuilder greaterThan(String key, Object value) {
            stringBuilder.append(key);
            stringBuilder.append(">");
            stringBuilder.append('\'');
            stringBuilder.append(value);
            stringBuilder.append('\'');
            stringBuilder.append(' ');
            return this;
        }

        public QueryBuilder lowerThan(String key, Object value) {
            stringBuilder.append(key);
            stringBuilder.append("<");
            stringBuilder.append('\'');
            stringBuilder.append(value);
            stringBuilder.append('\'');
            stringBuilder.append(' ');
            return this;
        }

        public QueryBuilder subquery(QueryBuilder queryBuilder) {
            stringBuilder.append('(');
            stringBuilder.append(queryBuilder.build());
            stringBuilder.append(')');
            stringBuilder.append(' ');
            return this;
        }

        public QueryBuilder and() {
            stringBuilder.append("AND");
            stringBuilder.append(' ');
            return this;
        }

        public QueryBuilder or() {
            stringBuilder.append("OR");
            stringBuilder.append(' ');
            return this;
        }

        public String build() {
            return stringBuilder.toString();
        }

        public static class Prebuilts {

            public static String getCurrentDataByChannel(Object channel) {
                return new QueryBuilder().equals("channel.id", channel).and().subquery(new QueryBuilder().greaterThan("stopdatetime", "NOW").and().lowerThan("startdatetime", "NOW")).build();
            }

            public static String getCurrentDataForMainChannels() {
                return new QueryBuilder().greaterThan("channel.id", 0).and().lowerThan("channel.id", 8).and().subquery(new QueryBuilder().greaterThan("stopdatetime", "NOW").and().lowerThan("startdatetime", "NOW")).build();
            }

            public static String getNextDataByChannel(Object channel) {
                return new QueryBuilder().equals("channel.id", channel).and().subquery(new QueryBuilder().greaterThan("stopdatetime", "NOW").and().lowerThan("startdatetime", "NOW").or().greaterThan("startdatetime", "NOW")).build();
            }

            public static String getUnprotectedDataByChannel(Object channel) {
                return new QueryBuilder().equals("channel.id", channel).and().equals("protected", 0).build();
            }

            public static String getAvailableDataByChannel(Object channel) {
                return new QueryBuilder().equals("channel.id", channel).and().equals("available", 1).build();
            }

        }

    }

} public interface UGApiService {

    class PubOption {
        public static final String DOWNLOAD = "h264_std";
        public static final String ADAPTIVE = "adaptive";
    }

    @GET("tips.json")
    Single<List<Recommendation>> getRecommendations();

    @GET("timeline.json")
    Single<List<Timeline>> getTimeline();

    @GET("broadcasts/recent.json")
    Single<List<Broadcast>> getLatestBroadcasts();

    @GET("broadcasts/{date}.json")
    Single<List<Broadcast>> getBroadcastsByDate(@Path("date") String date);

    @GET("episodes/popular.json")
    Single<List<Episode>> getPopularEpisodes();

    @GET("episodes/trending.json")
    Single<List<Episode>> getTrendingEpisodes();

    @GET("episodes/{mid}.json")
    Single<Episode> getEpisodeById(@Path("mid") String id);

    @GET("episodes/genre/{genre}.json")
    Single<Episode> getEpisodeByGenre(@Path("genre") String genre);

    @GET("episodes/search/{keyword}.json")
    Single<List<Episode>> getEpisodesBySearch(@Path("keyword") String keyword);

    @GET("episodes/series/{mid}/latest.json")
    Single<Episode> getLatestEpisodesBySeriesId(@Path("mid") String id);

    @POST("episodes/{mid}/view.json")
    Single<Object> postEpisodeView(@Path("mid") String id, @Body String string);

    @GET("series.json")
    Single<List<Series>> getSeriesIndex();

    @GET("series/popular.json")
    Single<List<Series>> getPopularSeries();

    @GET("series/{seriesId}.json")
    Single<Series> getSeries(@Path("seriesId") String seriesId);

    @GET("series/suggest/{keyword}.json")
    Single<List<Series>> getSeriesBySuggestion(@Path("keyword") String keyword);

    @GET("fragments/trending.json")
    Single<List<VideoFragment>> getTrendingFragments();

    @GET("fragments/{mid}/view.json")
    Single<List<VideoFragment>> postFragmentView(@Path("mid") String id);

    @GET("promos.json")
    Single<List<Promo>> getPromos();

    @GET("odi_plus/android/{mid}.json")
    Single<EncryptedStreamData> getOdiData(@Path("mid") String id, @Query("pub_option") String option);

    @GET("guide/{date}.json")
    Single<MainChannelsGuide> getMainChannelsGuide(@Path("date") String date);

    @GET("guide/thema/{date}.json")
    Single<ThemeChannelsGuide> getThemeChannelsGuide(@Path("date") String date);

    @GET("live/android/{channelId}.json")
    Single<EncryptedStreamData> getLiveDataByChannel(@Path("channelId") String id, @Query("extension") String extension);

} public class DetailsActivity extends BaseActivity {

    public static Intent startActivity(Activity activity, OverviewGridItem item) {
        Intent intent = new Intent(activity, DetailsActivity.class);
        intent.putExtra(DetailsFragment.ITEM_ARG, item);
        activity.startActivity(intent);
        return intent;
    }

    public static Intent buildIntent(Context context, OverviewGridItem item, int notifId){
        Intent intent = new Intent(context, DetailsActivity.class);
        intent.putExtra(DetailsFragment.ITEM_ARG, item);
        intent.putExtra(DetailsFragment.NOTIFICATION_ARG, notifId);
        return intent;
    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState, R.layout.activity_details);
    }

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

    @Override
    protected void onPause() {
        super.onPause();
        eventBus.unregister(this);
    }

    @Subscribe
    @Override
    public void updateBackground(UpdateBackgroundEvent updateBackgroundEvent) {
        super.updateBackground(updateBackgroundEvent);
    }

    @Override
    protected void injectComponent(AppInjectionComponent component) {
        component.inject(this);
    }
} public class VideoPlayerActivity extends BaseActivity implements VideoPlayerFragment.Callback {

    private VideoPlayerFragment playerFragment;
    @SuppressLint("unused")
    private PlaybackOverlayFragment playbackOverlayFragment;

    public final static String EXTRA_STREAM_INFO = "stream_info";

    private StreamInfo streamInfo;

    public static void startActivity(Context context, StreamInfo info) {
        Intent i = new Intent(context, VideoPlayerActivity.class);
        i.putExtra(EXTRA_STREAM_INFO, info);
        context.startActivity(i);
    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState, R.layout.activity_videoplayer);
        streamInfo = getIntent().getParcelableExtra(EXTRA_STREAM_INFO);

        playerFragment = (VideoPlayerFragment) getSupportFragmentManager().findFragmentById(R.id.fragment);
        playbackOverlayFragment = (PlaybackOverlayFragment) getSupportFragmentManager().findFragmentById(R.id.playback_overlay_fragment);
    }

    @Override
    public void onVisibleBehindCanceled() {
        playerFragment.getPresenter().pause(playerFragment);
        super.onVisibleBehindCanceled();
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                finish();
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public StreamInfo getInfo() {
        return streamInfo;
    }

    @Override
    protected void injectComponent(AppInjectionComponent component) {
        component.inject(this);
    }

} public class ProgressDialogFragment extends DialogFragment {

    private static final String FRAGMENT_TAG = "ProgressDialogFragment";
    private static final String MESSAGE_ARG = "message";
    private static final String CANCELABLE_ARG = "cancelable";

    private DialogInterface.OnCancelListener onCancelListener;

    public ProgressDialogFragment() {

    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        final ProgressDialog dialog = new ProgressDialog(getActivity());

        dialog.setMessage(getArguments().getString(MESSAGE_ARG));
        dialog.setIndeterminate(true);
        dialog.setCancelable(getArguments().getBoolean(CANCELABLE_ARG, false));

        return dialog;
    }

    public static ProgressDialogFragment showLoadingProgress(FragmentManager fm, String message, Boolean cancelable) {
        dismissLoadingProgress(fm);
        ProgressDialogFragment fragment = new ProgressDialogFragment();
        Bundle args = new Bundle();
        args.putString(MESSAGE_ARG, message);
        args.putBoolean(CANCELABLE_ARG, cancelable);
        fragment.setArguments(args);
        fragment.show(fm, FRAGMENT_TAG);
        return fragment;
    }

    public static void dismissLoadingProgress(FragmentManager fm) {
        DialogFragment frag = (DialogFragment) fm.findFragmentByTag(FRAGMENT_TAG);
        if (frag != null) {
            frag.dismiss();
        }
    }

    public void setOnCancelListener(DialogInterface.OnCancelListener onCancelListener) {
        this.onCancelListener = onCancelListener;
    }

    @Override
    public void onCancel(DialogInterface dialog) {
        super.onCancel(dialog);
        if(onCancelListener != null)
            onCancelListener.onCancel(dialog);
    }
} @RequiresPresenter(BrowseFragPresenter.class)
public class OverviewBrowseFragment extends BrowseSupportFragment implements ViewWithPresenter<BrowseFragPresenter> {

    private static final int PERMISSIONS_REQUEST = 1;

    /* Lifecycle */

    @Override
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        if (bundle != null)
            presenterDelegate.onRestoreInstanceState(bundle.getBundle(PRESENTER_STATE_KEY));

        setOnSearchClickedListener(onSearchClickedListener);
        setOnItemViewSelectedListener(onItemSelectedListener);
        setOnItemViewClickedListener(onItemViewClickedListener);
    }

    @Override
    public void onSaveInstanceState(Bundle bundle) {
        super.onSaveInstanceState(bundle);
        bundle.putBundle(PRESENTER_STATE_KEY, presenterDelegate.onSaveInstanceState());
    }

    @Override
    public void onResume() {
        super.onResume();
        presenterDelegate.onResume(this);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        presenterDelegate.onDestroy(getActivity().isFinishing());
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
    }

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

        setupUIElements();
    }

    /* Presenter */

    private static final String PRESENTER_STATE_KEY = "presenter_state";
    private PresenterLifecycleDelegate<BrowseFragPresenter> presenterDelegate =
            new PresenterLifecycleDelegate<>(ReflectionPresenterFactory.<BrowseFragPresenter>fromViewClass(getClass()));

    @Override
    public PresenterFactory<BrowseFragPresenter> getPresenterFactory() {
        return presenterDelegate.getPresenterFactory();
    }

    @Override
    public void setPresenterFactory(PresenterFactory<BrowseFragPresenter> presenterFactory) {
        presenterDelegate.setPresenterFactory(presenterFactory);
    }

    @Override
    public BrowseFragPresenter getPresenter() {
        return presenterDelegate.getPresenter();
    }

    /* Listeners etc */

    private void setupUIElements() {
        setTitle(getString(R.string.app_name));
        setHeadersState(HEADERS_ENABLED);
        setHeadersTransitionOnBackEnabled(true);
        setBrandColor(getResources().getColor(R.color.primary));
        setSearchAffordanceColor(getResources().getColor(R.color.primary_dark));
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if(PERMISSIONS_REQUEST == requestCode) {
            boolean success = true;
            for (int result : grantResults) {
                if (result == PackageManager.PERMISSION_DENIED) {
                    success = false;
                    break;
                }
            }

            if (success)
                SearchActivity.startActivity(getActivity());
        }
    }

    private View.OnClickListener onSearchClickedListener = new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            if(ContextCompat.checkSelfPermission(getActivity(),
                    Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) {
                SearchActivity.startActivity(getActivity());
            } else {
                requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, PERMISSIONS_REQUEST);
            }
        }
    };

    private OnItemViewSelectedListener onItemSelectedListener = new OnItemViewSelectedListener() {
        @Override
        public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) {
            getPresenter().itemSelected(item);
        }
    };

    private OnItemViewClickedListener onItemViewClickedListener = new OnItemViewClickedListener() {
        @Override
        public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) {
            getPresenter().itemClicked(item);
        }
    };

    public void showErrorMessage(@StringRes int res) {
        Toast.makeText(getActivity(), res, Toast.LENGTH_SHORT).show();
    }

    public void showErrorMessage() {
        showErrorMessage(R.string.unknown_error);
    }

} public abstract class BasePresenter<V extends ViewWithPresenter> extends RxPresenter<V> {

    @Override
    protected void onCreate(Bundle savedState) {
        super.onCreate(savedState);
        injectComponent(TVNLApplication.get().appComponent());
    }

    protected Observable<V> viewFiltered() {
        return view().filter(new Func1<V, Boolean>() {
            @Override
            public Boolean call(V v) {
                return v != null;
            }
        });
    }

    protected abstract void injectComponent(AppInjectionComponent component);

} public class ChannelGridPresenter extends Presenter {

    private Context context;
    private final int cardWidth;
    private final int cardHeight;
    private final int defaultInfoBackgroundColor, progressBarColor;

    public ChannelGridPresenter(Context context) {
        this.context = context;
        this.defaultInfoBackgroundColor = context.getResources().getColor(R.color.grey);
        this.progressBarColor = context.getResources().getColor(R.color.white);
        this.cardWidth = context.getResources().getDimensionPixelSize(R.dimen.card_thumbnail_width);
        this.cardHeight = context.getResources().getDimensionPixelSize(R.dimen.card_thumbnail_height);
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent) {
        this.context = parent.getContext();

        ChannelCardView cardView = new ChannelCardView(parent.getContext()) {
            @Override
            public void setSelected(boolean selected) {
                HorizontalProgressDrawable progressDrawable = new HorizontalProgressDrawable(context);
                progressDrawable.setUseIntrinsicPadding(false);
                if (selected) {
                    setInfoAreaBackgroundColor(context.getResources().getColor(getColor()));
                    progressDrawable.setTint(progressBarColor);
                } else {
                    setInfoAreaBackgroundColor(defaultInfoBackgroundColor);
                    progressDrawable.setTint(context.getResources().getColor(getColor()));
                }
                getProgressBar().setProgressDrawable(progressDrawable);
                super.setSelected(selected);
            }
        };

        cardView.setInfoAreaBackgroundColor(defaultInfoBackgroundColor);
        cardView.setFocusable(true);
        cardView.setFocusableInTouchMode(true);

        return new ViewHolder(cardView);
    }

    @Override
    public void onBindViewHolder(ViewHolder viewHolder, Object item) {
        Channel channel = (Channel) item;
        final ChannelCardView cardView = (ChannelCardView) viewHolder.view;

        cardView.setMainImageDimensions(cardWidth, cardHeight);
        cardView.setTitleText(channel.getTitle());
        cardView.setContentTextView(channel.getSubtitle());
        cardView.setColor(channel.getColor());

        HorizontalProgressDrawable progressDrawable = new HorizontalProgressDrawable(context);
        progressDrawable.setUseIntrinsicPadding(false);
        if(!cardView.isSelected()) {
            progressDrawable.setTint(context.getResources().getColor(channel.getColor()));
        } else {
            progressDrawable.setTint(progressBarColor);
        }

        cardView.getProgressBar().setProgressDrawable(progressDrawable);
        cardView.getProgressBar().setMax(100);
        cardView.getProgressBar().setProgress(channel.getProgress());
        cardView.setOverlayImage(channel.getDrawable());

        final boolean fade = !TextUtils.equals(channel.getImage(), cardView.getCurrentImage());
        if(fade)
            cardView.setMainImage(null);

        cardView.setCurrentImage(channel.getImage());

        if (channel.getImage() != null) {
            Target target = new SimpleTarget<GlideBitmapDrawable>() {
                @Override
                public void onResourceReady(final GlideBitmapDrawable drawable, GlideAnimation glideAnimation) {
                    cardView.setMainImage(drawable, fade);
                }

                @Override
                public void onLoadFailed(Exception e, Drawable errorDrawable) {
                    super.onLoadFailed(e, errorDrawable);
                    cardView.setMainImage(ResourcesCompat.getDrawable(context.getResources(), R.drawable.placeholder, null), fade);
                }
            };

            Glide.with(context).load(channel.getImage())
                    .centerCrop()
                    .override(cardWidth, cardHeight)
                    .into(target);
            cardView.setTarget(target);
        } else {
            cardView.setMainImage(ResourcesCompat.getDrawable(context.getResources(), R.drawable.placeholder, null), fade);
        }
    }

    @Override
    public void onUnbindViewHolder(ViewHolder viewHolder) {
        ChannelCardView cardView = (ChannelCardView) viewHolder.view;
        // Remove references to images so that the garbage collector can free up memory
        cardView.setMainImage(null);
        if (cardView.getTarget() != null) {
            Glide.clear(cardView.getTarget());
            cardView.setTarget(null);
        }
    }

} public class OverviewGridPresenter extends Presenter {

	private Context context;
    protected final int cardWidth;
	protected final int cardHeight;
	private final int defaultInfoBackgroundColor;
	private final int defaultSelectedInfoBackgroundColor;

	public OverviewGridPresenter(Context context) {
        this.context = context;
		this.defaultSelectedInfoBackgroundColor = context.getResources().getColor(R.color.primary_dark);
        this.defaultInfoBackgroundColor = context.getResources().getColor(R.color.grey);
        this.cardWidth = context.getResources().getDimensionPixelSize(R.dimen.card_thumbnail_width);
        this.cardHeight = context.getResources().getDimensionPixelSize(R.dimen.card_thumbnail_height);
	}

	@Override
	public ViewHolder onCreateViewHolder(ViewGroup parent) {
        this.context = parent.getContext();

		CustomImageCardView cardView = new CustomImageCardView(parent.getContext()) {
			@Override
			public void setSelected(boolean selected) {
				if (getCustomSelectedSwatch() != null && selected) {
					setInfoAreaBackgroundColor(getCustomSelectedSwatch().getRgb());
				} else {
                    setInfoAreaBackgroundColor(selected ? defaultSelectedInfoBackgroundColor : defaultInfoBackgroundColor);
                }
				super.setSelected(selected);
			}
		};

		cardView.setInfoAreaBackgroundColor(defaultInfoBackgroundColor);
		cardView.setFocusable(true);
		cardView.setFocusableInTouchMode(true);

		return new ViewHolder(cardView);
	}

	@Override
	public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object object) {
        OverviewGridItem overview = (OverviewGridItem) object;

        final CustomImageCardView cardView = (CustomImageCardView) viewHolder.view;

        cardView.setTitleText(overview.getTitle());
        cardView.setContentText(overview.getSubtitle());
        cardView.getMainImageView().setAlpha(1f);
        cardView.getMainImageView().setPadding(0, 0, 0, 0);
        cardView.setMainImageDimensions(cardWidth, cardHeight);
        cardView.getMainImageView().setVisibility(View.GONE);
        cardView.setCustomSelectedSwatch(null);
        cardView.getTitleTextView().setMaxLines(1);
        cardView.getTitleTextView().setEllipsize(TextUtils.TruncateAt.END);
        cardView.getContentTextView().setVisibility(View.VISIBLE);

        if (overview.getImage() != null) {
            Target target = new SimpleTarget<GlideBitmapDrawable>() {
                @Override
                public void onResourceReady(final GlideBitmapDrawable drawable, GlideAnimation glideAnimation) {
                    Palette.from(drawable.getBitmap()).maximumColorCount(16).generate(new Palette.PaletteAsyncListener() {
                        @Override
                        public void onGenerated(Palette palette) {
                            Palette.Swatch swatch = palette.getDarkVibrantSwatch();
                            if(swatch == null) {
                                swatch = palette.getDarkMutedSwatch();
                            }
                            cardView.setCustomSelectedSwatch(swatch);

                            cardView.getMainImageView().setImageDrawable(drawable);
                            cardView.getMainImageView().setVisibility(View.GONE);
                            AnimUtil.fadeIn(cardView.getMainImageView());
                        }
                    });
                }

                @Override
                public void onLoadFailed(Exception e, Drawable errorDrawable) {
                    super.onLoadFailed(e, errorDrawable);
                    cardView.getMainImageView().setImageResource(R.drawable.placeholder);
                    cardView.getMainImageView().setAlpha(0.4f);
                    cardView.getMainImageView().setVisibility(View.GONE);
                    AnimUtil.fadeIn(cardView.getMainImageView());
                }
            };

            Glide.with(context).load(overview.getImage())
                    .centerCrop()
                    .override(cardWidth, cardHeight)
                    .into(target);
            cardView.setTarget(target);
        } else {
            cardView.getMainImageView().setImageResource(R.drawable.placeholder);
            cardView.getMainImageView().setAlpha(0.4f);
            cardView.getMainImageView().setVisibility(View.GONE);
            AnimUtil.fadeIn(cardView.getMainImageView());
        }
	}

	@Override
	public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
		CustomImageCardView cardView = (CustomImageCardView) viewHolder.view;
		// Remove references to images so that the garbage collector can free up memory
		cardView.setBadgeImage(null);
		cardView.setMainImage(null);
		if (cardView.getTarget() != null) {
            Glide.clear(cardView.getTarget());
			cardView.setTarget(null);
		}
	}

    public static List<OverviewGridItem> toOverviewGridItems(List<?> objects) {
        List<OverviewGridItem> returnData = new ArrayList<>();
        for (Object object : objects) {
            if(object instanceof Timeline) {
                if(((Timeline) object).getUnpublishAt() == null || ((Timeline) object).getUnpublishAt() > (System.currentTimeMillis() / 1000L))
                    returnData.add(((Timeline) object).toOverviewGridItem());
            } else if(object instanceof VideoFragment) {
                returnData.add(((VideoFragment) object).toOverviewGridItem());
            } else if(object instanceof Broadcast) {
                returnData.add(((Broadcast) object).toOverviewGridItem());
            } else if(object instanceof Series) {
                returnData.add(((Series) object).toOverviewGridItem());
            } else if(object instanceof Promo) {
                returnData.add(((Promo) object).toOverviewGridItem());
            } else if(object instanceof Recommendation) {
                returnData.add(((Recommendation) object).toOverviewGridItem());
            }
        }
        return returnData;
    }

}
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.os.Bundle;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ListRow;
import android.support.v17.leanback.widget.ListRowPresenter;
import android.text.TextUtils;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javax.inject.Inject;
import eu.se_bastiaan.tvnl.AppInjectionComponent;
import eu.se_bastiaan.tvnl.R;
import eu.se_bastiaan.tvnl.content.EventBus;
import eu.se_bastiaan.tvnl.event.UpdateBackgroundEvent;
import eu.se_bastiaan.tvnl.model.Broadcast;
import eu.se_bastiaan.tvnl.model.Channel;
import eu.se_bastiaan.tvnl.model.EncryptedStreamData;
import eu.se_bastiaan.tvnl.model.Episode;
import eu.se_bastiaan.tvnl.model.Extension;
import eu.se_bastiaan.tvnl.model.MainChannelsGuide;
import eu.se_bastiaan.tvnl.model.OdiData;
import eu.se_bastiaan.tvnl.model.OverviewGridItem;
import eu.se_bastiaan.tvnl.model.Promo;
import eu.se_bastiaan.tvnl.model.RadioboxBroadcast;
import eu.se_bastiaan.tvnl.model.RadioboxChannel;
import eu.se_bastiaan.tvnl.model.RadioboxSearch;
import eu.se_bastiaan.tvnl.model.RadioboxVideostream;
import eu.se_bastiaan.tvnl.model.Series;
import eu.se_bastiaan.tvnl.model.StreamInfo;
import eu.se_bastiaan.tvnl.model.ThemeChannelsGuide;
import eu.se_bastiaan.tvnl.model.Timeline;
import eu.se_bastiaan.tvnl.model.VideoFragment;
import eu.se_bastiaan.tvnl.network.RxSntpClient;
import eu.se_bastiaan.tvnl.network.service.LivestreamsApiService;
import eu.se_bastiaan.tvnl.network.service.OdiApiService;
import eu.se_bastiaan.tvnl.network.service.RadioboxApiService;
import eu.se_bastiaan.tvnl.network.service.UGApiService;
import eu.se_bastiaan.tvnl.ui.activity.DetailsActivity;
import eu.se_bastiaan.tvnl.ui.activity.VideoPlayerActivity;
import eu.se_bastiaan.tvnl.ui.dialog.ProgressDialogFragment;
import eu.se_bastiaan.tvnl.ui.fragment.OverviewBrowseFragment;
import eu.se_bastiaan.tvnl.ui.presenter.base.BasePresenter;
import eu.se_bastiaan.tvnl.ui.viewpresenter.ChannelGridPresenter;
import eu.se_bastiaan.tvnl.ui.viewpresenter.OverviewGridPresenter;
import rx.Observable;
import rx.Single;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action1;
import rx.functions.Action2;
import rx.functions.Func0;
import rx.functions.Func1;
import rx.functions.Func2;
import rx.schedulers.Schedulers;
import timber.log.Timber;

    public void itemSelected(Object item) {
        if (item instanceof OverviewGridItem) {
            eventBus.postOnMain(new UpdateBackgroundEvent(((OverviewGridItem) item).getImage()));
        } else if (item instanceof Channel) {
            String image = ((Channel) item).getImage();
            if(image != null) {
                eventBus.postOnMain(new UpdateBackgroundEvent(image));
            } else {
                eventBus.postOnMain(new UpdateBackgroundEvent());
            }
        }
    }

    public void itemClicked(Object item) {
        if(item instanceof OverviewGridItem) {
            final OverviewGridItem gridItem = (OverviewGridItem) item;
            Object object = gridItem.getObject();

            if(object instanceof Timeline && ((Timeline) object).isLive()) {
                final Timeline timeline = (Timeline) object;
                restartableFirst(CLICKED, new Func0<Observable<Channel>>() {
                    @Override
                    public Observable<Channel> call() {
                        return viewFiltered().first()
                                .observeOn(Schedulers.newThread())
                                .flatMap(new Func1<OverviewBrowseFragment, Observable<Channel>>() {
                                    @Override
                                    public Observable<Channel> call(OverviewBrowseFragment overviewBrowseFragment) {
                                        ProgressDialogFragment dialogFragment = ProgressDialogFragment.showLoadingProgress(overviewBrowseFragment.getChildFragmentManager(), overviewBrowseFragment.getString(R.string.loading_livestream), true);
                                        dialogFragment.setOnCancelListener(new DialogInterface.OnCancelListener() {
                                            @Override
                                            public void onCancel(DialogInterface dialogInterface) {
                                                stop(CLICKED);
                                            }
                                        });

                                        if (timeline.getKind().equals("radio")) {
                                            return radioboxApiService.getChannelData(Integer.parseInt(timeline.getId()))
                                                    .toObservable()
                                                    .map(new Func1<RadioboxChannel, Channel>() {
                                                        @Override
                                                        public Channel call(RadioboxChannel radioboxChannel) {
                                                            return new Channel(timeline.getId(), null, radioboxChannel.getName(), null, -1, -1, -1, true);
                                                        }
                                                    });
                                        } else if (timeline.getKind().equals("tv")) {
                                            String id = timeline.getId().replace("NPO", "");
                                            Channel channel = new Channel("ned" + id, null, "NPO " + id, null, -1, -1, -1, false);
                                            return Observable.just(channel);
                                        }

                                        return Observable.just(null);
                                    }
                                })
                                .subscribeOn(Schedulers.newThread())
                                .observeOn(AndroidSchedulers.mainThread());
                    }
                }, new Action2<OverviewBrowseFragment, Channel>() {
                    @Override
                    public void call(OverviewBrowseFragment overviewBrowseFragment, Channel channel) {
                        if(channel != null)
                            openLiveStream(channel);
                    }
                }, new Action2<OverviewBrowseFragment, Throwable>() {
                    @Override
                    public void call(OverviewBrowseFragment overviewBrowseFragment, Throwable throwable) {
                        ProgressDialogFragment.dismissLoadingProgress(overviewBrowseFragment.getChildFragmentManager());
                        overviewBrowseFragment.showErrorMessage(R.string.loading_livestream_error);
                    }
                });

                start(CLICKED);
                return;
            } else if(object instanceof Promo) {
                playVideo(object);
                return;
            }

            viewFiltered().first().observeOn(AndroidSchedulers.mainThread())
                    .doOnNext(new Action1<OverviewBrowseFragment>() {
                        @Override
                        public void call(OverviewBrowseFragment overviewBrowseFragment) {
                            DetailsActivity.startActivity(overviewBrowseFragment.getActivity(), gridItem);
                        }
                    })
                    .subscribe();
        } else if (item instanceof Channel) {
            Timber.d("Channel clicked! -> %s", ((Channel) item).getId());
            openLiveStream((Channel) item);
        }
    }

    private void openLiveStream(final Channel channel) {
        restartableFirst(LIVESTREAM, new Func0<Observable<String>>() {
            @Override
            public Observable<String> call() {
                final Observable<String> liveStreamUrlObservable;
                if (channel.isRadio()) {
                    liveStreamUrlObservable = radioboxApiService.videostreamSearch(RadioboxApiService.QueryBuilder.Prebuilts.getUnprotectedDataByChannel(channel.getId()), null, null)
                            .toObservable()
                            .observeOn(Schedulers.newThread())
                            .map(new Func1<RadioboxSearch<RadioboxVideostream>, String>() {
                                @Override
                                public String call(RadioboxSearch<RadioboxVideostream> radioboxSearch) {
                                    String url = "";
                                    for (RadioboxVideostream videostream : radioboxSearch.getResults()) {
                                        if (videostream.getName().equals("hasp-hls") || videostream.getName().endsWith(".m3u8")) {
                                            url = videostream.getUrl();
                                        } else if (videostream.getName().equals("hasp-sms")) {
                                            return videostream.getUrl();
                                        }
                                    }
                                    return url;
                                }
                            });
                } else {
                    liveStreamUrlObservable = ugApiService.getLiveDataByChannel(channel.getId(), Extension.HLS)
                            .toObservable()
                            .observeOn(Schedulers.newThread())
                            .map(new Func1<EncryptedStreamData, String>() {