mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-06-23 21:46:01 +02:00
355 lines
15 KiB
Kotlin
355 lines
15 KiB
Kotlin
package eu.kanade.presentation.more.settings.screen
|
|
|
|
import android.content.Context
|
|
import androidx.compose.foundation.layout.Arrangement
|
|
import androidx.compose.foundation.layout.Column
|
|
import androidx.compose.foundation.layout.Row
|
|
import androidx.compose.foundation.layout.RowScope
|
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
import androidx.compose.foundation.text.KeyboardOptions
|
|
import androidx.compose.material.icons.Icons
|
|
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
|
import androidx.compose.material.icons.filled.Visibility
|
|
import androidx.compose.material.icons.filled.VisibilityOff
|
|
import androidx.compose.material.icons.outlined.Close
|
|
import androidx.compose.material3.AlertDialog
|
|
import androidx.compose.material3.Button
|
|
import androidx.compose.material3.ButtonDefaults
|
|
import androidx.compose.material3.Icon
|
|
import androidx.compose.material3.IconButton
|
|
import androidx.compose.material3.MaterialTheme
|
|
import androidx.compose.material3.OutlinedButton
|
|
import androidx.compose.material3.OutlinedTextField
|
|
import androidx.compose.material3.Text
|
|
import androidx.compose.runtime.Composable
|
|
import androidx.compose.runtime.ReadOnlyComposable
|
|
import androidx.compose.runtime.getValue
|
|
import androidx.compose.runtime.mutableStateOf
|
|
import androidx.compose.runtime.remember
|
|
import androidx.compose.runtime.rememberCoroutineScope
|
|
import androidx.compose.runtime.setValue
|
|
import androidx.compose.ui.Alignment
|
|
import androidx.compose.ui.Modifier
|
|
import androidx.compose.ui.platform.LocalContext
|
|
import androidx.compose.ui.platform.LocalUriHandler
|
|
import androidx.compose.ui.text.input.ImeAction
|
|
import androidx.compose.ui.text.input.KeyboardType
|
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
|
import androidx.compose.ui.text.input.TextFieldValue
|
|
import androidx.compose.ui.text.input.VisualTransformation
|
|
import androidx.compose.ui.text.style.TextAlign
|
|
import androidx.compose.ui.unit.dp
|
|
import dev.icerock.moko.resources.StringResource
|
|
import eu.kanade.domain.track.service.TrackPreferences
|
|
import eu.kanade.presentation.more.settings.Preference
|
|
import eu.kanade.tachiyomi.data.track.EnhancedTracker
|
|
import eu.kanade.tachiyomi.data.track.Tracker
|
|
import eu.kanade.tachiyomi.data.track.TrackerManager
|
|
import eu.kanade.tachiyomi.data.track.anilist.AnilistApi
|
|
import eu.kanade.tachiyomi.data.track.bangumi.BangumiApi
|
|
import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeListApi
|
|
import eu.kanade.tachiyomi.data.track.shikimori.ShikimoriApi
|
|
import eu.kanade.tachiyomi.util.system.openInBrowser
|
|
import eu.kanade.tachiyomi.util.system.toast
|
|
import kotlinx.collections.immutable.persistentListOf
|
|
import kotlinx.collections.immutable.toImmutableList
|
|
import tachiyomi.core.util.lang.launchIO
|
|
import tachiyomi.core.util.lang.withUIContext
|
|
import tachiyomi.domain.source.service.SourceManager
|
|
import tachiyomi.i18n.MR
|
|
import tachiyomi.presentation.core.components.material.padding
|
|
import tachiyomi.presentation.core.i18n.stringResource
|
|
import uy.kohesive.injekt.Injekt
|
|
import uy.kohesive.injekt.api.get
|
|
|
|
object SettingsTrackingScreen : SearchableSettings {
|
|
|
|
@ReadOnlyComposable
|
|
@Composable
|
|
override fun getTitleRes() = MR.strings.pref_category_tracking
|
|
|
|
@Composable
|
|
override fun RowScope.AppBarAction() {
|
|
val uriHandler = LocalUriHandler.current
|
|
IconButton(onClick = { uriHandler.openUri("https://tachiyomi.org/docs/guides/tracking") }) {
|
|
Icon(
|
|
imageVector = Icons.AutoMirrored.Outlined.HelpOutline,
|
|
contentDescription = stringResource(MR.strings.tracking_guide),
|
|
)
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
override fun getPreferences(): List<Preference> {
|
|
val context = LocalContext.current
|
|
val trackPreferences = remember { Injekt.get<TrackPreferences>() }
|
|
val trackerManager = remember { Injekt.get<TrackerManager>() }
|
|
val sourceManager = remember { Injekt.get<SourceManager>() }
|
|
|
|
var dialog by remember { mutableStateOf<Any?>(null) }
|
|
dialog?.run {
|
|
when (this) {
|
|
is LoginDialog -> {
|
|
TrackingLoginDialog(
|
|
tracker = tracker,
|
|
uNameStringRes = uNameStringRes,
|
|
onDismissRequest = { dialog = null },
|
|
)
|
|
}
|
|
is LogoutDialog -> {
|
|
TrackingLogoutDialog(
|
|
tracker = tracker,
|
|
onDismissRequest = { dialog = null },
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
val enhancedTrackers = trackerManager.trackers
|
|
.filter { it is EnhancedTracker }
|
|
.partition { service ->
|
|
val acceptedSources = (service as EnhancedTracker).getAcceptedSources()
|
|
sourceManager.getCatalogueSources().any { it::class.qualifiedName in acceptedSources }
|
|
}
|
|
var enhancedTrackerInfo = stringResource(MR.strings.enhanced_tracking_info)
|
|
if (enhancedTrackers.second.isNotEmpty()) {
|
|
val missingSourcesInfo = stringResource(
|
|
MR.strings.enhanced_services_not_installed,
|
|
enhancedTrackers.second.joinToString { it.name },
|
|
)
|
|
enhancedTrackerInfo += "\n\n$missingSourcesInfo"
|
|
}
|
|
|
|
return listOf(
|
|
Preference.PreferenceItem.SwitchPreference(
|
|
pref = trackPreferences.autoUpdateTrack(),
|
|
title = stringResource(MR.strings.pref_auto_update_manga_sync),
|
|
),
|
|
Preference.PreferenceGroup(
|
|
title = stringResource(MR.strings.services),
|
|
preferenceItems = persistentListOf(
|
|
Preference.PreferenceItem.TrackerPreference(
|
|
title = trackerManager.myAnimeList.name,
|
|
tracker = trackerManager.myAnimeList,
|
|
login = { context.openInBrowser(MyAnimeListApi.authUrl(), forceDefaultBrowser = true) },
|
|
logout = { dialog = LogoutDialog(trackerManager.myAnimeList) },
|
|
),
|
|
Preference.PreferenceItem.TrackerPreference(
|
|
title = trackerManager.aniList.name,
|
|
tracker = trackerManager.aniList,
|
|
login = { context.openInBrowser(AnilistApi.authUrl(), forceDefaultBrowser = true) },
|
|
logout = { dialog = LogoutDialog(trackerManager.aniList) },
|
|
),
|
|
Preference.PreferenceItem.TrackerPreference(
|
|
title = trackerManager.kitsu.name,
|
|
tracker = trackerManager.kitsu,
|
|
login = { dialog = LoginDialog(trackerManager.kitsu, MR.strings.email) },
|
|
logout = { dialog = LogoutDialog(trackerManager.kitsu) },
|
|
),
|
|
Preference.PreferenceItem.TrackerPreference(
|
|
title = trackerManager.mangaUpdates.name,
|
|
tracker = trackerManager.mangaUpdates,
|
|
login = { dialog = LoginDialog(trackerManager.mangaUpdates, MR.strings.username) },
|
|
logout = { dialog = LogoutDialog(trackerManager.mangaUpdates) },
|
|
),
|
|
Preference.PreferenceItem.TrackerPreference(
|
|
title = trackerManager.shikimori.name,
|
|
tracker = trackerManager.shikimori,
|
|
login = { context.openInBrowser(ShikimoriApi.authUrl(), forceDefaultBrowser = true) },
|
|
logout = { dialog = LogoutDialog(trackerManager.shikimori) },
|
|
),
|
|
Preference.PreferenceItem.TrackerPreference(
|
|
title = trackerManager.bangumi.name,
|
|
tracker = trackerManager.bangumi,
|
|
login = { context.openInBrowser(BangumiApi.authUrl(), forceDefaultBrowser = true) },
|
|
logout = { dialog = LogoutDialog(trackerManager.bangumi) },
|
|
),
|
|
Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.tracking_info)),
|
|
),
|
|
),
|
|
Preference.PreferenceGroup(
|
|
title = stringResource(MR.strings.enhanced_services),
|
|
preferenceItems = (
|
|
enhancedTrackers.first
|
|
.map { service ->
|
|
Preference.PreferenceItem.TrackerPreference(
|
|
title = service.name,
|
|
tracker = service,
|
|
login = { (service as EnhancedTracker).loginNoop() },
|
|
logout = service::logout,
|
|
)
|
|
} + listOf(Preference.PreferenceItem.InfoPreference(enhancedTrackerInfo))
|
|
).toImmutableList(),
|
|
),
|
|
)
|
|
}
|
|
|
|
@Composable
|
|
private fun TrackingLoginDialog(
|
|
tracker: Tracker,
|
|
uNameStringRes: StringResource,
|
|
onDismissRequest: () -> Unit,
|
|
) {
|
|
val context = LocalContext.current
|
|
val scope = rememberCoroutineScope()
|
|
|
|
var username by remember { mutableStateOf(TextFieldValue(tracker.getUsername())) }
|
|
var password by remember { mutableStateOf(TextFieldValue(tracker.getPassword())) }
|
|
var processing by remember { mutableStateOf(false) }
|
|
var inputError by remember { mutableStateOf(false) }
|
|
|
|
AlertDialog(
|
|
onDismissRequest = onDismissRequest,
|
|
title = {
|
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
|
Text(
|
|
text = stringResource(MR.strings.login_title, tracker.name),
|
|
modifier = Modifier.weight(1f),
|
|
)
|
|
IconButton(onClick = onDismissRequest) {
|
|
Icon(
|
|
imageVector = Icons.Outlined.Close,
|
|
contentDescription = stringResource(MR.strings.action_close),
|
|
)
|
|
}
|
|
}
|
|
},
|
|
text = {
|
|
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
|
OutlinedTextField(
|
|
modifier = Modifier.fillMaxWidth(),
|
|
value = username,
|
|
onValueChange = { username = it },
|
|
label = { Text(text = stringResource(uNameStringRes)) },
|
|
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
|
|
singleLine = true,
|
|
isError = inputError && !processing,
|
|
)
|
|
|
|
var hidePassword by remember { mutableStateOf(true) }
|
|
OutlinedTextField(
|
|
modifier = Modifier.fillMaxWidth(),
|
|
value = password,
|
|
onValueChange = { password = it },
|
|
label = { Text(text = stringResource(MR.strings.password)) },
|
|
trailingIcon = {
|
|
IconButton(onClick = { hidePassword = !hidePassword }) {
|
|
Icon(
|
|
imageVector = if (hidePassword) {
|
|
Icons.Filled.Visibility
|
|
} else {
|
|
Icons.Filled.VisibilityOff
|
|
},
|
|
contentDescription = null,
|
|
)
|
|
}
|
|
},
|
|
visualTransformation = if (hidePassword) {
|
|
PasswordVisualTransformation()
|
|
} else {
|
|
VisualTransformation.None
|
|
},
|
|
keyboardOptions = KeyboardOptions(
|
|
keyboardType = KeyboardType.Password,
|
|
imeAction = ImeAction.Done,
|
|
),
|
|
singleLine = true,
|
|
isError = inputError && !processing,
|
|
)
|
|
}
|
|
},
|
|
confirmButton = {
|
|
Button(
|
|
modifier = Modifier.fillMaxWidth(),
|
|
enabled = !processing && username.text.isNotBlank() && password.text.isNotBlank(),
|
|
onClick = {
|
|
scope.launchIO {
|
|
processing = true
|
|
val result = checkLogin(
|
|
context = context,
|
|
tracker = tracker,
|
|
username = username.text,
|
|
password = password.text,
|
|
)
|
|
inputError = !result
|
|
if (result) onDismissRequest()
|
|
processing = false
|
|
}
|
|
},
|
|
) {
|
|
val id = if (processing) MR.strings.loading else MR.strings.login
|
|
Text(text = stringResource(id))
|
|
}
|
|
},
|
|
)
|
|
}
|
|
|
|
private suspend fun checkLogin(
|
|
context: Context,
|
|
tracker: Tracker,
|
|
username: String,
|
|
password: String,
|
|
): Boolean {
|
|
return try {
|
|
tracker.login(username, password)
|
|
withUIContext { context.toast(MR.strings.login_success) }
|
|
true
|
|
} catch (e: Throwable) {
|
|
tracker.logout()
|
|
withUIContext { context.toast(e.message.toString()) }
|
|
false
|
|
}
|
|
}
|
|
|
|
@Composable
|
|
private fun TrackingLogoutDialog(
|
|
tracker: Tracker,
|
|
onDismissRequest: () -> Unit,
|
|
) {
|
|
val context = LocalContext.current
|
|
AlertDialog(
|
|
onDismissRequest = onDismissRequest,
|
|
title = {
|
|
Text(
|
|
text = stringResource(MR.strings.logout_title, tracker.name),
|
|
textAlign = TextAlign.Center,
|
|
modifier = Modifier.fillMaxWidth(),
|
|
)
|
|
},
|
|
confirmButton = {
|
|
Row(horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall)) {
|
|
OutlinedButton(
|
|
modifier = Modifier.weight(1f),
|
|
onClick = onDismissRequest,
|
|
) {
|
|
Text(text = stringResource(MR.strings.action_cancel))
|
|
}
|
|
Button(
|
|
modifier = Modifier.weight(1f),
|
|
onClick = {
|
|
tracker.logout()
|
|
onDismissRequest()
|
|
context.toast(MR.strings.logout_success)
|
|
},
|
|
colors = ButtonDefaults.buttonColors(
|
|
containerColor = MaterialTheme.colorScheme.error,
|
|
contentColor = MaterialTheme.colorScheme.onError,
|
|
),
|
|
) {
|
|
Text(text = stringResource(MR.strings.logout))
|
|
}
|
|
}
|
|
},
|
|
)
|
|
}
|
|
}
|
|
|
|
private data class LoginDialog(
|
|
val tracker: Tracker,
|
|
val uNameStringRes: StringResource,
|
|
)
|
|
|
|
private data class LogoutDialog(
|
|
val tracker: Tracker,
|
|
)
|