Select storage where save manga
This commit is contained in:
@@ -14,6 +14,7 @@ import org.koitharu.kotatsu.utils.AlphanumComparator
|
|||||||
import org.koitharu.kotatsu.utils.ext.longHashCode
|
import org.koitharu.kotatsu.utils.ext.longHashCode
|
||||||
import org.koitharu.kotatsu.utils.ext.readText
|
import org.koitharu.kotatsu.utils.ext.readText
|
||||||
import org.koitharu.kotatsu.utils.ext.safe
|
import org.koitharu.kotatsu.utils.ext.safe
|
||||||
|
import org.koitharu.kotatsu.utils.ext.sub
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.zip.ZipEntry
|
import java.util.zip.ZipEntry
|
||||||
@@ -29,8 +30,8 @@ class LocalMangaRepository : MangaRepository, KoinComponent {
|
|||||||
sortOrder: SortOrder?,
|
sortOrder: SortOrder?,
|
||||||
tag: MangaTag?
|
tag: MangaTag?
|
||||||
): List<Manga> {
|
): List<Manga> {
|
||||||
val files = context.getExternalFilesDirs("manga")
|
val files = getAvailableStorageDirs(context)
|
||||||
.flatMap { x -> x?.listFiles(CbzFilter())?.toList().orEmpty() }
|
.flatMap { x -> x.listFiles(CbzFilter())?.toList().orEmpty() }
|
||||||
return files.mapNotNull { x -> safe { getFromFile(x) } }
|
return files.mapNotNull { x -> safe { getFromFile(x) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,9 +134,18 @@ class LocalMangaRepository : MangaRepository, KoinComponent {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
private const val DIR_NAME = "manga"
|
||||||
|
|
||||||
fun isFileSupported(name: String): Boolean {
|
fun isFileSupported(name: String): Boolean {
|
||||||
val ext = name.substringAfterLast('.').toLowerCase(Locale.ROOT)
|
val ext = name.substringAfterLast('.').toLowerCase(Locale.ROOT)
|
||||||
return ext == "cbz" || ext == "zip"
|
return ext == "cbz" || ext == "zip"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getAvailableStorageDirs(context: Context): List<File> {
|
||||||
|
val result = ArrayList<File>(5)
|
||||||
|
result += context.filesDir.sub(DIR_NAME)
|
||||||
|
result += context.getExternalFilesDirs(DIR_NAME)
|
||||||
|
return result.distinctBy { it.canonicalPath }.filter { it.exists() || it.mkdir() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,11 +3,15 @@ package org.koitharu.kotatsu.core.prefs
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
|
import android.os.StatFs
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
import androidx.core.content.edit
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.parser.LocalMangaRepository
|
||||||
import org.koitharu.kotatsu.utils.delegates.prefs.*
|
import org.koitharu.kotatsu.utils.delegates.prefs.*
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
class AppSettings private constructor(resources: Resources, private val prefs: SharedPreferences) :
|
class AppSettings private constructor(resources: Resources, private val prefs: SharedPreferences) :
|
||||||
SharedPreferences by prefs {
|
SharedPreferences by prefs {
|
||||||
@@ -88,6 +92,26 @@ class AppSettings private constructor(resources: Resources, private val prefs: S
|
|||||||
|
|
||||||
var hiddenSources by StringSetPreferenceDelegate(resources.getString(R.string.key_sources_hidden))
|
var hiddenSources by StringSetPreferenceDelegate(resources.getString(R.string.key_sources_hidden))
|
||||||
|
|
||||||
|
fun getStorageDir(context: Context): File? {
|
||||||
|
val value = prefs.getString(context.getString(R.string.key_local_storage), null)?.let {
|
||||||
|
File(it)
|
||||||
|
}?.takeIf { it.exists() && it.canWrite() }
|
||||||
|
return value ?: LocalMangaRepository.getAvailableStorageDirs(context).maxBy {
|
||||||
|
StatFs(it.path).availableBytes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setStorageDir(context: Context, file: File?) {
|
||||||
|
val key = context.getString(R.string.key_local_storage)
|
||||||
|
prefs.edit {
|
||||||
|
if (file == null) {
|
||||||
|
remove(key)
|
||||||
|
} else {
|
||||||
|
putString(key, file.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun subscribe(listener: SharedPreferences.OnSharedPreferenceChangeListener) {
|
fun subscribe(listener: SharedPreferences.OnSharedPreferenceChangeListener) {
|
||||||
prefs.registerOnSharedPreferenceChangeListener(listener)
|
prefs.registerOnSharedPreferenceChangeListener(listener)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package org.koitharu.kotatsu.ui.common.dialog
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.os.Environment
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.BaseAdapter
|
import android.widget.BaseAdapter
|
||||||
@@ -10,7 +9,8 @@ import androidx.annotation.StringRes
|
|||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import kotlinx.android.synthetic.main.item_storage.view.*
|
import kotlinx.android.synthetic.main.item_storage.view.*
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.utils.ext.findParent
|
import org.koitharu.kotatsu.core.parser.LocalMangaRepository
|
||||||
|
import org.koitharu.kotatsu.utils.ext.getStorageName
|
||||||
import org.koitharu.kotatsu.utils.ext.inflate
|
import org.koitharu.kotatsu.utils.ext.inflate
|
||||||
import org.koitharu.kotatsu.utils.ext.longHashCode
|
import org.koitharu.kotatsu.utils.ext.longHashCode
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -20,12 +20,24 @@ class StorageSelectDialog private constructor(private val delegate: AlertDialog)
|
|||||||
|
|
||||||
fun show() = delegate.show()
|
fun show() = delegate.show()
|
||||||
|
|
||||||
class Builder(context: Context) {
|
class Builder(context: Context, defaultValue: File?, listener: OnStorageSelectListener) {
|
||||||
|
|
||||||
|
private val adapter = VolumesAdapter(context)
|
||||||
private val delegate = AlertDialog.Builder(context)
|
private val delegate = AlertDialog.Builder(context)
|
||||||
.setAdapter(VolumesAdapter(context)) { _, _ ->
|
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (adapter.isEmpty) {
|
||||||
|
delegate.setMessage(R.string.cannot_find_available_storage)
|
||||||
|
} else {
|
||||||
|
val checked = adapter.volumes.indexOfFirst {
|
||||||
|
it.first.canonicalPath == defaultValue?.canonicalPath
|
||||||
|
}
|
||||||
|
delegate.setSingleChoiceItems(adapter, checked) { d, i ->
|
||||||
|
listener.onStorageSelected(adapter.getItem(i).first)
|
||||||
|
d.dismiss()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun setTitle(@StringRes titleResId: Int): Builder {
|
fun setTitle(@StringRes titleResId: Int): Builder {
|
||||||
delegate.setTitle(titleResId)
|
delegate.setTitle(titleResId)
|
||||||
@@ -37,12 +49,17 @@ class StorageSelectDialog private constructor(private val delegate: AlertDialog)
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setNegativeButton(@StringRes textId: Int): Builder {
|
||||||
|
delegate.setNegativeButton(textId, null)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
fun create() = StorageSelectDialog(delegate.create())
|
fun create() = StorageSelectDialog(delegate.create())
|
||||||
}
|
}
|
||||||
|
|
||||||
private class VolumesAdapter(context: Context): BaseAdapter() {
|
private class VolumesAdapter(context: Context) : BaseAdapter() {
|
||||||
|
|
||||||
private val volumes = getAvailableVolumes(context)
|
val volumes = getAvailableVolumes(context)
|
||||||
|
|
||||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||||
val view = convertView ?: parent.inflate(R.layout.item_storage)
|
val view = convertView ?: parent.inflate(R.layout.item_storage)
|
||||||
@@ -52,7 +69,7 @@ class StorageSelectDialog private constructor(private val delegate: AlertDialog)
|
|||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItem(position: Int): Any = volumes[position]
|
override fun getItem(position: Int): Pair<File, String> = volumes[position]
|
||||||
|
|
||||||
override fun getItemId(position: Int) = volumes[position].first.absolutePath.longHashCode()
|
override fun getItemId(position: Int) = volumes[position].first.absolutePath.longHashCode()
|
||||||
|
|
||||||
@@ -60,15 +77,17 @@ class StorageSelectDialog private constructor(private val delegate: AlertDialog)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface OnStorageSelectListener {
|
||||||
|
|
||||||
|
fun onStorageSelected(file: File)
|
||||||
|
}
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getAvailableVolumes(context: Context): List<Pair<File,String>> = context.getExternalFilesDirs(null).mapNotNull {
|
fun getAvailableVolumes(context: Context): List<Pair<File, String>> {
|
||||||
val root = it.findParent { x -> x.name == "Android" }?.parentFile ?: return@mapNotNull null
|
return LocalMangaRepository.getAvailableStorageDirs(context).map {
|
||||||
root to when {
|
it to it.getStorageName(context)
|
||||||
Environment.isExternalStorageEmulated(root) -> context.getString(R.string.internal_storage)
|
|
||||||
Environment.isExternalStorageRemovable(root) -> context.getString(R.string.external_storage)
|
|
||||||
else -> root.name
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ class DownloadService : BaseService() {
|
|||||||
|
|
||||||
private val okHttp by inject<OkHttpClient>()
|
private val okHttp by inject<OkHttpClient>()
|
||||||
private val cache by inject<PagesCache>()
|
private val cache by inject<PagesCache>()
|
||||||
|
private val settings by inject<AppSettings>()
|
||||||
private val jobs = HashMap<Int, Job>()
|
private val jobs = HashMap<Int, Job>()
|
||||||
private val mutex = Mutex()
|
private val mutex = Mutex()
|
||||||
|
|
||||||
@@ -80,7 +81,8 @@ class DownloadService : BaseService() {
|
|||||||
notification.setCancelId(startId)
|
notification.setCancelId(startId)
|
||||||
startForeground(DownloadNotification.NOTIFICATION_ID, notification())
|
startForeground(DownloadNotification.NOTIFICATION_ID, notification())
|
||||||
}
|
}
|
||||||
val destination = getExternalFilesDir("manga")!!
|
val destination = settings.getStorageDir(this@DownloadService)
|
||||||
|
checkNotNull(destination) { getString(R.string.cannot_find_available_storage) }
|
||||||
var output: MangaZip? = null
|
var output: MangaZip? = null
|
||||||
try {
|
try {
|
||||||
val repo = MangaProviderFactory.create(manga.source)
|
val repo = MangaProviderFactory.create(manga.source)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException
|
|||||||
import org.koitharu.kotatsu.core.model.Manga
|
import org.koitharu.kotatsu.core.model.Manga
|
||||||
import org.koitharu.kotatsu.core.model.MangaSource
|
import org.koitharu.kotatsu.core.model.MangaSource
|
||||||
import org.koitharu.kotatsu.core.parser.LocalMangaRepository
|
import org.koitharu.kotatsu.core.parser.LocalMangaRepository
|
||||||
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
import org.koitharu.kotatsu.domain.MangaProviderFactory
|
import org.koitharu.kotatsu.domain.MangaProviderFactory
|
||||||
import org.koitharu.kotatsu.domain.history.HistoryRepository
|
import org.koitharu.kotatsu.domain.history.HistoryRepository
|
||||||
import org.koitharu.kotatsu.ui.common.BasePresenter
|
import org.koitharu.kotatsu.ui.common.BasePresenter
|
||||||
@@ -64,7 +65,7 @@ class LocalListPresenter : BasePresenter<MangaListView<File>>() {
|
|||||||
if (!LocalMangaRepository.isFileSupported(name)) {
|
if (!LocalMangaRepository.isFileSupported(name)) {
|
||||||
throw UnsupportedFileException("Unsupported file on $uri")
|
throw UnsupportedFileException("Unsupported file on $uri")
|
||||||
}
|
}
|
||||||
val dest = context.getExternalFilesDir("manga")?.sub(name)
|
val dest = get<AppSettings>().getStorageDir(context)?.sub(name)
|
||||||
?: throw IOException("External files dir unavailable")
|
?: throw IOException("External files dir unavailable")
|
||||||
context.contentResolver.openInputStream(uri)?.use { source ->
|
context.contentResolver.openInputStream(uri)?.use { source ->
|
||||||
dest.outputStream().use { output ->
|
dest.outputStream().use { output ->
|
||||||
|
|||||||
@@ -16,13 +16,17 @@ import org.koitharu.kotatsu.R
|
|||||||
import org.koitharu.kotatsu.core.model.MangaSource
|
import org.koitharu.kotatsu.core.model.MangaSource
|
||||||
import org.koitharu.kotatsu.core.prefs.ListMode
|
import org.koitharu.kotatsu.core.prefs.ListMode
|
||||||
import org.koitharu.kotatsu.ui.common.BasePreferenceFragment
|
import org.koitharu.kotatsu.ui.common.BasePreferenceFragment
|
||||||
|
import org.koitharu.kotatsu.ui.common.dialog.StorageSelectDialog
|
||||||
import org.koitharu.kotatsu.ui.main.list.ListModeSelectDialog
|
import org.koitharu.kotatsu.ui.main.list.ListModeSelectDialog
|
||||||
import org.koitharu.kotatsu.ui.settings.utils.MultiSummaryProvider
|
import org.koitharu.kotatsu.ui.settings.utils.MultiSummaryProvider
|
||||||
import org.koitharu.kotatsu.ui.tracker.TrackWorker
|
import org.koitharu.kotatsu.ui.tracker.TrackWorker
|
||||||
|
import org.koitharu.kotatsu.utils.ext.getStorageName
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
|
||||||
class MainSettingsFragment : BasePreferenceFragment(R.string.settings),
|
class MainSettingsFragment : BasePreferenceFragment(R.string.settings),
|
||||||
SharedPreferences.OnSharedPreferenceChangeListener {
|
SharedPreferences.OnSharedPreferenceChangeListener,
|
||||||
|
StorageSelectDialog.OnStorageSelectListener {
|
||||||
|
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
addPreferencesFromResource(R.xml.pref_main)
|
addPreferencesFromResource(R.xml.pref_main)
|
||||||
@@ -40,15 +44,25 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings),
|
|||||||
findPreference<Preference>(R.string.key_app_update_auto)?.run {
|
findPreference<Preference>(R.string.key_app_update_auto)?.run {
|
||||||
isVisible = AppUpdateService.isUpdateSupported(context)
|
isVisible = AppUpdateService.isUpdateSupported(context)
|
||||||
}
|
}
|
||||||
|
findPreference<Preference>(R.string.key_local_storage)?.run {
|
||||||
|
summary = settings.getStorageDir(context)?.getStorageName(context)
|
||||||
|
?: getString(R.string.not_available)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
|
||||||
when (key) {
|
when (key) {
|
||||||
getString(R.string.key_list_mode) -> findPreference<Preference>(R.string.key_list_mode)?.summary =
|
getString(R.string.key_list_mode) -> findPreference<Preference>(R.string.key_list_mode)?.summary =
|
||||||
LIST_MODES[settings.listMode]?.let(::getString)
|
LIST_MODES[settings.listMode]?.let(::getString)
|
||||||
getString(R.string.key_theme) -> {
|
getString(R.string.key_theme) -> {
|
||||||
AppCompatDelegate.setDefaultNightMode(settings.theme)
|
AppCompatDelegate.setDefaultNightMode(settings.theme)
|
||||||
}
|
}
|
||||||
|
getString(R.string.key_local_storage) -> {
|
||||||
|
findPreference<Preference>(R.string.key_local_storage)?.run {
|
||||||
|
summary = settings.getStorageDir(context)?.getStorageName(context)
|
||||||
|
?: getString(R.string.not_available)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,10 +103,23 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings),
|
|||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
getString(R.string.key_local_storage) -> {
|
||||||
|
val ctx = context ?: return false
|
||||||
|
StorageSelectDialog.Builder(ctx, settings.getStorageDir(ctx),this)
|
||||||
|
.setTitle(preference.title)
|
||||||
|
.setNegativeButton(android.R.string.cancel)
|
||||||
|
.create()
|
||||||
|
.show()
|
||||||
|
true
|
||||||
|
}
|
||||||
else -> super.onPreferenceTreeClick(preference)
|
else -> super.onPreferenceTreeClick(preference)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onStorageSelected(file: File) {
|
||||||
|
settings.setStorageDir(context ?: return, file)
|
||||||
|
}
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
|
|
||||||
val LIST_MODES = arrayMapOf(
|
val LIST_MODES = arrayMapOf(
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
package org.koitharu.kotatsu.utils.ext
|
package org.koitharu.kotatsu.utils.ext
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Environment
|
||||||
|
import android.os.storage.StorageManager
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.zip.ZipEntry
|
import java.util.zip.ZipEntry
|
||||||
import java.util.zip.ZipFile
|
import java.util.zip.ZipFile
|
||||||
@@ -22,8 +27,22 @@ fun File.computeSize(): Long = listFiles()?.sumByLong { x ->
|
|||||||
|
|
||||||
inline fun File.findParent(predicate: (File) -> Boolean): File? {
|
inline fun File.findParent(predicate: (File) -> Boolean): File? {
|
||||||
var current = this
|
var current = this
|
||||||
while(!predicate(current)) {
|
while (!predicate(current)) {
|
||||||
current = current.parentFile ?: return null
|
current = current.parentFile ?: return null
|
||||||
}
|
}
|
||||||
return current
|
return current
|
||||||
|
}
|
||||||
|
|
||||||
|
fun File.getStorageName(context: Context): String {
|
||||||
|
val manager = context.getSystemService(Context.STORAGE_SERVICE) as StorageManager
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
manager.getStorageVolume(this)?.getDescription(context)?.let {
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return when {
|
||||||
|
Environment.isExternalStorageEmulated(this) -> context.getString(R.string.internal_storage)
|
||||||
|
Environment.isExternalStorageRemovable(this) -> context.getString(R.string.external_storage)
|
||||||
|
else -> context.getString(R.string.other_storage)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -2,34 +2,35 @@
|
|||||||
<LinearLayout
|
<LinearLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:minHeight="?listPreferredItemHeightLarge"
|
android:layout_height="wrap_content"
|
||||||
android:paddingStart="?listPreferredItemPaddingStart"
|
|
||||||
android:paddingEnd="?listPreferredItemPaddingEnd"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:background="?selectableItemBackground"
|
android:background="?selectableItemBackground"
|
||||||
android:layout_height="wrap_content">
|
android:gravity="center_vertical"
|
||||||
|
android:minHeight="?listPreferredItemHeightLarge"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="?listPreferredItemPaddingStart"
|
||||||
|
android:paddingTop="16dp"
|
||||||
|
android:paddingEnd="?listPreferredItemPaddingEnd"
|
||||||
|
android:paddingBottom="16dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/textView_title"
|
android:id="@+id/textView_title"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:maxLines="1"
|
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
tools:text="@tools:sample/lorem[3]"
|
android:maxLines="1"
|
||||||
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||||
android:textColor="?android:textColorPrimary"
|
android:textColor="?android:textColorPrimary"
|
||||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" />
|
tools:text="@tools:sample/lorem[3]" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/textView_subtitle"
|
android:id="@+id/textView_subtitle"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="6dp"
|
android:layout_marginTop="6dp"
|
||||||
android:maxLines="1"
|
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
|
||||||
android:textColor="?android:textColorSecondary"
|
android:textColor="?android:textColorSecondary"
|
||||||
tools:text="@tools:sample/lorem[3]"
|
tools:text="@tools:sample/lorem[20]" />
|
||||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -126,4 +126,8 @@
|
|||||||
<string name="manga_shelf">Полка с мангой</string>
|
<string name="manga_shelf">Полка с мангой</string>
|
||||||
<string name="recent_manga">Недавняя манга</string>
|
<string name="recent_manga">Недавняя манга</string>
|
||||||
<string name="pages_animation">Анимация листания</string>
|
<string name="pages_animation">Анимация листания</string>
|
||||||
|
<string name="manga_save_location">Место сохранения манги</string>
|
||||||
|
<string name="not_available">Недоступно</string>
|
||||||
|
<string name="cannot_find_available_storage">Не удалось найти ни одного доступного хранилища</string>
|
||||||
|
<string name="other_storage">Другое хранилище</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
<string name="key_search_history_clear">search_history_clear</string>
|
<string name="key_search_history_clear">search_history_clear</string>
|
||||||
<string name="key_grid_size">grid_size</string>
|
<string name="key_grid_size">grid_size</string>
|
||||||
<string name="key_remote_sources">remote_sources</string>
|
<string name="key_remote_sources">remote_sources</string>
|
||||||
|
<string name="key_local_storage">local_storage</string>
|
||||||
<string name="key_reader_switchers">reader_switchers</string>
|
<string name="key_reader_switchers">reader_switchers</string>
|
||||||
<string name="key_app_update">app_update</string>
|
<string name="key_app_update">app_update</string>
|
||||||
<string name="key_app_update_auto">app_update_auto</string>
|
<string name="key_app_update_auto">app_update_auto</string>
|
||||||
|
|||||||
@@ -127,4 +127,8 @@
|
|||||||
<string name="manga_shelf">Manga shelf</string>
|
<string name="manga_shelf">Manga shelf</string>
|
||||||
<string name="recent_manga">Recent manga</string>
|
<string name="recent_manga">Recent manga</string>
|
||||||
<string name="pages_animation">Pages animation</string>
|
<string name="pages_animation">Pages animation</string>
|
||||||
|
<string name="manga_save_location">Manga download location</string>
|
||||||
|
<string name="not_available">Not available</string>
|
||||||
|
<string name="cannot_find_available_storage">Cannot find any available storage</string>
|
||||||
|
<string name="other_storage">Other storage</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -38,6 +38,11 @@
|
|||||||
app:allowDividerAbove="true"
|
app:allowDividerAbove="true"
|
||||||
app:iconSpaceReserved="false" />
|
app:iconSpaceReserved="false" />
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:key="@string/key_local_storage"
|
||||||
|
android:title="@string/manga_save_location"
|
||||||
|
app:iconSpaceReserved="false" />
|
||||||
|
|
||||||
<PreferenceScreen
|
<PreferenceScreen
|
||||||
android:fragment="org.koitharu.kotatsu.ui.settings.HistorySettingsFragment"
|
android:fragment="org.koitharu.kotatsu.ui.settings.HistorySettingsFragment"
|
||||||
android:title="@string/history_and_cache"
|
android:title="@string/history_and_cache"
|
||||||
|
|||||||
Reference in New Issue
Block a user