diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f70692fb5..0e760fca5 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -95,6 +95,7 @@
+
getString(R.string.disabled)
}
}
+ findPreference(AppSettings.KEY_SYNC_SETTINGS)?.isEnabled = account != null
}
}
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/SettingsActivity.kt b/app/src/main/java/org/koitharu/kotatsu/settings/SettingsActivity.kt
index ad800fcd4..5cecec9ab 100644
--- a/app/src/main/java/org/koitharu/kotatsu/settings/SettingsActivity.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/settings/SettingsActivity.kt
@@ -137,6 +137,7 @@ class SettingsActivity :
Intent.ACTION_VIEW -> {
when (intent.data?.host) {
HOST_ABOUT -> AboutSettingsFragment()
+ HOST_SYNC_SETTINGS -> SyncSettingsFragment()
else -> SettingsHeadersFragment()
}
}
@@ -159,6 +160,7 @@ class SettingsActivity :
private const val ACTION_MANAGE_SOURCES = "${BuildConfig.APPLICATION_ID}.action.MANAGE_SOURCES_LIST"
private const val EXTRA_SOURCE = "source"
private const val HOST_ABOUT = "about"
+ private const val HOST_SYNC_SETTINGS = "sync-settings"
fun newIntent(context: Context) = Intent(context, SettingsActivity::class.java)
diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/SyncSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/SyncSettingsFragment.kt
new file mode 100644
index 000000000..530b8d0ea
--- /dev/null
+++ b/app/src/main/java/org/koitharu/kotatsu/settings/SyncSettingsFragment.kt
@@ -0,0 +1,49 @@
+package org.koitharu.kotatsu.settings
+
+import android.os.Bundle
+import android.view.View
+import androidx.fragment.app.FragmentResultListener
+import androidx.preference.Preference
+import dagger.hilt.android.AndroidEntryPoint
+import org.koitharu.kotatsu.R
+import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
+import org.koitharu.kotatsu.sync.data.SyncSettings
+import org.koitharu.kotatsu.sync.ui.SyncHostDialogFragment
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class SyncSettingsFragment : BasePreferenceFragment(R.string.sync_settings), FragmentResultListener {
+
+ @Inject
+ lateinit var syncSettings: SyncSettings
+
+ override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+ addPreferencesFromResource(R.xml.pref_sync)
+ bindHostSummary()
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ childFragmentManager.setFragmentResultListener(SyncHostDialogFragment.REQUEST_KEY, viewLifecycleOwner, this)
+ }
+
+ override fun onPreferenceTreeClick(preference: Preference): Boolean {
+ return when (preference.key) {
+ SyncSettings.KEY_HOST -> {
+ SyncHostDialogFragment.show(childFragmentManager)
+ true
+ }
+
+ else -> super.onPreferenceTreeClick(preference)
+ }
+ }
+
+ override fun onFragmentResult(requestKey: String, result: Bundle) {
+ bindHostSummary()
+ }
+
+ private fun bindHostSummary() {
+ val preference = findPreference(SyncSettings.KEY_HOST) ?: return
+ preference.summary = syncSettings.host
+ }
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/utils/AutoCompleteTextViewPreference.kt b/app/src/main/java/org/koitharu/kotatsu/settings/utils/AutoCompleteTextViewPreference.kt
index fe1d3f15c..378133c58 100644
--- a/app/src/main/java/org/koitharu/kotatsu/settings/utils/AutoCompleteTextViewPreference.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/settings/utils/AutoCompleteTextViewPreference.kt
@@ -10,6 +10,7 @@ import android.widget.EditText
import androidx.annotation.ArrayRes
import androidx.annotation.AttrRes
import androidx.annotation.StyleRes
+import androidx.core.content.withStyledAttributes
import androidx.preference.EditTextPreference
import org.koitharu.kotatsu.R
@@ -25,6 +26,12 @@ class AutoCompleteTextViewPreference @JvmOverloads constructor(
init {
super.setOnBindEditTextListener(autoCompleteBindListener)
+ context.withStyledAttributes(attrs, R.styleable.AutoCompleteTextViewPreference, defStyleAttr, defStyleRes) {
+ val entriesId = getResourceId(R.styleable.AutoCompleteTextViewPreference_android_entries, 0)
+ if (entriesId != 0) {
+ setEntries(entriesId)
+ }
+ }
}
fun setEntries(@ArrayRes arrayResId: Int) {
@@ -55,4 +62,4 @@ class AutoCompleteTextViewPreference @JvmOverloads constructor(
}
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/sync/data/SyncAuthApi.kt b/app/src/main/java/org/koitharu/kotatsu/sync/data/SyncAuthApi.kt
index 69d2e9844..55f983967 100644
--- a/app/src/main/java/org/koitharu/kotatsu/sync/data/SyncAuthApi.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/sync/data/SyncAuthApi.kt
@@ -1,11 +1,9 @@
package org.koitharu.kotatsu.sync.data
-import android.content.Context
-import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.Reusable
import okhttp3.OkHttpClient
import okhttp3.Request
import org.json.JSONObject
-import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.SyncApiException
import org.koitharu.kotatsu.parsers.util.await
import org.koitharu.kotatsu.parsers.util.parseJson
@@ -13,19 +11,17 @@ import org.koitharu.kotatsu.parsers.util.removeSurrounding
import org.koitharu.kotatsu.utils.ext.toRequestBody
import javax.inject.Inject
+@Reusable
class SyncAuthApi @Inject constructor(
- @ApplicationContext context: Context,
private val okHttpClient: OkHttpClient,
) {
- private val baseUrl = context.getString(R.string.url_sync_server)
-
- suspend fun authenticate(email: String, password: String): String {
+ suspend fun authenticate(host: String, email: String, password: String): String {
val body = JSONObject(
mapOf("email" to email, "password" to password),
).toRequestBody()
val request = Request.Builder()
- .url("$baseUrl/auth")
+ .url("http://$host/auth")
.post(body)
.build()
val response = okHttpClient.newCall(request).await()
diff --git a/app/src/main/java/org/koitharu/kotatsu/sync/data/SyncAuthenticator.kt b/app/src/main/java/org/koitharu/kotatsu/sync/data/SyncAuthenticator.kt
index 02ab48f3f..c37809ecf 100644
--- a/app/src/main/java/org/koitharu/kotatsu/sync/data/SyncAuthenticator.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/sync/data/SyncAuthenticator.kt
@@ -13,6 +13,7 @@ import org.koitharu.kotatsu.R
class SyncAuthenticator(
context: Context,
private val account: Account,
+ private val syncSettings: SyncSettings,
private val authApi: SyncAuthApi,
) : Authenticator {
@@ -30,6 +31,7 @@ class SyncAuthenticator(
private fun tryRefreshToken() = runCatching {
runBlocking {
authApi.authenticate(
+ syncSettings.host,
account.name,
accountManager.getPassword(account),
)
diff --git a/app/src/main/java/org/koitharu/kotatsu/sync/data/SyncSettings.kt b/app/src/main/java/org/koitharu/kotatsu/sync/data/SyncSettings.kt
new file mode 100644
index 000000000..cee53bdb0
--- /dev/null
+++ b/app/src/main/java/org/koitharu/kotatsu/sync/data/SyncSettings.kt
@@ -0,0 +1,46 @@
+package org.koitharu.kotatsu.sync.data
+
+import android.accounts.Account
+import android.accounts.AccountManager
+import android.content.Context
+import androidx.annotation.WorkerThread
+import dagger.Reusable
+import dagger.hilt.android.qualifiers.ApplicationContext
+import org.koitharu.kotatsu.R
+import org.koitharu.kotatsu.utils.ext.ifNullOrEmpty
+import javax.inject.Inject
+
+@Reusable
+class SyncSettings(
+ context: Context,
+ private val account: Account?,
+) {
+
+ @Inject
+ constructor(@ApplicationContext context: Context) : this(
+ context,
+ AccountManager.get(context)
+ .getAccountsByType(context.getString(R.string.account_type_sync))
+ .firstOrNull(),
+ )
+
+ private val accountManager = AccountManager.get(context)
+ private val defaultHost = context.getString(R.string.sync_host_default)
+
+ @get:WorkerThread
+ @set:WorkerThread
+ var host: String
+ get() = account?.let {
+ accountManager.getUserData(it, KEY_HOST)
+ }.ifNullOrEmpty { defaultHost }
+ set(value) {
+ account?.let {
+ accountManager.setUserData(it, KEY_HOST, value)
+ }
+ }
+
+ companion object {
+
+ const val KEY_HOST = "host"
+ }
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncAuthResult.kt b/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncAuthResult.kt
index e16e1241c..b1581e1d0 100644
--- a/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncAuthResult.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncAuthResult.kt
@@ -1,6 +1,7 @@
package org.koitharu.kotatsu.sync.domain
class SyncAuthResult(
+ val host: String,
val email: String,
val password: String,
val token: String,
@@ -12,17 +13,17 @@ class SyncAuthResult(
other as SyncAuthResult
+ if (host != other.host) return false
if (email != other.email) return false
if (password != other.password) return false
- if (token != other.token) return false
-
- return true
+ return token == other.token
}
override fun hashCode(): Int {
- var result = email.hashCode()
+ var result = host.hashCode()
+ result = 31 * result + email.hashCode()
result = 31 * result + password.hashCode()
result = 31 * result + token.hashCode()
return result
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncHelper.kt b/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncHelper.kt
index 60128fb84..dcdac1c83 100644
--- a/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncHelper.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncHelper.kt
@@ -26,6 +26,7 @@ import org.koitharu.kotatsu.parsers.util.json.mapJSONTo
import org.koitharu.kotatsu.sync.data.SyncAuthApi
import org.koitharu.kotatsu.sync.data.SyncAuthenticator
import org.koitharu.kotatsu.sync.data.SyncInterceptor
+import org.koitharu.kotatsu.sync.data.SyncSettings
import org.koitharu.kotatsu.utils.GZipInterceptor
import org.koitharu.kotatsu.utils.ext.parseJsonOrNull
import org.koitharu.kotatsu.utils.ext.toContentValues
@@ -41,18 +42,20 @@ private const val FIELD_TIMESTAMP = "timestamp"
@WorkerThread
class SyncHelper(
context: Context,
- account: Account,
+ private val account: Account,
private val provider: ContentProviderClient,
) {
private val authorityHistory = context.getString(R.string.sync_authority_history)
private val authorityFavourites = context.getString(R.string.sync_authority_favourites)
+ private val settings = SyncSettings(context, account)
private val httpClient = OkHttpClient.Builder()
- .authenticator(SyncAuthenticator(context, account, SyncAuthApi(context, OkHttpClient())))
+ .authenticator(SyncAuthenticator(context, account, settings, SyncAuthApi(OkHttpClient())))
.addInterceptor(SyncInterceptor(context, account))
.addInterceptor(GZipInterceptor())
.build()
- private val baseUrl = context.getString(R.string.url_sync_server)
+ private val baseUrl: String
+ get() = "http://${settings.host}"
private val defaultGcPeriod: Long // gc period if sync enabled
get() = TimeUnit.DAYS.toMillis(4)
diff --git a/app/src/main/java/org/koitharu/kotatsu/sync/ui/SyncAuthActivity.kt b/app/src/main/java/org/koitharu/kotatsu/sync/ui/SyncAuthActivity.kt
index f08a41404..14bb26817 100644
--- a/app/src/main/java/org/koitharu/kotatsu/sync/ui/SyncAuthActivity.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/sync/ui/SyncAuthActivity.kt
@@ -11,8 +11,8 @@ import android.widget.Button
import androidx.activity.OnBackPressedCallback
import androidx.activity.viewModels
import androidx.core.graphics.Insets
-import androidx.core.view.isGone
import androidx.core.view.isVisible
+import androidx.fragment.app.FragmentResultListener
import androidx.transition.Fade
import androidx.transition.TransitionManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@@ -20,12 +20,13 @@ import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.databinding.ActivitySyncAuthBinding
+import org.koitharu.kotatsu.sync.data.SyncSettings
import org.koitharu.kotatsu.sync.domain.SyncAuthResult
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.getParcelableExtraCompat
@AndroidEntryPoint
-class SyncAuthActivity : BaseActivity(), View.OnClickListener {
+class SyncAuthActivity : BaseActivity(), View.OnClickListener, FragmentResultListener {
private var accountAuthenticatorResponse: AccountAuthenticatorResponse? = null
private var resultBundle: Bundle? = null
@@ -43,6 +44,8 @@ class SyncAuthActivity : BaseActivity(), View.OnClickLi
binding.buttonNext.setOnClickListener(this)
binding.buttonBack.setOnClickListener(this)
binding.buttonDone.setOnClickListener(this)
+ binding.layoutProgress.setOnClickListener(this)
+ binding.buttonSettings.setOnClickListener(this)
binding.editEmail.addTextChangedListener(EmailTextWatcher(binding.buttonNext))
binding.editPassword.addTextChangedListener(PasswordTextWatcher(binding.buttonDone))
@@ -52,6 +55,7 @@ class SyncAuthActivity : BaseActivity(), View.OnClickLi
viewModel.onError.observe(this, ::onError)
viewModel.isLoading.observe(this, ::onLoadingStateChanged)
+ supportFragmentManager.setFragmentResultListener(SyncHostDialogFragment.REQUEST_KEY, this, this)
pageBackCallback.update()
}
@@ -73,12 +77,14 @@ class SyncAuthActivity : BaseActivity(), View.OnClickLi
}
R.id.button_next -> {
- binding.switcher.showNext()
+ binding.groupLogin.isVisible = false
+ binding.groupPassword.isVisible = true
pageBackCallback.update()
}
R.id.button_back -> {
- binding.switcher.showPrevious()
+ binding.groupPassword.isVisible = false
+ binding.groupLogin.isVisible = true
pageBackCallback.update()
}
@@ -88,9 +94,18 @@ class SyncAuthActivity : BaseActivity(), View.OnClickLi
password = binding.editPassword.text.toString(),
)
}
+
+ R.id.button_settings -> {
+ SyncHostDialogFragment.show(supportFragmentManager)
+ }
}
}
+ override fun onFragmentResult(requestKey: String, result: Bundle) {
+ val host = result.getString(SyncHostDialogFragment.KEY_HOST) ?: return
+ viewModel.host.value = host
+ }
+
override fun finish() {
accountAuthenticatorResponse?.let { response ->
resultBundle?.also {
@@ -105,7 +120,6 @@ class SyncAuthActivity : BaseActivity(), View.OnClickLi
return
}
TransitionManager.beginDelayedTransition(binding.root, Fade())
- binding.switcher.isGone = isLoading
binding.layoutProgress.isVisible = isLoading
pageBackCallback.update()
}
@@ -121,8 +135,10 @@ class SyncAuthActivity : BaseActivity(), View.OnClickLi
private fun onTokenReceived(authResult: SyncAuthResult) {
val am = AccountManager.get(this)
val account = Account(authResult.email, getString(R.string.account_type_sync))
+ val userdata = Bundle(1)
+ userdata.putString(SyncSettings.KEY_HOST, authResult.host)
val result = Bundle()
- if (am.addAccountExplicitly(account, authResult.password, Bundle())) {
+ if (am.addAccountExplicitly(account, authResult.password, userdata)) {
result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name)
result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type)
result.putString(AccountManager.KEY_AUTHTOKEN, authResult.token)
@@ -168,12 +184,13 @@ class SyncAuthActivity : BaseActivity(), View.OnClickLi
private inner class PageBackCallback : OnBackPressedCallback(false) {
override fun handleOnBackPressed() {
- binding.switcher.showPrevious()
+ binding.groupLogin.isVisible = true
+ binding.groupPassword.isVisible = false
update()
}
fun update() {
- isEnabled = binding.switcher.isVisible && binding.switcher.displayedChild > 0
+ isEnabled = !binding.layoutProgress.isVisible && binding.groupPassword.isVisible
}
}
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/sync/ui/SyncAuthViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/sync/ui/SyncAuthViewModel.kt
index a5343ade5..567722392 100644
--- a/app/src/main/java/org/koitharu/kotatsu/sync/ui/SyncAuthViewModel.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/sync/ui/SyncAuthViewModel.kt
@@ -1,24 +1,34 @@
package org.koitharu.kotatsu.sync.ui
+import android.content.Context
+import androidx.lifecycle.MutableLiveData
import dagger.hilt.android.lifecycle.HiltViewModel
+import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
+import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.sync.data.SyncAuthApi
import org.koitharu.kotatsu.sync.domain.SyncAuthResult
import org.koitharu.kotatsu.utils.SingleLiveEvent
+import org.koitharu.kotatsu.utils.ext.ifNullOrEmpty
import javax.inject.Inject
@HiltViewModel
class SyncAuthViewModel @Inject constructor(
+ @ApplicationContext context: Context,
private val api: SyncAuthApi,
) : BaseViewModel() {
val onTokenObtained = SingleLiveEvent()
+ val host = MutableLiveData("")
+
+ private val defaultHost = context.getString(R.string.sync_host_default)
fun obtainToken(email: String, password: String) {
+ val hostValue = host.value.ifNullOrEmpty { defaultHost }
launchLoadingJob(Dispatchers.Default) {
- val token = api.authenticate(email, password)
- val result = SyncAuthResult(email, password, token)
+ val token = api.authenticate(hostValue, email, password)
+ val result = SyncAuthResult(host.value.orEmpty(), email, password, token)
onTokenObtained.emitCall(result)
}
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/sync/ui/SyncHostDialogFragment.kt b/app/src/main/java/org/koitharu/kotatsu/sync/ui/SyncHostDialogFragment.kt
new file mode 100644
index 000000000..970c33938
--- /dev/null
+++ b/app/src/main/java/org/koitharu/kotatsu/sync/ui/SyncHostDialogFragment.kt
@@ -0,0 +1,79 @@
+package org.koitharu.kotatsu.sync.ui
+
+import android.content.DialogInterface
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewGroup.MarginLayoutParams
+import android.widget.ArrayAdapter
+import androidx.core.os.bundleOf
+import androidx.core.view.updateLayoutParams
+import androidx.fragment.app.FragmentManager
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import dagger.hilt.android.AndroidEntryPoint
+import org.koitharu.kotatsu.R
+import org.koitharu.kotatsu.base.ui.AlertDialogFragment
+import org.koitharu.kotatsu.databinding.PreferenceDialogAutocompletetextviewBinding
+import org.koitharu.kotatsu.settings.DomainValidator
+import org.koitharu.kotatsu.sync.data.SyncSettings
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class SyncHostDialogFragment : AlertDialogFragment(),
+ DialogInterface.OnClickListener {
+
+ @Inject
+ lateinit var syncSettings: SyncSettings
+
+ override fun onInflateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?
+ ) = PreferenceDialogAutocompletetextviewBinding.inflate(inflater, container, false)
+
+ override fun onBuildDialog(builder: MaterialAlertDialogBuilder): MaterialAlertDialogBuilder {
+ return super.onBuildDialog(builder)
+ .setPositiveButton(android.R.string.ok, this)
+ .setNegativeButton(android.R.string.cancel, this)
+ .setCancelable(false)
+ .setTitle(R.string.server_address)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding.message.updateLayoutParams {
+ topMargin = view.resources.getDimensionPixelOffset(R.dimen.screen_padding)
+ bottomMargin = topMargin
+ }
+ binding.message.setText(R.string.sync_host_description)
+ val entries = view.resources.getStringArray(R.array.sync_host_list)
+ val editText = binding.edit
+ editText.setText(syncSettings.host)
+ editText.threshold = 0
+ editText.setAdapter(ArrayAdapter(view.context, android.R.layout.simple_spinner_dropdown_item, entries))
+ binding.dropdown.setOnClickListener {
+ editText.showDropDown()
+ }
+ DomainValidator().attachToEditText(editText)
+ }
+
+ override fun onClick(dialog: DialogInterface, which: Int) {
+ when (which) {
+ DialogInterface.BUTTON_POSITIVE -> {
+ val result = binding.edit.text?.toString().orEmpty()
+ syncSettings.host = result
+ parentFragmentManager.setFragmentResult(REQUEST_KEY, bundleOf(KEY_HOST to result))
+ }
+ }
+ dialog.dismiss()
+ }
+
+ companion object {
+
+ private const val TAG = "SyncHostDialogFragment"
+ const val REQUEST_KEY = "sync_host"
+ const val KEY_HOST = "host"
+
+ fun show(fm: FragmentManager) = SyncHostDialogFragment().show(fm, TAG)
+ }
+}
diff --git a/app/src/main/res/layout/activity_sync_auth.xml b/app/src/main/res/layout/activity_sync_auth.xml
index 47248a40a..797586b04 100644
--- a/app/src/main/res/layout/activity_sync_auth.xml
+++ b/app/src/main/res/layout/activity_sync_auth.xml
@@ -1,5 +1,5 @@
-
-
+
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:layout_height="wrap_content"
+ android:autofillHints="password"
+ android:imeOptions="actionDone"
+ android:inputType="textPassword"
+ android:maxLength="24"
+ android:singleLine="true"
+ android:textSize="16sp"
+ tools:text="qwerty" />
+
+
+
+
+
+
+
+
+
+
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:background="?android:windowBackground"
+ android:visibility="gone"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/textView_title">
-
+
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index 32b5338bb..d0290cefa 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -103,4 +103,8 @@
+
+
+
+
diff --git a/app/src/main/res/values/constants.xml b/app/src/main/res/values/constants.xml
index 324fc1d75..060c11acb 100644
--- a/app/src/main/res/values/constants.xml
+++ b/app/src/main/res/values/constants.xml
@@ -7,7 +7,7 @@
https://hosted.weblate.org/engage/kotatsu
https://acra.kotatsu.app/report
org.kotatsu.sync
- https://sync.kotatsu.app
+ sync.kotatsu.app
Mw6F0tPEOgyV7F9U9Twg50Q8SndMY7hzIOfXg0AX_XU
euBMt1GGRSDpVIFQVPxZrO7Kh6X4gWyv0dABuj4B-M8
9887
@@ -41,9 +41,13 @@
- block_nsfw
- block_all
-
+
- 1
- 2
- 0
+
+ - @string/sync_host_default
+ - 86.57.183.214:8081
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 148c22c77..7bb343771 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -435,4 +435,7 @@
Show on the Shelf
You can sign in into an existing account or create a new one
Find similar
+ Synchronization settings
+ Server address
+ You can use a self-hosted synchronization server or a default one. Don\'t change this if you\'re not sure what you\'re doing.
diff --git a/app/src/main/res/xml/authenticator_sync.xml b/app/src/main/res/xml/authenticator_sync.xml
index d40082822..12f369c3b 100644
--- a/app/src/main/res/xml/authenticator_sync.xml
+++ b/app/src/main/res/xml/authenticator_sync.xml
@@ -1,7 +1,7 @@
diff --git a/app/src/main/res/xml/pref_services.xml b/app/src/main/res/xml/pref_services.xml
index 47e1d59e4..ca42cf497 100644
--- a/app/src/main/res/xml/pref_services.xml
+++ b/app/src/main/res/xml/pref_services.xml
@@ -10,6 +10,12 @@
android:summary="@string/sync_title"
android:title="@string/sync" />
+
+
-
\ No newline at end of file
+
+
+
+
+
diff --git a/app/src/main/res/xml/pref_sync_header.xml b/app/src/main/res/xml/pref_sync_header.xml
new file mode 100644
index 000000000..fbaf2113c
--- /dev/null
+++ b/app/src/main/res/xml/pref_sync_header.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+