diff --git a/app/build.gradle b/app/build.gradle index 90a3b490c..22f136427 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -32,7 +32,7 @@ ext { android { compileSdkVersion 27 - buildToolsVersion "27.0.2" + buildToolsVersion '27.0.3' publishNonDefault true defaultConfig { @@ -40,8 +40,8 @@ android { minSdkVersion 16 targetSdkVersion 27 testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" - versionCode 7000 - versionName "v7.0.0-EH" + versionCode 7200 + versionName "v7.2.0-EH" buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\"" buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\"" @@ -121,7 +121,7 @@ dependencies { implementation "com.android.support:support-annotations:$support_library_version" implementation "com.android.support:customtabs:$support_library_version" - implementation 'com.android.support.constraint:constraint-layout:1.0.2' + implementation 'com.android.support.constraint:constraint-layout:1.1.0-beta6' implementation 'com.android.support:multidex:1.0.2' diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubService.kt index 5e0aa932e..5215dc16a 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubService.kt @@ -1,10 +1,13 @@ package eu.kanade.tachiyomi.data.updater +import eu.kanade.tachiyomi.network.NetworkHelper import retrofit2.Retrofit import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory import retrofit2.converter.gson.GsonConverterFactory import retrofit2.http.GET import rx.Observable +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get /** * Used to connect with the Github API. @@ -17,6 +20,7 @@ interface GithubService { .baseUrl("https://api.github.com") .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) + .client(Injekt.get().client) .build() return restAdapter.create(GithubService::class.java) diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/CloudflareInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/network/CloudflareInterceptor.kt index bc55342ab..18a87170b 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/network/CloudflareInterceptor.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/CloudflareInterceptor.kt @@ -1,6 +1,7 @@ package eu.kanade.tachiyomi.network import com.squareup.duktape.Duktape +import okhttp3.CacheControl import okhttp3.HttpUrl import okhttp3.Interceptor import okhttp3.Request @@ -21,7 +22,7 @@ class CloudflareInterceptor : Interceptor { val response = chain.proceed(chain.request()) // Check if Cloudflare anti-bot is on - if (response.code() == 503 && serverCheck.contains(response.header("Server"))) { + if (response.code() == 503 && response.header("Server") in serverCheck) { return chain.proceed(resolveChallenge(response)) } @@ -43,32 +44,33 @@ class CloudflareInterceptor : Interceptor { val pass = passPattern.find(content)?.groups?.get(1)?.value if (operation == null || challenge == null || pass == null) { - throw RuntimeException("Failed resolving Cloudflare challenge") + throw Exception("Failed resolving Cloudflare challenge") } val js = operation - .replace(Regex("""a\.value =(.+?) \+.*"""), "$1") + .replace(Regex("""a\.value = (.+ \+ t\.length).+"""), "$1") .replace(Regex("""\s{3,}[a-z](?: = |\.).+"""), "") + .replace("t.length", "${domain.length}") .replace("\n", "") - val result = (duktape.evaluate(js) as Double).toInt() - - val answer = "${result + domain.length}" + val result = duktape.evaluate(js) as Double val cloudflareUrl = HttpUrl.parse("${url.scheme()}://$domain/cdn-cgi/l/chk_jschl")!! .newBuilder() .addQueryParameter("jschl_vc", challenge) .addQueryParameter("pass", pass) - .addQueryParameter("jschl_answer", answer) + .addQueryParameter("jschl_answer", "$result") .toString() val cloudflareHeaders = originalRequest.headers() .newBuilder() .add("Referer", url.toString()) + .add("Accept", "text/html,application/xhtml+xml,application/xml") + .add("Accept-Language", "en") .build() - return GET(cloudflareUrl, cloudflareHeaders) + return GET(cloudflareUrl, cloudflareHeaders, cache = CacheControl.Builder().build()) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt index b8f5d756a..97d4b4d7d 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt @@ -1,9 +1,22 @@ package eu.kanade.tachiyomi.network import android.content.Context +import android.os.Build 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.SSLContext +import javax.net.ssl.SSLSocket +import javax.net.ssl.SSLSocketFactory +import javax.net.ssl.TrustManagerFactory +import javax.net.ssl.X509TrustManager class NetworkHelper(context: Context) { @@ -16,6 +29,7 @@ class NetworkHelper(context: Context) { val client = OkHttpClient.Builder() .cookieJar(cookieManager) .cache(Cache(cacheDir, cacheSize)) + .enableTLS12() .build() val cloudflareClient = client.newBuilder() @@ -25,4 +39,75 @@ class NetworkHelper(context: Context) { val cookies: PersistentCookieStore get() = cookieManager.store + 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 { + return internalSSLSocketFactory.defaultCipherSuites + } + + override fun getSupportedCipherSuites(): Array { + 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) + } + + return this + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt index 70076df2b..28c598625 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt @@ -277,7 +277,11 @@ class MangaInfoController : NucleusController(), } fun setLastUpdateDate(date: Date) { - manga_last_update?.text = DateFormat.getDateInstance(DateFormat.SHORT).format(date) + if (date.time != 0L) { + manga_last_update?.text = DateFormat.getDateInstance(DateFormat.SHORT).format(date) + } else { + manga_last_update?.text = resources?.getString(R.string.unknown) + } } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchDialog.kt index 691800058..9856ce2e5 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchDialog.kt @@ -8,9 +8,9 @@ import com.jakewharton.rxbinding.widget.itemClicks import com.jakewharton.rxbinding.widget.textChanges import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Track -import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackService +import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.util.plusAssign import kotlinx.android.synthetic.main.track_search_dialog.view.* @@ -114,14 +114,14 @@ class TrackSearchDialog : DialogController { private fun search(query: String) { val view = dialogView ?: return view.progress.visibility = View.VISIBLE - view.track_search_list.visibility = View.GONE + view.track_search_list.visibility = View.INVISIBLE trackController.presenter.search(query, service) } fun onSearchResults(results: List) { selectedItem = null val view = dialogView ?: return - view.progress.visibility = View.GONE + view.progress.visibility = View.INVISIBLE view.track_search_list.visibility = View.VISIBLE adapter?.setItems(results) } @@ -129,7 +129,7 @@ class TrackSearchDialog : DialogController { fun onSearchResultsError() { val view = dialogView ?: return view.progress.visibility = View.VISIBLE - view.track_search_list.visibility = View.GONE + view.track_search_list.visibility = View.INVISIBLE adapter?.setItems(emptyList()) } @@ -141,4 +141,4 @@ class TrackSearchDialog : DialogController { const val KEY_SERVICE = "service_id" } -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutController.kt index b13cf0d43..db043f18a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutController.kt @@ -109,6 +109,7 @@ class SettingsAboutController : SettingsController() { } } }, { error -> + activity?.toast(error.message) Timber.e(error) }) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/ContextExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/ContextExtensions.kt index b236b000c..78aa2c1b1 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/util/ContextExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/ContextExtensions.kt @@ -43,7 +43,7 @@ fun Context.toast(@StringRes resource: Int, duration: Int = Toast.LENGTH_SHORT) * @param duration the duration of the toast. Defaults to short. */ fun Context.toast(text: String?, duration: Int = Toast.LENGTH_SHORT) { - Toast.makeText(this, text, duration).show() + Toast.makeText(this, text.orEmpty(), duration).show() } /** diff --git a/app/src/main/res/layout/catalogue_list_item.xml b/app/src/main/res/layout/catalogue_list_item.xml index 40f52022f..b2b43a826 100755 --- a/app/src/main/res/layout/catalogue_list_item.xml +++ b/app/src/main/res/layout/catalogue_list_item.xml @@ -105,7 +105,7 @@ tools:text="122" tools:visibility="visible" android:layout_marginEnd="8dp" - app:layout_constraintRight_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" android:layout_marginTop="8dp" app:layout_constraintBottom_toBottomOf="parent" diff --git a/app/src/main/res/layout/chapters_item.xml b/app/src/main/res/layout/chapters_item.xml index c62b52da2..95a5c4eaf 100644 --- a/app/src/main/res/layout/chapters_item.xml +++ b/app/src/main/res/layout/chapters_item.xml @@ -63,7 +63,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" app:srcCompat="@drawable/ic_more_vert_black_24dp" - app:layout_constraintRight_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" android:paddingStart="24dp" android:paddingEnd="16dp" @@ -82,7 +82,7 @@ android:layout_height="wrap_content" tools:text="DOWNLOADED" android:textAllCaps="true" - app:layout_constraintRight_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" app:layout_constraintBottom_toBottomOf="parent" android:layout_marginRight="16dp"/> diff --git a/app/src/main/res/layout/extension_card_item.xml b/app/src/main/res/layout/extension_card_item.xml index 3d25905a6..23ee4e5e7 100644 --- a/app/src/main/res/layout/extension_card_item.xml +++ b/app/src/main/res/layout/extension_card_item.xml @@ -27,13 +27,16 @@ - + android:inputType="text" + android:maxLines="1"/> - - - + android:layout_height="0dp" + android:layout_weight="1"> + + + + + + + android:background="?android:attr/divider"/> - \ No newline at end of file + diff --git a/app/src/main/res/layout/track_search_item.xml b/app/src/main/res/layout/track_search_item.xml index 2c5c7c7ee..acab56c21 100755 --- a/app/src/main/res/layout/track_search_item.xml +++ b/app/src/main/res/layout/track_search_item.xml @@ -12,7 +12,6 @@ android:layout_height="216dp" android:background="?attr/selectable_list_drawable" android:orientation="horizontal"> - > - \ No newline at end of file + diff --git a/app/src/main/res/raw/changelog_release.xml b/app/src/main/res/raw/changelog_release.xml index e0af47c8d..75ddfc123 100755 --- a/app/src/main/res/raw/changelog_release.xml +++ b/app/src/main/res/raw/changelog_release.xml @@ -22,6 +22,20 @@ Various bug fixes + + Fixed missing downloaded label in chapters screen. + + Fixed updater in KitKat and lower due to TLS. + + + + Updated Cloudflare bypass. + + Enabled TLS 1.1 and TLS 1.2 on Android KitKat and lower. + + Minor UI changes. + + Added extensions support. You can now install and update extensions within the app. If you installed any extension previously through F-Droid, you'll have to uninstall them first. diff --git a/build.gradle b/build.gradle index d6560e9cd..3b84f286f 100755 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.0.1' + classpath 'com.android.tools.build:gradle:3.1.0' classpath 'com.github.ben-manes:gradle-versions-plugin:0.17.0' classpath 'com.github.zellius:android-shortcut-gradle-plugin:0.1.2' // NOTE: Do not place your application dependencies here; they belong diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c6a753671..620a96dc3 100755 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Oct 25 23:17:30 CEST 2017 +#Thu Apr 05 09:21:32 CEST 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip