Browse Source

Implement latest updates. (#472)

Roman Martinez 8 years ago
parent
commit
0b3dda18d3
27 changed files with 466 additions and 87 deletions
  1. 36 0
      app/src/main/java/eu/kanade/tachiyomi/data/source/online/OnlineSource.kt
  2. 32 0
      app/src/main/java/eu/kanade/tachiyomi/data/source/online/ParsedOnlineSource.kt
  3. 29 0
      app/src/main/java/eu/kanade/tachiyomi/data/source/online/YamlOnlineSource.kt
  4. 15 0
      app/src/main/java/eu/kanade/tachiyomi/data/source/online/YamlOnlineSourceMappings.kt
  5. 27 0
      app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.kt
  6. 13 1
      app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.kt
  7. 13 0
      app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangafox.kt
  8. 14 2
      app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangahere.kt
  9. 19 0
      app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangasee.kt
  10. 19 1
      app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Readmangatoday.kt
  11. 65 53
      app/src/main/java/eu/kanade/tachiyomi/data/source/online/german/WieManga.kt
  12. 13 0
      app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mangachan.kt
  13. 13 0
      app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mintmanga.kt
  14. 13 0
      app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Readmanga.kt
  15. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt
  16. 2 15
      app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePager.kt
  17. 9 5
      app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt
  18. 25 0
      app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/Pager.kt
  19. 29 0
      app/src/main/java/eu/kanade/tachiyomi/ui/latest_updates/LatestUpdatesFragment.kt
  20. 28 0
      app/src/main/java/eu/kanade/tachiyomi/ui/latest_updates/LatestUpdatesPager.kt
  21. 26 0
      app/src/main/java/eu/kanade/tachiyomi/ui/latest_updates/LatestUpdatesPresenter.kt
  22. 3 1
      app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
  23. 9 0
      app/src/main/res/drawable-anydpi/ic_watch_later.xml
  24. 2 1
      app/src/main/res/layout/item_catalogue_grid.xml
  25. 8 5
      app/src/main/res/menu/menu_navigation.xml
  26. 2 1
      app/src/main/res/values/strings.xml
  27. 1 1
      build.gradle

+ 36 - 0
app/src/main/java/eu/kanade/tachiyomi/data/source/online/OnlineSource.kt

@@ -53,6 +53,11 @@ abstract class OnlineSource(context: Context) : Source {
      */
     abstract val lang: Language
 
+    /**
+     * If the Source has Support for Latest Updates
+     */
+    abstract val supportsLatest : Boolean
+
     /**
      * Headers used for requests.
      */
@@ -96,6 +101,17 @@ abstract class OnlineSource(context: Context) : Source {
                 page
             }
 
+    /**
+     * Returns an observable containing a page with a list of latest manga.
+     */
+    open fun fetchLatestUpdates(page: MangasPage): Observable<MangasPage> = client
+            .newCall(latestUpdatesRequest(page))
+            .asObservable()
+            .map { response ->
+                latestUpdatesParse(response, page)
+                page
+            }
+
     /**
      * Returns the request for the popular manga given the page. Override only if it's needed to
      * send different headers or request method like POST.
@@ -109,11 +125,26 @@ abstract class OnlineSource(context: Context) : Source {
         return GET(page.url, headers)
     }
 
+    /**
+     * Returns the request for latest manga given the page.
+     */
+    open protected fun latestUpdatesRequest(page: MangasPage): Request {
+        if (page.page == 1) {
+            page.url = latestUpdatesInitialUrl()
+        }
+        return GET(page.url, headers)
+    }
+
     /**
      * Returns the absolute url of the first page to popular manga.
      */
     abstract protected fun popularMangaInitialUrl(): String
 
+    /**
+     * Returns the absolute url of the first page to latest manga.
+     */
+    abstract protected fun latestUpdatesInitialUrl(): String
+
     /**
      * Parse the response from the site. It should add a list of manga and the absolute url to the
      * next page (if it has a next one) to [page].
@@ -123,6 +154,11 @@ abstract class OnlineSource(context: Context) : Source {
      */
     abstract protected fun popularMangaParse(response: Response, page: MangasPage)
 
+    /**
+     * Same as [popularMangaParse], but for latest manga.
+     */
+    abstract protected fun latestUpdatesParse(response: Response, page: MangasPage)
+
     /**
      * Returns an observable containing a page with a list of manga. Normally it's not needed to
      * override this method.

+ 32 - 0
app/src/main/java/eu/kanade/tachiyomi/data/source/online/ParsedOnlineSource.kt

@@ -37,11 +37,33 @@ abstract class ParsedOnlineSource(context: Context) : OnlineSource(context) {
         }
     }
 
+    /**
+     * Parse the response from the site for latest updates and fills [page].
+     */
+    override fun latestUpdatesParse(response: Response, page: MangasPage) {
+        val document = response.asJsoup()
+        for (element in document.select(latestUpdatesSelector())) {
+            Manga.create(id).apply {
+                latestUpdatesFromElement(element, this)
+                page.mangas.add(this)
+            }
+        }
+
+        latestUpdatesNextPageSelector()?.let { selector ->
+            page.nextPageUrl = document.select(selector).first()?.absUrl("href")
+        }
+    }
+
     /**
      * Returns the Jsoup selector that returns a list of [Element] corresponding to each manga.
      */
     abstract protected fun popularMangaSelector(): String
 
+    /**
+     * Returns the Jsoup selector similar to [popularMangaSelector], but for latest updates.
+     */
+    abstract protected fun latestUpdatesSelector(): String
+
     /**
      * Fills [manga] with the given [element]. Most sites only show the title and the url, it's
      * totally safe to fill only those two values.
@@ -51,12 +73,22 @@ abstract class ParsedOnlineSource(context: Context) : OnlineSource(context) {
      */
     abstract protected fun popularMangaFromElement(element: Element, manga: Manga)
 
+    /**
+     * Fills [manga] with the given [element]. For latest updates.
+     */
+    abstract protected fun latestUpdatesFromElement(element: Element, manga: Manga)
+
     /**
      * Returns the Jsoup selector that returns the <a> tag linking to the next page, or null if
      * there's no next page.
      */
     abstract protected fun popularMangaNextPageSelector(): String?
 
+    /**
+     * Returns the Jsoup selector that returns the <a> tag, like [popularMangaNextPageSelector].
+     */
+    abstract protected fun latestUpdatesNextPageSelector(): String?
+
     /**
      * Parse the response from the site and fills [page].
      *

+ 29 - 0
app/src/main/java/eu/kanade/tachiyomi/data/source/online/YamlOnlineSource.kt

@@ -34,6 +34,8 @@ class YamlOnlineSource(context: Context, mappings: Map<*, *>) : OnlineSource(con
         getLanguages().find { code == it.code }!!
     }
 
+    override val supportsLatest = map.supportsLatest.toBoolean()
+
     override val client = when(map.client) {
         "cloudflare" -> network.cloudflareClient
         else -> network.client
@@ -53,8 +55,20 @@ class YamlOnlineSource(context: Context, mappings: Map<*, *>) : OnlineSource(con
         }
     }
 
+    override fun latestUpdatesRequest(page: MangasPage): Request {
+        if (page.page == 1) {
+            page.url = latestUpdatesInitialUrl()
+        }
+        return when (map.latestupdates.method?.toLowerCase()) {
+            "post" -> POST(page.url, headers, map.latestupdates.createForm())
+            else -> GET(page.url, headers)
+        }
+    }
+
     override fun popularMangaInitialUrl() = map.popular.url
 
+    override fun latestUpdatesInitialUrl() = map.latestupdates.url
+
     override fun popularMangaParse(response: Response, page: MangasPage) {
         val document = response.asJsoup()
         for (element in document.select(map.popular.manga_css)) {
@@ -70,6 +84,21 @@ class YamlOnlineSource(context: Context, mappings: Map<*, *>) : OnlineSource(con
         }
     }
 
+    override fun latestUpdatesParse(response: Response, page: MangasPage) {
+        val document = response.asJsoup()
+        for (element in document.select(map.latestupdates.manga_css)) {
+            Manga.create(id).apply {
+                title = element.text()
+                setUrlWithoutDomain(element.attr("href"))
+                page.mangas.add(this)
+            }
+        }
+
+        map.popular.next_url_css?.let { selector ->
+            page.nextPageUrl = document.select(selector).first()?.absUrl("href")
+        }
+    }
+
     override fun searchMangaRequest(page: MangasPage, query: String, filters: List<Filter>): Request {
         if (page.page == 1) {
             page.url = searchMangaInitialUrl(query, filters)

+ 15 - 0
app/src/main/java/eu/kanade/tachiyomi/data/source/online/YamlOnlineSourceMappings.kt

@@ -25,11 +25,15 @@ class YamlSourceNode(uncheckedMap: Map<*, *>) {
 
     val lang: String by map
 
+    val supportsLatest: String by map
+
     val client: String?
         get() = map["client"] as? String
 
     val popular = PopularNode(toMap(map["popular"])!!)
 
+    val latestupdates = LatestUpdatesNode(toMap(map["latest_updates"])!!)
+
     val search = SearchNode(toMap(map["search"])!!)
 
     val manga = MangaNode(toMap(map["manga"])!!)
@@ -73,6 +77,17 @@ class PopularNode(override val map: Map<String, Any?>): RequestableNode {
 
 }
 
+
+class LatestUpdatesNode(override val map: Map<String, Any?>): RequestableNode {
+
+    val manga_css: String by map
+
+    val next_url_css: String?
+        get() = map["next_url_css"] as? String
+
+}
+
+
 class SearchNode(override val map: Map<String, Any?>): RequestableNode {
 
     val manga_css: String by map

+ 27 - 0
app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.kt

@@ -36,6 +36,8 @@ class Batoto(context: Context, override val id: Int) : ParsedOnlineSource(contex
 
     override val lang: Language get() = EN
 
+    override val supportsLatest = true
+
     private val datePattern = Pattern.compile("(\\d+|A|An)\\s+(.*?)s? ago.*")
 
     private val dateFields = HashMap<String, Int>().apply {
@@ -59,6 +61,8 @@ class Batoto(context: Context, override val id: Int) : ParsedOnlineSource(contex
 
     override fun popularMangaInitialUrl() = "$baseUrl/search_ajax?order_cond=views&order=desc&p=1"
 
+    override fun latestUpdatesInitialUrl() = "$baseUrl/search_ajax?order_cond=update&order=desc&p=1"
+
     override fun popularMangaParse(response: Response, page: MangasPage) {
         val document = response.asJsoup()
         for (element in document.select(popularMangaSelector())) {
@@ -73,8 +77,24 @@ class Batoto(context: Context, override val id: Int) : ParsedOnlineSource(contex
         }
     }
 
+    override fun latestUpdatesParse(response: Response, page: MangasPage) {
+        val document = response.asJsoup()
+        for (element in document.select(latestUpdatesSelector())) {
+            Manga.create(id).apply {
+                latestUpdatesFromElement(element, this)
+                page.mangas.add(this)
+            }
+        }
+
+        page.nextPageUrl = document.select(latestUpdatesNextPageSelector()).first()?.let {
+            "$baseUrl/search_ajax?order_cond=update&order=desc&p=${page.page + 1}"
+        }
+    }
+
     override fun popularMangaSelector() = "tr:has(a)"
 
+    override fun latestUpdatesSelector() = "tr:has(a)"
+
     override fun popularMangaFromElement(element: Element, manga: Manga) {
         element.select("a[href^=http://bato.to]").first().let {
             manga.setUrlWithoutDomain(it.attr("href"))
@@ -82,8 +102,14 @@ class Batoto(context: Context, override val id: Int) : ParsedOnlineSource(contex
         }
     }
 
+    override fun latestUpdatesFromElement(element: Element, manga: Manga) {
+        popularMangaFromElement(element, manga)
+    }
+
     override fun popularMangaNextPageSelector() = "#show_more_row"
 
+    override fun latestUpdatesNextPageSelector() = "#show_more_row"
+
     override fun searchMangaInitialUrl(query: String, filters: List<Filter>) = "$baseUrl/search_ajax?name=${Uri.encode(query)}&order_cond=views&order=desc&p=1&genre_cond=and&genres=${getFilterParams(filters)}"
 
     private fun getFilterParams(filters: List<Filter>): String = filters
@@ -320,4 +346,5 @@ class Batoto(context: Context, override val id: Int) : ParsedOnlineSource(contex
             Filter("29", "Yaoi"),
             Filter("31", "Yuri")
     )
+
 }

+ 13 - 1
app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.kt

@@ -27,12 +27,18 @@ class Kissmanga(context: Context, override val id: Int) : ParsedOnlineSource(con
 
     override val lang: Language get() = EN
 
-    override val client: OkHttpClient get() = network.cloudflareClient
+    override val supportsLatest = true
+
+    override val client: OkHttpClient = network.cloudflareClient
 
     override fun popularMangaInitialUrl() = "$baseUrl/MangaList/MostPopular"
 
+    override fun latestUpdatesInitialUrl() = "http://kissmanga.com/MangaList/LatestUpdate"
+
     override fun popularMangaSelector() = "table.listing tr:gt(1)"
 
+    override fun latestUpdatesSelector() = "table.listing tr:gt(1)"
+
     override fun popularMangaFromElement(element: Element, manga: Manga) {
         element.select("td a:eq(0)").first().let {
             manga.setUrlWithoutDomain(it.attr("href"))
@@ -40,8 +46,14 @@ class Kissmanga(context: Context, override val id: Int) : ParsedOnlineSource(con
         }
     }
 
+    override fun latestUpdatesFromElement(element: Element, manga: Manga) {
+        popularMangaFromElement(element, manga)
+    }
+
     override fun popularMangaNextPageSelector() = "li > a:contains(› Next)"
 
+    override fun latestUpdatesNextPageSelector(): String = "ul.pager > li > a:contains(Next)"
+
     override fun searchMangaRequest(page: MangasPage, query: String, filters: List<Filter>): Request {
         if (page.page == 1) {
             page.url = searchMangaInitialUrl(query, filters)

+ 13 - 0
app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangafox.kt

@@ -23,10 +23,16 @@ class Mangafox(context: Context, override val id: Int) : ParsedOnlineSource(cont
 
     override val lang: Language get() = EN
 
+    override val supportsLatest = true
+
     override fun popularMangaInitialUrl() = "$baseUrl/directory/"
 
+    override fun latestUpdatesInitialUrl() = "$baseUrl/directory/?latest"
+
     override fun popularMangaSelector() = "div#mangalist > ul.list > li"
 
+    override fun latestUpdatesSelector() = "div#mangalist > ul.list > li"
+
     override fun popularMangaFromElement(element: Element, manga: Manga) {
         element.select("a.title").first().let {
             manga.setUrlWithoutDomain(it.attr("href"))
@@ -34,8 +40,14 @@ class Mangafox(context: Context, override val id: Int) : ParsedOnlineSource(cont
         }
     }
 
+    override fun latestUpdatesFromElement(element: Element, manga: Manga) {
+        popularMangaFromElement(element, manga)
+    }
+
     override fun popularMangaNextPageSelector() = "a:has(span.next)"
 
+    override fun latestUpdatesNextPageSelector() = "a:has(span.next)"
+
     override fun searchMangaInitialUrl(query: String, filters: List<Filter>) =
             "$baseUrl/search.php?name_method=cw&advopts=1&order=za&sort=views&name=$query&page=1&${filters.map { it.id + "=1" }.joinToString("&")}"
 
@@ -157,4 +169,5 @@ class Mangafox(context: Context, override val id: Int) : ParsedOnlineSource(cont
             Filter("genres[Yaoi]", "Yaoi"),
             Filter("genres[Yuri]", "Yuri")
     )
+
 }

+ 14 - 2
app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangahere.kt

@@ -1,7 +1,6 @@
 package eu.kanade.tachiyomi.data.source.online.english
 
 import android.content.Context
-import android.util.Log
 import eu.kanade.tachiyomi.data.database.models.Chapter
 import eu.kanade.tachiyomi.data.database.models.Manga
 import eu.kanade.tachiyomi.data.source.EN
@@ -22,10 +21,16 @@ class Mangahere(context: Context, override val id: Int) : ParsedOnlineSource(con
 
     override val lang: Language get() = EN
 
-    override fun popularMangaInitialUrl() = "$baseUrl/directory/"
+    override val supportsLatest = true
+
+    override fun popularMangaInitialUrl() = "$baseUrl/directory/?views.za"
+
+    override fun latestUpdatesInitialUrl() = "$baseUrl/directory/?last_chapter_time.za"
 
     override fun popularMangaSelector() = "div.directory_list > ul > li"
 
+    override fun latestUpdatesSelector() = "div.directory_list > ul > li"
+
     override fun popularMangaFromElement(element: Element, manga: Manga) {
         element.select("div.title > a").first().let {
             manga.setUrlWithoutDomain(it.attr("href"))
@@ -33,8 +38,14 @@ class Mangahere(context: Context, override val id: Int) : ParsedOnlineSource(con
         }
     }
 
+    override fun latestUpdatesFromElement(element: Element, manga: Manga) {
+        popularMangaFromElement(element, manga)
+    }
+
     override fun popularMangaNextPageSelector() = "div.next-page > a.next"
 
+    override fun latestUpdatesNextPageSelector() = "div.next-page > a.next"
+
     override fun searchMangaInitialUrl(query: String, filters: List<Filter>) = "$baseUrl/search.php?name=$query&page=1&sort=views&order=za&${filters.map { it.id + "=1" }.joinToString("&")}&advopts=1"
 
     override fun searchMangaSelector() = "div.result_search > dl:has(dt)"
@@ -146,4 +157,5 @@ class Mangahere(context: Context, override val id: Int) : ParsedOnlineSource(con
             Filter("genres[Yaoi]", "Yaoi"),
             Filter("genres[Yuri]", "Yuri")
     )
+
 }

+ 19 - 0
app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangasee.kt

@@ -22,6 +22,8 @@ class Mangasee(context: Context, override val id: Int) : ParsedOnlineSource(cont
 
     override val lang: Language get() = EN
 
+    override val supportsLatest = false
+
     private val datePattern = Pattern.compile("(\\d+)\\s+(.*?)s? (from now|ago).*")
 
     private val dateFields = HashMap<String, Int>().apply {
@@ -168,4 +170,21 @@ class Mangasee(context: Context, override val id: Int) : ParsedOnlineSource(cont
             Filter("Yaoi", "Yaoi"),
             Filter("Yuri", "Yuri")
     )
+
+    override fun latestUpdatesInitialUrl(): String {
+        throw UnsupportedOperationException("not implemented")
+    }
+
+    override fun latestUpdatesNextPageSelector(): String {
+        throw UnsupportedOperationException("not implemented")
+    }
+
+    override fun latestUpdatesFromElement(element: Element, manga: Manga) {
+        throw UnsupportedOperationException("not implemented")
+    }
+
+    override fun latestUpdatesSelector(): String {
+        throw UnsupportedOperationException("not implemented")
+    }
+
 }

+ 19 - 1
app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Readmangatoday.kt

@@ -6,7 +6,6 @@ import eu.kanade.tachiyomi.data.database.models.Manga
 import eu.kanade.tachiyomi.data.network.POST
 import eu.kanade.tachiyomi.data.source.EN
 import eu.kanade.tachiyomi.data.source.Language
-import eu.kanade.tachiyomi.data.source.Source
 import eu.kanade.tachiyomi.data.source.model.MangasPage
 import eu.kanade.tachiyomi.data.source.model.Page
 import eu.kanade.tachiyomi.data.source.online.OnlineSource
@@ -26,6 +25,8 @@ class Readmangatoday(context: Context, override val id: Int) : ParsedOnlineSourc
 
     override val lang: Language get() = EN
 
+    override val supportsLatest = false
+
     override val client: OkHttpClient get() = network.cloudflareClient
 
     /**
@@ -182,4 +183,21 @@ class Readmangatoday(context: Context, override val id: Int) : ParsedOnlineSourc
             Filter("36", "Yaoi"),
             Filter("37", "Yuri")
     )
+
+    override fun latestUpdatesInitialUrl(): String {
+        throw UnsupportedOperationException("not implemented")
+    }
+
+    override fun latestUpdatesNextPageSelector(): String {
+        throw UnsupportedOperationException("not implemented")
+    }
+
+    override fun latestUpdatesFromElement(element: Element, manga: Manga) {
+        throw UnsupportedOperationException("not implemented")
+    }
+
+    override fun latestUpdatesSelector(): String {
+        throw UnsupportedOperationException("not implemented")
+    }
+
 }

+ 65 - 53
app/src/main/java/eu/kanade/tachiyomi/data/source/online/german/WieManga.kt

@@ -15,83 +15,95 @@ import java.text.SimpleDateFormat
 
 class WieManga(context: Context, override val id: Int) : ParsedOnlineSource(context) {
 
-        override val name = "Wie Manga!"
+    override val name = "Wie Manga!"
 
-        override val baseUrl = "http://www.wiemanga.com"
+    override val baseUrl = "http://www.wiemanga.com"
 
-        override val lang: Language get() = DE
+    override val lang: Language get() = DE
 
-        override fun popularMangaInitialUrl() = "$baseUrl/list/Hot-Book/"
+    override val supportsLatest = true
 
-        override fun popularMangaSelector() = ".booklist td > div"
+    override fun popularMangaInitialUrl() = "$baseUrl/list/Hot-Book/"
 
-        override fun popularMangaFromElement(element: Element, manga: Manga) {
-                val image = element.select("dt img")
-                val title = element.select("dd a:first-child")
+    override fun latestUpdatesInitialUrl() = "$baseUrl/list/New-Update/"
 
-                manga.setUrlWithoutDomain(title.attr("href"))
-                manga.title = title.text()
-                manga.thumbnail_url = image.attr("src")
-        }
+    override fun popularMangaSelector() = ".booklist td > div"
 
-        override fun popularMangaNextPageSelector() = null
+    override fun latestUpdatesSelector() = ".booklist td > div"
 
-        override fun searchMangaInitialUrl(query: String, filters: List<Filter>) = "$baseUrl/search/?wd=$query"
+    override fun popularMangaFromElement(element: Element, manga: Manga) {
+        val image = element.select("dt img")
+        val title = element.select("dd a:first-child")
 
-        override fun searchMangaSelector() = ".searchresult td > div"
+        manga.setUrlWithoutDomain(title.attr("href"))
+        manga.title = title.text()
+        manga.thumbnail_url = image.attr("src")
+    }
 
-        override fun searchMangaFromElement(element: Element, manga: Manga) {
-                val image = element.select(".resultimg img")
-                val title = element.select(".resultbookname")
+    override fun latestUpdatesFromElement(element: Element, manga: Manga) {
+        popularMangaFromElement(element, manga)
+    }
 
-                manga.setUrlWithoutDomain(title.attr("href"))
-                manga.title = title.text()
-                manga.thumbnail_url = image.attr("src")
-        }
+    override fun popularMangaNextPageSelector() = null
 
-        override fun searchMangaNextPageSelector() = ".pagetor a.l"
+    override fun latestUpdatesNextPageSelector() = null
 
-        override fun mangaDetailsParse(document: Document, manga: Manga) {
-                val imageElement = document.select(".bookmessgae tr > td:nth-child(1)").first()
-                val infoElement = document.select(".bookmessgae tr > td:nth-child(2)").first()
+    override fun searchMangaInitialUrl(query: String, filters: List<Filter>) = "$baseUrl/search/?wd=$query"
 
-                manga.author = infoElement.select("dd:nth-of-type(2) a").first()?.text()
-                manga.artist = infoElement.select("dd:nth-of-type(3) a").first()?.text()
-                manga.description = infoElement.select("dl > dt:last-child").first()?.text()?.replaceFirst("Beschreibung", "")
-                manga.thumbnail_url = imageElement.select("img").first()?.attr("src")
+    override fun searchMangaSelector() = ".searchresult td > div"
 
-                if (manga.author == "RSS")
-                        manga.author = null
+    override fun searchMangaFromElement(element: Element, manga: Manga) {
+        val image = element.select(".resultimg img")
+        val title = element.select(".resultbookname")
 
-                if (manga.artist == "RSS")
-                        manga.artist = null
-        }
+        manga.setUrlWithoutDomain(title.attr("href"))
+        manga.title = title.text()
+        manga.thumbnail_url = image.attr("src")
+    }
 
-        override fun chapterListSelector() = ".chapterlist tr:not(:first-child)"
+    override fun searchMangaNextPageSelector() = ".pagetor a.l"
 
-        override fun chapterFromElement(element: Element, chapter: Chapter) {
-                val urlElement = element.select(".col1 a").first()
-                val dateElement = element.select(".col3 a").first()
+    override fun mangaDetailsParse(document: Document, manga: Manga) {
+        val imageElement = document.select(".bookmessgae tr > td:nth-child(1)").first()
+        val infoElement = document.select(".bookmessgae tr > td:nth-child(2)").first()
 
-                chapter.setUrlWithoutDomain(urlElement.attr("href"))
-                chapter.name = urlElement.text()
-                chapter.date_upload = dateElement?.text()?.let { parseChapterDate(it) } ?: 0
-        }
+        manga.author = infoElement.select("dd:nth-of-type(2) a").first()?.text()
+        manga.artist = infoElement.select("dd:nth-of-type(3) a").first()?.text()
+        manga.description = infoElement.select("dl > dt:last-child").first()?.text()?.replaceFirst("Beschreibung", "")
+        manga.thumbnail_url = imageElement.select("img").first()?.attr("src")
 
-        private fun parseChapterDate(date: String): Long {
-                return SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(date).time
-        }
+        if (manga.author == "RSS")
+                manga.author = null
+
+        if (manga.artist == "RSS")
+                manga.artist = null
+    }
+
+    override fun chapterListSelector() = ".chapterlist tr:not(:first-child)"
+
+    override fun chapterFromElement(element: Element, chapter: Chapter) {
+        val urlElement = element.select(".col1 a").first()
+        val dateElement = element.select(".col3 a").first()
+
+        chapter.setUrlWithoutDomain(urlElement.attr("href"))
+        chapter.name = urlElement.text()
+        chapter.date_upload = dateElement?.text()?.let { parseChapterDate(it) } ?: 0
+    }
+
+    private fun parseChapterDate(date: String): Long {
+        return SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(date).time
+    }
 
-        override fun pageListParse(response: Response, pages: MutableList<Page>) {
-                val document = response.asJsoup()
+    override fun pageListParse(response: Response, pages: MutableList<Page>) {
+        val document = response.asJsoup()
 
-                document.select("select#page").first().select("option").forEach {
-                        pages.add(Page(pages.size, it.attr("value")))
-                }
+        document.select("select#page").first().select("option").forEach {
+                pages.add(Page(pages.size, it.attr("value")))
         }
+    }
 
-        override fun pageListParse(document: Document, pages: MutableList<Page>) {}
+    override fun pageListParse(document: Document, pages: MutableList<Page>) {}
 
-        override fun imageUrlParse(document: Document) = document.select("img#comicpic").first().attr("src")
+    override fun imageUrlParse(document: Document) = document.select("img#comicpic").first().attr("src")
 
 }

+ 13 - 0
app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mangachan.kt

@@ -21,12 +21,18 @@ class Mangachan(context: Context, override val id: Int) : ParsedOnlineSource(con
 
     override val lang: Language get() = RU
 
+    override val supportsLatest = true
+
     override fun popularMangaInitialUrl() = "$baseUrl/mostfavorites"
 
+    override fun latestUpdatesInitialUrl() = "$baseUrl/manga/new"
+
     override fun searchMangaInitialUrl(query: String, filters: List<Filter>) = "$baseUrl/?do=search&subaction=search&story=$query"
 
     override fun popularMangaSelector() = "div.content_row"
 
+    override fun latestUpdatesSelector() = "div.content_row"
+
     override fun popularMangaFromElement(element: Element, manga: Manga) {
         element.select("h2 > a").first().let {
             manga.setUrlWithoutDomain(it.attr("href"))
@@ -34,8 +40,14 @@ class Mangachan(context: Context, override val id: Int) : ParsedOnlineSource(con
         }
     }
 
+    override fun latestUpdatesFromElement(element: Element, manga: Manga) {
+        popularMangaFromElement(element, manga)
+    }
+
     override fun popularMangaNextPageSelector() = "a:contains(Вперед)"
 
+    override fun latestUpdatesNextPageSelector() = "a:contains(Вперед)"
+
     override fun searchMangaSelector() = popularMangaSelector()
 
     override fun searchMangaFromElement(element: Element, manga: Manga) {
@@ -91,4 +103,5 @@ class Mangachan(context: Context, override val id: Int) : ParsedOnlineSource(con
     override fun pageListParse(document: Document, pages: MutableList<Page>) { }
 
     override fun imageUrlParse(document: Document) = ""
+
 }

+ 13 - 0
app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mintmanga.kt

@@ -22,13 +22,19 @@ class Mintmanga(context: Context, override val id: Int) : ParsedOnlineSource(con
 
     override val lang: Language get() = RU
 
+    override val supportsLatest = true
+
     override fun popularMangaInitialUrl() = "$baseUrl/list?sortType=rate"
 
+    override fun latestUpdatesInitialUrl() = "$baseUrl/list?sortType=updated"
+
     override fun searchMangaInitialUrl(query: String, filters: List<Filter>) =
             "$baseUrl/search?q=$query&${filters.map { it.id + "=in" }.joinToString("&")}"
 
     override fun popularMangaSelector() = "div.desc"
 
+    override fun latestUpdatesSelector() = "div.desc"
+
     override fun popularMangaFromElement(element: Element, manga: Manga) {
         element.select("h3 > a").first().let {
             manga.setUrlWithoutDomain(it.attr("href"))
@@ -36,8 +42,14 @@ class Mintmanga(context: Context, override val id: Int) : ParsedOnlineSource(con
         }
     }
 
+    override fun latestUpdatesFromElement(element: Element, manga: Manga) {
+        popularMangaFromElement(element, manga)
+    }
+
     override fun popularMangaNextPageSelector() = "a.nextLink"
 
+    override fun latestUpdatesNextPageSelector() = "a.nextLink"
+
     override fun searchMangaSelector() = popularMangaSelector()
 
     override fun searchMangaFromElement(element: Element, manga: Manga) {
@@ -151,4 +163,5 @@ class Mintmanga(context: Context, override val id: Int) : ParsedOnlineSource(con
             Filter("el_1315", "юри"),
             Filter("el_1336", "яой")
     )
+
 }

+ 13 - 0
app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Readmanga.kt

@@ -22,13 +22,19 @@ class Readmanga(context: Context, override val id: Int) : ParsedOnlineSource(con
 
     override val lang: Language get() = RU
 
+    override val supportsLatest = true
+
     override fun popularMangaInitialUrl() = "$baseUrl/list?sortType=rate"
 
+    override fun latestUpdatesInitialUrl() = "$baseUrl/list?sortType=updated"
+
     override fun searchMangaInitialUrl(query: String, filters: List<Filter>) =
             "$baseUrl/search?q=$query&${filters.map { it.id + "=in" }.joinToString("&")}"
 
     override fun popularMangaSelector() = "div.desc"
 
+    override fun latestUpdatesSelector() = "div.desc"
+
     override fun popularMangaFromElement(element: Element, manga: Manga) {
         element.select("h3 > a").first().let {
             manga.setUrlWithoutDomain(it.attr("href"))
@@ -36,8 +42,14 @@ class Readmanga(context: Context, override val id: Int) : ParsedOnlineSource(con
         }
     }
 
+    override fun latestUpdatesFromElement(element: Element, manga: Manga) {
+        popularMangaFromElement(element, manga)
+    }
+
     override fun popularMangaNextPageSelector() = "a.nextLink"
 
+    override fun latestUpdatesNextPageSelector() = "a.nextLink"
+
     override fun searchMangaSelector() = popularMangaSelector()
 
     override fun searchMangaFromElement(element: Element, manga: Manga) {
@@ -152,4 +164,5 @@ class Readmanga(context: Context, override val id: Int) : ParsedOnlineSource(con
             Filter("el_2149", "этти"),
             Filter("el_2123", "юри")
     )
+
 }

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt

@@ -41,7 +41,7 @@ import java.util.concurrent.TimeUnit.MILLISECONDS
  * Uses R.layout.fragment_catalogue.
  */
 @RequiresPresenter(CataloguePresenter::class)
-class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleViewHolder.OnListItemClickListener {
+open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleViewHolder.OnListItemClickListener {
 
     /**
      * Spinner shown in the toolbar to change the selected source.

+ 2 - 15
app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePager.kt

@@ -4,19 +4,10 @@ import eu.kanade.tachiyomi.data.source.model.MangasPage
 import eu.kanade.tachiyomi.data.source.online.OnlineSource
 import eu.kanade.tachiyomi.data.source.online.OnlineSource.Filter
 import rx.Observable
-import rx.subjects.PublishSubject
 
-class CataloguePager(val source: OnlineSource, val query: String, val filters: List<Filter>) {
+open class CataloguePager(val source: OnlineSource, val query: String, val filters: List<Filter>): Pager() {
 
-    private var lastPage: MangasPage? = null
-
-    private val results = PublishSubject.create<MangasPage>()
-
-    fun results(): Observable<MangasPage> {
-        return results.asObservable()
-    }
-
-    fun requestNext(transformer: (Observable<MangasPage>) -> Observable<MangasPage>): Observable<MangasPage> {
+    override fun requestNext(transformer: (Observable<MangasPage>) -> Observable<MangasPage>): Observable<MangasPage> {
         val lastPage = lastPage
 
         val page = if (lastPage == null)
@@ -34,8 +25,4 @@ class CataloguePager(val source: OnlineSource, val query: String, val filters: L
                 .doOnNext { [email protected] = it }
     }
 
-    fun hasNextPage(): Boolean {
-        return lastPage == null || lastPage?.nextPageUrl != null
-    }
-
 }

+ 9 - 5
app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt

@@ -26,7 +26,7 @@ import java.util.NoSuchElementException
 /**
  * Presenter of [CatalogueFragment].
  */
-class CataloguePresenter : BasePresenter<CatalogueFragment>() {
+open class CataloguePresenter : BasePresenter<CatalogueFragment>() {
 
     /**
      * Source manager.
@@ -73,7 +73,7 @@ class CataloguePresenter : BasePresenter<CatalogueFragment>() {
     /**
      * Pager containing a list of manga results.
      */
-    private lateinit var pager: CataloguePager
+    private lateinit var pager: Pager
 
     /**
      * Subject that initializes a list of manga.
@@ -140,7 +140,7 @@ class CataloguePresenter : BasePresenter<CatalogueFragment>() {
         }
 
         // Create a new pager.
-        pager = CataloguePager(source, query, filters)
+        pager = createPager(query, filters)
 
         // Prepare the pager.
         pagerSubscription?.let { remove(it) }
@@ -305,7 +305,7 @@ class CataloguePresenter : BasePresenter<CatalogueFragment>() {
      * @param source the source to check.
      * @return true if the source is valid, false otherwise.
      */
-    fun isValidSource(source: Source?): Boolean {
+    open fun isValidSource(source: Source?): Boolean {
         if (source == null) return false
 
         if (source is LoginSource) {
@@ -327,7 +327,7 @@ class CataloguePresenter : BasePresenter<CatalogueFragment>() {
     /**
      * Returns a list of enabled sources ordered by language and name.
      */
-    private fun getEnabledSources(): List<OnlineSource> {
+    open protected fun getEnabledSources(): List<OnlineSource> {
         val languages = prefs.enabledLanguages().getOrDefault()
         val hiddenCatalogues = prefs.hiddenCatalogues().getOrDefault()
 
@@ -371,4 +371,8 @@ class CataloguePresenter : BasePresenter<CatalogueFragment>() {
         restartPager(filters = selectedFilters)
     }
 
+    open fun createPager(query: String, filters: List<Filter>): Pager {
+        return CataloguePager(source, query, filters)
+    }
+
 }

+ 25 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/Pager.kt

@@ -0,0 +1,25 @@
+package eu.kanade.tachiyomi.ui.catalogue
+
+import eu.kanade.tachiyomi.data.source.model.MangasPage
+import rx.subjects.PublishSubject
+import rx.Observable
+
+/**
+ * A general pager for source requests (latest updates, popular, search)
+ */
+abstract class Pager {
+
+    protected var lastPage: MangasPage? = null
+
+    protected val results = PublishSubject.create<MangasPage>()
+
+    fun results(): Observable<MangasPage> {
+        return results.asObservable()
+    }
+
+    fun hasNextPage(): Boolean {
+        return lastPage == null || lastPage?.nextPageUrl != null
+    }
+
+    abstract fun requestNext(transformer: (Observable<MangasPage>) -> Observable<MangasPage>): Observable<MangasPage>
+}

+ 29 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/latest_updates/LatestUpdatesFragment.kt

@@ -0,0 +1,29 @@
+package eu.kanade.tachiyomi.ui.latest_updates
+
+import eu.kanade.tachiyomi.ui.catalogue.CatalogueFragment
+import nucleus.factory.RequiresPresenter
+import android.view.*
+import eu.kanade.tachiyomi.R
+
+/**
+ * Fragment that shows the manga from the catalogue. Inherit CatalogueFragment.
+ */
+@RequiresPresenter(LatestUpdatesPresenter::class)
+class LatestUpdatesFragment : CatalogueFragment() {
+
+    override fun onPrepareOptionsMenu(menu: Menu) {
+        super.onPrepareOptionsMenu(menu)
+        menu.findItem(R.id.action_search).isVisible = false
+        menu.findItem(R.id.action_set_filter).isVisible = false
+
+    }
+
+    companion object {
+
+        fun newInstance(): LatestUpdatesFragment {
+            return LatestUpdatesFragment()
+        }
+
+    }
+
+}

+ 28 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/latest_updates/LatestUpdatesPager.kt

@@ -0,0 +1,28 @@
+package eu.kanade.tachiyomi.ui.latest_updates
+
+import eu.kanade.tachiyomi.data.source.model.MangasPage
+import eu.kanade.tachiyomi.data.source.online.OnlineSource
+import eu.kanade.tachiyomi.ui.catalogue.Pager
+import rx.Observable
+
+/**
+ * LatestUpdatesPager inherited from the general Pager.
+ */
+class LatestUpdatesPager(val source: OnlineSource): Pager() {
+
+    override fun requestNext(transformer: (Observable<MangasPage>) -> Observable<MangasPage>): Observable<MangasPage> {
+        val lastPage = lastPage
+
+        val page = if (lastPage == null)
+            MangasPage(1)
+        else
+            MangasPage(lastPage.page + 1).apply { url = lastPage.nextPageUrl!! }
+
+        val observable = source.fetchLatestUpdates(page)
+
+        return transformer(observable)
+                .doOnNext { results.onNext(it) }
+                .doOnNext { [email protected] = it }
+    }
+
+}

+ 26 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/latest_updates/LatestUpdatesPresenter.kt

@@ -0,0 +1,26 @@
+package eu.kanade.tachiyomi.ui.latest_updates
+
+import eu.kanade.tachiyomi.data.source.Source
+import eu.kanade.tachiyomi.data.source.online.OnlineSource
+import eu.kanade.tachiyomi.ui.catalogue.CataloguePresenter
+import eu.kanade.tachiyomi.ui.catalogue.Pager
+import eu.kanade.tachiyomi.data.source.online.OnlineSource.Filter
+
+/**
+ * Presenter of [LatestUpdatesFragment]. Inherit CataloguePresenter.
+ */
+class LatestUpdatesPresenter : CataloguePresenter() {
+
+    override fun createPager(query: String, filters: List<Filter>): Pager {
+        return LatestUpdatesPager(source)
+    }
+
+    override fun getEnabledSources(): List<OnlineSource> {
+        return super.getEnabledSources().filter { it.supportsLatest }
+    }
+
+    override fun isValidSource(source: Source?): Boolean {
+        return super.isValidSource(source) && (source as OnlineSource).supportsLatest
+    }
+
+}

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

@@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.ui.backup.BackupFragment
 import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
 import eu.kanade.tachiyomi.ui.catalogue.CatalogueFragment
+import eu.kanade.tachiyomi.ui.latest_updates.LatestUpdatesFragment
 import eu.kanade.tachiyomi.ui.download.DownloadFragment
 import eu.kanade.tachiyomi.ui.library.LibraryFragment
 import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersFragment
@@ -58,9 +59,10 @@ class MainActivity : BaseActivity() {
             val id = item.itemId
             when (id) {
                 R.id.nav_drawer_library -> setFragment(LibraryFragment.newInstance(), id)
-                R.id.nav_drawer_recently_read -> setFragment(RecentlyReadFragment.newInstance(), id)
                 R.id.nav_drawer_recent_updates -> setFragment(RecentChaptersFragment.newInstance(), id)
+                R.id.nav_drawer_recently_read -> setFragment(RecentlyReadFragment.newInstance(), id)
                 R.id.nav_drawer_catalogues -> setFragment(CatalogueFragment.newInstance(), id)
+                R.id.nav_drawer_latest_updates -> setFragment(LatestUpdatesFragment.newInstance(), id)
                 R.id.nav_drawer_downloads -> setFragment(DownloadFragment.newInstance(), id)
                 R.id.nav_drawer_settings -> {
                     val intent = Intent(this, SettingsActivity::class.java)

+ 9 - 0
app/src/main/res/drawable-anydpi/ic_watch_later.xml

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M12,2C6.5,2 2,6.5 2,12s4.5,10 10,10 10,-4.5 10,-10S17.5,2 12,2zM16.2,16.2L11,13L11,7h1.5v5.2l4.5,2.7 -0.8,1.3z"/>
+</vector>

+ 2 - 1
app/src/main/res/layout/item_catalogue_grid.xml

@@ -19,7 +19,8 @@
             android:layout_height="match_parent"
             android:background="?android:attr/colorBackground"
             tools:background="?android:attr/colorBackground"
-            tools:src="@mipmap/ic_launcher" />
+            tools:src="@mipmap/ic_launcher"
+            tools:ignore="ContentDescription" />
 
         <View
             android:id="@+id/gradient"

+ 8 - 5
app/src/main/res/menu/menu_navigation.xml

@@ -7,19 +7,22 @@
             android:id="@+id/nav_drawer_library"
             android:icon="@drawable/ic_book_black_24dp"
             android:title="@string/label_library" />
-        <item
-            android:id="@+id/nav_drawer_recently_read"
-            android:icon="@drawable/ic_glasses_black_24dp"
-            android:title="@string/label_recent_manga"/>
         <item
             android:id="@+id/nav_drawer_recent_updates"
             android:icon="@drawable/ic_update_black_24dp"
             android:title="@string/label_recent_updates" />
-
+        <item
+            android:id="@+id/nav_drawer_recently_read"
+            android:icon="@drawable/ic_glasses_black_24dp"
+            android:title="@string/label_recent_manga"/>
         <item
             android:id="@+id/nav_drawer_catalogues"
             android:icon="@drawable/ic_explore_black_24dp"
             android:title="@string/label_catalogues" />
+        <item
+            android:id="@+id/nav_drawer_latest_updates"
+            android:icon="@drawable/ic_watch_later"
+            android:title="@string/label_latest_updates" />
         <item
             android:id="@+id/nav_drawer_downloads"
             android:icon="@drawable/ic_file_download_black_24dp"

+ 2 - 1
app/src/main/res/values/strings.xml

@@ -8,8 +8,9 @@
     <string name="label_download_queue">Download queue</string>
     <string name="label_library">My library</string>
     <string name="label_recent_manga">Recently read</string>
-    <string name="label_recent_updates">Recent updates</string>
     <string name="label_catalogues">Catalogues</string>
+    <string name="label_recent_updates">Library updates</string>
+    <string name="label_latest_updates">Latest updates</string>
     <string name="label_categories">Categories</string>
     <string name="label_selected">Selected: %1$d</string>
     <string name="label_backup">Backup</string>

+ 1 - 1
build.gradle

@@ -18,4 +18,4 @@ allprojects {
         jcenter()
         maven { url "https://jitpack.io" }
     }
-}
+}