Ver código fonte

Initial support for recent updates. #20

inorichi 9 anos atrás
pai
commit
522e900b5a
21 arquivos alterados com 466 adições e 55 exclusões
  1. 9 16
      app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.java
  2. 1 1
      app/src/main/java/eu/kanade/tachiyomi/data/database/DbOpenHelper.java
  3. 12 0
      app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapter.java
  4. 1 1
      app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaSync.java
  5. 49 0
      app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterGetResolver.java
  6. 0 23
      app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaWithUnreadGetResolver.java
  7. 0 1
      app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.java
  8. 3 1
      app/src/main/java/eu/kanade/tachiyomi/injection/component/AppComponent.java
  9. 1 1
      app/src/main/java/eu/kanade/tachiyomi/injection/module/DataModule.java
  10. 5 5
      app/src/main/java/eu/kanade/tachiyomi/ui/decoration/DividerItemDecoration.java
  11. 5 3
      app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.java
  12. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/HorizontalPager.java
  13. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalPager.java
  14. 102 0
      app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersAdapter.java
  15. 76 0
      app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersFragment.java
  16. 42 0
      app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersHolder.java
  17. 78 0
      app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersPresenter.java
  18. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAccountsFragment.java
  19. 18 0
      app/src/main/res/layout/fragment_recent_chapters.xml
  20. 47 0
      app/src/main/res/layout/item_recent_chapter.xml
  21. 14 0
      app/src/main/res/layout/item_recent_chapter_section.xml

+ 9 - 16
app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.java

@@ -27,10 +27,12 @@ import eu.kanade.tachiyomi.data.database.models.ChapterSQLiteTypeMapping;
 import eu.kanade.tachiyomi.data.database.models.Manga;
 import eu.kanade.tachiyomi.data.database.models.MangaCategory;
 import eu.kanade.tachiyomi.data.database.models.MangaCategorySQLiteTypeMapping;
+import eu.kanade.tachiyomi.data.database.models.MangaChapter;
 import eu.kanade.tachiyomi.data.database.models.MangaSQLiteTypeMapping;
 import eu.kanade.tachiyomi.data.database.models.MangaSync;
 import eu.kanade.tachiyomi.data.database.models.MangaSyncSQLiteTypeMapping;
 import eu.kanade.tachiyomi.data.database.resolvers.LibraryMangaGetResolver;
+import eu.kanade.tachiyomi.data.database.resolvers.MangaChapterGetResolver;
 import eu.kanade.tachiyomi.data.database.tables.CategoryTable;
 import eu.kanade.tachiyomi.data.database.tables.ChapterTable;
 import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable;
@@ -160,23 +162,14 @@ public class DatabaseHelper {
                 .prepare();
     }
 
-    public PreparedGetListOfObjects<Chapter> getChapters(long manga_id, boolean sortAToZ, boolean onlyUnread) {
-        Query.CompleteBuilder query = Query.builder()
-                .table(ChapterTable.TABLE)
-
-                .orderBy(ChapterTable.COLUMN_CHAPTER_NUMBER + (sortAToZ ? " ASC" : " DESC"));
-
-        if (onlyUnread) {
-            query = query.where(ChapterTable.COLUMN_MANGA_ID + "=? AND " + ChapterTable.COLUMN_READ + "=?")
-                    .whereArgs(manga_id, 0);
-        } else {
-            query = query.where(ChapterTable.COLUMN_MANGA_ID + "=?")
-                    .whereArgs(manga_id);
-        }
-
+    public PreparedGetListOfObjects<MangaChapter> getRecentChapters() {
         return db.get()
-                .listOfObjects(Chapter.class)
-                .withQuery(query.build())
+                .listOfObjects(MangaChapter.class)
+                .withQuery(RawQuery.builder()
+                        .query(MangaChapterGetResolver.RECENT_CHAPTERS_QUERY)
+                        .observesTables(ChapterTable.TABLE)
+                        .build())
+                .withGetResolver(MangaChapterGetResolver.INSTANCE)
                 .prepare();
     }
 

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/data/database/DbOpenHelper.java

@@ -6,9 +6,9 @@ import android.database.sqlite.SQLiteOpenHelper;
 import android.support.annotation.NonNull;
 
 import eu.kanade.tachiyomi.data.database.tables.CategoryTable;
+import eu.kanade.tachiyomi.data.database.tables.ChapterTable;
 import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable;
 import eu.kanade.tachiyomi.data.database.tables.MangaSyncTable;
-import eu.kanade.tachiyomi.data.database.tables.ChapterTable;
 import eu.kanade.tachiyomi.data.database.tables.MangaTable;
 
 public class DbOpenHelper extends SQLiteOpenHelper {

+ 12 - 0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapter.java

@@ -0,0 +1,12 @@
+package eu.kanade.tachiyomi.data.database.models;
+
+public class MangaChapter {
+
+    public Manga manga;
+    public Chapter chapter;
+
+    public MangaChapter(Manga manga, Chapter chapter) {
+        this.manga = manga;
+        this.chapter = chapter;
+    }
+}

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaSync.java

@@ -5,8 +5,8 @@ import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteType;
 
 import java.io.Serializable;
 
-import eu.kanade.tachiyomi.data.mangasync.base.MangaSyncService;
 import eu.kanade.tachiyomi.data.database.tables.MangaSyncTable;
+import eu.kanade.tachiyomi.data.mangasync.base.MangaSyncService;
 
 @StorIOSQLiteType(table = MangaSyncTable.TABLE)
 public class MangaSync implements Serializable {

+ 49 - 0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaChapterGetResolver.java

@@ -0,0 +1,49 @@
+package eu.kanade.tachiyomi.data.database.resolvers;
+
+import android.database.Cursor;
+import android.support.annotation.NonNull;
+
+import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver;
+
+import eu.kanade.tachiyomi.data.database.models.Chapter;
+import eu.kanade.tachiyomi.data.database.models.ChapterStorIOSQLiteGetResolver;
+import eu.kanade.tachiyomi.data.database.models.Manga;
+import eu.kanade.tachiyomi.data.database.models.MangaChapter;
+import eu.kanade.tachiyomi.data.database.models.MangaStorIOSQLiteGetResolver;
+import eu.kanade.tachiyomi.data.database.tables.ChapterTable;
+import eu.kanade.tachiyomi.data.database.tables.MangaTable;
+
+public class MangaChapterGetResolver extends DefaultGetResolver<MangaChapter> {
+
+    public static final MangaChapterGetResolver INSTANCE = new MangaChapterGetResolver();
+
+    public static final String QUERY = String.format(
+            "SELECT * FROM %1$s JOIN %2$s on %1$s.%3$s = %2$s.%4$s",
+            MangaTable.TABLE,
+            ChapterTable.TABLE,
+            MangaTable.COLUMN_ID,
+            ChapterTable.COLUMN_MANGA_ID);
+
+    public static final String RECENT_CHAPTERS_QUERY = String.format(
+            QUERY + " ORDER BY %1$s DESC LIMIT 100", ChapterTable.COLUMN_DATE_UPLOAD);
+
+    @NonNull
+    private final MangaStorIOSQLiteGetResolver mangaGetResolver;
+
+    @NonNull
+    private final ChapterStorIOSQLiteGetResolver chapterGetResolver;
+
+    public MangaChapterGetResolver() {
+        this.mangaGetResolver = new MangaStorIOSQLiteGetResolver();
+        this.chapterGetResolver = new ChapterStorIOSQLiteGetResolver();
+    }
+
+    @NonNull
+    @Override
+    public MangaChapter mapFromCursor(@NonNull Cursor cursor) {
+        final Manga manga = mangaGetResolver.mapFromCursor(cursor);
+        final Chapter chapter = chapterGetResolver.mapFromCursor(cursor);
+
+        return new MangaChapter(manga, chapter);
+    }
+}

+ 0 - 23
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaWithUnreadGetResolver.java

@@ -1,23 +0,0 @@
-package eu.kanade.tachiyomi.data.database.resolvers;
-
-import android.database.Cursor;
-import android.support.annotation.NonNull;
-
-import eu.kanade.tachiyomi.data.database.models.Manga;
-import eu.kanade.tachiyomi.data.database.models.MangaStorIOSQLiteGetResolver;
-import eu.kanade.tachiyomi.data.database.tables.MangaTable;
-
-public class MangaWithUnreadGetResolver extends MangaStorIOSQLiteGetResolver {
-
-    public static final MangaWithUnreadGetResolver INSTANCE = new MangaWithUnreadGetResolver();
-
-    @Override
-    @NonNull
-    public Manga mapFromCursor(@NonNull Cursor cursor) {
-        Manga manga = super.mapFromCursor(cursor);
-        int unreadColumn = cursor.getColumnIndex(MangaTable.COLUMN_UNREAD);
-        manga.unread = cursor.getInt(unreadColumn);
-        return manga;
-    }
-
-}

+ 0 - 1
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.java

@@ -26,7 +26,6 @@ import eu.kanade.tachiyomi.data.source.base.Source;
 import eu.kanade.tachiyomi.data.source.model.Page;
 import eu.kanade.tachiyomi.event.DownloadChaptersEvent;
 import eu.kanade.tachiyomi.util.DiskUtils;
-import eu.kanade.tachiyomi.util.DynamicConcurrentMergeOperator;
 import rx.Observable;
 import rx.Subscription;
 import rx.android.schedulers.AndroidSchedulers;

+ 3 - 1
app/src/main/java/eu/kanade/tachiyomi/injection/component/AppComponent.java

@@ -14,8 +14,8 @@ import eu.kanade.tachiyomi.injection.module.AppModule;
 import eu.kanade.tachiyomi.injection.module.DataModule;
 import eu.kanade.tachiyomi.ui.catalogue.CataloguePresenter;
 import eu.kanade.tachiyomi.ui.download.DownloadPresenter;
-import eu.kanade.tachiyomi.ui.library.category.CategoryPresenter;
 import eu.kanade.tachiyomi.ui.library.LibraryPresenter;
+import eu.kanade.tachiyomi.ui.library.category.CategoryPresenter;
 import eu.kanade.tachiyomi.ui.manga.MangaActivity;
 import eu.kanade.tachiyomi.ui.manga.MangaPresenter;
 import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersPresenter;
@@ -23,6 +23,7 @@ import eu.kanade.tachiyomi.ui.manga.info.MangaInfoPresenter;
 import eu.kanade.tachiyomi.ui.manga.myanimelist.MyAnimeListPresenter;
 import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
 import eu.kanade.tachiyomi.ui.reader.ReaderPresenter;
+import eu.kanade.tachiyomi.ui.recent.RecentChaptersPresenter;
 import eu.kanade.tachiyomi.ui.setting.SettingsAccountsFragment;
 import eu.kanade.tachiyomi.ui.setting.SettingsActivity;
 
@@ -44,6 +45,7 @@ public interface AppComponent {
     void inject(DownloadPresenter downloadPresenter);
     void inject(MyAnimeListPresenter myAnimeListPresenter);
     void inject(CategoryPresenter categoryPresenter);
+    void inject(RecentChaptersPresenter recentChaptersPresenter);
 
     void inject(ReaderActivity readerActivity);
     void inject(MangaActivity mangaActivity);

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/injection/module/DataModule.java

@@ -8,9 +8,9 @@ import dagger.Module;
 import dagger.Provides;
 import eu.kanade.tachiyomi.data.cache.ChapterCache;
 import eu.kanade.tachiyomi.data.cache.CoverCache;
-import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager;
 import eu.kanade.tachiyomi.data.database.DatabaseHelper;
 import eu.kanade.tachiyomi.data.download.DownloadManager;
+import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager;
 import eu.kanade.tachiyomi.data.network.NetworkHelper;
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper;
 import eu.kanade.tachiyomi.data.source.SourceManager;

+ 5 - 5
app/src/main/java/eu/kanade/tachiyomi/ui/decoration/DividerItemDecoration.java

@@ -2,13 +2,13 @@ package eu.kanade.tachiyomi.ui.decoration;
 
 import android.content.Context;
 import android.content.res.TypedArray;
-import android.util.AttributeSet;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.LinearLayoutManager;
-import android.view.View;
+import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.graphics.Canvas;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.view.View;
 
 public class DividerItemDecoration extends RecyclerView.ItemDecoration {
 

+ 5 - 3
app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.java

@@ -19,6 +19,7 @@ import eu.kanade.tachiyomi.ui.base.activity.BaseActivity;
 import eu.kanade.tachiyomi.ui.catalogue.CatalogueFragment;
 import eu.kanade.tachiyomi.ui.download.DownloadFragment;
 import eu.kanade.tachiyomi.ui.library.LibraryFragment;
+import eu.kanade.tachiyomi.ui.recent.RecentChaptersFragment;
 import eu.kanade.tachiyomi.ui.setting.SettingsActivity;
 import icepick.State;
 import nucleus.view.ViewWithPresenter;
@@ -71,9 +72,9 @@ public class MainActivity extends BaseActivity {
                         new PrimaryDrawerItem()
                                 .withName(R.string.label_library)
                                 .withIdentifier(R.id.nav_drawer_library),
-//                        new PrimaryDrawerItem()
-//                                .withName(R.string.recent_updates_title)
-//                                .withIdentifier(R.id.nav_drawer_recent_updates),
+                        new PrimaryDrawerItem()
+                                .withName(R.string.label_recent_updates)
+                                .withIdentifier(R.id.nav_drawer_recent_updates),
                         new PrimaryDrawerItem()
                                 .withName(R.string.label_catalogues)
                                 .withIdentifier(R.id.nav_drawer_catalogues),
@@ -95,6 +96,7 @@ public class MainActivity extends BaseActivity {
                                         setFragment(LibraryFragment.newInstance());
                                         break;
                                     case R.id.nav_drawer_recent_updates:
+                                        setFragment(RecentChaptersFragment.newInstance());
                                         break;
                                     case R.id.nav_drawer_catalogues:
                                         setFragment(CatalogueFragment.newInstance());

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/horizontal/HorizontalPager.java

@@ -8,8 +8,8 @@ import android.view.MotionEvent;
 
 import eu.kanade.tachiyomi.ui.reader.viewer.base.OnChapterBoundariesOutListener;
 import eu.kanade.tachiyomi.ui.reader.viewer.base.OnChapterSingleTapListener;
-import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerGestureListener;
 import eu.kanade.tachiyomi.ui.reader.viewer.pager.Pager;
+import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerGestureListener;
 import rx.functions.Action1;
 
 public class HorizontalPager extends ViewPager implements Pager {

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/vertical/VerticalPager.java

@@ -7,8 +7,8 @@ import android.view.MotionEvent;
 
 import eu.kanade.tachiyomi.ui.reader.viewer.base.OnChapterBoundariesOutListener;
 import eu.kanade.tachiyomi.ui.reader.viewer.base.OnChapterSingleTapListener;
-import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerGestureListener;
 import eu.kanade.tachiyomi.ui.reader.viewer.pager.Pager;
+import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerGestureListener;
 import rx.functions.Action1;
 
 public class VerticalPager extends VerticalViewPagerImpl implements Pager {

+ 102 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersAdapter.java

@@ -0,0 +1,102 @@
+package eu.kanade.tachiyomi.ui.recent;
+
+import android.support.v7.widget.RecyclerView;
+import android.text.format.DateFormat;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import java.util.Date;
+import java.util.List;
+
+import eu.davidea.flexibleadapter.FlexibleAdapter;
+import eu.kanade.tachiyomi.R;
+import eu.kanade.tachiyomi.data.database.models.MangaChapter;
+
+public class RecentChaptersAdapter extends FlexibleAdapter<RecyclerView.ViewHolder, Object> {
+
+    private RecentChaptersFragment fragment;
+
+    private static final int CHAPTER = 0;
+    private static final int SECTION = 1;
+
+    public RecentChaptersAdapter(RecentChaptersFragment fragment) {
+        this.fragment = fragment;
+        setHasStableIds(true);
+    }
+
+    @Override
+    public long getItemId(int position) {
+        Object item = getItem(position);
+        if (item instanceof MangaChapter)
+            return ((MangaChapter) item).chapter.id;
+        else
+            return item.hashCode();
+    }
+
+    public void setItems(List<Object> items) {
+        mItems = items;
+        notifyDataSetChanged();
+    }
+
+    @Override
+    public void updateDataSet(String param) {
+
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        return getItem(position) instanceof MangaChapter ? CHAPTER : SECTION;
+    }
+
+    @Override
+    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+        View v;
+        switch (viewType) {
+            case CHAPTER:
+                v = inflater.inflate(R.layout.item_recent_chapter, parent, false);
+                return new RecentChaptersHolder(v, this, fragment);
+            case SECTION:
+                v = inflater.inflate(R.layout.item_recent_chapter_section, parent, false);
+                return new SectionViewHolder(v);
+        }
+        return null;
+    }
+
+    @Override
+    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+        switch (holder.getItemViewType()) {
+            case CHAPTER:
+                final MangaChapter chapter = (MangaChapter) getItem(position);
+                ((RecentChaptersHolder) holder).onSetValues(chapter);
+                break;
+            case SECTION:
+                final Date date = (Date) getItem(position);
+                ((SectionViewHolder) holder).onSetValues(date);
+        }
+
+        //When user scrolls this bind the correct selection status
+        holder.itemView.setActivated(isSelected(position));
+    }
+
+    public RecentChaptersFragment getFragment() {
+        return fragment;
+    }
+
+    private static class SectionViewHolder extends RecyclerView.ViewHolder {
+
+        private TextView view;
+
+        public SectionViewHolder(View view) {
+            super(view);
+            this.view = (TextView) view;
+        }
+
+        public void onSetValues(Date date) {
+            String s = DateFormat.getDateFormat(view.getContext()).format(date);
+            view.setText(s);
+        }
+    }
+}

+ 76 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersFragment.java

@@ -0,0 +1,76 @@
+package eu.kanade.tachiyomi.ui.recent;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.List;
+
+import butterknife.Bind;
+import butterknife.ButterKnife;
+import eu.kanade.tachiyomi.R;
+import eu.kanade.tachiyomi.data.database.models.MangaChapter;
+import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder;
+import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment;
+import eu.kanade.tachiyomi.ui.decoration.DividerItemDecoration;
+import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
+import nucleus.factory.RequiresPresenter;
+
+@RequiresPresenter(RecentChaptersPresenter.class)
+public class RecentChaptersFragment extends BaseRxFragment<RecentChaptersPresenter> implements FlexibleViewHolder.OnListItemClickListener {
+
+    @Bind(R.id.chapter_list) RecyclerView recyclerView;
+
+    private RecentChaptersAdapter adapter;
+
+    public static RecentChaptersFragment newInstance() {
+        return new RecentChaptersFragment();
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
+        // Inflate the layout for this fragment
+        View view = inflater.inflate(R.layout.fragment_recent_chapters, container, false);
+        ButterKnife.bind(this, view);
+
+        // Init RecyclerView and adapter
+        recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
+        recyclerView.addItemDecoration(new DividerItemDecoration(ContextCompat.getDrawable(
+                getContext(), R.drawable.line_divider)));
+        recyclerView.setHasFixedSize(true);
+        adapter = new RecentChaptersAdapter(this);
+        recyclerView.setAdapter(adapter);
+
+        setToolbarTitle(R.string.label_recent_updates);
+        return view;
+    }
+
+    public void onNextMangaChapters(List<Object> chapters) {
+        adapter.setItems(chapters);
+    }
+
+    @Override
+    public boolean onListItemClick(int position) {
+        Object item = adapter.getItem(position);
+        if (item instanceof MangaChapter) {
+            openChapter((MangaChapter) item);
+        }
+        return false;
+    }
+
+    @Override
+    public void onListItemLongClick(int position) {
+
+    }
+
+    protected void openChapter(MangaChapter chapter) {
+        getPresenter().onOpenChapter(chapter);
+        Intent intent = ReaderActivity.newIntent(getActivity());
+        startActivity(intent);
+    }
+}

+ 42 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersHolder.java

@@ -0,0 +1,42 @@
+package eu.kanade.tachiyomi.ui.recent;
+
+import android.support.v4.content.ContextCompat;
+import android.view.View;
+import android.widget.TextView;
+
+import butterknife.Bind;
+import butterknife.ButterKnife;
+import eu.kanade.tachiyomi.R;
+import eu.kanade.tachiyomi.data.database.models.MangaChapter;
+import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder;
+
+public class RecentChaptersHolder extends FlexibleViewHolder {
+
+    @Bind(R.id.chapter_title) TextView chapterTitle;
+    @Bind(R.id.manga_title) TextView mangaTitle;
+
+    private final int readColor;
+    private final int unreadColor;
+
+    public RecentChaptersHolder(View view, RecentChaptersAdapter adapter, OnListItemClickListener onListItemClickListener) {
+        super(view, adapter, onListItemClickListener);
+        ButterKnife.bind(this, view);
+
+        readColor = ContextCompat.getColor(view.getContext(), R.color.hint_text);
+        unreadColor = ContextCompat.getColor(view.getContext(), R.color.primary_text);
+    }
+
+    public void onSetValues(MangaChapter item) {
+        chapterTitle.setText(item.chapter.name);
+        mangaTitle.setText(item.manga.title);
+
+        if (item.chapter.read) {
+            chapterTitle.setTextColor(readColor);
+            mangaTitle.setTextColor(readColor);
+        } else {
+            chapterTitle.setTextColor(unreadColor);
+            mangaTitle.setTextColor(unreadColor);
+        }
+    }
+
+}

+ 78 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/recent/RecentChaptersPresenter.java

@@ -0,0 +1,78 @@
+package eu.kanade.tachiyomi.ui.recent;
+
+import android.os.Bundle;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.inject.Inject;
+
+import de.greenrobot.event.EventBus;
+import eu.kanade.tachiyomi.data.database.DatabaseHelper;
+import eu.kanade.tachiyomi.data.database.models.MangaChapter;
+import eu.kanade.tachiyomi.data.source.SourceManager;
+import eu.kanade.tachiyomi.data.source.base.Source;
+import eu.kanade.tachiyomi.event.ReaderEvent;
+import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
+import rx.Observable;
+import rx.android.schedulers.AndroidSchedulers;
+
+public class RecentChaptersPresenter extends BasePresenter<RecentChaptersFragment> {
+
+    @Inject DatabaseHelper db;
+    @Inject SourceManager sourceManager;
+
+    private static final int GET_RECENT_CHAPTERS = 1;
+
+    @Override
+    protected void onCreate(Bundle savedState) {
+        super.onCreate(savedState);
+
+        restartableLatestCache(GET_RECENT_CHAPTERS,
+                this::getRecentChaptersObservable,
+                RecentChaptersFragment::onNextMangaChapters);
+
+        if (savedState == null)
+            start(GET_RECENT_CHAPTERS);
+    }
+
+    private Observable<List<Object>> getRecentChaptersObservable() {
+        return db.getRecentChapters().createObservable()
+                // group chapters by the date they were fetched on a ordered map
+                .flatMap(recents -> Observable.from(recents)
+                        .toMultimap(
+                                recent -> getMapKey(recent.chapter.date_fetch),
+                                recent -> recent,
+                                () -> new TreeMap<>((d1, d2) -> d2.compareTo(d1))))
+                // add every day and all its chapters to a single list
+                .map(recents -> {
+                    List<Object> items = new ArrayList<>();
+                    for (Map.Entry<Date, Collection<MangaChapter>> recent : recents.entrySet()) {
+                        items.add(recent.getKey());
+                        items.addAll(recent.getValue());
+                    }
+                    return items;
+                })
+                .observeOn(AndroidSchedulers.mainThread());
+    }
+
+    private Date getMapKey(long date) {
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(new Date(date));
+        cal.set(Calendar.HOUR_OF_DAY, 0);
+        cal.set(Calendar.MINUTE, 0);
+        cal.set(Calendar.SECOND, 0);
+        cal.set(Calendar.MILLISECOND, 0);
+        return cal.getTime();
+    }
+
+    public void onOpenChapter(MangaChapter item) {
+        Source source = sourceManager.get(item.manga.source);
+        EventBus.getDefault().postSticky(new ReaderEvent(source, item.manga, item.chapter));
+    }
+}

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAccountsFragment.java

@@ -12,8 +12,8 @@ import java.util.List;
 import javax.inject.Inject;
 
 import eu.kanade.tachiyomi.App;
-import eu.kanade.tachiyomi.data.mangasync.base.MangaSyncService;
 import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager;
+import eu.kanade.tachiyomi.data.mangasync.base.MangaSyncService;
 import eu.kanade.tachiyomi.data.source.SourceManager;
 import eu.kanade.tachiyomi.data.source.base.Source;
 import eu.kanade.tachiyomi.widget.preference.MangaSyncLoginDialog;

+ 18 - 0
app/src/main/res/layout/fragment_recent_chapters.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                xmlns:tools="http://schemas.android.com/tools"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:orientation="vertical">
+
+    <android.support.v7.widget.RecyclerView
+        android:id="@+id/chapter_list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:descendantFocusability="blocksDescendants"
+        android:background="@color/white"
+        tools:listitem="@layout/item_recent_chapter">
+
+    </android.support.v7.widget.RecyclerView>
+
+</RelativeLayout>

+ 47 - 0
app/src/main/res/layout/item_recent_chapter.xml

@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                xmlns:tools="http://schemas.android.com/tools"
+                android:layout_width="fill_parent"
+                android:layout_height="?android:attr/listPreferredItemHeight"
+                android:background="@drawable/selector_chapter_light">
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+        android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
+        android:paddingRight="?android:attr/listPreferredItemPaddingRight"
+        android:paddingStart="?android:attr/listPreferredItemPaddingStart">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginRight="30dp"
+            android:layout_alignParentLeft="true"
+            android:layout_alignParentStart="true"
+            android:layout_centerVertical="true"
+            android:orientation="vertical">
+
+            <TextView
+                android:id="@+id/manga_title"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:ellipsize="end"
+                android:textAppearance="@style/TextAppearance.AppCompat.Medium"
+                android:singleLine="true"
+                tools:text="My manga"/>
+
+            <TextView
+                android:id="@+id/chapter_title"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:ellipsize="end"
+                android:textAppearance="@style/TextAppearance.AppCompat.Small"
+                android:maxLines="1"
+                tools:text="Title"/>
+
+        </LinearLayout>
+
+    </RelativeLayout>
+
+</RelativeLayout>

+ 14 - 0
app/src/main/res/layout/item_recent_chapter_section.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+          android:layout_width="match_parent"
+          android:layout_height="48dp"
+          android:gravity="center_vertical"
+          android:paddingLeft="24dp"
+          android:singleLine="true"
+          android:textAllCaps="true"
+          android:textColor="@color/colorAccent"
+          android:background="@android:color/transparent"
+          android:textSize="16sp"
+          android:id="@+id/section_text"
+          android:textStyle="bold" />