@Module
public class NetworkModule {

    @Provides
    @NonNull
    @Singleton
    public UGApiService provideUGApiService(Retrofit.Builder retrofit) {
        return retrofit.build().create(UGApiService.class);
    }

    @Provides
    @NonNull
    @Singleton
    public OdiApiService provideOdiApiService(Retrofit.Builder retrofit) {
        return retrofit.build().create(OdiApiService.class);
    }

    @Provides
    @NonNull
    @Singleton
    public RadioboxApiService provideRadioboxApiService(Retrofit.Builder retrofit) {
        return retrofit.baseUrl(Constants.API_URL_RADIOBOX).build().create(RadioboxApiService.class);
    }

    @Provides
    @NonNull
    @Singleton
    public LivestreamsApiService provideLivestreamsApiService(OkHttpClient.Builder httpClientBuilder, Retrofit.Builder retrofit) {
        httpClientBuilder.addInterceptor(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request();
                if (request.url().toString().contains("livestream")) {
                    HttpUrl url = request.url().newBuilder().addQueryParameter("type", "json").addQueryParameter("protection", "url").build();
                    request = request.newBuilder().url(url).build();
                }
                return chain.proceed(request);
            }
        });

        return retrofit.client(httpClientBuilder.build()).build().create(LivestreamsApiService.class);
    }

    @Provides
    @NonNull
    public OkHttpClient.Builder provideOkHttpClientBuilder() {
        OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();

        httpClientBuilder.addInterceptor(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request();

                Request.Builder requestBuilder = request.newBuilder();
                requestBuilder.addHeader("User-Agent", String.format(Locale.US, "NPOxUG/%s AndroidTV/%s %s/%s", BuildConfig.VERSION_NAME, Build.VERSION.RELEASE, Build.MANUFACTURER, Build.MODEL));

                return chain.proceed(requestBuilder.build());
            }
        });

        if (BuildConfig.DEBUG) {
            HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
            interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
            httpClientBuilder.addInterceptor(interceptor);
        }

        return httpClientBuilder;
    }

    @Provides
    @NonNull
    @Singleton
    public OkHttpClient provideOkHttpClient(OkHttpClient.Builder httpClientBuilder) {
        return httpClientBuilder.build();
    }

    @Provides
    @NonNull
    @Singleton
    public Retrofit.Builder provideRetrofitBuilder(OkHttpClient okHttpClient) {
        Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ssZZZ").create();

        return new Retrofit.Builder()
                .client(okHttpClient)
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create(gson))
                .validateEagerly(true)
                .baseUrl(Constants.API_URL);
    }

} public class OkHttpGlideModule implements GlideModule {
    @Inject
    OkHttpClient okHttpClient;

    @Override
    public void applyOptions(Context context, GlideBuilder builder) {
        // Do nothing.
    }

    @Override
    public void registerComponents(Context context, Glide glide) {
        TVNLApplication.get().appComponent().inject(this);
        glide.register(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory(okHttpClient));
    }
} public class RecommendationService extends IntentService {

    private static final int MAX_RECOMMENDATIONS = 3;

    @Inject
    UGApiService ugApiService;

    public RecommendationService() {
        super("RecommendationService");
    }

    @Override
    public void onCreate() {
        super.onCreate();
        TVNLApplication.get().appComponent().inject(this);
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        final RecommendationBuilder builder = new RecommendationBuilder()
                .setContext(getApplicationContext())
                .setSmallIcon(R.drawable.ic_recommendation);


        ugApiService.getRecommendations()
                .subscribeOn(Schedulers.newThread())
                .observeOn(Schedulers.newThread())
                .subscribe(new Action1<List<Recommendation>>() {
                    @Override
                    public void call(List<Recommendation> recommendations) {
                        try {
                            for(int i = 0; i < MAX_RECOMMENDATIONS; i++) {
                                Recommendation recommendation = recommendations.get(i);
                                builder.setBackgroundContentUri(RecommendationContentProvider.CONTENT_URI + URLEncoder.encode(recommendation.getImage(), "UTF-8"))
                                        .setId(i)
                                        .setPriority(i)
                                        .setTitle(recommendation.getName())
                                        .setDescription(recommendation.getDescription())
                                        .setImage(recommendation.getImage())
                                        .setIntent(buildPendingIntent(recommendation.toOverviewGridItem(), i))
                                        .build();
                            }

                        } catch (IOException e) {
                            Timber.e(e, "Unable to update recommendation");
                        }
                    }
                }, new Action1<Throwable>() {
                    @Override
                    public void call(Throwable throwable) {
                        Timber.e(throwable, "Unable to update recommendation");
                    }
                });
    }

    private PendingIntent buildPendingIntent(OverviewGridItem media, int notifId) {
        Intent detailIntent = DetailsActivity.buildIntent(this, media, notifId);
        return PendingIntent.getActivity(this, 0, detailIntent, PendingIntent.FLAG_UPDATE_CURRENT);
    }

} public class RecommendationContentProvider extends ContentProvider {

    public static String AUTHORITY = "eu.se_basitaan.tvnl.RecommendationContentProvider";
    public static String CONTENT_URI = "content://" + AUTHORITY + "/";

    @Inject
    OkHttpClient okHttpClient;

    @Override
    public boolean onCreate() {
        TVNLApplication.get().appComponent().inject(this);
        return true;
    }

    @Override
    public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode)
            throws FileNotFoundException {
        ParcelFileDescriptor[] pipe = null;

        String url = uri.getPath();

        try {
            String decodedUrl = URLDecoder.decode(url.replaceFirst("/", ""),
                    "UTF-8");
            pipe = ParcelFileDescriptor.createPipe();

            URL connectionUrl = new URL(decodedUrl);
            HttpURLConnection connection = (HttpURLConnection) connectionUrl.openConnection();

            new TransferThread(connection.getInputStream(),
                    new ParcelFileDescriptor.AutoCloseOutputStream(pipe[1]))
                    .start();
        } catch (IOException e) {
            Log.e(getClass().getSimpleName(), "Exception opening pipe", e);
            throw new FileNotFoundException("Could not open pipe for: "
                    + uri.toString());
        }

        return (pipe[0]);
    }

    @Override
    public Cursor query(@NonNull Uri uri, String[] projection, String selection,
            String[] selectionArgs, String sortOrder) {
        return null;
    }

    @Override
    public String getType(@NonNull Uri uri) {
        return "image/*";
    }

    @Override
    public Uri insert(@NonNull Uri uri, ContentValues values) {
        return null;
    }

    @Override
    public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }

    @Override
    public int update(@NonNull Uri uri, ContentValues values, String selection,
            String[] selectionArgs) {
        return 0;
    }

    static class TransferThread extends Thread {
        InputStream in;
        OutputStream out;

        TransferThread(InputStream in, OutputStream out) {
            this.in = in;
            this.out = out;
        }

        @Override
        public void run() {
            byte[] buf = new byte[8192];
            int len;

            try {
                while ((len = in.read(buf)) > 0) {
                    out.write(buf, 0, len);
                }

                in.close();
                out.flush();
                out.close();
            } catch (IOException e) {
                Log.e(getClass().getSimpleName(),
                        "Exception transferring file", e);
            }
        }
    }

} 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 MainActivity extends BaseActivity {

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

    @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 SearchActivity extends BaseActivity {

    public static Intent startActivity(Activity activity) {
        Intent intent = new Intent(activity, SearchActivity.class);
        activity.startActivity(intent);
        return intent;
    }

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

    @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
    public boolean onSearchRequested() {
        ((SearchFragment) getSupportFragmentManager().findFragmentById(R.id.search_fragment)).startRecognition();
        return true;
    }

    @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);
    }

} @RequiresPresenter(PlaybackOverlayFragPresenter.class)
public class PlaybackOverlayFragment extends PlaybackOverlaySupportFragment implements ViewWithPresenter<PlaybackOverlayFragPresenter> {

    StreamInfo streamInfo;

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

        TVNLApplication.get().appComponent().inject(this);

        setFadeCompleteListener(onFadeCompleteListener);
        setInputEventHandler(inputEventHandler);
        setOnItemViewSelectedListener(onItemViewSelectedListener);
        setBackgroundType(android.support.v17.leanback.app.PlaybackOverlayFragment.BG_LIGHT);
        setFadingEnabled(false);
    }

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

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        streamInfo = ((VideoPlayerFragment.Callback) getActivity()).getInfo();
    }

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

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

    public StreamInfo getStreamInfo() {
        return streamInfo;
    }

    /* Presenter */

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

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

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

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

    /* Listener section */
    PlaybackOverlaySupportFragment.OnFadeCompleteListener onFadeCompleteListener = new PlaybackOverlaySupportFragment.OnFadeCompleteListener() {
        @Override
        public void onFadeInComplete() {
            super.onFadeInComplete();
            getPresenter().onFadeInComplete();
        }

        @Override
        public void onFadeOutComplete() {
            super.onFadeOutComplete();
            getPresenter().onFadeOutComplete();
        }
    };

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

    PlaybackOverlaySupportFragment.InputEventHandler inputEventHandler = new PlaybackOverlaySupportFragment.InputEventHandler() {
        @Override
        public boolean handleInputEvent(@NonNull InputEvent event) {
            return getPresenter().handleInputEvent(event);
        }
    };

} public class BrowseFragPresenter extends BasePresenter<OverviewBrowseFragment> {

    private static final Integer TIMELINE = 0, FRAGMENTS = 1, MISSED = 2, LIVE = 3, RADIO = 4, LIVESTREAM = 5, REFRESH = 6, CLICKED = 7, PLAY = 8;

    @Inject
    Resources resources;
    @Inject
    EventBus eventBus;
    @Inject
    Context context;
    @Inject
    UGApiService ugApiService;
    @Inject
    OdiApiService odiApiService;
    @Inject
    RadioboxApiService radioboxApiService;
    @Inject
    LivestreamsApiService livestreamsApiService;

    ArrayObjectAdapter rowsAdapter;

    @Override
    protected void onCreate(Bundle savedState) {
        super.onCreate(savedState);

        rowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
        if (rowsAdapter.size() < 1) {
            rowsAdapter.add(new ListRow(new HeaderItem(context.getString(R.string.start)), new ArrayObjectAdapter()));
            rowsAdapter.add(new ListRow(new HeaderItem(context.getString(R.string.fragments)), new ArrayObjectAdapter()));
            rowsAdapter.add(new ListRow(new HeaderItem(context.getString(R.string.missed)), new ArrayObjectAdapter()));
            rowsAdapter.add(new ListRow(new HeaderItem(context.getString(R.string.live)), new ArrayObjectAdapter()));
            rowsAdapter.add(new ListRow(new HeaderItem(context.getString(R.string.radio)), new ArrayObjectAdapter()));
        }

        restartableLatestCache(TIMELINE, new Func0<Observable<List<OverviewGridItem>>>() {
            @Override
            public Observable<List<OverviewGridItem>> call() {
                return ugApiService.getTimeline()
                        .observeOn(Schedulers.newThread())
                        .zipWith(ugApiService.getTrendingFragments(), new Func2<List<Timeline>, List<VideoFragment>, List<OverviewGridItem>>() {
                            @Override
                            public List<OverviewGridItem> call(List<Timeline> timelines, List<VideoFragment> videoFragments) {
                                List<OverviewGridItem> overviewGridItemList = new ArrayList<>();
                                for (Timeline timeline : timelines) {
                                    if (timeline.getKind().equals("segment")) {
                                        VideoFragment fragment = null;
                                        for (VideoFragment videoFragment : videoFragments) {
                                            if (videoFragment.getId().equals(timeline.getId())) {
                                                fragment = videoFragment;
                                                break;
                                            }
                                        }

                                        if (fragment != null) {
                                            OverviewGridItem overviewGridItem = new OverviewGridItem<>(fragment, timeline.getTitle(), timeline.getLabel(), timeline.getImage());
                                            overviewGridItemList.add(overviewGridItem);
                                        }
                                    } else {
                                        overviewGridItemList.add(timeline.toOverviewGridItem());
                                    }
                                }
                                return overviewGridItemList;
                            }
                        })
                        .zipWith(ugApiService.getPromos(), new Func2<List<OverviewGridItem>, List<Promo>, List<OverviewGridItem>>() {
                            @Override
                            public List<OverviewGridItem> call(List<OverviewGridItem> overviewGridItems, List<Promo> promos) {
                                List<OverviewGridItem> overviewGridItemList = new ArrayList<>();
                                for (OverviewGridItem overviewGridItem : overviewGridItems) {
                                    if (overviewGridItem.getObject() instanceof Timeline && ((Timeline) overviewGridItem.getObject()).getKind().equals("promo")) {
                                        Timeline timeline = (Timeline) overviewGridItem.getObject();
                                        Promo promoItem = null;
                                        for (Promo promo : promos) {
                                            if (promo.getId().equals(timeline.getId())) {
                                                promoItem = promo;
                                                break;
                                            }
                                        }

                                        if (promoItem != null) {
                                            overviewGridItem = new OverviewGridItem<>(promoItem, timeline.getTitle(), timeline.getLabel(), timeline.getImage());
                                            overviewGridItemList.add(overviewGridItem);
                                        }
                                    } else {
                                        overviewGridItemList.add(overviewGridItem);
                                    }
                                }
                                return overviewGridItemList;
                            }
                        })
                        .zipWith(ugApiService.getTrendingEpisodes(), new Func2<List<OverviewGridItem>, List<Episode>, List<OverviewGridItem>>() {
                            @Override
                            public List<OverviewGridItem> call(List<OverviewGridItem> overviewGridItems, List<Episode> episodeList) {
                                List<OverviewGridItem> overviewGridItemList = new ArrayList<>();
                                for (OverviewGridItem overviewGridItem : overviewGridItems) {
                                    if (overviewGridItem.getObject() instanceof Timeline && ((Timeline) overviewGridItem.getObject()).getKind().equals("program")) {
                                        Timeline timeline = (Timeline) overviewGridItem.getObject();
                                        Episode episodeItem = null;
                                        for (Episode episode : episodeList) {
                                            if (episode.getId().equals(timeline.getId())) {
                                                episodeItem = episode;
                                                break;
                                            }
                                        }

                                        if (episodeItem != null) {
                                            overviewGridItem = new OverviewGridItem<>(episodeItem, timeline.getTitle(), timeline.getLabel(), timeline.getImage());
                                            overviewGridItemList.add(overviewGridItem);
                                        }
                                    } else {
                                        overviewGridItemList.add(overviewGridItem);
                                    }
                                }
                                return overviewGridItemList;
                            }
                        })
                        .toObservable()
                        .subscribeOn(Schedulers.newThread())
                        .observeOn(AndroidSchedulers.mainThread());
            }
        }, new Action2<OverviewBrowseFragment, List<OverviewGridItem>>() {
            @Override
            public void call(OverviewBrowseFragment overviewBrowseFragment, List<OverviewGridItem> timelineList) {
                OverviewGridPresenter presenter = new OverviewGridPresenter(context);
                ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(presenter);
                listRowAdapter.addAll(0, timelineList);
                HeaderItem header = new HeaderItem(overviewBrowseFragment.getString(R.string.start));

                rowsAdapter.replace(TIMELINE, new ListRow(header, listRowAdapter));
            }
        }, new Action2<OverviewBrowseFragment, Throwable>() {
            @Override
            public void call(OverviewBrowseFragment overviewBrowseFragment, Throwable throwable) {
                Timber.e(throwable, "Unable to update timeline items");
            }
        });

        restartableLatestCache(FRAGMENTS, new Func0<Observable<List<VideoFragment>>>() {
            @Override
            public Observable<List<VideoFragment>> call() {
                return ugApiService.getTrendingFragments()
                        .toObservable()
                        .subscribeOn(Schedulers.newThread())
                        .observeOn(AndroidSchedulers.mainThread());
            }
        }, new Action2<OverviewBrowseFragment, List<VideoFragment>>() {
            @Override
            public void call(OverviewBrowseFragment overviewBrowseFragment, List<VideoFragment> videoFragmentList) {
                OverviewGridPresenter presenter = new OverviewGridPresenter(context);
                ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(presenter);
                listRowAdapter.addAll(0, OverviewGridPresenter.toOverviewGridItems(videoFragmentList));
                HeaderItem header = new HeaderItem(overviewBrowseFragment.getString(R.string.fragments));

                rowsAdapter.replace(FRAGMENTS, new ListRow(header, listRowAdapter));
            }
        }, new Action2<OverviewBrowseFragment, Throwable>() {
            @Override
            public void call(OverviewBrowseFragment overviewBrowseFragment, Throwable throwable) {
                Timber.e(throwable, "Unable to update fragment items");
            }
        });

        restartableLatestCache(MISSED, new Func0<Observable<List<OverviewGridItem>>>() {
            @Override
            public Observable<List<OverviewGridItem>> call() {
                Single<List<Broadcast>> broadcasts = ugApiService.getLatestBroadcasts()
                        .observeOn(Schedulers.newThread());

                return ugApiService.getTrendingEpisodes()
                        .observeOn(Schedulers.newThread())
                        .zipWith(broadcasts, new Func2<List<Episode>, List<Broadcast>, List<OverviewGridItem>>() {
                            @Override
                            public List<OverviewGridItem> call(List<Episode> episodeList, List<Broadcast> broadcastsList) {
                                ArrayList<OverviewGridItem> returnData = new ArrayList<>();

                                for (Episode episode : episodeList) {
                                    OverviewGridItem gridItem = episode.toOverviewGridItem();
                                    gridItem.setSubtitle("meest bekeken");
                                    returnData.add(gridItem);
                                }

                                for (Broadcast broadcast : broadcastsList) {
                                    OverviewGridItem gridItem = broadcast.toOverviewGridItem();
                                    gridItem.setSubtitle("nieuw");
                                    returnData.add(gridItem);
                                }

                                Collections.shuffle(returnData);

                                return returnData;
                            }
                        })
                        .toObservable()
                        .subscribeOn(Schedulers.newThread())
                        .observeOn(AndroidSchedulers.mainThread());
            }
        }, new Action2<OverviewBrowseFragment, List<OverviewGridItem>>() {
            @Override
            public void call(OverviewBrowseFragment overviewBrowseFragment, List<OverviewGridItem> items) {
                OverviewGridPresenter presenter = new OverviewGridPresenter(context);
                ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(presenter);
                listRowAdapter.addAll(listRowAdapter.size(), items);
                HeaderItem header = new HeaderItem(overviewBrowseFragment.getString(R.string.missed));

                rowsAdapter.replace(MISSED, new ListRow(header, listRowAdapter));
            }
        }, new Action2<OverviewBrowseFragment, Throwable>() {
            @Override
            public void call(OverviewBrowseFragment overviewBrowseFragment, Throwable throwable) {
                Timber.e(throwable, "Unable to update missed items");
            }
        });



        restartableLatestCache(LIVE, new Func0<Observable<List<Channel>>>() {
            @Override
            public Observable<List<Channel>> call() {
                return RxSntpClient.getCurrentTime("pool.ntp.org", 3000)
                        .observeOn(Schedulers.newThread())
                        .onErrorReturn(new Func1<Throwable, Long>() {
                            @Override
                            public Long call(Throwable throwable) {
                                Timber.e(throwable, "Unable to get current timestamp, returning device time");
                                return System.currentTimeMillis();
                            }
                        }).flatMap(new Func1<Long, Single<List<Channel>>>() {
                            @Override
                            public Single<List<Channel>> call(final Long timestamp) {
                                Calendar cal = Calendar.getInstance();
                                cal.setTimeInMillis(timestamp);
                                cal.add(Calendar.HOUR, -5);
                                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
                                final String date = sdf.format(cal.getTime());

                                Single<ThemeChannelsGuide> themeChannels = ugApiService.getThemeChannelsGuide(date)
                                        .observeOn(Schedulers.newThread());

                                return ugApiService.getMainChannelsGuide(date)
                                        .observeOn(Schedulers.newThread())
                                        .zipWith(themeChannels, new Func2<MainChannelsGuide, ThemeChannelsGuide, List<Channel>>() {
                                            @Override
                                            public List<Channel> call(MainChannelsGuide mainChannelsGuide, ThemeChannelsGuide themeChannelsGuide) {
                                                ArrayList<Channel> returnData = new ArrayList<>();
                                                ArrayList<List<Broadcast>> channelList = new ArrayList<>();
                                                String[] channelIds = {"ned1", "ned2", "ned3", "journaal24", "politiek24", "best24", "cultura24", "hollanddoc24", "humor24", "101tv", "zappelin24"};
                                                String[] names = {"NPO 1", "NPO 2", "NPO 3", "NPO Nieuws", "NPO Politiek", "NPO Best", "NPO Cultura", "NPO Doc", "NPO Humor TV", "NPO 101", "NPO Zapp Xtra"};
                                                int[] images = {R.drawable.ic_npo1, R.drawable.ic_npo2, R.drawable.ic_npo3, R.drawable.ic_npo_nieuws, R.drawable.ic_npo_politiek, R.drawable.ic_npo_best, R.drawable.ic_npo_cultura, R.drawable.ic_npo_doc, R.drawable.ic_npo_humor_tv, R.drawable.ic_npo_101, R.drawable.ic_npo_zapp_xtra};
                                                int[] colors = {R.color.npo1, R.color.npo2, R.color.npo3, R.color.npo_nieuws, R.color.npo_pol, R.color.npo_best, R.color.npo_cult, R.color.npo_doc, R.color.npo_humor_tv, R.color.npo_101, R.color.npo_zapp_xtra};

                                                channelList.add(mainChannelsGuide.getNPO1());
                                                channelList.add(mainChannelsGuide.getNPO2());
                                                channelList.add(mainChannelsGuide.getNPO3());

                                                channelList.add(themeChannelsGuide.getNews());
                                                channelList.add(themeChannelsGuide.getPol());
                                                channelList.add(themeChannelsGuide.getBest());
                                                channelList.add(themeChannelsGuide.getCult());
                                                channelList.add(themeChannelsGuide.getDoc());
                                                channelList.add(themeChannelsGuide.getHumor());
                                                channelList.add(themeChannelsGuide.get101());
                                                channelList.add(themeChannelsGuide.getZapp());

                                                for (int i = 0; i < channelList.size(); i++) {
                                                    Broadcast currentBroadcast = null;
                                                    List<Broadcast> broadcastList = channelList.get(i);

                                                    Collections.sort(broadcastList, new Comparator<Broadcast>() {
                                                        @Override
                                                        public int compare(Broadcast b1, Broadcast b2) {
                                                            return b1.getStartsAt().compareTo(b2.getStartsAt());
                                                        }
                                                    });
                                                    Collections.reverse(broadcastList);

                                                    for (Broadcast broadcast : broadcastList) {
                                                        Long starts = broadcast.getStartsAt().getTime();
                                                        if (starts < timestamp) {
                                                            currentBroadcast = broadcast;
                                                            break;
                                                        }
                                                    }

                                                    String broadcastImage = null;
                                                    if (currentBroadcast != null)
                                                        broadcastImage = currentBroadcast.getEpisode().getImage();
                                                    if (broadcastImage == null && currentBroadcast != null && currentBroadcast.getEpisode().getStills() != null)
                                                        broadcastImage = currentBroadcast.getEpisode().getStills().get(0).getUrl();

                                                    String name = "Geen informatie beschikbaar";
                                                    int progress = 0;
                                                    if (currentBroadcast != null) {
                                                        long startedAt = currentBroadcast.getStartsAt().getTime() / 1000L;
                                                        long timePassed = (timestamp / 1000L) - startedAt;
                                                        progress = (int) Math.ceil((100f / currentBroadcast.getDuration()) * timePassed);

                                                        if (currentBroadcast.getEpisode().getSeries() != null) {
                                                            name = currentBroadcast.getEpisode().getSeries().getName();

                                                            if (TextUtils.isEmpty(broadcastImage)) {
                                                                Series series = ugApiService.getSeries(currentBroadcast.getEpisode().getSeries().getId())
                                                                        .toBlocking()
                                                                        .value();
                                                                if (series != null)
                                                                    broadcastImage = series.getImage();
                                                            }
                                                        } else {
                                                            name = currentBroadcast.getEpisode().getName();
                                                        }

                                                    }

                                                    returnData.add(new Channel(channelIds[i], name, names[i], broadcastImage, progress, images[i], colors[i]));
                                                }

                                                return returnData;
                                            }
                                        });
                            }
                        })
                        .toObservable()
                        .subscribeOn(Schedulers.newThread())
                        .observeOn(AndroidSchedulers.mainThread());
            }
        }, new Action2<OverviewBrowseFragment, List<Channel>>() {
            @Override
            public void call(OverviewBrowseFragment overviewBrowseFragment, List<Channel> channels) {
                ChannelGridPresenter presenter = new ChannelGridPresenter(context);
                ArrayObjectAdapter listRowAdapter = ((ArrayObjectAdapter) ((ListRow) rowsAdapter.get(LIVE)).getAdapter());
                if (listRowAdapter == null || listRowAdapter.size() == 0) {
                    listRowAdapter = new ArrayObjectAdapter(presenter);
                    listRowAdapter.addAll(0, channels);
                    HeaderItem header = new HeaderItem(overviewBrowseFragment.getString(R.string.live));
                    rowsAdapter.replace(LIVE, new ListRow(header, listRowAdapter));
                    return;
                }

                for (int i = 0; i < channels.size(); i++) {
                    listRowAdapter.replace(i, channels.get(i));
                }
            }
        }, new Action2<OverviewBrowseFragment, Throwable>() {
            @Override
            public void call(OverviewBrowseFragment overviewBrowseFragment, Throwable throwable) {
                Timber.e(throwable, "Unable to update live items");
            }
        });

        restartableLatestCache(RADIO, new Func0<Observable<List<Channel>>>() {
            @Override
            public Observable<List<Channel>> call() {
                return RxSntpClient.getCurrentTime("pool.ntp.org", 3000)
                        .observeOn(Schedulers.newThread())
                        .onErrorReturn(new Func1<Throwable, Long>() {
                            @Override
                            public Long call(Throwable throwable) {
                                Timber.e(throwable, "Unable to get current timestamp, returning device time");
                                return System.currentTimeMillis();
                            }
                        })
                        .toObservable()
                        .flatMap(new Func1<Long, Observable<List<Channel>>>() {
                            @Override
                            public Observable<List<Channel>> call(final Long timestamp) {
                                return radioboxApiService.broadcastSearch(RadioboxApiService.QueryBuilder.Prebuilts.getCurrentDataForMainChannels(), 7, "channel.id:asc")
                                        .observeOn(Schedulers.newThread())
                                        .toObservable()
                                        .flatMap(new Func1<RadioboxSearch<RadioboxBroadcast>, Observable<List<Channel>>>() {
                                            @Override
                                            public Observable<List<Channel>> call(RadioboxSearch<RadioboxBroadcast> radioboxBroadcastRadioboxSearch) {
                                                final AtomicInteger i = new AtomicInteger();
                                                final List<RadioboxBroadcast> radioboxBroadcasts = radioboxBroadcastRadioboxSearch.getResults();
                                                final String[] channelIds = {"1", "2", "3", "4", "5", "6", "7"};
                                                final String[] names = {"NPO Radio 1", "NPO Radio 2", "NPO 3FM", "NPO Radio 4", "NPO Radio 5", "NPO Radio 6", "NPO FunX"};
                                                final int[] images = {R.drawable.ic_radio1, R.drawable.ic_radio2, R.drawable.ic_radio3, R.drawable.ic_radio4, R.drawable.ic_radio5, R.drawable.ic_radio6, R.drawable.ic_radio7};
                                                final int[] colors = {R.color.radio1, R.color.radio2, R.color.radio3, R.color.radio4, R.color.radio5, R.color.radio6, R.color.radio7};

                                                return Observable.from(radioboxBroadcasts)
                                                        .map(new Func1<RadioboxBroadcast, Channel>() {
                                                            @Override
                                                            public Channel call(RadioboxBroadcast broadcast) {
                                                                long startedAt = broadcast.getStartTime().getTime();
                                                                long timePassed = timestamp - startedAt;
                                                                long duration = broadcast.getStopTime().getTime() - broadcast.getStartTime().getTime();
                                                                int progress = (int) Math.ceil((100f / duration) * timePassed);

                                                                String image = null;
                                                                if (broadcast.getImage() != null)
                                                                    image = broadcast.getImage().getUrl();

                                                                int index = i.getAndIncrement();
                                                                return new Channel(channelIds[index], broadcast.getName(), names[index], image, progress, images[index], colors[index], true);
                                                            }
                                                        })
                                                        .toList();
                                            }
                                        });
                            }
                        })
                        .subscribeOn(Schedulers.newThread())
                        .observeOn(AndroidSchedulers.mainThread());
            }
        }, new Action2<OverviewBrowseFragment, List<Channel>>() {
            @Override
            public void call(OverviewBrowseFragment overviewBrowseFragment, List<Channel> channels) {
                ChannelGridPresenter presenter = new ChannelGridPresenter(context);
                ArrayObjectAdapter listRowAdapter = ((ArrayObjectAdapter) ((ListRow) rowsAdapter.get(RADIO)).getAdapter());
                if (listRowAdapter == null || listRowAdapter.size() == 0) {
                    listRowAdapter = new ArrayObjectAdapter(presenter);
                    listRowAdapter.addAll(0, channels);
                    HeaderItem header = new HeaderItem(overviewBrowseFragment.getString(R.string.radio));
                    rowsAdapter.replace(RADIO, new ListRow(header, listRowAdapter));
                    return;
                }

                for (int i = 0; i < channels.size(); i++) {
                    listRowAdapter.replace(i, channels.get(i));
                }
            }
        }, new Action2<OverviewBrowseFragment, Throwable>() {
            @Override
            public void call(OverviewBrowseFragment overviewBrowseFragment, Throwable throwable) {
                Timber.e(throwable, "Unable to update radio items");
            }
        });


        restartable(REFRESH, new Func0<Subscription>() {
            @Override
            public Subscription call() {
                return Observable.just(this)
                        .delay(90, TimeUnit.SECONDS)
                        .subscribe(new Action1<Object>() {
                            @Override
                            public void call(Object browseFragPresenter) {
                                start(LIVE);
                                start(RADIO);
                                start(REFRESH);
                            }
                        });
            }
        });
    }

    @Override
    protected void onTakeView(OverviewBrowseFragment overviewBrowseFragment) {
        super.onTakeView(overviewBrowseFragment);

        overviewBrowseFragment.setAdapter(rowsAdapter);

        start(TIMELINE);
        start(FRAGMENTS);
        start(MISSED);
        start(LIVE);
        start(RADIO);
        start(REFRESH);
    }

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

    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>() {
                                @Override
                                public String call(EncryptedStreamData encryptedStreamData) {
                                    return encryptedStreamData.getUrl();
                                }
                            });
                }

                return viewFiltered().first()
                        .subscribeOn(Schedulers.newThread())
                        .flatMap(new Func1<OverviewBrowseFragment, Observable<String>>() {
                            @Override
                            public Observable<String> 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(LIVESTREAM);
                                    }
                                });
                                return liveStreamUrlObservable;
                            }
                        })
                        .flatMap(new Func1<String, Observable<String>>() {
                            @Override
                            public Observable<String> call(String streamUrl) {
                                return livestreamsApiService.getRealUrl(streamUrl).toObservable();
                            }
                        })
                        .observeOn(AndroidSchedulers.mainThread());
            }
        }, new Action2<OverviewBrowseFragment, String>() {
            @Override
            public void call(OverviewBrowseFragment overviewBrowseFragment, String streamUrl) {
                ProgressDialogFragment.dismissLoadingProgress(overviewBrowseFragment.getChildFragmentManager());

                StreamInfo streamInfo = new StreamInfo(channel.getId(), streamUrl, channel.getSubtitle(), "Live", channel.getImage(), true);
                VideoPlayerActivity.startActivity(overviewBrowseFragment.getActivity(), streamInfo);
            }
        }, new Action2<OverviewBrowseFragment, Throwable>() {
            @Override
            public void call(OverviewBrowseFragment overviewBrowseFragment, Throwable throwable) {
                ProgressDialogFragment.dismissLoadingProgress(overviewBrowseFragment.getChildFragmentManager());

                Timber.e(throwable, "Unable to get videostream data");

                overviewBrowseFragment.showErrorMessage(R.string.loading_livestream_error);
            }
        });

        start(LIVESTREAM);
    }

    private void playVideo(final Object data) {
        restartableFirst(PLAY, new Func0<Observable<StreamInfo>>() {
            @Override
            public Observable<StreamInfo> call() {
                if(data instanceof Episode) {
                    Episode episode = (Episode) data;
                    return ugApiService.getEpisodeById(episode.getId())
                            .zipWith(ugApiService.getOdiData(episode.getId(), UGApiService.PubOption.ADAPTIVE)
                                            .flatMap(new Func1<EncryptedStreamData, Single<OdiData>>() {
                                                @Override
                                                public Single<OdiData> call(EncryptedStreamData encryptedStreamData) {
                                                    return odiApiService.getData(encryptedStreamData.getUrl(), Extension.HLS);
                                                }
                                            }),
                                    new Func2<Episode, OdiData, StreamInfo>() {
                                        @Override
                                        public StreamInfo call(Episode episode, OdiData odiData) {
                                            String title = episode.getName();
                                            if (!episode.getSeries().getName().equals(episode.getName()))
                                                title = String.format(Locale.getDefault(), "%s: %s", episode.getSeries().getName(), episode.getName());
                                            return new StreamInfo(episode.getId(), odiData.getUrl(), title, TextUtils.join(", ", episode.getBroadcasters()), episode.getImage());
                                        }
                                    }
                            )
                            .toObservable()
                            .subscribeOn(Schedulers.newThread())
                            .observeOn(AndroidSchedulers.mainThread());
                } else if (data instanceof Promo) {
                    return Observable.just((Promo) data)
                            .map(new Func1<Promo, StreamInfo>() {
                                @Override
                                public StreamInfo call(Promo promo) {
                                    String location = promo.getLocations().get(0).getUrl();
                                    String lastPart = location.substring(location.lastIndexOf('/'));
                                    lastPart = lastPart.replace("ism", "m3u8");
                                    location = location + lastPart;

                                    return new StreamInfo(promo.getId(), location, promo.getName(), TextUtils.join(", ", promo.getEpisode().getBroadcasters()), promo.getEpisode().getImage());
                                }
                            })
                            .subscribeOn(Schedulers.newThread())
                            .observeOn(AndroidSchedulers.mainThread());
                }

                return Observable.just(null);
            }
        }, new Action2<OverviewBrowseFragment, StreamInfo>() {
            @Override
            public void call(OverviewBrowseFragment detailsFragment, StreamInfo streamInfo) {
                VideoPlayerActivity.startActivity(detailsFragment.getActivity(), streamInfo);
            }
        }, new Action2<OverviewBrowseFragment, Throwable>() {
            @Override
            public void call(OverviewBrowseFragment detailsFragment, Throwable throwable) {
                Timber.e(throwable, "Unable to get streamdata");
            }
        });

        start(PLAY);
    }

} public class DetailsFragPresenter extends BasePresenter<DetailsFragment> {

    public static final int NO_NOTIFICATION = -1;
    private static final int RESTARTABLE_DATA = 0, RESTARTABLE_PLAY = 1;
    private static final int ACTION_PLAY_EPISODE = 0, ACTION_PLAY_FRAG = 1;

    @Inject
    EventBus eventBus;
    @Inject
    Context context;
    @Inject
    UGApiService ugApiService;
    @Inject
    OdiApiService odiApiService;

    OverviewGridItem item;
    Episode episode;
    String title, subtitle, description;

    @Override
    protected void onCreate(Bundle savedState) {
        super.onCreate(savedState);

        restartableLatestCache(RESTARTABLE_DATA, new Func0<Observable<ArrayObjectAdapter>>() {
            @Override
            public Observable<ArrayObjectAdapter> call() {
                Episode episode;
                Observable<Episode> episodeObservable = null;
                if (item.getObject() instanceof Episode) {
                    episodeObservable = Observable.just((Episode) item.getObject());
                } else if (item.getObject() instanceof Series) {
                    episodeObservable = ugApiService.getLatestEpisodesBySeriesId(((Series) item.getObject()).getId())
                            .toObservable();
                } else if (item.getObject() instanceof VideoFragment) {
                    VideoFragment fragment = (VideoFragment) item.getObject();
                    title = fragment.getName();
                    episodeObservable = ugApiService.getEpisodeById(fragment.getEpisode().getId())
                            .toObservable();
                } else if (item.getObject() instanceof Broadcast) {
                    episodeObservable = ugApiService.getEpisodeById(((Broadcast) item.getObject()).getEpisode().getId())
                            .toObservable();
                } else if (item.getObject() instanceof Recommendation) {
                    episodeObservable = ugApiService.getEpisodeById(((Recommendation) item.getObject()).getEpisode().getId())
                            .toObservable();
                }

                if (episodeObservable == null)
                    return Observable.just(null);

                return episodeObservable
                        .subscribeOn(Schedulers.newThread())
                        .observeOn(Schedulers.newThread())
                        .map(new Func1<Episode, ArrayObjectAdapter>() {
                            @Override
                            public ArrayObjectAdapter call(Episode episode) {
                                DetailsFragPresenter.this.episode = episode;

                                if (title == null) {
                                    title = episode.getSeries().getName();
                                }
                                subtitle = context.getString(R.string.episode_subtitle, episode.getName(), episode.getBroadcastedAtAsStr(),
                                        TimeUnit.SECONDS.toMinutes(episode.getDuration()), TextUtils.join(", ", episode.getBroadcasters()));
                                description = episode.getDescription();

                                if(item.getImage() == null) {
                                    eventBus.postOnMain(new UpdateBackgroundEvent(episode.getImage()));
                                } else {
                                    eventBus.postOnMain(new UpdateBackgroundEvent(item.getImage()));
                                }

                                ClassPresenterSelector selector = new ClassPresenterSelector();
                                TVNLDetailsOverviewRowPresenter rowPresenter = new TVNLDetailsOverviewRowPresenter(new DetailsDescriptionPresenter());

                                selector.addClassPresenter(DetailsOverviewRow.class, rowPresenter);
                                selector.addClassPresenter(ListRow.class, new ListRowPresenter());
                                ArrayObjectAdapter rowsAdapter = new ArrayObjectAdapter(selector);

                                DetailsOverviewRow detailsOverview = new DetailsOverviewRow(new Object());

                                SparseArrayObjectAdapter actionsAdapter = new SparseArrayObjectAdapter();
                                if (item.getObject() instanceof VideoFragment) {
                                    actionsAdapter.set(0, new Action(ACTION_PLAY_FRAG, context.getString(R.string.watch_fragment)));
                                }
                                actionsAdapter.set(1, new Action(ACTION_PLAY_EPISODE, context.getString(R.string.watch_episode)));
                                detailsOverview.setActionsAdapter(actionsAdapter);
                                rowPresenter.setOnActionClickedListener(onActionClickListener);

                                rowsAdapter.add(detailsOverview);

                                return rowsAdapter;
                            }
                        })
                        .flatMap(new Func1<ArrayObjectAdapter, Observable<ArrayObjectAdapter>>() {
                            @Override
                            public Observable<ArrayObjectAdapter> call(final ArrayObjectAdapter rowsAdapter) {
                                return ugApiService.getSeries(DetailsFragPresenter.this.episode.getSeries().getId())
                                        .toObservable()
                                        .map(new Func1<Series, ArrayObjectAdapter>() {
                                            @Override
                                            public ArrayObjectAdapter call(Series series) {
                                                ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new OverviewGridPresenter(context));

                                                Series episodeSeries = series.getWithoutEpisodes();
                                                for (Episode episode : series.getEpisodes()) {
                                                    if (episode.getId().equals(DetailsFragPresenter.this.episode.getId()))
                                                        continue;

                                                    episode.setSeries(episodeSeries);
                                                    OverviewGridItem gridItem = episode.toOverviewGridItem();
                                                    gridItem.setSubtitle(context.getString(R.string.episode_grid_subtitle, episode.getBroadcastedAtAsStr(), TimeUnit.SECONDS.toMinutes(episode.getDuration())));
                                                    listRowAdapter.add(gridItem);
                                                }

                                                if (listRowAdapter.size() > 0) {
                                                    HeaderItem header = new HeaderItem(0, context.getString(R.string.episodes));
                                                    rowsAdapter.add(new ListRow(header, listRowAdapter));
                                                }

                                                return rowsAdapter;
                                            }
                                        });
                            }
                        })
                        .observeOn(AndroidSchedulers.mainThread());
            }
        }, new Action2<DetailsFragment, ArrayObjectAdapter>() {
            @Override
            public void call(DetailsFragment detailsFragment, ArrayObjectAdapter arrayObjectAdapter) {
                ProgressOverlayDialogFragment.dismissLoadingProgress(detailsFragment.getChildFragmentManager());
                if (arrayObjectAdapter != null)
                    detailsFragment.setAdapter(arrayObjectAdapter);
            }
        }, new Action2<DetailsFragment, Throwable>() {
            @Override
            public void call(DetailsFragment detailsFragment, Throwable throwable) {
                Timber.e(throwable, "Detail loading problem");
            }
        });
    }

    @Override
    protected void onTakeView(final DetailsFragment detailsFragment) {
        super.onTakeView(detailsFragment);

        ProgressOverlayDialogFragment dialogFragment = ProgressOverlayDialogFragment.showLoadingProgress(detailsFragment.getChildFragmentManager());
        dialogFragment.setOnCancelListener(new DialogInterface.OnCancelListener() {
            @Override
            public void onCancel(DialogInterface dialogInterface) {
                detailsFragment.getActivity().onBackPressed();
            }
        });

        removeNotification(detailsFragment.getNotificationId());

        item = detailsFragment.getItem();

        start(RESTARTABLE_DATA);
    }

    public void itemClicked(Fragment fragment, Object item) {
        if(item instanceof OverviewGridItem) {
            DetailsActivity.startActivity(fragment.getActivity(), (OverviewGridItem) item);
        }
    }

    private void removeNotification(int notificationId) {
        if (notificationId != NO_NOTIFICATION) {
            NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
            notificationManager.cancel(notificationId);
        }
    }

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

    private OnActionClickedListener onActionClickListener = new OnActionClickedListener() {
        @Override
        public void onActionClicked(final Action action) {
            if(action.getId() == ACTION_PLAY_EPISODE || action.getId() == ACTION_PLAY_FRAG) {
                restartableFirst(RESTARTABLE_PLAY, new Func0<Observable<StreamInfo>>() {
                    @Override
                    public Observable<StreamInfo> call() {
                        return ugApiService.getEpisodeById(episode.getId())
                                .zipWith(ugApiService.getOdiData(episode.getId(), UGApiService.PubOption.ADAPTIVE)
                                                .flatMap(new Func1<EncryptedStreamData, Single<OdiData>>() {
                                                    @Override
                                                    public Single<OdiData> call(EncryptedStreamData encryptedStreamData) {
                                                        return odiApiService.getData(encryptedStreamData.getUrl(), Extension.HLS);
                                                    }
                                                }),
                                        new Func2<Episode, OdiData, StreamInfo>() {
                                            @Override
                                            public StreamInfo call(Episode episode, OdiData odiData) {
                                                String title = episode.getName();
                                                String subtitle = "";
                                                if (!episode.getSeries().getName().equals(episode.getName()))
                                                    subtitle = String.format(Locale.getDefault(), "%s (%s)", episode.getSeries().getName(), TextUtils.join(", ", episode.getBroadcasters()));
                                                if(action.getId() == ACTION_PLAY_FRAG) {
                                                    return new StreamInfo(episode.getId(), odiData.getUrl(), title, subtitle, episode.getImage(), TimeUnit.SECONDS.toMillis(((VideoFragment) item.getObject()).getStartsAt()));
                                                }
                                                return new StreamInfo(episode.getId(), odiData.getUrl(), title, subtitle, episode.getImage());
                                            }
                                        }
                                )
                                .toObservable()
                                .subscribeOn(Schedulers.newThread())
                                .observeOn(AndroidSchedulers.mainThread());
                    }
                }, new Action2<DetailsFragment, StreamInfo>() {
                    @Override
                    public void call(DetailsFragment detailsFragment, StreamInfo streamInfo) {
                        VideoPlayerActivity.startActivity(detailsFragment.getActivity(), streamInfo);
                    }
                }, new Action2<DetailsFragment, Throwable>() {
                    @Override
                    public void call(DetailsFragment detailsFragment, Throwable throwable) {
                        Timber.e(throwable, "Unable to get streamdata");
                    }
                });

                start(RESTARTABLE_PLAY);
            }
        }
    };

    public class DetailsDescriptionPresenter extends AbstractDetailsDescriptionPresenter {

        @Override
        protected void onBindDescription(ViewHolder viewHolder, Object itemData) {
            viewHolder.getTitle().setText(title);
            viewHolder.getSubtitle().setText(subtitle);
            viewHolder.getBody().setText(description);
        }

    }

} public class PlaybackOverlayFragPresenter extends BasePresenter<PlaybackOverlayFragment> {

    private static final String STATE_MEDIA_READY = "state_ready";
    private static final String STATE_MEDIA_PLAYING = "playing";

    private static final int MODE_NOTHING = 0;
    private static final int MODE_FAST_FORWARD = 1;
    private static final int MODE_REWIND = 2;

    @Inject
    EventBus eventBus;

    ArrayObjectAdapter rowsAdapter;
    ArrayObjectAdapter primaryActionsAdapter;
    ArrayObjectAdapter secondaryActionsAdapter;
    PlaybackControlsRow.PlayPauseAction playPauseAction;
    PlaybackControlsRow.RewindAction rewindAction;
    PlaybackControlsRow.FastForwardAction fastForwardAction;
    PlaybackControlsRowPresenter playbackControlsRowPresenter;
    PlaybackControlsRow playbackControlsRow;
    Handler handlerPlayback;
    Handler handlerPlaybackSpeed;
    StreamInfo streamInfo;

    long selectedActionId = 0;
    int currentMode = MODE_NOTHING;
    int seek;
    int currentTime;
    int seekSpeed = SeekBackwardEvent.MINIMUM_SEEK_SPEED;
    boolean isMediaReady = false;
    boolean isPlaying = false;

    /* Lifecycle section */
    @Override
    protected void onCreate(Bundle savedState) {
        super.onCreate(savedState);

        if(savedState != null) {
            isMediaReady = savedState.getBoolean(STATE_MEDIA_READY);
            isPlaying = savedState.getBoolean(STATE_MEDIA_PLAYING);
        }

        handlerPlayback = new Handler();
        handlerPlaybackSpeed = new Handler();

        try {
            eventBus.register(this);
        } catch (IllegalArgumentException e) {
            // Already registered
        }
    }

    @Override
    protected void onTakeView(PlaybackOverlayFragment fragment) {
        super.onTakeView(fragment);

        streamInfo = fragment.getStreamInfo();

        initialisePlaybackControlPresenter(fragment);
        if(isMediaReady) {
            setupPlaybackControlItemsToReadyState();
        } else {
            setupPlaybackControlItemsToInitialisingState(fragment);
        }

        if(isPlaying) {
            playPauseAction.setIndex(PlaybackControlsRow.PlayPauseAction.PAUSE);
            fragment.setFadingEnabled(true);
            notifyPlaybackControlActionChanged(playPauseAction);
        }
    }

    @Override
    protected void onSave(Bundle state) {
        super.onSave(state);
        state.putBoolean(STATE_MEDIA_PLAYING, isPlaying);
        state.putBoolean(STATE_MEDIA_READY, isMediaReady);
    }

    @Override
    protected void onDropView() {
        playPauseAction.setIndex(PlaybackControlsRow.PlayPauseAction.PLAY);
        viewFiltered().first().subscribe(new Action1<PlaybackOverlayFragment>() {
            @Override
            public void call(PlaybackOverlayFragment fragment) {
                fragment.setFadingEnabled(false);
            }
        });
        notifyPlaybackControlActionChanged(playPauseAction);

        super.onDropView();
    }

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

    private void initialisePlaybackControlPresenter(PlaybackOverlayFragment fragment) {
        ClassPresenterSelector presenterSelector = new ClassPresenterSelector();
        playbackControlsRowPresenter = new PlaybackControlsRowPresenter(new DescriptionPresenter());
        playbackControlsRowPresenter.setSecondaryActionsHidden(false);

        presenterSelector.addClassPresenter(PlaybackControlsRow.class, playbackControlsRowPresenter);
        presenterSelector.addClassPresenter(ListRow.class, new ListRowPresenter());

        rowsAdapter = new ArrayObjectAdapter(presenterSelector);
        fragment.setAdapter(rowsAdapter);
    }

    private void setupPlaybackControlItemsActions() {
        playbackControlsRowPresenter.setOnActionClickedListener(onActionClickedListener);
    }

    private void setupPlaybackControlItemsToInitialisingState(PlaybackOverlayFragment fragment) {
        if(rowsAdapter == null)
            return;

        rowsAdapter.clear();
        playbackControlsRow = new PlaybackControlsRow(streamInfo);
        playbackControlsRow.setCurrentTime(0);
        playbackControlsRow.setBufferedProgress(0);

        ControlButtonPresenterSelector presenterSelector = new ControlButtonPresenterSelector();
        primaryActionsAdapter = new ArrayObjectAdapter(presenterSelector);
        playbackControlsRow.setPrimaryActionsAdapter(primaryActionsAdapter);

        playPauseAction = new PlaybackControlsRow.PlayPauseAction(fragment.getActivity());
        primaryActionsAdapter.add(playPauseAction);

        setupSecondaryRowPlaybackControl(presenterSelector);

        rowsAdapter.add(playbackControlsRow);
        rowsAdapter.notifyArrayItemRangeChanged(0, rowsAdapter.size());
    }

    private void setupPlaybackControlItemsToReadyState() {
        if(rowsAdapter == null)
            return;

        rowsAdapter.clear();
        playbackControlsRow = new PlaybackControlsRow(streamInfo);
        playbackControlsRow.setCurrentTime(0);
        playbackControlsRow.setBufferedProgress(0);

        ControlButtonPresenterSelector presenterSelector = new ControlButtonPresenterSelector();
        setupPrimaryRowPlaybackControl(presenterSelector);
        setupSecondaryRowPlaybackControl(presenterSelector);

        rowsAdapter.add(playbackControlsRow);
        rowsAdapter.notifyArrayItemRangeChanged(0, rowsAdapter.size());

        setupPlaybackControlItemsActions();
    }

    private void setupPrimaryRowPlaybackControl(@NonNull ControlButtonPresenterSelector presenterSelector) {
        primaryActionsAdapter = new ArrayObjectAdapter(presenterSelector);
        playbackControlsRow.setPrimaryActionsAdapter(primaryActionsAdapter);

        viewFiltered().first().subscribe(new Action1<PlaybackOverlayFragment>() {
            @Override
            public void call(PlaybackOverlayFragment fragment) {
                playPauseAction = new PlaybackControlsRow.PlayPauseAction(fragment.getActivity());
                fastForwardAction = new PlaybackControlsRow.FastForwardAction(fragment.getActivity());
                rewindAction = new PlaybackControlsRow.RewindAction(fragment.getActivity());

                if (isPlaying)
                    playPauseAction.setIndex(PlaybackControlsRow.PlayPauseAction.PAUSE);

                // Add main controls to primary adapter.
                if (!streamInfo.isLive())
                    primaryActionsAdapter.add(rewindAction);
                primaryActionsAdapter.add(playPauseAction);
                if (!streamInfo.isLive())
                    primaryActionsAdapter.add(fastForwardAction);
            }
        });
    }

    private void setupSecondaryRowPlaybackControl(@NonNull PresenterSelector presenterSelector) {
        secondaryActionsAdapter = new ArrayObjectAdapter(presenterSelector);
        playbackControlsRow.setSecondaryActionsAdapter(secondaryActionsAdapter);
    }

    /* Events section */
    @SuppressWarnings("unused")
    @Subscribe
    public void onEvent(UpdatePlaybackStateEvent event) {
        isPlaying = event.isPlaying();

        if (isPlaying) {
            if (!isMediaReady) {
                setupPlaybackControlItemsToReadyState();
                isMediaReady = true;
            }

            viewFiltered().first().subscribe(new Action1<PlaybackOverlayFragment>() {
                @Override
                public void call(PlaybackOverlayFragment fragment) {
                    playPauseAction.setIndex(PlaybackControlsRow.PlayPauseAction.PAUSE);
                    fragment.setFadingEnabled(true);
                }
            });
        } else {
            viewFiltered().first().subscribe(new Action1<PlaybackOverlayFragment>() {
                @Override
                public void call(PlaybackOverlayFragment fragment) {
                    playPauseAction.setIndex(PlaybackControlsRow.PlayPauseAction.PLAY);
                    fragment.setFadingEnabled(false);
                }
            });
        }

        notifyPlaybackControlActionChanged(playPauseAction);
        if (rowsAdapter != null) rowsAdapter.notifyArrayItemRangeChanged(0, rowsAdapter.size());
    }

    @SuppressWarnings("unused")
    @Subscribe
    public void onEvent(@NonNull PlaybackProgressChangedEvent event) {
        if(playbackControlsRow == null)
            return;

        if (playbackControlsRow.getTotalTime() == 0 && !streamInfo.isLive()) {
            playbackControlsRow.setTotalTime((int) event.getDuration());
        }

        if (seek != 0 && currentMode != MODE_NOTHING) {
            return;
        }

        currentTime = (int) event.getCurrentTime();

        viewFiltered().first().subscribe(new Action1<PlaybackOverlayFragment>() {
            @Override
            public void call(PlaybackOverlayFragment fragment) {
                if(!fragment.isHidden()) {
                    playbackControlsRow.setCurrentTime(currentTime);
                    if (rowsAdapter != null) rowsAdapter.notifyArrayItemRangeChanged(0, rowsAdapter.size());
                }
            }
        });
    }

    private void notifyPlaybackControlActionChanged(@NonNull Action action) {
        ArrayObjectAdapter adapter = primaryActionsAdapter;
        if(adapter == null)
            return;

        if (adapter.indexOf(action) >= 0) {
            adapter.notifyArrayItemRangeChanged(adapter.indexOf(action), 1);
            return;
        }

        adapter = secondaryActionsAdapter;
        if(adapter == null)
            return;

        if (adapter.indexOf(action) >= 0) {
            adapter.notifyArrayItemRangeChanged(adapter.indexOf(action), 1);
        }
    }

    private void invokeTogglePlaybackAction(final boolean play) {
        viewFiltered().first().subscribe(new Action1<PlaybackOverlayFragment>() {
            @Override
            public void call(PlaybackOverlayFragment fragment) {
                if (play) {
                    eventBus.postOnMain(new StartPlaybackEvent());
                    fragment.setFadingEnabled(true);
                } else {
                    eventBus.postOnMain(new PausePlaybackEvent());
                    fragment.setFadingEnabled(false);
                }
            }
        });
    }

    private void invokeSeekAction() {
        viewFiltered().first().subscribe(new Action1<PlaybackOverlayFragment>() {
            @Override
            public void call(final PlaybackOverlayFragment fragment) {
                final int refreshDuration = 100;
                final int speedRefreshDuration = 5000;

                Runnable runnablePlayback = new Runnable() {
                    @Override
                    public void run() {
                        fragment.tickle();
                        if (currentMode == MODE_REWIND) {
                            int currentTime = playbackControlsRow.getCurrentTime();
                            currentTime -= seekSpeed;

                            if (currentTime > 0) {
                                playbackControlsRow.setCurrentTime(currentTime);
                                rowsAdapter.notifyArrayItemRangeChanged(0, 1);
                                seek += seekSpeed;
                            }

                            handlerPlayback.postDelayed(this, refreshDuration);
                        } else if (selectedActionId == rewindAction.getId()) {
                            SeekBackwardEvent event = new SeekBackwardEvent();
                            event.setSeek(seek);
                            seek = 0;
                            eventBus.postOnMain(event);
                        } else if (currentMode == MODE_FAST_FORWARD) {
                            int currentTime = playbackControlsRow.getCurrentTime();
                            currentTime += seekSpeed;

                            if (currentTime < playbackControlsRow.getTotalTime()) {
                                playbackControlsRow.setCurrentTime(currentTime);
                                rowsAdapter.notifyArrayItemRangeChanged(0, 1);
                                seek += seekSpeed;
                            }

                            handlerPlayback.postDelayed(this, refreshDuration);
                        } else if (selectedActionId == fastForwardAction.getId()) {
                            SeekForwardEvent event = new SeekForwardEvent();
                            event.setSeek(seek);
                            seek = 0;
                            eventBus.postOnMain(event);
                        }
                    }
                };

                Runnable runnablePlaybackSpeed = new Runnable() {
                    @Override
                    public void run() {
                        if (currentMode == MODE_FAST_FORWARD || currentMode == MODE_REWIND) {
                            seekSpeed *= 2;
                            handlerPlaybackSpeed.postDelayed(this, speedRefreshDuration);
                        } else {
                            seekSpeed = SeekForwardEvent.MINIMUM_SEEK_SPEED;
                        }
                    }
                };

                handlerPlayback.postDelayed(runnablePlayback, refreshDuration);
                handlerPlaybackSpeed.postDelayed(runnablePlaybackSpeed, speedRefreshDuration);
                fragment.tickle();
            }
        });
    }

    public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) {
        currentMode = MODE_NOTHING;
        selectedActionId = 0;
        if (item != null && item instanceof Action) {
            Action action = (Action) item;
            selectedActionId = action.getId();
        }
    }

    public boolean handleInputEvent(@NonNull InputEvent event) {
        if (event instanceof KeyEvent) {
            KeyEvent keyEvent = (KeyEvent) event;
            if (keyEvent.getKeyCode() != KeyEvent.KEYCODE_DPAD_CENTER && keyEvent.getKeyCode() != KeyEvent.KEYCODE_ENTER && keyEvent.getKeyCode() != KeyEvent.KEYCODE_NUMPAD_ENTER) return false;
            if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
                if (fastForwardAction != null && selectedActionId == fastForwardAction.getId()) {
                    if (keyEvent.getRepeatCount() == 0) {
                        currentMode = MODE_FAST_FORWARD;
                        invokeSeekAction();
                    }
                } else if (rewindAction != null && selectedActionId == rewindAction.getId()) {
                    if (keyEvent.getRepeatCount() == 0) {
                        currentMode = MODE_REWIND;
                        invokeSeekAction();
                    }
                }
            } else if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
                currentMode = MODE_NOTHING;
                seekSpeed = SeekBackwardEvent.MINIMUM_SEEK_SPEED;
            }
        }
        return false;
    }

    public void onFadeInComplete() {
        if(playbackControlsRow == null)
            return;

        playbackControlsRow.setCurrentTime(currentTime);
        selectedActionId = playPauseAction.getId();

        if (rowsAdapter != null)
            rowsAdapter.notifyArrayItemRangeChanged(0, rowsAdapter.size());
    }

    public void onFadeOutComplete() {
        currentMode = MODE_NOTHING;
        selectedActionId = 0;
    }

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

    OnActionClickedListener onActionClickedListener = new OnActionClickedListener() {
        @Override
        public void onActionClicked(Action action) {
            if (action.getId() == playPauseAction.getId()) {
                invokeTogglePlaybackAction(playPauseAction.getIndex() == PlaybackControlsRow.PlayPauseAction.PLAY);
            }

            if (action instanceof PlaybackControlsRow.MultiAction) {
                notifyPlaybackControlActionChanged(action);
            }
        }
    };

} public class SearchFragPresenter extends BasePresenter<SearchFragment> {

    private static final int SEARCH_DELAY_MS = 300;

    @Inject
    Context context;
    @Inject
    EventBus eventBus;
    @Inject
    UGApiService ugApiService;

    ArrayObjectAdapter rowsAdapter;
    ListRowPresenter listRowPresenter;
    Subscription searchSubscription;

    @Override
    protected void onCreate(Bundle savedState) {
        super.onCreate(savedState);
        listRowPresenter = new ListRowPresenter();
        rowsAdapter = new ArrayObjectAdapter(listRowPresenter);
    }

    private void loadRows(final String query) {
        rowsAdapter.clear();

        Single<Pair<String, List<OverviewGridItem>>> seriesSearchSingle = ugApiService.getSeriesIndex()
                .observeOn(Schedulers.newThread())
                .subscribeOn(Schedulers.newThread())
                .map(new Func1<List<Series>, Pair<String, List<OverviewGridItem>>>() {
                    @Override
                    public Pair<String, List<OverviewGridItem>> call(List<Series> seriesList) {
                        List<OverviewGridItem> returnList = new ArrayList<>();
                        for (Series series : seriesList) {
                            if (series.getName().toLowerCase().contains(query))
                                returnList.add(series.toOverviewGridItem());
                        }
                        return new Pair<>(context.getString(R.string.series), returnList);
                    }
                })
                .onErrorReturn(new Func1<Throwable, Pair<String, List<OverviewGridItem>>>() {
                    @Override
                    public Pair<String, List<OverviewGridItem>> call(Throwable throwable) {
                        Timber.d("Couldn't load search results", throwable);
                        List<OverviewGridItem> returnList = new ArrayList<>();
                        return new Pair<>(context.getString(R.string.series), returnList);
                    }
                });

        searchSubscription = ugApiService.getEpisodesBySearch(query)
                .delay(SEARCH_DELAY_MS, TimeUnit.MILLISECONDS)
                .observeOn(Schedulers.newThread())
                .subscribeOn(Schedulers.newThread())
                .map(new Func1<List<Episode>, Pair<String, List<OverviewGridItem>>>() {
                    @Override
                    public Pair<String, List<OverviewGridItem>> call(List<Episode> episodeList) {
                        List<OverviewGridItem> returnList = new ArrayList<>();
                        for (Episode episode : episodeList) {
                            returnList.add(episode.toOverviewGridItem());
                        }
                        return new Pair<>(context.getString(R.string.episodes), returnList);
                    }
                })
                .onErrorReturn(new Func1<Throwable, Pair<String, List<OverviewGridItem>>>() {
                    @Override
                    public Pair<String, List<OverviewGridItem>> call(Throwable throwable) {
                        Timber.d("Couldn't load search results", throwable);
                        List<OverviewGridItem> returnList = new ArrayList<>();
                        return new Pair<>(context.getString(R.string.episodes), returnList);
                    }
                })
                .concatWith(seriesSearchSingle)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Action1<Pair<String, List<OverviewGridItem>>>() {
                    @Override
                    public void call(Pair<String, List<OverviewGridItem>> dataPair) {
                        addRow(dataPair.first, dataPair.second);
                    }
                }, new Action1<Throwable>() {
                    @Override
                    public void call(Throwable throwable) {
                        Timber.d("Couldn't load search results", throwable);
                    }
                });
    }

    private void addRow(String title, List<OverviewGridItem> items) {
        OverviewGridPresenter presenter = new OverviewGridPresenter(context);
        HeaderItem header = new HeaderItem(title);
        ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(presenter);
        listRowAdapter.addAll(0, items);
        rowsAdapter.add(new ListRow(header, listRowAdapter));
    }

    public ObjectAdapter getRowsAdapter() {
        return rowsAdapter;
    }

    public void queryByWords(String words) {
        rowsAdapter.clear();
        if (!TextUtils.isEmpty(words)) {
            if(searchSubscription != null && !searchSubscription.isUnsubscribed())
                searchSubscription.unsubscribe();
            loadRows(words.toLowerCase());
        }
    }

    public void itemSelected(Object item) {
        if (item instanceof OverviewGridItem) {
            eventBus.postOnMain(new UpdateBackgroundEvent(((OverviewGridItem) item).getImage()));
        }
    }

    public void itemClicked(Fragment fragment, Object item) {
        if(item instanceof OverviewGridItem) {
            OverviewGridItem gridItem = (OverviewGridItem) item;
            DetailsActivity.startActivity(fragment.getActivity(), gridItem);
        }
    }

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

} public class VideoPlayerFragPresenter extends BasePresenter<VideoPlayerFragment> {

    private static final String MEDIASESSION_TAG = "TVNLMediaSession";
    private static final String STATE_CURRENTTIME = "state_currenttime";

    @Inject
    Context context;
    @Inject
    EventBus eventBus;
    MediaSession mediaSession;

    long duration = 0;
    int currentTime = -1;

    boolean ended = false;
    boolean seeking = false;
    boolean mediaSessionMetadataApplied = false;

    StreamInfo streamInfo;

    /* Lifecycle Section */

    @Override
    protected void onCreate(Bundle savedState) {
        super.onCreate(savedState);

        try {
            eventBus.register(this);
        } catch (IllegalArgumentException exception) {
            // Object already registered.
        }

        if(savedState != null) {
            currentTime = savedState.getInt(STATE_CURRENTTIME);
        }
    }

    @Override
    protected void onSave(Bundle state) {
        super.onSave(state);
        state.putInt(STATE_CURRENTTIME, currentTime);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        try {
            eventBus.unregister(this);
        } catch (IllegalArgumentException exception) {
            // Object not registered.
        }

        if(mediaSession != null) {
            mediaSession.setActive(false);
            mediaSession.release();
        }
    }

    @Override
    protected void onTakeView(VideoPlayerFragment fragment) {
        super.onTakeView(fragment);

        streamInfo = fragment.getStreamInfo();

        TVNLVideoView videoView = fragment.getVideoView();
        videoView.setKeepScreenOn(true);

        if(duration == 0 || !videoView.isPlaying()) {
            loadMedia(fragment);
        } else {
            fragment.setProgressVisible(false);
        }
    }

    /* MediaPlayer section */

    public void loadMedia(VideoPlayerFragment fragment) {
        String videoLocation;
        if (TextUtils.isEmpty(streamInfo.getVideoLocation())) {
            fragment.showPlayerInfo("Error loading media");
            fragment.getActivity().finish();
            return;
        } else {
            videoLocation = streamInfo.getVideoLocation();
            if (!videoLocation.startsWith("file://") && !videoLocation.startsWith("http://") && !videoLocation.startsWith("https://")) {
                videoLocation = "file://" + videoLocation;
            }
        }

        TVNLVideoView videoView = fragment.getVideoView();

        if(currentTime == -1 && streamInfo.getStartPosition() > 0 && streamInfo.getStartPosition() > currentTime) {
            currentTime = streamInfo.getStartPosition().intValue();
        } else if (currentTime == -1) {
            currentTime = 0;
        }

        videoView.setVideoURI(Uri.parse(videoLocation));
        ended = false;

        activateMediaSession();
    }

    private Boolean isPlaying(VideoPlayerFragment fragment) {
        return fragment.getVideoView().isPlaying();
    }

    public void setCurrentTime(final long time) {
        viewFiltered().first().subscribe(new Action1<VideoPlayerFragment>() {
            @Override
            public void call(VideoPlayerFragment fragment) {
                fragment.getVideoView().seekTo((int) time);
            }
        });
    }

    private long getCurrentTime(VideoPlayerFragment fragment) {
        return fragment.getVideoView().getCurrentPosition();
    }

    private long getDuration() {
        return duration;
    }

    public void seek(final int delta) {
        if (duration <= 0 && !seeking) return;

        viewFiltered().first().subscribe(new Action1<VideoPlayerFragment>() {
            @Override
            public void call(VideoPlayerFragment fragment) {
                long position = getCurrentTime(fragment) + delta;
                if (position < 0) position = 0;
                setCurrentTime(position);
                updateProgress(getCurrentTime(fragment), getDuration());
            }
        });
    }

    public void isReady(VideoPlayerFragment fragment, TVNLVideoView videoView) {
        videoView.seekTo(currentTime);
        videoView.start();
        duration = videoView.getDuration();
        updatePlayPauseState();
        fragment.setProgressVisible(false);
    }

    public void isCompleted(TVNLVideoView videoView) {
        ended = true;
        currentTime = 0;

        updatePlayPauseState();
        videoView.setKeepScreenOn(false);

        mediaSession.setActive(false);
        mediaSession.release();
    }

    public void encounteredError(VideoPlayerFragment fragment, TVNLVideoView videoView) {
        updatePlayPauseState();
        fragment.showErrorDialog(context.getString(R.string.encountered_error_title), context.getString(R.string.encountered_error));
    }

    public void updateProgress(final long currentTime, final long duration) {
        viewFiltered().first().subscribe(new Action1<VideoPlayerFragment>() {
            @Override
            public void call(VideoPlayerFragment fragment) {
                VideoPlayerFragPresenter.this.currentTime = (int) currentTime;
                eventBus.postOnMain(new PlaybackProgressChangedEvent(currentTime, duration));

                if (mediaSession != null && duration > 0) {
                    PlaybackState.Builder builder = new PlaybackState.Builder();
                    builder.setActions(isPlaying(fragment)
                            ? PlaybackState.ACTION_PLAY_PAUSE | PlaybackState.ACTION_PAUSE
                            : PlaybackState.ACTION_PLAY_PAUSE | PlaybackState.ACTION_PLAY);
                    builder.setState(
                            isPlaying(fragment)
                                    ? PlaybackState.STATE_PLAYING
                                    : PlaybackState.STATE_PAUSED,
                            getCurrentTime(fragment),
                            1.0f);
                    mediaSession.setPlaybackState(builder.build());

                    if (!mediaSessionMetadataApplied) {
                        setupMediaMetadata();
                    }
                }
            }
        });
    }

    public void play(VideoPlayerFragment fragment) {
        TVNLVideoView videoView = fragment.getVideoView();
        videoView.seekTo(currentTime);
        videoView.start();
        videoView.setKeepScreenOn(true);

        updatePlayPauseState();
    }

    public void pause(VideoPlayerFragment fragment) {
        TVNLVideoView videoView = fragment.getVideoView();
        videoView.pause();
        videoView.setKeepScreenOn(false);

        updatePlayPauseState();
    }

    public void togglePlayPause() {
        viewFiltered().first().subscribe(new Action1<VideoPlayerFragment>() {
            @Override
            public void call(VideoPlayerFragment fragment) {
                if(fragment.getVideoView().isPlaying()) {
                    pause(fragment);
                } else {
                    loadMedia(fragment);
                    play(fragment);
                }
            }
        });
    }

    public void updatePlayPauseState() {
        viewFiltered().first().subscribe(new Action1<VideoPlayerFragment>() {
            @Override
            public void call(VideoPlayerFragment fragment) {
                eventBus.postOnMain(new UpdatePlaybackStateEvent(isPlaying(fragment)));

                if (mediaSession != null) {
                    PlaybackState.Builder builder = new PlaybackState.Builder();
                    builder.setActions(isPlaying(fragment)
                            ? PlaybackState.ACTION_PLAY_PAUSE | PlaybackState.ACTION_PAUSE
                            : PlaybackState.ACTION_PLAY_PAUSE | PlaybackState.ACTION_PLAY);
                    builder.setState(
                            isPlaying(fragment) ? PlaybackState.STATE_PLAYING : PlaybackState.STATE_PAUSED,
                            getCurrentTime(fragment),
                            1.0f);
                    mediaSession.setPlaybackState(builder.build());
                }
            }
        });
    }

    /* Eventbus Events section */

    @SuppressWarnings("unused")
    @Subscribe
    public void onEvent(StartPlaybackEvent event) {
        togglePlayPause();

        viewFiltered().first().subscribe(new Action1<VideoPlayerFragment>() {
            @Override
            public void call(VideoPlayerFragment fragment) {
                if (mediaSession != null) {
                    PlaybackState.Builder builder = new PlaybackState.Builder();
                    builder.setActions(PlaybackState.ACTION_PAUSE | PlaybackState.ACTION_PLAY_PAUSE);
                    builder.setState(PlaybackState.STATE_PLAYING, getCurrentTime(fragment), 1.0f);
                    mediaSession.setPlaybackState(builder.build());
                }
            }
        });
    }

    @SuppressWarnings("unused")
    @Subscribe
    public void onEvent(PausePlaybackEvent event) {
        togglePlayPause();

        viewFiltered().first().subscribe(new Action1<VideoPlayerFragment>() {
            @Override
            public void call(VideoPlayerFragment fragment) {
                if (mediaSession != null) {
                    PlaybackState.Builder builder = new PlaybackState.Builder();
                    builder.setActions(PlaybackState.ACTION_PLAY | PlaybackState.ACTION_PLAY_PAUSE);
                    builder.setState(PlaybackState.STATE_PAUSED, getCurrentTime(fragment), 1.0f);
                    mediaSession.setPlaybackState(builder.build());
                }
            }
        });
    }

    @SuppressWarnings("unused")
    @Subscribe
    public void onEvent(SeekBackwardEvent event) {
        if (!seeking) {
            seeking = true;
            seek(event.getSeek());
            seeking = false;
        }
    }

    @SuppressWarnings("unused")
    @Subscribe
    public void onEvent(SeekForwardEvent event) {
        if (!seeking) {
            seeking = true;
            seek(event.getSeek());
            seeking = false;
        }
    }

    /* MediaSession section */

    private void activateMediaSession() {
        viewFiltered().first().subscribe(new Action1<VideoPlayerFragment>() {
            @Override
            public void call(VideoPlayerFragment fragment) {
                Activity activity = fragment.getActivity();
                mediaSession = new MediaSession(activity, MEDIASESSION_TAG);
                mediaSession.setCallback(new MediaSessionCallback(activity));
                mediaSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
                mediaSession.setActive(true);
                activity.setMediaController(new MediaController(activity, mediaSession.getSessionToken()));
            }
        });
    }

    private void setupMediaMetadata() {
        mediaSessionMetadataApplied = false;

        final MediaMetadata.Builder metadataBuilder = new MediaMetadata.Builder();
        metadataBuilder.putString(MediaMetadata.METADATA_KEY_MEDIA_ID, streamInfo.getId());
        metadataBuilder.putLong(MediaMetadata.METADATA_KEY_DURATION, getDuration());

        metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, streamInfo.getTitle());
        metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, streamInfo.getSubtitle());
        metadataBuilder.putString(MediaMetadata.METADATA_KEY_TITLE, streamInfo.getTitle());

        String imageUrl = streamInfo.getImage();
        if (imageUrl != null && !imageUrl.equals("")) {
            metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI, imageUrl);
            metadataBuilder.putString(MediaMetadata.METADATA_KEY_ART_URI, imageUrl);

            Target target = new SimpleTarget<GlideBitmapDrawable>() {
                @Override
                public void onResourceReady(GlideBitmapDrawable drawable, GlideAnimation glideAnimation) {
                    metadataBuilder.putBitmap(MediaMetadata.METADATA_KEY_ART, drawable.getBitmap());
                    mediaSession.setMetadata(metadataBuilder.build());
                    mediaSessionMetadataApplied = true;
                }

                @Override
                public void onLoadFailed(Exception e, Drawable errorDrawable) {
                    super.onLoadFailed(e, errorDrawable);
                    mediaSession.setMetadata(metadataBuilder.build());
                    mediaSessionMetadataApplied = true;
                }
            };

            Glide.with(context).load(imageUrl).into(target);
        } else {
            mediaSession.setMetadata(metadataBuilder.build());
            mediaSessionMetadataApplied = true;
        }
    }

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

    private class MediaSessionCallback extends MediaSession.Callback {
        private final WeakReference<Context> contextReference;

        public MediaSessionCallback(Context context) {
            contextReference = new WeakReference<>(context);
        }

        @Override
        public void onPlay() {
            eventBus.postOnMain(new StartPlaybackEvent());
        }

        @Override
        public void onPause() {
            eventBus.postOnMain(new PausePlaybackEvent());
        }

        @Override
        public void onFastForward() {
            eventBus.postOnMain(new SeekForwardEvent());
        }

        @Override
        public void onRewind() {
            eventBus.postOnMain(new SeekForwardEvent());
        }

        @Override
        public void onPlayFromMediaId(@NonNull String mediaId, @NonNull Bundle extras) {
            super.onPlayFromMediaId(mediaId, extras);
            if (contextReference.get() == null) return;
            Context context = contextReference.get();
            OverviewGridItem item = extras.getParcelable(DetailsFragment.ITEM_ARG);
            if (item == null) return;
            Intent detailIntent = DetailsActivity.buildIntent(context, item, DetailsFragPresenter.NO_NOTIFICATION);
            context.startActivity(detailIntent);
        }

        @Override
        public boolean onMediaButtonEvent(@NonNull Intent intent) {
            if (!intent.getAction().equals(Intent.ACTION_MEDIA_BUTTON)) return false;
            KeyEvent keyEvent = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
            if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) return false;

            if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PAUSE) {
                eventBus.post(new PausePlaybackEvent());
                return true;
            }
            if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
                viewFiltered().first().subscribe(new Action1<VideoPlayerFragment>() {
                    @Override
                    public void call(VideoPlayerFragment fragment) {
                        if (isPlaying(fragment)) {
                            eventBus.post(new PausePlaybackEvent());
                        } else {
                            eventBus.post(new StartPlaybackEvent());
                        }
                    }
                });
                return true;
            }

            return false;
        }
    }

}
import android.support.annotation.NonNull;
import javax.inject.Singleton;
import dagger.Component;
import eu.se_bastiaan.tvnl.network.NetworkModule;
import eu.se_bastiaan.tvnl.network.glide.OkHttpGlideModule;
import eu.se_bastiaan.tvnl.service.RecommendationService;
import eu.se_bastiaan.tvnl.service.recommendation.RecommendationContentProvider;
import eu.se_bastiaan.tvnl.ui.activity.DetailsActivity;
import eu.se_bastiaan.tvnl.ui.activity.MainActivity;
import eu.se_bastiaan.tvnl.ui.activity.SearchActivity;
import eu.se_bastiaan.tvnl.ui.activity.VideoPlayerActivity;
import eu.se_bastiaan.tvnl.ui.fragment.PlaybackOverlayFragment;
import eu.se_bastiaan.tvnl.ui.presenter.BrowseFragPresenter;
import eu.se_bastiaan.tvnl.ui.presenter.DetailsFragPresenter;
import eu.se_bastiaan.tvnl.ui.presenter.PlaybackOverlayFragPresenter;
import eu.se_bastiaan.tvnl.ui.presenter.SearchFragPresenter;
import eu.se_bastiaan.tvnl.ui.presenter.VideoPlayerFragPresenter;
package eu.se_bastiaan.tvnl;




@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);