Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Jay 2020-01-09 18:15:42 -08:00
commit b1d221c117
175 changed files with 827 additions and 1222 deletions

26
.gitattributes vendored
View File

@ -1,2 +1,28 @@
* text=auto * text=auto
* text eol=lf * text eol=lf
# Windows forced line-endings
/.idea/* text eol=crlf
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.mov binary
*.mp4 binary
*.mp3 binary
*.flv binary
*.fla binary
*.swf binary
*.gz binary
*.zip binary
*.7z binary
*.ttf binary
*.eot binary
*.woff binary
*.pyc binary
*.pdf binary
*.ez binary
*.bz2 binary
*.swp binary

View File

@ -3,7 +3,7 @@ language: android
android: android:
components: components:
- build-tools-29.0.2 - build-tools-29.0.2
- android-28 - android-29
- extra-android-m2repository - extra-android-m2repository
- extra-google-m2repository - extra-google-m2repository
- extra-android-support - extra-android-support
@ -11,7 +11,7 @@ android:
licenses: licenses:
- android-sdk-license-.+ - android-sdk-license-.+
before_install: before_install:
- yes | sdkmanager "platforms;android-28" # workaround for accepting the license - yes | sdkmanager "platforms;android-29" # workaround for accepting the license
- if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then - if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then
openssl aes-256-cbc -K $encrypted_e56be693d4fd_key -iv $encrypted_e56be693d4fd_iv -in "$PWD/.travis/secrets.tar.enc" -out secrets.tar -d; openssl aes-256-cbc -K $encrypted_e56be693d4fd_key -iv $encrypted_e56be693d4fd_iv -in "$PWD/.travis/secrets.tar.enc" -out secrets.tar -d;
tar xf secrets.tar; tar xf secrets.tar;

View File

@ -1,5 +1,5 @@
# ![app icon](./.github/readme-images/app-icon.png)Tachiyomi # ![app icon](./.github/readme-images/app-icon.png)Tachiyomi
Tachiyomi is a free and open source manga reader for Android. Tachiyomi is a free and open source manga reader for Android 5.0 and above.
![screenshots of app](./.github/readme-images/theming-screenshots.gif) ![screenshots of app](./.github/readme-images/theming-screenshots.gif)

View File

@ -28,7 +28,8 @@
android:networkSecurityConfig="@xml/network_security_config"> android:networkSecurityConfig="@xml/network_security_config">
<activity <activity
android:name=".ui.main.MainActivity" android:name=".ui.main.MainActivity"
android:launchMode="singleTask"> android:launchMode="singleTask"
android:theme="@style/Theme.Splash">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />

View File

@ -31,7 +31,7 @@ import uy.kohesive.injekt.registry.default.DefaultRegistrar
reportType = org.acra.sender.HttpSender.Type.JSON, reportType = org.acra.sender.HttpSender.Type.JSON,
httpMethod = org.acra.sender.HttpSender.Method.PUT, httpMethod = org.acra.sender.HttpSender.Method.PUT,
buildConfigClass = BuildConfig::class, buildConfigClass = BuildConfig::class,
excludeMatchingSharedPreferencesKeys = arrayOf(".*username.*", ".*password.*", ".*token.*") excludeMatchingSharedPreferencesKeys = [".*username.*", ".*password.*", ".*token.*"]
) )
open class App : Application(), LifecycleObserver { open class App : Application(), LifecycleObserver {

View File

@ -35,6 +35,7 @@ import eu.kanade.tachiyomi.util.syncChaptersWithSource
import rx.Observable import rx.Observable
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import kotlin.math.max
class BackupManager(val context: Context, version: Int = CURRENT_VERSION) { class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
@ -204,7 +205,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
if (options and BACKUP_CHAPTER_MASK == BACKUP_CHAPTER) { if (options and BACKUP_CHAPTER_MASK == BACKUP_CHAPTER) {
// Backup all the chapters // Backup all the chapters
val chapters = databaseHelper.getChapters(manga).executeAsBlocking() val chapters = databaseHelper.getChapters(manga).executeAsBlocking()
if (!chapters.isEmpty()) { if (chapters.isNotEmpty()) {
val chaptersJson = parser.toJsonTree(chapters) val chaptersJson = parser.toJsonTree(chapters)
if (chaptersJson.asJsonArray.size() > 0) { if (chaptersJson.asJsonArray.size() > 0) {
entry[CHAPTERS] = chaptersJson entry[CHAPTERS] = chaptersJson
@ -216,7 +217,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
if (options and BACKUP_CATEGORY_MASK == BACKUP_CATEGORY) { if (options and BACKUP_CATEGORY_MASK == BACKUP_CATEGORY) {
// Backup categories for this manga // Backup categories for this manga
val categoriesForManga = databaseHelper.getCategoriesForManga(manga).executeAsBlocking() val categoriesForManga = databaseHelper.getCategoriesForManga(manga).executeAsBlocking()
if (!categoriesForManga.isEmpty()) { if (categoriesForManga.isNotEmpty()) {
val categoriesNames = categoriesForManga.map { it.name } val categoriesNames = categoriesForManga.map { it.name }
entry[CATEGORIES] = parser.toJsonTree(categoriesNames) entry[CATEGORIES] = parser.toJsonTree(categoriesNames)
} }
@ -225,7 +226,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
// Check if user wants track information in backup // Check if user wants track information in backup
if (options and BACKUP_TRACK_MASK == BACKUP_TRACK) { if (options and BACKUP_TRACK_MASK == BACKUP_TRACK) {
val tracks = databaseHelper.getTracks(manga).executeAsBlocking() val tracks = databaseHelper.getTracks(manga).executeAsBlocking()
if (!tracks.isEmpty()) { if (tracks.isNotEmpty()) {
entry[TRACK] = parser.toJsonTree(tracks) entry[TRACK] = parser.toJsonTree(tracks)
} }
} }
@ -233,7 +234,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
// Check if user wants history information in backup // Check if user wants history information in backup
if (options and BACKUP_HISTORY_MASK == BACKUP_HISTORY) { if (options and BACKUP_HISTORY_MASK == BACKUP_HISTORY) {
val historyForManga = databaseHelper.getHistoryByMangaId(manga.id!!).executeAsBlocking() val historyForManga = databaseHelper.getHistoryByMangaId(manga.id!!).executeAsBlocking()
if (!historyForManga.isEmpty()) { if (historyForManga.isNotEmpty()) {
val historyData = historyForManga.mapNotNull { history -> val historyData = historyForManga.mapNotNull { history ->
val url = databaseHelper.getChapter(history.chapter_id).executeAsBlocking()?.url val url = databaseHelper.getChapter(history.chapter_id).executeAsBlocking()?.url
url?.let { DHistory(url, history.last_read) } url?.let { DHistory(url, history.last_read) }
@ -344,7 +345,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
} }
// Update database // Update database
if (!mangaCategoriesToUpdate.isEmpty()) { if (mangaCategoriesToUpdate.isNotEmpty()) {
val mangaAsList = ArrayList<Manga>() val mangaAsList = ArrayList<Manga>()
mangaAsList.add(manga) mangaAsList.add(manga)
databaseHelper.deleteOldMangasCategories(mangaAsList).executeAsBlocking() databaseHelper.deleteOldMangasCategories(mangaAsList).executeAsBlocking()
@ -365,7 +366,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
// Check if history already in database and update // Check if history already in database and update
if (dbHistory != null) { if (dbHistory != null) {
dbHistory.apply { dbHistory.apply {
last_read = Math.max(lastRead, dbHistory.last_read) last_read = max(lastRead, dbHistory.last_read)
} }
historyToBeUpdated.add(dbHistory) historyToBeUpdated.add(dbHistory)
} else { } else {
@ -408,7 +409,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
if (track.library_id != dbTrack.library_id) { if (track.library_id != dbTrack.library_id) {
dbTrack.library_id = track.library_id dbTrack.library_id = track.library_id
} }
dbTrack.last_chapter_read = Math.max(dbTrack.last_chapter_read, track.last_chapter_read) dbTrack.last_chapter_read = max(dbTrack.last_chapter_read, track.last_chapter_read)
isInDatabase = true isInDatabase = true
trackToUpdate.add(dbTrack) trackToUpdate.add(dbTrack)
break break
@ -422,7 +423,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
} }
} }
// Update database // Update database
if (!trackToUpdate.isEmpty()) { if (trackToUpdate.isNotEmpty()) {
databaseHelper.insertTracks(trackToUpdate).executeAsBlocking() databaseHelper.insertTracks(trackToUpdate).executeAsBlocking()
} }
} }

View File

@ -43,9 +43,7 @@ object ChapterTypeAdapter {
beginObject() beginObject()
while (hasNext()) { while (hasNext()) {
if (peek() == JsonToken.NAME) { if (peek() == JsonToken.NAME) {
val name = nextName() when (nextName()) {
when (name) {
URL -> chapter.url = nextString() URL -> chapter.url = nextString()
READ -> chapter.read = nextInt() == 1 READ -> chapter.read = nextInt() == 1
BOOKMARK -> chapter.bookmark = nextInt() == 1 BOOKMARK -> chapter.bookmark = nextInt() == 1
@ -58,4 +56,4 @@ object ChapterTypeAdapter {
} }
} }
} }
} }

View File

@ -42,9 +42,7 @@ object TrackTypeAdapter {
beginObject() beginObject()
while (hasNext()) { while (hasNext()) {
if (peek() == JsonToken.NAME) { if (peek() == JsonToken.NAME) {
val name = nextName() when (nextName()) {
when (name) {
TITLE -> track.title = nextString() TITLE -> track.title = nextString()
SYNC -> track.sync_id = nextInt() SYNC -> track.sync_id = nextInt()
MEDIA -> track.media_id = nextInt() MEDIA -> track.media_id = nextInt()
@ -59,4 +57,4 @@ object TrackTypeAdapter {
} }
} }
} }
} }

View File

@ -266,7 +266,7 @@ class DownloadCache(
for (element in this) { for (element in this) {
val (key, value) = transform(element) val (key, value) = transform(element)
if (key != null) { if (key != null) {
destination.put(key, value) destination[key] = value
} }
} }
return destination return destination

View File

@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.data.download
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import com.hippo.unifile.UniFile import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
@ -46,9 +47,13 @@ class DownloadProvider(private val context: Context) {
* @param source the source of the manga. * @param source the source of the manga.
*/ */
internal fun getMangaDir(manga: Manga, source: Source): UniFile { internal fun getMangaDir(manga: Manga, source: Source): UniFile {
return downloadsDir try {
.createDirectory(getSourceDirName(source)) return downloadsDir
.createDirectory(getMangaDirName(manga)) .createDirectory(getSourceDirName(source))
.createDirectory(getMangaDirName(manga))
} catch (e: NullPointerException) {
throw Exception(context.getString(R.string.invalid_download_dir))
}
} }
/** /**

View File

@ -108,7 +108,7 @@ class Downloader(
pending.forEach { if (it.status != Download.QUEUE) it.status = Download.QUEUE } pending.forEach { if (it.status != Download.QUEUE) it.status = Download.QUEUE }
downloadsRelay.call(pending) downloadsRelay.call(pending)
return !pending.isEmpty() return pending.isNotEmpty()
} }
/** /**

View File

@ -48,4 +48,4 @@ class LibraryUpdateJob : Job() {
JobManager.instance().cancelAllForTag(TAG) JobManager.instance().cancelAllForTag(TAG)
} }
} }
} }

View File

@ -257,7 +257,6 @@ class LibraryUpdateService(
else else
db.getLibraryMangas().executeAsBlocking().distinctBy { it.id } db.getLibraryMangas().executeAsBlocking().distinctBy { it.id }
} }
if (target == Target.CHAPTERS && preferences.updateOnlyNonCompleted()) { if (target == Target.CHAPTERS && preferences.updateOnlyNonCompleted()) {
listToUpdate = listToUpdate.filter { it.status != SManga.COMPLETED } listToUpdate = listToUpdate.filter { it.status != SManga.COMPLETED }
} }

View File

@ -57,8 +57,8 @@ abstract class TrackService(val id: Int) {
} }
open val isLogged: Boolean open val isLogged: Boolean
get() = !getUsername().isEmpty() && get() = getUsername().isNotEmpty() &&
!getPassword().isEmpty() getPassword().isNotEmpty()
fun getUsername() = preferences.trackUsername(this)!! fun getUsername() = preferences.trackUsername(this)!!

View File

@ -52,7 +52,7 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
} }
} }
override fun getLogo() = R.drawable.al override fun getLogo() = R.drawable.anilist
override fun getLogoColor() = Color.rgb(18, 25, 35) override fun getLogoColor() = Color.rgb(18, 25, 35)

View File

@ -17,6 +17,7 @@ import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import rx.Observable import rx.Observable
import java.util.Calendar import java.util.Calendar
@ -45,7 +46,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
"query" to query, "query" to query,
"variables" to variables "variables" to variables
) )
val body = RequestBody.create(jsonMime, payload.toString()) val body = payload.toString().toRequestBody(jsonMime)
val request = Request.Builder() val request = Request.Builder()
.url(apiUrl) .url(apiUrl)
.post(body) .post(body)
@ -84,7 +85,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
"query" to query, "query" to query,
"variables" to variables "variables" to variables
) )
val body = RequestBody.create(jsonMime, payload.toString()) val body = payload.toString().toRequestBody(jsonMime)
val request = Request.Builder() val request = Request.Builder()
.url(apiUrl) .url(apiUrl)
.post(body) .post(body)
@ -128,7 +129,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
"query" to query, "query" to query,
"variables" to variables "variables" to variables
) )
val body = RequestBody.create(jsonMime, payload.toString()) val body = payload.toString().toRequestBody(jsonMime)
val request = Request.Builder() val request = Request.Builder()
.url(apiUrl) .url(apiUrl)
.post(body) .post(body)
@ -189,7 +190,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
"query" to query, "query" to query,
"variables" to variables "variables" to variables
) )
val body = RequestBody.create(jsonMime, payload.toString()) val body = payload.toString().toRequestBody(jsonMime)
val request = Request.Builder() val request = Request.Builder()
.url(apiUrl) .url(apiUrl)
.post(body) .post(body)
@ -234,7 +235,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
val payload = jsonObject( val payload = jsonObject(
"query" to query "query" to query
) )
val body = RequestBody.create(jsonMime, payload.toString()) val body = payload.toString().toRequestBody(jsonMime)
val request = Request.Builder() val request = Request.Builder()
.url(apiUrl) .url(apiUrl)
.post(body) .post(body)

View File

@ -35,7 +35,7 @@ class BangumiInterceptor(val bangumi: Bangumi, val gson: Gson) : Interceptor {
} }
} }
var authRequest = if (originalRequest.method == "GET") originalRequest.newBuilder() val authRequest = if (originalRequest.method == "GET") originalRequest.newBuilder()
.header("User-Agent", "Tachiyomi") .header("User-Agent", "Tachiyomi")
.url(originalRequest.url.newBuilder() .url(originalRequest.url.newBuilder()
.addQueryParameter("access_token", currAuth.access_token).build()) .addQueryParameter("access_token", currAuth.access_token).build())

View File

@ -9,7 +9,7 @@ data class OAuth(
val user_id: Long? val user_id: Long?
) { ) {
// Access token refersh before expired // Access token refresh before expired
fun isExpired() = (System.currentTimeMillis() / 1000) > (created_at + expires_in - 3600) fun isExpired() = (System.currentTimeMillis() / 1000) > (created_at + expires_in - 3600)
} }

View File

@ -30,7 +30,7 @@ class Myanimelist(private val context: Context, id: Int) : TrackService(id) {
} }
private val interceptor by lazy { MyAnimeListInterceptor(this) } private val interceptor by lazy { MyAnimeListInterceptor(this) }
private val api by lazy { MyanimelistApi(client, interceptor) } private val api by lazy { MyAnimeListApi(client, interceptor) }
override val name: String override val name: String
get() = "MyAnimeList" get() = "MyAnimeList"

View File

@ -12,7 +12,10 @@ import eu.kanade.tachiyomi.util.selectInt
import eu.kanade.tachiyomi.util.selectText import eu.kanade.tachiyomi.util.selectText
import okhttp3.* import okhttp3.*
import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import org.json.JSONObject import org.json.JSONObject
import org.jsoup.Jsoup import org.jsoup.Jsoup
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
@ -24,7 +27,7 @@ import java.io.InputStreamReader
import java.util.zip.GZIPInputStream import java.util.zip.GZIPInputStream
class MyanimelistApi(private val client: OkHttpClient, interceptor: MyAnimeListInterceptor) { class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListInterceptor) {
private val authClient = client.newBuilder().addInterceptor(interceptor).build() private val authClient = client.newBuilder().addInterceptor(interceptor).build()
@ -35,8 +38,7 @@ class MyanimelistApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.flatMap { Observable.from(it) } .flatMap { Observable.from(it) }
.filter { it.title.contains(realQuery, true) } .filter { it.title.contains(realQuery, true) }
.toList() .toList()
} } else {
else {
client.newCall(GET(searchUrl(query))) client.newCall(GET(searchUrl(query)))
.asObservable() .asObservable()
.flatMap { response -> .flatMap { response ->
@ -264,7 +266,7 @@ class MyanimelistApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.put("score", track.score) .put("score", track.score)
.put("num_read_chapters", track.last_chapter_read) .put("num_read_chapters", track.last_chapter_read)
return RequestBody.create("application/json; charset=utf-8".toMediaTypeOrNull(), body.toString()) return body.toString().toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())
} }
private fun Element.searchTitle() = select("strong").text()!! private fun Element.searchTitle() = select("strong").text()!!

View File

@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.data.track.myanimelist
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response import okhttp3.Response
import okio.Buffer import okio.Buffer
import org.json.JSONObject import org.json.JSONObject
@ -15,7 +16,7 @@ class MyAnimeListInterceptor(private val myanimelist: Myanimelist): Interceptor
val request = chain.request() val request = chain.request()
var response = chain.proceed(updateRequest(request)) var response = chain.proceed(updateRequest(request))
if (response.code == 400){ if (response.code == 400) {
myanimelist.refreshLogin() myanimelist.refreshLogin()
response = chain.proceed(updateRequest(request)) response = chain.proceed(updateRequest(request))
} }
@ -45,15 +46,14 @@ class MyAnimeListInterceptor(private val myanimelist: Myanimelist): Interceptor
private fun updateFormBody(requestBody: RequestBody): RequestBody { private fun updateFormBody(requestBody: RequestBody): RequestBody {
val formString = bodyToString(requestBody) val formString = bodyToString(requestBody)
return RequestBody.create(requestBody.contentType(), return "$formString${if (formString.isNotEmpty()) "&" else ""}${MyAnimeListApi.CSRF}=${myanimelist.getCSRF()}".toRequestBody(requestBody.contentType())
"$formString${if (formString.isNotEmpty()) "&" else ""}${MyanimelistApi.CSRF}=${myanimelist.getCSRF()}")
} }
private fun updateJsonBody(requestBody: RequestBody): RequestBody { private fun updateJsonBody(requestBody: RequestBody): RequestBody {
val jsonString = bodyToString(requestBody) val jsonString = bodyToString(requestBody)
val newBody = JSONObject(jsonString) val newBody = JSONObject(jsonString)
.put(MyanimelistApi.CSRF, myanimelist.getCSRF()) .put(MyAnimeListApi.CSRF, myanimelist.getCSRF())
return RequestBody.create(requestBody.contentType(), newBody.toString()) return newBody.toString().toRequestBody(requestBody.contentType())
} }
} }

View File

@ -16,6 +16,10 @@ import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.asObservableSuccess
import okhttp3.* import okhttp3.*
import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import rx.Observable import rx.Observable
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
@ -37,7 +41,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
"status" to track.toShikimoriStatus() "status" to track.toShikimoriStatus()
) )
) )
val body = RequestBody.create(jsonime, payload.toString()) val body = payload.toString().toRequestBody(jsonime)
val request = Request.Builder() val request = Request.Builder()
.url("$apiUrl/v2/user_rates") .url("$apiUrl/v2/user_rates")
.post(body) .post(body)

View File

@ -94,7 +94,7 @@ internal class ExtensionInstallReceiver(private val listener: Listener) :
private suspend fun getExtensionFromIntent(context: Context, intent: Intent?): LoadResult { private suspend fun getExtensionFromIntent(context: Context, intent: Intent?): LoadResult {
val pkgName = getPackageNameFromIntent(intent) ?: val pkgName = getPackageNameFromIntent(intent) ?:
return LoadResult.Error("Package name not found") return LoadResult.Error("Package name not found")
return GlobalScope.async(Dispatchers.Default, CoroutineStart.DEFAULT, { ExtensionLoader.loadExtensionFromPkgName(context, pkgName) }).await() return GlobalScope.async(Dispatchers.Default, CoroutineStart.DEFAULT) { ExtensionLoader.loadExtensionFromPkgName(context, pkgName) }.await()
} }
/** /**

View File

@ -173,7 +173,7 @@ internal object ExtensionLoader {
*/ */
private fun getSignatureHash(pkgInfo: PackageInfo): String? { private fun getSignatureHash(pkgInfo: PackageInfo): String? {
val signatures = pkgInfo.signatures val signatures = pkgInfo.signatures
return if (signatures != null && !signatures.isEmpty()) { return if (signatures != null && signatures.isNotEmpty()) {
Hash.sha256(signatures.first().toByteArray()) Hash.sha256(signatures.first().toByteArray())
} else { } else {
null null

View File

@ -1,19 +1,14 @@
package eu.kanade.tachiyomi.network package eu.kanade.tachiyomi.network
import android.content.Context
import android.os.Build
import android.webkit.CookieManager import android.webkit.CookieManager
import android.webkit.CookieSyncManager
import okhttp3.Cookie import okhttp3.Cookie
import okhttp3.CookieJar import okhttp3.CookieJar
import okhttp3.HttpUrl import okhttp3.HttpUrl
class AndroidCookieJar(context: Context) : CookieJar { class AndroidCookieJar : CookieJar {
private val manager = CookieManager.getInstance() private val manager = CookieManager.getInstance()
private val syncManager by lazy { CookieSyncManager.createInstance(context) }
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) { override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
val urlString = url.toString() val urlString = url.toString()
@ -29,7 +24,7 @@ class AndroidCookieJar(context: Context) : CookieJar {
fun get(url: HttpUrl): List<Cookie> { fun get(url: HttpUrl): List<Cookie> {
val cookies = manager.getCookie(url.toString()) val cookies = manager.getCookie(url.toString())
return if (cookies != null && !cookies.isEmpty()) { return if (cookies != null && cookies.isNotEmpty()) {
cookies.split(";").mapNotNull { Cookie.parse(url, it) } cookies.split(";").mapNotNull { Cookie.parse(url, it) }
} else { } else {
emptyList() emptyList()
@ -43,19 +38,10 @@ class AndroidCookieJar(context: Context) : CookieJar {
cookies.split(";") cookies.split(";")
.map { it.substringBefore("=") } .map { it.substringBefore("=") }
.onEach { manager.setCookie(urlString, "$it=;Max-Age=-1") } .onEach { manager.setCookie(urlString, "$it=;Max-Age=-1") }
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
syncManager.sync()
}
} }
fun removeAll() { fun removeAll() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { manager.removeAllCookies {}
manager.removeAllCookies {}
} else {
manager.removeAllCookie()
syncManager.sync()
}
} }
} }

View File

@ -28,11 +28,7 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
* Application class. * Application class.
*/ */
private val initWebView by lazy { private val initWebView by lazy {
if (Build.VERSION.SDK_INT >= 17) { WebSettings.getDefaultUserAgent(context)
WebSettings.getDefaultUserAgent(context)
} else {
null
}
} }
@Synchronized @Synchronized

View File

@ -1,17 +1,9 @@
package eu.kanade.tachiyomi.network package eu.kanade.tachiyomi.network
import android.content.Context import android.content.Context
import android.os.Build import okhttp3.Cache
import okhttp3.* import okhttp3.OkHttpClient
import java.io.File import java.io.File
import java.io.IOException
import java.net.InetAddress
import java.net.Socket
import java.net.UnknownHostException
import java.security.KeyManagementException
import java.security.KeyStore
import java.security.NoSuchAlgorithmException
import javax.net.ssl.*
class NetworkHelper(context: Context) { class NetworkHelper(context: Context) {
@ -19,99 +11,15 @@ class NetworkHelper(context: Context) {
private val cacheSize = 5L * 1024 * 1024 // 5 MiB private val cacheSize = 5L * 1024 * 1024 // 5 MiB
val cookieManager = AndroidCookieJar(context) val cookieManager = AndroidCookieJar()
val client = OkHttpClient.Builder() val client = OkHttpClient.Builder()
.cookieJar(cookieManager) .cookieJar(cookieManager)
.cache(Cache(cacheDir, cacheSize)) .cache(Cache(cacheDir, cacheSize))
.enableTLS12()
.build() .build()
val cloudflareClient = client.newBuilder() val cloudflareClient = client.newBuilder()
.addInterceptor(CloudflareInterceptor(context)) .addInterceptor(CloudflareInterceptor(context))
.build() .build()
private fun OkHttpClient.Builder.enableTLS12(): OkHttpClient.Builder {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
return this
}
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
trustManagerFactory.init(null as KeyStore?)
val trustManagers = trustManagerFactory.trustManagers
if (trustManagers.size == 1 && trustManagers[0] is X509TrustManager) {
class TLSSocketFactory @Throws(KeyManagementException::class, NoSuchAlgorithmException::class)
constructor() : SSLSocketFactory() {
private val internalSSLSocketFactory: SSLSocketFactory
init {
val context = SSLContext.getInstance("TLS")
context.init(null, null, null)
internalSSLSocketFactory = context.socketFactory
}
override fun getDefaultCipherSuites(): Array<String> {
return internalSSLSocketFactory.defaultCipherSuites
}
override fun getSupportedCipherSuites(): Array<String> {
return internalSSLSocketFactory.supportedCipherSuites
}
@Throws(IOException::class)
override fun createSocket(): Socket? {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket())
}
@Throws(IOException::class)
override fun createSocket(s: Socket, host: String, port: Int, autoClose: Boolean): Socket? {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose))
}
@Throws(IOException::class, UnknownHostException::class)
override fun createSocket(host: String, port: Int): Socket? {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port))
}
@Throws(IOException::class, UnknownHostException::class)
override fun createSocket(host: String, port: Int, localHost: InetAddress, localPort: Int): Socket? {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort))
}
@Throws(IOException::class)
override fun createSocket(host: InetAddress, port: Int): Socket? {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port))
}
@Throws(IOException::class)
override fun createSocket(address: InetAddress, port: Int, localAddress: InetAddress, localPort: Int): Socket? {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort))
}
private fun enableTLSOnSocket(socket: Socket?): Socket? {
if (socket != null && socket is SSLSocket) {
socket.enabledProtocols = socket.supportedProtocols
}
return socket
}
}
sslSocketFactory(TLSSocketFactory(), trustManagers[0] as X509TrustManager)
}
val specCompat = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.tlsVersions(TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0)
.cipherSuites(
*ConnectionSpec.MODERN_TLS.cipherSuites.orEmpty().toTypedArray(),
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
)
.build()
val specs = listOf(specCompat, ConnectionSpec.CLEARTEXT)
connectionSpecs(specs)
return this
}
} }

View File

@ -37,7 +37,6 @@ open class Page(
} }
companion object { companion object {
const val QUEUE = 0 const val QUEUE = 0
const val LOAD_PAGE = 1 const val LOAD_PAGE = 1
const val DOWNLOAD_IMAGE = 2 const val DOWNLOAD_IMAGE = 2

View File

@ -38,7 +38,7 @@ abstract class DialogController : RestoreViewOnCreateController {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedViewState: Bundle?): View { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedViewState: Bundle?): View {
dialog = onCreateDialog(savedViewState) dialog = onCreateDialog(savedViewState)
//dialog!!.ownerActivity = activity dialog!!.setOwnerActivity(activity!!)
dialog!!.setOnDismissListener { dismissDialog() } dialog!!.setOnDismissListener { dismissDialog() }
if (savedViewState != null) { if (savedViewState != null) {
val dialogState = savedViewState.getBundle(SAVED_DIALOG_STATE_TAG) val dialogState = savedViewState.getBundle(SAVED_DIALOG_STATE_TAG)

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.ui.base.holder package eu.kanade.tachiyomi.ui.base.holder
import android.os.Build
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
@ -51,10 +50,6 @@ interface SlicedHolder {
slice.showRightTopRect(topRect) slice.showRightTopRect(topRect)
slice.showLeftBottomRect(bottomRect) slice.showLeftBottomRect(bottomRect)
slice.showRightBottomRect(bottomRect) slice.showRightBottomRect(bottomRect)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
slice.showTopEdgeShadow(topShadow)
slice.showBottomEdgeShadow(bottomShadow)
}
setMargins(margin, if (topShadow) margin else 0, margin, if (bottomShadow) margin else 0) setMargins(margin, if (topShadow) margin else 0, margin, if (bottomShadow) margin else 0)
} }
@ -68,4 +63,4 @@ interface SlicedHolder {
val margin val margin
get() = 8.dpToPx get() = 8.dpToPx
} }

View File

@ -27,8 +27,8 @@ class SourceDividerItemDecoration(context: Context) : androidx.recyclerview.widg
val params = child.layoutParams as androidx.recyclerview.widget.RecyclerView.LayoutParams val params = child.layoutParams as androidx.recyclerview.widget.RecyclerView.LayoutParams
val top = child.bottom + params.bottomMargin val top = child.bottom + params.bottomMargin
val bottom = top + divider.intrinsicHeight val bottom = top + divider.intrinsicHeight
val left = parent.paddingLeft + holder.margin val left = parent.paddingStart + holder.margin
val right = parent.width - parent.paddingRight - holder.margin val right = parent.width - parent.paddingEnd - holder.margin
divider.setBounds(left, top, right, bottom) divider.setBounds(left, top, right, bottom)
divider.draw(c) divider.draw(c)

View File

@ -367,7 +367,7 @@ open class BrowseCataloguePresenter(
* @param selectedCategories selected categories * @param selectedCategories selected categories
*/ */
fun updateMangaCategories(manga: Manga, selectedCategories: List<Category>) { fun updateMangaCategories(manga: Manga, selectedCategories: List<Category>) {
if (!selectedCategories.isEmpty()) { if (selectedCategories.isNotEmpty()) {
if (!manga.favorite) if (!manga.favorite)
changeMangaFavorite(manga) changeMangaFavorite(manga)

View File

@ -24,8 +24,8 @@ abstract class Pager(var currentPage: Int = 1) {
fun onPageReceived(mangasPage: MangasPage) { fun onPageReceived(mangasPage: MangasPage) {
val page = currentPage val page = currentPage
currentPage++ currentPage++
hasNextPage = mangasPage.hasNextPage && !mangasPage.mangas.isEmpty() hasNextPage = mangasPage.hasNextPage && mangasPage.mangas.isNotEmpty()
results.call(Pair(page, mangasPage.mangas)) results.call(Pair(page, mangasPage.mangas))
} }
} }

View File

@ -205,7 +205,6 @@ open class CatalogueSearchPresenter(
.map { Pair(source as CatalogueSource, it) } .map { Pair(source as CatalogueSource, it) }
} }
.onBackpressureBuffer() .onBackpressureBuffer()
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe({ (source, manga) -> .subscribe({ (source, manga) ->

View File

@ -235,11 +235,11 @@ class CategoryController : NucleusController<CategoryPresenter>(),
*/ */
override fun onItemClick(view: View?, position: Int): Boolean { override fun onItemClick(view: View?, position: Int): Boolean {
// Check if action mode is initialized and selected item exist. // Check if action mode is initialized and selected item exist.
if (actionMode != null && position != androidx.recyclerview.widget.RecyclerView.NO_POSITION) { return if (actionMode != null && position != RecyclerView.NO_POSITION) {
toggleSelection(position) toggleSelection(position)
return true true
} else { } else {
return false false
} }
} }

View File

@ -33,9 +33,9 @@ class CategoryCreateDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
.title(R.string.action_add_category) .title(R.string.action_add_category)
.negativeText(android.R.string.cancel) .negativeText(android.R.string.cancel)
.alwaysCallInputCallback() .alwaysCallInputCallback()
.input(resources?.getString(R.string.name), currentName, false, { _, input -> .input(resources?.getString(R.string.name), currentName, false) { _, input ->
currentName = input.toString() currentName = input.toString()
}) }
.onPositive { _, _ -> (targetController as? Listener)?.createCategory(currentName) } .onPositive { _, _ -> (targetController as? Listener)?.createCategory(currentName) }
.build() .build()
} }
@ -44,4 +44,4 @@ class CategoryCreateDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
fun createCategory(name: String) fun createCategory(name: String)
} }
} }

View File

@ -38,9 +38,9 @@ class CategoryRenameDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
.title(R.string.action_rename_category) .title(R.string.action_rename_category)
.negativeText(android.R.string.cancel) .negativeText(android.R.string.cancel)
.alwaysCallInputCallback() .alwaysCallInputCallback()
.input(resources!!.getString(R.string.name), currentName, false, { _, input -> .input(resources!!.getString(R.string.name), currentName, false) { _, input ->
currentName = input.toString() currentName = input.toString()
}) }
.onPositive { _, _ -> onPositive() } .onPositive { _, _ -> onPositive() }
.build() .build()
} }
@ -83,4 +83,4 @@ class CategoryRenameDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
const val CATEGORY_KEY = "CategoryRenameDialog.category" const val CATEGORY_KEY = "CategoryRenameDialog.category"
} }
} }

View File

@ -69,4 +69,4 @@ class DownloadPresenter : BasePresenter<DownloadController>() {
downloadManager.deletePendingDownloads(download) downloadManager.deletePendingDownloads(download)
} }
} }

View File

@ -138,7 +138,7 @@ open class ExtensionController : NucleusController<ExtensionPresenter>(),
val searchView = searchItem.actionView as SearchView val searchView = searchItem.actionView as SearchView
searchView.maxWidth = Int.MAX_VALUE searchView.maxWidth = Int.MAX_VALUE
if (!query.isEmpty()) { if (query.isNotEmpty()) {
searchItem.expandActionView() searchItem.expandActionView()
searchView.setQuery(query, true) searchView.setQuery(query, true)
searchView.clearFocus() searchView.clearFocus()

View File

@ -27,8 +27,8 @@ class ExtensionDividerItemDecoration(context: Context) : androidx.recyclerview.w
val params = child.layoutParams as androidx.recyclerview.widget.RecyclerView.LayoutParams val params = child.layoutParams as androidx.recyclerview.widget.RecyclerView.LayoutParams
val top = child.bottom + params.bottomMargin val top = child.bottom + params.bottomMargin
val bottom = top + divider.intrinsicHeight val bottom = top + divider.intrinsicHeight
val left = parent.paddingLeft + holder.margin val left = parent.paddingStart + holder.margin
val right = parent.width - parent.paddingRight - holder.margin val right = parent.width - parent.paddingEnd - holder.margin
divider.setBounds(left, top, right, bottom) divider.setBounds(left, top, right, bottom)
divider.draw(c) divider.draw(c)

View File

@ -218,8 +218,7 @@ class LibraryController(
override fun createSecondaryDrawer(drawer: androidx.drawerlayout.widget.DrawerLayout): ViewGroup { override fun createSecondaryDrawer(drawer: androidx.drawerlayout.widget.DrawerLayout): ViewGroup {
val view = drawer.inflate(R.layout.library_drawer) as LibraryNavigationView val view = drawer.inflate(R.layout.library_drawer) as LibraryNavigationView
navView = view navView = view
drawer.setDrawerLockMode(androidx.drawerlayout.widget.DrawerLayout.LOCK_MODE_UNLOCKED, drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END)
GravityCompat.END)
navView?.onGroupClicked = { group -> navView?.onGroupClicked = { group ->
when (group) { when (group) {

View File

@ -231,9 +231,9 @@ class LibraryNavigationView @JvmOverloads constructor(context: Context, attrs: A
item.group.items.forEach { (it as Item.Radio).checked = false } item.group.items.forEach { (it as Item.Radio).checked = false }
item.checked = true item.checked = true
preferences.libraryAsList().set(if (item == list) true else false) preferences.libraryAsList().set(item == list)
item.group.items.forEach { adapter.notifyItemChanged(it) } item.group.items.forEach { adapter.notifyItemChanged(it) }
} }
} }
} }

View File

@ -97,12 +97,15 @@ class LibraryPresenter(
fun subscribeLibrary() { fun subscribeLibrary() {
if (librarySubscription.isNullOrUnsubscribed()) { if (librarySubscription.isNullOrUnsubscribed()) {
librarySubscription = getLibraryObservable() librarySubscription = getLibraryObservable()
.combineLatest(downloadTriggerRelay.observeOn(Schedulers.io()), .combineLatest(downloadTriggerRelay.observeOn(Schedulers.io())) {
{ lib, _ -> lib.apply { setDownloadCount(mangaMap) } }) lib, _ -> lib.apply { setDownloadCount(mangaMap) }
.combineLatest(filterTriggerRelay.observeOn(Schedulers.io()), }
{ lib, _ -> lib.copy(mangaMap = applyFilters(lib.mangaMap)) }) .combineLatest(filterTriggerRelay.observeOn(Schedulers.io())) {
.combineLatest(sortTriggerRelay.observeOn(Schedulers.io()), lib, _ -> lib.copy(mangaMap = applyFilters(lib.mangaMap))
{ lib, _ -> lib.copy(mangaMap = applySort(lib.mangaMap)) }) }
.combineLatest(sortTriggerRelay.observeOn(Schedulers.io())) {
lib, _ -> lib.copy(mangaMap = applySort(lib.mangaMap))
}
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribeLatestCache({ view, (categories, mangaMap) -> .subscribeLatestCache({ view, (categories, mangaMap) ->
view.onNextLibraryUpdate(categories, mangaMap) view.onNextLibraryUpdate(categories, mangaMap)

View File

@ -12,7 +12,7 @@ import it.gmariotti.changelibs.library.view.ChangeLogRecyclerView
class ChangelogDialogController : DialogController() { class ChangelogDialogController : DialogController() {
override fun onCreateDialog(savedState: Bundle?): Dialog { override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val activity = activity!! val activity = activity!!
val view = WhatsNewRecyclerView(activity) val view = WhatsNewRecyclerView(activity)
return MaterialDialog.Builder(activity) return MaterialDialog.Builder(activity)
@ -29,4 +29,4 @@ class ChangelogDialogController : DialogController() {
mChangeLogFileResourceId = if (BuildConfig.DEBUG) R.raw.changelog_debug else R.raw.changelog_release mChangeLogFileResourceId = if (BuildConfig.DEBUG) R.raw.changelog_debug else R.raw.changelog_release
} }
} }
} }

View File

@ -315,7 +315,7 @@ class MainActivity : BaseActivity() {
//Get the search query provided in extras, and if not null, perform a global search with it. //Get the search query provided in extras, and if not null, perform a global search with it.
val query = intent.getStringExtra(SearchManager.QUERY) val query = intent.getStringExtra(SearchManager.QUERY)
if (query != null && !query.isEmpty()) { if (query != null && query.isNotEmpty()) {
if (router.backstackSize > 1) { if (router.backstackSize > 1) {
router.popToRoot() router.popToRoot()
} }
@ -325,7 +325,7 @@ class MainActivity : BaseActivity() {
INTENT_SEARCH -> { INTENT_SEARCH -> {
val query = intent.getStringExtra(INTENT_SEARCH_QUERY) val query = intent.getStringExtra(INTENT_SEARCH_QUERY)
val filter = intent.getStringExtra(INTENT_SEARCH_FILTER) val filter = intent.getStringExtra(INTENT_SEARCH_FILTER)
if (query != null && !query.isEmpty()) { if (query != null && query.isNotEmpty()) {
if (router.backstackSize > 1) { if (router.backstackSize > 1) {
router.popToRoot() router.popToRoot()
} }

View File

@ -109,8 +109,9 @@ class ChaptersPresenter(
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.filter { download -> download.manga.id == manga.id } .filter { download -> download.manga.id == manga.id }
.doOnNext { onDownloadStatusChange(it) } .doOnNext { onDownloadStatusChange(it) }
.subscribeLatestCache(ChaptersController::onChapterStatusChange, .subscribeLatestCache(ChaptersController::onChapterStatusChange) {
{ _, error -> Timber.e(error) }) _, error -> Timber.e(error)
}
} }
/** /**

View File

@ -13,7 +13,7 @@ class DeletingChaptersDialog(bundle: Bundle? = null) : DialogController(bundle)
const val TAG = "deleting_dialog" const val TAG = "deleting_dialog"
} }
override fun onCreateDialog(savedState: Bundle?): Dialog { override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog.Builder(activity!!) return MaterialDialog.Builder(activity!!)
.progress(true, 0) .progress(true, 0)
.content(R.string.deleting) .content(R.string.deleting)
@ -24,4 +24,4 @@ class DeletingChaptersDialog(bundle: Bundle? = null) : DialogController(bundle)
showDialog(router, TAG) showDialog(router, TAG)
} }
} }

View File

@ -27,6 +27,7 @@ class DownloadChaptersDialog<T>(bundle: Bundle? = null) : DialogController(bundl
).map { activity.getString(it) } ).map { activity.getString(it) }
return MaterialDialog.Builder(activity) return MaterialDialog.Builder(activity)
.title(R.string.manga_download)
.negativeText(android.R.string.cancel) .negativeText(android.R.string.cancel)
.items(choices) .items(choices)
.itemsCallback { _, _, position, _ -> .itemsCallback { _, _, position, _ ->
@ -39,4 +40,4 @@ class DownloadChaptersDialog<T>(bundle: Bundle? = null) : DialogController(bundl
fun downloadChapters(choice: Int) fun downloadChapters(choice: Int)
} }
} }

View File

@ -21,7 +21,9 @@ import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat import androidx.core.graphics.drawable.IconCompat
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.load.resource.bitmap.RoundedCorners import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.request.target.CustomTarget import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition import com.bumptech.glide.request.transition.Transition
@ -240,11 +242,7 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
} }
// If manga source is known update source TextView. // If manga source is known update source TextView.
manga_source.text = if (source == null) { manga_source.text = source?.toString() ?: view.context.getString(R.string.unknown)
view.context.getString(R.string.unknown)
} else {
source.toString()
}
// Update genres list // Update genres list
if (manga.genre.isNullOrBlank().not()) { if (manga.genre.isNullOrBlank().not()) {
@ -580,17 +578,18 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
3 -> centerCrop().transform(MaskTransformation(R.drawable.mask_star)) 3 -> centerCrop().transform(MaskTransformation(R.drawable.mask_star))
} }
} }
.into(object : CustomTarget<Bitmap>(96, 96) { .listener(object : RequestListener<Bitmap> {
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) { override fun onResourceReady(resource: Bitmap, model: Any, target: Target<Bitmap>, dataSource: DataSource, isFirstResource: Boolean): Boolean {
createShortcut(resource) createShortcut(resource)
return true
} }
override fun onLoadCleared(placeholder: Drawable?) { } override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Bitmap>?, isFirstResource: Boolean): Boolean {
override fun onLoadFailed(errorDrawable: Drawable?) {
activity?.toast(R.string.icon_creation_fail) activity?.toast(R.string.icon_creation_fail)
return true
} }
}) })
.submit()
} }
/** /**

View File

@ -32,7 +32,7 @@ class SetTrackStatusDialog<T> : DialogController
override fun onCreateDialog(savedViewState: Bundle?): Dialog { override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val item = item val item = item
val statusList = item.service.getStatusList().orEmpty() val statusList = item.service.getStatusList()
val statusString = statusList.mapNotNull { item.service.getStatus(it) } val statusString = statusList.mapNotNull { item.service.getStatus(it) }
val selectedIndex = statusList.indexOf(item.track?.status) val selectedIndex = statusList.indexOf(item.track?.status)
@ -40,10 +40,10 @@ class SetTrackStatusDialog<T> : DialogController
.title(R.string.status) .title(R.string.status)
.negativeText(android.R.string.cancel) .negativeText(android.R.string.cancel)
.items(statusString) .items(statusString)
.itemsCallbackSingleChoice(selectedIndex, { _, _, i, _ -> .itemsCallbackSingleChoice(selectedIndex) { _, _, i, _ ->
(targetController as? Listener)?.setStatus(item, i) (targetController as? Listener)?.setStatus(item, i)
true true
}) }
.build() .build()
} }
@ -55,4 +55,4 @@ class SetTrackStatusDialog<T> : DialogController
const val KEY_ITEM_TRACK = "SetTrackStatusDialog.item.track" const val KEY_ITEM_TRACK = "SetTrackStatusDialog.item.track"
} }
} }

View File

@ -25,7 +25,7 @@ class TrackHolder(view: View, adapter: TrackAdapter) : BaseViewHolder(view) {
logo_container.setBackgroundColor(item.service.getLogoColor()) logo_container.setBackgroundColor(item.service.getLogoColor())
if (track != null) { if (track != null) {
track_title.setTextAppearance(itemView.context, R.style.TextAppearance_Regular_Body1_Secondary) track_title.setTextAppearance(itemView.context, R.style.TextAppearance_Regular_Body1_Secondary)
track_title.setAllCaps(false) track_title.isAllCaps = false
track_title.text = track.title track_title.text = track.title
track_chapters.text = "${track.last_chapter_read}/" + track_chapters.text = "${track.last_chapter_read}/" +
if (track.total_chapters > 0) track.total_chapters else "-" if (track.total_chapters > 0) track.total_chapters else "-"

View File

@ -76,4 +76,4 @@ class TrackSearchAdapter(context: Context)
} }
} }
} }
} }

View File

@ -50,7 +50,7 @@ class TrackSearchDialog : DialogController {
service = Injekt.get<TrackManager>().getService(bundle.getInt(KEY_SERVICE))!! service = Injekt.get<TrackManager>().getService(bundle.getInt(KEY_SERVICE))!!
} }
override fun onCreateDialog(savedState: Bundle?): Dialog { override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val dialog = MaterialDialog.Builder(activity!!) val dialog = MaterialDialog.Builder(activity!!)
.customView(R.layout.track_search_dialog, false) .customView(R.layout.track_search_dialog, false)
.positiveText(android.R.string.ok) .positiveText(android.R.string.ok)
@ -63,7 +63,7 @@ class TrackSearchDialog : DialogController {
} }
dialogView = dialog.view dialogView = dialog.view
onViewCreated(dialog.view, savedState) onViewCreated(dialog.view, savedViewState)
return dialog return dialog
} }

View File

@ -42,8 +42,8 @@ class MigrationPresenter(
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.doOnNext { state = state.copy(sourcesWithManga = findSourcesWithManga(it)) } .doOnNext { state = state.copy(sourcesWithManga = findSourcesWithManga(it)) }
.combineLatest(stateRelay.map { it.selectedSource } .combineLatest(stateRelay.map { it.selectedSource }
.distinctUntilChanged(), .distinctUntilChanged()
{ library, source -> library to source }) ) { library, source -> library to source }
.filter { (_, source) -> source != null } .filter { (_, source) -> source != null }
.observeOn(Schedulers.io()) .observeOn(Schedulers.io())
.map { (library, source) -> libraryToMigrationItem(library, source!!.id) } .map { (library, source) -> libraryToMigrationItem(library, source!!.id) }

View File

@ -147,7 +147,8 @@ class SearchController(
preferences.migrateFlags().set(newValue) preferences.migrateFlags().set(newValue)
true true
}.positiveText(R.string.migrate) }
.positiveText(R.string.migrate)
.negativeText(R.string.copy) .negativeText(R.string.copy)
.neutralText(android.R.string.cancel) .neutralText(android.R.string.cancel)
.onPositive { _, _ -> .onPositive { _, _ ->
@ -197,4 +198,4 @@ class SearchController(
} }
} }

View File

@ -9,7 +9,6 @@ import android.content.pm.ActivityInfo
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Color import android.graphics.Color
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.KeyEvent import android.view.KeyEvent
import android.view.Menu import android.view.Menu
@ -68,6 +67,7 @@ import uy.kohesive.injekt.injectLazy
import java.io.File import java.io.File
import java.util.Date import java.util.Date
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.math.abs
/** /**
* Activity containing the reader of Tachiyomi. This activity is mostly a container of the * Activity containing the reader of Tachiyomi. This activity is mostly a container of the
@ -803,18 +803,22 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>(),
*/ */
private fun setCustomBrightnessValue(value: Int) { private fun setCustomBrightnessValue(value: Int) {
// Calculate and set reader brightness. // Calculate and set reader brightness.
val readerBrightness = if (value > 0) { val readerBrightness = when {
value / 100f value > 0 -> {
} else if (value < 0) { value / 100f
0.01f }
} else WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE value < 0 -> {
0.01f
}
else -> WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
}
window.attributes = window.attributes.apply { screenBrightness = readerBrightness } window.attributes = window.attributes.apply { screenBrightness = readerBrightness }
// Set black overlay visibility. // Set black overlay visibility.
if (value < 0) { if (value < 0) {
brightness_overlay.visibility = View.VISIBLE brightness_overlay.visibility = View.VISIBLE
val alpha = (Math.abs(value) * 2.56).toInt() val alpha = (abs(value) * 2.56).toInt()
brightness_overlay.setBackgroundColor(Color.argb(alpha, 0, 0, 0)) brightness_overlay.setBackgroundColor(Color.argb(alpha, 0, 0, 0))
} else { } else {
brightness_overlay.visibility = View.GONE brightness_overlay.visibility = View.GONE

View File

@ -1,12 +1,12 @@
package eu.kanade.tachiyomi.ui.reader package eu.kanade.tachiyomi.ui.reader
import android.graphics.Color import android.graphics.Color
import androidx.annotation.ColorInt
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.SeekBar import android.widget.SeekBar
import androidx.annotation.ColorInt
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.preference.getOrDefault
@ -14,12 +14,14 @@ import eu.kanade.tachiyomi.util.plusAssign
import eu.kanade.tachiyomi.widget.IgnoreFirstSpinnerListener import eu.kanade.tachiyomi.widget.IgnoreFirstSpinnerListener
import eu.kanade.tachiyomi.widget.SimpleSeekBarListener import eu.kanade.tachiyomi.widget.SimpleSeekBarListener
import kotlinx.android.synthetic.main.reader_color_filter.* import kotlinx.android.synthetic.main.reader_color_filter.*
import kotlinx.android.synthetic.main.reader_color_filter_sheet.* import kotlinx.android.synthetic.main.reader_color_filter_sheet.brightness_overlay
import kotlinx.android.synthetic.main.reader_color_filter_sheet.color_overlay
import rx.Subscription import rx.Subscription
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import rx.subscriptions.CompositeSubscription import rx.subscriptions.CompositeSubscription
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.math.abs
/** /**
* Color filter sheet to toggle custom filter and brightness overlay. * Color filter sheet to toggle custom filter and brightness overlay.
@ -221,7 +223,7 @@ class ReaderColorFilterSheet(activity: ReaderActivity) : BottomSheetDialog(activ
// Set black overlay visibility. // Set black overlay visibility.
if (value < 0) { if (value < 0) {
brightness_overlay.visibility = View.VISIBLE brightness_overlay.visibility = View.VISIBLE
val alpha = (Math.abs(value) * 2.56).toInt() val alpha = (abs(value) * 2.56).toInt()
brightness_overlay.setBackgroundColor(Color.argb(alpha, 0, 0, 0)) brightness_overlay.setBackgroundColor(Color.argb(alpha, 0, 0, 0))
} else { } else {
brightness_overlay.visibility = View.GONE brightness_overlay.visibility = View.GONE

View File

@ -13,7 +13,7 @@ class ReaderColorFilterView(
private val colorFilterPaint: Paint = Paint() private val colorFilterPaint: Paint = Paint()
fun setFilterColor(color: Int, filterMode: Int) { fun setFilterColor(color: Int, filterMode: Int) {
colorFilterPaint.setColor(color) colorFilterPaint.color = color
colorFilterPaint.xfermode = PorterDuffXfermode(when (filterMode) { colorFilterPaint.xfermode = PorterDuffXfermode(when (filterMode) {
1 -> PorterDuff.Mode.MULTIPLY 1 -> PorterDuff.Mode.MULTIPLY
2 -> PorterDuff.Mode.SCREEN 2 -> PorterDuff.Mode.SCREEN

View File

@ -90,7 +90,7 @@ class ReaderPresenter(
val chaptersForReader = val chaptersForReader =
if (preferences.skipRead()) { if (preferences.skipRead()) {
var list = dbChapters.filter { it -> !it.read }.toMutableList() val list = dbChapters.filter { !it.read }.toMutableList()
val find = list.find { it.id == chapterId } val find = list.find { it.id == chapterId }
if (find == null) { if (find == null) {
list.add(selectedChapter) list.add(selectedChapter)

View File

@ -60,7 +60,7 @@ class SaveImageNotifier(private val context: Context) {
setAutoCancel(true) setAutoCancel(true)
color = ContextCompat.getColor(context, R.color.colorAccentLight) color = ContextCompat.getColor(context, R.color.colorAccentLight)
// Clear old actions if they exist // Clear old actions if they exist
if (!mActions.isEmpty()) if (mActions.isNotEmpty())
mActions.clear() mActions.clear()
setContentIntent(NotificationHandler.openImagePendingActivity(context, file)) setContentIntent(NotificationHandler.openImagePendingActivity(context, file))
@ -72,8 +72,8 @@ class SaveImageNotifier(private val context: Context) {
addAction(R.drawable.ic_delete_grey_24dp, addAction(R.drawable.ic_delete_grey_24dp,
context.getString(R.string.action_delete), context.getString(R.string.action_delete),
NotificationReceiver.deleteImagePendingBroadcast(context, file.absolutePath, notificationId)) NotificationReceiver.deleteImagePendingBroadcast(context, file.absolutePath, notificationId))
updateNotification()
updateNotification()
} }
} }
@ -89,7 +89,6 @@ class SaveImageNotifier(private val context: Context) {
context.notificationManager.notify(notificationId, notificationBuilder.build()) context.notificationManager.notify(notificationId, notificationBuilder.build())
} }
/** /**
* Called on error while downloading image. * Called on error while downloading image.
* @param error string containing error information. * @param error string containing error information.

View File

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

View File

@ -5,6 +5,7 @@ import android.os.Handler
import android.view.GestureDetector import android.view.GestureDetector
import android.view.MotionEvent import android.view.MotionEvent
import android.view.ViewConfiguration import android.view.ViewConfiguration
import kotlin.math.abs
/** /**
* A custom gesture detector that also implements an on long tap confirmed, because the built-in * A custom gesture detector that also implements an on long tap confirmed, because the built-in
@ -45,7 +46,7 @@ open class GestureDetectorWithLongTap(
} }
} }
MotionEvent.ACTION_MOVE -> { MotionEvent.ACTION_MOVE -> {
if (Math.abs(ev.rawX - downX) > slop || Math.abs(ev.rawY - downY) > slop) { if (abs(ev.rawX - downX) > slop || abs(ev.rawY - downY) > slop) {
handler.removeCallbacks(longTapFn) handler.removeCallbacks(longTapFn)
} }
} }

View File

@ -16,6 +16,7 @@ import android.view.animation.LinearInterpolator
import android.view.animation.RotateAnimation import android.view.animation.RotateAnimation
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.getResourceColor import eu.kanade.tachiyomi.util.getResourceColor
import kotlin.math.min
/** /**
* A custom progress bar that always rotates while being determinate. By always rotating we give * A custom progress bar that always rotates while being determinate. By always rotating we give
@ -75,7 +76,7 @@ class ReaderProgressBar @JvmOverloads constructor(
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom) super.onLayout(changed, left, top, right, bottom)
val diameter = Math.min(width, height) val diameter = min(width, height)
val thickness = diameter / 10f val thickness = diameter / 10f
val pad = thickness / 2f val pad = thickness / 2f
ovalRect.set(pad, pad, diameter - pad, diameter - pad) ovalRect.set(pad, pad, diameter - pad, diameter - pad)

View File

@ -3,17 +3,16 @@ package eu.kanade.tachiyomi.ui.reader.viewer.webtoon
import android.animation.Animator import android.animation.Animator
import android.animation.AnimatorSet import android.animation.AnimatorSet
import android.animation.ValueAnimator import android.animation.ValueAnimator
import android.annotation.TargetApi
import android.content.Context import android.content.Context
import android.os.Build
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.util.AttributeSet import android.util.AttributeSet
import android.view.HapticFeedbackConstants import android.view.HapticFeedbackConstants
import android.view.MotionEvent import android.view.MotionEvent
import android.view.ViewConfiguration import android.view.ViewConfiguration
import android.view.animation.DecelerateInterpolator import android.view.animation.DecelerateInterpolator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.ui.reader.viewer.GestureDetectorWithLongTap import eu.kanade.tachiyomi.ui.reader.viewer.GestureDetectorWithLongTap
import kotlin.math.abs
/** /**
* Implementation of a [RecyclerView] used by the webtoon reader. * Implementation of a [RecyclerView] used by the webtoon reader.
@ -58,7 +57,6 @@ open class WebtoonRecyclerView @JvmOverloads constructor(
firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition() firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
} }
@TargetApi(Build.VERSION_CODES.KITKAT)
override fun onScrollStateChanged(state: Int) { override fun onScrollStateChanged(state: Int) {
super.onScrollStateChanged(state) super.onScrollStateChanged(state)
val layoutManager = layoutManager val layoutManager = layoutManager
@ -270,7 +268,7 @@ open class WebtoonRecyclerView @JvmOverloads constructor(
if (!isZoomDragging && currentScale > 1f) { if (!isZoomDragging && currentScale > 1f) {
var startScroll = false var startScroll = false
if (Math.abs(dx) > touchSlop) { if (abs(dx) > touchSlop) {
if (dx < 0) { if (dx < 0) {
dx += touchSlop dx += touchSlop
} else { } else {
@ -278,7 +276,7 @@ open class WebtoonRecyclerView @JvmOverloads constructor(
} }
startScroll = true startScroll = true
} }
if (Math.abs(dy) > touchSlop) { if (abs(dy) > touchSlop) {
if (dy < 0) { if (dy < 0) {
dy += touchSlop dy += touchSlop
} else { } else {

View File

@ -13,7 +13,7 @@ class DeletingChaptersDialog(bundle: Bundle? = null) : DialogController(bundle)
const val TAG = "deleting_dialog" const val TAG = "deleting_dialog"
} }
override fun onCreateDialog(savedState: Bundle?): Dialog { override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialDialog.Builder(activity!!) return MaterialDialog.Builder(activity!!)
.progress(true, 0) .progress(true, 0)
.content(R.string.deleting) .content(R.string.deleting)
@ -24,4 +24,4 @@ class DeletingChaptersDialog(bundle: Bundle? = null) : DialogController(bundle)
showDialog(router, TAG) showDialog(router, TAG)
} }
} }

View File

@ -38,8 +38,9 @@ class RecentChaptersPresenter(
.subscribeLatestCache(RecentChaptersController::onNextRecentChapters) .subscribeLatestCache(RecentChaptersController::onNextRecentChapters)
getChapterStatusObservable() getChapterStatusObservable()
.subscribeLatestCache(RecentChaptersController::onChapterStatusChange, .subscribeLatestCache(RecentChaptersController::onChapterStatusChange) {
{ _, error -> Timber.e(error) }) _, error -> Timber.e(error)
}
} }
/** /**

View File

@ -31,7 +31,7 @@ class AnilistLoginActivity : AppCompatActivity() {
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe({ .subscribe({
returnToSettings() returnToSettings()
}, { _ -> }, {
returnToSettings() returnToSettings()
}) })
} else { } else {

View File

@ -63,14 +63,16 @@ inline fun <P : Preference> PreferenceGroup.initThenAdd(p: P, block: P.() -> Uni
return p.apply { return p.apply {
block() block()
this.isIconSpaceReserved = false this.isIconSpaceReserved = false
addPreference(this) } addPreference(this)
}
} }
inline fun <P : Preference> PreferenceGroup.addThenInit(p: P, block: P.() -> Unit): P { inline fun <P : Preference> PreferenceGroup.addThenInit(p: P, block: P.() -> Unit): P {
return p.apply { return p.apply {
this.isIconSpaceReserved = false this.isIconSpaceReserved = false
addPreference(this) addPreference(this)
block() } block()
}
} }
inline fun Preference.onClick(crossinline block: () -> Unit) { inline fun Preference.onClick(crossinline block: () -> Unit) {

View File

@ -5,10 +5,9 @@ import android.app.Activity
import android.app.Dialog import android.app.Dialog
import android.content.* import android.content.*
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import androidx.preference.PreferenceScreen
import android.view.View import android.view.View
import androidx.preference.PreferenceScreen
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import com.hippo.unifile.UniFile import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
@ -106,21 +105,12 @@ class SettingsBackupController : SettingsController() {
onClick { onClick {
val currentDir = preferences.backupsDirectory().getOrDefault() val currentDir = preferences.backupsDirectory().getOrDefault()
try{ try{
val intent = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
// Custom dir selected, open directory selector
preferences.context.getFilePicker(currentDir)
} else {
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
}
startActivityForResult(intent, CODE_BACKUP_DIR) startActivityForResult(intent, CODE_BACKUP_DIR)
} catch (e: ActivityNotFoundException){ } catch (e: ActivityNotFoundException){
//Fall back to custom picker on error // Fall back to custom picker on error
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){ startActivityForResult(preferences.context.getFilePicker(currentDir), CODE_BACKUP_DIR)
startActivityForResult(preferences.context.getFilePicker(currentDir), CODE_BACKUP_DIR)
}
} }
} }
preferences.backupsDirectory().asObservable() preferences.backupsDirectory().asObservable()
@ -154,38 +144,32 @@ class SettingsBackupController : SettingsController() {
// Get uri of backup folder. // Get uri of backup folder.
val uri = data.data val uri = data.data
// Get UriPermission so it's possible to write files post kitkat. // Get UriPermission so it's possible to write files
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
if (uri != null) if (uri != null) {
activity.contentResolver.takePersistableUriPermission(uri, flags) activity.contentResolver.takePersistableUriPermission(uri, flags)
} }
// Set backup Uri. // Set backup Uri
preferences.backupsDirectory().set(uri.toString()) preferences.backupsDirectory().set(uri.toString())
} }
CODE_BACKUP_CREATE -> if (data != null && resultCode == Activity.RESULT_OK) { CODE_BACKUP_CREATE -> if (data != null && resultCode == Activity.RESULT_OK) {
val activity = activity ?: return val activity = activity ?: return
val uri = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
val dir = data.data?.path
val file = File(dir, Backup.getDefaultFilename())
Uri.fromFile(file) val uri = data.data
} else { val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
val uri = data.data Intent.FLAG_GRANT_WRITE_URI_PERMISSION
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
if (uri != null) if (uri != null) {
activity.contentResolver.takePersistableUriPermission(uri, flags) activity.contentResolver.takePersistableUriPermission(uri, flags)
val file = UniFile.fromUri(activity, uri)
file.uri
} }
val file = UniFile.fromUri(activity, uri)
CreatingBackupDialog().showDialog(router, TAG_CREATING_BACKUP_DIALOG) CreatingBackupDialog().showDialog(router, TAG_CREATING_BACKUP_DIALOG)
BackupCreateService.makeBackup(activity, uri, backupFlags) BackupCreateService.makeBackup(activity, file.uri, backupFlags)
} }
CODE_BACKUP_RESTORE -> if (data != null && resultCode == Activity.RESULT_OK) { CODE_BACKUP_RESTORE -> if (data != null && resultCode == Activity.RESULT_OK) {
val uri = data.data val uri = data.data
@ -203,25 +187,17 @@ class SettingsBackupController : SettingsController() {
val currentDir = preferences.backupsDirectory().getOrDefault() val currentDir = preferences.backupsDirectory().getOrDefault()
try { try {
// If API is lower than Lollipop use custom picker // Use Android's built-in file creator
val intent = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
preferences.context.getFilePicker(currentDir)
} else {
// Use Androids build in file creator
Intent(Intent.ACTION_CREATE_DOCUMENT)
.addCategory(Intent.CATEGORY_OPENABLE) .addCategory(Intent.CATEGORY_OPENABLE)
.setType("application/*") .setType("application/*")
.putExtra(Intent.EXTRA_TITLE, Backup.getDefaultFilename()) .putExtra(Intent.EXTRA_TITLE, Backup.getDefaultFilename())
}
startActivityForResult(intent, CODE_BACKUP_CREATE) startActivityForResult(intent, CODE_BACKUP_CREATE)
} catch (e: ActivityNotFoundException) { } catch (e: ActivityNotFoundException) {
// Handle errors where the android ROM doesn't support the built in picker // Handle errors where the android ROM doesn't support the built in picker
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){ startActivityForResult(preferences.context.getFilePicker(currentDir), CODE_BACKUP_CREATE)
startActivityForResult(preferences.context.getFilePicker(currentDir), CODE_BACKUP_CREATE)
}
} }
} }
class CreateBackupDialog : DialogController() { class CreateBackupDialog : DialogController() {
@ -236,7 +212,7 @@ class SettingsBackupController : SettingsController() {
.content(R.string.backup_choice) .content(R.string.backup_choice)
.items(options) .items(options)
.itemsDisabledIndices(0) .itemsDisabledIndices(0)
.itemsCallbackMultiChoice(arrayOf(0, 1, 2, 3, 4), { _, positions, _ -> .itemsCallbackMultiChoice(arrayOf(0, 1, 2, 3, 4)) { _, positions, _ ->
var flags = 0 var flags = 0
for (i in 1 until positions.size) { for (i in 1 until positions.size) {
when (positions[i]) { when (positions[i]) {
@ -249,7 +225,7 @@ class SettingsBackupController : SettingsController() {
(targetController as? SettingsBackupController)?.createBackup(flags) (targetController as? SettingsBackupController)?.createBackup(flags)
true true
}) }
.positiveText(R.string.action_create) .positiveText(R.string.action_create)
.negativeText(android.R.string.cancel) .negativeText(android.R.string.cancel)
.build() .build()
@ -395,7 +371,7 @@ class SettingsBackupController : SettingsController() {
.negativeText(R.string.action_open_log) .negativeText(R.string.action_open_log)
.onNegative { _, _ -> .onNegative { _, _ ->
val context = applicationContext ?: return@onNegative val context = applicationContext ?: return@onNegative
if (!path!!.isEmpty()) { if (!path.isNullOrEmpty()) {
val destFile = File(path, file) val destFile = File(path, file)
val uri = destFile.getUriCompat(context) val uri = destFile.getUriCompat(context)
val sendIntent = Intent(Intent.ACTION_VIEW).apply { val sendIntent = Intent(Intent.ACTION_VIEW).apply {

View File

@ -5,7 +5,6 @@ import android.app.Dialog
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Environment import android.os.Environment
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
@ -107,19 +106,16 @@ class SettingsDownloadController : SettingsController() {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) { when (requestCode) {
DOWNLOAD_DIR_PRE_L -> if (data != null && resultCode == Activity.RESULT_OK) { DOWNLOAD_DIR -> if (data != null && resultCode == Activity.RESULT_OK) {
val uri = Uri.fromFile(File(data.data?.path))
preferences.downloadsDirectory().set(uri?.toString() ?: "")
}
DOWNLOAD_DIR_L -> if (data != null && resultCode == Activity.RESULT_OK) {
val context = applicationContext ?: return val context = applicationContext ?: return
val uri = data.data val uri = data.data
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION Intent.FLAG_GRANT_WRITE_URI_PERMISSION
@Suppress("NewApi") if (uri != null) {
if (uri != null) @Suppress("NewApi")
context.contentResolver.takePersistableUriPermission(uri, flags) context.contentResolver.takePersistableUriPermission(uri, flags)
}
val file = UniFile.fromUri(context, uri) val file = UniFile.fromUri(context, uri)
preferences.downloadsDirectory().set(file.uri.toString()) preferences.downloadsDirectory().set(file.uri.toString())
@ -133,19 +129,11 @@ class SettingsDownloadController : SettingsController() {
} }
fun customDirectorySelected(currentDir: String) { fun customDirectorySelected(currentDir: String) {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { try {
startActivityForResult(preferences.context.getFilePicker(currentDir), DOWNLOAD_DIR_PRE_L) startActivityForResult(intent, DOWNLOAD_DIR)
} else { } catch (e: ActivityNotFoundException) {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) startActivityForResult(preferences.context.getFilePicker(currentDir), DOWNLOAD_DIR)
try {
startActivityForResult(intent, DOWNLOAD_DIR_L)
} catch (e: ActivityNotFoundException) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
startActivityForResult(preferences.context.getFilePicker(currentDir), DOWNLOAD_DIR_L)
}
}
} }
} }
@ -161,7 +149,7 @@ class SettingsDownloadController : SettingsController() {
return MaterialDialog.Builder(activity) return MaterialDialog.Builder(activity)
.items(externalDirs) .items(externalDirs)
.itemsCallbackSingleChoice(selectedIndex, { _, _, which, text -> .itemsCallbackSingleChoice(selectedIndex) { _, _, which, text ->
val target = targetController as? SettingsDownloadController val target = targetController as? SettingsDownloadController
if (which == externalDirs.lastIndex) { if (which == externalDirs.lastIndex) {
target?.customDirectorySelected(currentDir) target?.customDirectorySelected(currentDir)
@ -169,7 +157,7 @@ class SettingsDownloadController : SettingsController() {
target?.predefinedDirectorySelected(text.toString()) target?.predefinedDirectorySelected(text.toString())
} }
true true
}) }
.build() .build()
} }
@ -184,7 +172,6 @@ class SettingsDownloadController : SettingsController() {
} }
private companion object { private companion object {
const val DOWNLOAD_DIR_PRE_L = 103 const val DOWNLOAD_DIR = 104
const val DOWNLOAD_DIR_L = 104
} }
} }

View File

@ -97,7 +97,7 @@ fun syncChaptersWithSource(db: DatabaseHelper,
db.inTransaction { db.inTransaction {
val deletedChapterNumbers = TreeSet<Float>() val deletedChapterNumbers = TreeSet<Float>()
val deletedReadChapterNumbers = TreeSet<Float>() val deletedReadChapterNumbers = TreeSet<Float>()
if (!toDelete.isEmpty()) { if (toDelete.isNotEmpty()) {
for (c in toDelete) { for (c in toDelete) {
if (c.read) { if (c.read) {
deletedReadChapterNumbers.add(c.chapter_number) deletedReadChapterNumbers.add(c.chapter_number)
@ -107,7 +107,7 @@ fun syncChaptersWithSource(db: DatabaseHelper,
db.deleteChapters(toDelete).executeAsBlocking() db.deleteChapters(toDelete).executeAsBlocking()
} }
if (!toAdd.isEmpty()) { if (toAdd.isNotEmpty()) {
// Set the date fetch for new items in reverse order to allow another sorting method. // Set the date fetch for new items in reverse order to allow another sorting method.
// Sources MUST return the chapters from most to less recent, which is common. // Sources MUST return the chapters from most to less recent, which is common.
var now = Date().time var now = Date().time
@ -126,7 +126,7 @@ fun syncChaptersWithSource(db: DatabaseHelper,
db.insertChapters(toAdd).executeAsBlocking() db.insertChapters(toAdd).executeAsBlocking()
} }
if (!toChange.isEmpty()) { if (toChange.isNotEmpty()) {
db.insertChapters(toChange).executeAsBlocking() db.insertChapters(toChange).executeAsBlocking()
} }
@ -139,8 +139,8 @@ fun syncChaptersWithSource(db: DatabaseHelper,
manga.last_update = if (dateFetch == 0L) Date().time else dateFetch manga.last_update = if (dateFetch == 0L) Date().time else dateFetch
db.updateLastUpdated(manga).executeAsBlocking() db.updateLastUpdated(manga).executeAsBlocking()
} }
return Pair(toAdd.subtract(readded).toList(), toDelete.subtract(readded).toList())
return Pair(toAdd.subtract(readded).toList(), toDelete.subtract(readded).toList())
} }
//checks if the chapter in db needs updated //checks if the chapter in db needs updated
@ -148,4 +148,4 @@ private fun shouldUpdateDbChapter(dbChapter: Chapter, sourceChapter: SChapter):
return dbChapter.scanlator != sourceChapter.scanlator || dbChapter.name != sourceChapter.name || return dbChapter.scanlator != sourceChapter.scanlator || dbChapter.name != sourceChapter.name ||
dbChapter.date_upload != sourceChapter.date_upload || dbChapter.date_upload != sourceChapter.date_upload ||
dbChapter.chapter_number != sourceChapter.chapter_number dbChapter.chapter_number != sourceChapter.chapter_number
} }

View File

@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.util
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Environment import android.os.Environment
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.os.EnvironmentCompat import androidx.core.os.EnvironmentCompat
@ -45,13 +44,6 @@ object DiskUtil {
} }
} }
if (Build.VERSION.SDK_INT < 21) {
val extStorages = System.getenv("SECONDARY_STORAGE")
if (extStorages != null) {
directories += extStorages.split(":").map(::File)
}
}
return directories return directories
} }
@ -79,11 +71,7 @@ object DiskUtil {
* Scans the given file so that it can be shown in gallery apps, for example. * Scans the given file so that it can be shown in gallery apps, for example.
*/ */
fun scanMedia(context: Context, uri: Uri) { fun scanMedia(context: Context, uri: Uri) {
val action = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { val action = Intent.ACTION_MEDIA_SCANNER_SCAN_FILE
Intent.ACTION_MEDIA_MOUNTED
} else {
Intent.ACTION_MEDIA_SCANNER_SCAN_FILE
}
val mediaScanIntent = Intent(action) val mediaScanIntent = Intent(action)
mediaScanIntent.data = uri mediaScanIntent.data = uri
context.sendBroadcast(mediaScanIntent) context.sendBroadcast(mediaScanIntent)

View File

@ -216,7 +216,7 @@ object ImageUtil {
} }
private fun ByteArray.compareWith(magic: ByteArray): Boolean { private fun ByteArray.compareWith(magic: ByteArray): Boolean {
for (i in 0 until magic.size) { for (i in magic.indices) {
if (this[i] != magic[i]) return false if (this[i] != magic[i]) return false
} }
return true return true
@ -224,7 +224,7 @@ object ImageUtil {
private fun charByteArrayOf(vararg bytes: Int): ByteArray { private fun charByteArrayOf(vararg bytes: Int): ByteArray {
return ByteArray(bytes.size).apply { return ByteArray(bytes.size).apply {
for (i in 0 until bytes.size) { for (i in bytes.indices) {
set(i, bytes[i].toByte()) set(i, bytes[i].toByte())
} }
} }

View File

@ -4,12 +4,11 @@ import android.app.Application
import android.content.Context import android.content.Context
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Build import android.os.Build
import android.os.LocaleList
import android.view.ContextThemeWrapper import android.view.ContextThemeWrapper
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.util.* import java.util.Locale
/** /**
* Utility class to change the application's language in runtime. * Utility class to change the application's language in runtime.
@ -43,7 +42,7 @@ object LocaleHelper {
* *
* @param pref the string value stored in preferences. * @param pref the string value stored in preferences.
*/ */
fun getLocaleFromString(pref: String): Locale? { fun getLocaleFromString(pref: String?): Locale? {
if (pref.isNullOrEmpty()) { if (pref.isNullOrEmpty()) {
return null return null
} }
@ -90,7 +89,7 @@ object LocaleHelper {
* Updates the app's language to an activity. * Updates the app's language to an activity.
*/ */
fun updateConfiguration(wrapper: ContextThemeWrapper) { fun updateConfiguration(wrapper: ContextThemeWrapper) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && appLocale != null) { if (appLocale != null) {
val config = Configuration(preferences.context.resources.configuration) val config = Configuration(preferences.context.resources.configuration)
config.setLocale(appLocale) config.setLocale(appLocale)
wrapper.applyOverrideConfiguration(config) wrapper.applyOverrideConfiguration(config)
@ -138,7 +137,7 @@ object LocaleHelper {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
newConfig.setLocale(locale) newConfig.setLocale(locale)
} else { } else {
newConfig.setLocales(LocaleList(locale)) newConfig.setLocale(locale)
} }
return newConfig return newConfig
} }

View File

@ -22,7 +22,7 @@ object SharedData {
* @param data the object to put. * @param data the object to put.
*/ */
fun <T : Any> put(data: T) { fun <T : Any> put(data: T) {
map.put(data.javaClass, data) map[data.javaClass] = data
} }
/** /**

View File

@ -1,6 +1,6 @@
package eu.kanade.tachiyomi.util package eu.kanade.tachiyomi.util
import java.lang.Math.floor import kotlin.math.floor
/** /**
* Replaces the given string to have at most [count] characters using [replacement] at its end. * Replaces the given string to have at most [count] characters using [replacement] at its end.
@ -29,4 +29,4 @@ fun String.truncateCenter(count: Int, replacement: String = "..."): String{
val pieceLength:Int = floor((count - replacement.length).div(2.0)).toInt() val pieceLength:Int = floor((count - replacement.length).div(2.0)).toInt()
return "${ take(pieceLength) }$replacement${ takeLast(pieceLength) }" return "${ take(pieceLength) }$replacement${ takeLast(pieceLength) }"
} }

View File

@ -113,7 +113,7 @@ inline fun View.visibleIf(block: () -> Boolean) {
* @param random random color * @param random random color
*/ */
fun View.getRound(text: String, random : Boolean = true): TextDrawable { fun View.getRound(text: String, random : Boolean = true): TextDrawable {
val size = Math.min(this.width, this.height) val size = min(this.width, this.height)
return TextDrawable.builder() return TextDrawable.builder()
.beginConfig() .beginConfig()
.width(size) .width(size)

View File

@ -36,7 +36,6 @@ abstract class WebViewClientCompat : WebViewClient() {
return shouldOverrideUrlCompat(view, url) return shouldOverrideUrlCompat(view, url)
} }
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
final override fun shouldInterceptRequest( final override fun shouldInterceptRequest(
view: WebView, view: WebView,
request: WebResourceRequest request: WebResourceRequest

View File

@ -1,9 +1,10 @@
package eu.kanade.tachiyomi.widget package eu.kanade.tachiyomi.widget
import android.content.Context import android.content.Context
import android.util.AttributeSet
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.util.AttributeSet import kotlin.math.max
class AutofitRecyclerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : class AutofitRecyclerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
androidx.recyclerview.widget.RecyclerView(context, attrs) { androidx.recyclerview.widget.RecyclerView(context, attrs) {
@ -37,7 +38,7 @@ class AutofitRecyclerView @JvmOverloads constructor(context: Context, attrs: Att
override fun onMeasure(widthSpec: Int, heightSpec: Int) { override fun onMeasure(widthSpec: Int, heightSpec: Int) {
super.onMeasure(widthSpec, heightSpec) super.onMeasure(widthSpec, heightSpec)
if (spanCount == 0 && columnWidth > 0) { if (spanCount == 0 && columnWidth > 0) {
val count = Math.max(1, measuredWidth / columnWidth) val count = max(1, measuredWidth / columnWidth)
spanCount = count spanCount = count
} }
} }

View File

@ -21,13 +21,13 @@ class CustomLayoutPickerActivity : FilePickerActivity() {
} }
class CustomLayoutFilePickerFragment : FilePickerFragment() { class CustomLayoutFilePickerFragment : FilePickerFragment() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): androidx.recyclerview.widget.RecyclerView.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
when (viewType) { return when (viewType) {
LogicHandler.VIEWTYPE_DIR -> { LogicHandler.VIEWTYPE_DIR -> {
val view = parent.inflate(R.layout.common_listitem_dir) val view = parent.inflate(R.layout.common_listitem_dir)
return DirViewHolder(view) DirViewHolder(view)
} }
else -> return super.onCreateViewHolder(parent, viewType) else -> super.onCreateViewHolder(parent, viewType)
} }
} }
} }

View File

@ -16,32 +16,26 @@ class ElevationAppBarLayout @JvmOverloads constructor(
private var origStateAnimator: StateListAnimator? = null private var origStateAnimator: StateListAnimator? = null
init { init {
if (Build.VERSION.SDK_INT >= 21) { origStateAnimator = stateListAnimator
origStateAnimator = stateListAnimator
}
} }
fun enableElevation() { fun enableElevation() {
if (Build.VERSION.SDK_INT >= 21) { stateListAnimator = origStateAnimator
stateListAnimator = origStateAnimator
}
} }
fun disableElevation() { fun disableElevation() {
if (Build.VERSION.SDK_INT >= 21) { stateListAnimator = StateListAnimator().apply {
stateListAnimator = StateListAnimator().apply { val objAnimator = ObjectAnimator.ofFloat(this, "elevation", 0f)
val objAnimator = ObjectAnimator.ofFloat(this, "elevation", 0f)
// Enabled and collapsible, but not collapsed means not elevated // Enabled and collapsible, but not collapsed means not elevated
addState(intArrayOf(android.R.attr.enabled, R.attr.state_collapsible, -R.attr.state_collapsed), addState(intArrayOf(android.R.attr.enabled, R.attr.state_collapsible, -R.attr.state_collapsed),
objAnimator) objAnimator)
// Default enabled state // Default enabled state
addState(intArrayOf(android.R.attr.enabled), objAnimator) addState(intArrayOf(android.R.attr.enabled), objAnimator)
// Disabled state // Disabled state
addState(IntArray(0), objAnimator) addState(IntArray(0), objAnimator)
}
} }
} }

View File

@ -13,7 +13,7 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.getResourceColor import eu.kanade.tachiyomi.util.getResourceColor
/** /**
* An alternative implementation of [android.support.design.widget.NavigationView], without menu * An alternative implementation of [com.google.android.material.navigation.NavigationView], without menu
* inflation and allowing customizable items (multiple selections, custom views, etc). * inflation and allowing customizable items (multiple selections, custom views, etc).
*/ */
open class ExtendedNavigationView @JvmOverloads constructor( open class ExtendedNavigationView @JvmOverloads constructor(
@ -210,8 +210,7 @@ open class ExtendedNavigationView @JvmOverloads constructor(
@CallSuper @CallSuper
override fun getItemViewType(position: Int): Int { override fun getItemViewType(position: Int): Int {
val item = items[position] return when (items[position]) {
return when (item) {
is Item.Header -> VIEW_TYPE_HEADER is Item.Header -> VIEW_TYPE_HEADER
is Item.Separator -> VIEW_TYPE_SEPARATOR is Item.Separator -> VIEW_TYPE_SEPARATOR
is Item.Radio -> VIEW_TYPE_RADIO is Item.Radio -> VIEW_TYPE_RADIO

View File

@ -1,34 +0,0 @@
package eu.kanade.tachiyomi.widget
import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.google.android.material.floatingactionbutton.FloatingActionButton
import androidx.core.view.ViewCompat
import android.view.View
abstract class FABAnimationBase : FloatingActionButton.Behavior() {
var isAnimatingOut = false
override fun onStartNestedScroll(coordinatorLayout: CoordinatorLayout, child: FloatingActionButton,
directTargetChild: View, target: View, axes: Int, type: Int): Boolean {
// Ensure we react to vertical scrolling
return axes == ViewCompat.SCROLL_AXIS_VERTICAL ||
super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, axes, type)
}
override fun onNestedScroll(coordinatorLayout: CoordinatorLayout, child: FloatingActionButton,
target: View, dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int,
dyUnconsumed: Int, type: Int) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type)
if (dyConsumed > 0 && !isAnimatingOut && child.visibility == View.VISIBLE) {
// User scrolled down and the FAB is currently visible -> hide the FAB
animateOut(child)
} else if (dyConsumed < 0 && child.visibility != View.VISIBLE) {
// User scrolled up and the FAB is currently not visible -> show the FAB
animateIn(child)
}
}
abstract fun animateOut(button: FloatingActionButton)
abstract fun animateIn(button: FloatingActionButton)
}

View File

@ -1,87 +0,0 @@
package eu.kanade.tachiyomi.widget
import android.content.Context
import android.content.res.Configuration
import android.graphics.Rect
import android.util.AttributeSet
import android.view.View
import android.view.animation.Animation
import android.view.animation.AnimationUtils
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.snackbar.Snackbar
import eu.kanade.tachiyomi.R
import kotlin.math.min
@Suppress("unused", "UNUSED_PARAMETER")
class FABAnimationUpDown @JvmOverloads constructor(ctx: Context, attrs: AttributeSet? = null) :
FABAnimationBase() {
private val INTERPOLATOR = FastOutSlowInInterpolator()
private val outAnimation by lazy {
AnimationUtils.loadAnimation(ctx, R.anim.fab_hide_to_bottom).apply {
duration = 200
interpolator = INTERPOLATOR
}
}
private val inAnimation by lazy {
AnimationUtils.loadAnimation(ctx, R.anim.fab_show_from_bottom).apply {
duration = 200
interpolator = INTERPOLATOR
}
}
override fun animateOut(button: FloatingActionButton) {
outAnimation.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationStart(animation: Animation) {
isAnimatingOut = true
}
override fun onAnimationEnd(animation: Animation) {
isAnimatingOut = false
button.visibility = View.INVISIBLE
}
override fun onAnimationRepeat(animation: Animation) {
}
})
button.startAnimation(outAnimation)
}
override fun animateIn(button: FloatingActionButton) {
button.visibility = View.VISIBLE
button.startAnimation(inAnimation)
}
override fun onDependentViewChanged(parent: CoordinatorLayout, child: FloatingActionButton, dependency: View): Boolean {
if (isTablet(child.context)) return true
val translationY = getFabTranslationYForSnackbar(parent, child)
child.translationY = translationY
return true
}
private fun isTablet(context: Context): Boolean {
return (context.resources.configuration.screenLayout and Configuration
.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE
}
private fun getFabTranslationYForSnackbar(parent: CoordinatorLayout, fab:
FloatingActionButton): Float {
var minOffset = 0f
val dependencies = parent.getDependencies(fab)
for (i in 0 until dependencies.size) {
val view = dependencies[i]
if (view is Snackbar.SnackbarLayout) {
minOffset = min(minOffset, view.translationY - view.height)
}
}
return minOffset
}
override fun getInsetDodgeRect(parent: CoordinatorLayout, child: FloatingActionButton, rect: Rect): Boolean {
rect.set(child.left, child.top + 100, child.right, child.bottom - 1000)
return true
}
}

View File

@ -5,6 +5,7 @@ import android.os.Parcelable
import android.util.AttributeSet import android.util.AttributeSet
import android.widget.SeekBar import android.widget.SeekBar
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import kotlin.math.abs
class NegativeSeekBar @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : class NegativeSeekBar @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
@ -28,21 +29,21 @@ class NegativeSeekBar @JvmOverloads constructor(context: Context, attrs: Attribu
super.setOnSeekBarChangeListener(object : OnSeekBarChangeListener { super.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, value: Int, fromUser: Boolean) { override fun onProgressChanged(seekBar: SeekBar?, value: Int, fromUser: Boolean) {
listener?.let { it.onProgressChanged(seekBar, minValue + value, fromUser) } listener?.onProgressChanged(seekBar, minValue + value, fromUser)
} }
override fun onStartTrackingTouch(p0: SeekBar?) { override fun onStartTrackingTouch(p0: SeekBar?) {
listener?.let { it.onStartTrackingTouch(p0) } listener?.onStartTrackingTouch(p0)
} }
override fun onStopTrackingTouch(p0: SeekBar?) { override fun onStopTrackingTouch(p0: SeekBar?) {
listener?.let { it.onStopTrackingTouch(p0) } listener?.onStopTrackingTouch(p0)
} }
}) })
} }
override fun setProgress(progress: Int) { override fun setProgress(progress: Int) {
super.setProgress(Math.abs(minValue) + progress) super.setProgress(abs(minValue) + progress)
} }
fun setMinSeek(minValue: Int) { fun setMinSeek(minValue: Int) {
@ -66,4 +67,4 @@ class NegativeSeekBar @JvmOverloads constructor(context: Context, attrs: Attribu
super.setProgress(origProgress) super.setProgress(origProgress)
} }
} }

View File

@ -30,7 +30,7 @@ class PTSansTextView @JvmOverloads constructor(context: Context, attrs: Attribut
Typeface.createFromAsset(context.assets, when (typeface) { Typeface.createFromAsset(context.assets, when (typeface) {
PTSANS_NARROW -> "fonts/PTSans-Narrow.ttf" PTSANS_NARROW -> "fonts/PTSans-Narrow.ttf"
PTSANS_NARROW_BOLD -> "fonts/PTSans-NarrowBold.ttf" PTSANS_NARROW_BOLD -> "fonts/PTSans-NarrowBold.ttf"
else -> throw IllegalArgumentException("Font not found " + typeface) else -> throw IllegalArgumentException("Font not found $typeface")
}) })
}) })

View File

@ -2,14 +2,11 @@ package eu.kanade.tachiyomi.widget
import android.animation.Animator import android.animation.Animator
import android.animation.AnimatorListenerAdapter import android.animation.AnimatorListenerAdapter
import android.annotation.TargetApi
import android.content.Context import android.content.Context
import android.os.Build
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import android.view.ViewAnimationUtils import android.view.ViewAnimationUtils
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
class RevealAnimationView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : class RevealAnimationView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
View(context, attrs) { View(context, attrs) {
@ -21,28 +18,25 @@ class RevealAnimationView @JvmOverloads constructor(context: Context, attrs: Att
* @param initialRadius size of radius of animation * @param initialRadius size of radius of animation
*/ */
fun hideRevealEffect(centerX: Int, centerY: Int, initialRadius: Int) { fun hideRevealEffect(centerX: Int, centerY: Int, initialRadius: Int) {
if (Build.VERSION.SDK_INT >= 21) { // Make the view visible.
this.visibility = View.VISIBLE
// Make the view visible. // Create the animation (the final radius is zero).
this.visibility = View.VISIBLE val anim = ViewAnimationUtils.createCircularReveal(
this, centerX, centerY, initialRadius.toFloat(), 0f)
// Create the animation (the final radius is zero). // Set duration of animation.
val anim = ViewAnimationUtils.createCircularReveal( anim.duration = 500
this, centerX, centerY, initialRadius.toFloat(), 0f)
// Set duration of animation. // make the view invisible when the animation is done
anim.duration = 500 anim.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
this@RevealAnimationView.visibility = View.INVISIBLE
}
})
// make the view invisible when the animation is done anim.start()
anim.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
this@RevealAnimationView.visibility = View.INVISIBLE
}
})
anim.start()
}
} }
/** /**
@ -55,25 +49,20 @@ class RevealAnimationView @JvmOverloads constructor(context: Context, attrs: Att
* @return sdk version lower then 21 * @return sdk version lower then 21
*/ */
fun showRevealEffect(centerX: Int, centerY: Int, listener: Animator.AnimatorListener): Boolean { fun showRevealEffect(centerX: Int, centerY: Int, listener: Animator.AnimatorListener): Boolean {
if (Build.VERSION.SDK_INT >= 21) { this.visibility = View.VISIBLE
this.visibility = View.VISIBLE val height = this.height
val height = this.height // Create animation
val anim = ViewAnimationUtils.createCircularReveal(
this, centerX, centerY, 0f, height.toFloat())
// Create animation // Set duration of animation
val anim = ViewAnimationUtils.createCircularReveal( anim.duration = 350
this, centerX, centerY, 0f, height.toFloat())
// Set duration of animation anim.addListener(listener)
anim.duration = 350 anim.start()
return true
anim.addListener(listener)
anim.start()
return true
}
return false
} }
} }

View File

@ -2,18 +2,19 @@ package eu.kanade.tachiyomi.widget
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import com.google.android.material.R
import com.google.android.material.textfield.TextInputLayout
import androidx.core.view.ViewCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.appcompat.widget.TintTypedArray
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.* import android.widget.*
import androidx.appcompat.widget.TintTypedArray
import androidx.core.view.ViewCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.R
import com.google.android.material.internal.ScrimInsetsFrameLayout import com.google.android.material.internal.ScrimInsetsFrameLayout
import com.google.android.material.textfield.TextInputLayout
import eu.kanade.tachiyomi.util.inflate import eu.kanade.tachiyomi.util.inflate
import kotlin.math.min
import eu.kanade.tachiyomi.R as TR import eu.kanade.tachiyomi.R as TR
@Suppress("LeakingThis") @Suppress("LeakingThis")
@ -67,7 +68,7 @@ open class SimpleNavigationView @JvmOverloads constructor(
override fun onMeasure(widthSpec: Int, heightSpec: Int) { override fun onMeasure(widthSpec: Int, heightSpec: Int) {
val width = when (MeasureSpec.getMode(widthSpec)) { val width = when (MeasureSpec.getMode(widthSpec)) {
MeasureSpec.AT_MOST -> MeasureSpec.makeMeasureSpec( MeasureSpec.AT_MOST -> MeasureSpec.makeMeasureSpec(
Math.min(MeasureSpec.getSize(widthSpec), maxWidth), MeasureSpec.EXACTLY) min(MeasureSpec.getSize(widthSpec), maxWidth), MeasureSpec.EXACTLY)
MeasureSpec.UNSPECIFIED -> MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.EXACTLY) MeasureSpec.UNSPECIFIED -> MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.EXACTLY)
else -> widthSpec else -> widthSpec
} }

View File

@ -25,7 +25,7 @@ abstract class LoginDialogPreference(bundle: Bundle? = null) : DialogController(
var requestSubscription: Subscription? = null var requestSubscription: Subscription? = null
override fun onCreateDialog(savedState: Bundle?): Dialog { override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val dialog = MaterialDialog.Builder(activity!!) val dialog = MaterialDialog.Builder(activity!!)
.customView(R.layout.pref_account_login, false) .customView(R.layout.pref_account_login, false)
.negativeText(android.R.string.cancel) .negativeText(android.R.string.cancel)

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:color="@color/primary_text_disabled_material_dark" />
<item android:color="@color/primary_text_default_material_dark" />
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 902 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple
xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/selectorColorDark"
>
<item>
<selector>
<item android:state_selected="true">
<color android:color="@color/selectorColorDark"/>
</item>
<item android:state_activated="true">
<color android:color="@color/selectorColorDark"/>
</item>
<item>
<color android:color="@color/md_black_1000"/>
</item>
</selector>
</item>
</ripple>

View File

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple
xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/colorAccentDark"
>
<item>
<selector>
<item android:state_selected="true">
<color android:color="@color/selectorColorDark"/>
</item>
<item android:state_activated="true">
<color android:color="@color/selectorColorDark"/>
</item>
<item>
<color android:color="@color/backgroundDark"/>
</item>
</selector>
</item>
</ripple>

View File

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple
xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/colorAccentLight"
>
<item>
<selector>
<item android:state_selected="true">
<color android:color="@color/selectorColorLight"/>
</item>
<item android:state_activated="true">
<color android:color="@color/selectorColorLight"/>
</item>
<item>
<color android:color="@color/backgroundLight"/>
</item>
</selector>
</item>
</ripple>

View File

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/rippleColorDark">
<item>
<selector>
<item android:state_selected="true">
<color android:color="@color/rippleColorDark"/>
</item>
<item android:state_activated="true">
<color android:color="@color/rippleColorDark"/>
</item>
<item>
<color android:color="@color/md_black_1000"/>
</item>
</selector>
</item>
</ripple>

Some files were not shown because too many files have changed in this diff Show More