Manga repository authorization support

This commit is contained in:
Koitharu
2021-09-08 07:27:25 +03:00
parent c4585c81e1
commit 593624fdb9
20 changed files with 312 additions and 78 deletions

View File

@@ -23,7 +23,9 @@
android:theme="@style/AppTheme" android:theme="@style/AppTheme"
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true"
tools:ignore="UnusedAttribute"> tools:ignore="UnusedAttribute">
<activity android:name="org.koitharu.kotatsu.main.ui.MainActivity"> <activity
android:name="org.koitharu.kotatsu.main.ui.MainActivity"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
@@ -32,12 +34,16 @@
android:name="android.app.default_searchable" android:name="android.app.default_searchable"
android:value=".ui.search.SearchActivity" /> android:value=".ui.search.SearchActivity" />
</activity> </activity>
<activity android:name="org.koitharu.kotatsu.details.ui.DetailsActivity"> <activity
android:name="org.koitharu.kotatsu.details.ui.DetailsActivity"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="${applicationId}.action.VIEW_MANGA" /> <action android:name="${applicationId}.action.VIEW_MANGA" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name="org.koitharu.kotatsu.reader.ui.ReaderActivity"> <activity
android:name="org.koitharu.kotatsu.reader.ui.ReaderActivity"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="${applicationId}.action.READ_MANGA" /> <action android:name="${applicationId}.action.READ_MANGA" />
</intent-filter> </intent-filter>
@@ -50,13 +56,19 @@
android:label="@string/settings" /> android:label="@string/settings" />
<activity <activity
android:name="org.koitharu.kotatsu.reader.ui.SimpleSettingsActivity" android:name="org.koitharu.kotatsu.reader.ui.SimpleSettingsActivity"
android:exported="true"
android:label="@string/settings"> android:label="@string/settings">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MANAGE_NETWORK_USAGE" /> <action android:name="android.intent.action.MANAGE_NETWORK_USAGE" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name="org.koitharu.kotatsu.browser.BrowserActivity" /> <activity
android:name="org.koitharu.kotatsu.browser.BrowserActivity"
android:windowSoftInputMode="adjustResize" />
<activity
android:name="org.koitharu.kotatsu.settings.sources.auth.SourceAuthActivity"
android:windowSoftInputMode="adjustResize" />
<activity <activity
android:name="org.koitharu.kotatsu.core.ui.CrashActivity" android:name="org.koitharu.kotatsu.core.ui.CrashActivity"
android:label="@string/error_occurred" android:label="@string/error_occurred"

View File

@@ -67,6 +67,23 @@ open class MangaLoaderContext(
}) })
} }
fun getCookies(domain: String): List<Cookie> {
val url = HttpUrl.Builder()
.scheme(SCHEME_HTTP)
.host(domain)
.build()
return cookieJar.loadForRequest(url)
}
fun copyCookies(oldDomain: String, newDomain: String) {
val url = HttpUrl.Builder()
.scheme(SCHEME_HTTP)
.host(oldDomain)
val cookies = cookieJar.loadForRequest(url.build())
url.host(newDomain)
cookieJar.saveFromResponse(url.build(), cookies)
}
private companion object { private companion object {
private const val SCHEME_HTTP = "http" private const val SCHEME_HTTP = "http"

View File

@@ -38,6 +38,7 @@ abstract class BaseFragment<B : ViewBinding> : Fragment(), OnApplyWindowInsetsLi
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
lastInsets = Insets.NONE
ViewCompat.setOnApplyWindowInsetsListener(view, this) ViewCompat.setOnApplyWindowInsetsListener(view, this)
} }

View File

@@ -1,10 +1,8 @@
package org.koitharu.kotatsu.browser package org.koitharu.kotatsu.browser
import android.graphics.Bitmap import android.graphics.Bitmap
import android.webkit.WebResourceResponse
import android.webkit.WebView import android.webkit.WebView
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.inject import org.koin.core.component.inject
import org.koitharu.kotatsu.core.network.WebViewClientCompat import org.koitharu.kotatsu.core.network.WebViewClientCompat
@@ -27,19 +25,4 @@ class BrowserClient(private val callback: BrowserCallback) : WebViewClientCompat
super.onPageCommitVisible(view, url) super.onPageCommitVisible(view, url)
callback.onTitleChanged(view.title.orEmpty(), url) callback.onTitleChanged(view.title.orEmpty(), url)
} }
override fun shouldInterceptRequestCompat(view: WebView, url: String): WebResourceResponse? {
return runCatching {
val request = Request.Builder()
.url(url)
.build()
val response = okHttp.newCall(request).execute()
val ct = response.body?.contentType()
WebResourceResponse(
"${ct?.type}/${ct?.subtype}",
ct?.charset()?.name() ?: "utf-8",
response.body?.byteStream()
)
}.getOrNull()
}
} }

View File

@@ -5,6 +5,6 @@ import kotlinx.parcelize.Parcelize
@Parcelize @Parcelize
data class MangaFilter( data class MangaFilter(
val sortOrder: SortOrder, val sortOrder: SortOrder?,
val tag: MangaTag? val tag: MangaTag?,
) : Parcelable ) : Parcelable

View File

@@ -0,0 +1,8 @@
package org.koitharu.kotatsu.core.parser
interface MangaRepositoryAuthProvider {
val authUrl: String
fun isAuthorized(): Boolean
}

View File

@@ -27,5 +27,6 @@ interface SourceSettings {
const val KEY_DOMAIN = "domain" const val KEY_DOMAIN = "domain"
const val KEY_USE_SSL = "ssl" const val KEY_USE_SSL = "ssl"
const val KEY_AUTH = "auth"
} }
} }

View File

@@ -7,7 +7,6 @@ import org.koitharu.kotatsu.core.model.MangaFilter
import org.koitharu.kotatsu.core.model.MangaTag import org.koitharu.kotatsu.core.model.MangaTag
import org.koitharu.kotatsu.core.model.SortOrder import org.koitharu.kotatsu.core.model.SortOrder
import java.util.* import java.util.*
import kotlin.collections.ArrayList
class FilterAdapter( class FilterAdapter(
sortOrders: List<SortOrder> = emptyList(), sortOrders: List<SortOrder> = emptyList(),
@@ -19,7 +18,7 @@ class FilterAdapter(
private val sortOrders = ArrayList<SortOrder>(sortOrders) private val sortOrders = ArrayList<SortOrder>(sortOrders)
private val tags = ArrayList(Collections.singletonList(null) + tags) private val tags = ArrayList(Collections.singletonList(null) + tags)
private var currentState = state ?: MangaFilter(sortOrders.first(), null) private var currentState = state ?: MangaFilter(sortOrders.firstOrNull(), null)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = when (viewType) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = when (viewType) {
VIEW_TYPE_SORT -> FilterSortHolder(parent).apply { VIEW_TYPE_SORT -> FilterSortHolder(parent).apply {

View File

@@ -61,7 +61,7 @@ class LocalListViewModel(
launchLoadingJob(Dispatchers.Default) { launchLoadingJob(Dispatchers.Default) {
try { try {
listError.value = null listError.value = null
mangaList.value = repository.getList(0) mangaList.value = repository.getList(0, tags = null)
} catch (e: Throwable) { } catch (e: Throwable) {
listError.value = e listError.value = e
} }

View File

@@ -3,16 +3,19 @@ package org.koitharu.kotatsu.reader.ui
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.commit import androidx.fragment.app.commit
import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.databinding.ActivitySettingsSimpleBinding import org.koitharu.kotatsu.databinding.ActivitySettingsSimpleBinding
import org.koitharu.kotatsu.settings.MainSettingsFragment import org.koitharu.kotatsu.settings.MainSettingsFragment
import org.koitharu.kotatsu.settings.NetworkSettingsFragment import org.koitharu.kotatsu.settings.NetworkSettingsFragment
import org.koitharu.kotatsu.settings.ReaderSettingsFragment import org.koitharu.kotatsu.settings.ReaderSettingsFragment
import org.koitharu.kotatsu.settings.SourceSettingsFragment
class SimpleSettingsActivity : BaseActivity<ActivitySettingsSimpleBinding>() { class SimpleSettingsActivity : BaseActivity<ActivitySettingsSimpleBinding>() {
@@ -25,6 +28,9 @@ class SimpleSettingsActivity : BaseActivity<ActivitySettingsSimpleBinding>() {
R.id.container, when (intent?.action) { R.id.container, when (intent?.action) {
Intent.ACTION_MANAGE_NETWORK_USAGE -> NetworkSettingsFragment() Intent.ACTION_MANAGE_NETWORK_USAGE -> NetworkSettingsFragment()
ACTION_READER -> ReaderSettingsFragment() ACTION_READER -> ReaderSettingsFragment()
ACTION_SOURCE -> SourceSettingsFragment.newInstance(
intent.getParcelableExtra(EXTRA_SOURCE) ?: MangaSource.LOCAL
)
else -> MainSettingsFragment() else -> MainSettingsFragment()
} }
) )
@@ -43,9 +49,17 @@ class SimpleSettingsActivity : BaseActivity<ActivitySettingsSimpleBinding>() {
private const val ACTION_READER = private const val ACTION_READER =
"${BuildConfig.APPLICATION_ID}.action.MANAGE_READER_SETTINGS" "${BuildConfig.APPLICATION_ID}.action.MANAGE_READER_SETTINGS"
private const val ACTION_SOURCE =
"${BuildConfig.APPLICATION_ID}.action.MANAGE_SOURCE_SETTINGS"
private const val EXTRA_SOURCE = "source"
fun newReaderSettingsIntent(context: Context) = fun newReaderSettingsIntent(context: Context) =
Intent(context, SimpleSettingsActivity::class.java) Intent(context, SimpleSettingsActivity::class.java)
.setAction(ACTION_READER) .setAction(ACTION_READER)
fun newSourceSettingsIntent(context: Context, source: MangaSource) =
Intent(context, SimpleSettingsActivity::class.java)
.setAction(ACTION_SOURCE)
.putExtra(EXTRA_SOURCE, source as Parcelable)
} }
} }

View File

@@ -1,10 +1,15 @@
package org.koitharu.kotatsu.remotelist.ui package org.koitharu.kotatsu.remotelist.ui
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaFilter import org.koitharu.kotatsu.core.model.MangaFilter
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.list.ui.MangaListFragment import org.koitharu.kotatsu.list.ui.MangaListFragment
import org.koitharu.kotatsu.reader.ui.SimpleSettingsActivity
import org.koitharu.kotatsu.utils.ext.parcelableArgument import org.koitharu.kotatsu.utils.ext.parcelableArgument
import org.koitharu.kotatsu.utils.ext.withArgs import org.koitharu.kotatsu.utils.ext.withArgs
@@ -29,6 +34,26 @@ class RemoteListFragment : MangaListFragment() {
super.onFilterChanged(filter) super.onFilterChanged(filter)
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.opt_list_remote, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_source_settings -> {
startActivity(
SimpleSettingsActivity.newSourceSettingsIntent(
context ?: return false,
source,
)
)
true
}
else -> super.onOptionsItemSelected(item)
}
}
companion object { companion object {
private const val ARG_SOURCE = "provider" private const val ARG_SOURCE = "provider"

View File

@@ -90,6 +90,9 @@ class RemoteListViewModel(
} }
hasNextPage.value = list.isNotEmpty() hasNextPage.value = list.isNotEmpty()
} catch (e: Throwable) { } catch (e: Throwable) {
if (BuildConfig.DEBUG) {
e.printStackTrace()
}
listError.value = e listError.value = e
} }
} }

View File

@@ -12,7 +12,6 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.list.ui.MangaListViewModel import org.koitharu.kotatsu.list.ui.MangaListViewModel
import org.koitharu.kotatsu.list.ui.model.* import org.koitharu.kotatsu.list.ui.model.*
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import java.util.*
class SearchViewModel( class SearchViewModel(
private val repository: MangaRepository, private val repository: MangaRepository,
@@ -74,6 +73,7 @@ class SearchViewModel(
listError.value = null listError.value = null
val list = repository.getList( val list = repository.getList(
offset = if (append) mangaList.value?.size ?: 0 else 0, offset = if (append) mangaList.value?.size ?: 0 else 0,
tags = null,
query = query query = query
) )
if (!append) { if (!append) {

View File

@@ -76,15 +76,7 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
true true
} }
AppSettings.KEY_COOKIES_CLEAR -> { AppSettings.KEY_COOKIES_CLEAR -> {
viewLifecycleScope.launch { clearCookies()
val cookieJar = get<AndroidCookieJar>()
cookieJar.clear()
Snackbar.make(
listView ?: return@launch,
R.string.cookies_cleared,
Snackbar.LENGTH_SHORT
).show()
}
true true
} }
AppSettings.KEY_SEARCH_HISTORY_CLEAR -> { AppSettings.KEY_SEARCH_HISTORY_CLEAR -> {
@@ -144,4 +136,22 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
} }
}.show() }.show()
} }
private fun clearCookies() {
AlertDialog.Builder(context ?: return)
.setTitle(R.string.clear_cookies)
.setMessage(R.string.text_clear_cookies_prompt)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.clear) { _, _ ->
viewLifecycleScope.launch {
val cookieJar = get<AndroidCookieJar>()
cookieJar.clear()
Snackbar.make(
listView ?: return@launch,
R.string.cookies_cleared,
Snackbar.LENGTH_SHORT
).show()
}
}.show()
}
} }

View File

@@ -9,8 +9,10 @@ import androidx.preference.PreferenceFragmentCompat
import androidx.preference.TwoStatePreference import androidx.preference.TwoStatePreference
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.parser.MangaRepositoryAuthProvider
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.prefs.SourceSettings import org.koitharu.kotatsu.core.prefs.SourceSettings
import org.koitharu.kotatsu.settings.sources.auth.SourceAuthActivity
import org.koitharu.kotatsu.settings.utils.EditTextBindListener import org.koitharu.kotatsu.settings.utils.EditTextBindListener
import org.koitharu.kotatsu.settings.utils.EditTextDefaultSummaryProvider import org.koitharu.kotatsu.settings.utils.EditTextDefaultSummaryProvider
import org.koitharu.kotatsu.utils.ext.mangaRepositoryOf import org.koitharu.kotatsu.utils.ext.mangaRepositoryOf
@@ -20,6 +22,7 @@ import org.koitharu.kotatsu.utils.ext.withArgs
class SourceSettingsFragment : PreferenceFragmentCompat() { class SourceSettingsFragment : PreferenceFragmentCompat() {
private val source by parcelableArgument<MangaSource>(EXTRA_SOURCE) private val source by parcelableArgument<MangaSource>(EXTRA_SOURCE)
private var repository: RemoteMangaRepository? = null
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
@@ -29,6 +32,7 @@ class SourceSettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
preferenceManager.sharedPreferencesName = source.name preferenceManager.sharedPreferencesName = source.name
val repo = mangaRepositoryOf(source) as? RemoteMangaRepository ?: return val repo = mangaRepositoryOf(source) as? RemoteMangaRepository ?: return
repository = repo
addPreferencesFromResource(R.xml.pref_source) addPreferencesFromResource(R.xml.pref_source)
val screen = preferenceScreen val screen = preferenceScreen
val prefsMap = ArrayMap<String, Any>(screen.preferenceCount) val prefsMap = ArrayMap<String, Any>(screen.preferenceCount)
@@ -41,13 +45,32 @@ class SourceSettingsFragment : PreferenceFragmentCompat() {
initPreferenceWithDefaultValue(pref, defValue) initPreferenceWithDefaultValue(pref, defValue)
} }
} }
findPreference<Preference>(SourceSettings.KEY_AUTH)?.run {
isVisible = repo is MangaRepositoryAuthProvider
isEnabled = (repo as? MangaRepositoryAuthProvider)?.isAuthorized() == false
}
}
override fun onPreferenceTreeClick(preference: Preference?): Boolean {
return when (preference?.key) {
SourceSettings.KEY_AUTH -> {
startActivity(
SourceAuthActivity.newIntent(
context ?: return false,
source,
)
)
true
}
else -> super.onPreferenceTreeClick(preference)
}
} }
private fun initPreferenceWithDefaultValue(preference: Preference, defaultValue: Any) { private fun initPreferenceWithDefaultValue(preference: Preference, defaultValue: Any) {
when(preference) { when (preference) {
is EditTextPreference -> { is EditTextPreference -> {
preference.summaryProvider = EditTextDefaultSummaryProvider(defaultValue.toString()) preference.summaryProvider = EditTextDefaultSummaryProvider(defaultValue.toString())
when(preference.key) { when (preference.key) {
SourceSettings.KEY_DOMAIN -> preference.setOnBindEditTextListener( SourceSettings.KEY_DOMAIN -> preference.setOnBindEditTextListener(
EditTextBindListener( EditTextBindListener(
EditorInfo.TYPE_CLASS_TEXT or EditorInfo.TYPE_TEXT_VARIATION_URI, EditorInfo.TYPE_CLASS_TEXT or EditorInfo.TYPE_TEXT_VARIATION_URI,

View File

@@ -0,0 +1,114 @@
package org.koitharu.kotatsu.settings.sources.auth
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import android.view.MenuItem
import android.widget.Toast
import androidx.core.graphics.Insets
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.browser.BrowserCallback
import org.koitharu.kotatsu.browser.BrowserClient
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.parser.MangaRepositoryAuthProvider
import org.koitharu.kotatsu.databinding.ActivityBrowserBinding
import org.koitharu.kotatsu.utils.ext.mangaRepositoryOf
class SourceAuthActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback {
private lateinit var repository: MangaRepositoryAuthProvider
@SuppressLint("SetJavaScriptEnabled")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(ActivityBrowserBinding.inflate(layoutInflater))
val source = intent?.getParcelableExtra<MangaSource>(EXTRA_SOURCE)
if (source == null) {
finish()
return
}
repository = mangaRepositoryOf(source) as? MangaRepositoryAuthProvider ?: run {
Toast.makeText(
this,
getString(R.string.auth_not_supported_by, source.title),
Toast.LENGTH_SHORT
).show()
finishAfterTransition()
return
}
supportActionBar?.run {
setDisplayHomeAsUpEnabled(true)
setHomeAsUpIndicator(R.drawable.ic_cross)
}
with(binding.webView.settings) {
javaScriptEnabled = true
}
binding.webView.webViewClient = BrowserClient(this)
val url = repository.authUrl
onTitleChanged(
source.title,
getString(R.string.loading_)
)
binding.webView.loadUrl(url)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
android.R.id.home -> {
binding.webView.stopLoading()
finishAfterTransition()
true
}
else -> super.onOptionsItemSelected(item)
}
override fun onBackPressed() {
if (binding.webView.canGoBack()) {
binding.webView.goBack()
} else {
super.onBackPressed()
}
}
override fun onPause() {
binding.webView.onPause()
super.onPause()
}
override fun onResume() {
super.onResume()
binding.webView.onResume()
}
override fun onLoadingStateChanged(isLoading: Boolean) {
binding.progressBar.isVisible = isLoading
if (!isLoading && repository.isAuthorized()) {
Toast.makeText(this, R.string.auth_complete, Toast.LENGTH_SHORT).show()
finishAfterTransition()
}
}
override fun onTitleChanged(title: CharSequence, subtitle: CharSequence?) {
this.title = title
supportActionBar?.subtitle = subtitle
}
override fun onWindowInsetsChanged(insets: Insets) {
binding.appbar.updatePadding(top = insets.top)
binding.webView.updatePadding(bottom = insets.bottom)
}
companion object {
private const val EXTRA_SOURCE = "source"
fun newIntent(context: Context, source: MangaSource): Intent {
return Intent(context, SourceAuthActivity::class.java)
.putExtra(EXTRA_SOURCE, source as Parcelable)
}
}
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_source_settings"
android:orderInCategory="50"
android:title="@string/settings"
app:showAsAction="never" />
</menu>

View File

@@ -213,24 +213,27 @@
<string name="other">Другие</string> <string name="other">Другие</string>
<string name="description">Описание</string> <string name="description">Описание</string>
<string name="languages">Языки</string> <string name="languages">Языки</string>
<string name="welcome">Добро пожаловать</string> <string name="welcome">Добро пожаловать</string>
<string name="text_clear_search_history_prompt">Вы действительно хотите удалить все недавние поисковые запросы? Это действие не может быть отменено.</string> <string name="text_clear_search_history_prompt">Вы действительно хотите удалить все недавние поисковые запросы? Это действие не может быть отменено.</string>
<string name="backup_saved">Резервная копия успешно сохранена</string> <string name="backup_saved">Резервная копия успешно сохранена</string>
<string name="tracker_warning">Некоторые производители могут изменять поведение системы, нарушая работу фоновых задач.</string> <string name="tracker_warning">Некоторые производители могут изменять поведение системы, нарушая работу фоновых задач.</string>
<string name="read_more">Подробнее</string> <string name="read_more">Подробнее</string>
<string name="queued">В очереди</string> <string name="queued">В очереди</string>
<string name="text_downloads_holder">На данный момент нет активных загрузок</string> <string name="text_downloads_holder">На данный момент нет активных загрузок</string>
<string name="chapter_is_missing">Глава отсутствует</string> <string name="chapter_is_missing">Глава отсутствует</string>
<string name="chapter_is_missing_text">Эта глава отсутствует на вашем устройстве. Скачайте или прочитайте её онлайн.</string> <string name="chapter_is_missing_text">Эта глава отсутствует на вашем устройстве. Скачайте или прочитайте её онлайн.</string>
<string name="about_app_translation_summary">Помочь с переводом приложения</string> <string name="about_app_translation_summary">Помочь с переводом приложения</string>
<string name="about_app_translation">Перевод</string> <string name="about_app_translation">Перевод</string>
<string name="about_author">Автор</string> <string name="about_author">Автор</string>
<string name="about_feedback_4pda">Тема на 4PDA</string> <string name="about_feedback_4pda">Тема на 4PDA</string>
<string name="about_feedback">Обратная связь</string> <string name="about_feedback">Обратная связь</string>
<string name="about_support_developer">Поддержать разработчика</string> <string name="about_support_developer">Поддержать разработчика</string>
<string name="about_support_developer_summary">Если вам нравится это приложение, вы можете помочь финансово с помощью ЮMoney (бывш. Яндекс.Деньги)</string> <string name="about_support_developer_summary">Если вам нравится это приложение, вы можете помочь финансово с помощью ЮMoney (бывш. Яндекс.Деньги)</string>
<string name="about_gratitudes">Благодарности</string> <string name="about_gratitudes">Благодарности</string>
<string name="about_gratitudes_summary">Эти люди помогают Kotatsu стать лучше!</string> <string name="about_gratitudes_summary">Эти люди помогают Kotatsu стать лучше!</string>
<string name="about_copyright_and_licenses">Авторские права и лицензии</string> <string name="about_copyright_and_licenses">Авторские права и лицензии</string>
<string name="about_license">Лицензия</string> <string name="about_license">Лицензия</string>
<string name="auth_complete">Авторизация выполнена</string>
<string name="auth_not_supported_by">Авторизация в %s не поддерживается</string>
<string name="text_clear_cookies_prompt">Вы выйдете из всех источников, в которых Вы авторизованы</string>
</resources> </resources>

View File

@@ -216,24 +216,27 @@
<string name="text_clear_search_history_prompt">Do you really want to remove all recent search queries? This action cannot be undone.</string> <string name="text_clear_search_history_prompt">Do you really want to remove all recent search queries? This action cannot be undone.</string>
<string name="other">Other</string> <string name="other">Other</string>
<string name="languages">Languages</string> <string name="languages">Languages</string>
<string name="welcome">Welcome</string> <string name="welcome">Welcome</string>
<string name="description">Description</string> <string name="description">Description</string>
<string name="backup_saved">Backup saved successfully</string> <string name="backup_saved">Backup saved successfully</string>
<string name="tracker_warning">Some manufacturers can change the system behavior, which may breaks background tasks.</string> <string name="tracker_warning">Some manufacturers can change the system behavior, which may breaks background tasks.</string>
<string name="read_more">Read more</string> <string name="read_more">Read more</string>
<string name="queued">Queued</string> <string name="queued">Queued</string>
<string name="text_downloads_holder">There are currently no active downloads</string> <string name="text_downloads_holder">There are currently no active downloads</string>
<string name="chapter_is_missing_text">This chapter is missing on your device. Download or read it online.</string> <string name="chapter_is_missing_text">This chapter is missing on your device. Download or read it online.</string>
<string name="chapter_is_missing">Chapter is missing</string> <string name="chapter_is_missing">Chapter is missing</string>
<string name="about_app_translation_summary">Translate this app</string> <string name="about_app_translation_summary">Translate this app</string>
<string name="about_app_translation">Translation</string> <string name="about_app_translation">Translation</string>
<string name="about_author">Author</string> <string name="about_author">Author</string>
<string name="about_feedback">Feedback</string> <string name="about_feedback">Feedback</string>
<string name="about_feedback_4pda">Topic on 4PDA</string> <string name="about_feedback_4pda">Topic on 4PDA</string>
<string name="about_support_developer">Support the developer</string> <string name="about_support_developer">Support the developer</string>
<string name="about_support_developer_summary">If you like this app, you can help financially through Yoomoney (ex. Yandex.Money)</string> <string name="about_support_developer_summary">If you like this app, you can help financially through Yoomoney (ex. Yandex.Money)</string>
<string name="about_gratitudes">Gratitudes</string> <string name="about_gratitudes">Gratitudes</string>
<string name="about_gratitudes_summary">These people make Kotatsu become better!</string> <string name="about_gratitudes_summary">These people make Kotatsu become better!</string>
<string name="about_copyright_and_licenses">Copyright &amp; Licenses</string> <string name="about_copyright_and_licenses">Copyright &amp; Licenses</string>
<string name="about_license">License</string> <string name="about_license">License</string>
<string name="auth_complete">Authorization complete</string>
<string name="auth_not_supported_by">Authorization on %s is not supported</string>
<string name="text_clear_cookies_prompt">You will be logged out from all sources that you are authorized in</string>
</resources> </resources>

View File

@@ -14,4 +14,11 @@
android:title="@string/use_ssl" android:title="@string/use_ssl"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<Preference
android:key="auth"
android:persistent="false"
android:title="@string/sign_in"
app:allowDividerAbove="true"
app:iconSpaceReserved="false" />
</PreferenceScreen> </PreferenceScreen>