mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-04 16:18:55 +01:00 
			
		
		
		
	Drop support for Android 4.x (#2440)
* Bump minSdkVersion * Remove Android 4.x specific logic * Consolidate res assets * Add note about minimum Android version to README * Restore incorrectly removed method, remove unneeded Lollipop TargetApi annotations
This commit is contained in:
		@@ -1,35 +1,20 @@
 | 
			
		||||
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) }
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        // Init sync manager when using anything below L
 | 
			
		||||
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
 | 
			
		||||
            syncManager
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
 | 
			
		||||
        val urlString = url.toString()
 | 
			
		||||
 | 
			
		||||
        for (cookie in cookies) {
 | 
			
		||||
            manager.setCookie(urlString, cookie.toString())
 | 
			
		||||
        }
 | 
			
		||||
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
 | 
			
		||||
            syncManager.sync()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun loadForRequest(url: HttpUrl): List<Cookie> {
 | 
			
		||||
@@ -39,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()
 | 
			
		||||
@@ -53,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 {}
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,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.*
 | 
			
		||||
import android.view.animation.Animation
 | 
			
		||||
@@ -21,9 +20,7 @@ import eu.kanade.tachiyomi.data.database.models.Manga
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.AddToLibraryFirst
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.Error
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.Success
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.*
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
 | 
			
		||||
@@ -276,10 +273,7 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
 | 
			
		||||
                toolbarAnimation.setAnimationListener(object : SimpleAnimationListener() {
 | 
			
		||||
                    override fun onAnimationStart(animation: Animation) {
 | 
			
		||||
                        // Fix status bar being translucent the first time it's opened.
 | 
			
		||||
                        if (Build.VERSION.SDK_INT >= 21) {
 | 
			
		||||
                            window.addFlags(
 | 
			
		||||
                                    WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
 | 
			
		||||
                        }
 | 
			
		||||
                        window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
 | 
			
		||||
                    }
 | 
			
		||||
                })
 | 
			
		||||
                toolbar.startAnimation(toolbarAnimation)
 | 
			
		||||
@@ -637,11 +631,7 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
 | 
			
		||||
         */
 | 
			
		||||
        private fun setFullscreen(enabled: Boolean) {
 | 
			
		||||
            systemUi = if (enabled) {
 | 
			
		||||
                val level = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
 | 
			
		||||
                    SystemUiHelper.LEVEL_IMMERSIVE
 | 
			
		||||
                } else {
 | 
			
		||||
                    SystemUiHelper.LEVEL_HIDE_STATUS_BAR
 | 
			
		||||
                }
 | 
			
		||||
                val level = SystemUiHelper.LEVEL_IMMERSIVE
 | 
			
		||||
                val flags = SystemUiHelper.FLAG_IMMERSIVE_STICKY or
 | 
			
		||||
                        SystemUiHelper.FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,16 +3,14 @@ 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
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -58,7 +56,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
 | 
			
		||||
 
 | 
			
		||||
@@ -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,37 +144,27 @@ 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
 | 
			
		||||
                // Get UriPermission so it's possible to write files
 | 
			
		||||
                val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
 | 
			
		||||
                        Intent.FLAG_GRANT_WRITE_URI_PERMISSION
 | 
			
		||||
 | 
			
		||||
                    activity.contentResolver.takePersistableUriPermission(uri, flags)
 | 
			
		||||
                }
 | 
			
		||||
                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
 | 
			
		||||
 | 
			
		||||
                    activity.contentResolver.takePersistableUriPermission(uri, flags)
 | 
			
		||||
                    val file = UniFile.fromUri(activity, uri)
 | 
			
		||||
 | 
			
		||||
                    file.uri
 | 
			
		||||
                }
 | 
			
		||||
                activity.contentResolver.takePersistableUriPermission(uri, flags)
 | 
			
		||||
                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
 | 
			
		||||
@@ -201,25 +181,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() {
 | 
			
		||||
 
 | 
			
		||||
@@ -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,11 +106,7 @@ 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
 | 
			
		||||
@@ -132,19 +127,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)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -183,7 +170,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
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user