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 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:
components:
- build-tools-29.0.2
- android-28
- android-29
- extra-android-m2repository
- extra-google-m2repository
- extra-android-support
@ -11,7 +11,7 @@ android:
licenses:
- android-sdk-license-.+
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
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;

View File

@ -1,5 +1,5 @@
# ![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)

View File

@ -28,7 +28,8 @@
android:networkSecurityConfig="@xml/network_security_config">
<activity
android:name=".ui.main.MainActivity"
android:launchMode="singleTask">
android:launchMode="singleTask"
android:theme="@style/Theme.Splash">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<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,
httpMethod = org.acra.sender.HttpSender.Method.PUT,
buildConfigClass = BuildConfig::class,
excludeMatchingSharedPreferencesKeys = arrayOf(".*username.*", ".*password.*", ".*token.*")
excludeMatchingSharedPreferencesKeys = [".*username.*", ".*password.*", ".*token.*"]
)
open class App : Application(), LifecycleObserver {

View File

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

View File

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

View File

@ -42,9 +42,7 @@ object TrackTypeAdapter {
beginObject()
while (hasNext()) {
if (peek() == JsonToken.NAME) {
val name = nextName()
when (name) {
when (nextName()) {
TITLE -> track.title = nextString()
SYNC -> track.sync_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) {
val (key, value) = transform(element)
if (key != null) {
destination.put(key, value)
destination[key] = value
}
}
return destination

View File

@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.data.download
import android.content.Context
import android.net.Uri
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.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
@ -46,9 +47,13 @@ class DownloadProvider(private val context: Context) {
* @param source the source of the manga.
*/
internal fun getMangaDir(manga: Manga, source: Source): UniFile {
return downloadsDir
.createDirectory(getSourceDirName(source))
.createDirectory(getMangaDirName(manga))
try {
return downloadsDir
.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 }
downloadsRelay.call(pending)
return !pending.isEmpty()
return pending.isNotEmpty()
}
/**

View File

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

View File

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

View File

@ -57,8 +57,8 @@ abstract class TrackService(val id: Int) {
}
open val isLogged: Boolean
get() = !getUsername().isEmpty() &&
!getPassword().isEmpty()
get() = getUsername().isNotEmpty() &&
getPassword().isNotEmpty()
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)

View File

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

View File

@ -9,7 +9,7 @@ data class OAuth(
val user_id: Long?
) {
// Access token refersh before expired
// Access token refresh before expired
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 api by lazy { MyanimelistApi(client, interceptor) }
private val api by lazy { MyAnimeListApi(client, interceptor) }
override val name: String
get() = "MyAnimeList"

View File

@ -12,7 +12,10 @@ import eu.kanade.tachiyomi.util.selectInt
import eu.kanade.tachiyomi.util.selectText
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import org.json.JSONObject
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
@ -24,7 +27,7 @@ import java.io.InputStreamReader
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()
@ -35,8 +38,7 @@ class MyanimelistApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.flatMap { Observable.from(it) }
.filter { it.title.contains(realQuery, true) }
.toList()
}
else {
} else {
client.newCall(GET(searchUrl(query)))
.asObservable()
.flatMap { response ->
@ -264,7 +266,7 @@ class MyanimelistApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.put("score", track.score)
.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()!!

View File

@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.data.track.myanimelist
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import okio.Buffer
import org.json.JSONObject
@ -15,7 +16,7 @@ class MyAnimeListInterceptor(private val myanimelist: Myanimelist): Interceptor
val request = chain.request()
var response = chain.proceed(updateRequest(request))
if (response.code == 400){
if (response.code == 400) {
myanimelist.refreshLogin()
response = chain.proceed(updateRequest(request))
}
@ -45,15 +46,14 @@ class MyAnimeListInterceptor(private val myanimelist: Myanimelist): Interceptor
private fun updateFormBody(requestBody: RequestBody): RequestBody {
val formString = bodyToString(requestBody)
return RequestBody.create(requestBody.contentType(),
"$formString${if (formString.isNotEmpty()) "&" else ""}${MyanimelistApi.CSRF}=${myanimelist.getCSRF()}")
return "$formString${if (formString.isNotEmpty()) "&" else ""}${MyAnimeListApi.CSRF}=${myanimelist.getCSRF()}".toRequestBody(requestBody.contentType())
}
private fun updateJsonBody(requestBody: RequestBody): RequestBody {
val jsonString = bodyToString(requestBody)
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 okhttp3.*
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import rx.Observable
import uy.kohesive.injekt.injectLazy
@ -37,7 +41,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
"status" to track.toShikimoriStatus()
)
)
val body = RequestBody.create(jsonime, payload.toString())
val body = payload.toString().toRequestBody(jsonime)
val request = Request.Builder()
.url("$apiUrl/v2/user_rates")
.post(body)

View File

@ -94,7 +94,7 @@ internal class ExtensionInstallReceiver(private val listener: Listener) :
private suspend fun getExtensionFromIntent(context: Context, intent: Intent?): LoadResult {
val pkgName = getPackageNameFromIntent(intent) ?:
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? {
val signatures = pkgInfo.signatures
return if (signatures != null && !signatures.isEmpty()) {
return if (signatures != null && signatures.isNotEmpty()) {
Hash.sha256(signatures.first().toByteArray())
} else {
null

View File

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

View File

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

View File

@ -1,17 +1,9 @@
package eu.kanade.tachiyomi.network
import android.content.Context
import android.os.Build
import okhttp3.*
import okhttp3.Cache
import okhttp3.OkHttpClient
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) {
@ -19,99 +11,15 @@ class NetworkHelper(context: Context) {
private val cacheSize = 5L * 1024 * 1024 // 5 MiB
val cookieManager = AndroidCookieJar(context)
val cookieManager = AndroidCookieJar()
val client = OkHttpClient.Builder()
.cookieJar(cookieManager)
.cache(Cache(cacheDir, cacheSize))
.enableTLS12()
.build()
val cloudflareClient = client.newBuilder()
.addInterceptor(CloudflareInterceptor(context))
.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 {
const val QUEUE = 0
const val LOAD_PAGE = 1
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 {
dialog = onCreateDialog(savedViewState)
//dialog!!.ownerActivity = activity
dialog!!.setOwnerActivity(activity!!)
dialog!!.setOnDismissListener { dismissDialog() }
if (savedViewState != null) {
val dialogState = savedViewState.getBundle(SAVED_DIALOG_STATE_TAG)

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.ui.base.holder
import android.os.Build
import android.view.View
import android.view.ViewGroup
import eu.davidea.flexibleadapter.FlexibleAdapter
@ -51,10 +50,6 @@ interface SlicedHolder {
slice.showRightTopRect(topRect)
slice.showLeftBottomRect(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)
}
@ -68,4 +63,4 @@ interface SlicedHolder {
val margin
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 top = child.bottom + params.bottomMargin
val bottom = top + divider.intrinsicHeight
val left = parent.paddingLeft + holder.margin
val right = parent.width - parent.paddingRight - holder.margin
val left = parent.paddingStart + holder.margin
val right = parent.width - parent.paddingEnd - holder.margin
divider.setBounds(left, top, right, bottom)
divider.draw(c)

View File

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

View File

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

View File

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

View File

@ -235,11 +235,11 @@ class CategoryController : NucleusController<CategoryPresenter>(),
*/
override fun onItemClick(view: View?, position: Int): Boolean {
// 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)
return true
true
} else {
return false
false
}
}

View File

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

View File

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

View File

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

View File

@ -138,7 +138,7 @@ open class ExtensionController : NucleusController<ExtensionPresenter>(),
val searchView = searchItem.actionView as SearchView
searchView.maxWidth = Int.MAX_VALUE
if (!query.isEmpty()) {
if (query.isNotEmpty()) {
searchItem.expandActionView()
searchView.setQuery(query, true)
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 top = child.bottom + params.bottomMargin
val bottom = top + divider.intrinsicHeight
val left = parent.paddingLeft + holder.margin
val right = parent.width - parent.paddingRight - holder.margin
val left = parent.paddingStart + holder.margin
val right = parent.width - parent.paddingEnd - holder.margin
divider.setBounds(left, top, right, bottom)
divider.draw(c)

View File

@ -218,8 +218,7 @@ class LibraryController(
override fun createSecondaryDrawer(drawer: androidx.drawerlayout.widget.DrawerLayout): ViewGroup {
val view = drawer.inflate(R.layout.library_drawer) as LibraryNavigationView
navView = view
drawer.setDrawerLockMode(androidx.drawerlayout.widget.DrawerLayout.LOCK_MODE_UNLOCKED,
GravityCompat.END)
drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END)
navView?.onGroupClicked = { 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.checked = true
preferences.libraryAsList().set(if (item == list) true else false)
preferences.libraryAsList().set(item == list)
item.group.items.forEach { adapter.notifyItemChanged(it) }
}
}
}
}

View File

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

View File

@ -12,7 +12,7 @@ import it.gmariotti.changelibs.library.view.ChangeLogRecyclerView
class ChangelogDialogController : DialogController() {
override fun onCreateDialog(savedState: Bundle?): Dialog {
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val activity = activity!!
val view = WhatsNewRecyclerView(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
}
}
}
}

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.
val query = intent.getStringExtra(SearchManager.QUERY)
if (query != null && !query.isEmpty()) {
if (query != null && query.isNotEmpty()) {
if (router.backstackSize > 1) {
router.popToRoot()
}
@ -325,7 +325,7 @@ class MainActivity : BaseActivity() {
INTENT_SEARCH -> {
val query = intent.getStringExtra(INTENT_SEARCH_QUERY)
val filter = intent.getStringExtra(INTENT_SEARCH_FILTER)
if (query != null && !query.isEmpty()) {
if (query != null && query.isNotEmpty()) {
if (router.backstackSize > 1) {
router.popToRoot()
}

View File

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

View File

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

View File

@ -27,6 +27,7 @@ class DownloadChaptersDialog<T>(bundle: Bundle? = null) : DialogController(bundl
).map { activity.getString(it) }
return MaterialDialog.Builder(activity)
.title(R.string.manga_download)
.negativeText(android.R.string.cancel)
.items(choices)
.itemsCallback { _, _, position, _ ->
@ -39,4 +40,4 @@ class DownloadChaptersDialog<T>(bundle: Bundle? = null) : DialogController(bundl
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.graphics.drawable.IconCompat
import com.afollestad.materialdialogs.MaterialDialog
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
@ -240,11 +242,7 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
}
// If manga source is known update source TextView.
manga_source.text = if (source == null) {
view.context.getString(R.string.unknown)
} else {
source.toString()
}
manga_source.text = source?.toString() ?: view.context.getString(R.string.unknown)
// Update genres list
if (manga.genre.isNullOrBlank().not()) {
@ -580,17 +578,18 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
3 -> centerCrop().transform(MaskTransformation(R.drawable.mask_star))
}
}
.into(object : CustomTarget<Bitmap>(96, 96) {
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
.listener(object : RequestListener<Bitmap> {
override fun onResourceReady(resource: Bitmap, model: Any, target: Target<Bitmap>, dataSource: DataSource, isFirstResource: Boolean): Boolean {
createShortcut(resource)
return true
}
override fun onLoadCleared(placeholder: Drawable?) { }
override fun onLoadFailed(errorDrawable: Drawable?) {
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Bitmap>?, isFirstResource: Boolean): Boolean {
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 {
val item = item
val statusList = item.service.getStatusList().orEmpty()
val statusList = item.service.getStatusList()
val statusString = statusList.mapNotNull { item.service.getStatus(it) }
val selectedIndex = statusList.indexOf(item.track?.status)
@ -40,10 +40,10 @@ class SetTrackStatusDialog<T> : DialogController
.title(R.string.status)
.negativeText(android.R.string.cancel)
.items(statusString)
.itemsCallbackSingleChoice(selectedIndex, { _, _, i, _ ->
.itemsCallbackSingleChoice(selectedIndex) { _, _, i, _ ->
(targetController as? Listener)?.setStatus(item, i)
true
})
}
.build()
}
@ -55,4 +55,4 @@ class SetTrackStatusDialog<T> : DialogController
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())
if (track != null) {
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_chapters.text = "${track.last_chapter_read}/" +
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))!!
}
override fun onCreateDialog(savedState: Bundle?): Dialog {
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val dialog = MaterialDialog.Builder(activity!!)
.customView(R.layout.track_search_dialog, false)
.positiveText(android.R.string.ok)
@ -63,7 +63,7 @@ class TrackSearchDialog : DialogController {
}
dialogView = dialog.view
onViewCreated(dialog.view, savedState)
onViewCreated(dialog.view, savedViewState)
return dialog
}

View File

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

View File

@ -147,7 +147,8 @@ class SearchController(
preferences.migrateFlags().set(newValue)
true
}.positiveText(R.string.migrate)
}
.positiveText(R.string.migrate)
.negativeText(R.string.copy)
.neutralText(android.R.string.cancel)
.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.graphics.Bitmap
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.view.KeyEvent
import android.view.Menu
@ -68,6 +67,7 @@ import uy.kohesive.injekt.injectLazy
import java.io.File
import java.util.Date
import java.util.concurrent.TimeUnit
import kotlin.math.abs
/**
* 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) {
// Calculate and set reader brightness.
val readerBrightness = if (value > 0) {
value / 100f
} else if (value < 0) {
0.01f
} else WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
val readerBrightness = when {
value > 0 -> {
value / 100f
}
value < 0 -> {
0.01f
}
else -> WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
}
window.attributes = window.attributes.apply { screenBrightness = readerBrightness }
// Set black overlay visibility.
if (value < 0) {
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))
} else {
brightness_overlay.visibility = View.GONE

View File

@ -1,12 +1,12 @@
package eu.kanade.tachiyomi.ui.reader
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.ViewGroup
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.data.preference.PreferencesHelper
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.SimpleSeekBarListener
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.android.schedulers.AndroidSchedulers
import rx.subscriptions.CompositeSubscription
import uy.kohesive.injekt.injectLazy
import java.util.concurrent.TimeUnit
import kotlin.math.abs
/**
* Color filter sheet to toggle custom filter and brightness overlay.
@ -221,7 +223,7 @@ class ReaderColorFilterSheet(activity: ReaderActivity) : BottomSheetDialog(activ
// Set black overlay visibility.
if (value < 0) {
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))
} else {
brightness_overlay.visibility = View.GONE

View File

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

View File

@ -90,7 +90,7 @@ class ReaderPresenter(
val chaptersForReader =
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 }
if (find == null) {
list.add(selectedChapter)

View File

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

View File

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

View File

@ -5,6 +5,7 @@ import android.os.Handler
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.ViewConfiguration
import kotlin.math.abs
/**
* 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 -> {
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)
}
}

View File

@ -16,6 +16,7 @@ import android.view.animation.LinearInterpolator
import android.view.animation.RotateAnimation
import eu.kanade.tachiyomi.R
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
@ -75,7 +76,7 @@ class ReaderProgressBar @JvmOverloads constructor(
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
val diameter = Math.min(width, height)
val diameter = min(width, height)
val thickness = diameter / 10f
val pad = thickness / 2f
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.AnimatorSet
import android.animation.ValueAnimator
import android.annotation.TargetApi
import android.content.Context
import android.os.Build
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.util.AttributeSet
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
import android.view.ViewConfiguration
import android.view.animation.DecelerateInterpolator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.ui.reader.viewer.GestureDetectorWithLongTap
import kotlin.math.abs
/**
* Implementation of a [RecyclerView] used by the webtoon reader.
@ -58,7 +57,6 @@ open class WebtoonRecyclerView @JvmOverloads constructor(
firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
}
@TargetApi(Build.VERSION_CODES.KITKAT)
override fun onScrollStateChanged(state: Int) {
super.onScrollStateChanged(state)
val layoutManager = layoutManager
@ -270,7 +268,7 @@ open class WebtoonRecyclerView @JvmOverloads constructor(
if (!isZoomDragging && currentScale > 1f) {
var startScroll = false
if (Math.abs(dx) > touchSlop) {
if (abs(dx) > touchSlop) {
if (dx < 0) {
dx += touchSlop
} else {
@ -278,7 +276,7 @@ open class WebtoonRecyclerView @JvmOverloads constructor(
}
startScroll = true
}
if (Math.abs(dy) > touchSlop) {
if (abs(dy) > touchSlop) {
if (dy < 0) {
dy += touchSlop
} else {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -97,7 +97,7 @@ fun syncChaptersWithSource(db: DatabaseHelper,
db.inTransaction {
val deletedChapterNumbers = TreeSet<Float>()
val deletedReadChapterNumbers = TreeSet<Float>()
if (!toDelete.isEmpty()) {
if (toDelete.isNotEmpty()) {
for (c in toDelete) {
if (c.read) {
deletedReadChapterNumbers.add(c.chapter_number)
@ -107,7 +107,7 @@ fun syncChaptersWithSource(db: DatabaseHelper,
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.
// Sources MUST return the chapters from most to less recent, which is common.
var now = Date().time
@ -126,7 +126,7 @@ fun syncChaptersWithSource(db: DatabaseHelper,
db.insertChapters(toAdd).executeAsBlocking()
}
if (!toChange.isEmpty()) {
if (toChange.isNotEmpty()) {
db.insertChapters(toChange).executeAsBlocking()
}
@ -139,8 +139,8 @@ fun syncChaptersWithSource(db: DatabaseHelper,
manga.last_update = if (dateFetch == 0L) Date().time else dateFetch
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
@ -148,4 +148,4 @@ private fun shouldUpdateDbChapter(dbChapter: Chapter, sourceChapter: SChapter):
return dbChapter.scanlator != sourceChapter.scanlator || dbChapter.name != sourceChapter.name ||
dbChapter.date_upload != sourceChapter.date_upload ||
dbChapter.chapter_number != sourceChapter.chapter_number
}
}

View File

@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.util
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Environment
import androidx.core.content.ContextCompat
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
}
@ -79,11 +71,7 @@ object DiskUtil {
* Scans the given file so that it can be shown in gallery apps, for example.
*/
fun scanMedia(context: Context, uri: Uri) {
val action = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
Intent.ACTION_MEDIA_MOUNTED
} else {
Intent.ACTION_MEDIA_SCANNER_SCAN_FILE
}
val action = Intent.ACTION_MEDIA_SCANNER_SCAN_FILE
val mediaScanIntent = Intent(action)
mediaScanIntent.data = uri
context.sendBroadcast(mediaScanIntent)

View File

@ -216,7 +216,7 @@ object ImageUtil {
}
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
}
return true
@ -224,7 +224,7 @@ object ImageUtil {
private fun charByteArrayOf(vararg bytes: Int): ByteArray {
return ByteArray(bytes.size).apply {
for (i in 0 until bytes.size) {
for (i in bytes.indices) {
set(i, bytes[i].toByte())
}
}

View File

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

View File

@ -22,7 +22,7 @@ object SharedData {
* @param data the object to put.
*/
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
import java.lang.Math.floor
import kotlin.math.floor
/**
* 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()
return "${ take(pieceLength) }$replacement${ takeLast(pieceLength) }"
}
}

View File

@ -113,7 +113,7 @@ inline fun View.visibleIf(block: () -> Boolean) {
* @param random random color
*/
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()
.beginConfig()
.width(size)

View File

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

View File

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

View File

@ -21,13 +21,13 @@ class CustomLayoutPickerActivity : FilePickerActivity() {
}
class CustomLayoutFilePickerFragment : FilePickerFragment() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): androidx.recyclerview.widget.RecyclerView.ViewHolder {
when (viewType) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
LogicHandler.VIEWTYPE_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
init {
if (Build.VERSION.SDK_INT >= 21) {
origStateAnimator = stateListAnimator
}
origStateAnimator = stateListAnimator
}
fun enableElevation() {
if (Build.VERSION.SDK_INT >= 21) {
stateListAnimator = origStateAnimator
}
stateListAnimator = origStateAnimator
}
fun disableElevation() {
if (Build.VERSION.SDK_INT >= 21) {
stateListAnimator = StateListAnimator().apply {
val objAnimator = ObjectAnimator.ofFloat(this, "elevation", 0f)
stateListAnimator = StateListAnimator().apply {
val objAnimator = ObjectAnimator.ofFloat(this, "elevation", 0f)
// Enabled and collapsible, but not collapsed means not elevated
addState(intArrayOf(android.R.attr.enabled, R.attr.state_collapsible, -R.attr.state_collapsed),
objAnimator)
// Enabled and collapsible, but not collapsed means not elevated
addState(intArrayOf(android.R.attr.enabled, R.attr.state_collapsible, -R.attr.state_collapsed),
objAnimator)
// Default enabled state
addState(intArrayOf(android.R.attr.enabled), objAnimator)
// Default enabled state
addState(intArrayOf(android.R.attr.enabled), objAnimator)
// Disabled state
addState(IntArray(0), objAnimator)
}
// Disabled state
addState(IntArray(0), objAnimator)
}
}

View File

@ -13,7 +13,7 @@ import eu.kanade.tachiyomi.R
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).
*/
open class ExtendedNavigationView @JvmOverloads constructor(
@ -210,8 +210,7 @@ open class ExtendedNavigationView @JvmOverloads constructor(
@CallSuper
override fun getItemViewType(position: Int): Int {
val item = items[position]
return when (item) {
return when (items[position]) {
is Item.Header -> VIEW_TYPE_HEADER
is Item.Separator -> VIEW_TYPE_SEPARATOR
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.widget.SeekBar
import eu.kanade.tachiyomi.R
import kotlin.math.abs
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 {
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?) {
listener?.let { it.onStartTrackingTouch(p0) }
listener?.onStartTrackingTouch(p0)
}
override fun onStopTrackingTouch(p0: SeekBar?) {
listener?.let { it.onStopTrackingTouch(p0) }
listener?.onStopTrackingTouch(p0)
}
})
}
override fun setProgress(progress: Int) {
super.setProgress(Math.abs(minValue) + progress)
super.setProgress(abs(minValue) + progress)
}
fun setMinSeek(minValue: Int) {
@ -66,4 +67,4 @@ class NegativeSeekBar @JvmOverloads constructor(context: Context, attrs: Attribu
super.setProgress(origProgress)
}
}
}

View File

@ -30,7 +30,7 @@ class PTSansTextView @JvmOverloads constructor(context: Context, attrs: Attribut
Typeface.createFromAsset(context.assets, when (typeface) {
PTSANS_NARROW -> "fonts/PTSans-Narrow.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.AnimatorListenerAdapter
import android.annotation.TargetApi
import android.content.Context
import android.os.Build
import android.util.AttributeSet
import android.view.View
import android.view.ViewAnimationUtils
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
class RevealAnimationView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
View(context, attrs) {
@ -21,28 +18,25 @@ class RevealAnimationView @JvmOverloads constructor(context: Context, attrs: Att
* @param initialRadius size of radius of animation
*/
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.
this.visibility = View.VISIBLE
// Create the animation (the final radius is zero).
val anim = ViewAnimationUtils.createCircularReveal(
this, centerX, centerY, initialRadius.toFloat(), 0f)
// Create the animation (the final radius is zero).
val anim = ViewAnimationUtils.createCircularReveal(
this, centerX, centerY, initialRadius.toFloat(), 0f)
// Set duration of animation.
anim.duration = 500
// Set duration of animation.
anim.duration = 500
// make the view invisible when the animation is done
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.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
this@RevealAnimationView.visibility = View.INVISIBLE
}
})
anim.start()
}
anim.start()
}
/**
@ -55,25 +49,20 @@ class RevealAnimationView @JvmOverloads constructor(context: Context, attrs: Att
* @return sdk version lower then 21
*/
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
val anim = ViewAnimationUtils.createCircularReveal(
this, centerX, centerY, 0f, height.toFloat())
// Set duration of animation
anim.duration = 350
// Set duration of animation
anim.duration = 350
anim.addListener(listener)
anim.start()
return true
}
return false
anim.addListener(listener)
anim.start()
return true
}
}

View File

@ -2,18 +2,19 @@ package eu.kanade.tachiyomi.widget
import android.annotation.SuppressLint
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.view.View
import android.view.ViewGroup
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.textfield.TextInputLayout
import eu.kanade.tachiyomi.util.inflate
import kotlin.math.min
import eu.kanade.tachiyomi.R as TR
@Suppress("LeakingThis")
@ -67,7 +68,7 @@ open class SimpleNavigationView @JvmOverloads constructor(
override fun onMeasure(widthSpec: Int, heightSpec: Int) {
val width = when (MeasureSpec.getMode(widthSpec)) {
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)
else -> widthSpec
}

View File

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