mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-09-11 00:13:22 +02:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
b1d221c117
26
.gitattributes
vendored
26
.gitattributes
vendored
@ -1,2 +1,28 @@
|
|||||||
* text=auto
|
* text=auto
|
||||||
* text eol=lf
|
* text eol=lf
|
||||||
|
|
||||||
|
# Windows forced line-endings
|
||||||
|
/.idea/* text eol=crlf
|
||||||
|
|
||||||
|
*.png binary
|
||||||
|
*.jpg binary
|
||||||
|
*.jpeg binary
|
||||||
|
*.gif binary
|
||||||
|
*.ico binary
|
||||||
|
*.mov binary
|
||||||
|
*.mp4 binary
|
||||||
|
*.mp3 binary
|
||||||
|
*.flv binary
|
||||||
|
*.fla binary
|
||||||
|
*.swf binary
|
||||||
|
*.gz binary
|
||||||
|
*.zip binary
|
||||||
|
*.7z binary
|
||||||
|
*.ttf binary
|
||||||
|
*.eot binary
|
||||||
|
*.woff binary
|
||||||
|
*.pyc binary
|
||||||
|
*.pdf binary
|
||||||
|
*.ez binary
|
||||||
|
*.bz2 binary
|
||||||
|
*.swp binary
|
||||||
|
@ -3,7 +3,7 @@ language: android
|
|||||||
android:
|
android:
|
||||||
components:
|
components:
|
||||||
- build-tools-29.0.2
|
- build-tools-29.0.2
|
||||||
- android-28
|
- android-29
|
||||||
- extra-android-m2repository
|
- extra-android-m2repository
|
||||||
- extra-google-m2repository
|
- extra-google-m2repository
|
||||||
- extra-android-support
|
- extra-android-support
|
||||||
@ -11,7 +11,7 @@ android:
|
|||||||
licenses:
|
licenses:
|
||||||
- android-sdk-license-.+
|
- android-sdk-license-.+
|
||||||
before_install:
|
before_install:
|
||||||
- yes | sdkmanager "platforms;android-28" # workaround for accepting the license
|
- yes | sdkmanager "platforms;android-29" # workaround for accepting the license
|
||||||
- if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then
|
- if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then
|
||||||
openssl aes-256-cbc -K $encrypted_e56be693d4fd_key -iv $encrypted_e56be693d4fd_iv -in "$PWD/.travis/secrets.tar.enc" -out secrets.tar -d;
|
openssl aes-256-cbc -K $encrypted_e56be693d4fd_key -iv $encrypted_e56be693d4fd_iv -in "$PWD/.travis/secrets.tar.enc" -out secrets.tar -d;
|
||||||
tar xf secrets.tar;
|
tar xf secrets.tar;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# ![app icon](./.github/readme-images/app-icon.png)Tachiyomi
|
# ![app icon](./.github/readme-images/app-icon.png)Tachiyomi
|
||||||
Tachiyomi is a free and open source manga reader for Android.
|
Tachiyomi is a free and open source manga reader for Android 5.0 and above.
|
||||||
|
|
||||||
![screenshots of app](./.github/readme-images/theming-screenshots.gif)
|
![screenshots of app](./.github/readme-images/theming-screenshots.gif)
|
||||||
|
|
||||||
|
@ -28,7 +28,8 @@
|
|||||||
android:networkSecurityConfig="@xml/network_security_config">
|
android:networkSecurityConfig="@xml/network_security_config">
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.main.MainActivity"
|
android:name=".ui.main.MainActivity"
|
||||||
android:launchMode="singleTask">
|
android:launchMode="singleTask"
|
||||||
|
android:theme="@style/Theme.Splash">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
@ -31,7 +31,7 @@ import uy.kohesive.injekt.registry.default.DefaultRegistrar
|
|||||||
reportType = org.acra.sender.HttpSender.Type.JSON,
|
reportType = org.acra.sender.HttpSender.Type.JSON,
|
||||||
httpMethod = org.acra.sender.HttpSender.Method.PUT,
|
httpMethod = org.acra.sender.HttpSender.Method.PUT,
|
||||||
buildConfigClass = BuildConfig::class,
|
buildConfigClass = BuildConfig::class,
|
||||||
excludeMatchingSharedPreferencesKeys = arrayOf(".*username.*", ".*password.*", ".*token.*")
|
excludeMatchingSharedPreferencesKeys = [".*username.*", ".*password.*", ".*token.*"]
|
||||||
)
|
)
|
||||||
open class App : Application(), LifecycleObserver {
|
open class App : Application(), LifecycleObserver {
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ import eu.kanade.tachiyomi.util.syncChaptersWithSource
|
|||||||
import rx.Observable
|
import rx.Observable
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
||||||
|
|
||||||
@ -204,7 +205,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
|||||||
if (options and BACKUP_CHAPTER_MASK == BACKUP_CHAPTER) {
|
if (options and BACKUP_CHAPTER_MASK == BACKUP_CHAPTER) {
|
||||||
// Backup all the chapters
|
// Backup all the chapters
|
||||||
val chapters = databaseHelper.getChapters(manga).executeAsBlocking()
|
val chapters = databaseHelper.getChapters(manga).executeAsBlocking()
|
||||||
if (!chapters.isEmpty()) {
|
if (chapters.isNotEmpty()) {
|
||||||
val chaptersJson = parser.toJsonTree(chapters)
|
val chaptersJson = parser.toJsonTree(chapters)
|
||||||
if (chaptersJson.asJsonArray.size() > 0) {
|
if (chaptersJson.asJsonArray.size() > 0) {
|
||||||
entry[CHAPTERS] = chaptersJson
|
entry[CHAPTERS] = chaptersJson
|
||||||
@ -216,7 +217,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
|||||||
if (options and BACKUP_CATEGORY_MASK == BACKUP_CATEGORY) {
|
if (options and BACKUP_CATEGORY_MASK == BACKUP_CATEGORY) {
|
||||||
// Backup categories for this manga
|
// Backup categories for this manga
|
||||||
val categoriesForManga = databaseHelper.getCategoriesForManga(manga).executeAsBlocking()
|
val categoriesForManga = databaseHelper.getCategoriesForManga(manga).executeAsBlocking()
|
||||||
if (!categoriesForManga.isEmpty()) {
|
if (categoriesForManga.isNotEmpty()) {
|
||||||
val categoriesNames = categoriesForManga.map { it.name }
|
val categoriesNames = categoriesForManga.map { it.name }
|
||||||
entry[CATEGORIES] = parser.toJsonTree(categoriesNames)
|
entry[CATEGORIES] = parser.toJsonTree(categoriesNames)
|
||||||
}
|
}
|
||||||
@ -225,7 +226,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
|||||||
// Check if user wants track information in backup
|
// Check if user wants track information in backup
|
||||||
if (options and BACKUP_TRACK_MASK == BACKUP_TRACK) {
|
if (options and BACKUP_TRACK_MASK == BACKUP_TRACK) {
|
||||||
val tracks = databaseHelper.getTracks(manga).executeAsBlocking()
|
val tracks = databaseHelper.getTracks(manga).executeAsBlocking()
|
||||||
if (!tracks.isEmpty()) {
|
if (tracks.isNotEmpty()) {
|
||||||
entry[TRACK] = parser.toJsonTree(tracks)
|
entry[TRACK] = parser.toJsonTree(tracks)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -233,7 +234,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
|||||||
// Check if user wants history information in backup
|
// Check if user wants history information in backup
|
||||||
if (options and BACKUP_HISTORY_MASK == BACKUP_HISTORY) {
|
if (options and BACKUP_HISTORY_MASK == BACKUP_HISTORY) {
|
||||||
val historyForManga = databaseHelper.getHistoryByMangaId(manga.id!!).executeAsBlocking()
|
val historyForManga = databaseHelper.getHistoryByMangaId(manga.id!!).executeAsBlocking()
|
||||||
if (!historyForManga.isEmpty()) {
|
if (historyForManga.isNotEmpty()) {
|
||||||
val historyData = historyForManga.mapNotNull { history ->
|
val historyData = historyForManga.mapNotNull { history ->
|
||||||
val url = databaseHelper.getChapter(history.chapter_id).executeAsBlocking()?.url
|
val url = databaseHelper.getChapter(history.chapter_id).executeAsBlocking()?.url
|
||||||
url?.let { DHistory(url, history.last_read) }
|
url?.let { DHistory(url, history.last_read) }
|
||||||
@ -344,7 +345,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update database
|
// Update database
|
||||||
if (!mangaCategoriesToUpdate.isEmpty()) {
|
if (mangaCategoriesToUpdate.isNotEmpty()) {
|
||||||
val mangaAsList = ArrayList<Manga>()
|
val mangaAsList = ArrayList<Manga>()
|
||||||
mangaAsList.add(manga)
|
mangaAsList.add(manga)
|
||||||
databaseHelper.deleteOldMangasCategories(mangaAsList).executeAsBlocking()
|
databaseHelper.deleteOldMangasCategories(mangaAsList).executeAsBlocking()
|
||||||
@ -365,7 +366,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
|||||||
// Check if history already in database and update
|
// Check if history already in database and update
|
||||||
if (dbHistory != null) {
|
if (dbHistory != null) {
|
||||||
dbHistory.apply {
|
dbHistory.apply {
|
||||||
last_read = Math.max(lastRead, dbHistory.last_read)
|
last_read = max(lastRead, dbHistory.last_read)
|
||||||
}
|
}
|
||||||
historyToBeUpdated.add(dbHistory)
|
historyToBeUpdated.add(dbHistory)
|
||||||
} else {
|
} else {
|
||||||
@ -408,7 +409,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
|||||||
if (track.library_id != dbTrack.library_id) {
|
if (track.library_id != dbTrack.library_id) {
|
||||||
dbTrack.library_id = track.library_id
|
dbTrack.library_id = track.library_id
|
||||||
}
|
}
|
||||||
dbTrack.last_chapter_read = Math.max(dbTrack.last_chapter_read, track.last_chapter_read)
|
dbTrack.last_chapter_read = max(dbTrack.last_chapter_read, track.last_chapter_read)
|
||||||
isInDatabase = true
|
isInDatabase = true
|
||||||
trackToUpdate.add(dbTrack)
|
trackToUpdate.add(dbTrack)
|
||||||
break
|
break
|
||||||
@ -422,7 +423,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Update database
|
// Update database
|
||||||
if (!trackToUpdate.isEmpty()) {
|
if (trackToUpdate.isNotEmpty()) {
|
||||||
databaseHelper.insertTracks(trackToUpdate).executeAsBlocking()
|
databaseHelper.insertTracks(trackToUpdate).executeAsBlocking()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,9 +43,7 @@ object ChapterTypeAdapter {
|
|||||||
beginObject()
|
beginObject()
|
||||||
while (hasNext()) {
|
while (hasNext()) {
|
||||||
if (peek() == JsonToken.NAME) {
|
if (peek() == JsonToken.NAME) {
|
||||||
val name = nextName()
|
when (nextName()) {
|
||||||
|
|
||||||
when (name) {
|
|
||||||
URL -> chapter.url = nextString()
|
URL -> chapter.url = nextString()
|
||||||
READ -> chapter.read = nextInt() == 1
|
READ -> chapter.read = nextInt() == 1
|
||||||
BOOKMARK -> chapter.bookmark = nextInt() == 1
|
BOOKMARK -> chapter.bookmark = nextInt() == 1
|
||||||
@ -58,4 +56,4 @@ object ChapterTypeAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,9 +42,7 @@ object TrackTypeAdapter {
|
|||||||
beginObject()
|
beginObject()
|
||||||
while (hasNext()) {
|
while (hasNext()) {
|
||||||
if (peek() == JsonToken.NAME) {
|
if (peek() == JsonToken.NAME) {
|
||||||
val name = nextName()
|
when (nextName()) {
|
||||||
|
|
||||||
when (name) {
|
|
||||||
TITLE -> track.title = nextString()
|
TITLE -> track.title = nextString()
|
||||||
SYNC -> track.sync_id = nextInt()
|
SYNC -> track.sync_id = nextInt()
|
||||||
MEDIA -> track.media_id = nextInt()
|
MEDIA -> track.media_id = nextInt()
|
||||||
@ -59,4 +57,4 @@ object TrackTypeAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -266,7 +266,7 @@ class DownloadCache(
|
|||||||
for (element in this) {
|
for (element in this) {
|
||||||
val (key, value) = transform(element)
|
val (key, value) = transform(element)
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
destination.put(key, value)
|
destination[key] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return destination
|
return destination
|
||||||
|
@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.data.download
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import com.hippo.unifile.UniFile
|
import com.hippo.unifile.UniFile
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
@ -46,9 +47,13 @@ class DownloadProvider(private val context: Context) {
|
|||||||
* @param source the source of the manga.
|
* @param source the source of the manga.
|
||||||
*/
|
*/
|
||||||
internal fun getMangaDir(manga: Manga, source: Source): UniFile {
|
internal fun getMangaDir(manga: Manga, source: Source): UniFile {
|
||||||
return downloadsDir
|
try {
|
||||||
.createDirectory(getSourceDirName(source))
|
return downloadsDir
|
||||||
.createDirectory(getMangaDirName(manga))
|
.createDirectory(getSourceDirName(source))
|
||||||
|
.createDirectory(getMangaDirName(manga))
|
||||||
|
} catch (e: NullPointerException) {
|
||||||
|
throw Exception(context.getString(R.string.invalid_download_dir))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -108,7 +108,7 @@ class Downloader(
|
|||||||
pending.forEach { if (it.status != Download.QUEUE) it.status = Download.QUEUE }
|
pending.forEach { if (it.status != Download.QUEUE) it.status = Download.QUEUE }
|
||||||
|
|
||||||
downloadsRelay.call(pending)
|
downloadsRelay.call(pending)
|
||||||
return !pending.isEmpty()
|
return pending.isNotEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -48,4 +48,4 @@ class LibraryUpdateJob : Job() {
|
|||||||
JobManager.instance().cancelAllForTag(TAG)
|
JobManager.instance().cancelAllForTag(TAG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -257,7 +257,6 @@ class LibraryUpdateService(
|
|||||||
else
|
else
|
||||||
db.getLibraryMangas().executeAsBlocking().distinctBy { it.id }
|
db.getLibraryMangas().executeAsBlocking().distinctBy { it.id }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target == Target.CHAPTERS && preferences.updateOnlyNonCompleted()) {
|
if (target == Target.CHAPTERS && preferences.updateOnlyNonCompleted()) {
|
||||||
listToUpdate = listToUpdate.filter { it.status != SManga.COMPLETED }
|
listToUpdate = listToUpdate.filter { it.status != SManga.COMPLETED }
|
||||||
}
|
}
|
||||||
|
@ -57,8 +57,8 @@ abstract class TrackService(val id: Int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
open val isLogged: Boolean
|
open val isLogged: Boolean
|
||||||
get() = !getUsername().isEmpty() &&
|
get() = getUsername().isNotEmpty() &&
|
||||||
!getPassword().isEmpty()
|
getPassword().isNotEmpty()
|
||||||
|
|
||||||
fun getUsername() = preferences.trackUsername(this)!!
|
fun getUsername() = preferences.trackUsername(this)!!
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLogo() = R.drawable.al
|
override fun getLogo() = R.drawable.anilist
|
||||||
|
|
||||||
override fun getLogoColor() = Color.rgb(18, 25, 35)
|
override fun getLogoColor() = Color.rgb(18, 25, 35)
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
|||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.RequestBody
|
import okhttp3.RequestBody
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
|
|
||||||
@ -45,7 +46,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
"query" to query,
|
"query" to query,
|
||||||
"variables" to variables
|
"variables" to variables
|
||||||
)
|
)
|
||||||
val body = RequestBody.create(jsonMime, payload.toString())
|
val body = payload.toString().toRequestBody(jsonMime)
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url(apiUrl)
|
.url(apiUrl)
|
||||||
.post(body)
|
.post(body)
|
||||||
@ -84,7 +85,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
"query" to query,
|
"query" to query,
|
||||||
"variables" to variables
|
"variables" to variables
|
||||||
)
|
)
|
||||||
val body = RequestBody.create(jsonMime, payload.toString())
|
val body = payload.toString().toRequestBody(jsonMime)
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url(apiUrl)
|
.url(apiUrl)
|
||||||
.post(body)
|
.post(body)
|
||||||
@ -128,7 +129,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
"query" to query,
|
"query" to query,
|
||||||
"variables" to variables
|
"variables" to variables
|
||||||
)
|
)
|
||||||
val body = RequestBody.create(jsonMime, payload.toString())
|
val body = payload.toString().toRequestBody(jsonMime)
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url(apiUrl)
|
.url(apiUrl)
|
||||||
.post(body)
|
.post(body)
|
||||||
@ -189,7 +190,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
"query" to query,
|
"query" to query,
|
||||||
"variables" to variables
|
"variables" to variables
|
||||||
)
|
)
|
||||||
val body = RequestBody.create(jsonMime, payload.toString())
|
val body = payload.toString().toRequestBody(jsonMime)
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url(apiUrl)
|
.url(apiUrl)
|
||||||
.post(body)
|
.post(body)
|
||||||
@ -234,7 +235,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
val payload = jsonObject(
|
val payload = jsonObject(
|
||||||
"query" to query
|
"query" to query
|
||||||
)
|
)
|
||||||
val body = RequestBody.create(jsonMime, payload.toString())
|
val body = payload.toString().toRequestBody(jsonMime)
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url(apiUrl)
|
.url(apiUrl)
|
||||||
.post(body)
|
.post(body)
|
||||||
|
@ -35,7 +35,7 @@ class BangumiInterceptor(val bangumi: Bangumi, val gson: Gson) : Interceptor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var authRequest = if (originalRequest.method == "GET") originalRequest.newBuilder()
|
val authRequest = if (originalRequest.method == "GET") originalRequest.newBuilder()
|
||||||
.header("User-Agent", "Tachiyomi")
|
.header("User-Agent", "Tachiyomi")
|
||||||
.url(originalRequest.url.newBuilder()
|
.url(originalRequest.url.newBuilder()
|
||||||
.addQueryParameter("access_token", currAuth.access_token).build())
|
.addQueryParameter("access_token", currAuth.access_token).build())
|
||||||
|
@ -9,7 +9,7 @@ data class OAuth(
|
|||||||
val user_id: Long?
|
val user_id: Long?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
// Access token refersh before expired
|
// Access token refresh before expired
|
||||||
fun isExpired() = (System.currentTimeMillis() / 1000) > (created_at + expires_in - 3600)
|
fun isExpired() = (System.currentTimeMillis() / 1000) > (created_at + expires_in - 3600)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ class Myanimelist(private val context: Context, id: Int) : TrackService(id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val interceptor by lazy { MyAnimeListInterceptor(this) }
|
private val interceptor by lazy { MyAnimeListInterceptor(this) }
|
||||||
private val api by lazy { MyanimelistApi(client, interceptor) }
|
private val api by lazy { MyAnimeListApi(client, interceptor) }
|
||||||
|
|
||||||
override val name: String
|
override val name: String
|
||||||
get() = "MyAnimeList"
|
get() = "MyAnimeList"
|
||||||
|
@ -12,7 +12,10 @@ import eu.kanade.tachiyomi.util.selectInt
|
|||||||
import eu.kanade.tachiyomi.util.selectText
|
import eu.kanade.tachiyomi.util.selectText
|
||||||
import okhttp3.*
|
import okhttp3.*
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.RequestBody
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import okhttp3.Response
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import org.jsoup.Jsoup
|
import org.jsoup.Jsoup
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
@ -24,7 +27,7 @@ import java.io.InputStreamReader
|
|||||||
import java.util.zip.GZIPInputStream
|
import java.util.zip.GZIPInputStream
|
||||||
|
|
||||||
|
|
||||||
class MyanimelistApi(private val client: OkHttpClient, interceptor: MyAnimeListInterceptor) {
|
class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListInterceptor) {
|
||||||
|
|
||||||
private val authClient = client.newBuilder().addInterceptor(interceptor).build()
|
private val authClient = client.newBuilder().addInterceptor(interceptor).build()
|
||||||
|
|
||||||
@ -35,8 +38,7 @@ class MyanimelistApi(private val client: OkHttpClient, interceptor: MyAnimeListI
|
|||||||
.flatMap { Observable.from(it) }
|
.flatMap { Observable.from(it) }
|
||||||
.filter { it.title.contains(realQuery, true) }
|
.filter { it.title.contains(realQuery, true) }
|
||||||
.toList()
|
.toList()
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
client.newCall(GET(searchUrl(query)))
|
client.newCall(GET(searchUrl(query)))
|
||||||
.asObservable()
|
.asObservable()
|
||||||
.flatMap { response ->
|
.flatMap { response ->
|
||||||
@ -264,7 +266,7 @@ class MyanimelistApi(private val client: OkHttpClient, interceptor: MyAnimeListI
|
|||||||
.put("score", track.score)
|
.put("score", track.score)
|
||||||
.put("num_read_chapters", track.last_chapter_read)
|
.put("num_read_chapters", track.last_chapter_read)
|
||||||
|
|
||||||
return RequestBody.create("application/json; charset=utf-8".toMediaTypeOrNull(), body.toString())
|
return body.toString().toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Element.searchTitle() = select("strong").text()!!
|
private fun Element.searchTitle() = select("strong").text()!!
|
@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.data.track.myanimelist
|
|||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.RequestBody
|
import okhttp3.RequestBody
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import okio.Buffer
|
import okio.Buffer
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
@ -15,7 +16,7 @@ class MyAnimeListInterceptor(private val myanimelist: Myanimelist): Interceptor
|
|||||||
val request = chain.request()
|
val request = chain.request()
|
||||||
var response = chain.proceed(updateRequest(request))
|
var response = chain.proceed(updateRequest(request))
|
||||||
|
|
||||||
if (response.code == 400){
|
if (response.code == 400) {
|
||||||
myanimelist.refreshLogin()
|
myanimelist.refreshLogin()
|
||||||
response = chain.proceed(updateRequest(request))
|
response = chain.proceed(updateRequest(request))
|
||||||
}
|
}
|
||||||
@ -45,15 +46,14 @@ class MyAnimeListInterceptor(private val myanimelist: Myanimelist): Interceptor
|
|||||||
private fun updateFormBody(requestBody: RequestBody): RequestBody {
|
private fun updateFormBody(requestBody: RequestBody): RequestBody {
|
||||||
val formString = bodyToString(requestBody)
|
val formString = bodyToString(requestBody)
|
||||||
|
|
||||||
return RequestBody.create(requestBody.contentType(),
|
return "$formString${if (formString.isNotEmpty()) "&" else ""}${MyAnimeListApi.CSRF}=${myanimelist.getCSRF()}".toRequestBody(requestBody.contentType())
|
||||||
"$formString${if (formString.isNotEmpty()) "&" else ""}${MyanimelistApi.CSRF}=${myanimelist.getCSRF()}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateJsonBody(requestBody: RequestBody): RequestBody {
|
private fun updateJsonBody(requestBody: RequestBody): RequestBody {
|
||||||
val jsonString = bodyToString(requestBody)
|
val jsonString = bodyToString(requestBody)
|
||||||
val newBody = JSONObject(jsonString)
|
val newBody = JSONObject(jsonString)
|
||||||
.put(MyanimelistApi.CSRF, myanimelist.getCSRF())
|
.put(MyAnimeListApi.CSRF, myanimelist.getCSRF())
|
||||||
|
|
||||||
return RequestBody.create(requestBody.contentType(), newBody.toString())
|
return newBody.toString().toRequestBody(requestBody.contentType())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,10 @@ import eu.kanade.tachiyomi.network.POST
|
|||||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||||
import okhttp3.*
|
import okhttp3.*
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.RequestBody
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
@ -37,7 +41,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
|
|||||||
"status" to track.toShikimoriStatus()
|
"status" to track.toShikimoriStatus()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val body = RequestBody.create(jsonime, payload.toString())
|
val body = payload.toString().toRequestBody(jsonime)
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url("$apiUrl/v2/user_rates")
|
.url("$apiUrl/v2/user_rates")
|
||||||
.post(body)
|
.post(body)
|
||||||
|
@ -94,7 +94,7 @@ internal class ExtensionInstallReceiver(private val listener: Listener) :
|
|||||||
private suspend fun getExtensionFromIntent(context: Context, intent: Intent?): LoadResult {
|
private suspend fun getExtensionFromIntent(context: Context, intent: Intent?): LoadResult {
|
||||||
val pkgName = getPackageNameFromIntent(intent) ?:
|
val pkgName = getPackageNameFromIntent(intent) ?:
|
||||||
return LoadResult.Error("Package name not found")
|
return LoadResult.Error("Package name not found")
|
||||||
return GlobalScope.async(Dispatchers.Default, CoroutineStart.DEFAULT, { ExtensionLoader.loadExtensionFromPkgName(context, pkgName) }).await()
|
return GlobalScope.async(Dispatchers.Default, CoroutineStart.DEFAULT) { ExtensionLoader.loadExtensionFromPkgName(context, pkgName) }.await()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -173,7 +173,7 @@ internal object ExtensionLoader {
|
|||||||
*/
|
*/
|
||||||
private fun getSignatureHash(pkgInfo: PackageInfo): String? {
|
private fun getSignatureHash(pkgInfo: PackageInfo): String? {
|
||||||
val signatures = pkgInfo.signatures
|
val signatures = pkgInfo.signatures
|
||||||
return if (signatures != null && !signatures.isEmpty()) {
|
return if (signatures != null && signatures.isNotEmpty()) {
|
||||||
Hash.sha256(signatures.first().toByteArray())
|
Hash.sha256(signatures.first().toByteArray())
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
|
@ -1,19 +1,14 @@
|
|||||||
package eu.kanade.tachiyomi.network
|
package eu.kanade.tachiyomi.network
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Build
|
|
||||||
import android.webkit.CookieManager
|
import android.webkit.CookieManager
|
||||||
import android.webkit.CookieSyncManager
|
|
||||||
import okhttp3.Cookie
|
import okhttp3.Cookie
|
||||||
import okhttp3.CookieJar
|
import okhttp3.CookieJar
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
|
|
||||||
class AndroidCookieJar(context: Context) : CookieJar {
|
class AndroidCookieJar : CookieJar {
|
||||||
|
|
||||||
private val manager = CookieManager.getInstance()
|
private val manager = CookieManager.getInstance()
|
||||||
|
|
||||||
private val syncManager by lazy { CookieSyncManager.createInstance(context) }
|
|
||||||
|
|
||||||
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
|
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
|
||||||
val urlString = url.toString()
|
val urlString = url.toString()
|
||||||
|
|
||||||
@ -29,7 +24,7 @@ class AndroidCookieJar(context: Context) : CookieJar {
|
|||||||
fun get(url: HttpUrl): List<Cookie> {
|
fun get(url: HttpUrl): List<Cookie> {
|
||||||
val cookies = manager.getCookie(url.toString())
|
val cookies = manager.getCookie(url.toString())
|
||||||
|
|
||||||
return if (cookies != null && !cookies.isEmpty()) {
|
return if (cookies != null && cookies.isNotEmpty()) {
|
||||||
cookies.split(";").mapNotNull { Cookie.parse(url, it) }
|
cookies.split(";").mapNotNull { Cookie.parse(url, it) }
|
||||||
} else {
|
} else {
|
||||||
emptyList()
|
emptyList()
|
||||||
@ -43,19 +38,10 @@ class AndroidCookieJar(context: Context) : CookieJar {
|
|||||||
cookies.split(";")
|
cookies.split(";")
|
||||||
.map { it.substringBefore("=") }
|
.map { it.substringBefore("=") }
|
||||||
.onEach { manager.setCookie(urlString, "$it=;Max-Age=-1") }
|
.onEach { manager.setCookie(urlString, "$it=;Max-Age=-1") }
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
|
||||||
syncManager.sync()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeAll() {
|
fun removeAll() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
manager.removeAllCookies {}
|
||||||
manager.removeAllCookies {}
|
|
||||||
} else {
|
|
||||||
manager.removeAllCookie()
|
|
||||||
syncManager.sync()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -28,11 +28,7 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
|
|||||||
* Application class.
|
* Application class.
|
||||||
*/
|
*/
|
||||||
private val initWebView by lazy {
|
private val initWebView by lazy {
|
||||||
if (Build.VERSION.SDK_INT >= 17) {
|
WebSettings.getDefaultUserAgent(context)
|
||||||
WebSettings.getDefaultUserAgent(context)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
|
@ -1,17 +1,9 @@
|
|||||||
package eu.kanade.tachiyomi.network
|
package eu.kanade.tachiyomi.network
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build
|
import okhttp3.Cache
|
||||||
import okhttp3.*
|
import okhttp3.OkHttpClient
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
|
||||||
import java.net.InetAddress
|
|
||||||
import java.net.Socket
|
|
||||||
import java.net.UnknownHostException
|
|
||||||
import java.security.KeyManagementException
|
|
||||||
import java.security.KeyStore
|
|
||||||
import java.security.NoSuchAlgorithmException
|
|
||||||
import javax.net.ssl.*
|
|
||||||
|
|
||||||
class NetworkHelper(context: Context) {
|
class NetworkHelper(context: Context) {
|
||||||
|
|
||||||
@ -19,99 +11,15 @@ class NetworkHelper(context: Context) {
|
|||||||
|
|
||||||
private val cacheSize = 5L * 1024 * 1024 // 5 MiB
|
private val cacheSize = 5L * 1024 * 1024 // 5 MiB
|
||||||
|
|
||||||
val cookieManager = AndroidCookieJar(context)
|
val cookieManager = AndroidCookieJar()
|
||||||
|
|
||||||
val client = OkHttpClient.Builder()
|
val client = OkHttpClient.Builder()
|
||||||
.cookieJar(cookieManager)
|
.cookieJar(cookieManager)
|
||||||
.cache(Cache(cacheDir, cacheSize))
|
.cache(Cache(cacheDir, cacheSize))
|
||||||
.enableTLS12()
|
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val cloudflareClient = client.newBuilder()
|
val cloudflareClient = client.newBuilder()
|
||||||
.addInterceptor(CloudflareInterceptor(context))
|
.addInterceptor(CloudflareInterceptor(context))
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
private fun OkHttpClient.Builder.enableTLS12(): OkHttpClient.Builder {
|
|
||||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
|
|
||||||
trustManagerFactory.init(null as KeyStore?)
|
|
||||||
val trustManagers = trustManagerFactory.trustManagers
|
|
||||||
if (trustManagers.size == 1 && trustManagers[0] is X509TrustManager) {
|
|
||||||
class TLSSocketFactory @Throws(KeyManagementException::class, NoSuchAlgorithmException::class)
|
|
||||||
constructor() : SSLSocketFactory() {
|
|
||||||
|
|
||||||
private val internalSSLSocketFactory: SSLSocketFactory
|
|
||||||
|
|
||||||
init {
|
|
||||||
val context = SSLContext.getInstance("TLS")
|
|
||||||
context.init(null, null, null)
|
|
||||||
internalSSLSocketFactory = context.socketFactory
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getDefaultCipherSuites(): Array<String> {
|
|
||||||
return internalSSLSocketFactory.defaultCipherSuites
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getSupportedCipherSuites(): Array<String> {
|
|
||||||
return internalSSLSocketFactory.supportedCipherSuites
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
|
||||||
override fun createSocket(): Socket? {
|
|
||||||
return enableTLSOnSocket(internalSSLSocketFactory.createSocket())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
|
||||||
override fun createSocket(s: Socket, host: String, port: Int, autoClose: Boolean): Socket? {
|
|
||||||
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(IOException::class, UnknownHostException::class)
|
|
||||||
override fun createSocket(host: String, port: Int): Socket? {
|
|
||||||
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(IOException::class, UnknownHostException::class)
|
|
||||||
override fun createSocket(host: String, port: Int, localHost: InetAddress, localPort: Int): Socket? {
|
|
||||||
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
|
||||||
override fun createSocket(host: InetAddress, port: Int): Socket? {
|
|
||||||
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
|
||||||
override fun createSocket(address: InetAddress, port: Int, localAddress: InetAddress, localPort: Int): Socket? {
|
|
||||||
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun enableTLSOnSocket(socket: Socket?): Socket? {
|
|
||||||
if (socket != null && socket is SSLSocket) {
|
|
||||||
socket.enabledProtocols = socket.supportedProtocols
|
|
||||||
}
|
|
||||||
return socket
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sslSocketFactory(TLSSocketFactory(), trustManagers[0] as X509TrustManager)
|
|
||||||
}
|
|
||||||
|
|
||||||
val specCompat = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
|
|
||||||
.tlsVersions(TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0)
|
|
||||||
.cipherSuites(
|
|
||||||
*ConnectionSpec.MODERN_TLS.cipherSuites.orEmpty().toTypedArray(),
|
|
||||||
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
|
||||||
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val specs = listOf(specCompat, ConnectionSpec.CLEARTEXT)
|
|
||||||
connectionSpecs(specs)
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,6 @@ open class Page(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
const val QUEUE = 0
|
const val QUEUE = 0
|
||||||
const val LOAD_PAGE = 1
|
const val LOAD_PAGE = 1
|
||||||
const val DOWNLOAD_IMAGE = 2
|
const val DOWNLOAD_IMAGE = 2
|
||||||
|
@ -38,7 +38,7 @@ abstract class DialogController : RestoreViewOnCreateController {
|
|||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedViewState: Bundle?): View {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedViewState: Bundle?): View {
|
||||||
dialog = onCreateDialog(savedViewState)
|
dialog = onCreateDialog(savedViewState)
|
||||||
//dialog!!.ownerActivity = activity
|
dialog!!.setOwnerActivity(activity!!)
|
||||||
dialog!!.setOnDismissListener { dismissDialog() }
|
dialog!!.setOnDismissListener { dismissDialog() }
|
||||||
if (savedViewState != null) {
|
if (savedViewState != null) {
|
||||||
val dialogState = savedViewState.getBundle(SAVED_DIALOG_STATE_TAG)
|
val dialogState = savedViewState.getBundle(SAVED_DIALOG_STATE_TAG)
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.tachiyomi.ui.base.holder
|
package eu.kanade.tachiyomi.ui.base.holder
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
@ -51,10 +50,6 @@ interface SlicedHolder {
|
|||||||
slice.showRightTopRect(topRect)
|
slice.showRightTopRect(topRect)
|
||||||
slice.showLeftBottomRect(bottomRect)
|
slice.showLeftBottomRect(bottomRect)
|
||||||
slice.showRightBottomRect(bottomRect)
|
slice.showRightBottomRect(bottomRect)
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
|
||||||
slice.showTopEdgeShadow(topShadow)
|
|
||||||
slice.showBottomEdgeShadow(bottomShadow)
|
|
||||||
}
|
|
||||||
setMargins(margin, if (topShadow) margin else 0, margin, if (bottomShadow) margin else 0)
|
setMargins(margin, if (topShadow) margin else 0, margin, if (bottomShadow) margin else 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,4 +63,4 @@ interface SlicedHolder {
|
|||||||
val margin
|
val margin
|
||||||
get() = 8.dpToPx
|
get() = 8.dpToPx
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -27,8 +27,8 @@ class SourceDividerItemDecoration(context: Context) : androidx.recyclerview.widg
|
|||||||
val params = child.layoutParams as androidx.recyclerview.widget.RecyclerView.LayoutParams
|
val params = child.layoutParams as androidx.recyclerview.widget.RecyclerView.LayoutParams
|
||||||
val top = child.bottom + params.bottomMargin
|
val top = child.bottom + params.bottomMargin
|
||||||
val bottom = top + divider.intrinsicHeight
|
val bottom = top + divider.intrinsicHeight
|
||||||
val left = parent.paddingLeft + holder.margin
|
val left = parent.paddingStart + holder.margin
|
||||||
val right = parent.width - parent.paddingRight - holder.margin
|
val right = parent.width - parent.paddingEnd - holder.margin
|
||||||
|
|
||||||
divider.setBounds(left, top, right, bottom)
|
divider.setBounds(left, top, right, bottom)
|
||||||
divider.draw(c)
|
divider.draw(c)
|
||||||
|
@ -367,7 +367,7 @@ open class BrowseCataloguePresenter(
|
|||||||
* @param selectedCategories selected categories
|
* @param selectedCategories selected categories
|
||||||
*/
|
*/
|
||||||
fun updateMangaCategories(manga: Manga, selectedCategories: List<Category>) {
|
fun updateMangaCategories(manga: Manga, selectedCategories: List<Category>) {
|
||||||
if (!selectedCategories.isEmpty()) {
|
if (selectedCategories.isNotEmpty()) {
|
||||||
if (!manga.favorite)
|
if (!manga.favorite)
|
||||||
changeMangaFavorite(manga)
|
changeMangaFavorite(manga)
|
||||||
|
|
||||||
|
@ -24,8 +24,8 @@ abstract class Pager(var currentPage: Int = 1) {
|
|||||||
fun onPageReceived(mangasPage: MangasPage) {
|
fun onPageReceived(mangasPage: MangasPage) {
|
||||||
val page = currentPage
|
val page = currentPage
|
||||||
currentPage++
|
currentPage++
|
||||||
hasNextPage = mangasPage.hasNextPage && !mangasPage.mangas.isEmpty()
|
hasNextPage = mangasPage.hasNextPage && mangasPage.mangas.isNotEmpty()
|
||||||
results.call(Pair(page, mangasPage.mangas))
|
results.call(Pair(page, mangasPage.mangas))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -205,7 +205,6 @@ open class CatalogueSearchPresenter(
|
|||||||
.map { Pair(source as CatalogueSource, it) }
|
.map { Pair(source as CatalogueSource, it) }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.onBackpressureBuffer()
|
.onBackpressureBuffer()
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe({ (source, manga) ->
|
.subscribe({ (source, manga) ->
|
||||||
|
@ -235,11 +235,11 @@ class CategoryController : NucleusController<CategoryPresenter>(),
|
|||||||
*/
|
*/
|
||||||
override fun onItemClick(view: View?, position: Int): Boolean {
|
override fun onItemClick(view: View?, position: Int): Boolean {
|
||||||
// Check if action mode is initialized and selected item exist.
|
// Check if action mode is initialized and selected item exist.
|
||||||
if (actionMode != null && position != androidx.recyclerview.widget.RecyclerView.NO_POSITION) {
|
return if (actionMode != null && position != RecyclerView.NO_POSITION) {
|
||||||
toggleSelection(position)
|
toggleSelection(position)
|
||||||
return true
|
true
|
||||||
} else {
|
} else {
|
||||||
return false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,9 +33,9 @@ class CategoryCreateDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
|
|||||||
.title(R.string.action_add_category)
|
.title(R.string.action_add_category)
|
||||||
.negativeText(android.R.string.cancel)
|
.negativeText(android.R.string.cancel)
|
||||||
.alwaysCallInputCallback()
|
.alwaysCallInputCallback()
|
||||||
.input(resources?.getString(R.string.name), currentName, false, { _, input ->
|
.input(resources?.getString(R.string.name), currentName, false) { _, input ->
|
||||||
currentName = input.toString()
|
currentName = input.toString()
|
||||||
})
|
}
|
||||||
.onPositive { _, _ -> (targetController as? Listener)?.createCategory(currentName) }
|
.onPositive { _, _ -> (targetController as? Listener)?.createCategory(currentName) }
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
@ -44,4 +44,4 @@ class CategoryCreateDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
|
|||||||
fun createCategory(name: String)
|
fun createCategory(name: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -38,9 +38,9 @@ class CategoryRenameDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
|
|||||||
.title(R.string.action_rename_category)
|
.title(R.string.action_rename_category)
|
||||||
.negativeText(android.R.string.cancel)
|
.negativeText(android.R.string.cancel)
|
||||||
.alwaysCallInputCallback()
|
.alwaysCallInputCallback()
|
||||||
.input(resources!!.getString(R.string.name), currentName, false, { _, input ->
|
.input(resources!!.getString(R.string.name), currentName, false) { _, input ->
|
||||||
currentName = input.toString()
|
currentName = input.toString()
|
||||||
})
|
}
|
||||||
.onPositive { _, _ -> onPositive() }
|
.onPositive { _, _ -> onPositive() }
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
@ -83,4 +83,4 @@ class CategoryRenameDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
|
|||||||
const val CATEGORY_KEY = "CategoryRenameDialog.category"
|
const val CATEGORY_KEY = "CategoryRenameDialog.category"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -69,4 +69,4 @@ class DownloadPresenter : BasePresenter<DownloadController>() {
|
|||||||
downloadManager.deletePendingDownloads(download)
|
downloadManager.deletePendingDownloads(download)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -138,7 +138,7 @@ open class ExtensionController : NucleusController<ExtensionPresenter>(),
|
|||||||
val searchView = searchItem.actionView as SearchView
|
val searchView = searchItem.actionView as SearchView
|
||||||
searchView.maxWidth = Int.MAX_VALUE
|
searchView.maxWidth = Int.MAX_VALUE
|
||||||
|
|
||||||
if (!query.isEmpty()) {
|
if (query.isNotEmpty()) {
|
||||||
searchItem.expandActionView()
|
searchItem.expandActionView()
|
||||||
searchView.setQuery(query, true)
|
searchView.setQuery(query, true)
|
||||||
searchView.clearFocus()
|
searchView.clearFocus()
|
||||||
|
@ -27,8 +27,8 @@ class ExtensionDividerItemDecoration(context: Context) : androidx.recyclerview.w
|
|||||||
val params = child.layoutParams as androidx.recyclerview.widget.RecyclerView.LayoutParams
|
val params = child.layoutParams as androidx.recyclerview.widget.RecyclerView.LayoutParams
|
||||||
val top = child.bottom + params.bottomMargin
|
val top = child.bottom + params.bottomMargin
|
||||||
val bottom = top + divider.intrinsicHeight
|
val bottom = top + divider.intrinsicHeight
|
||||||
val left = parent.paddingLeft + holder.margin
|
val left = parent.paddingStart + holder.margin
|
||||||
val right = parent.width - parent.paddingRight - holder.margin
|
val right = parent.width - parent.paddingEnd - holder.margin
|
||||||
|
|
||||||
divider.setBounds(left, top, right, bottom)
|
divider.setBounds(left, top, right, bottom)
|
||||||
divider.draw(c)
|
divider.draw(c)
|
||||||
|
@ -218,8 +218,7 @@ class LibraryController(
|
|||||||
override fun createSecondaryDrawer(drawer: androidx.drawerlayout.widget.DrawerLayout): ViewGroup {
|
override fun createSecondaryDrawer(drawer: androidx.drawerlayout.widget.DrawerLayout): ViewGroup {
|
||||||
val view = drawer.inflate(R.layout.library_drawer) as LibraryNavigationView
|
val view = drawer.inflate(R.layout.library_drawer) as LibraryNavigationView
|
||||||
navView = view
|
navView = view
|
||||||
drawer.setDrawerLockMode(androidx.drawerlayout.widget.DrawerLayout.LOCK_MODE_UNLOCKED,
|
drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END)
|
||||||
GravityCompat.END)
|
|
||||||
|
|
||||||
navView?.onGroupClicked = { group ->
|
navView?.onGroupClicked = { group ->
|
||||||
when (group) {
|
when (group) {
|
||||||
|
@ -231,9 +231,9 @@ class LibraryNavigationView @JvmOverloads constructor(context: Context, attrs: A
|
|||||||
item.group.items.forEach { (it as Item.Radio).checked = false }
|
item.group.items.forEach { (it as Item.Radio).checked = false }
|
||||||
item.checked = true
|
item.checked = true
|
||||||
|
|
||||||
preferences.libraryAsList().set(if (item == list) true else false)
|
preferences.libraryAsList().set(item == list)
|
||||||
|
|
||||||
item.group.items.forEach { adapter.notifyItemChanged(it) }
|
item.group.items.forEach { adapter.notifyItemChanged(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -97,12 +97,15 @@ class LibraryPresenter(
|
|||||||
fun subscribeLibrary() {
|
fun subscribeLibrary() {
|
||||||
if (librarySubscription.isNullOrUnsubscribed()) {
|
if (librarySubscription.isNullOrUnsubscribed()) {
|
||||||
librarySubscription = getLibraryObservable()
|
librarySubscription = getLibraryObservable()
|
||||||
.combineLatest(downloadTriggerRelay.observeOn(Schedulers.io()),
|
.combineLatest(downloadTriggerRelay.observeOn(Schedulers.io())) {
|
||||||
{ lib, _ -> lib.apply { setDownloadCount(mangaMap) } })
|
lib, _ -> lib.apply { setDownloadCount(mangaMap) }
|
||||||
.combineLatest(filterTriggerRelay.observeOn(Schedulers.io()),
|
}
|
||||||
{ lib, _ -> lib.copy(mangaMap = applyFilters(lib.mangaMap)) })
|
.combineLatest(filterTriggerRelay.observeOn(Schedulers.io())) {
|
||||||
.combineLatest(sortTriggerRelay.observeOn(Schedulers.io()),
|
lib, _ -> lib.copy(mangaMap = applyFilters(lib.mangaMap))
|
||||||
{ lib, _ -> lib.copy(mangaMap = applySort(lib.mangaMap)) })
|
}
|
||||||
|
.combineLatest(sortTriggerRelay.observeOn(Schedulers.io())) {
|
||||||
|
lib, _ -> lib.copy(mangaMap = applySort(lib.mangaMap))
|
||||||
|
}
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribeLatestCache({ view, (categories, mangaMap) ->
|
.subscribeLatestCache({ view, (categories, mangaMap) ->
|
||||||
view.onNextLibraryUpdate(categories, mangaMap)
|
view.onNextLibraryUpdate(categories, mangaMap)
|
||||||
|
@ -12,7 +12,7 @@ import it.gmariotti.changelibs.library.view.ChangeLogRecyclerView
|
|||||||
|
|
||||||
class ChangelogDialogController : DialogController() {
|
class ChangelogDialogController : DialogController() {
|
||||||
|
|
||||||
override fun onCreateDialog(savedState: Bundle?): Dialog {
|
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
||||||
val activity = activity!!
|
val activity = activity!!
|
||||||
val view = WhatsNewRecyclerView(activity)
|
val view = WhatsNewRecyclerView(activity)
|
||||||
return MaterialDialog.Builder(activity)
|
return MaterialDialog.Builder(activity)
|
||||||
@ -29,4 +29,4 @@ class ChangelogDialogController : DialogController() {
|
|||||||
mChangeLogFileResourceId = if (BuildConfig.DEBUG) R.raw.changelog_debug else R.raw.changelog_release
|
mChangeLogFileResourceId = if (BuildConfig.DEBUG) R.raw.changelog_debug else R.raw.changelog_release
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -315,7 +315,7 @@ class MainActivity : BaseActivity() {
|
|||||||
|
|
||||||
//Get the search query provided in extras, and if not null, perform a global search with it.
|
//Get the search query provided in extras, and if not null, perform a global search with it.
|
||||||
val query = intent.getStringExtra(SearchManager.QUERY)
|
val query = intent.getStringExtra(SearchManager.QUERY)
|
||||||
if (query != null && !query.isEmpty()) {
|
if (query != null && query.isNotEmpty()) {
|
||||||
if (router.backstackSize > 1) {
|
if (router.backstackSize > 1) {
|
||||||
router.popToRoot()
|
router.popToRoot()
|
||||||
}
|
}
|
||||||
@ -325,7 +325,7 @@ class MainActivity : BaseActivity() {
|
|||||||
INTENT_SEARCH -> {
|
INTENT_SEARCH -> {
|
||||||
val query = intent.getStringExtra(INTENT_SEARCH_QUERY)
|
val query = intent.getStringExtra(INTENT_SEARCH_QUERY)
|
||||||
val filter = intent.getStringExtra(INTENT_SEARCH_FILTER)
|
val filter = intent.getStringExtra(INTENT_SEARCH_FILTER)
|
||||||
if (query != null && !query.isEmpty()) {
|
if (query != null && query.isNotEmpty()) {
|
||||||
if (router.backstackSize > 1) {
|
if (router.backstackSize > 1) {
|
||||||
router.popToRoot()
|
router.popToRoot()
|
||||||
}
|
}
|
||||||
|
@ -109,8 +109,9 @@ class ChaptersPresenter(
|
|||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.filter { download -> download.manga.id == manga.id }
|
.filter { download -> download.manga.id == manga.id }
|
||||||
.doOnNext { onDownloadStatusChange(it) }
|
.doOnNext { onDownloadStatusChange(it) }
|
||||||
.subscribeLatestCache(ChaptersController::onChapterStatusChange,
|
.subscribeLatestCache(ChaptersController::onChapterStatusChange) {
|
||||||
{ _, error -> Timber.e(error) })
|
_, error -> Timber.e(error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -13,7 +13,7 @@ class DeletingChaptersDialog(bundle: Bundle? = null) : DialogController(bundle)
|
|||||||
const val TAG = "deleting_dialog"
|
const val TAG = "deleting_dialog"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateDialog(savedState: Bundle?): Dialog {
|
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
||||||
return MaterialDialog.Builder(activity!!)
|
return MaterialDialog.Builder(activity!!)
|
||||||
.progress(true, 0)
|
.progress(true, 0)
|
||||||
.content(R.string.deleting)
|
.content(R.string.deleting)
|
||||||
@ -24,4 +24,4 @@ class DeletingChaptersDialog(bundle: Bundle? = null) : DialogController(bundle)
|
|||||||
showDialog(router, TAG)
|
showDialog(router, TAG)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ class DownloadChaptersDialog<T>(bundle: Bundle? = null) : DialogController(bundl
|
|||||||
).map { activity.getString(it) }
|
).map { activity.getString(it) }
|
||||||
|
|
||||||
return MaterialDialog.Builder(activity)
|
return MaterialDialog.Builder(activity)
|
||||||
|
.title(R.string.manga_download)
|
||||||
.negativeText(android.R.string.cancel)
|
.negativeText(android.R.string.cancel)
|
||||||
.items(choices)
|
.items(choices)
|
||||||
.itemsCallback { _, _, position, _ ->
|
.itemsCallback { _, _, position, _ ->
|
||||||
@ -39,4 +40,4 @@ class DownloadChaptersDialog<T>(bundle: Bundle? = null) : DialogController(bundl
|
|||||||
fun downloadChapters(choice: Int)
|
fun downloadChapters(choice: Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,9 @@ import androidx.core.content.pm.ShortcutInfoCompat
|
|||||||
import androidx.core.content.pm.ShortcutManagerCompat
|
import androidx.core.content.pm.ShortcutManagerCompat
|
||||||
import androidx.core.graphics.drawable.IconCompat
|
import androidx.core.graphics.drawable.IconCompat
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
|
import com.bumptech.glide.load.DataSource
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
|
import com.bumptech.glide.load.engine.GlideException
|
||||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
||||||
import com.bumptech.glide.request.target.CustomTarget
|
import com.bumptech.glide.request.target.CustomTarget
|
||||||
import com.bumptech.glide.request.transition.Transition
|
import com.bumptech.glide.request.transition.Transition
|
||||||
@ -240,11 +242,7 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If manga source is known update source TextView.
|
// If manga source is known update source TextView.
|
||||||
manga_source.text = if (source == null) {
|
manga_source.text = source?.toString() ?: view.context.getString(R.string.unknown)
|
||||||
view.context.getString(R.string.unknown)
|
|
||||||
} else {
|
|
||||||
source.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update genres list
|
// Update genres list
|
||||||
if (manga.genre.isNullOrBlank().not()) {
|
if (manga.genre.isNullOrBlank().not()) {
|
||||||
@ -580,17 +578,18 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
|
|||||||
3 -> centerCrop().transform(MaskTransformation(R.drawable.mask_star))
|
3 -> centerCrop().transform(MaskTransformation(R.drawable.mask_star))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.into(object : CustomTarget<Bitmap>(96, 96) {
|
.listener(object : RequestListener<Bitmap> {
|
||||||
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
|
override fun onResourceReady(resource: Bitmap, model: Any, target: Target<Bitmap>, dataSource: DataSource, isFirstResource: Boolean): Boolean {
|
||||||
createShortcut(resource)
|
createShortcut(resource)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLoadCleared(placeholder: Drawable?) { }
|
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Bitmap>?, isFirstResource: Boolean): Boolean {
|
||||||
|
|
||||||
override fun onLoadFailed(errorDrawable: Drawable?) {
|
|
||||||
activity?.toast(R.string.icon_creation_fail)
|
activity?.toast(R.string.icon_creation_fail)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.submit()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -32,7 +32,7 @@ class SetTrackStatusDialog<T> : DialogController
|
|||||||
|
|
||||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
||||||
val item = item
|
val item = item
|
||||||
val statusList = item.service.getStatusList().orEmpty()
|
val statusList = item.service.getStatusList()
|
||||||
val statusString = statusList.mapNotNull { item.service.getStatus(it) }
|
val statusString = statusList.mapNotNull { item.service.getStatus(it) }
|
||||||
val selectedIndex = statusList.indexOf(item.track?.status)
|
val selectedIndex = statusList.indexOf(item.track?.status)
|
||||||
|
|
||||||
@ -40,10 +40,10 @@ class SetTrackStatusDialog<T> : DialogController
|
|||||||
.title(R.string.status)
|
.title(R.string.status)
|
||||||
.negativeText(android.R.string.cancel)
|
.negativeText(android.R.string.cancel)
|
||||||
.items(statusString)
|
.items(statusString)
|
||||||
.itemsCallbackSingleChoice(selectedIndex, { _, _, i, _ ->
|
.itemsCallbackSingleChoice(selectedIndex) { _, _, i, _ ->
|
||||||
(targetController as? Listener)?.setStatus(item, i)
|
(targetController as? Listener)?.setStatus(item, i)
|
||||||
true
|
true
|
||||||
})
|
}
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,4 +55,4 @@ class SetTrackStatusDialog<T> : DialogController
|
|||||||
const val KEY_ITEM_TRACK = "SetTrackStatusDialog.item.track"
|
const val KEY_ITEM_TRACK = "SetTrackStatusDialog.item.track"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ class TrackHolder(view: View, adapter: TrackAdapter) : BaseViewHolder(view) {
|
|||||||
logo_container.setBackgroundColor(item.service.getLogoColor())
|
logo_container.setBackgroundColor(item.service.getLogoColor())
|
||||||
if (track != null) {
|
if (track != null) {
|
||||||
track_title.setTextAppearance(itemView.context, R.style.TextAppearance_Regular_Body1_Secondary)
|
track_title.setTextAppearance(itemView.context, R.style.TextAppearance_Regular_Body1_Secondary)
|
||||||
track_title.setAllCaps(false)
|
track_title.isAllCaps = false
|
||||||
track_title.text = track.title
|
track_title.text = track.title
|
||||||
track_chapters.text = "${track.last_chapter_read}/" +
|
track_chapters.text = "${track.last_chapter_read}/" +
|
||||||
if (track.total_chapters > 0) track.total_chapters else "-"
|
if (track.total_chapters > 0) track.total_chapters else "-"
|
||||||
|
@ -76,4 +76,4 @@ class TrackSearchAdapter(context: Context)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@ class TrackSearchDialog : DialogController {
|
|||||||
service = Injekt.get<TrackManager>().getService(bundle.getInt(KEY_SERVICE))!!
|
service = Injekt.get<TrackManager>().getService(bundle.getInt(KEY_SERVICE))!!
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateDialog(savedState: Bundle?): Dialog {
|
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
||||||
val dialog = MaterialDialog.Builder(activity!!)
|
val dialog = MaterialDialog.Builder(activity!!)
|
||||||
.customView(R.layout.track_search_dialog, false)
|
.customView(R.layout.track_search_dialog, false)
|
||||||
.positiveText(android.R.string.ok)
|
.positiveText(android.R.string.ok)
|
||||||
@ -63,7 +63,7 @@ class TrackSearchDialog : DialogController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dialogView = dialog.view
|
dialogView = dialog.view
|
||||||
onViewCreated(dialog.view, savedState)
|
onViewCreated(dialog.view, savedViewState)
|
||||||
|
|
||||||
return dialog
|
return dialog
|
||||||
}
|
}
|
||||||
|
@ -42,8 +42,8 @@ class MigrationPresenter(
|
|||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.doOnNext { state = state.copy(sourcesWithManga = findSourcesWithManga(it)) }
|
.doOnNext { state = state.copy(sourcesWithManga = findSourcesWithManga(it)) }
|
||||||
.combineLatest(stateRelay.map { it.selectedSource }
|
.combineLatest(stateRelay.map { it.selectedSource }
|
||||||
.distinctUntilChanged(),
|
.distinctUntilChanged()
|
||||||
{ library, source -> library to source })
|
) { library, source -> library to source }
|
||||||
.filter { (_, source) -> source != null }
|
.filter { (_, source) -> source != null }
|
||||||
.observeOn(Schedulers.io())
|
.observeOn(Schedulers.io())
|
||||||
.map { (library, source) -> libraryToMigrationItem(library, source!!.id) }
|
.map { (library, source) -> libraryToMigrationItem(library, source!!.id) }
|
||||||
|
@ -147,7 +147,8 @@ class SearchController(
|
|||||||
preferences.migrateFlags().set(newValue)
|
preferences.migrateFlags().set(newValue)
|
||||||
|
|
||||||
true
|
true
|
||||||
}.positiveText(R.string.migrate)
|
}
|
||||||
|
.positiveText(R.string.migrate)
|
||||||
.negativeText(R.string.copy)
|
.negativeText(R.string.copy)
|
||||||
.neutralText(android.R.string.cancel)
|
.neutralText(android.R.string.cancel)
|
||||||
.onPositive { _, _ ->
|
.onPositive { _, _ ->
|
||||||
@ -197,4 +198,4 @@ class SearchController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ import android.content.pm.ActivityInfo
|
|||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
@ -68,6 +67,7 @@ import uy.kohesive.injekt.injectLazy
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activity containing the reader of Tachiyomi. This activity is mostly a container of the
|
* Activity containing the reader of Tachiyomi. This activity is mostly a container of the
|
||||||
@ -803,18 +803,22 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>(),
|
|||||||
*/
|
*/
|
||||||
private fun setCustomBrightnessValue(value: Int) {
|
private fun setCustomBrightnessValue(value: Int) {
|
||||||
// Calculate and set reader brightness.
|
// Calculate and set reader brightness.
|
||||||
val readerBrightness = if (value > 0) {
|
val readerBrightness = when {
|
||||||
value / 100f
|
value > 0 -> {
|
||||||
} else if (value < 0) {
|
value / 100f
|
||||||
0.01f
|
}
|
||||||
} else WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
|
value < 0 -> {
|
||||||
|
0.01f
|
||||||
|
}
|
||||||
|
else -> WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
|
||||||
|
}
|
||||||
|
|
||||||
window.attributes = window.attributes.apply { screenBrightness = readerBrightness }
|
window.attributes = window.attributes.apply { screenBrightness = readerBrightness }
|
||||||
|
|
||||||
// Set black overlay visibility.
|
// Set black overlay visibility.
|
||||||
if (value < 0) {
|
if (value < 0) {
|
||||||
brightness_overlay.visibility = View.VISIBLE
|
brightness_overlay.visibility = View.VISIBLE
|
||||||
val alpha = (Math.abs(value) * 2.56).toInt()
|
val alpha = (abs(value) * 2.56).toInt()
|
||||||
brightness_overlay.setBackgroundColor(Color.argb(alpha, 0, 0, 0))
|
brightness_overlay.setBackgroundColor(Color.argb(alpha, 0, 0, 0))
|
||||||
} else {
|
} else {
|
||||||
brightness_overlay.visibility = View.GONE
|
brightness_overlay.visibility = View.GONE
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader
|
package eu.kanade.tachiyomi.ui.reader
|
||||||
|
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import androidx.annotation.ColorInt
|
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.SeekBar
|
import android.widget.SeekBar
|
||||||
|
import androidx.annotation.ColorInt
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
@ -14,12 +14,14 @@ import eu.kanade.tachiyomi.util.plusAssign
|
|||||||
import eu.kanade.tachiyomi.widget.IgnoreFirstSpinnerListener
|
import eu.kanade.tachiyomi.widget.IgnoreFirstSpinnerListener
|
||||||
import eu.kanade.tachiyomi.widget.SimpleSeekBarListener
|
import eu.kanade.tachiyomi.widget.SimpleSeekBarListener
|
||||||
import kotlinx.android.synthetic.main.reader_color_filter.*
|
import kotlinx.android.synthetic.main.reader_color_filter.*
|
||||||
import kotlinx.android.synthetic.main.reader_color_filter_sheet.*
|
import kotlinx.android.synthetic.main.reader_color_filter_sheet.brightness_overlay
|
||||||
|
import kotlinx.android.synthetic.main.reader_color_filter_sheet.color_overlay
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import rx.subscriptions.CompositeSubscription
|
import rx.subscriptions.CompositeSubscription
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Color filter sheet to toggle custom filter and brightness overlay.
|
* Color filter sheet to toggle custom filter and brightness overlay.
|
||||||
@ -221,7 +223,7 @@ class ReaderColorFilterSheet(activity: ReaderActivity) : BottomSheetDialog(activ
|
|||||||
// Set black overlay visibility.
|
// Set black overlay visibility.
|
||||||
if (value < 0) {
|
if (value < 0) {
|
||||||
brightness_overlay.visibility = View.VISIBLE
|
brightness_overlay.visibility = View.VISIBLE
|
||||||
val alpha = (Math.abs(value) * 2.56).toInt()
|
val alpha = (abs(value) * 2.56).toInt()
|
||||||
brightness_overlay.setBackgroundColor(Color.argb(alpha, 0, 0, 0))
|
brightness_overlay.setBackgroundColor(Color.argb(alpha, 0, 0, 0))
|
||||||
} else {
|
} else {
|
||||||
brightness_overlay.visibility = View.GONE
|
brightness_overlay.visibility = View.GONE
|
||||||
|
@ -13,7 +13,7 @@ class ReaderColorFilterView(
|
|||||||
private val colorFilterPaint: Paint = Paint()
|
private val colorFilterPaint: Paint = Paint()
|
||||||
|
|
||||||
fun setFilterColor(color: Int, filterMode: Int) {
|
fun setFilterColor(color: Int, filterMode: Int) {
|
||||||
colorFilterPaint.setColor(color)
|
colorFilterPaint.color = color
|
||||||
colorFilterPaint.xfermode = PorterDuffXfermode(when (filterMode) {
|
colorFilterPaint.xfermode = PorterDuffXfermode(when (filterMode) {
|
||||||
1 -> PorterDuff.Mode.MULTIPLY
|
1 -> PorterDuff.Mode.MULTIPLY
|
||||||
2 -> PorterDuff.Mode.SCREEN
|
2 -> PorterDuff.Mode.SCREEN
|
||||||
|
@ -90,7 +90,7 @@ class ReaderPresenter(
|
|||||||
|
|
||||||
val chaptersForReader =
|
val chaptersForReader =
|
||||||
if (preferences.skipRead()) {
|
if (preferences.skipRead()) {
|
||||||
var list = dbChapters.filter { it -> !it.read }.toMutableList()
|
val list = dbChapters.filter { !it.read }.toMutableList()
|
||||||
val find = list.find { it.id == chapterId }
|
val find = list.find { it.id == chapterId }
|
||||||
if (find == null) {
|
if (find == null) {
|
||||||
list.add(selectedChapter)
|
list.add(selectedChapter)
|
||||||
|
@ -60,7 +60,7 @@ class SaveImageNotifier(private val context: Context) {
|
|||||||
setAutoCancel(true)
|
setAutoCancel(true)
|
||||||
color = ContextCompat.getColor(context, R.color.colorAccentLight)
|
color = ContextCompat.getColor(context, R.color.colorAccentLight)
|
||||||
// Clear old actions if they exist
|
// Clear old actions if they exist
|
||||||
if (!mActions.isEmpty())
|
if (mActions.isNotEmpty())
|
||||||
mActions.clear()
|
mActions.clear()
|
||||||
|
|
||||||
setContentIntent(NotificationHandler.openImagePendingActivity(context, file))
|
setContentIntent(NotificationHandler.openImagePendingActivity(context, file))
|
||||||
@ -72,8 +72,8 @@ class SaveImageNotifier(private val context: Context) {
|
|||||||
addAction(R.drawable.ic_delete_grey_24dp,
|
addAction(R.drawable.ic_delete_grey_24dp,
|
||||||
context.getString(R.string.action_delete),
|
context.getString(R.string.action_delete),
|
||||||
NotificationReceiver.deleteImagePendingBroadcast(context, file.absolutePath, notificationId))
|
NotificationReceiver.deleteImagePendingBroadcast(context, file.absolutePath, notificationId))
|
||||||
updateNotification()
|
|
||||||
|
|
||||||
|
updateNotification()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,7 +89,6 @@ class SaveImageNotifier(private val context: Context) {
|
|||||||
context.notificationManager.notify(notificationId, notificationBuilder.build())
|
context.notificationManager.notify(notificationId, notificationBuilder.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called on error while downloading image.
|
* Called on error while downloading image.
|
||||||
* @param error string containing error information.
|
* @param error string containing error information.
|
||||||
|
@ -33,9 +33,9 @@ class DownloadPageLoader(
|
|||||||
return downloadManager.buildPageList(source, manga, chapter.chapter)
|
return downloadManager.buildPageList(source, manga, chapter.chapter)
|
||||||
.map { pages ->
|
.map { pages ->
|
||||||
pages.map { page ->
|
pages.map { page ->
|
||||||
ReaderPage(page.index, page.url, page.imageUrl, {
|
ReaderPage(page.index, page.url, page.imageUrl) {
|
||||||
context.contentResolver.openInputStream(page.uri ?: Uri.EMPTY)!!
|
context.contentResolver.openInputStream(page.uri ?: Uri.EMPTY)!!
|
||||||
}).apply {
|
}.apply {
|
||||||
status = Page.READY
|
status = Page.READY
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import android.os.Handler
|
|||||||
import android.view.GestureDetector
|
import android.view.GestureDetector
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.ViewConfiguration
|
import android.view.ViewConfiguration
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A custom gesture detector that also implements an on long tap confirmed, because the built-in
|
* A custom gesture detector that also implements an on long tap confirmed, because the built-in
|
||||||
@ -45,7 +46,7 @@ open class GestureDetectorWithLongTap(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
MotionEvent.ACTION_MOVE -> {
|
MotionEvent.ACTION_MOVE -> {
|
||||||
if (Math.abs(ev.rawX - downX) > slop || Math.abs(ev.rawY - downY) > slop) {
|
if (abs(ev.rawX - downX) > slop || abs(ev.rawY - downY) > slop) {
|
||||||
handler.removeCallbacks(longTapFn)
|
handler.removeCallbacks(longTapFn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import android.view.animation.LinearInterpolator
|
|||||||
import android.view.animation.RotateAnimation
|
import android.view.animation.RotateAnimation
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.util.getResourceColor
|
import eu.kanade.tachiyomi.util.getResourceColor
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A custom progress bar that always rotates while being determinate. By always rotating we give
|
* A custom progress bar that always rotates while being determinate. By always rotating we give
|
||||||
@ -75,7 +76,7 @@ class ReaderProgressBar @JvmOverloads constructor(
|
|||||||
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
||||||
super.onLayout(changed, left, top, right, bottom)
|
super.onLayout(changed, left, top, right, bottom)
|
||||||
|
|
||||||
val diameter = Math.min(width, height)
|
val diameter = min(width, height)
|
||||||
val thickness = diameter / 10f
|
val thickness = diameter / 10f
|
||||||
val pad = thickness / 2f
|
val pad = thickness / 2f
|
||||||
ovalRect.set(pad, pad, diameter - pad, diameter - pad)
|
ovalRect.set(pad, pad, diameter - pad, diameter - pad)
|
||||||
|
@ -3,17 +3,16 @@ package eu.kanade.tachiyomi.ui.reader.viewer.webtoon
|
|||||||
import android.animation.Animator
|
import android.animation.Animator
|
||||||
import android.animation.AnimatorSet
|
import android.animation.AnimatorSet
|
||||||
import android.animation.ValueAnimator
|
import android.animation.ValueAnimator
|
||||||
import android.annotation.TargetApi
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.HapticFeedbackConstants
|
import android.view.HapticFeedbackConstants
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.ViewConfiguration
|
import android.view.ViewConfiguration
|
||||||
import android.view.animation.DecelerateInterpolator
|
import android.view.animation.DecelerateInterpolator
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.GestureDetectorWithLongTap
|
import eu.kanade.tachiyomi.ui.reader.viewer.GestureDetectorWithLongTap
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of a [RecyclerView] used by the webtoon reader.
|
* Implementation of a [RecyclerView] used by the webtoon reader.
|
||||||
@ -58,7 +57,6 @@ open class WebtoonRecyclerView @JvmOverloads constructor(
|
|||||||
firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
|
firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
|
||||||
override fun onScrollStateChanged(state: Int) {
|
override fun onScrollStateChanged(state: Int) {
|
||||||
super.onScrollStateChanged(state)
|
super.onScrollStateChanged(state)
|
||||||
val layoutManager = layoutManager
|
val layoutManager = layoutManager
|
||||||
@ -270,7 +268,7 @@ open class WebtoonRecyclerView @JvmOverloads constructor(
|
|||||||
if (!isZoomDragging && currentScale > 1f) {
|
if (!isZoomDragging && currentScale > 1f) {
|
||||||
var startScroll = false
|
var startScroll = false
|
||||||
|
|
||||||
if (Math.abs(dx) > touchSlop) {
|
if (abs(dx) > touchSlop) {
|
||||||
if (dx < 0) {
|
if (dx < 0) {
|
||||||
dx += touchSlop
|
dx += touchSlop
|
||||||
} else {
|
} else {
|
||||||
@ -278,7 +276,7 @@ open class WebtoonRecyclerView @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
startScroll = true
|
startScroll = true
|
||||||
}
|
}
|
||||||
if (Math.abs(dy) > touchSlop) {
|
if (abs(dy) > touchSlop) {
|
||||||
if (dy < 0) {
|
if (dy < 0) {
|
||||||
dy += touchSlop
|
dy += touchSlop
|
||||||
} else {
|
} else {
|
||||||
|
@ -13,7 +13,7 @@ class DeletingChaptersDialog(bundle: Bundle? = null) : DialogController(bundle)
|
|||||||
const val TAG = "deleting_dialog"
|
const val TAG = "deleting_dialog"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateDialog(savedState: Bundle?): Dialog {
|
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
||||||
return MaterialDialog.Builder(activity!!)
|
return MaterialDialog.Builder(activity!!)
|
||||||
.progress(true, 0)
|
.progress(true, 0)
|
||||||
.content(R.string.deleting)
|
.content(R.string.deleting)
|
||||||
@ -24,4 +24,4 @@ class DeletingChaptersDialog(bundle: Bundle? = null) : DialogController(bundle)
|
|||||||
showDialog(router, TAG)
|
showDialog(router, TAG)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -38,8 +38,9 @@ class RecentChaptersPresenter(
|
|||||||
.subscribeLatestCache(RecentChaptersController::onNextRecentChapters)
|
.subscribeLatestCache(RecentChaptersController::onNextRecentChapters)
|
||||||
|
|
||||||
getChapterStatusObservable()
|
getChapterStatusObservable()
|
||||||
.subscribeLatestCache(RecentChaptersController::onChapterStatusChange,
|
.subscribeLatestCache(RecentChaptersController::onChapterStatusChange) {
|
||||||
{ _, error -> Timber.e(error) })
|
_, error -> Timber.e(error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -31,7 +31,7 @@ class AnilistLoginActivity : AppCompatActivity() {
|
|||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe({
|
.subscribe({
|
||||||
returnToSettings()
|
returnToSettings()
|
||||||
}, { _ ->
|
}, {
|
||||||
returnToSettings()
|
returnToSettings()
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -63,14 +63,16 @@ inline fun <P : Preference> PreferenceGroup.initThenAdd(p: P, block: P.() -> Uni
|
|||||||
return p.apply {
|
return p.apply {
|
||||||
block()
|
block()
|
||||||
this.isIconSpaceReserved = false
|
this.isIconSpaceReserved = false
|
||||||
addPreference(this) }
|
addPreference(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <P : Preference> PreferenceGroup.addThenInit(p: P, block: P.() -> Unit): P {
|
inline fun <P : Preference> PreferenceGroup.addThenInit(p: P, block: P.() -> Unit): P {
|
||||||
return p.apply {
|
return p.apply {
|
||||||
this.isIconSpaceReserved = false
|
this.isIconSpaceReserved = false
|
||||||
addPreference(this)
|
addPreference(this)
|
||||||
block() }
|
block()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun Preference.onClick(crossinline block: () -> Unit) {
|
inline fun Preference.onClick(crossinline block: () -> Unit) {
|
||||||
|
@ -5,10 +5,9 @@ import android.app.Activity
|
|||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.*
|
import android.content.*
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.preference.PreferenceScreen
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.preference.PreferenceScreen
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
import com.hippo.unifile.UniFile
|
import com.hippo.unifile.UniFile
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
@ -106,21 +105,12 @@ class SettingsBackupController : SettingsController() {
|
|||||||
onClick {
|
onClick {
|
||||||
val currentDir = preferences.backupsDirectory().getOrDefault()
|
val currentDir = preferences.backupsDirectory().getOrDefault()
|
||||||
try{
|
try{
|
||||||
val intent = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||||
// Custom dir selected, open directory selector
|
|
||||||
preferences.context.getFilePicker(currentDir)
|
|
||||||
} else {
|
|
||||||
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
|
||||||
}
|
|
||||||
|
|
||||||
startActivityForResult(intent, CODE_BACKUP_DIR)
|
startActivityForResult(intent, CODE_BACKUP_DIR)
|
||||||
} catch (e: ActivityNotFoundException){
|
} catch (e: ActivityNotFoundException){
|
||||||
//Fall back to custom picker on error
|
// Fall back to custom picker on error
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
|
startActivityForResult(preferences.context.getFilePicker(currentDir), CODE_BACKUP_DIR)
|
||||||
startActivityForResult(preferences.context.getFilePicker(currentDir), CODE_BACKUP_DIR)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
preferences.backupsDirectory().asObservable()
|
preferences.backupsDirectory().asObservable()
|
||||||
@ -154,38 +144,32 @@ class SettingsBackupController : SettingsController() {
|
|||||||
// Get uri of backup folder.
|
// Get uri of backup folder.
|
||||||
val uri = data.data
|
val uri = data.data
|
||||||
|
|
||||||
// Get UriPermission so it's possible to write files post kitkat.
|
// Get UriPermission so it's possible to write files
|
||||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
|
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||||
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
|
||||||
if (uri != null)
|
if (uri != null) {
|
||||||
activity.contentResolver.takePersistableUriPermission(uri, flags)
|
activity.contentResolver.takePersistableUriPermission(uri, flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set backup Uri.
|
// Set backup Uri
|
||||||
preferences.backupsDirectory().set(uri.toString())
|
preferences.backupsDirectory().set(uri.toString())
|
||||||
}
|
}
|
||||||
CODE_BACKUP_CREATE -> if (data != null && resultCode == Activity.RESULT_OK) {
|
CODE_BACKUP_CREATE -> if (data != null && resultCode == Activity.RESULT_OK) {
|
||||||
val activity = activity ?: return
|
val activity = activity ?: return
|
||||||
val uri = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
|
||||||
val dir = data.data?.path
|
|
||||||
val file = File(dir, Backup.getDefaultFilename())
|
|
||||||
|
|
||||||
Uri.fromFile(file)
|
val uri = data.data
|
||||||
} else {
|
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||||
val uri = data.data
|
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||||
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
|
||||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
|
||||||
|
|
||||||
if (uri != null)
|
if (uri != null) {
|
||||||
activity.contentResolver.takePersistableUriPermission(uri, flags)
|
activity.contentResolver.takePersistableUriPermission(uri, flags)
|
||||||
val file = UniFile.fromUri(activity, uri)
|
|
||||||
|
|
||||||
file.uri
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val file = UniFile.fromUri(activity, uri)
|
||||||
|
|
||||||
CreatingBackupDialog().showDialog(router, TAG_CREATING_BACKUP_DIALOG)
|
CreatingBackupDialog().showDialog(router, TAG_CREATING_BACKUP_DIALOG)
|
||||||
BackupCreateService.makeBackup(activity, uri, backupFlags)
|
BackupCreateService.makeBackup(activity, file.uri, backupFlags)
|
||||||
}
|
}
|
||||||
CODE_BACKUP_RESTORE -> if (data != null && resultCode == Activity.RESULT_OK) {
|
CODE_BACKUP_RESTORE -> if (data != null && resultCode == Activity.RESULT_OK) {
|
||||||
val uri = data.data
|
val uri = data.data
|
||||||
@ -203,25 +187,17 @@ class SettingsBackupController : SettingsController() {
|
|||||||
val currentDir = preferences.backupsDirectory().getOrDefault()
|
val currentDir = preferences.backupsDirectory().getOrDefault()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// If API is lower than Lollipop use custom picker
|
// Use Android's built-in file creator
|
||||||
val intent = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
|
||||||
preferences.context.getFilePicker(currentDir)
|
|
||||||
} else {
|
|
||||||
// Use Androids build in file creator
|
|
||||||
Intent(Intent.ACTION_CREATE_DOCUMENT)
|
|
||||||
.addCategory(Intent.CATEGORY_OPENABLE)
|
.addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
.setType("application/*")
|
.setType("application/*")
|
||||||
.putExtra(Intent.EXTRA_TITLE, Backup.getDefaultFilename())
|
.putExtra(Intent.EXTRA_TITLE, Backup.getDefaultFilename())
|
||||||
}
|
|
||||||
|
|
||||||
startActivityForResult(intent, CODE_BACKUP_CREATE)
|
startActivityForResult(intent, CODE_BACKUP_CREATE)
|
||||||
} catch (e: ActivityNotFoundException) {
|
} catch (e: ActivityNotFoundException) {
|
||||||
// Handle errors where the android ROM doesn't support the built in picker
|
// Handle errors where the android ROM doesn't support the built in picker
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
|
startActivityForResult(preferences.context.getFilePicker(currentDir), CODE_BACKUP_CREATE)
|
||||||
startActivityForResult(preferences.context.getFilePicker(currentDir), CODE_BACKUP_CREATE)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class CreateBackupDialog : DialogController() {
|
class CreateBackupDialog : DialogController() {
|
||||||
@ -236,7 +212,7 @@ class SettingsBackupController : SettingsController() {
|
|||||||
.content(R.string.backup_choice)
|
.content(R.string.backup_choice)
|
||||||
.items(options)
|
.items(options)
|
||||||
.itemsDisabledIndices(0)
|
.itemsDisabledIndices(0)
|
||||||
.itemsCallbackMultiChoice(arrayOf(0, 1, 2, 3, 4), { _, positions, _ ->
|
.itemsCallbackMultiChoice(arrayOf(0, 1, 2, 3, 4)) { _, positions, _ ->
|
||||||
var flags = 0
|
var flags = 0
|
||||||
for (i in 1 until positions.size) {
|
for (i in 1 until positions.size) {
|
||||||
when (positions[i]) {
|
when (positions[i]) {
|
||||||
@ -249,7 +225,7 @@ class SettingsBackupController : SettingsController() {
|
|||||||
|
|
||||||
(targetController as? SettingsBackupController)?.createBackup(flags)
|
(targetController as? SettingsBackupController)?.createBackup(flags)
|
||||||
true
|
true
|
||||||
})
|
}
|
||||||
.positiveText(R.string.action_create)
|
.positiveText(R.string.action_create)
|
||||||
.negativeText(android.R.string.cancel)
|
.negativeText(android.R.string.cancel)
|
||||||
.build()
|
.build()
|
||||||
@ -395,7 +371,7 @@ class SettingsBackupController : SettingsController() {
|
|||||||
.negativeText(R.string.action_open_log)
|
.negativeText(R.string.action_open_log)
|
||||||
.onNegative { _, _ ->
|
.onNegative { _, _ ->
|
||||||
val context = applicationContext ?: return@onNegative
|
val context = applicationContext ?: return@onNegative
|
||||||
if (!path!!.isEmpty()) {
|
if (!path.isNullOrEmpty()) {
|
||||||
val destFile = File(path, file)
|
val destFile = File(path, file)
|
||||||
val uri = destFile.getUriCompat(context)
|
val uri = destFile.getUriCompat(context)
|
||||||
val sendIntent = Intent(Intent.ACTION_VIEW).apply {
|
val sendIntent = Intent(Intent.ACTION_VIEW).apply {
|
||||||
|
@ -5,7 +5,6 @@ import android.app.Dialog
|
|||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
@ -107,19 +106,16 @@ class SettingsDownloadController : SettingsController() {
|
|||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
when (requestCode) {
|
when (requestCode) {
|
||||||
DOWNLOAD_DIR_PRE_L -> if (data != null && resultCode == Activity.RESULT_OK) {
|
DOWNLOAD_DIR -> if (data != null && resultCode == Activity.RESULT_OK) {
|
||||||
val uri = Uri.fromFile(File(data.data?.path))
|
|
||||||
preferences.downloadsDirectory().set(uri?.toString() ?: "")
|
|
||||||
}
|
|
||||||
DOWNLOAD_DIR_L -> if (data != null && resultCode == Activity.RESULT_OK) {
|
|
||||||
val context = applicationContext ?: return
|
val context = applicationContext ?: return
|
||||||
val uri = data.data
|
val uri = data.data
|
||||||
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||||
|
|
||||||
@Suppress("NewApi")
|
if (uri != null) {
|
||||||
if (uri != null)
|
@Suppress("NewApi")
|
||||||
context.contentResolver.takePersistableUriPermission(uri, flags)
|
context.contentResolver.takePersistableUriPermission(uri, flags)
|
||||||
|
}
|
||||||
|
|
||||||
val file = UniFile.fromUri(context, uri)
|
val file = UniFile.fromUri(context, uri)
|
||||||
preferences.downloadsDirectory().set(file.uri.toString())
|
preferences.downloadsDirectory().set(file.uri.toString())
|
||||||
@ -133,19 +129,11 @@ class SettingsDownloadController : SettingsController() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun customDirectorySelected(currentDir: String) {
|
fun customDirectorySelected(currentDir: String) {
|
||||||
|
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
try {
|
||||||
startActivityForResult(preferences.context.getFilePicker(currentDir), DOWNLOAD_DIR_PRE_L)
|
startActivityForResult(intent, DOWNLOAD_DIR)
|
||||||
} else {
|
} catch (e: ActivityNotFoundException) {
|
||||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
startActivityForResult(preferences.context.getFilePicker(currentDir), DOWNLOAD_DIR)
|
||||||
try {
|
|
||||||
startActivityForResult(intent, DOWNLOAD_DIR_L)
|
|
||||||
} catch (e: ActivityNotFoundException) {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
||||||
startActivityForResult(preferences.context.getFilePicker(currentDir), DOWNLOAD_DIR_L)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,7 +149,7 @@ class SettingsDownloadController : SettingsController() {
|
|||||||
|
|
||||||
return MaterialDialog.Builder(activity)
|
return MaterialDialog.Builder(activity)
|
||||||
.items(externalDirs)
|
.items(externalDirs)
|
||||||
.itemsCallbackSingleChoice(selectedIndex, { _, _, which, text ->
|
.itemsCallbackSingleChoice(selectedIndex) { _, _, which, text ->
|
||||||
val target = targetController as? SettingsDownloadController
|
val target = targetController as? SettingsDownloadController
|
||||||
if (which == externalDirs.lastIndex) {
|
if (which == externalDirs.lastIndex) {
|
||||||
target?.customDirectorySelected(currentDir)
|
target?.customDirectorySelected(currentDir)
|
||||||
@ -169,7 +157,7 @@ class SettingsDownloadController : SettingsController() {
|
|||||||
target?.predefinedDirectorySelected(text.toString())
|
target?.predefinedDirectorySelected(text.toString())
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
})
|
}
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,7 +172,6 @@ class SettingsDownloadController : SettingsController() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
const val DOWNLOAD_DIR_PRE_L = 103
|
const val DOWNLOAD_DIR = 104
|
||||||
const val DOWNLOAD_DIR_L = 104
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -97,7 +97,7 @@ fun syncChaptersWithSource(db: DatabaseHelper,
|
|||||||
db.inTransaction {
|
db.inTransaction {
|
||||||
val deletedChapterNumbers = TreeSet<Float>()
|
val deletedChapterNumbers = TreeSet<Float>()
|
||||||
val deletedReadChapterNumbers = TreeSet<Float>()
|
val deletedReadChapterNumbers = TreeSet<Float>()
|
||||||
if (!toDelete.isEmpty()) {
|
if (toDelete.isNotEmpty()) {
|
||||||
for (c in toDelete) {
|
for (c in toDelete) {
|
||||||
if (c.read) {
|
if (c.read) {
|
||||||
deletedReadChapterNumbers.add(c.chapter_number)
|
deletedReadChapterNumbers.add(c.chapter_number)
|
||||||
@ -107,7 +107,7 @@ fun syncChaptersWithSource(db: DatabaseHelper,
|
|||||||
db.deleteChapters(toDelete).executeAsBlocking()
|
db.deleteChapters(toDelete).executeAsBlocking()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!toAdd.isEmpty()) {
|
if (toAdd.isNotEmpty()) {
|
||||||
// Set the date fetch for new items in reverse order to allow another sorting method.
|
// Set the date fetch for new items in reverse order to allow another sorting method.
|
||||||
// Sources MUST return the chapters from most to less recent, which is common.
|
// Sources MUST return the chapters from most to less recent, which is common.
|
||||||
var now = Date().time
|
var now = Date().time
|
||||||
@ -126,7 +126,7 @@ fun syncChaptersWithSource(db: DatabaseHelper,
|
|||||||
db.insertChapters(toAdd).executeAsBlocking()
|
db.insertChapters(toAdd).executeAsBlocking()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!toChange.isEmpty()) {
|
if (toChange.isNotEmpty()) {
|
||||||
db.insertChapters(toChange).executeAsBlocking()
|
db.insertChapters(toChange).executeAsBlocking()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,8 +139,8 @@ fun syncChaptersWithSource(db: DatabaseHelper,
|
|||||||
manga.last_update = if (dateFetch == 0L) Date().time else dateFetch
|
manga.last_update = if (dateFetch == 0L) Date().time else dateFetch
|
||||||
db.updateLastUpdated(manga).executeAsBlocking()
|
db.updateLastUpdated(manga).executeAsBlocking()
|
||||||
}
|
}
|
||||||
return Pair(toAdd.subtract(readded).toList(), toDelete.subtract(readded).toList())
|
|
||||||
|
|
||||||
|
return Pair(toAdd.subtract(readded).toList(), toDelete.subtract(readded).toList())
|
||||||
}
|
}
|
||||||
|
|
||||||
//checks if the chapter in db needs updated
|
//checks if the chapter in db needs updated
|
||||||
@ -148,4 +148,4 @@ private fun shouldUpdateDbChapter(dbChapter: Chapter, sourceChapter: SChapter):
|
|||||||
return dbChapter.scanlator != sourceChapter.scanlator || dbChapter.name != sourceChapter.name ||
|
return dbChapter.scanlator != sourceChapter.scanlator || dbChapter.name != sourceChapter.name ||
|
||||||
dbChapter.date_upload != sourceChapter.date_upload ||
|
dbChapter.date_upload != sourceChapter.date_upload ||
|
||||||
dbChapter.chapter_number != sourceChapter.chapter_number
|
dbChapter.chapter_number != sourceChapter.chapter_number
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.util
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.os.EnvironmentCompat
|
import androidx.core.os.EnvironmentCompat
|
||||||
@ -45,13 +44,6 @@ object DiskUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT < 21) {
|
|
||||||
val extStorages = System.getenv("SECONDARY_STORAGE")
|
|
||||||
if (extStorages != null) {
|
|
||||||
directories += extStorages.split(":").map(::File)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return directories
|
return directories
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,11 +71,7 @@ object DiskUtil {
|
|||||||
* Scans the given file so that it can be shown in gallery apps, for example.
|
* Scans the given file so that it can be shown in gallery apps, for example.
|
||||||
*/
|
*/
|
||||||
fun scanMedia(context: Context, uri: Uri) {
|
fun scanMedia(context: Context, uri: Uri) {
|
||||||
val action = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
val action = Intent.ACTION_MEDIA_SCANNER_SCAN_FILE
|
||||||
Intent.ACTION_MEDIA_MOUNTED
|
|
||||||
} else {
|
|
||||||
Intent.ACTION_MEDIA_SCANNER_SCAN_FILE
|
|
||||||
}
|
|
||||||
val mediaScanIntent = Intent(action)
|
val mediaScanIntent = Intent(action)
|
||||||
mediaScanIntent.data = uri
|
mediaScanIntent.data = uri
|
||||||
context.sendBroadcast(mediaScanIntent)
|
context.sendBroadcast(mediaScanIntent)
|
||||||
|
@ -216,7 +216,7 @@ object ImageUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun ByteArray.compareWith(magic: ByteArray): Boolean {
|
private fun ByteArray.compareWith(magic: ByteArray): Boolean {
|
||||||
for (i in 0 until magic.size) {
|
for (i in magic.indices) {
|
||||||
if (this[i] != magic[i]) return false
|
if (this[i] != magic[i]) return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@ -224,7 +224,7 @@ object ImageUtil {
|
|||||||
|
|
||||||
private fun charByteArrayOf(vararg bytes: Int): ByteArray {
|
private fun charByteArrayOf(vararg bytes: Int): ByteArray {
|
||||||
return ByteArray(bytes.size).apply {
|
return ByteArray(bytes.size).apply {
|
||||||
for (i in 0 until bytes.size) {
|
for (i in bytes.indices) {
|
||||||
set(i, bytes[i].toByte())
|
set(i, bytes[i].toByte())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,11 @@ import android.app.Application
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.LocaleList
|
|
||||||
import android.view.ContextThemeWrapper
|
import android.view.ContextThemeWrapper
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.util.*
|
import java.util.Locale
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class to change the application's language in runtime.
|
* Utility class to change the application's language in runtime.
|
||||||
@ -43,7 +42,7 @@ object LocaleHelper {
|
|||||||
*
|
*
|
||||||
* @param pref the string value stored in preferences.
|
* @param pref the string value stored in preferences.
|
||||||
*/
|
*/
|
||||||
fun getLocaleFromString(pref: String): Locale? {
|
fun getLocaleFromString(pref: String?): Locale? {
|
||||||
if (pref.isNullOrEmpty()) {
|
if (pref.isNullOrEmpty()) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@ -90,7 +89,7 @@ object LocaleHelper {
|
|||||||
* Updates the app's language to an activity.
|
* Updates the app's language to an activity.
|
||||||
*/
|
*/
|
||||||
fun updateConfiguration(wrapper: ContextThemeWrapper) {
|
fun updateConfiguration(wrapper: ContextThemeWrapper) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && appLocale != null) {
|
if (appLocale != null) {
|
||||||
val config = Configuration(preferences.context.resources.configuration)
|
val config = Configuration(preferences.context.resources.configuration)
|
||||||
config.setLocale(appLocale)
|
config.setLocale(appLocale)
|
||||||
wrapper.applyOverrideConfiguration(config)
|
wrapper.applyOverrideConfiguration(config)
|
||||||
@ -138,7 +137,7 @@ object LocaleHelper {
|
|||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||||
newConfig.setLocale(locale)
|
newConfig.setLocale(locale)
|
||||||
} else {
|
} else {
|
||||||
newConfig.setLocales(LocaleList(locale))
|
newConfig.setLocale(locale)
|
||||||
}
|
}
|
||||||
return newConfig
|
return newConfig
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ object SharedData {
|
|||||||
* @param data the object to put.
|
* @param data the object to put.
|
||||||
*/
|
*/
|
||||||
fun <T : Any> put(data: T) {
|
fun <T : Any> put(data: T) {
|
||||||
map.put(data.javaClass, data)
|
map[data.javaClass] = data
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.util
|
package eu.kanade.tachiyomi.util
|
||||||
|
|
||||||
import java.lang.Math.floor
|
import kotlin.math.floor
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces the given string to have at most [count] characters using [replacement] at its end.
|
* Replaces the given string to have at most [count] characters using [replacement] at its end.
|
||||||
@ -29,4 +29,4 @@ fun String.truncateCenter(count: Int, replacement: String = "..."): String{
|
|||||||
val pieceLength:Int = floor((count - replacement.length).div(2.0)).toInt()
|
val pieceLength:Int = floor((count - replacement.length).div(2.0)).toInt()
|
||||||
|
|
||||||
return "${ take(pieceLength) }$replacement${ takeLast(pieceLength) }"
|
return "${ take(pieceLength) }$replacement${ takeLast(pieceLength) }"
|
||||||
}
|
}
|
||||||
|
@ -113,7 +113,7 @@ inline fun View.visibleIf(block: () -> Boolean) {
|
|||||||
* @param random random color
|
* @param random random color
|
||||||
*/
|
*/
|
||||||
fun View.getRound(text: String, random : Boolean = true): TextDrawable {
|
fun View.getRound(text: String, random : Boolean = true): TextDrawable {
|
||||||
val size = Math.min(this.width, this.height)
|
val size = min(this.width, this.height)
|
||||||
return TextDrawable.builder()
|
return TextDrawable.builder()
|
||||||
.beginConfig()
|
.beginConfig()
|
||||||
.width(size)
|
.width(size)
|
||||||
|
@ -36,7 +36,6 @@ abstract class WebViewClientCompat : WebViewClient() {
|
|||||||
return shouldOverrideUrlCompat(view, url)
|
return shouldOverrideUrlCompat(view, url)
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
final override fun shouldInterceptRequest(
|
final override fun shouldInterceptRequest(
|
||||||
view: WebView,
|
view: WebView,
|
||||||
request: WebResourceRequest
|
request: WebResourceRequest
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
package eu.kanade.tachiyomi.widget
|
package eu.kanade.tachiyomi.widget
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import android.util.AttributeSet
|
import kotlin.math.max
|
||||||
|
|
||||||
class AutofitRecyclerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
class AutofitRecyclerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
||||||
androidx.recyclerview.widget.RecyclerView(context, attrs) {
|
androidx.recyclerview.widget.RecyclerView(context, attrs) {
|
||||||
@ -37,7 +38,7 @@ class AutofitRecyclerView @JvmOverloads constructor(context: Context, attrs: Att
|
|||||||
override fun onMeasure(widthSpec: Int, heightSpec: Int) {
|
override fun onMeasure(widthSpec: Int, heightSpec: Int) {
|
||||||
super.onMeasure(widthSpec, heightSpec)
|
super.onMeasure(widthSpec, heightSpec)
|
||||||
if (spanCount == 0 && columnWidth > 0) {
|
if (spanCount == 0 && columnWidth > 0) {
|
||||||
val count = Math.max(1, measuredWidth / columnWidth)
|
val count = max(1, measuredWidth / columnWidth)
|
||||||
spanCount = count
|
spanCount = count
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,13 +21,13 @@ class CustomLayoutPickerActivity : FilePickerActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class CustomLayoutFilePickerFragment : FilePickerFragment() {
|
class CustomLayoutFilePickerFragment : FilePickerFragment() {
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): androidx.recyclerview.widget.RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
when (viewType) {
|
return when (viewType) {
|
||||||
LogicHandler.VIEWTYPE_DIR -> {
|
LogicHandler.VIEWTYPE_DIR -> {
|
||||||
val view = parent.inflate(R.layout.common_listitem_dir)
|
val view = parent.inflate(R.layout.common_listitem_dir)
|
||||||
return DirViewHolder(view)
|
DirViewHolder(view)
|
||||||
}
|
}
|
||||||
else -> return super.onCreateViewHolder(parent, viewType)
|
else -> super.onCreateViewHolder(parent, viewType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,32 +16,26 @@ class ElevationAppBarLayout @JvmOverloads constructor(
|
|||||||
private var origStateAnimator: StateListAnimator? = null
|
private var origStateAnimator: StateListAnimator? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (Build.VERSION.SDK_INT >= 21) {
|
origStateAnimator = stateListAnimator
|
||||||
origStateAnimator = stateListAnimator
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun enableElevation() {
|
fun enableElevation() {
|
||||||
if (Build.VERSION.SDK_INT >= 21) {
|
stateListAnimator = origStateAnimator
|
||||||
stateListAnimator = origStateAnimator
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun disableElevation() {
|
fun disableElevation() {
|
||||||
if (Build.VERSION.SDK_INT >= 21) {
|
stateListAnimator = StateListAnimator().apply {
|
||||||
stateListAnimator = StateListAnimator().apply {
|
val objAnimator = ObjectAnimator.ofFloat(this, "elevation", 0f)
|
||||||
val objAnimator = ObjectAnimator.ofFloat(this, "elevation", 0f)
|
|
||||||
|
|
||||||
// Enabled and collapsible, but not collapsed means not elevated
|
// Enabled and collapsible, but not collapsed means not elevated
|
||||||
addState(intArrayOf(android.R.attr.enabled, R.attr.state_collapsible, -R.attr.state_collapsed),
|
addState(intArrayOf(android.R.attr.enabled, R.attr.state_collapsible, -R.attr.state_collapsed),
|
||||||
objAnimator)
|
objAnimator)
|
||||||
|
|
||||||
// Default enabled state
|
// Default enabled state
|
||||||
addState(intArrayOf(android.R.attr.enabled), objAnimator)
|
addState(intArrayOf(android.R.attr.enabled), objAnimator)
|
||||||
|
|
||||||
// Disabled state
|
// Disabled state
|
||||||
addState(IntArray(0), objAnimator)
|
addState(IntArray(0), objAnimator)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.util.getResourceColor
|
import eu.kanade.tachiyomi.util.getResourceColor
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An alternative implementation of [android.support.design.widget.NavigationView], without menu
|
* An alternative implementation of [com.google.android.material.navigation.NavigationView], without menu
|
||||||
* inflation and allowing customizable items (multiple selections, custom views, etc).
|
* inflation and allowing customizable items (multiple selections, custom views, etc).
|
||||||
*/
|
*/
|
||||||
open class ExtendedNavigationView @JvmOverloads constructor(
|
open class ExtendedNavigationView @JvmOverloads constructor(
|
||||||
@ -210,8 +210,7 @@ open class ExtendedNavigationView @JvmOverloads constructor(
|
|||||||
|
|
||||||
@CallSuper
|
@CallSuper
|
||||||
override fun getItemViewType(position: Int): Int {
|
override fun getItemViewType(position: Int): Int {
|
||||||
val item = items[position]
|
return when (items[position]) {
|
||||||
return when (item) {
|
|
||||||
is Item.Header -> VIEW_TYPE_HEADER
|
is Item.Header -> VIEW_TYPE_HEADER
|
||||||
is Item.Separator -> VIEW_TYPE_SEPARATOR
|
is Item.Separator -> VIEW_TYPE_SEPARATOR
|
||||||
is Item.Radio -> VIEW_TYPE_RADIO
|
is Item.Radio -> VIEW_TYPE_RADIO
|
||||||
|
@ -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)
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,6 +5,7 @@ import android.os.Parcelable
|
|||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.widget.SeekBar
|
import android.widget.SeekBar
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
|
||||||
class NegativeSeekBar @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
class NegativeSeekBar @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
||||||
@ -28,21 +29,21 @@ class NegativeSeekBar @JvmOverloads constructor(context: Context, attrs: Attribu
|
|||||||
|
|
||||||
super.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
|
super.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
|
||||||
override fun onProgressChanged(seekBar: SeekBar?, value: Int, fromUser: Boolean) {
|
override fun onProgressChanged(seekBar: SeekBar?, value: Int, fromUser: Boolean) {
|
||||||
listener?.let { it.onProgressChanged(seekBar, minValue + value, fromUser) }
|
listener?.onProgressChanged(seekBar, minValue + value, fromUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStartTrackingTouch(p0: SeekBar?) {
|
override fun onStartTrackingTouch(p0: SeekBar?) {
|
||||||
listener?.let { it.onStartTrackingTouch(p0) }
|
listener?.onStartTrackingTouch(p0)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStopTrackingTouch(p0: SeekBar?) {
|
override fun onStopTrackingTouch(p0: SeekBar?) {
|
||||||
listener?.let { it.onStopTrackingTouch(p0) }
|
listener?.onStopTrackingTouch(p0)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setProgress(progress: Int) {
|
override fun setProgress(progress: Int) {
|
||||||
super.setProgress(Math.abs(minValue) + progress)
|
super.setProgress(abs(minValue) + progress)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setMinSeek(minValue: Int) {
|
fun setMinSeek(minValue: Int) {
|
||||||
@ -66,4 +67,4 @@ class NegativeSeekBar @JvmOverloads constructor(context: Context, attrs: Attribu
|
|||||||
super.setProgress(origProgress)
|
super.setProgress(origProgress)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ class PTSansTextView @JvmOverloads constructor(context: Context, attrs: Attribut
|
|||||||
Typeface.createFromAsset(context.assets, when (typeface) {
|
Typeface.createFromAsset(context.assets, when (typeface) {
|
||||||
PTSANS_NARROW -> "fonts/PTSans-Narrow.ttf"
|
PTSANS_NARROW -> "fonts/PTSans-Narrow.ttf"
|
||||||
PTSANS_NARROW_BOLD -> "fonts/PTSans-NarrowBold.ttf"
|
PTSANS_NARROW_BOLD -> "fonts/PTSans-NarrowBold.ttf"
|
||||||
else -> throw IllegalArgumentException("Font not found " + typeface)
|
else -> throw IllegalArgumentException("Font not found $typeface")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -2,14 +2,11 @@ package eu.kanade.tachiyomi.widget
|
|||||||
|
|
||||||
import android.animation.Animator
|
import android.animation.Animator
|
||||||
import android.animation.AnimatorListenerAdapter
|
import android.animation.AnimatorListenerAdapter
|
||||||
import android.annotation.TargetApi
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build
|
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewAnimationUtils
|
import android.view.ViewAnimationUtils
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
class RevealAnimationView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
class RevealAnimationView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
||||||
View(context, attrs) {
|
View(context, attrs) {
|
||||||
|
|
||||||
@ -21,28 +18,25 @@ class RevealAnimationView @JvmOverloads constructor(context: Context, attrs: Att
|
|||||||
* @param initialRadius size of radius of animation
|
* @param initialRadius size of radius of animation
|
||||||
*/
|
*/
|
||||||
fun hideRevealEffect(centerX: Int, centerY: Int, initialRadius: Int) {
|
fun hideRevealEffect(centerX: Int, centerY: Int, initialRadius: Int) {
|
||||||
if (Build.VERSION.SDK_INT >= 21) {
|
// Make the view visible.
|
||||||
|
this.visibility = View.VISIBLE
|
||||||
|
|
||||||
// Make the view visible.
|
// Create the animation (the final radius is zero).
|
||||||
this.visibility = View.VISIBLE
|
val anim = ViewAnimationUtils.createCircularReveal(
|
||||||
|
this, centerX, centerY, initialRadius.toFloat(), 0f)
|
||||||
|
|
||||||
// Create the animation (the final radius is zero).
|
// Set duration of animation.
|
||||||
val anim = ViewAnimationUtils.createCircularReveal(
|
anim.duration = 500
|
||||||
this, centerX, centerY, initialRadius.toFloat(), 0f)
|
|
||||||
|
|
||||||
// Set duration of animation.
|
// make the view invisible when the animation is done
|
||||||
anim.duration = 500
|
anim.addListener(object : AnimatorListenerAdapter() {
|
||||||
|
override fun onAnimationEnd(animation: Animator) {
|
||||||
|
super.onAnimationEnd(animation)
|
||||||
|
this@RevealAnimationView.visibility = View.INVISIBLE
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// make the view invisible when the animation is done
|
anim.start()
|
||||||
anim.addListener(object : AnimatorListenerAdapter() {
|
|
||||||
override fun onAnimationEnd(animation: Animator) {
|
|
||||||
super.onAnimationEnd(animation)
|
|
||||||
this@RevealAnimationView.visibility = View.INVISIBLE
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
anim.start()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -55,25 +49,20 @@ class RevealAnimationView @JvmOverloads constructor(context: Context, attrs: Att
|
|||||||
* @return sdk version lower then 21
|
* @return sdk version lower then 21
|
||||||
*/
|
*/
|
||||||
fun showRevealEffect(centerX: Int, centerY: Int, listener: Animator.AnimatorListener): Boolean {
|
fun showRevealEffect(centerX: Int, centerY: Int, listener: Animator.AnimatorListener): Boolean {
|
||||||
if (Build.VERSION.SDK_INT >= 21) {
|
this.visibility = View.VISIBLE
|
||||||
|
|
||||||
this.visibility = View.VISIBLE
|
val height = this.height
|
||||||
|
|
||||||
val height = this.height
|
// Create animation
|
||||||
|
val anim = ViewAnimationUtils.createCircularReveal(
|
||||||
|
this, centerX, centerY, 0f, height.toFloat())
|
||||||
|
|
||||||
// Create animation
|
// Set duration of animation
|
||||||
val anim = ViewAnimationUtils.createCircularReveal(
|
anim.duration = 350
|
||||||
this, centerX, centerY, 0f, height.toFloat())
|
|
||||||
|
|
||||||
// Set duration of animation
|
anim.addListener(listener)
|
||||||
anim.duration = 350
|
anim.start()
|
||||||
|
return true
|
||||||
anim.addListener(listener)
|
|
||||||
anim.start()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,18 +2,19 @@ package eu.kanade.tachiyomi.widget
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.google.android.material.R
|
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
|
||||||
import androidx.core.view.ViewCompat
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import androidx.appcompat.widget.TintTypedArray
|
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
|
import androidx.appcompat.widget.TintTypedArray
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.google.android.material.R
|
||||||
import com.google.android.material.internal.ScrimInsetsFrameLayout
|
import com.google.android.material.internal.ScrimInsetsFrameLayout
|
||||||
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import eu.kanade.tachiyomi.util.inflate
|
import eu.kanade.tachiyomi.util.inflate
|
||||||
|
import kotlin.math.min
|
||||||
import eu.kanade.tachiyomi.R as TR
|
import eu.kanade.tachiyomi.R as TR
|
||||||
|
|
||||||
@Suppress("LeakingThis")
|
@Suppress("LeakingThis")
|
||||||
@ -67,7 +68,7 @@ open class SimpleNavigationView @JvmOverloads constructor(
|
|||||||
override fun onMeasure(widthSpec: Int, heightSpec: Int) {
|
override fun onMeasure(widthSpec: Int, heightSpec: Int) {
|
||||||
val width = when (MeasureSpec.getMode(widthSpec)) {
|
val width = when (MeasureSpec.getMode(widthSpec)) {
|
||||||
MeasureSpec.AT_MOST -> MeasureSpec.makeMeasureSpec(
|
MeasureSpec.AT_MOST -> MeasureSpec.makeMeasureSpec(
|
||||||
Math.min(MeasureSpec.getSize(widthSpec), maxWidth), MeasureSpec.EXACTLY)
|
min(MeasureSpec.getSize(widthSpec), maxWidth), MeasureSpec.EXACTLY)
|
||||||
MeasureSpec.UNSPECIFIED -> MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.EXACTLY)
|
MeasureSpec.UNSPECIFIED -> MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.EXACTLY)
|
||||||
else -> widthSpec
|
else -> widthSpec
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ abstract class LoginDialogPreference(bundle: Bundle? = null) : DialogController(
|
|||||||
|
|
||||||
var requestSubscription: Subscription? = null
|
var requestSubscription: Subscription? = null
|
||||||
|
|
||||||
override fun onCreateDialog(savedState: Bundle?): Dialog {
|
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
||||||
val dialog = MaterialDialog.Builder(activity!!)
|
val dialog = MaterialDialog.Builder(activity!!)
|
||||||
.customView(R.layout.pref_account_login, false)
|
.customView(R.layout.pref_account_login, false)
|
||||||
.negativeText(android.R.string.cancel)
|
.negativeText(android.R.string.cancel)
|
||||||
|
@ -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>
|
|
BIN
app/src/main/res/drawable-hdpi/splash_icon.webp
Normal file
BIN
app/src/main/res/drawable-hdpi/splash_icon.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
BIN
app/src/main/res/drawable-hdpi/tachiyomi_circle.webp
Normal file
BIN
app/src/main/res/drawable-hdpi/tachiyomi_circle.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.0 KiB |
BIN
app/src/main/res/drawable-mdpi/splash_icon.webp
Normal file
BIN
app/src/main/res/drawable-mdpi/splash_icon.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 902 B |
Binary file not shown.
BIN
app/src/main/res/drawable-mdpi/tachiyomi_circle.webp
Normal file
BIN
app/src/main/res/drawable-mdpi/tachiyomi_circle.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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
Loading…
Reference in New Issue
Block a user