add tristate library filters, update to dev

This commit is contained in:
Rani Sargees
2020-01-04 15:16:49 -05:00
97 changed files with 3348 additions and 1225 deletions

View File

@@ -60,7 +60,7 @@ class CoverCache(private val context: Context) {
return false
// Remove file.
val file = getCoverFile(thumbnailUrl!!)
val file = getCoverFile(thumbnailUrl)
return file.exists() && file.delete()
}

View File

@@ -28,7 +28,9 @@ class DownloadProvider(private val context: Context) {
* The root directory for downloads.
*/
private var downloadsDir = preferences.downloadsDirectory().getOrDefault().let {
UniFile.fromUri(context, Uri.parse(it))
val dir = UniFile.fromUri(context, Uri.parse(it))
DiskUtil.createNoMediaFile(dir, context)
dir
}
init {

View File

@@ -132,7 +132,7 @@ class DownloadService : Service() {
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ state -> onNetworkStateChanged(state)
}, { _ ->
}, {
toast(R.string.download_queue_error)
stopSelf()
})

View File

@@ -427,6 +427,8 @@ class Downloader(
if (download.status == Download.DOWNLOADED) {
tmpDir.renameTo(dirname)
cache.addChapter(dirname, mangaDir, download.manga)
DiskUtil.createNoMediaFile(tmpDir, context)
}
}

View File

@@ -150,11 +150,12 @@ class PreferencesHelper(val context: Context) {
fun downloadBadge() = rxPrefs.getBoolean(Keys.downloadBadge, false)
fun filterDownloaded() = rxPrefs.getBoolean(Keys.filterDownloaded, false)
// J2K converted from boolean to integer
fun filterDownloaded() = rxPrefs.getInteger(Keys.filterDownloaded, 0)
fun filterUnread() = rxPrefs.getBoolean(Keys.filterUnread, false)
fun filterUnread() = rxPrefs.getInteger(Keys.filterUnread, 0)
fun filterCompleted() = rxPrefs.getBoolean(Keys.filterCompleted, false)
fun filterCompleted() = rxPrefs.getInteger(Keys.filterCompleted, 0)
fun librarySortingMode() = rxPrefs.getInteger(Keys.librarySortingMode, 0)
@@ -178,6 +179,20 @@ class PreferencesHelper(val context: Context) {
fun trustedSignatures() = rxPrefs.getStringSet("trusted_signatures", emptySet())
// --> AZ J2K CHERRYPICKING
fun upgradeFilters() {
val filterDl = rxPrefs.getBoolean(Keys.filterDownloaded, false).getOrDefault()
val filterUn = rxPrefs.getBoolean(Keys.filterUnread, false).getOrDefault()
val filterCm = rxPrefs.getBoolean(Keys.filterCompleted, false).getOrDefault()
filterDownloaded().set(if (filterDl) 1 else 0)
filterUnread().set(if (filterUn) 1 else 0)
filterCompleted().set(if (filterCm) 1 else 0)
}
// <--
// --> EH
fun enableExhentai() = rxPrefs.getBoolean(Keys.eh_enableExHentai, false)

View File

@@ -45,11 +45,11 @@ class SharedPreferencesDataStore(private val prefs: SharedPreferences) : Prefere
prefs.edit().putString(key, value).apply()
}
override fun getStringSet(key: String?, defValues: MutableSet<String>?): MutableSet<String> {
override fun getStringSet(key: String?, defValues: MutableSet<String>?): MutableSet<String>? {
return prefs.getStringSet(key, defValues)
}
override fun putStringSet(key: String?, values: MutableSet<String>?) {
prefs.edit().putStringSet(key, values).apply()
}
}
}

View File

@@ -21,13 +21,15 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
private val jsonMime = MediaType.parse("application/json; charset=utf-8")
private val authClient = client.newBuilder().addInterceptor(interceptor).build()
fun addLibManga(track: Track): Observable<Track> {
val query = """
mutation AddManga(${'$'}mangaId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus) {
SaveMediaListEntry (mediaId: ${'$'}mangaId, progress: ${'$'}progress, status: ${'$'}status)
{ id status } }
"""
|mutation AddManga(${'$'}mangaId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus) {
|SaveMediaListEntry (mediaId: ${'$'}mangaId, progress: ${'$'}progress, status: ${'$'}status) {
| id
| status
|}
|}
|""".trimMargin()
val variables = jsonObject(
"mangaId" to track.media_id,
"progress" to track.last_chapter_read,
@@ -58,14 +60,14 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
fun updateLibManga(track: Track): Observable<Track> {
val query = """
mutation UpdateManga(${'$'}listId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus, ${'$'}score: Int) {
SaveMediaListEntry (id: ${'$'}listId, progress: ${'$'}progress, status: ${'$'}status, scoreRaw: ${'$'}score) {
id
status
progress
}
}
"""
|mutation UpdateManga(${'$'}listId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus, ${'$'}score: Int) {
|SaveMediaListEntry (id: ${'$'}listId, progress: ${'$'}progress, status: ${'$'}status, scoreRaw: ${'$'}score) {
|id
|status
|progress
|}
|}
|""".trimMargin()
val variables = jsonObject(
"listId" to track.library_id,
"progress" to track.last_chapter_read,
@@ -90,29 +92,29 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
fun search(search: String): Observable<List<TrackSearch>> {
val query = """
query Search(${'$'}query: String) {
Page (perPage: 50) {
media(search: ${'$'}query, type: MANGA, format_not_in: [NOVEL]) {
id
title {
romaji
}
coverImage {
large
}
type
status
chapters
description
startDate {
year
month
day
}
}
}
}
"""
|query Search(${'$'}query: String) {
|Page (perPage: 50) {
|media(search: ${'$'}query, type: MANGA, format_not_in: [NOVEL]) {
|id
|title {
|romaji
|}
|coverImage {
|large
|}
|type
|status
|chapters
|description
|startDate {
|year
|month
|day
|}
|}
|}
|}
|""".trimMargin()
val variables = jsonObject(
"query" to search
)
@@ -142,37 +144,37 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
}
fun findLibManga(track: Track, userid: Int) : Observable<Track?> {
fun findLibManga(track: Track, userid: Int): Observable<Track?> {
val query = """
query (${'$'}id: Int!, ${'$'}manga_id: Int!) {
Page {
mediaList(userId: ${'$'}id, type: MANGA, mediaId: ${'$'}manga_id) {
id
status
scoreRaw: score(format: POINT_100)
progress
media{
id
title {
romaji
}
coverImage {
large
}
type
status
chapters
description
startDate {
year
month
day
}
}
}
}
}
"""
|query (${'$'}id: Int!, ${'$'}manga_id: Int!) {
|Page {
|mediaList(userId: ${'$'}id, type: MANGA, mediaId: ${'$'}manga_id) {
|id
|status
|scoreRaw: score(format: POINT_100)
|progress
|media {
|id
|title {
|romaji
|}
|coverImage {
|large
|}
|type
|status
|chapters
|description
|startDate {
|year
|month
|day
|}
|}
|}
|}
|}
|""".trimMargin()
val variables = jsonObject(
"id" to userid,
"manga_id" to track.media_id
@@ -214,16 +216,15 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
fun getCurrentUser(): Observable<Pair<Int, String>> {
val query = """
query User
{
Viewer {
id
mediaListOptions {
scoreFormat
}
}
}
"""
|query User {
|Viewer {
|id
|mediaListOptions {
|scoreFormat
|}
|}
|}
|""".trimMargin()
val payload = jsonObject(
"query" to query
)
@@ -246,7 +247,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
}
}
fun jsonToALManga(struct: JsonObject): ALManga{
private fun jsonToALManga(struct: JsonObject): ALManga {
val date = try {
val date = Calendar.getInstance()
date.set(struct["startDate"]["year"].nullInt ?: 0, (struct["startDate"]["month"].nullInt ?: 0) - 1,
@@ -261,11 +262,10 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
date, struct["chapters"].nullInt ?: 0)
}
fun jsonToALUserManga(struct: JsonObject): ALUserManga{
return ALUserManga(struct["id"].asLong, struct["status"].asString, struct["scoreRaw"].asInt, struct["progress"].asInt, jsonToALManga(struct["media"].obj) )
private fun jsonToALUserManga(struct: JsonObject): ALUserManga {
return ALUserManga(struct["id"].asLong, struct["status"].asString, struct["scoreRaw"].asInt, struct["progress"].asInt, jsonToALManga(struct["media"].obj))
}
companion object {
private const val clientId = "385"
private const val clientUrl = "tachiyomi://anilist-auth"

View File

@@ -18,13 +18,12 @@ class KitsuSearchManga(obj: JsonObject) {
private val synopsis by obj.byString
private var startDate = obj.get("startDate").nullString?.let {
val outputDf = SimpleDateFormat("yyyy-MM-dd", Locale.US)
outputDf.format(Date(it!!.toLong() * 1000))
outputDf.format(Date(it.toLong() * 1000))
}
private val endDate = obj.get("endDate").nullString
@CallSuper
open fun toTrack() = TrackSearch.create(TrackManager.KITSU).apply {
fun toTrack() = TrackSearch.create(TrackManager.KITSU).apply {
media_id = this@KitsuSearchManga.id
title = canonicalTitle
total_chapters = chapterCount ?: 0
@@ -55,7 +54,7 @@ class KitsuLibManga(obj: JsonObject, manga: JsonObject) {
private val ratingTwenty = obj["attributes"].obj.get("ratingTwenty").nullString
val progress by obj["attributes"].byInt
open fun toTrack() = TrackSearch.create(TrackManager.KITSU).apply {
fun toTrack() = TrackSearch.create(TrackManager.KITSU).apply {
media_id = libraryId
title = canonicalTitle
total_chapters = chapterCount ?: 0

View File

@@ -112,11 +112,7 @@ class Myanimelist(private val context: Context, id: Int) : TrackService(id) {
.toCompletable()
}
// Attempt to login again if cookies have been cleared but credentials are still filled
fun ensureLoggedIn() {
if (isAuthorized) return
if (!isLogged) throw Exception("MAL Login Credentials not found")
fun refreshLogin() {
val username = getUsername()
val password = getPassword()
logout()
@@ -131,6 +127,14 @@ class Myanimelist(private val context: Context, id: Int) : TrackService(id) {
}
}
// Attempt to login again if cookies have been cleared but credentials are still filled
fun ensureLoggedIn() {
if (isAuthorized) return
if (!isLogged) throw Exception("MAL Login Credentials not found")
refreshLogin()
}
override fun logout() {
super.logout()
preferences.trackToken(this).delete()

View File

@@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.data.track.myanimelist
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.Response
import okio.Buffer
@@ -11,18 +12,27 @@ class MyAnimeListInterceptor(private val myanimelist: Myanimelist): Interceptor
override fun intercept(chain: Interceptor.Chain): Response {
myanimelist.ensureLoggedIn()
var request = chain.request()
request.body()?.let {
val request = chain.request()
var response = chain.proceed(updateRequest(request))
if (response.code() == 400){
myanimelist.refreshLogin()
response = chain.proceed(updateRequest(request))
}
return response
}
private fun updateRequest(request: Request): Request {
return request.body()?.let {
val contentType = it.contentType().toString()
val updatedBody = when {
contentType.contains("x-www-form-urlencoded") -> updateFormBody(it)
contentType.contains("json") -> updateJsonBody(it)
else -> it
}
request = request.newBuilder().post(updatedBody).build()
}
return chain.proceed(request)
request.newBuilder().post(updatedBody).build()
} ?: request
}
private fun bodyToString(requestBody: RequestBody): String {

View File

@@ -39,7 +39,7 @@ class ExtensionInstallActivity : Activity() {
}
private fun checkInstallationResult(resultCode: Int) {
val downloadId = intent.extras.getLong(ExtensionInstaller.EXTRA_DOWNLOAD_ID)
val downloadId = intent.extras!!.getLong(ExtensionInstaller.EXTRA_DOWNLOAD_ID)
val success = resultCode == RESULT_OK
val extensionManager = Injekt.get<ExtensionManager>()

View File

@@ -7,8 +7,11 @@ import android.content.IntentFilter
import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.extension.model.LoadResult
import eu.kanade.tachiyomi.util.launchNow
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
/**
* Broadcast receiver that listens for the system's packages installed, updated or removed, and only

View File

@@ -121,7 +121,7 @@ internal object ExtensionLoader {
val classLoader = PathClassLoader(appInfo.sourceDir, null, context.classLoader)
val sources = appInfo.metaData.getString(METADATA_SOURCE_CLASS)
val sources = appInfo.metaData.getString(METADATA_SOURCE_CLASS)!!
.split(";")
.map {
val sourceClass = it.trim()

View File

@@ -2,20 +2,24 @@ package eu.kanade.tachiyomi.source
import android.content.Context
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.*
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.ChapterRecognition
import eu.kanade.tachiyomi.util.ComparatorUtil.CaseInsensitiveNaturalComparator
import eu.kanade.tachiyomi.util.DiskUtil
import eu.kanade.tachiyomi.util.EpubFile
import eu.kanade.tachiyomi.util.ImageUtil
import junrar.Archive
import junrar.rarfile.FileHeader
import net.greypanther.natsort.CaseInsensitiveSimpleNaturalComparator
import rx.Observable
import timber.log.Timber
import java.io.File
import java.io.FileInputStream
import java.io.InputStream
import java.util.Comparator
import java.util.Locale
import java.util.concurrent.TimeUnit
import java.util.zip.ZipEntry
@@ -125,7 +129,6 @@ class LocalSource(private val context: Context) : CatalogueSource {
override fun fetchMangaDetails(manga: SManga) = Observable.just(manga)
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
val comparator = CaseInsensitiveSimpleNaturalComparator.getInstance<String>()
val chapters = getBaseDirectories(context)
.mapNotNull { File(it, manga.url).listFiles()?.toList() }
.flatten()
@@ -146,7 +149,7 @@ class LocalSource(private val context: Context) : CatalogueSource {
}
.sortedWith(Comparator { c1, c2 ->
val c = c2.chapter_number.compareTo(c1.chapter_number)
if (c == 0) comparator.compare(c2.name, c1.name) else c
if (c == 0) CaseInsensitiveNaturalComparator.compare(c2.name, c1.name) else c
})
return Observable.just(chapters)
@@ -189,20 +192,19 @@ class LocalSource(private val context: Context) : CatalogueSource {
private fun updateCover(chapter: SChapter, manga: SManga): File? {
val format = getFormat(chapter)
val comparator = CaseInsensitiveSimpleNaturalComparator.getInstance<String>()
return when (format) {
is Format.Directory -> {
val entry = format.file.listFiles()
.sortedWith(Comparator<File> { f1, f2 -> comparator.compare(f1.name, f2.name) })
.find { !it.isDirectory && ImageUtil.isImage(it.name, { FileInputStream(it) }) }
.sortedWith(Comparator<File> { f1, f2 -> CaseInsensitiveNaturalComparator.compare(f1.name, f2.name) })
.find { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } }
entry?.let { updateCover(context, manga, it.inputStream())}
}
is Format.Zip -> {
ZipFile(format.file).use { zip ->
val entry = zip.entries().toList()
.sortedWith(Comparator<ZipEntry> { f1, f2 -> comparator.compare(f1.name, f2.name) })
.find { !it.isDirectory && ImageUtil.isImage(it.name, { zip.getInputStream(it) }) }
.sortedWith(Comparator<ZipEntry> { f1, f2 -> CaseInsensitiveNaturalComparator.compare(f1.name, f2.name) })
.find { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } }
entry?.let { updateCover(context, manga, zip.getInputStream(it) )}
}
@@ -210,8 +212,8 @@ class LocalSource(private val context: Context) : CatalogueSource {
is Format.Rar -> {
Archive(format.file).use { archive ->
val entry = archive.fileHeaders
.sortedWith(Comparator<FileHeader> { f1, f2 -> comparator.compare(f1.fileNameString, f2.fileNameString) })
.find { !it.isDirectory && ImageUtil.isImage(it.fileNameString, { archive.getInputStream(it) }) }
.sortedWith(Comparator<FileHeader> { f1, f2 -> CaseInsensitiveNaturalComparator.compare(f1.fileNameString, f2.fileNameString) })
.find { !it.isDirectory && ImageUtil.isImage(it.fileNameString) { archive.getInputStream(it) } }
entry?.let { updateCover(context, manga, archive.getInputStream(it) )}
}

View File

@@ -13,7 +13,7 @@ class SourceDividerItemDecoration(context: Context) : RecyclerView.ItemDecoratio
init {
val a = context.obtainStyledAttributes(intArrayOf(android.R.attr.listDivider))
divider = a.getDrawable(0)
divider = a.getDrawable(0)!!
a.recycle()
}

View File

@@ -271,6 +271,7 @@ open class BrowseCatalogueController(bundle: Bundle) :
RecyclerView(view.context).apply {
id = R.id.recycler
layoutManager = LinearLayoutManager(context)
layoutParams = RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
}
} else {
@@ -441,19 +442,22 @@ open class BrowseCatalogueController(bundle: Bundle) :
adapter.onLoadMoreComplete(null)
hideProgressBar()
val message = if (error is NoResultsException) "No results found" else (error.message ?: "")
snack?.dismiss()
snack = catalogue_view?.snack(message, Snackbar.LENGTH_INDEFINITE) {
setAction(R.string.action_retry) {
// If not the first page, show bottom progress bar.
if (adapter.mainItemCount > 0) {
val item = progressItem ?: return@setAction
adapter.addScrollableFooterWithDelay(item, 0, true)
} else {
showProgressBar()
if (catalogue_view != null) {
val message = if (error is NoResultsException) catalogue_view.context.getString(R.string.no_results_found) else (error.message ?: "")
snack = catalogue_view.snack(message, Snackbar.LENGTH_INDEFINITE) {
setAction(R.string.action_retry) {
// If not the first page, show bottom progress bar.
if (adapter.mainItemCount > 0) {
val item = progressItem ?: return@setAction
adapter.addScrollableFooterWithDelay(item, 0, true)
} else {
showProgressBar()
}
presenter.requestNext()
}
presenter.requestNext()
}
}
}
@@ -602,19 +606,21 @@ open class BrowseCatalogueController(bundle: Bundle) :
adapter?.notifyItemChanged(position)
val categories = presenter.getCategories()
val defaultCategory = categories.find { it.id == preferences.defaultCategory() }
if (defaultCategory != null) {
presenter.moveMangaToCategory(manga, defaultCategory)
} else if (categories.size <= 1) { // default or the one from the user
presenter.moveMangaToCategory(manga, categories.firstOrNull())
} else {
val ids = presenter.getMangaCategoryIds(manga)
val preselected = ids.mapNotNull { id ->
categories.indexOfFirst { it.id == id }.takeIf { it != -1 }
}.toTypedArray()
val defaultCategoryId = preferences.defaultCategory()
val defaultCategory = categories.find { it.id == defaultCategoryId }
when {
defaultCategory != null -> presenter.moveMangaToCategory(manga, defaultCategory)
defaultCategoryId == 0 || categories.isEmpty() -> // 'Default' or no category
presenter.moveMangaToCategory(manga, null)
else -> {
val ids = presenter.getMangaCategoryIds(manga)
val preselected = ids.mapNotNull { id ->
categories.indexOfFirst { it.id == id }.takeIf { it != -1 }
}.toTypedArray()
ChangeMangaCategoriesDialog(this, listOf(manga), categories, preselected)
.showDialog(router)
ChangeMangaCategoriesDialog(this, listOf(manga), categories, preselected)
.showDialog(router)
}
}
activity?.toast(activity?.getString(R.string.manga_added_library))
}

View File

@@ -328,9 +328,9 @@ open class BrowseCataloguePresenter(
}
/**
* Get the default, and user categories.
* Get user categories.
*
* @return List of categories, default plus user categories
* @return List of categories, not including the default category
*/
fun getCategories(): List<Category> {
return db.getCategories().executeAsBlocking()

View File

@@ -86,4 +86,4 @@ class CatalogueNavigationView @JvmOverloads constructor(context: Context, attrs:
}
// EXH <--
}
}

View File

@@ -44,7 +44,7 @@ class CatalogueSearchAdapter(val controller: CatalogueSearchController) :
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
bundle = savedInstanceState.getBundle(HOLDER_BUNDLE_KEY)
bundle = savedInstanceState.getBundle(HOLDER_BUNDLE_KEY)!!
}
/**
@@ -81,4 +81,4 @@ class CatalogueSearchAdapter(val controller: CatalogueSearchController) :
private companion object {
const val HOLDER_BUNDLE_KEY = "holder_bundle"
}
}
}

View File

@@ -1,10 +1,14 @@
package eu.kanade.tachiyomi.ui.extension
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.SearchView
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.View
import android.view.ViewGroup
import com.jakewharton.rxbinding.support.v4.widget.refreshes
import com.jakewharton.rxbinding.support.v7.widget.queryTextChanges
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R
@@ -28,6 +32,10 @@ open class ExtensionController : NucleusController<ExtensionPresenter>(),
*/
private var adapter: FlexibleAdapter<IFlexible<*>>? = null
private var extensions: List<ExtensionItem> = emptyList()
private var query = ""
init {
setHasOptionsMenu(true)
}
@@ -84,7 +92,32 @@ open class ExtensionController : NucleusController<ExtensionPresenter>(),
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.extension_main, menu)
val searchItem = menu.findItem(R.id.action_search)
val searchView = searchItem.actionView as SearchView
searchView.maxWidth = Int.MAX_VALUE
if (!query.isEmpty()) {
searchItem.expandActionView()
searchView.setQuery(query, true)
searchView.clearFocus()
}
searchView.queryTextChanges()
.filter { router.backstack.lastOrNull()?.controller() == this }
.subscribeUntilDestroy {
query = it.toString()
drawExtensions()
}
// Fixes problem with the overflow icon showing up in lieu of search
searchItem.fixExpand()
}
override fun onItemClick(view: View, position: Int): Boolean {
// override fun onItemClick(position: Int): Boolean {
val extension = (adapter?.getItem(position) as? ExtensionItem)?.extension ?: return false
if (extension is Extension.Installed) {
openDetails(extension)
@@ -114,7 +147,19 @@ open class ExtensionController : NucleusController<ExtensionPresenter>(),
fun setExtensions(extensions: List<ExtensionItem>) {
ext_swipe_refresh?.isRefreshing = false
adapter?.updateDataSet(extensions)
this.extensions = extensions
drawExtensions()
}
fun drawExtensions() {
if (!query.isBlank()) {
adapter?.updateDataSet(
extensions.filter {
it.extension.name.contains(query, ignoreCase = true)
})
} else {
adapter?.updateDataSet(extensions)
}
}
fun downloadUpdate(item: ExtensionItem) {

View File

@@ -53,7 +53,7 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
}
override fun createPresenter(): ExtensionDetailsPresenter {
return ExtensionDetailsPresenter(args.getString(PKGNAME_KEY))
return ExtensionDetailsPresenter(args.getString(PKGNAME_KEY)!!)
}
override fun getTitle(): String? {
@@ -134,7 +134,7 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
private fun addPreferencesForSource(screen: PreferenceScreen, source: Source, multiSource: Boolean) {
val context = screen.context
// TODO
// TODO
val dataStore = SharedPreferencesDataStore(/*if (source is HttpSource) {
source.preferences
} else {*/

View File

@@ -13,7 +13,7 @@ class ExtensionDividerItemDecoration(context: Context) : RecyclerView.ItemDecora
init {
val a = context.obtainStyledAttributes(intArrayOf(android.R.attr.listDivider))
divider = a.getDrawable(0)
divider = a.getDrawable(0)!!
a.recycle()
}

View File

@@ -3,18 +3,14 @@ package eu.kanade.tachiyomi.ui.extension
import android.annotation.SuppressLint
import android.view.View
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
import kotlinx.android.synthetic.main.extension_card_header.*
import kotlinx.android.synthetic.main.extension_card_header.title
class ExtensionGroupHolder(view: View, adapter: FlexibleAdapter<*>) :
BaseFlexibleViewHolder(view, adapter) {
@SuppressLint("SetTextI18n")
fun bind(item: ExtensionGroupItem) {
title.text = when {
item.installed -> itemView.context.getString(R.string.ext_installed)
else -> itemView.context.getString(R.string.ext_available)
} + " (" + item.size + ")"
title.text = item.name
}
}

View File

@@ -8,11 +8,12 @@ import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R
/**
* Item that contains the language header.
* Item that contains the group header.
*
* @param code The lang code.
* @param name The header name.
* @param size The number of items in the group.
*/
data class ExtensionGroupItem(val installed: Boolean, val size: Int) : AbstractHeaderItem<ExtensionGroupHolder>() {
data class ExtensionGroupItem(val name: String, val size: Int) : AbstractHeaderItem<ExtensionGroupHolder>() {
/**
* Returns the layout resource of this item.
@@ -40,13 +41,13 @@ data class ExtensionGroupItem(val installed: Boolean, val size: Int) : AbstractH
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other is ExtensionGroupItem) {
return installed == other.installed
return name == other.name
}
return false
}
override fun hashCode(): Int {
return installed.hashCode()
return name.hashCode()
}
}

View File

@@ -1,10 +1,13 @@
package eu.kanade.tachiyomi.ui.extension
import android.app.Application
import android.os.Bundle
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.extension.model.InstallStep
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.util.LocaleHelper
import rx.Observable
import rx.Subscription
import rx.android.schedulers.AndroidSchedulers
@@ -49,6 +52,8 @@ open class ExtensionPresenter(
@Synchronized
private fun toItems(tuple: ExtensionTuple): List<ExtensionItem> {
val context = Injekt.get<Application>()
val (installed, untrusted, available) = tuple
val items = mutableListOf<ExtensionItem>()
@@ -62,7 +67,7 @@ open class ExtensionPresenter(
.sortedBy { it.pkgName }
if (installedSorted.isNotEmpty() || untrustedSorted.isNotEmpty()) {
val header = ExtensionGroupItem(true, installedSorted.size + untrustedSorted.size)
val header = ExtensionGroupItem(context.getString(R.string.ext_installed), installedSorted.size + untrustedSorted.size)
items += installedSorted.map { extension ->
ExtensionItem(extension, header, currentDownloads[extension.pkgName])
}
@@ -71,10 +76,17 @@ open class ExtensionPresenter(
}
}
if (availableSorted.isNotEmpty()) {
val header = ExtensionGroupItem(false, availableSorted.size)
items += availableSorted.map { extension ->
ExtensionItem(extension, header, currentDownloads[extension.pkgName])
}
val availableGroupedByLang = availableSorted
.groupBy { LocaleHelper.getDisplayName(it.lang, context) }
.toSortedMap()
availableGroupedByLang
.forEach {
val header = ExtensionGroupItem(it.key, it.value.size)
items += it.value.map { extension ->
ExtensionItem(extension, header, currentDownloads[extension.pkgName])
}
}
}
this.extensions = items

View File

@@ -24,10 +24,10 @@ class ExtensionTrustDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
.positiveText(R.string.ext_trust)
.negativeText(R.string.ext_uninstall)
.onPositive { _, _ ->
(targetController as? Listener)?.trustSignature(args.getString(SIGNATURE_KEY))
(targetController as? Listener)?.trustSignature(args.getString(SIGNATURE_KEY)!!)
}
.onNegative { _, _ ->
(targetController as? Listener)?.uninstallExtension(args.getString(PKGNAME_KEY))
(targetController as? Listener)?.uninstallExtension(args.getString(PKGNAME_KEY)!!)
}
.build()
}

View File

@@ -5,10 +5,14 @@ import android.util.AttributeSet
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.ui.catalogue.filter.TriStateItem
import eu.kanade.tachiyomi.widget.ExtendedNavigationView
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.MultiSort.Companion.SORT_ASC
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.MultiSort.Companion.SORT_DESC
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.MultiSort.Companion.SORT_NONE
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.Companion.STATE_IGNORE
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.Companion.STATE_INCLUDE
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.Companion.STATE_EXCLUDE
import uy.kohesive.injekt.injectLazy
/**
@@ -48,7 +52,7 @@ class LibraryNavigationView @JvmOverloads constructor(context: Context, attrs: A
* Returns true if there's at least one filter from [FilterGroup] active.
*/
fun hasActiveFilters(): Boolean {
return (groups[0] as FilterGroup).items.any { it.checked }
return (groups[0] as FilterGroup).items.any { it.state != STATE_IGNORE } //j2k it.checked -> this
}
/**
@@ -69,11 +73,11 @@ class LibraryNavigationView @JvmOverloads constructor(context: Context, attrs: A
*/
inner class FilterGroup : Group {
private val downloaded = Item.CheckboxGroup(R.string.action_filter_downloaded, this)
private val downloaded = Item.TriStateGroup(R.string.action_filter_downloaded, this)
private val unread = Item.CheckboxGroup(R.string.action_filter_unread, this)
private val unread = Item.TriStateGroup(R.string.action_filter_unread, this)
private val completed = Item.CheckboxGroup(R.string.completed, this)
private val completed = Item.TriStateGroup(R.string.completed, this)
override val items = listOf(downloaded, unread, completed)
@@ -81,19 +85,28 @@ class LibraryNavigationView @JvmOverloads constructor(context: Context, attrs: A
override val footer = Item.Separator()
override fun initModels() {
downloaded.checked = preferences.filterDownloaded().getOrDefault()
unread.checked = preferences.filterUnread().getOrDefault()
completed.checked = preferences.filterCompleted().getOrDefault()
override fun initModels() { //j2k changes
try {
downloaded.state = preferences.filterDownloaded().getOrDefault()
unread.state = preferences.filterUnread().getOrDefault()
completed.state = preferences.filterCompleted().getOrDefault()
} catch (e: Exception) {
preferences.upgradeFilters()
}
}
override fun onItemClicked(item: Item) {
item as Item.CheckboxGroup
item.checked = !item.checked
override fun onItemClicked(item: Item) { //j2k changes
item as Item.TriStateGroup
val newState = when (item.state) {
STATE_IGNORE -> STATE_INCLUDE
STATE_INCLUDE -> STATE_EXCLUDE
else -> STATE_IGNORE
}
item.state = newState
when (item) {
downloaded -> preferences.filterDownloaded().set(item.checked)
unread -> preferences.filterUnread().set(item.checked)
completed -> preferences.filterCompleted().set(item.checked)
downloaded -> preferences.filterDownloaded().set(item.state)
unread -> preferences.filterUnread().set(item.state)
completed -> preferences.filterCompleted().set(item.state)
}
adapter.notifyItemChanged(item)
@@ -214,4 +227,4 @@ class LibraryNavigationView @JvmOverloads constructor(context: Context, attrs: A
item.group.items.forEach { adapter.notifyItemChanged(it) }
}
}
}
}

View File

@@ -14,6 +14,9 @@ import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.Companion.STATE_EXCLUDE
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.Companion.STATE_INCLUDE
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.Companion.STATE_IGNORE
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.util.combineLatest
import eu.kanade.tachiyomi.util.isNullOrUnsubscribed
@@ -121,26 +124,22 @@ class LibraryPresenter(
val filterFn: (LibraryItem) -> Boolean = f@ { item ->
// Filter when there isn't unread chapters.
if (filterUnread && item.manga.unread == 0) {
if (filterUnread == STATE_INCLUDE && item.manga.unread == 0) {return@f false}
if (filterUnread == STATE_EXCLUDE && item.manga.unread > 0) {return@f false}
if (filterCompleted == STATE_INCLUDE && item.manga.status != SManga.COMPLETED) {
return@f false
}
if (filterCompleted && item.manga.status != SManga.COMPLETED) {
if (filterCompleted == STATE_EXCLUDE && item.manga.status == SManga.COMPLETED) {
return@f false
}
// Filter when there are no downloads.
if (filterDownloaded) {
// Local manga are always downloaded
if (item.manga.source == LocalSource.ID) {
return@f true
if (filterDownloaded != STATE_IGNORE) {
val isDownloaded = when {
item.manga.source == LocalSource.ID -> true
item.downloadCount != -1 -> item.downloadCount > 0
else -> downloadManager.getDownloadCount(item.manga) > 0
}
// Don't bother with directory checking if download count has been set.
if (item.downloadCount != -1) {
return@f item.downloadCount > 0
}
return@f downloadManager.getDownloadCount(item.manga) > 0
return@f if (filterDownloaded == STATE_INCLUDE) isDownloaded else !isDownloaded
}
true
}

View File

@@ -31,6 +31,7 @@ import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersController
import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadController
import eu.kanade.tachiyomi.ui.setting.SettingsMainController
import eu.kanade.tachiyomi.util.openInBrowser
import exh.uconfig.WarnConfigureDialogController
import exh.ui.batchadd.BatchAddController
import exh.ui.lock.LockChangeHandler
@@ -145,6 +146,9 @@ class MainActivity : BaseActivity() {
R.id.nav_drawer_settings -> {
router.pushController(SettingsMainController().withFadeTransaction())
}
R.id.nav_drawer_help -> {
openInBrowser(URL_HELP)
}
}
}
drawer.closeDrawer(GravityCompat.START)
@@ -467,6 +471,8 @@ class MainActivity : BaseActivity() {
const val INTENT_SEARCH = "eu.kanade.tachiyomi.SEARCH"
const val INTENT_SEARCH_QUERY = "query"
const val INTENT_SEARCH_FILTER = "filter"
private const val URL_HELP = "https://tachiyomi.org/help/"
}
}

View File

@@ -204,7 +204,6 @@ class MangaController : RxController, TabbedController {
}
companion object {
// EXH -->
const val UPDATE_EXTRA = "update"
const val SMART_SEARCH_CONFIG_EXTRA = "smartSearchConfig"
@@ -220,5 +219,4 @@ class MangaController : RxController, TabbedController {
.apply { isAccessible = true }
}
}

View File

@@ -235,3 +235,4 @@ class MangaInfoPresenter(
return toInsert
}
}

View File

@@ -1,9 +1,7 @@
package eu.kanade.tachiyomi.ui.manga.info
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.*
import android.webkit.WebView
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.SourceManager
@@ -16,6 +14,10 @@ class MangaWebViewController(bundle: Bundle? = null) : BaseController(bundle) {
private val sourceManager by injectLazy<SourceManager>()
init {
setHasOptionsMenu(true)
}
constructor(sourceId: Long, url: String) : this(Bundle().apply {
putLong(SOURCE_KEY, sourceId)
putString(URL_KEY, url)
@@ -43,6 +45,40 @@ class MangaWebViewController(bundle: Bundle? = null) : BaseController(bundle) {
web.loadUrl(url, headers)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.web_view, menu)
}
override fun onPrepareOptionsMenu(menu: Menu) {
val web = view as WebView
menu.findItem(R.id.action_forward).isVisible = web.canGoForward()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_forward -> {
val web = view as WebView
if (web.canGoForward()) web.goForward()
}
R.id.action_refresh -> {
val web = view as WebView
web.reload()
}
R.id.action_close -> router.popController(this)
else -> return super.onOptionsItemSelected(item)
}
return true
}
override fun handleBack(): Boolean {
val web = view as WebView
if (web.canGoBack()) {
web.goBack()
return true
}
return super.handleBack()
}
override fun onDestroyView(view: View) {
val web = view as WebView
web.stopLoading()

View File

@@ -7,7 +7,7 @@ import eu.davidea.flexibleadapter.items.AbstractHeaderItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
import kotlinx.android.synthetic.main.catalogue_main_controller_card.*
import kotlinx.android.synthetic.main.catalogue_main_controller_card.title
/**
* Item that contains the selection header.
@@ -38,7 +38,7 @@ class SelectionHeader : AbstractHeaderItem<SelectionHeader.Holder>() {
class Holder(view: View, adapter: FlexibleAdapter<*>) : BaseFlexibleViewHolder(view, adapter) {
init {
title.text = "Please select a source to migrate from"
title.text = view.context.getString(R.string.migration_selection_prompt)
}
}

View File

@@ -141,8 +141,8 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
setContentView(R.layout.reader_activity)
if (presenter.needsInit()) {
val manga = intent.extras.getLong("manga", -1)
val chapter = intent.extras.getLong("chapter", -1)
val manga = intent.extras!!.getLong("manga", -1)
val chapter = intent.extras!!.getLong("chapter", -1)
if (manga == -1L || chapter == -1L) {
finish()

View File

@@ -513,7 +513,7 @@ class ReaderPresenter(
.observeOn(AndroidSchedulers.mainThread())
.subscribeFirst(
{ view, file -> view.onShareImageResult(file) },
{ view, error -> /* Empty */ }
{ _, _ -> /* Empty */ }
)
}

View File

@@ -2,8 +2,8 @@ package eu.kanade.tachiyomi.ui.reader.loader
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
import eu.kanade.tachiyomi.util.ComparatorUtil.CaseInsensitiveNaturalComparator
import eu.kanade.tachiyomi.util.ImageUtil
import net.greypanther.natsort.CaseInsensitiveSimpleNaturalComparator
import rx.Observable
import java.io.File
import java.io.FileInputStream
@@ -18,11 +18,9 @@ class DirectoryPageLoader(val file: File) : PageLoader() {
* comparator.
*/
override fun getPages(): Observable<List<ReaderPage>> {
val comparator = CaseInsensitiveSimpleNaturalComparator.getInstance<String>()
return file.listFiles()
.filter { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } }
.sortedWith(Comparator<File> { f1, f2 -> comparator.compare(f1.name, f2.name) })
.sortedWith(Comparator<File> { f1, f2 -> CaseInsensitiveNaturalComparator.compare(f1.name, f2.name) })
.mapIndexed { i, file ->
val streamFn = { FileInputStream(file) }
ReaderPage(i).apply {

View File

@@ -32,9 +32,9 @@ class DownloadPageLoader(
return downloadManager.buildPageList(source, manga, chapter.chapter)
.map { pages ->
pages.map { page ->
ReaderPage(page.index, page.url, page.imageUrl, {
ReaderPage(page.index, page.url, page.imageUrl) {
context.contentResolver.openInputStream(page.uri)
}).apply {
}.apply {
status = Page.READY
}
}

View File

@@ -2,10 +2,10 @@ package eu.kanade.tachiyomi.ui.reader.loader
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
import eu.kanade.tachiyomi.util.ComparatorUtil.CaseInsensitiveNaturalComparator
import eu.kanade.tachiyomi.util.ImageUtil
import junrar.Archive
import junrar.rarfile.FileHeader
import net.greypanther.natsort.CaseInsensitiveSimpleNaturalComparator
import rx.Observable
import java.io.File
import java.io.InputStream
@@ -42,11 +42,9 @@ class RarPageLoader(file: File) : PageLoader() {
* comparator.
*/
override fun getPages(): Observable<List<ReaderPage>> {
val comparator = CaseInsensitiveSimpleNaturalComparator.getInstance<String>()
return archive.fileHeaders
.filter { !it.isDirectory && ImageUtil.isImage(it.fileNameString) { archive.getInputStream(it) } }
.sortedWith(Comparator<FileHeader> { f1, f2 -> comparator.compare(f1.fileNameString, f2.fileNameString) })
.sortedWith(Comparator<FileHeader> { f1, f2 -> CaseInsensitiveNaturalComparator.compare(f1.fileNameString, f2.fileNameString) })
.mapIndexed { i, header ->
val streamFn = { getStream(header) }

View File

@@ -2,8 +2,8 @@ package eu.kanade.tachiyomi.ui.reader.loader
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
import eu.kanade.tachiyomi.util.ComparatorUtil.CaseInsensitiveNaturalComparator
import eu.kanade.tachiyomi.util.ImageUtil
import net.greypanther.natsort.CaseInsensitiveSimpleNaturalComparator
import rx.Observable
import java.io.File
import java.util.zip.ZipEntry
@@ -32,11 +32,9 @@ class ZipPageLoader(file: File) : PageLoader() {
* comparator.
*/
override fun getPages(): Observable<List<ReaderPage>> {
val comparator = CaseInsensitiveSimpleNaturalComparator.getInstance<String>()
return zip.entries().toList()
.filter { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } }
.sortedWith(Comparator<ZipEntry> { f1, f2 -> comparator.compare(f1.name, f2.name) })
.sortedWith(Comparator<ZipEntry> { f1, f2 -> CaseInsensitiveNaturalComparator.compare(f1.name, f2.name) })
.mapIndexed { i, entry ->
val streamFn = { zip.getInputStream(entry) }
ReaderPage(i).apply {

View File

@@ -19,6 +19,7 @@ import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.bumptech.glide.load.resource.gif.GifDrawable
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.bumptech.glide.request.transition.NoTransition
@@ -457,6 +458,9 @@ class PagerPageHolder(
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
if (resource is GifDrawable) {
resource.setLoopCount(GifDrawable.LOOP_INTRINSIC)
}
onImageDecoded()
return false
}

View File

@@ -46,6 +46,7 @@ class PagerTransitionHolder(
* Text view used to display the text of the current and next/prev chapters.
*/
private var textView = TextView(context).apply {
textSize = 17.5F
wrapContent()
}

View File

@@ -18,6 +18,7 @@ import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.bumptech.glide.load.resource.gif.GifDrawable
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.bumptech.glide.request.transition.NoTransition
@@ -497,6 +498,9 @@ class WebtoonPageHolder(
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
if (resource is GifDrawable) {
resource.setLoopCount(GifDrawable.LOOP_INTRINSIC)
}
onImageDecoded()
return false
}

View File

@@ -62,12 +62,10 @@ open class WebtoonRecyclerView @JvmOverloads constructor(
override fun onScrollStateChanged(state: Int) {
super.onScrollStateChanged(state)
val layoutManager = layoutManager
if(layoutManager != null) {
val visibleItemCount = layoutManager.childCount
val totalItemCount = layoutManager.itemCount
atLastPosition = visibleItemCount > 0 && lastVisibleItemPosition == totalItemCount - 1
atFirstPosition = firstVisibleItemPosition == 0
}
val visibleItemCount = layoutManager?.childCount ?: 0
val totalItemCount = layoutManager?.itemCount ?: 0
atLastPosition = visibleItemCount > 0 && lastVisibleItemPosition == totalItemCount - 1
atFirstPosition = firstVisibleItemPosition == 0
}
private fun getPositionX(positionX: Float): Float {

View File

@@ -111,7 +111,7 @@ class WebtoonViewer(val activity: ReaderActivity) : BaseViewer {
recycler.longTapListener = f@ { event ->
if (activity.menuVisible || config.longTapEnabled) {
val child = recycler.findChildViewUnder(event.x, event.y)
if(child != null) {
if (child != null) {
val position = recycler.getChildAdapterPosition(child)
val item = adapter.items.getOrNull(position)
if (item is ReaderPage) {

View File

@@ -135,14 +135,14 @@ class SettingsAboutController : SettingsController() {
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog.Builder(activity!!)
.title(R.string.update_check_title)
.content(args.getString(BODY_KEY))
.content(args.getString(BODY_KEY) ?: "")
.positiveText(R.string.update_check_confirm)
.negativeText(R.string.update_check_ignore)
.onPositive { _, _ ->
val appContext = applicationContext
if (appContext != null) {
// Start download
val url = args.getString(URL_KEY)
val url = args.getString(URL_KEY) ?: ""
UpdaterService.downloadUpdate(appContext, url)
}
}

View File

@@ -17,7 +17,6 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.util.DiskUtil
import eu.kanade.tachiyomi.util.getFilePicker
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@@ -45,15 +44,6 @@ class SettingsDownloadController : SettingsController() {
.subscribeUntilDestroy { path ->
val dir = UniFile.fromUri(context, Uri.parse(path))
summary = dir.filePath ?: path
// Don't display downloaded chapters in gallery apps creating .nomedia
if (dir != null && dir.exists()) {
val nomedia = dir.findFile(".nomedia")
if (nomedia == null) {
dir.createFile(".nomedia")
applicationContext?.let { DiskUtil.scanMedia(it, dir.uri) }
}
}
}
}
switchPreference {

View File

@@ -9,6 +9,7 @@ import android.view.View
import com.afollestad.materialdialogs.MaterialDialog
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
@@ -182,15 +183,17 @@ class SettingsGeneralController : SettingsController() {
key = Keys.defaultCategory
titleRes = R.string.default_category
val selectedCategory = dbCategories.find { it.id == preferences.defaultCategory() }
val categories = listOf(Category.createDefault()) + dbCategories
val selectedCategory = categories.find { it.id == preferences.defaultCategory() }
entries = arrayOf(context.getString(R.string.default_category_summary)) +
dbCategories.map { it.name }.toTypedArray()
entryValues = arrayOf("-1") + dbCategories.map { it.id.toString() }.toTypedArray()
categories.map { it.name }.toTypedArray()
entryValues = arrayOf("-1") + categories.map { it.id.toString() }.toTypedArray()
defaultValue = "-1"
summary = selectedCategory?.name ?: context.getString(R.string.default_category_summary)
onChange { newValue ->
summary = dbCategories.find {
summary = categories.find {
it.id == (newValue as String).toInt()
}?.name ?: context.getString(R.string.default_category_summary)
true

View File

@@ -91,7 +91,7 @@ object ChapterRecognition {
* @param chapter chapter object
* @return true if volume is found
*/
fun updateChapter(match: MatchResult?, chapter: SChapter): Boolean {
private fun updateChapter(match: MatchResult?, chapter: SChapter): Boolean {
match?.let {
val initial = it.groups[1]?.value?.toFloat()!!
val subChapterDecimal = it.groups[2]?.value
@@ -109,12 +109,12 @@ object ChapterRecognition {
* @param alpha alpha value of regex
* @return decimal/alpha float value
*/
fun checkForDecimal(decimal: String?, alpha: String?): Float {
private fun checkForDecimal(decimal: String?, alpha: String?): Float {
if (!decimal.isNullOrEmpty())
return decimal?.toFloat()!!
return decimal.toFloat()
if (!alpha.isNullOrEmpty()) {
if (alpha!!.contains("extra"))
if (alpha.contains("extra"))
return .99f
if (alpha.contains("omake"))
@@ -138,7 +138,7 @@ object ChapterRecognition {
* x.a -> x.1, x.b -> x.2, etc
*/
private fun parseAlphaPostFix(alpha: Char): Float {
return ("0." + Integer.toString(alpha.toInt() - 96)).toFloat()
return ("0." + (alpha.toInt() - 96).toString()).toFloat()
}
}

View File

@@ -0,0 +1,5 @@
package eu.kanade.tachiyomi.util
object ComparatorUtil {
val CaseInsensitiveNaturalComparator = compareBy<String, String>(String.CASE_INSENSITIVE_ORDER) { it }.then(naturalOrder())
}

View File

@@ -199,11 +199,11 @@ fun Context.isServiceRunning(serviceClass: Class<*>): Boolean {
*/
fun Context.openInBrowser(url: String) {
try {
val url = Uri.parse(url)
val parsedUrl = Uri.parse(url)
val intent = CustomTabsIntent.Builder()
.setToolbarColor(getResourceColor(R.attr.colorPrimary))
.build()
intent.launchUrl(this, url)
intent.launchUrl(this, parsedUrl)
} catch (e: Exception) {
toast(e.message)
}

View File

@@ -5,5 +5,6 @@ import kotlinx.coroutines.*
fun launchUI(block: suspend CoroutineScope.() -> Unit): Job =
GlobalScope.launch(Dispatchers.Main, CoroutineStart.DEFAULT, block)
@UseExperimental(ExperimentalCoroutinesApi::class)
fun launchNow(block: suspend CoroutineScope.() -> Unit): Job =
GlobalScope.launch(Dispatchers.Main, CoroutineStart.UNDISPATCHED, block)

View File

@@ -7,6 +7,7 @@ import android.os.Build
import android.os.Environment
import android.support.v4.content.ContextCompat
import android.support.v4.os.EnvironmentCompat
import com.hippo.unifile.UniFile
import java.io.File
object DiskUtil {
@@ -54,6 +55,19 @@ object DiskUtil {
return directories
}
/**
* Don't display downloaded chapters in gallery apps creating `.nomedia`.
*/
fun createNoMediaFile(dir: UniFile?, context: Context?) {
if (dir != null && dir.exists()) {
val nomedia = dir.findFile(".nomedia")
if (nomedia == null) {
dir.createFile(".nomedia")
context?.let { scanMedia(it, dir.uri) }
}
}
}
/**
* Scans the given file so that it can be shown in gallery apps, for example.
*/

View File

@@ -77,6 +77,17 @@ open class ExtendedNavigationView @JvmOverloads constructor(
setTint(context.getResourceColor(R.attr.colorAccent))
}
}
/**
* Creates a vector tinted with the accent color.
*
* @param context any context.
* @param resId the vector resource to load and tint
*/
fun tintVector(context: Context, resId: Int, colorId: Int): Drawable {
return VectorDrawableCompat.create(context.resources, resId, context.theme)!!.apply {
setTint(context.getResourceColor(colorId))
}
}
}
/**
@@ -107,6 +118,25 @@ open class ExtendedNavigationView @JvmOverloads constructor(
}
}
class TriStateGroup(resId: Int, group: Group) : MultiStateGroup(resId, group) {
companion object {
const val STATE_IGNORE = 0
const val STATE_INCLUDE = 1
const val STATE_EXCLUDE = 2
}
override fun getStateDrawable(context: Context): Drawable? {
return when(state) {
STATE_INCLUDE -> tintVector(context, R.drawable.ic_check_box_24dp)
STATE_EXCLUDE -> tintVector(context, R.drawable.ic_check_box_x_24dp,
android.R.attr.textColorSecondary)
else -> tintVector(context, R.drawable.ic_check_box_outline_blank_24dp,
android.R.attr.textColorSecondary)
}
}
}
}
/**
@@ -236,4 +266,4 @@ open class ExtendedNavigationView @JvmOverloads constructor(
}
}
}