Merge branch 'devel' into feature/multiselect
This commit is contained in:
10
README.md
10
README.md
@@ -25,6 +25,8 @@ Download APK from Github Releases:
|
||||
* Tablet-optimized material design UI
|
||||
* Standard and Webtoon-optimized reader
|
||||
* Notifications about new chapters with updates feed
|
||||
* Available in multiple languages
|
||||
* Password protect access to the app
|
||||
|
||||
### Screenshots
|
||||
|
||||
@@ -35,6 +37,14 @@ Download APK from Github Releases:
|
||||
|  |  |
|
||||
|-----------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------|
|
||||
|
||||
### Localization
|
||||
|
||||
<a href="https://hosted.weblate.org/engage/kotatsu/">
|
||||
<img src="https://hosted.weblate.org/widgets/kotatsu/-/287x66-white.png" alt="Translation status" />
|
||||
</a>
|
||||
|
||||
Kotatsu is localized in a number of different languages, if you would like to help improve these or add new languages, please head over to the Weblate <a href="https://hosted.weblate.org/engage/kotatsu/">project page</a>
|
||||
|
||||
### License
|
||||
[](http://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
||||
|
||||
@@ -65,12 +65,12 @@ android {
|
||||
}
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
|
||||
implementation('com.github.nv95:kotatsu-parsers:3ea7e92e64') {
|
||||
implementation('com.github.nv95:kotatsu-parsers:0ee689cd2f') {
|
||||
exclude group: 'org.json', module: 'json'
|
||||
}
|
||||
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1'
|
||||
|
||||
implementation 'androidx.core:core-ktx:1.7.0'
|
||||
implementation 'androidx.activity:activity-ktx:1.4.0'
|
||||
@@ -108,7 +108,7 @@ dependencies {
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1'
|
||||
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.0'
|
||||
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.1'
|
||||
testImplementation 'io.insert-koin:koin-test-junit4:3.1.5'
|
||||
|
||||
androidTestImplementation 'androidx.test:runner:1.4.0'
|
||||
|
||||
@@ -58,15 +58,6 @@
|
||||
<activity
|
||||
android:name="org.koitharu.kotatsu.settings.SettingsActivity"
|
||||
android:label="@string/settings" />
|
||||
<activity
|
||||
android:name="org.koitharu.kotatsu.reader.ui.SimpleSettingsActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/settings">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MANAGE_NETWORK_USAGE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name="org.koitharu.kotatsu.browser.BrowserActivity"
|
||||
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
|
||||
|
||||
@@ -9,7 +9,7 @@ import org.koitharu.kotatsu.core.prefs.ReaderMode
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.utils.ext.mapToSet
|
||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||
|
||||
class MangaDataRepository(private val db: MangaDatabase) {
|
||||
|
||||
@@ -37,7 +37,7 @@ class MangaDataRepository(private val db: MangaDatabase) {
|
||||
|
||||
suspend fun resolveIntent(intent: MangaIntent): Manga? = when {
|
||||
intent.manga != null -> intent.manga
|
||||
intent.mangaId != 0L -> db.mangaDao.find(intent.mangaId)?.toManga()
|
||||
intent.mangaId != 0L -> findMangaById(intent.mangaId)
|
||||
else -> null // TODO resolve uri
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import org.koitharu.kotatsu.core.network.CommonHeaders
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
import org.koitharu.kotatsu.parsers.util.await
|
||||
import org.koitharu.kotatsu.utils.ext.medianOrNull
|
||||
import org.koitharu.kotatsu.parsers.util.medianOrNull
|
||||
import java.io.InputStream
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
|
||||
@@ -6,14 +6,18 @@ import androidx.annotation.CallSuper
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner
|
||||
import org.koitharu.kotatsu.base.ui.util.WindowInsetsDelegate
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.settings.SettingsActivity
|
||||
import org.koitharu.kotatsu.settings.SettingsHeadersFragment
|
||||
|
||||
abstract class BasePreferenceFragment(@StringRes private val titleId: Int) : PreferenceFragmentCompat(),
|
||||
abstract class BasePreferenceFragment(@StringRes private val titleId: Int) :
|
||||
PreferenceFragmentCompat(),
|
||||
WindowInsetsDelegate.WindowInsetsListener,
|
||||
RecyclerViewOwner {
|
||||
|
||||
@@ -39,16 +43,20 @@ abstract class BasePreferenceFragment(@StringRes private val titleId: Int) : Pre
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (titleId != 0) {
|
||||
activity?.setTitle(titleId)
|
||||
setTitle(getString(titleId))
|
||||
}
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun onWindowInsetsChanged(insets: Insets) {
|
||||
listView.updatePadding(
|
||||
left = insets.left,
|
||||
right = insets.right,
|
||||
bottom = insets.bottom
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UsePropertyAccessSyntax")
|
||||
protected fun setTitle(title: CharSequence) {
|
||||
(parentFragment as? SettingsHeadersFragment)?.setTitle(title)
|
||||
?: activity?.setTitle(title)
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ abstract class TagsDao {
|
||||
@Query(
|
||||
"""SELECT tags.* FROM tags
|
||||
LEFT JOIN manga_tags ON tags.tag_id = manga_tags.tag_id
|
||||
GROUP BY manga_tags.tag_id
|
||||
GROUP BY tags.title
|
||||
ORDER BY COUNT(manga_id) DESC
|
||||
LIMIT :limit"""
|
||||
)
|
||||
@@ -22,7 +22,7 @@ abstract class TagsDao {
|
||||
"""SELECT tags.* FROM tags
|
||||
LEFT JOIN manga_tags ON tags.tag_id = manga_tags.tag_id
|
||||
WHERE tags.source = :source
|
||||
GROUP BY manga_tags.tag_id
|
||||
GROUP BY tags.title
|
||||
ORDER BY COUNT(manga_id) DESC
|
||||
LIMIT :limit"""
|
||||
)
|
||||
@@ -32,7 +32,7 @@ abstract class TagsDao {
|
||||
"""SELECT tags.* FROM tags
|
||||
LEFT JOIN manga_tags ON tags.tag_id = manga_tags.tag_id
|
||||
WHERE tags.source = :source AND title LIKE :query
|
||||
GROUP BY manga_tags.tag_id
|
||||
GROUP BY tags.title
|
||||
ORDER BY COUNT(manga_id) DESC
|
||||
LIMIT :limit"""
|
||||
)
|
||||
@@ -42,7 +42,7 @@ abstract class TagsDao {
|
||||
"""SELECT tags.* FROM tags
|
||||
LEFT JOIN manga_tags ON tags.tag_id = manga_tags.tag_id
|
||||
WHERE title LIKE :query
|
||||
GROUP BY manga_tags.tag_id
|
||||
GROUP BY tags.title
|
||||
ORDER BY COUNT(manga_id) DESC
|
||||
LIMIT :limit"""
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.core.db.entity
|
||||
import androidx.room.Embedded
|
||||
import androidx.room.Junction
|
||||
import androidx.room.Relation
|
||||
import org.koitharu.kotatsu.utils.ext.mapToSet
|
||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||
|
||||
class MangaWithTags(
|
||||
@Embedded val manga: MangaEntity,
|
||||
@@ -15,7 +15,5 @@ class MangaWithTags(
|
||||
val tags: List<TagEntity>
|
||||
) {
|
||||
|
||||
fun toManga() = manga.toManga(tags.mapToSet {
|
||||
it.toMangaTag()
|
||||
})
|
||||
fun toManga() = manga.toManga(tags.mapToSet { it.toMangaTag() })
|
||||
}
|
||||
@@ -3,9 +3,10 @@ package org.koitharu.kotatsu.core.db.entity
|
||||
import androidx.room.Embedded
|
||||
import androidx.room.Junction
|
||||
import androidx.room.Relation
|
||||
import org.koitharu.kotatsu.core.model.TrackingLogItem
|
||||
import org.koitharu.kotatsu.utils.ext.mapToSet
|
||||
import java.util.*
|
||||
import org.koitharu.kotatsu.core.model.TrackingLogItem
|
||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||
import org.koitharu.kotatsu.utils.ext.mapToSet
|
||||
|
||||
class TrackLogWithManga(
|
||||
@Embedded val trackLog: TrackLogEntity,
|
||||
|
||||
@@ -4,7 +4,7 @@ import android.os.Parcel
|
||||
import androidx.core.os.ParcelCompat
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
|
||||
fun Manga.writeToParcel(out: Parcel, flags: Int) {
|
||||
fun Manga.writeToParcel(out: Parcel, flags: Int, withChapters: Boolean) {
|
||||
out.writeLong(id)
|
||||
out.writeString(title)
|
||||
out.writeString(altTitle)
|
||||
@@ -18,7 +18,11 @@ fun Manga.writeToParcel(out: Parcel, flags: Int) {
|
||||
out.writeParcelable(ParcelableMangaTags(tags), flags)
|
||||
out.writeSerializable(state)
|
||||
out.writeString(author)
|
||||
out.writeParcelable(chapters?.let(::ParcelableMangaChapters), flags)
|
||||
if (withChapters) {
|
||||
out.writeParcelable(chapters?.let(::ParcelableMangaChapters), flags)
|
||||
} else {
|
||||
out.writeString(null)
|
||||
}
|
||||
out.writeSerializable(source)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,24 +2,34 @@ package org.koitharu.kotatsu.core.model.parcelable
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.util.Log
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
|
||||
// Limits to avoid TransactionTooLargeException
|
||||
private const val MAX_SAFE_SIZE = 1024 * 512 // Assume that 512 kb is safe parcel size
|
||||
private const val MAX_SAFE_CHAPTERS_COUNT = 40 // this is 100% safe
|
||||
|
||||
class ParcelableManga(
|
||||
val manga: Manga,
|
||||
) : Parcelable {
|
||||
|
||||
constructor(parcel: Parcel) : this(parcel.readManga())
|
||||
|
||||
init {
|
||||
if (BuildConfig.DEBUG && manga.chapters != null) {
|
||||
Log.w("ParcelableManga", "Passing manga with chapters as Parcelable is dangerous!")
|
||||
}
|
||||
}
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
manga.writeToParcel(parcel, flags)
|
||||
val chapters = manga.chapters
|
||||
if (chapters == null || chapters.size <= MAX_SAFE_CHAPTERS_COUNT) {
|
||||
// fast path
|
||||
manga.writeToParcel(parcel, flags, withChapters = true)
|
||||
return
|
||||
}
|
||||
val tempParcel = Parcel.obtain()
|
||||
manga.writeToParcel(tempParcel, flags, withChapters = true)
|
||||
val size = tempParcel.dataSize()
|
||||
if (size < MAX_SAFE_SIZE) {
|
||||
parcel.appendFrom(tempParcel, 0, size)
|
||||
} else {
|
||||
manga.writeToParcel(parcel, flags, withChapters = false)
|
||||
}
|
||||
tempParcel.recycle()
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
package org.koitharu.kotatsu.core.network
|
||||
|
||||
import android.util.Log
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import okio.Buffer
|
||||
import java.io.IOException
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
private const val TAG = "CURL"
|
||||
|
||||
class CurlLoggingInterceptor(
|
||||
private val extraCurlOptions: String? = null,
|
||||
) : Interceptor {
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val request: Request = chain.request()
|
||||
var compressed = false
|
||||
val curlCmd = StringBuilder("curl")
|
||||
if (extraCurlOptions != null) {
|
||||
curlCmd.append(" ").append(extraCurlOptions)
|
||||
}
|
||||
curlCmd.append(" -X ").append(request.method)
|
||||
val headers = request.headers
|
||||
var i = 0
|
||||
val count = headers.size
|
||||
while (i < count) {
|
||||
val name = headers.name(i)
|
||||
val value = headers.value(i)
|
||||
if ("Accept-Encoding".equals(name, ignoreCase = true) && "gzip".equals(value,
|
||||
ignoreCase = true)
|
||||
) {
|
||||
compressed = true
|
||||
}
|
||||
curlCmd.append(" -H " + "\"").append(name).append(": ").append(value).append("\"")
|
||||
i++
|
||||
}
|
||||
val requestBody = request.body
|
||||
if (requestBody != null) {
|
||||
val buffer = Buffer()
|
||||
requestBody.writeTo(buffer)
|
||||
val contentType = requestBody.contentType()
|
||||
val charset = contentType?.charset(StandardCharsets.UTF_8) ?: StandardCharsets.UTF_8
|
||||
curlCmd.append(" --data $'")
|
||||
.append(buffer.readString(charset).replace("\n", "\\n"))
|
||||
.append("'")
|
||||
}
|
||||
curlCmd.append(if (compressed) " --compressed " else " ").append(request.url)
|
||||
Log.d(TAG, "╭--- cURL (" + request.url + ")")
|
||||
Log.d(TAG, curlCmd.toString())
|
||||
Log.d(TAG, "╰--- (copy and paste the above line to a terminal)")
|
||||
return chain.proceed(request)
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import okhttp3.CookieJar
|
||||
import okhttp3.OkHttpClient
|
||||
import org.koin.dsl.bind
|
||||
import org.koin.dsl.module
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.core.parser.MangaLoaderContextImpl
|
||||
import org.koitharu.kotatsu.local.data.LocalStorageManager
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
@@ -22,9 +21,6 @@ val networkModule
|
||||
cache(get<LocalStorageManager>().createHttpCache())
|
||||
addInterceptor(UserAgentInterceptor())
|
||||
addInterceptor(CloudFlareInterceptor())
|
||||
if (BuildConfig.DEBUG) {
|
||||
addNetworkInterceptor(CurlLoggingInterceptor())
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
single<MangaLoaderContext> { MangaLoaderContextImpl(get(), get(), get()) }
|
||||
|
||||
@@ -165,6 +165,18 @@ class AppSettings(context: Context) {
|
||||
else -> SimpleDateFormat(format, Locale.getDefault())
|
||||
}
|
||||
|
||||
fun getSuggestionsTagsBlacklistRegex(): Regex? {
|
||||
val string = prefs.getString(KEY_SUGGESTIONS_EXCLUDE_TAGS, null)?.trimEnd(' ', ',')
|
||||
if (string.isNullOrEmpty()) {
|
||||
return null
|
||||
}
|
||||
val tags = string.split(',')
|
||||
val regex = tags.joinToString(prefix = "(", separator = "|", postfix = ")") { tag ->
|
||||
Regex.escape(tag.trim())
|
||||
}
|
||||
return Regex(regex, RegexOption.IGNORE_CASE)
|
||||
}
|
||||
|
||||
fun getMangaSources(includeHidden: Boolean): List<MangaSource> {
|
||||
val list = MangaSource.values().toMutableList()
|
||||
list.remove(MangaSource.LOCAL)
|
||||
@@ -247,17 +259,16 @@ class AppSettings(context: Context) {
|
||||
const val KEY_PAGES_PRELOAD = "pages_preload"
|
||||
const val KEY_SUGGESTIONS = "suggestions"
|
||||
const val KEY_SUGGESTIONS_EXCLUDE_NSFW = "suggestions_exclude_nsfw"
|
||||
const val KEY_SUGGESTIONS_EXCLUDE_TAGS = "suggestions_exclude_tags"
|
||||
const val KEY_SEARCH_SINGLE_SOURCE = "search_single_source"
|
||||
|
||||
// About
|
||||
const val KEY_APP_UPDATE = "app_update"
|
||||
const val KEY_APP_UPDATE_AUTO = "app_update_auto"
|
||||
const val KEY_APP_TRANSLATION = "about_app_translation"
|
||||
const val KEY_APP_GRATITUDES = "about_gratitudes"
|
||||
const val KEY_FEEDBACK_4PDA = "about_feedback_4pda"
|
||||
const val KEY_FEEDBACK_DISCORD = "about_feedback_discord"
|
||||
const val KEY_FEEDBACK_GITHUB = "about_feedback_github"
|
||||
const val KEY_SUPPORT_DEVELOPER = "about_support_developer"
|
||||
|
||||
private const val NETWORK_NEVER = 0
|
||||
private const val NETWORK_ALWAYS = 1
|
||||
|
||||
@@ -24,11 +24,11 @@ import org.koitharu.kotatsu.local.domain.LocalMangaRepository
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||
import org.koitharu.kotatsu.parsers.util.toTitleCase
|
||||
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||
import org.koitharu.kotatsu.utils.SingleLiveEvent
|
||||
import org.koitharu.kotatsu.utils.ext.iterator
|
||||
import org.koitharu.kotatsu.utils.ext.mapToSet
|
||||
import java.io.IOException
|
||||
|
||||
class DetailsViewModel(
|
||||
|
||||
@@ -6,7 +6,7 @@ import android.view.ViewGroup
|
||||
import android.widget.BaseAdapter
|
||||
import android.widget.TextView
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.utils.ext.replaceWith
|
||||
import org.koitharu.kotatsu.parsers.util.replaceWith
|
||||
|
||||
class BranchesAdapter : BaseAdapter() {
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import kotlinx.coroutines.flow.onEach
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.databinding.ItemDownloadBinding
|
||||
import org.koitharu.kotatsu.download.domain.DownloadState
|
||||
import org.koitharu.kotatsu.parsers.util.format
|
||||
import org.koitharu.kotatsu.utils.ext.*
|
||||
import org.koitharu.kotatsu.utils.progress.ProgressJob
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ import org.koitharu.kotatsu.details.ui.DetailsActivity
|
||||
import org.koitharu.kotatsu.download.domain.DownloadState
|
||||
import org.koitharu.kotatsu.download.ui.DownloadsActivity
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.util.format
|
||||
import org.koitharu.kotatsu.utils.PendingIntentCompat
|
||||
import org.koitharu.kotatsu.utils.ext.format
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ import org.koitharu.kotatsu.utils.ext.throttle
|
||||
import org.koitharu.kotatsu.utils.ext.toArraySet
|
||||
import org.koitharu.kotatsu.utils.progress.ProgressJob
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.collections.set
|
||||
|
||||
class DownloadService : BaseService() {
|
||||
|
||||
@@ -71,7 +70,6 @@ class DownloadService : BaseService() {
|
||||
return if (manga != null) {
|
||||
jobs[startId] = downloadManga(startId, manga, chapters)
|
||||
jobCount.value = jobs.size
|
||||
Toast.makeText(this, R.string.manga_downloading_, Toast.LENGTH_SHORT).show()
|
||||
START_REDELIVER_INTENT
|
||||
} else {
|
||||
stopSelf(startId)
|
||||
@@ -185,6 +183,7 @@ class DownloadService : BaseService() {
|
||||
intent.putExtra(EXTRA_CHAPTERS_IDS, chaptersIds.toLongArray())
|
||||
}
|
||||
ContextCompat.startForegroundService(context, intent)
|
||||
Toast.makeText(context, R.string.manga_downloading_, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,9 +24,10 @@ class ForegroundNotificationSwitcher(
|
||||
@Synchronized
|
||||
fun notify(startId: Int, notification: Notification) {
|
||||
if (notifications.isEmpty()) {
|
||||
handler.postDelayed(StartForegroundRunnable(startId, notification), DEFAULT_DELAY)
|
||||
StartForegroundRunnable(startId, notification)
|
||||
} else {
|
||||
notificationManager.notify(startId, notification)
|
||||
}
|
||||
notificationManager.notify(startId, notification)
|
||||
notifications[startId] = notification
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity
|
||||
import org.koitharu.kotatsu.favourites.data.FavouriteEntity
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||
import org.koitharu.kotatsu.utils.ext.mapItems
|
||||
import org.koitharu.kotatsu.utils.ext.mapToSet
|
||||
|
||||
class FavouritesRepository(private val db: MangaDatabase) {
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import kotlinx.coroutines.flow.Flow
|
||||
import org.koitharu.kotatsu.core.db.entity.MangaEntity
|
||||
import org.koitharu.kotatsu.core.db.entity.TagEntity
|
||||
|
||||
|
||||
@Dao
|
||||
abstract class HistoryDao {
|
||||
|
||||
@@ -23,8 +22,15 @@ abstract class HistoryDao {
|
||||
@Query("SELECT * FROM manga WHERE manga_id IN (SELECT manga_id FROM history)")
|
||||
abstract suspend fun findAllManga(): List<MangaEntity>
|
||||
|
||||
@Query("SELECT * FROM tags WHERE tag_id IN (SELECT tag_id FROM manga_tags WHERE manga_id IN (SELECT manga_id FROM history))")
|
||||
abstract suspend fun findAllTags(): List<TagEntity>
|
||||
@Query(
|
||||
"""SELECT tags.* FROM tags
|
||||
LEFT JOIN manga_tags ON tags.tag_id = manga_tags.tag_id
|
||||
INNER JOIN history ON history.manga_id = manga_tags.manga_id
|
||||
GROUP BY manga_tags.tag_id
|
||||
ORDER BY COUNT(manga_tags.manga_id) DESC
|
||||
LIMIT :limit"""
|
||||
)
|
||||
abstract suspend fun findPopularTags(limit: Int): List<TagEntity>
|
||||
|
||||
@Query("SELECT * FROM history WHERE manga_id = :id")
|
||||
abstract suspend fun find(id: Long): HistoryEntity?
|
||||
@@ -60,5 +66,4 @@ abstract class HistoryDao {
|
||||
true
|
||||
} else false
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,9 +11,9 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.history.data.HistoryEntity
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||
import org.koitharu.kotatsu.utils.ext.mapItems
|
||||
import org.koitharu.kotatsu.utils.ext.mapToSet
|
||||
|
||||
class HistoryRepository(
|
||||
private val db: MangaDatabase,
|
||||
@@ -99,7 +99,7 @@ class HistoryRepository(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getAllTags(): Set<MangaTag> {
|
||||
return db.historyDao.findAllTags().mapToSet { x -> x.toMangaTag() }
|
||||
suspend fun getPopularTags(limit: Int): List<MangaTag> {
|
||||
return db.historyDao.findPopularTags(limit).map { x -> x.toMangaTag() }
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,7 @@ import org.koitharu.kotatsu.reader.ui.pager.standard.PagerReaderFragment
|
||||
import org.koitharu.kotatsu.reader.ui.pager.webtoon.WebtoonReaderFragment
|
||||
import org.koitharu.kotatsu.reader.ui.thumbnails.OnPageSelectListener
|
||||
import org.koitharu.kotatsu.reader.ui.thumbnails.PagesThumbnailsSheet
|
||||
import org.koitharu.kotatsu.settings.SettingsActivity
|
||||
import org.koitharu.kotatsu.utils.GridTouchHelper
|
||||
import org.koitharu.kotatsu.utils.ScreenOrientationHelper
|
||||
import org.koitharu.kotatsu.utils.ShareHelper
|
||||
@@ -158,7 +159,7 @@ class ReaderActivity :
|
||||
)
|
||||
}
|
||||
R.id.action_settings -> {
|
||||
startActivity(SimpleSettingsActivity.newReaderSettingsIntent(this))
|
||||
startActivity(SettingsActivity.newReaderSettingsIntent(this))
|
||||
}
|
||||
R.id.action_chapters -> {
|
||||
ChaptersBottomSheet.show(
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
package org.koitharu.kotatsu.reader.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.commit
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.databinding.ActivitySettingsSimpleBinding
|
||||
import org.koitharu.kotatsu.main.ui.AppBarOwner
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.settings.*
|
||||
|
||||
class SimpleSettingsActivity : BaseActivity<ActivitySettingsSimpleBinding>(), AppBarOwner {
|
||||
|
||||
override val appBar: AppBarLayout
|
||||
get() = binding.appbar
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(ActivitySettingsSimpleBinding.inflate(layoutInflater))
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
supportFragmentManager.commit {
|
||||
replace(
|
||||
R.id.container,
|
||||
when (intent?.action) {
|
||||
Intent.ACTION_MANAGE_NETWORK_USAGE -> NetworkSettingsFragment()
|
||||
ACTION_READER -> ReaderSettingsFragment()
|
||||
ACTION_SUGGESTIONS -> SuggestionsSettingsFragment()
|
||||
ACTION_SOURCE -> SourceSettingsFragment.newInstance(
|
||||
intent.getSerializableExtra(EXTRA_SOURCE) as? MangaSource ?: MangaSource.LOCAL
|
||||
)
|
||||
else -> MainSettingsFragment()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onWindowInsetsChanged(insets: Insets) {
|
||||
with(binding.toolbar) {
|
||||
updatePadding(
|
||||
left = insets.left,
|
||||
right = insets.right
|
||||
)
|
||||
updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
topMargin = insets.top
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val ACTION_READER =
|
||||
"${BuildConfig.APPLICATION_ID}.action.MANAGE_READER_SETTINGS"
|
||||
private const val ACTION_SUGGESTIONS =
|
||||
"${BuildConfig.APPLICATION_ID}.action.MANAGE_SUGGESTIONS"
|
||||
private const val ACTION_SOURCE =
|
||||
"${BuildConfig.APPLICATION_ID}.action.MANAGE_SOURCE_SETTINGS"
|
||||
private const val EXTRA_SOURCE = "source"
|
||||
|
||||
fun newReaderSettingsIntent(context: Context) =
|
||||
Intent(context, SimpleSettingsActivity::class.java)
|
||||
.setAction(ACTION_READER)
|
||||
|
||||
fun newSuggestionsSettingsIntent(context: Context) =
|
||||
Intent(context, SimpleSettingsActivity::class.java)
|
||||
.setAction(ACTION_SUGGESTIONS)
|
||||
|
||||
fun newSourceSettingsIntent(context: Context, source: MangaSource) =
|
||||
Intent(context, SimpleSettingsActivity::class.java)
|
||||
.setAction(ACTION_SOURCE)
|
||||
.putExtra(EXTRA_SOURCE, source)
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.list.ui.MangaListFragment
|
||||
import org.koitharu.kotatsu.list.ui.filter.FilterBottomSheet
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.reader.ui.SimpleSettingsActivity
|
||||
import org.koitharu.kotatsu.settings.SettingsActivity
|
||||
import org.koitharu.kotatsu.utils.ext.serializableArgument
|
||||
import org.koitharu.kotatsu.utils.ext.withArgs
|
||||
|
||||
@@ -35,10 +35,7 @@ class RemoteListFragment : MangaListFragment() {
|
||||
return when (item.itemId) {
|
||||
R.id.action_source_settings -> {
|
||||
startActivity(
|
||||
SimpleSettingsActivity.newSourceSettingsIntent(
|
||||
context ?: return false,
|
||||
source,
|
||||
)
|
||||
SettingsActivity.newSourceSettingsIntent(context ?: return false, source)
|
||||
)
|
||||
true
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ class MangaSearchRepository(
|
||||
return when {
|
||||
query.isNotEmpty() && source != null -> db.tagsDao.findTags(source.name, "%$query%", limit)
|
||||
query.isNotEmpty() -> db.tagsDao.findTags("%$query%", limit)
|
||||
source != null -> db.tagsDao.findTags(source.name, limit)
|
||||
source != null -> db.tagsDao.findPopularTags(source.name, limit)
|
||||
else -> db.tagsDao.findPopularTags(limit)
|
||||
}.map {
|
||||
it.toMangaTag()
|
||||
|
||||
@@ -4,7 +4,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.koitharu.kotatsu.base.ui.widgets.ChipsView
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.utils.ext.areItemsEquals
|
||||
import org.koitharu.kotatsu.parsers.util.areItemsEquals
|
||||
|
||||
sealed interface SearchSuggestionItem {
|
||||
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package org.koitharu.kotatsu.settings
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.TwoStatePreference
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.ListMode
|
||||
import org.koitharu.kotatsu.settings.protect.ProtectSetupActivity
|
||||
import org.koitharu.kotatsu.settings.utils.SliderPreference
|
||||
import org.koitharu.kotatsu.utils.ext.names
|
||||
import org.koitharu.kotatsu.utils.ext.setDefaultValueCompat
|
||||
import java.util.*
|
||||
|
||||
class AppearanceSettingsFragment :
|
||||
BasePreferenceFragment(R.string.appearance),
|
||||
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
addPreferencesFromResource(R.xml.pref_appearance)
|
||||
findPreference<SliderPreference>(AppSettings.KEY_GRID_SIZE)?.run {
|
||||
summary = "%d%%".format(value)
|
||||
setOnPreferenceChangeListener { preference, newValue ->
|
||||
preference.summary = "%d%%".format(newValue)
|
||||
true
|
||||
}
|
||||
}
|
||||
preferenceScreen?.findPreference<ListPreference>(AppSettings.KEY_LIST_MODE)?.run {
|
||||
entryValues = ListMode.values().names()
|
||||
setDefaultValueCompat(ListMode.GRID.name)
|
||||
}
|
||||
findPreference<Preference>(AppSettings.KEY_DYNAMIC_THEME)?.isVisible = AppSettings.isDynamicColorAvailable
|
||||
findPreference<ListPreference>(AppSettings.KEY_DATE_FORMAT)?.run {
|
||||
entryValues = resources.getStringArray(R.array.date_formats)
|
||||
val now = Date().time
|
||||
entries = entryValues.map { value ->
|
||||
val formattedDate = settings.getDateFormat(value.toString()).format(now)
|
||||
if (value == "") {
|
||||
"${context.getString(R.string.system_default)} ($formattedDate)"
|
||||
} else {
|
||||
formattedDate
|
||||
}
|
||||
}.toTypedArray()
|
||||
setDefaultValueCompat("")
|
||||
summary = "%s"
|
||||
}
|
||||
findPreference<TwoStatePreference>(AppSettings.KEY_PROTECT_APP)
|
||||
?.isChecked = !settings.appPassword.isNullOrEmpty()
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
settings.subscribe(this)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
settings.unsubscribe(this)
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(prefs: SharedPreferences?, key: String?) {
|
||||
when (key) {
|
||||
AppSettings.KEY_THEME -> {
|
||||
AppCompatDelegate.setDefaultNightMode(settings.theme)
|
||||
}
|
||||
AppSettings.KEY_DYNAMIC_THEME -> {
|
||||
findPreference<Preference>(key)?.setSummary(R.string.restart_required)
|
||||
}
|
||||
AppSettings.KEY_THEME_AMOLED -> {
|
||||
findPreference<Preference>(key)?.setSummary(R.string.restart_required)
|
||||
}
|
||||
AppSettings.KEY_APP_PASSWORD -> {
|
||||
findPreference<TwoStatePreference>(AppSettings.KEY_PROTECT_APP)
|
||||
?.isChecked = !settings.appPassword.isNullOrEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
||||
return when (preference.key) {
|
||||
AppSettings.KEY_PROTECT_APP -> {
|
||||
val pref = (preference as? TwoStatePreference ?: return false)
|
||||
if (pref.isChecked) {
|
||||
pref.isChecked = false
|
||||
startActivity(Intent(preference.context, ProtectSetupActivity::class.java))
|
||||
} else {
|
||||
settings.appPassword = null
|
||||
}
|
||||
true
|
||||
}
|
||||
else -> super.onPreferenceTreeClick(preference)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package org.koitharu.kotatsu.settings
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.preference.Preference
|
||||
import java.io.File
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
|
||||
import org.koitharu.kotatsu.base.ui.dialog.StorageSelectDialog
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.local.data.LocalStorageManager
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.utils.ext.getStorageName
|
||||
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
|
||||
|
||||
class ContentSettingsFragment :
|
||||
BasePreferenceFragment(R.string.content),
|
||||
SharedPreferences.OnSharedPreferenceChangeListener,
|
||||
StorageSelectDialog.OnStorageSelectListener {
|
||||
|
||||
private val storageManager by inject<LocalStorageManager>()
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
addPreferencesFromResource(R.xml.pref_content)
|
||||
|
||||
findPreference<Preference>(AppSettings.KEY_SUGGESTIONS)?.setSummary(
|
||||
if (settings.isSuggestionsEnabled) R.string.enabled else R.string.disabled
|
||||
)
|
||||
bindRemoteSourcesSummary()
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
findPreference<Preference>(AppSettings.KEY_LOCAL_STORAGE)?.bindStorageName()
|
||||
settings.subscribe(this)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
settings.unsubscribe(this)
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(prefs: SharedPreferences?, key: String?) {
|
||||
when (key) {
|
||||
AppSettings.KEY_LOCAL_STORAGE -> {
|
||||
findPreference<Preference>(key)?.bindStorageName()
|
||||
}
|
||||
AppSettings.KEY_SUGGESTIONS -> {
|
||||
findPreference<Preference>(AppSettings.KEY_SUGGESTIONS)?.setSummary(
|
||||
if (settings.isSuggestionsEnabled) R.string.enabled else R.string.disabled
|
||||
)
|
||||
}
|
||||
AppSettings.KEY_SOURCES_HIDDEN -> {
|
||||
bindRemoteSourcesSummary()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
||||
return when (preference.key) {
|
||||
AppSettings.KEY_LOCAL_STORAGE -> {
|
||||
val ctx = context ?: return false
|
||||
StorageSelectDialog.Builder(ctx, storageManager, this)
|
||||
.setTitle(preference.title ?: "")
|
||||
.setNegativeButton(android.R.string.cancel)
|
||||
.create()
|
||||
.show()
|
||||
true
|
||||
}
|
||||
else -> super.onPreferenceTreeClick(preference)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStorageSelected(file: File) {
|
||||
settings.mangaStorageDir = file
|
||||
}
|
||||
|
||||
private fun Preference.bindStorageName() {
|
||||
viewLifecycleScope.launch {
|
||||
val storage = storageManager.getDefaultWriteableDir()
|
||||
summary = storage?.getStorageName(context) ?: getString(R.string.not_available)
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindRemoteSourcesSummary() {
|
||||
findPreference<Preference>(AppSettings.KEY_REMOTE_SOURCES)?.run {
|
||||
val total = MangaSource.values().size - 1
|
||||
summary = getString(
|
||||
R.string.enabled_d_of_d, total - settings.hiddenSources.size, total
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
package org.koitharu.kotatsu.settings
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import androidx.preference.TwoStatePreference
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
|
||||
import org.koitharu.kotatsu.base.ui.dialog.StorageSelectDialog
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.ListMode
|
||||
import org.koitharu.kotatsu.local.data.LocalStorageManager
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.settings.protect.ProtectSetupActivity
|
||||
import org.koitharu.kotatsu.settings.utils.SliderPreference
|
||||
import org.koitharu.kotatsu.utils.ext.getStorageName
|
||||
import org.koitharu.kotatsu.utils.ext.names
|
||||
import org.koitharu.kotatsu.utils.ext.setDefaultValueCompat
|
||||
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
|
||||
class MainSettingsFragment : BasePreferenceFragment(R.string.settings),
|
||||
SharedPreferences.OnSharedPreferenceChangeListener,
|
||||
StorageSelectDialog.OnStorageSelectListener {
|
||||
|
||||
private val storageManager by inject<LocalStorageManager>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
addPreferencesFromResource(R.xml.pref_main)
|
||||
findPreference<SliderPreference>(AppSettings.KEY_GRID_SIZE)?.run {
|
||||
summary = "%d%%".format(value)
|
||||
setOnPreferenceChangeListener { preference, newValue ->
|
||||
preference.summary = "%d%%".format(newValue)
|
||||
true
|
||||
}
|
||||
}
|
||||
preferenceScreen?.findPreference<ListPreference>(AppSettings.KEY_LIST_MODE)?.run {
|
||||
entryValues = ListMode.values().names()
|
||||
setDefaultValueCompat(ListMode.GRID.name)
|
||||
}
|
||||
findPreference<Preference>(AppSettings.KEY_DYNAMIC_THEME)?.isVisible =
|
||||
AppSettings.isDynamicColorAvailable
|
||||
findPreference<ListPreference>(AppSettings.KEY_DATE_FORMAT)?.run {
|
||||
entryValues = resources.getStringArray(R.array.date_formats)
|
||||
val now = Date().time
|
||||
entries = entryValues.map { value ->
|
||||
val formattedDate = settings.getDateFormat(value.toString()).format(now)
|
||||
if (value == "") {
|
||||
"${context.getString(R.string.system_default)} ($formattedDate)"
|
||||
} else {
|
||||
formattedDate
|
||||
}
|
||||
}.toTypedArray()
|
||||
setDefaultValueCompat("")
|
||||
summary = "%s"
|
||||
}
|
||||
findPreference<Preference>(AppSettings.KEY_SUGGESTIONS)?.setSummary(
|
||||
if (settings.isSuggestionsEnabled) R.string.enabled else R.string.disabled
|
||||
)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
findPreference<Preference>(AppSettings.KEY_LOCAL_STORAGE)?.bindStorageName()
|
||||
findPreference<TwoStatePreference>(AppSettings.KEY_PROTECT_APP)?.isChecked =
|
||||
!settings.appPassword.isNullOrEmpty()
|
||||
settings.subscribe(this)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater)
|
||||
inflater.inflate(R.menu.opt_settings, menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.action_leaks -> {
|
||||
val intent = Intent()
|
||||
intent.component = ComponentName(requireContext(), "leakcanary.internal.activity.LeakActivity")
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
startActivity(intent)
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
settings.unsubscribe(this)
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
|
||||
when (key) {
|
||||
AppSettings.KEY_THEME -> {
|
||||
AppCompatDelegate.setDefaultNightMode(settings.theme)
|
||||
}
|
||||
AppSettings.KEY_DYNAMIC_THEME -> {
|
||||
findPreference<Preference>(key)?.setSummary(R.string.restart_required)
|
||||
}
|
||||
AppSettings.KEY_THEME_AMOLED -> {
|
||||
findPreference<Preference>(key)?.setSummary(R.string.restart_required)
|
||||
}
|
||||
AppSettings.KEY_LOCAL_STORAGE -> {
|
||||
findPreference<Preference>(key)?.bindStorageName()
|
||||
}
|
||||
AppSettings.KEY_APP_PASSWORD -> {
|
||||
findPreference<TwoStatePreference>(AppSettings.KEY_PROTECT_APP)
|
||||
?.isChecked = !settings.appPassword.isNullOrEmpty()
|
||||
}
|
||||
AppSettings.KEY_SUGGESTIONS -> {
|
||||
findPreference<Preference>(AppSettings.KEY_SUGGESTIONS)?.setSummary(
|
||||
if (settings.isSuggestionsEnabled) R.string.enabled else R.string.disabled
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
findPreference<PreferenceScreen>(AppSettings.KEY_REMOTE_SOURCES)?.run {
|
||||
val total = MangaSource.values().size - 1
|
||||
summary = getString(
|
||||
R.string.enabled_d_of_d, total - settings.hiddenSources.size, total
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
||||
return when (preference.key) {
|
||||
AppSettings.KEY_LOCAL_STORAGE -> {
|
||||
val ctx = context ?: return false
|
||||
StorageSelectDialog.Builder(ctx, storageManager, this)
|
||||
.setTitle(preference.title ?: "")
|
||||
.setNegativeButton(android.R.string.cancel)
|
||||
.create()
|
||||
.show()
|
||||
true
|
||||
}
|
||||
AppSettings.KEY_PROTECT_APP -> {
|
||||
val pref = (preference as? TwoStatePreference ?: return false)
|
||||
if (pref.isChecked) {
|
||||
pref.isChecked = false
|
||||
startActivity(Intent(preference.context, ProtectSetupActivity::class.java))
|
||||
} else {
|
||||
settings.appPassword = null
|
||||
}
|
||||
true
|
||||
}
|
||||
else -> super.onPreferenceTreeClick(preference)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStorageSelected(file: File) {
|
||||
settings.mangaStorageDir = file
|
||||
}
|
||||
|
||||
private fun Preference.bindStorageName() {
|
||||
viewLifecycleScope.launch {
|
||||
val storage = storageManager.getDefaultWriteableDir()
|
||||
summary = storage?.getStorageName(context) ?: getString(R.string.not_available)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,9 @@ import android.os.Bundle
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
|
||||
|
||||
class NetworkSettingsFragment : BasePreferenceFragment(R.string.settings) {
|
||||
class RootSettingsFragment : BasePreferenceFragment(R.string.settings) {
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
//TODO https://developer.android.com/training/basics/network-ops/managing
|
||||
addPreferencesFromResource(R.xml.pref_root)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
package org.koitharu.kotatsu.settings
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.FragmentTransaction
|
||||
@@ -11,6 +15,7 @@ import androidx.fragment.app.commit
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner
|
||||
@@ -34,9 +39,7 @@ class SettingsActivity :
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
if (supportFragmentManager.findFragmentById(R.id.container) == null) {
|
||||
supportFragmentManager.commit {
|
||||
replace(R.id.container, MainSettingsFragment())
|
||||
}
|
||||
openDefaultFragment()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +58,22 @@ class SettingsActivity :
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.opt_settings, menu)
|
||||
return super.onCreateOptionsMenu(menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
|
||||
R.id.action_leaks -> {
|
||||
val intent = Intent()
|
||||
intent.component = ComponentName(this, "leakcanary.internal.activity.LeakActivity")
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
startActivity(intent)
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onBackStackChanged() {
|
||||
val fragment = supportFragmentManager.findFragmentById(R.id.container) as? RecyclerViewOwner ?: return
|
||||
val recyclerView = fragment.recyclerView
|
||||
@@ -70,32 +89,66 @@ class SettingsActivity :
|
||||
val fm = supportFragmentManager
|
||||
val fragment = fm.fragmentFactory.instantiate(classLoader, pref.fragment ?: return false)
|
||||
fragment.arguments = pref.extras
|
||||
fragment.setTargetFragment(caller, 0)
|
||||
// fragment.setTargetFragment(caller, 0)
|
||||
openFragment(fragment)
|
||||
return true
|
||||
}
|
||||
|
||||
fun openMangaSourceSettings(mangaSource: MangaSource) {
|
||||
openFragment(SourceSettingsFragment.newInstance(mangaSource))
|
||||
override fun onWindowInsetsChanged(insets: Insets) {
|
||||
binding.appbar.updatePadding(
|
||||
left = insets.left,
|
||||
right = insets.right,
|
||||
)
|
||||
binding.container.updatePadding(
|
||||
left = insets.left,
|
||||
right = insets.right,
|
||||
)
|
||||
}
|
||||
|
||||
fun openNotificationSettingsLegacy() {
|
||||
openFragment(NotificationSettingsLegacyFragment())
|
||||
}
|
||||
|
||||
private fun openFragment(fragment: Fragment) {
|
||||
fun openFragment(fragment: Fragment) {
|
||||
supportFragmentManager.commit {
|
||||
setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
|
||||
replace(R.id.container, fragment)
|
||||
setReorderingAllowed(true)
|
||||
replace(R.id.container, fragment)
|
||||
setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
|
||||
addToBackStack(null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onWindowInsetsChanged(insets: Insets) = Unit
|
||||
private fun openDefaultFragment() {
|
||||
val fragment = when (intent?.action) {
|
||||
ACTION_READER -> ReaderSettingsFragment()
|
||||
ACTION_SUGGESTIONS -> SuggestionsSettingsFragment()
|
||||
ACTION_SOURCE -> SourceSettingsFragment.newInstance(
|
||||
intent.getSerializableExtra(EXTRA_SOURCE) as? MangaSource ?: MangaSource.LOCAL
|
||||
)
|
||||
else -> SettingsHeadersFragment()
|
||||
}
|
||||
supportFragmentManager.commit {
|
||||
setReorderingAllowed(true)
|
||||
replace(R.id.container, fragment)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val ACTION_READER = "${BuildConfig.APPLICATION_ID}.action.MANAGE_READER_SETTINGS"
|
||||
private const val ACTION_SUGGESTIONS = "${BuildConfig.APPLICATION_ID}.action.MANAGE_SUGGESTIONS"
|
||||
private const val ACTION_SOURCE = "${BuildConfig.APPLICATION_ID}.action.MANAGE_SOURCE_SETTINGS"
|
||||
private const val EXTRA_SOURCE = "source"
|
||||
|
||||
fun newIntent(context: Context) = Intent(context, SettingsActivity::class.java)
|
||||
|
||||
fun newReaderSettingsIntent(context: Context) =
|
||||
Intent(context, SettingsActivity::class.java)
|
||||
.setAction(ACTION_READER)
|
||||
|
||||
fun newSuggestionsSettingsIntent(context: Context) =
|
||||
Intent(context, SettingsActivity::class.java)
|
||||
.setAction(ACTION_SUGGESTIONS)
|
||||
|
||||
fun newSourceSettingsIntent(context: Context, source: MangaSource) =
|
||||
Intent(context, SettingsActivity::class.java)
|
||||
.setAction(ACTION_SOURCE)
|
||||
.putExtra(EXTRA_SOURCE, source)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package org.koitharu.kotatsu.settings
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentTransaction
|
||||
import androidx.fragment.app.commit
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.PreferenceHeaderFragmentCompat
|
||||
import androidx.slidingpanelayout.widget.SlidingPaneLayout
|
||||
import org.koitharu.kotatsu.R
|
||||
|
||||
class SettingsHeadersFragment : PreferenceHeaderFragmentCompat(), SlidingPaneLayout.PanelSlideListener {
|
||||
|
||||
private var currentTitle: CharSequence? = null
|
||||
|
||||
override fun onCreatePreferenceHeader(): PreferenceFragmentCompat = RootSettingsFragment()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
slidingPaneLayout.addPanelSlideListener(this)
|
||||
}
|
||||
|
||||
override fun onPanelSlide(panel: View, slideOffset: Float) = Unit
|
||||
|
||||
override fun onPanelOpened(panel: View) {
|
||||
activity?.title = currentTitle ?: getString(R.string.settings)
|
||||
}
|
||||
|
||||
override fun onPanelClosed(panel: View) {
|
||||
activity?.setTitle(R.string.settings)
|
||||
}
|
||||
|
||||
fun setTitle(title: CharSequence?) {
|
||||
currentTitle = title
|
||||
if (slidingPaneLayout.isSlideable && slidingPaneLayout.isOpen) {
|
||||
activity?.title = title
|
||||
}
|
||||
}
|
||||
|
||||
fun openFragment(fragment: Fragment) {
|
||||
childFragmentManager.commit {
|
||||
setReorderingAllowed(true)
|
||||
replace(androidx.preference.R.id.preferences_detail, fragment)
|
||||
setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
|
||||
addToBackStack(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ class SourceSettingsFragment : BasePreferenceFragment(0) {
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
activity?.title = source.title
|
||||
setTitle(source.title)
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
|
||||
@@ -4,10 +4,13 @@ import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.get
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.settings.utils.MultiAutoCompleteTextViewPreference
|
||||
import org.koitharu.kotatsu.settings.utils.TagsAutoCompleteProvider
|
||||
import org.koitharu.kotatsu.suggestions.domain.SuggestionRepository
|
||||
import org.koitharu.kotatsu.suggestions.ui.SuggestionsWorker
|
||||
|
||||
@@ -23,6 +26,11 @@ class SuggestionsSettingsFragment : BasePreferenceFragment(R.string.suggestions)
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
addPreferencesFromResource(R.xml.pref_suggestions)
|
||||
|
||||
findPreference<MultiAutoCompleteTextViewPreference>(AppSettings.KEY_SUGGESTIONS_EXCLUDE_TAGS)?.run {
|
||||
autoCompleteProvider = TagsAutoCompleteProvider(get())
|
||||
summaryProvider = MultiAutoCompleteTextViewPreference.SimpleSummaryProvider(summary)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
||||
@@ -31,7 +31,6 @@ class TrackerSettingsFragment : BasePreferenceFragment(R.string.check_for_new_ch
|
||||
append(getString(R.string.read_more))
|
||||
}
|
||||
}
|
||||
warningPreference
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,10 +42,10 @@ class TrackerSettingsFragment : BasePreferenceFragment(R.string.check_for_new_ch
|
||||
.putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName)
|
||||
.putExtra(Settings.EXTRA_CHANNEL_ID, TrackWorker.CHANNEL_ID)
|
||||
startActivity(intent)
|
||||
true
|
||||
} else {
|
||||
(activity as? SettingsActivity)?.openNotificationSettingsLegacy()
|
||||
super.onPreferenceTreeClick(preference)
|
||||
}
|
||||
true
|
||||
}
|
||||
else -> super.onPreferenceTreeClick(preference)
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package org.koitharu.kotatsu.settings.about
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.core.net.toUri
|
||||
import androidx.preference.Preference
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
|
||||
import org.koitharu.kotatsu.browser.BrowserActivity
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.settings.AppUpdateChecker
|
||||
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
|
||||
@@ -16,20 +16,16 @@ class AboutSettingsFragment : BasePreferenceFragment(R.string.about) {
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
addPreferencesFromResource(R.xml.pref_about)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
val isUpdateSupported = AppUpdateChecker.isUpdateSupported(requireContext())
|
||||
findPreference<Preference>(AppSettings.KEY_APP_UPDATE_AUTO)?.run {
|
||||
isVisible = AppUpdateChecker.isUpdateSupported(context)
|
||||
isVisible = isUpdateSupported
|
||||
}
|
||||
findPreference<Preference>(AppSettings.KEY_APP_VERSION)?.run {
|
||||
title = getString(R.string.app_version, BuildConfig.VERSION_NAME)
|
||||
isEnabled = AppUpdateChecker.isUpdateSupported(context)
|
||||
isEnabled = isUpdateSupported
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
||||
return when (preference.key) {
|
||||
AppSettings.KEY_APP_VERSION -> {
|
||||
@@ -37,39 +33,19 @@ class AboutSettingsFragment : BasePreferenceFragment(R.string.about) {
|
||||
true
|
||||
}
|
||||
AppSettings.KEY_APP_TRANSLATION -> {
|
||||
startActivity(context?.let { BrowserActivity.newIntent(it,
|
||||
"https://hosted.weblate.org/engage/kotatsu",
|
||||
resources.getString(R.string.about_app_translation)) })
|
||||
openLink(getString(R.string.url_weblate), preference.title)
|
||||
true
|
||||
}
|
||||
AppSettings.KEY_FEEDBACK_4PDA -> {
|
||||
startActivity(context?.let { BrowserActivity.newIntent(it,
|
||||
"https://4pda.to/forum/index.php?showtopic=697669",
|
||||
resources.getString(R.string.about_feedback_4pda)) })
|
||||
openLink(getString(R.string.url_forpda), preference.title)
|
||||
true
|
||||
}
|
||||
AppSettings.KEY_FEEDBACK_DISCORD -> {
|
||||
startActivity(context?.let { BrowserActivity.newIntent(it,
|
||||
"https://discord.gg/NNJ5RgVBC5",
|
||||
"Discord") })
|
||||
openLink(getString(R.string.url_discord), preference.title)
|
||||
true
|
||||
}
|
||||
AppSettings.KEY_FEEDBACK_GITHUB -> {
|
||||
startActivity(context?.let { BrowserActivity.newIntent(it,
|
||||
"https://github.com/nv95/Kotatsu/issues",
|
||||
"GitHub") })
|
||||
true
|
||||
}
|
||||
AppSettings.KEY_SUPPORT_DEVELOPER -> {
|
||||
startActivity(context?.let { BrowserActivity.newIntent(it,
|
||||
"https://yoomoney.ru/to/410012543938752",
|
||||
resources.getString(R.string.about_support_developer)) })
|
||||
true
|
||||
}
|
||||
AppSettings.KEY_APP_GRATITUDES -> {
|
||||
startActivity(context?.let { BrowserActivity.newIntent(it,
|
||||
"https://github.com/nv95/Kotatsu/graphs/contributors",
|
||||
resources.getString(R.string.about_gratitudes)) })
|
||||
openLink(getString(R.string.url_github_issues), preference.title)
|
||||
true
|
||||
}
|
||||
else -> super.onPreferenceTreeClick(preference)
|
||||
@@ -95,4 +71,16 @@ class AboutSettingsFragment : BasePreferenceFragment(R.string.about) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun openLink(url: String, title: CharSequence?) {
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.data = url.toUri()
|
||||
startActivity(
|
||||
if (title != null) {
|
||||
Intent.createChooser(intent, title)
|
||||
} else {
|
||||
intent
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package org.koitharu.kotatsu.settings.about
|
||||
|
||||
import android.os.Bundle
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.core.text.parseAsHtml
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseFragment
|
||||
import org.koitharu.kotatsu.databinding.FragmentCopyrightBinding
|
||||
|
||||
class LicenseFragment : BaseFragment<FragmentCopyrightBinding>() {
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding.textView.apply {
|
||||
text =
|
||||
SpannableStringBuilder(resources.openRawResource(R.raw.copyright).bufferedReader()
|
||||
.readText()
|
||||
.parseAsHtml(HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_LIST))
|
||||
movementMethod = LinkMovementMethod.getInstance()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onInflateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?
|
||||
) = FragmentCopyrightBinding.inflate(inflater, container, false)
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
activity?.setTitle(R.string.about_license)
|
||||
}
|
||||
|
||||
override fun onWindowInsetsChanged(insets: Insets) = Unit
|
||||
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import androidx.lifecycle.MutableLiveData
|
||||
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||
import org.koitharu.kotatsu.parsers.util.toTitleCase
|
||||
import org.koitharu.kotatsu.settings.onboard.model.SourceLocale
|
||||
import org.koitharu.kotatsu.utils.ext.map
|
||||
|
||||
@@ -15,6 +15,8 @@ import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner
|
||||
import org.koitharu.kotatsu.databinding.FragmentSettingsSourcesBinding
|
||||
import org.koitharu.kotatsu.main.ui.AppBarOwner
|
||||
import org.koitharu.kotatsu.settings.SettingsActivity
|
||||
import org.koitharu.kotatsu.settings.SettingsHeadersFragment
|
||||
import org.koitharu.kotatsu.settings.SourceSettingsFragment
|
||||
import org.koitharu.kotatsu.settings.sources.adapter.SourceConfigAdapter
|
||||
import org.koitharu.kotatsu.settings.sources.adapter.SourceConfigListener
|
||||
import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem
|
||||
@@ -87,7 +89,9 @@ class SourcesSettingsFragment : BaseFragment<FragmentSettingsSourcesBinding>(),
|
||||
}
|
||||
|
||||
override fun onItemSettingsClick(item: SourceConfigItem.SourceItem) {
|
||||
(activity as? SettingsActivity)?.openMangaSourceSettings(item.source)
|
||||
val fragment = SourceSettingsFragment.newInstance(item.source)
|
||||
(parentFragment as? SettingsHeadersFragment)?.openFragment(fragment)
|
||||
?: (activity as? SettingsActivity)?.openFragment(fragment)
|
||||
}
|
||||
|
||||
override fun onItemEnabledChanged(item: SourceConfigItem.SourceItem, isEnabled: Boolean) {
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
package org.koitharu.kotatsu.settings.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.EditText
|
||||
import android.widget.Filter
|
||||
import android.widget.MultiAutoCompleteTextView
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.annotation.StyleRes
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.preference.EditTextPreference
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.parsers.util.replaceWith
|
||||
|
||||
class MultiAutoCompleteTextViewPreference @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
@AttrRes defStyleAttr: Int = R.attr.multiAutoCompleteTextViewPreferenceStyle,
|
||||
@StyleRes defStyleRes: Int = R.style.Preference_MultiAutoCompleteTextView,
|
||||
) : EditTextPreference(context, attrs, defStyleAttr, defStyleRes) {
|
||||
|
||||
private val autoCompleteBindListener = AutoCompleteBindListener()
|
||||
|
||||
var autoCompleteProvider: AutoCompleteProvider? = null
|
||||
|
||||
init {
|
||||
super.setOnBindEditTextListener(autoCompleteBindListener)
|
||||
}
|
||||
|
||||
override fun setOnBindEditTextListener(onBindEditTextListener: OnBindEditTextListener?) {
|
||||
autoCompleteBindListener.delegate = onBindEditTextListener
|
||||
}
|
||||
|
||||
private inner class AutoCompleteBindListener : OnBindEditTextListener {
|
||||
|
||||
var delegate: OnBindEditTextListener? = null
|
||||
|
||||
override fun onBindEditText(editText: EditText) {
|
||||
delegate?.onBindEditText(editText)
|
||||
if (editText !is MultiAutoCompleteTextView) {
|
||||
return
|
||||
}
|
||||
editText.setTokenizer(MultiAutoCompleteTextView.CommaTokenizer())
|
||||
editText.setAdapter(
|
||||
autoCompleteProvider?.let {
|
||||
CompletionAdapter(editText.context, it, ArrayList())
|
||||
}
|
||||
)
|
||||
editText.threshold = 1
|
||||
}
|
||||
}
|
||||
|
||||
interface AutoCompleteProvider {
|
||||
|
||||
suspend fun getSuggestions(query: String): List<String>
|
||||
}
|
||||
|
||||
class SimpleSummaryProvider(
|
||||
private val emptySummary: CharSequence?,
|
||||
) : SummaryProvider<MultiAutoCompleteTextViewPreference> {
|
||||
|
||||
override fun provideSummary(preference: MultiAutoCompleteTextViewPreference): CharSequence? {
|
||||
return if (preference.text.isNullOrEmpty()) {
|
||||
emptySummary
|
||||
} else {
|
||||
preference.text?.trimEnd(' ', ',')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CompletionAdapter(
|
||||
context: Context,
|
||||
private val completionProvider: AutoCompleteProvider,
|
||||
private val dataset: MutableList<String>,
|
||||
) : ArrayAdapter<String>(context, android.R.layout.simple_dropdown_item_1line, dataset) {
|
||||
|
||||
override fun getFilter(): Filter {
|
||||
return CompletionFilter(this, completionProvider)
|
||||
}
|
||||
|
||||
fun publishResults(results: List<String>) {
|
||||
dataset.replaceWith(results)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
private class CompletionFilter(
|
||||
private val adapter: CompletionAdapter,
|
||||
private val provider: AutoCompleteProvider,
|
||||
) : Filter() {
|
||||
|
||||
@WorkerThread
|
||||
override fun performFiltering(constraint: CharSequence?): FilterResults {
|
||||
val query = constraint?.toString().orEmpty()
|
||||
val suggestions = runBlocking { provider.getSuggestions(query) }
|
||||
return CompletionResults(suggestions)
|
||||
}
|
||||
|
||||
@MainThread
|
||||
override fun publishResults(constraint: CharSequence?, results: FilterResults) {
|
||||
val completions = (results as CompletionResults).completions
|
||||
adapter.publishResults(completions)
|
||||
}
|
||||
|
||||
private class CompletionResults(
|
||||
val completions: List<String>,
|
||||
) : FilterResults() {
|
||||
|
||||
init {
|
||||
values = completions
|
||||
count = completions.size
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,18 +30,22 @@ class SliderPreference @JvmOverloads constructor(
|
||||
set(value) = setValueInternal(value, notifyChanged = true)
|
||||
|
||||
private val sliderListener = Slider.OnChangeListener { _, value, fromUser ->
|
||||
if (fromUser) {
|
||||
syncValueInternal(value.toInt())
|
||||
}
|
||||
if (fromUser) {
|
||||
syncValueInternal(value.toInt())
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
context.withStyledAttributes(attrs,
|
||||
context.withStyledAttributes(
|
||||
attrs,
|
||||
R.styleable.SliderPreference,
|
||||
defStyleAttr,
|
||||
defStyleRes) {
|
||||
valueFrom = getFloat(R.styleable.SliderPreference_android_valueFrom,
|
||||
valueFrom.toFloat()).toInt()
|
||||
defStyleRes
|
||||
) {
|
||||
valueFrom = getFloat(
|
||||
R.styleable.SliderPreference_android_valueFrom,
|
||||
valueFrom.toFloat()
|
||||
).toInt()
|
||||
valueTo =
|
||||
getFloat(R.styleable.SliderPreference_android_valueTo, valueTo.toFloat()).toInt()
|
||||
stepSize =
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.koitharu.kotatsu.settings.utils
|
||||
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
|
||||
class TagsAutoCompleteProvider(
|
||||
private val db: MangaDatabase,
|
||||
) : MultiAutoCompleteTextViewPreference.AutoCompleteProvider {
|
||||
|
||||
override suspend fun getSuggestions(query: String): List<String> {
|
||||
if (query.isEmpty()) {
|
||||
return emptyList()
|
||||
}
|
||||
val tags = db.tagsDao.findTags(query = "$query%", limit = 6)
|
||||
val set = HashSet<String>()
|
||||
val result = ArrayList<String>(tags.size)
|
||||
for (tag in tags) {
|
||||
if (set.add(tag.title)) {
|
||||
result.add(tag.title)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,9 @@ import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
import org.koitharu.kotatsu.core.db.entity.MangaEntity
|
||||
import org.koitharu.kotatsu.core.db.entity.TagEntity
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||
import org.koitharu.kotatsu.suggestions.data.SuggestionEntity
|
||||
import org.koitharu.kotatsu.utils.ext.mapItems
|
||||
import org.koitharu.kotatsu.utils.ext.mapToSet
|
||||
|
||||
class SuggestionRepository(
|
||||
private val db: MangaDatabase,
|
||||
|
||||
@@ -9,7 +9,7 @@ import com.google.android.material.snackbar.Snackbar
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.list.ui.MangaListFragment
|
||||
import org.koitharu.kotatsu.reader.ui.SimpleSettingsActivity
|
||||
import org.koitharu.kotatsu.settings.SettingsActivity
|
||||
|
||||
class SuggestionsFragment : MangaListFragment() {
|
||||
|
||||
@@ -38,7 +38,7 @@ class SuggestionsFragment : MangaListFragment() {
|
||||
true
|
||||
}
|
||||
R.id.action_settings -> {
|
||||
startActivity(SimpleSettingsActivity.newSuggestionsSettingsIntent(requireContext()))
|
||||
startActivity(SettingsActivity.newSuggestionsSettingsIntent(requireContext()))
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
|
||||
@@ -1,16 +1,29 @@
|
||||
package org.koitharu.kotatsu.suggestions.ui
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.annotation.FloatRange
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.work.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.suggestions.domain.MangaSuggestion
|
||||
import org.koitharu.kotatsu.suggestions.domain.SuggestionRepository
|
||||
import org.koitharu.kotatsu.utils.ext.asArrayList
|
||||
import org.koitharu.kotatsu.utils.ext.trySetForeground
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.math.pow
|
||||
|
||||
@@ -21,11 +34,41 @@ class SuggestionsWorker(appContext: Context, params: WorkerParameters) :
|
||||
private val historyRepository by inject<HistoryRepository>()
|
||||
private val appSettings by inject<AppSettings>()
|
||||
|
||||
override suspend fun doWork(): Result = try {
|
||||
override suspend fun doWork(): Result {
|
||||
val count = doWorkImpl()
|
||||
Result.success(workDataOf(DATA_COUNT to count))
|
||||
} catch (t: Throwable) {
|
||||
Result.failure()
|
||||
val outputData = workDataOf(DATA_COUNT to count)
|
||||
return Result.success(outputData)
|
||||
}
|
||||
|
||||
override suspend fun getForegroundInfo(): ForegroundInfo {
|
||||
val manager = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
val title = applicationContext.getString(R.string.suggestions_updating)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel = NotificationChannel(
|
||||
WORKER_CHANNEL_ID,
|
||||
title,
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
)
|
||||
channel.setShowBadge(false)
|
||||
channel.enableVibration(false)
|
||||
channel.setSound(null, null)
|
||||
channel.enableLights(false)
|
||||
manager.createNotificationChannel(channel)
|
||||
}
|
||||
|
||||
val notification = NotificationCompat.Builder(applicationContext, WORKER_CHANNEL_ID)
|
||||
.setContentTitle(title)
|
||||
.setPriority(NotificationCompat.PRIORITY_MIN)
|
||||
.setDefaults(0)
|
||||
.setColor(ContextCompat.getColor(applicationContext, R.color.blue_primary_dark))
|
||||
.setSilent(true)
|
||||
.setProgress(0, 0, true)
|
||||
.setSmallIcon(android.R.drawable.stat_notify_sync)
|
||||
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_DEFERRED)
|
||||
.setOngoing(true)
|
||||
.build()
|
||||
|
||||
return ForegroundInfo(WORKER_NOTIFICATION_ID, notification)
|
||||
}
|
||||
|
||||
private suspend fun doWorkImpl(): Int {
|
||||
@@ -33,47 +76,75 @@ class SuggestionsWorker(appContext: Context, params: WorkerParameters) :
|
||||
suggestionRepository.clear()
|
||||
return 0
|
||||
}
|
||||
val rawResults = ArrayList<Manga>()
|
||||
val allTags = historyRepository.getAllTags()
|
||||
val blacklistTagRegex = appSettings.getSuggestionsTagsBlacklistRegex()
|
||||
val allTags = historyRepository.getPopularTags(TAGS_LIMIT).filterNot {
|
||||
blacklistTagRegex?.containsMatchIn(it.title) ?: false
|
||||
}
|
||||
if (allTags.isEmpty()) {
|
||||
return 0
|
||||
}
|
||||
if (TAG in tags) { // not expedited
|
||||
trySetForeground()
|
||||
}
|
||||
val tagsBySources = allTags.groupBy { x -> x.source }
|
||||
for ((source, tags) in tagsBySources) {
|
||||
val repo = MangaRepository(source)
|
||||
tags.flatMapTo(rawResults) { tag ->
|
||||
repo.getList(
|
||||
offset = 0,
|
||||
sortOrder = SortOrder.UPDATED,
|
||||
tags = setOf(tag),
|
||||
)
|
||||
}
|
||||
val dispatcher = Dispatchers.Default.limitedParallelism(MAX_PARALLELISM)
|
||||
val rawResults = coroutineScope {
|
||||
tagsBySources.flatMap { (source, tags) ->
|
||||
val repo = MangaRepository(source)
|
||||
tags.map { tag ->
|
||||
async(dispatcher) {
|
||||
repo.getList(
|
||||
offset = 0,
|
||||
sortOrder = SortOrder.UPDATED,
|
||||
tags = setOf(tag),
|
||||
)
|
||||
}
|
||||
}
|
||||
}.awaitAll().flatten().asArrayList()
|
||||
}
|
||||
if (appSettings.isSuggestionsExcludeNsfw) {
|
||||
rawResults.removeAll { it.isNsfw }
|
||||
}
|
||||
if (blacklistTagRegex != null) {
|
||||
rawResults.removeAll {
|
||||
it.tags.any { x -> blacklistTagRegex.containsMatchIn(x.title) }
|
||||
}
|
||||
}
|
||||
if (rawResults.isEmpty()) {
|
||||
return 0
|
||||
}
|
||||
val suggestions = rawResults.distinctBy { manga ->
|
||||
manga.id
|
||||
}.map { manga ->
|
||||
val jointTags = manga.tags intersect allTags
|
||||
MangaSuggestion(
|
||||
manga = manga,
|
||||
relevance = (jointTags.size / manga.tags.size.toDouble()).pow(2.0).toFloat(),
|
||||
relevance = computeRelevance(manga.tags, allTags)
|
||||
)
|
||||
}.sortedBy { it.relevance }.take(LIMIT)
|
||||
suggestionRepository.replace(suggestions)
|
||||
return suggestions.size
|
||||
}
|
||||
|
||||
@FloatRange(from = 0.0, to = 1.0)
|
||||
private fun computeRelevance(mangaTags: Set<MangaTag>, allTags: List<MangaTag>): Float {
|
||||
val maxWeight = (allTags.size + allTags.size + 1 - mangaTags.size) * mangaTags.size / 2.0
|
||||
val weight = mangaTags.sumOf { tag ->
|
||||
val index = allTags.indexOf(tag)
|
||||
if (index < 0) 0 else allTags.size - index
|
||||
}
|
||||
return (weight / maxWeight).pow(2.0).toFloat()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val TAG = "suggestions"
|
||||
private const val TAG_ONESHOT = "suggestions_oneshot"
|
||||
private const val LIMIT = 140
|
||||
private const val TAGS_LIMIT = 20
|
||||
private const val MAX_PARALLELISM = 4
|
||||
private const val DATA_COUNT = "count"
|
||||
private const val WORKER_CHANNEL_ID = "suggestion_worker"
|
||||
private const val WORKER_NOTIFICATION_ID = 36
|
||||
|
||||
fun setup(context: Context) {
|
||||
val constraints = Constraints.Builder()
|
||||
@@ -96,6 +167,7 @@ class SuggestionsWorker(appContext: Context, params: WorkerParameters) :
|
||||
val request = OneTimeWorkRequestBuilder<SuggestionsWorker>()
|
||||
.setConstraints(constraints)
|
||||
.addTag(TAG_ONESHOT)
|
||||
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
|
||||
.build()
|
||||
WorkManager.getInstance(context)
|
||||
.enqueue(request)
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||
import org.koitharu.kotatsu.utils.PendingIntentCompat
|
||||
import org.koitharu.kotatsu.utils.ext.referer
|
||||
import org.koitharu.kotatsu.utils.ext.toBitmapOrNull
|
||||
import org.koitharu.kotatsu.utils.ext.trySetForeground
|
||||
import org.koitharu.kotatsu.utils.progress.Progress
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@@ -53,7 +54,9 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) :
|
||||
if (tracks.isEmpty()) {
|
||||
return Result.success()
|
||||
}
|
||||
setForeground(createForegroundInfo())
|
||||
if (TAG in tags) { // not expedited
|
||||
trySetForeground()
|
||||
}
|
||||
var success = 0
|
||||
val workData = Data.Builder()
|
||||
.putInt(DATA_TOTAL, tracks.size)
|
||||
@@ -201,7 +204,7 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) :
|
||||
}
|
||||
}
|
||||
|
||||
private fun createForegroundInfo(): ForegroundInfo {
|
||||
override suspend fun getForegroundInfo(): ForegroundInfo {
|
||||
val title = applicationContext.getString(R.string.check_for_new_chapters)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel = NotificationChannel(
|
||||
@@ -281,6 +284,7 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) :
|
||||
val request = OneTimeWorkRequestBuilder<TrackWorker>()
|
||||
.setConstraints(constraints)
|
||||
.addTag(TAG_ONESHOT)
|
||||
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
|
||||
.build()
|
||||
WorkManager.getInstance(context)
|
||||
.enqueue(request)
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.net.ConnectivityManager
|
||||
import android.net.Network
|
||||
import android.net.NetworkRequest
|
||||
import android.net.Uri
|
||||
import androidx.work.CoroutineWorker
|
||||
import kotlin.coroutines.resume
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
|
||||
@@ -28,4 +29,9 @@ suspend fun ConnectivityManager.waitForNetwork(): Network {
|
||||
}
|
||||
}
|
||||
|
||||
fun String.toUriOrNull() = if (isEmpty()) null else Uri.parse(this)
|
||||
fun String.toUriOrNull() = if (isEmpty()) null else Uri.parse(this)
|
||||
|
||||
suspend fun CoroutineWorker.trySetForeground(): Boolean = runCatching {
|
||||
val info = getForegroundInfo()
|
||||
setForeground(info)
|
||||
}.isSuccess
|
||||
@@ -3,46 +3,12 @@ package org.koitharu.kotatsu.utils.ext
|
||||
import androidx.collection.ArraySet
|
||||
import java.util.*
|
||||
|
||||
fun <T> MutableCollection<T>.replaceWith(subject: Iterable<T>) {
|
||||
clear()
|
||||
addAll(subject)
|
||||
}
|
||||
|
||||
fun <T> List<T>.medianOrNull(): T? = when {
|
||||
isEmpty() -> null
|
||||
else -> get((size / 2).coerceIn(indices))
|
||||
}
|
||||
|
||||
inline fun <T, R> Collection<T>.mapToSet(transform: (T) -> R): Set<R> {
|
||||
return mapTo(ArraySet(size), transform)
|
||||
}
|
||||
|
||||
fun LongArray.toArraySet(): Set<Long> = createSet(size) { i -> this[i] }
|
||||
|
||||
fun <T : Enum<T>> Array<T>.names() = Array(size) { i ->
|
||||
this[i].name
|
||||
}
|
||||
|
||||
fun <T> Collection<T>.isDistinct(): Boolean {
|
||||
val set = HashSet<T>(size)
|
||||
for (item in this) {
|
||||
if (!set.add(item)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return set.size == size
|
||||
}
|
||||
|
||||
fun <T, K> Collection<T>.isDistinctBy(selector: (T) -> K): Boolean {
|
||||
val set = HashSet<K>(size)
|
||||
for (item in this) {
|
||||
if (!set.add(selector(item))) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return set.size == size
|
||||
}
|
||||
|
||||
fun <T> MutableList<T>.move(sourceIndex: Int, targetIndex: Int) {
|
||||
if (sourceIndex <= targetIndex) {
|
||||
Collections.rotate(subList(sourceIndex, targetIndex + 1), -1)
|
||||
@@ -51,20 +17,6 @@ fun <T> MutableList<T>.move(sourceIndex: Int, targetIndex: Int) {
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <T> List<T>.areItemsEquals(other: List<T>, equals: (T, T) -> Boolean): Boolean {
|
||||
if (size != other.size) {
|
||||
return false
|
||||
}
|
||||
for (i in indices) {
|
||||
val a = this[i]
|
||||
val b = other[i]
|
||||
if (!equals(a, b)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@Suppress("FunctionName")
|
||||
inline fun <T> MutableSet(size: Int, init: (index: Int) -> T): MutableSet<T> {
|
||||
val set = ArraySet<T>(size)
|
||||
@@ -82,4 +34,10 @@ inline fun <T> createList(size: Int, init: (index: Int) -> T): List<T> = when (s
|
||||
0 -> emptyList()
|
||||
1 -> Collections.singletonList(init(0))
|
||||
else -> MutableList(size, init)
|
||||
}
|
||||
|
||||
fun <T> List<T>.asArrayList(): ArrayList<T> = if (this is ArrayList<*>) {
|
||||
this as ArrayList<T>
|
||||
} else {
|
||||
ArrayList(this)
|
||||
}
|
||||
@@ -2,14 +2,15 @@ package org.koitharu.kotatsu.utils.ext
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.util.Log
|
||||
import java.io.FileNotFoundException
|
||||
import java.net.SocketTimeoutException
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
||||
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException
|
||||
import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException
|
||||
import org.koitharu.kotatsu.core.exceptions.WrongPasswordException
|
||||
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
|
||||
import java.io.FileNotFoundException
|
||||
import java.net.SocketTimeoutException
|
||||
import org.koitharu.kotatsu.parsers.util.format
|
||||
|
||||
fun Throwable.getDisplayMessage(resources: Resources) = when (this) {
|
||||
is AuthRequiredException -> resources.getString(R.string.auth_required)
|
||||
|
||||
@@ -1,27 +1,3 @@
|
||||
package org.koitharu.kotatsu.utils.ext
|
||||
|
||||
import java.text.DecimalFormat
|
||||
import java.text.NumberFormat
|
||||
import java.util.*
|
||||
|
||||
fun Number.format(decimals: Int = 0, decPoint: Char = '.', thousandsSep: Char? = ' '): String {
|
||||
val formatter = NumberFormat.getInstance(Locale.US) as DecimalFormat
|
||||
val symbols = formatter.decimalFormatSymbols
|
||||
if (thousandsSep != null) {
|
||||
symbols.groupingSeparator = thousandsSep
|
||||
formatter.isGroupingUsed = true
|
||||
} else {
|
||||
formatter.isGroupingUsed = false
|
||||
}
|
||||
symbols.decimalSeparator = decPoint
|
||||
formatter.decimalFormatSymbols = symbols
|
||||
formatter.minimumFractionDigits = decimals
|
||||
formatter.maximumFractionDigits = decimals
|
||||
return when (this) {
|
||||
is Float,
|
||||
is Double -> formatter.format(this.toDouble())
|
||||
else -> formatter.format(this.toLong())
|
||||
}
|
||||
}
|
||||
|
||||
inline fun Int.ifZero(defaultValue: () -> Int): Int = if (this == 0) defaultValue() else this
|
||||
12
app/src/main/res/drawable/ic_appearance.xml
Normal file
12
app/src/main/res/drawable/ic_appearance.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2C17.5,2 22,6 22,11A6,6 0 0,1 16,17H14.2C13.9,17 13.7,17.2 13.7,17.5C13.7,17.6 13.8,17.7 13.8,17.8C14.2,18.3 14.4,18.9 14.4,19.5C14.5,20.9 13.4,22 12,22M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20C12.3,20 12.5,19.8 12.5,19.5C12.5,19.3 12.4,19.2 12.4,19.1C12,18.6 11.8,18.1 11.8,17.5C11.8,16.1 12.9,15 14.3,15H16A4,4 0 0,0 20,11C20,7.1 16.4,4 12,4M6.5,10C7.3,10 8,10.7 8,11.5C8,12.3 7.3,13 6.5,13C5.7,13 5,12.3 5,11.5C5,10.7 5.7,10 6.5,10M9.5,6C10.3,6 11,6.7 11,7.5C11,8.3 10.3,9 9.5,9C8.7,9 8,8.3 8,7.5C8,6.7 8.7,6 9.5,6M14.5,6C15.3,6 16,6.7 16,7.5C16,8.3 15.3,9 14.5,9C13.7,9 13,8.3 13,7.5C13,6.7 13.7,6 14.5,6M17.5,10C18.3,10 19,10.7 19,11.5C19,12.3 18.3,13 17.5,13C16.7,13 16,12.3 16,11.5C16,10.7 16.7,10 17.5,10Z" />
|
||||
</vector>
|
||||
@@ -1,16 +0,0 @@
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M3.333 11.965a8.632 8.632 0 1 1 17.264 0 8.632 8.632 0 1 1-17.264 0"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="@android:color/white" />
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M6.889 10.777h2.453a2.81 2.87 0 1 1 0 2.393H6.89a5.15 5.263 0 1 0 0-2.392z"
|
||||
android:strokeWidth="1" />
|
||||
</vector>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
@@ -8,12 +8,14 @@
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true"
|
||||
app:elevation="@dimen/design_appbar_elevation"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:liftOnScroll="false">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@id/toolbar"
|
||||
@@ -22,23 +24,9 @@
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
style="?materialCardViewOutlinedStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/appbar"
|
||||
app:layout_constraintWidth_percent="0.6">
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</LinearLayout>
|
||||
@@ -1,35 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.appbar.CollapsingToolbarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/toolbar_height_expanded"
|
||||
app:toolbarId="@id/toolbar"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:layout_collapseMode="pin" />
|
||||
|
||||
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/scrollView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:textIsSelectable="true" />
|
||||
|
||||
</ScrollView>
|
||||
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="48dp"
|
||||
android:layout_marginBottom="48dp"
|
||||
android:overScrollMode="ifContentScrolls">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@android:id/message"
|
||||
style="?android:attr/textAppearanceSmall"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginLeft="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginRight="24dp"
|
||||
android:layout_marginBottom="48dp"
|
||||
android:labelFor="@android:id/edit"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
tools:ignore="LabelFor" />
|
||||
|
||||
<MultiAutoCompleteTextView
|
||||
android:id="@android:id/edit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginLeft="20dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_marginRight="20dp"
|
||||
android:importantForAutofill="no"
|
||||
android:minHeight="48dp" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
@@ -1,24 +0,0 @@
|
||||
<p><a href="https://github.com/nv95/Kotatsu">Kotatsu</a> is a free and open source manga reader for Android.</p>
|
||||
<p>Copyright (C) 2020 by Koitharu</p>
|
||||
<p>This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.</p>
|
||||
<p>This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.</p>
|
||||
<p>You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>.</p>
|
||||
<h3>Open Source Licenses</h3>
|
||||
<ul>
|
||||
<li>AOSP: <a href="https://source.android.com/setup/start/licenses">APL 2.0</a></li>
|
||||
<li>OkHttp: <a href="https://github.com/square/okhttp/blob/master/LICENSE.txt">APL 2.0</a></li>
|
||||
<li>Okio: <a href="https://github.com/square/okio/blob/master/LICENSE.txt">APL 2.0</a></li>
|
||||
<li>jsoup: <a href="https://github.com/jhy/jsoup/blob/master/LICENSE">MIT</a></li>
|
||||
<li>AdapterDelegates: <a href="https://github.com/sockeqwe/AdapterDelegates/blob/master/LICENSE">APL 2.0</a></li>
|
||||
<li>Koin: <a href="https://github.com/InsertKoinIO/koin/blob/master/LICENSE">APL 2.0</a></li>
|
||||
<li>Coil: <a href="https://github.com/coil-kt/coil/blob/main/LICENSE.txt">APL 2.0</a></li>
|
||||
<li>Subsampling Scale Image View: <a href="https://github.com/davemorrissey/subsampling-scale-image-view/blob/master/LICENSE">APL 2.0</a></li>
|
||||
<li>Disk LRU Cache: <a href="https://github.com/solkin/disk-lru-cache/blob/master/LICENSE">MIT</a></li>
|
||||
</ul>
|
||||
@@ -217,13 +217,7 @@
|
||||
<string name="chapter_is_missing_text">Гэтая глава адсутнічае на вашай прыладзе. Спампуйце ціпрачытайце яе онлайн.</string>
|
||||
<string name="text_downloads_holder">На дадзены момант няма актыўных спамповак</string>
|
||||
<string name="queued">У чарзе</string>
|
||||
<string name="about_license">Ліцэнзія</string>
|
||||
<string name="about_copyright_and_licenses">Аўтарскія правы і ліцэнзіі</string>
|
||||
<string name="about_gratitudes_summary">Гэтыя людзі робяць Kotatsu лепш</string>
|
||||
<string name="about_gratitudes">Падзякі</string>
|
||||
<string name="about_support_developer_summary">Калі вам падабаецца гэтая праграма, вы можаце дапамагчы фінансава з дапамогай ЮMoney (был. Яндекс.Деньги)</string>
|
||||
<string name="about_support_developer">Падтрымаць распрацоўшчыка</string>
|
||||
<string name="about_feedback_4pda">Тэма на 4PDA</string>
|
||||
<string name="about_feedback_4pda">Тэма на 4PDA</string>
|
||||
<string name="about_feedback">Зваротная сувязь</string>
|
||||
<string name="about_app_translation_summary">Дапамагчы з перакладам праграмы</string>
|
||||
<string name="about_app_translation">Пераклад</string>
|
||||
|
||||
@@ -217,13 +217,7 @@
|
||||
<string name="chapter_is_missing_text">Dieses Kapitel fehlt auf deinem Gerät. Lade ihn herunter oder lese ihn online.</string>
|
||||
<string name="text_downloads_holder">Zurzeit sind keine aktiven Datenübertragungen vorhanden</string>
|
||||
<string name="queued">In Warteschlange</string>
|
||||
<string name="about_support_developer_summary">Wenn diese Anwendung dir gefällt, kannst du über Yoomoney (Yandex.Money) finanziell helfen</string>
|
||||
<string name="about_license">Lizenz</string>
|
||||
<string name="about_copyright_and_licenses">Urheberrecht und Lizenzen</string>
|
||||
<string name="about_gratitudes_summary">Diese Leute machen Kotatsu besser</string>
|
||||
<string name="about_gratitudes">Danksagung</string>
|
||||
<string name="about_support_developer">Den Entwickler unterstützen</string>
|
||||
<string name="about_feedback_4pda">Thema auf 4PDA</string>
|
||||
<string name="about_feedback_4pda">Thema auf 4PDA</string>
|
||||
<string name="about_feedback">Rückmeldung</string>
|
||||
<string name="about_app_translation">Übersetzung</string>
|
||||
<string name="about_app_translation_summary">Diese Anwendung übersetzen</string>
|
||||
|
||||
@@ -205,14 +205,8 @@
|
||||
<string name="text_history_holder_secondary">Encuentra qué leer en el menú lateral.</string>
|
||||
<string name="text_history_holder_primary">Lo que leas se mostrará aquí</string>
|
||||
<string name="text_empty_holder_primary">Está un poco vacío aquí…</string>
|
||||
<string name="about_gratitudes">Agradecimientos</string>
|
||||
<string name="about_support_developer_summary">Si te gusta esta aplicación, puedes ayudar económicamente a través de Yoomoney (ex. Yandex.Money)</string>
|
||||
<string name="about_support_developer">Apoyar al desarrollador</string>
|
||||
<string name="search_only_on_s">Buscar sólo en %s</string>
|
||||
<string name="about_gratitudes_summary">Todas estas personas hicieron que Kotatsu fuera mejor</string>
|
||||
<string name="about_license">Licencia</string>
|
||||
<string name="about_copyright_and_licenses">Derechos de autor y licencias</string>
|
||||
<string name="chapter_is_missing">Falta un capítulo</string>
|
||||
<string name="search_only_on_s">Buscar sólo en %s</string>
|
||||
<string name="chapter_is_missing">Falta un capítulo</string>
|
||||
<string name="about_app_translation_summary">Traducir esta aplicación</string>
|
||||
<string name="about_feedback">Comentarios</string>
|
||||
<string name="about_app_translation">Traducción</string>
|
||||
|
||||
@@ -4,13 +4,7 @@
|
||||
<string name="genres">Tyylilajit</string>
|
||||
<string name="auth_not_supported_by">%s -valtuutusta ei tueta</string>
|
||||
<string name="auth_complete">Valtuutus valmis</string>
|
||||
<string name="about_license">Lisenssi</string>
|
||||
<string name="about_copyright_and_licenses">Tekijänoikeudet ja lisenssit</string>
|
||||
<string name="about_gratitudes_summary">Nämä ihmiset tekevät Kotatsusta paremman</string>
|
||||
<string name="about_gratitudes">Kiitokset</string>
|
||||
<string name="about_support_developer_summary">Jos pidät tästä sovelluksesta, voit auttaa taloudellisesti Yoomoneyn (ent. Yandex.Money) kautta</string>
|
||||
<string name="about_support_developer">Tue kehittäjää</string>
|
||||
<string name="about_feedback_4pda">Aihe 4PDA: ssa</string>
|
||||
<string name="about_feedback_4pda">Aihe 4PDA: ssa</string>
|
||||
<string name="about_feedback">Palaute</string>
|
||||
<string name="about_app_translation">Käännös</string>
|
||||
<string name="about_app_translation_summary">Käännä tämä sovellus</string>
|
||||
|
||||
@@ -217,13 +217,7 @@
|
||||
<string name="chapter_is_missing_text">Téléchargez ou lisez ce chapitre manquant en ligne.</string>
|
||||
<string name="text_downloads_holder">Aucun téléchargement actif</string>
|
||||
<string name="queued">En file d\'attente</string>
|
||||
<string name="about_support_developer_summary">Si vous aimez cette application, vous pouvez envoyer de l\'argent via Yoomoney (ex. Yandex.Money)</string>
|
||||
<string name="about_support_developer">Soutenir le concepteur</string>
|
||||
<string name="about_license">Licence</string>
|
||||
<string name="about_copyright_and_licenses">Droits d\'auteur et licences</string>
|
||||
<string name="about_gratitudes_summary">Ces personnes ont toutes rendu Kotatsu meilleur</string>
|
||||
<string name="about_gratitudes">Remerciements</string>
|
||||
<string name="about_feedback_4pda">Sujet sur 4PDA</string>
|
||||
<string name="about_feedback_4pda">Sujet sur 4PDA</string>
|
||||
<string name="about_feedback">Remarques</string>
|
||||
<string name="about_app_translation">Traduction</string>
|
||||
<string name="about_app_translation_summary">Traduire cette application</string>
|
||||
|
||||
@@ -217,13 +217,7 @@
|
||||
<string name="chapter_is_missing_text">Questo capitolo manca sul tuo dispositivo. Scaricalo o leggilo in linea.</string>
|
||||
<string name="text_downloads_holder">Attualmente non ci sono scaricamenti attivi</string>
|
||||
<string name="queued">In coda</string>
|
||||
<string name="about_license">Licenza</string>
|
||||
<string name="about_copyright_and_licenses">Diritti d\'autore e licenze</string>
|
||||
<string name="about_gratitudes_summary">Queste persone rendono Kotatsu migliore</string>
|
||||
<string name="about_gratitudes">Ringraziamenti</string>
|
||||
<string name="about_support_developer_summary">Se ti piace questa applicazione, puoi aiutare finanziariamente via Yoomoney (Yandex.Money)</string>
|
||||
<string name="about_support_developer">Sostieni lo sviluppatore</string>
|
||||
<string name="about_feedback_4pda">Argomento su 4PDA</string>
|
||||
<string name="about_feedback_4pda">Argomento su 4PDA</string>
|
||||
<string name="about_feedback">Commenti</string>
|
||||
<string name="about_app_translation">Traduzione</string>
|
||||
<string name="about_app_translation_summary">Traduci questa applicazione</string>
|
||||
|
||||
@@ -163,10 +163,7 @@
|
||||
<string name="prefer_rtl_reader">右から左(←)の読書を好む</string>
|
||||
<string name="about_feedback">フィードバック</string>
|
||||
<string name="about_feedback_4pda">4PDAに関する話題</string>
|
||||
<string name="about_support_developer">開発者をサポートします(Yoomoneyが開きます)</string>
|
||||
<string name="about_support_developer_summary">このアプリが気に入ったら、Yoomoney(Yandex.Moneyなど)から開発者をサポートできます</string>
|
||||
<string name="about_license">ライセンス</string>
|
||||
<string name="auth_complete">承認済み</string>
|
||||
<string name="auth_complete">承認済み</string>
|
||||
<string name="auth_not_supported_by">%sへのログインはサポートされていません</string>
|
||||
<string name="state_finished">完成</string>
|
||||
<string name="state_ongoing">進行中</string>
|
||||
@@ -212,12 +209,10 @@
|
||||
<string name="about_app_translation">このアプリを翻訳</string>
|
||||
<string name="data_restored_success">全てのデータが復元されました</string>
|
||||
<string name="data_restored">復元</string>
|
||||
<string name="about_gratitudes">感謝の気持ち</string>
|
||||
<string name="preparing_">準備中…</string>
|
||||
<string name="preparing_">準備中…</string>
|
||||
<string name="zoom_mode_keep_start">開始時に維持</string>
|
||||
<string name="backup_restore">バックアップと復元</string>
|
||||
<string name="about_copyright_and_licenses">著作権とライセンス</string>
|
||||
<string name="reverse">リバース</string>
|
||||
<string name="reverse">リバース</string>
|
||||
<string name="reader_mode_hint">選択した構成はこの漫画のために記憶されます</string>
|
||||
<string name="clear_cookies">クッキーを削除</string>
|
||||
<string name="report_github">GitHubでissueを作成する(GitHubのアカウントが必要です)</string>
|
||||
@@ -235,8 +230,7 @@
|
||||
<string name="scale_mode">スケールモード</string>
|
||||
<string name="error_empty_name">名前を入力する必要があります</string>
|
||||
<string name="today">今日</string>
|
||||
<string name="about_gratitudes_summary">この人達のお陰でKotatsuがより良くなりました</string>
|
||||
<string name="about_app_translation_summary">Kotatsuを翻訳する(Weblateのサイトに移動します)</string>
|
||||
<string name="about_app_translation_summary">Kotatsuを翻訳する(Weblateのサイトに移動します)</string>
|
||||
<string name="next">次のページ</string>
|
||||
<string name="silent">サイレント</string>
|
||||
<string name="sign_in">サインイン</string>
|
||||
|
||||
@@ -223,13 +223,7 @@
|
||||
<string name="about_app_translation">Oversettelse</string>
|
||||
<string name="about_feedback">Tilbakemelding</string>
|
||||
<string name="about_feedback_4pda">Emne på 4PDA</string>
|
||||
<string name="about_support_developer">Støtt utvikleren</string>
|
||||
<string name="about_support_developer_summary">Hvis du liker programmet kan du kronerulle det på Yoomoney (tidligere Yandex.Money)</string>
|
||||
<string name="about_gratitudes">Takk rettes til</string>
|
||||
<string name="about_gratitudes_summary">Folk som gjorde Kotatsu enda bedre</string>
|
||||
<string name="about_copyright_and_licenses">Opphavsrett og lisenser</string>
|
||||
<string name="about_license">Lisens</string>
|
||||
<string name="auth_complete">Identitetsbekreftet</string>
|
||||
<string name="auth_complete">Identitetsbekreftet</string>
|
||||
<string name="auth_not_supported_by">Innlogging på %s støttes ikke</string>
|
||||
<string name="text_clear_cookies_prompt">Du vil bli utlogget fra alle kilder</string>
|
||||
<string name="genres">Sjangere</string>
|
||||
|
||||
@@ -183,12 +183,7 @@
|
||||
<string name="about_app_translation">Tradução</string>
|
||||
<string name="about_feedback">Comentar</string>
|
||||
<string name="about_feedback_4pda">Tópico no 4PDA</string>
|
||||
<string name="about_support_developer">Apoie o desenvolvedor</string>
|
||||
<string name="about_support_developer_summary">Se você gosta deste aplicativo, você pode enviar dinheiro através do Yoomoney (ex. Yandex.Money)</string>
|
||||
<string name="about_gratitudes">Gratidão</string>
|
||||
<string name="about_gratitudes_summary">Todas essas pessoas tornaram o Kotatsu melhor</string>
|
||||
<string name="about_license">Licença</string>
|
||||
<string name="auth_complete">Autorizado</string>
|
||||
<string name="auth_complete">Autorizado</string>
|
||||
<string name="auth_not_supported_by">O login em %s não é suportado</string>
|
||||
<string name="text_clear_cookies_prompt">Você será desconectado de todas as fontes</string>
|
||||
<string name="genres">Gêneros</string>
|
||||
@@ -266,8 +261,7 @@
|
||||
<string name="data_restored_with_errors">Os dados foram restaurados, mas há erros</string>
|
||||
<string name="report_github">Criar problema no GitHub</string>
|
||||
<string name="file_not_found">Arquivo não encontrado</string>
|
||||
<string name="about_copyright_and_licenses">Direitos autorais e licenças</string>
|
||||
<string name="search_chapters">Localizar capítulo</string>
|
||||
<string name="search_chapters">Localizar capítulo</string>
|
||||
<string name="chapters_empty">Não há capítulos neste mangá</string>
|
||||
<string name="percent_string_pattern">%1$s%%</string>
|
||||
</resources>
|
||||
@@ -185,13 +185,7 @@
|
||||
<string name="about_app_translation_summary">Traduzir esta aplicação</string>
|
||||
<string name="about_feedback">Comentar</string>
|
||||
<string name="about_feedback_4pda">Tópico no 4PDA</string>
|
||||
<string name="about_support_developer">Apoiar o desenvolvedor</string>
|
||||
<string name="about_support_developer_summary">Se você gosta deste aplicativo, você pode enviar dinheiro através do Yoomoney (ex. Yandex.Money)</string>
|
||||
<string name="about_gratitudes">Agradecimentos</string>
|
||||
<string name="about_gratitudes_summary">Todas essas pessoas tornaram o Kotatsu melhor</string>
|
||||
<string name="about_copyright_and_licenses">Direitos de autor e licenças</string>
|
||||
<string name="about_license">Licença</string>
|
||||
<string name="chapter_is_missing_text">Baixe ou leia este capítulo perdido online.</string>
|
||||
<string name="chapter_is_missing_text">Baixe ou leia este capítulo perdido online.</string>
|
||||
<string name="chapter_is_missing">O capítulo está em falta</string>
|
||||
<string name="auth_complete">Autorizado</string>
|
||||
<string name="auth_not_supported_by">O login em %s não é suportado</string>
|
||||
|
||||
@@ -220,13 +220,7 @@
|
||||
<string name="about_app_translation">Перевод</string>
|
||||
<string name="about_feedback_4pda">Тема на 4PDA</string>
|
||||
<string name="about_feedback">Обратная связь</string>
|
||||
<string name="about_support_developer">Поддержать разработчика</string>
|
||||
<string name="about_support_developer_summary">Если вам нравится это приложение, вы можете помочь финансово с помощью ЮMoney (бывш. Яндекс.Деньги)</string>
|
||||
<string name="about_gratitudes">Благодарности</string>
|
||||
<string name="about_gratitudes_summary">Все эти люди сделали Kotatsu лучше</string>
|
||||
<string name="about_copyright_and_licenses">Авторские права и лицензии</string>
|
||||
<string name="about_license">Лицензия</string>
|
||||
<string name="auth_complete">Авторизация выполнена</string>
|
||||
<string name="auth_complete">Авторизация выполнена</string>
|
||||
<string name="auth_not_supported_by">Вход в %s не поддерживается</string>
|
||||
<string name="text_clear_cookies_prompt">Вы выйдете из всех источников</string>
|
||||
<string name="genres">Жанры</string>
|
||||
@@ -269,4 +263,9 @@
|
||||
<string name="various_languages">Разные языки</string>
|
||||
<string name="search_chapters">Найти главу</string>
|
||||
<string name="chapters_empty">В этой манге нет глав</string>
|
||||
<string name="appearance">Оформление</string>
|
||||
<string name="content">Контент</string>
|
||||
<string name="suggestions_updating">Обновление рекомендаций</string>
|
||||
<string name="suggestions_excluded_genres">Исключить жанры</string>
|
||||
<string name="suggestions_excluded_genres_summary">Укажите жанры, которые Вы не хотите видеть в рекомендациях</string>
|
||||
</resources>
|
||||
6
app/src/main/res/values-sw360dp/bools.xml
Normal file
6
app/src/main/res/values-sw360dp/bools.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<bool name="config_materialPreferenceIconSpaceReserved" tools:ignore="MissingDefaultResource,UnusedAttribute" tools:node="replace">
|
||||
false
|
||||
</bool>
|
||||
</resources>
|
||||
@@ -131,8 +131,7 @@
|
||||
<string name="restore_backup">Yedekten geri yükle</string>
|
||||
<string name="update">Güncelle</string>
|
||||
<string name="sign_in">Oturum aç</string>
|
||||
<string name="about_license">Lisans</string>
|
||||
<string name="state_finished">Bitti</string>
|
||||
<string name="state_finished">Bitti</string>
|
||||
<string name="about">Hakkında</string>
|
||||
<string name="auth_required">Bu içeriği görüntülemek için oturum açın</string>
|
||||
<string name="confirm">Onayla</string>
|
||||
@@ -185,9 +184,7 @@
|
||||
<string name="about_app_translation">Çeviri</string>
|
||||
<string name="search_only_on_s">Yalnızca %s içinde ara</string>
|
||||
<string name="about_feedback_4pda">4PDA\'daki konu</string>
|
||||
<string name="about_gratitudes">Teşekkürler</string>
|
||||
<string name="about_gratitudes_summary">Tüm bu insanlar Kotatsu\'yu daha iyi hale getirdi</string>
|
||||
<string name="state_ongoing">Devam ediyor</string>
|
||||
<string name="state_ongoing">Devam ediyor</string>
|
||||
<string name="text_clear_cookies_prompt">Tüm kaynaklardaki oturumunuz kapatılacak</string>
|
||||
<string name="enabled_sources">Kullanılan kaynaklar</string>
|
||||
<string name="available_sources">Kullanılabilir kaynaklar</string>
|
||||
@@ -226,16 +223,13 @@
|
||||
<string name="captcha_required">CAPTCHA gerekli</string>
|
||||
<string name="cookies_cleared">Tüm çerezler kaldırıldı</string>
|
||||
<string name="reader_mode_hint">Seçilen yapılandırma bu manga için hatırlanacak</string>
|
||||
<string name="about_support_developer_summary">Bu uygulamayı beğendiyseniz Yoomoney (eski Yandex.Money) üzerinden para gönderebilirsiniz</string>
|
||||
<string name="text_clear_updates_feed_prompt">Tüm güncelleme geçmişi kalıcı olarak silinsin mi\?</string>
|
||||
<string name="text_clear_updates_feed_prompt">Tüm güncelleme geçmişi kalıcı olarak silinsin mi\?</string>
|
||||
<string name="_and_x_more">…ve %1$d daha fazlası</string>
|
||||
<string name="protect_application_subtitle">Uygulamayı başlatmak için bir parola girin</string>
|
||||
<string name="text_clear_search_history_prompt">Tüm son arama sorguları kalıcı olarak kaldırılsın mı\?</string>
|
||||
<string name="about_feedback">Geri bildirim</string>
|
||||
<string name="backup_saved">Yedek kaydedildi</string>
|
||||
<string name="about_support_developer">Geliştiriciyi destekleyin</string>
|
||||
<string name="about_copyright_and_licenses">Telif Hakkı ve Lisanslar</string>
|
||||
<string name="genres">Türler</string>
|
||||
<string name="genres">Türler</string>
|
||||
<string name="date_format">Tarih biçimi</string>
|
||||
<string name="system_default">Öntanımlı</string>
|
||||
<string name="error_empty_name">Bir ad girmelisiniz</string>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<resources>
|
||||
|
||||
<attr name="sliderPreferenceStyle" />
|
||||
<attr name="multiAutoCompleteTextViewPreferenceStyle" />
|
||||
<attr name="listItemTextViewStyle" />
|
||||
|
||||
<declare-styleable name="Theme">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<bool name="is_tablet">false</bool>
|
||||
<bool name="light_status_bar">true</bool>
|
||||
<bool name="light_navigation_bar">false</bool>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="url_github_issues">https://github.com/nv95/Kotatsu/issues</string>
|
||||
<string name="url_discord">https://discord.gg/NNJ5RgVBC5</string>
|
||||
<string name="url_forpda">https://4pda.to/forum/index.php?showtopic=697669</string>
|
||||
<string name="url_weblate">https://hosted.weblate.org/engage/kotatsu</string>
|
||||
<string-array name="values_theme" translatable="false">
|
||||
<item>-1</item>
|
||||
<item>1</item>
|
||||
|
||||
@@ -222,13 +222,7 @@
|
||||
<string name="about_app_translation">Translation</string>
|
||||
<string name="about_feedback">Feedback</string>
|
||||
<string name="about_feedback_4pda">Topic on 4PDA</string>
|
||||
<string name="about_support_developer">Support the developer</string>
|
||||
<string name="about_support_developer_summary">If you like this app, you can send money through Yoomoney (ex. Yandex.Money)</string>
|
||||
<string name="about_gratitudes">Gratitudes</string>
|
||||
<string name="about_gratitudes_summary">These people all made Kotatsu better</string>
|
||||
<string name="about_copyright_and_licenses">Copyright and Licenses</string>
|
||||
<string name="about_license">License</string>
|
||||
<string name="auth_complete">Authorized</string>
|
||||
<string name="auth_complete">Authorized</string>
|
||||
<string name="auth_not_supported_by">Logging in on %s is not supported</string>
|
||||
<string name="text_clear_cookies_prompt">You will be logged out from all sources</string>
|
||||
<string name="genres">Genres</string>
|
||||
@@ -270,6 +264,13 @@
|
||||
<string name="search_chapters">Find chapter</string>
|
||||
<string name="chapters_empty">No chapters in this manga</string>
|
||||
<string name="percent_string_pattern">%1$s%%</string>
|
||||
<string name="appearance">Appearance</string>
|
||||
<string name="content">Content</string>
|
||||
<string name="github" translatable="false">GitHub</string>
|
||||
<string name="discord" translatable="false">Discord</string>
|
||||
<string name="suggestions_updating">Suggestions updating</string>
|
||||
<string name="suggestions_excluded_genres">Exclude genres</string>
|
||||
<string name="suggestions_excluded_genres_summary">Specify genres that you do not want to see in the suggestions</string>
|
||||
<string name="text_delete_local_manga_batch">Delete selected items from device permanently?</string>
|
||||
<string name="removal_completed">Removal completed</string>
|
||||
<string name="batch_manga_save_confirm">Are you sure you want to download all selected manga with all its chapters? This action can consume a lot of traffic and storage</string>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<!--Toolbars-->
|
||||
|
||||
@@ -149,7 +149,11 @@
|
||||
</style>
|
||||
|
||||
<style name="Preference.Slider" parent="Preference.SeekBarPreference.Material">
|
||||
<item name="android:layout">@layout/pref_slider</item>
|
||||
<item name="android:layout">@layout/preference_slider</item>
|
||||
</style>
|
||||
|
||||
<style name="Preference.MultiAutoCompleteTextView" parent="Preference.DialogPreference.EditTextPreference.Material">
|
||||
<item name="android:dialogLayout">@layout/preference_dialog_multiautocompletetextview</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -1,84 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
app:initialExpandedChildrenCount="5">
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<PreferenceCategory
|
||||
app:iconSpaceReserved="false"
|
||||
app:title="@string/app_name">
|
||||
<PreferenceCategory android:title="@string/app_name">
|
||||
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="app_version"
|
||||
app:persistent="false"
|
||||
app:summary="@string/check_for_updates" />
|
||||
android:key="app_version"
|
||||
android:persistent="false"
|
||||
android:summary="@string/check_for_updates" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
app:defaultValue="true"
|
||||
app:iconSpaceReserved="false"
|
||||
app:isPreferenceVisible="false"
|
||||
app:key="app_update_auto"
|
||||
app:summary="@string/show_notification_app_update"
|
||||
app:title="@string/application_update"
|
||||
tools:isPreferenceVisible="true" />
|
||||
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="about_app_translation"
|
||||
app:summary="@string/about_app_translation_summary"
|
||||
app:title="@string/about_app_translation" />
|
||||
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="about_support_developer"
|
||||
app:summary="@string/about_support_developer_summary"
|
||||
app:title="@string/about_support_developer" />
|
||||
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="about_gratitudes"
|
||||
app:summary="@string/about_gratitudes_summary"
|
||||
app:title="@string/about_gratitudes" />
|
||||
android:defaultValue="true"
|
||||
android:key="app_update_auto"
|
||||
android:summary="@string/show_notification_app_update"
|
||||
android:title="@string/application_update" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
app:iconSpaceReserved="false"
|
||||
app:title="@string/about_feedback">
|
||||
<PreferenceCategory android:title="@string/about_feedback">
|
||||
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="about_feedback_4pda"
|
||||
app:summary="https://4pda.to/forum/index.php?showtopic=697669"
|
||||
app:title="@string/about_feedback_4pda" />
|
||||
android:key="about_feedback_github"
|
||||
android:persistent="false"
|
||||
android:summary="@string/url_github_issues"
|
||||
android:title="@string/github" />
|
||||
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="about_feedback_discord"
|
||||
app:summary="https://discord.gg/NNJ5RgVBC5"
|
||||
app:title="Discord" />
|
||||
android:key="about_feedback_4pda"
|
||||
android:summary="@string/url_forpda"
|
||||
android:title="@string/about_feedback_4pda" />
|
||||
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="about_feedback_github"
|
||||
app:summary="https://github.com/nv95/Kotatsu/issues"
|
||||
app:title="GitHub" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="copyright"
|
||||
app:title="@string/about_copyright_and_licenses">
|
||||
android:key="about_feedback_discord"
|
||||
android:summary="@string/url_discord"
|
||||
android:title="@string/discord" />
|
||||
|
||||
<Preference
|
||||
android:fragment="org.koitharu.kotatsu.settings.about.LicenseFragment"
|
||||
app:icon="@drawable/ic_copyleft"
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="about_license"
|
||||
app:title="@string/about_license" />
|
||||
android:key="about_app_translation"
|
||||
android:summary="@string/about_app_translation_summary"
|
||||
android:title="@string/about_app_translation" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
|
||||
53
app/src/main/res/xml/pref_appearance.xml
Normal file
53
app/src/main/res/xml/pref_appearance.xml
Normal file
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="-1"
|
||||
android:entries="@array/themes"
|
||||
android:entryValues="@array/values_theme"
|
||||
android:key="theme"
|
||||
android:title="@string/theme"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="dynamic_theme"
|
||||
android:summary="@string/dynamic_theme_summary"
|
||||
android:title="@string/dynamic_theme"
|
||||
app:isPreferenceVisible="false" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="amoled_theme"
|
||||
android:summary="@string/black_dark_theme_summary"
|
||||
android:title="@string/black_dark_theme" />
|
||||
|
||||
<ListPreference
|
||||
android:key="date_format"
|
||||
android:title="@string/date_format" />
|
||||
|
||||
<ListPreference
|
||||
android:entries="@array/list_modes"
|
||||
android:key="list_mode_2"
|
||||
android:title="@string/list_mode"
|
||||
app:allowDividerAbove="true"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<org.koitharu.kotatsu.settings.utils.SliderPreference
|
||||
android:key="grid_size"
|
||||
android:stepSize="5"
|
||||
android:title="@string/grid_size"
|
||||
android:valueFrom="50"
|
||||
android:valueTo="150"
|
||||
app:defaultValue="100" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:key="protect_app"
|
||||
android:persistent="false"
|
||||
android:summary="@string/protect_application_summary"
|
||||
android:title="@string/protect_application"
|
||||
app:allowDividerAbove="true" />
|
||||
|
||||
</PreferenceScreen>
|
||||
@@ -6,14 +6,12 @@
|
||||
<Preference
|
||||
android:key="backup"
|
||||
android:persistent="false"
|
||||
android:title="@string/create_backup"
|
||||
app:iconSpaceReserved="false" />
|
||||
android:title="@string/create_backup" />
|
||||
|
||||
<Preference
|
||||
android:key="restore"
|
||||
android:persistent="false"
|
||||
android:title="@string/restore_backup"
|
||||
app:iconSpaceReserved="false" />
|
||||
android:title="@string/restore_backup" />
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_info_outline"
|
||||
|
||||
27
app/src/main/res/xml/pref_content.xml
Normal file
27
app/src/main/res/xml/pref_content.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<PreferenceScreen
|
||||
android:fragment="org.koitharu.kotatsu.settings.sources.SourcesSettingsFragment"
|
||||
android:key="remote_sources"
|
||||
android:title="@string/remote_sources" />
|
||||
|
||||
<Preference
|
||||
android:key="local_storage"
|
||||
android:persistent="false"
|
||||
android:title="@string/manga_save_location" />
|
||||
|
||||
<PreferenceScreen
|
||||
android:fragment="org.koitharu.kotatsu.settings.SuggestionsSettingsFragment"
|
||||
android:key="suggestions"
|
||||
android:persistent="false"
|
||||
android:title="@string/suggestions" />
|
||||
|
||||
<PreferenceScreen
|
||||
android:fragment="org.koitharu.kotatsu.settings.backup.BackupSettingsFragment"
|
||||
android:title="@string/backup_restore"
|
||||
app:allowDividerAbove="true" />
|
||||
|
||||
</PreferenceScreen>
|
||||
@@ -1,51 +1,42 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<Preference
|
||||
android:key="search_history_clear"
|
||||
android:persistent="false"
|
||||
android:summary="@string/loading_"
|
||||
android:title="@string/clear_search_history"
|
||||
app:iconSpaceReserved="false" />
|
||||
android:title="@string/clear_search_history" />
|
||||
|
||||
<Preference
|
||||
android:key="updates_feed_clear"
|
||||
android:persistent="false"
|
||||
android:summary="@string/loading_"
|
||||
android:title="@string/clear_updates_feed"
|
||||
app:iconSpaceReserved="false" />
|
||||
android:title="@string/clear_updates_feed" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:key="history_exclude_nsfw"
|
||||
android:title="@string/exclude_nsfw_from_history"
|
||||
app:iconSpaceReserved="false" />
|
||||
android:title="@string/exclude_nsfw_from_history" />
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/cache"
|
||||
app:iconSpaceReserved="false">
|
||||
<PreferenceCategory android:title="@string/cache">
|
||||
|
||||
<Preference
|
||||
android:key="thumbs_cache_clear"
|
||||
android:persistent="false"
|
||||
android:summary="@string/computing_"
|
||||
android:title="@string/clear_thumbs_cache"
|
||||
app:iconSpaceReserved="false" />
|
||||
android:title="@string/clear_thumbs_cache" />
|
||||
|
||||
<Preference
|
||||
android:key="pages_cache_clear"
|
||||
android:persistent="false"
|
||||
android:summary="@string/computing_"
|
||||
android:title="@string/clear_pages_cache"
|
||||
app:iconSpaceReserved="false" />
|
||||
android:title="@string/clear_pages_cache" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<Preference
|
||||
android:key="cookies_clear"
|
||||
android:persistent="false"
|
||||
android:title="@string/clear_cookies"
|
||||
app:iconSpaceReserved="false" />
|
||||
android:title="@string/clear_cookies" />
|
||||
|
||||
</PreferenceScreen>
|
||||
@@ -1,102 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="-1"
|
||||
android:entries="@array/themes"
|
||||
android:entryValues="@array/values_theme"
|
||||
android:key="theme"
|
||||
android:title="@string/theme"
|
||||
app:iconSpaceReserved="false"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="dynamic_theme"
|
||||
android:summary="@string/dynamic_theme_summary"
|
||||
android:title="@string/dynamic_theme"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="amoled_theme"
|
||||
android:summary="@string/black_dark_theme_summary"
|
||||
android:title="@string/black_dark_theme"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<ListPreference
|
||||
android:key="date_format"
|
||||
android:title="@string/date_format"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<ListPreference
|
||||
android:entries="@array/list_modes"
|
||||
android:key="list_mode_2"
|
||||
android:title="@string/list_mode"
|
||||
app:allowDividerAbove="true"
|
||||
app:iconSpaceReserved="false"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<org.koitharu.kotatsu.settings.utils.SliderPreference
|
||||
android:key="grid_size"
|
||||
android:stepSize="5"
|
||||
android:title="@string/grid_size"
|
||||
android:valueFrom="50"
|
||||
android:valueTo="150"
|
||||
app:defaultValue="100"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<PreferenceScreen
|
||||
android:fragment="org.koitharu.kotatsu.settings.sources.SourcesSettingsFragment"
|
||||
android:key="remote_sources"
|
||||
android:title="@string/remote_sources"
|
||||
app:allowDividerAbove="true"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<PreferenceScreen
|
||||
android:fragment="org.koitharu.kotatsu.settings.SuggestionsSettingsFragment"
|
||||
android:key="suggestions"
|
||||
android:persistent="false"
|
||||
android:title="@string/suggestions"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<Preference
|
||||
android:key="local_storage"
|
||||
android:title="@string/manga_save_location"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<PreferenceScreen
|
||||
android:fragment="org.koitharu.kotatsu.settings.HistorySettingsFragment"
|
||||
android:title="@string/history_and_cache"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:key="protect_app"
|
||||
android:persistent="false"
|
||||
android:summary="@string/protect_application_summary"
|
||||
android:title="@string/protect_application"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<PreferenceScreen
|
||||
android:fragment="org.koitharu.kotatsu.settings.ReaderSettingsFragment"
|
||||
android:title="@string/reader_settings"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<PreferenceScreen
|
||||
android:fragment="org.koitharu.kotatsu.settings.TrackerSettingsFragment"
|
||||
android:title="@string/check_for_new_chapters"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<PreferenceScreen
|
||||
android:fragment="org.koitharu.kotatsu.settings.backup.BackupSettingsFragment"
|
||||
android:title="@string/backup_restore"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<PreferenceScreen
|
||||
android:fragment="org.koitharu.kotatsu.settings.about.AboutSettingsFragment"
|
||||
android:title="@string/about"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
</PreferenceScreen>
|
||||
@@ -1,23 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<Preference
|
||||
android:key="notifications_sound"
|
||||
android:title="@string/notification_sound"
|
||||
app:iconSpaceReserved="false" />
|
||||
android:title="@string/notification_sound" />
|
||||
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="false"
|
||||
android:key="notifications_vibrate"
|
||||
android:title="@string/vibration"
|
||||
app:iconSpaceReserved="false" />
|
||||
android:title="@string/vibration" />
|
||||
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="true"
|
||||
android:key="notifications_light"
|
||||
android:title="@string/light_indicator"
|
||||
app:iconSpaceReserved="false" />
|
||||
android:title="@string/light_indicator" />
|
||||
|
||||
</PreferenceScreen>
|
||||
@@ -7,14 +7,13 @@
|
||||
android:defaultValue="false"
|
||||
android:key="reader_prefer_rtl"
|
||||
android:summary="@string/prefer_rtl_reader_summary"
|
||||
android:title="@string/prefer_rtl_reader"
|
||||
app:iconSpaceReserved="false" />
|
||||
android:title="@string/prefer_rtl_reader" />
|
||||
|
||||
<ListPreference
|
||||
android:entries="@array/zoom_modes"
|
||||
android:key="zoom_mode"
|
||||
android:title="@string/scale_mode"
|
||||
app:iconSpaceReserved="false"
|
||||
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<MultiSelectListPreference
|
||||
@@ -23,20 +22,17 @@
|
||||
android:entryValues="@array/values_reader_switchers"
|
||||
android:key="reader_switchers"
|
||||
android:title="@string/switch_pages"
|
||||
app:allowDividerAbove="true"
|
||||
app:iconSpaceReserved="false" />
|
||||
app:allowDividerAbove="true" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="reader_animation"
|
||||
android:title="@string/pages_animation"
|
||||
app:iconSpaceReserved="false" />
|
||||
android:title="@string/pages_animation" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="pages_numbers"
|
||||
android:title="@string/show_pages_numbers"
|
||||
app:iconSpaceReserved="false" />
|
||||
android:title="@string/show_pages_numbers" />
|
||||
|
||||
<ListPreference
|
||||
android:entries="@array/screenshots_policy"
|
||||
@@ -44,7 +40,7 @@
|
||||
android:key="screenshots_policy"
|
||||
android:title="@string/screenshots_policy"
|
||||
app:defaultValue="allow"
|
||||
app:iconSpaceReserved="false"
|
||||
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<ListPreference
|
||||
@@ -53,7 +49,7 @@
|
||||
android:key="pages_preload"
|
||||
android:title="@string/preload_pages"
|
||||
app:defaultValue="2"
|
||||
app:iconSpaceReserved="false"
|
||||
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
</PreferenceScreen>
|
||||
37
app/src/main/res/xml/pref_root.xml
Normal file
37
app/src/main/res/xml/pref_root.xml
Normal file
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<PreferenceScreen
|
||||
android:fragment="org.koitharu.kotatsu.settings.AppearanceSettingsFragment"
|
||||
android:icon="@drawable/ic_appearance"
|
||||
android:key="appearance"
|
||||
android:title="@string/appearance" />
|
||||
|
||||
<PreferenceScreen
|
||||
android:fragment="org.koitharu.kotatsu.settings.ContentSettingsFragment"
|
||||
android:icon="@drawable/ic_manga_source"
|
||||
android:key="content"
|
||||
android:title="@string/content" />
|
||||
|
||||
<PreferenceScreen
|
||||
android:fragment="org.koitharu.kotatsu.settings.HistorySettingsFragment"
|
||||
android:icon="@drawable/ic_history"
|
||||
android:title="@string/history_and_cache" />
|
||||
|
||||
<PreferenceScreen
|
||||
android:fragment="org.koitharu.kotatsu.settings.ReaderSettingsFragment"
|
||||
android:icon="@drawable/ic_book_page"
|
||||
android:title="@string/reader_settings" />
|
||||
|
||||
<PreferenceScreen
|
||||
android:fragment="org.koitharu.kotatsu.settings.TrackerSettingsFragment"
|
||||
android:icon="@drawable/ic_feed"
|
||||
android:title="@string/check_for_new_chapters" />
|
||||
|
||||
<PreferenceScreen
|
||||
android:fragment="org.koitharu.kotatsu.settings.about.AboutSettingsFragment"
|
||||
android:icon="@drawable/ic_info_outline"
|
||||
android:title="@string/about" />
|
||||
|
||||
</PreferenceScreen>
|
||||
@@ -5,10 +5,9 @@
|
||||
|
||||
<Preference
|
||||
android:key="auth"
|
||||
android:order="100"
|
||||
android:persistent="false"
|
||||
android:title="@string/sign_in"
|
||||
android:order="100"
|
||||
app:allowDividerAbove="true"
|
||||
app:iconSpaceReserved="false" />
|
||||
app:allowDividerAbove="true" />
|
||||
|
||||
</PreferenceScreen>
|
||||
@@ -7,14 +7,18 @@
|
||||
android:defaultValue="false"
|
||||
android:key="suggestions"
|
||||
android:summary="@string/suggestions_summary"
|
||||
android:title="@string/suggestions_enable"
|
||||
app:iconSpaceReserved="false" />
|
||||
android:title="@string/suggestions_enable" />
|
||||
|
||||
<org.koitharu.kotatsu.settings.utils.MultiAutoCompleteTextViewPreference
|
||||
android:dependency="suggestions"
|
||||
android:key="suggestions_exclude_tags"
|
||||
android:summary="@string/suggestions_excluded_genres_summary"
|
||||
android:title="@string/suggestions_excluded_genres" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:dependency="suggestions"
|
||||
android:key="suggestions_exclude_nsfw"
|
||||
android:title="@string/exclude_nsfw_from_suggestions"
|
||||
app:iconSpaceReserved="false" />
|
||||
android:title="@string/exclude_nsfw_from_suggestions" />
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_info_outline"
|
||||
|
||||
@@ -8,25 +8,23 @@
|
||||
android:entries="@array/track_sources"
|
||||
android:entryValues="@array/values_track_sources"
|
||||
android:key="track_sources"
|
||||
android:title="@string/track_sources"
|
||||
app:iconSpaceReserved="false" />
|
||||
android:title="@string/track_sources" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="true"
|
||||
android:key="tracker_notifications"
|
||||
android:summary="@string/show_notification_new_chapters"
|
||||
android:title="@string/notifications"
|
||||
app:iconSpaceReserved="false" />
|
||||
android:title="@string/notifications" />
|
||||
|
||||
<Preference
|
||||
android:dependency="tracker_notifications"
|
||||
android:fragment="org.koitharu.kotatsu.settings.NotificationSettingsLegacyFragment"
|
||||
android:key="notifications_settings"
|
||||
android:title="@string/notifications_settings"
|
||||
app:iconSpaceReserved="false" />
|
||||
android:title="@string/notifications_settings" />
|
||||
|
||||
<org.koitharu.kotatsu.settings.utils.LinksPreference
|
||||
android:key="track_warning"
|
||||
android:icon="@drawable/ic_info_outline"
|
||||
android:key="track_warning"
|
||||
android:persistent="false"
|
||||
android:selectable="false"
|
||||
android:summary="@string/tracker_warning"
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
Kotatsu is a free and open source manga reader for Android.<br>
|
||||
Main Features:
|
||||
<ul>
|
||||
<li>Online manga catalogues</li>
|
||||
<li>Search manga by name and genre</li>
|
||||
<li>Reading history</li>
|
||||
<li>Favourites organized by user-defined categories</li>
|
||||
<li>Downloading manga and reading it offline. Third-party CBZ archives also supported</li>
|
||||
<li>Tablet-optimized material design UI</li>
|
||||
<li>Standard and Webtoon-optimized reader</li>
|
||||
<li>Notifications about new chapters with updates feed</li>
|
||||
</ul>
|
||||
**Main Features:**
|
||||
|
||||
- Online manga catalogues
|
||||
- Search manga by name and genre
|
||||
- Reading history
|
||||
- Favourites organized by user-defined categories
|
||||
- Downloading manga and reading it offline. Third-party CBZ archives also supported
|
||||
- Tablet-optimized material design UI
|
||||
- Standard and Webtoon-optimized reader
|
||||
- Notifications about new chapters with updates feed
|
||||
- Available in multiple languages
|
||||
- Password protect access to the app
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
Kotatsu - приложения для чтения манги с открытым исходным кодом.<br>
|
||||
Основные возможности:
|
||||
<ul>
|
||||
<li>Онлайн каталоги с мангой</li>
|
||||
<li>Поиск манги по имени и жанрам</li>
|
||||
<li>История чтения</li>
|
||||
<li>Избранное с пользовательскими категориями</li>
|
||||
<li>Возможность сохранять мангу и читать её оффлайн. Поддержка сторонних комиксов в формате CBZ</li>
|
||||
<li>Интерфейс также оптимизирован для планшетов</li>
|
||||
<li>Поддержка манхвы (Webtoon)</li>
|
||||
<li>Уведомления о новых главах и лента обновлений</li>
|
||||
</ul>
|
||||
**Основные возможности:**
|
||||
|
||||
- Онлайн каталоги с мангой
|
||||
- Поиск манги по имени и жанрам
|
||||
- История чтения
|
||||
- Избранное с пользовательскими категориями
|
||||
- Возможность сохранять мангу и читать её оффлайн. Поддержка сторонних комиксов в формате CBZ
|
||||
- Интерфейс также оптимизирован для планшетов
|
||||
- Поддержка манхвы (Webtoon)
|
||||
- Уведомления о новых главах и лента обновлений
|
||||
|
||||
Reference in New Issue
Block a user