Implement latest updates. (#472)
This commit is contained in:
parent
09a8a494a0
commit
0b3dda18d3
@ -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.
|
||||
|
@ -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].
|
||||
*
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
)
|
||||
|
||||
}
|
@ -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)
|
||||
|
@ -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")
|
||||
)
|
||||
|
||||
}
|
@ -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")
|
||||
)
|
||||
|
||||
}
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
@ -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 latestUpdatesSelector() = ".booklist td > div"
|
||||
|
||||
override fun popularMangaFromElement(element: Element, manga: Manga) {
|
||||
val image = element.select("dt img")
|
||||
val title = element.select("dd a:first-child")
|
||||
|
||||
manga.setUrlWithoutDomain(title.attr("href"))
|
||||
manga.title = title.text()
|
||||
manga.thumbnail_url = image.attr("src")
|
||||
}
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element, manga: Manga) {
|
||||
popularMangaFromElement(element, manga)
|
||||
}
|
||||
|
||||
override fun popularMangaNextPageSelector() = null
|
||||
|
||||
override fun latestUpdatesNextPageSelector() = null
|
||||
|
||||
override fun searchMangaInitialUrl(query: String, filters: List<Filter>) = "$baseUrl/search/?wd=$query"
|
||||
|
||||
override fun searchMangaSelector() = ".searchresult td > div"
|
||||
|
||||
override fun searchMangaFromElement(element: Element, manga: Manga) {
|
||||
val image = element.select(".resultimg img")
|
||||
val title = element.select(".resultbookname")
|
||||
|
||||
manga.setUrlWithoutDomain(title.attr("href"))
|
||||
manga.title = title.text()
|
||||
manga.thumbnail_url = image.attr("src")
|
||||
}
|
||||
|
||||
override fun searchMangaNextPageSelector() = ".pagetor a.l"
|
||||
|
||||
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()
|
||||
|
||||
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")
|
||||
|
||||
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()
|
||||
|
||||
document.select("select#page").first().select("option").forEach {
|
||||
pages.add(Page(pages.size, it.attr("value")))
|
||||
}
|
||||
}
|
||||
|
||||
override fun popularMangaNextPageSelector() = null
|
||||
override fun pageListParse(document: Document, pages: MutableList<Page>) {}
|
||||
|
||||
override fun searchMangaInitialUrl(query: String, filters: List<Filter>) = "$baseUrl/search/?wd=$query"
|
||||
|
||||
override fun searchMangaSelector() = ".searchresult td > div"
|
||||
|
||||
override fun searchMangaFromElement(element: Element, manga: Manga) {
|
||||
val image = element.select(".resultimg img")
|
||||
val title = element.select(".resultbookname")
|
||||
|
||||
manga.setUrlWithoutDomain(title.attr("href"))
|
||||
manga.title = title.text()
|
||||
manga.thumbnail_url = image.attr("src")
|
||||
}
|
||||
|
||||
override fun searchMangaNextPageSelector() = ".pagetor a.l"
|
||||
|
||||
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()
|
||||
|
||||
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")
|
||||
|
||||
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()
|
||||
|
||||
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 imageUrlParse(document: Document) = document.select("img#comicpic").first().attr("src")
|
||||
override fun imageUrlParse(document: Document) = document.select("img#comicpic").first().attr("src")
|
||||
|
||||
}
|
@ -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) = ""
|
||||
|
||||
}
|
@ -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", "яой")
|
||||
)
|
||||
|
||||
}
|
@ -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", "юри")
|
||||
)
|
||||
|
||||
}
|
@ -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.
|
||||
|
@ -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 { this@CataloguePager.lastPage = it }
|
||||
}
|
||||
|
||||
fun hasNextPage(): Boolean {
|
||||
return lastPage == null || lastPage?.nextPageUrl != null
|
||||
}
|
||||
|
||||
}
|
@ -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
app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/Pager.kt
Normal file
25
app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/Pager.kt
Normal file
@ -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>
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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 { this@LatestUpdatesPager.lastPage = it }
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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
app/src/main/res/drawable-anydpi/ic_watch_later.xml
Normal file
9
app/src/main/res/drawable-anydpi/ic_watch_later.xml
Normal file
@ -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>
|
@ -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"
|
||||
|
@ -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_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_recent_updates"
|
||||
android:icon="@drawable/ic_update_black_24dp"
|
||||
android:title="@string/label_recent_updates" />
|
||||
|
||||
<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"
|
||||
|
@ -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>
|
||||
|
@ -18,4 +18,4 @@ allprojects {
|
||||
jcenter()
|
||||
maven { url "https://jitpack.io" }
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user