Skip to content

Commit

Permalink
Add Discord manual login (closes #46)
Browse files Browse the repository at this point in the history
Implement manual login for Discord
Fix WebViewLogin dark mode for A13
  • Loading branch information
toasterofbread committed Jul 1, 2023
1 parent 5f690a9 commit 608cf13
Show file tree
Hide file tree
Showing 20 changed files with 431 additions and 336 deletions.
6 changes: 3 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ buildscript {
}

plugins {
id 'com.android.application' version '7.4.2' apply false
id 'com.android.library' version '7.4.2' apply false
id 'com.android.application' version '7.3.0' apply false
id 'com.android.library' version '7.3.0' apply false
id 'org.jetbrains.kotlin.android' version '1.7.10' apply false
}

task clean(type: Delete) {
tasks.register('clean', Delete) {
delete rootProject.buildDir
}
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ android.minSdk=31
# Versions
kotlin.version=1.8.20
agp.version=7.4.2
compose.version=1.4.0
compose.version=1.4.1
20 changes: 11 additions & 9 deletions shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ kotlin {
implementation("com.google.accompanist:accompanist-swiperefresh:0.21.2-beta")
implementation("androidx.localbroadcastmanager:localbroadcastmanager:1.1.0")
implementation("androidx.palette:palette:1.0.0")
//noinspection GradleDependency
implementation("com.github.andob:android-awt:1.0.0")
implementation("io.coil-kt:coil-compose:2.3.0")
implementation("com.github.dead8309:KizzyRPC:1.0.71")
Expand Down Expand Up @@ -160,27 +161,28 @@ open class GenerateBuildConfig : DefaultTask() {
dir.deleteRecursively()
dir.mkdirs()

val fqName = class_fq_name.get()
val parts = fqName.split(".")
val className = parts.last()
val file = dir.resolve("$className.kt")
val class_parts = class_fq_name.get().split(".")
val class_name = class_parts.last()
val file = dir.resolve("$class_name.kt")

val content = buildString {
if (parts.size > 1) {
if (class_parts.size > 1) {
appendLine("@file:Suppress(\"RedundantNullableReturnType\", \"MayBeConstant\")\n")
appendLine("package ${parts.dropLast(1).joinToString(".")}")
appendLine("package ${class_parts.dropLast(1).joinToString(".")}")
}

appendLine()
appendLine("/* GENERATED ON BUILD */")
appendLine("object $className {")
appendLine("/* Generated on build in shared/build.gradle.kts */")
appendLine("object $class_name {")

for (field in fields_to_generate.get().sortedBy { it.first }) {
val type = if (field.second == null) "" else ": ${field.second}"
val type = field.second?.let { ": $it" } ?: ""
appendLine(" val ${field.first}$type = ${field.third}")
}

appendLine("}")
}

file.writeText(content)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.toasterofbread.spmp.platform

import android.graphics.Bitmap
import android.os.Build
import android.view.ViewGroup
import android.webkit.*
import androidx.compose.animation.AnimatedVisibility
Expand Down Expand Up @@ -48,8 +49,13 @@ actual fun WebViewLogin(

OnChangedEffect(web_view, is_dark) {
web_view?.apply {
@Suppress("DEPRECATION")
settings.forceDark = if (is_dark) WebSettings.FORCE_DARK_ON else WebSettings.FORCE_DARK_OFF
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
settings.isAlgorithmicDarkeningAllowed = is_dark
}
else {
@Suppress("DEPRECATION")
settings.forceDark = if (is_dark) WebSettings.FORCE_DARK_ON else WebSettings.FORCE_DARK_OFF
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,6 @@ actual fun PlatformDialog(
}
}

@Composable
actual fun PlatformDialog(
onDismissRequest: () -> Unit,
content: @Composable () -> Unit
) {
Dialog(
onDismissRequest,
DialogProperties(decorFitsSystemWindows = false)
) {
content()
}
}


@Composable
actual fun PlatformAlertDialog(
onDismissRequest: () -> Unit,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -760,14 +760,14 @@ class SettingsItemLargeToggle(
if (!enabled) theme.background else theme.vibrant_accent,
shape
)
.border(Dp.Hairline, theme.vibrant_accent, shape)
.border(2.dp, theme.vibrant_accent, shape)
.padding(horizontal = 10.dp)
.fillMaxWidth()
.height(IntrinsicSize.Max),
horizontalArrangement = Arrangement.spacedBy(3.dp), verticalAlignment = Alignment.CenterVertically
) {
(if (enabled) enabled_content else disabled_content)?.invoke(Modifier.weight(1f).padding(vertical = 5.dp))
(if (enabled) enabled_text else disabled_text)?.also { Text(it, Modifier.fillMaxWidth().weight(1f)) }
(if (enabled) enabled_text else disabled_text)?.also { WidthShrinkText(it, Modifier.fillMaxWidth().weight(1f)) }

Button(
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ class SettingsInterface(
}

suspend fun goBack() {
pill_menu?.clearAlongsideActions()
pill_menu?.clearExtraActions()
if (page_stack.size > 0) {
val target_page = page_stack.removeLast()
if (current_page != target_page) {
Expand Down
23 changes: 15 additions & 8 deletions shared/src/commonMain/kotlin/com/toasterofbread/spmp/api/Api.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,24 @@ import org.schabi.newpipe.extractor.downloader.Response as NewPipeResponse

const val DEFAULT_CONNECT_TIMEOUT = 3000

fun <T> Result.Companion.failure(response: Response): Result<T> {
fun <T> Result.Companion.failure(response: Response, is_gzip: Boolean = true): Result<T> {
var body: String
try {
val stream = response.getStream()
body = stream.reader().readText()
stream.close()
if (is_gzip) {
try {
val stream = response.getStream()
body = stream.reader().readText()
stream.close()
}
catch (e: ZipException) {
body = response.body!!.string()
}
}
catch (e: ZipException) {
else {
body = response.body!!.string()
}

println("FAAAAAAAAIL $body")

response.close()
return failure(RuntimeException(body))
}
Expand Down Expand Up @@ -239,13 +246,13 @@ class Api {
}
}

fun request(request: Request, allow_fail_response: Boolean = false): Result<Response> {
fun request(request: Request, allow_fail_response: Boolean = false, is_gzip: Boolean = true): Result<Response> {
try {
val response = client.newCall(request).execute()
if (response.isSuccessful || allow_fail_response) {
return Result.success(response)
}
return Result.failure(response)
return Result.failure(response, is_gzip)
}
catch (e: Throwable) {
return Result.failure(e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,6 @@ expect fun PlatformDialog(
content: @Composable () -> Unit
)

@Composable
expect fun PlatformDialog(
onDismissRequest: () -> Unit,
content: @Composable () -> Unit
)

@Composable
expect fun PlatformAlertDialog(
onDismissRequest: () -> Unit,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,34 +24,40 @@ import com.toasterofbread.spmp.platform.composable.PlatformAlertDialog
import com.toasterofbread.spmp.platform.composable.rememberImagePainter
import com.toasterofbread.spmp.platform.isWebViewLoginSupported
import com.toasterofbread.spmp.resources.getString
import com.toasterofbread.spmp.resources.getStringTODO
import com.toasterofbread.utils.catchInterrupts
import com.toasterofbread.utils.composable.LinkifyText
import com.toasterofbread.utils.composable.SubtleLoadingIndicator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.Request
import kotlin.concurrent.thread

private const val DISCORD_LOGIN_URL = "https://discord.com/login"
private const val DISCORD_API_URL = "https://discord.com/api/"
private const val DISCORD_DEFAULT_AVATAR = "https://discord.com/assets/1f0bfc0865d324c2587920a7d80c609b.png"

@Composable
fun DiscordLoginConfirmation(info_only: Boolean = false, onFinished: (proceed: Boolean) -> Unit) {
fun DiscordLoginConfirmation(info_only: Boolean = false, onFinished: (manual: Boolean?) -> Unit) {
PlatformAlertDialog(
{ onFinished(false) },
confirmButton = {
FilledTonalButton({
onFinished(!info_only)
onFinished(if (info_only) null else false)
}) {
Text(getString("action_confirm_action"))
}
},
dismissButton = if (info_only) null else ({
TextButton({ onFinished(false) }) { Text(getString("action_deny_action")) }
TextButton({ onFinished(null) }) { Text(getString("action_deny_action")) }
}),
title = if (info_only) null else ({ Text(getString("prompt_confirm_action")) }),
text = {
LinkifyText(getString(if (info_only) "info_discord_login" else "warning_discord_login"))
Column {
LinkifyText(getString(if (info_only) "info_discord_login" else "warning_discord_login"))
if (!info_only) {
FilledTonalButton({ onFinished(true) }, Modifier.fillMaxWidth().padding(top = 5.dp).offset(y = 20.dp)) {
Text(getString("action_login_manually"))
}
}
}
}
)
}
Expand Down Expand Up @@ -80,33 +86,7 @@ fun DiscordLogin(modifier: Modifier = Modifier, manual: Boolean = false, onFinis
}
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DiscordManualLogin(modifier: Modifier = Modifier, onFinished: (Result<String?>?) -> Unit) {
Column(modifier) {
Text(getStringTODO("TODO"))

var auth_value by remember { mutableStateOf("") }
TextField(
auth_value,
{ auth_value = it },
Modifier.fillMaxWidth(),
label = {
Text("Authorization")
}
)

Button({
onFinished(Result.success(
TODO(auth_value)
))
}) {
Text(getStringTODO("Done"))
}
}
}

private data class DiscordMeResponse(
data class DiscordMeResponse(
val id: String? = null,
val username: String? = null,
val avatar: String? = null,
Expand All @@ -130,23 +110,28 @@ private data class DiscordMeResponse(
}
}

private fun getDiscordAccountInfo(account_token: String): Result<DiscordMeResponse> {
suspend fun getDiscordAccountInfo(account_token: String): Result<DiscordMeResponse> = withContext(Dispatchers.IO) {
val request = Request.Builder()
.url("https://discord.com/api/v9/users/@me")
.addHeader("authorization", account_token)
.build()

val result = Api.request(request)
if (result.isFailure) {
return result.cast()
}
val result = Api.request(request, is_gzip = false)
val response = result.getOrNull() ?: return@withContext result.cast()

val response = result.getOrThrow()
val me: DiscordMeResponse = Klaxon().parse(response.body!!.charStream())!!
val stream = response.body!!.charStream()
val me: DiscordMeResponse = try {
Klaxon().parse(stream)!!
}
catch (e: Throwable) {
return@withContext Result.failure(e)
}
finally {
stream.close()
}
me.token = account_token

response.close()
return Result.success(me)
return@withContext Result.success(me)
}

private val DiscordMeResponseSaver = run {
Expand All @@ -169,31 +154,21 @@ private val DiscordMeResponseSaver = run {

@Composable
fun DiscordAccountPreview(account_token: String, modifier: Modifier = Modifier) {
var load_thread: Thread? by remember { mutableStateOf(null) }
var me by rememberSaveable(stateSaver = DiscordMeResponseSaver) { mutableStateOf(DiscordMeResponse.EMPTY) }
var account_info by rememberSaveable(stateSaver = DiscordMeResponseSaver) { mutableStateOf(DiscordMeResponse.EMPTY) }
var started by remember { mutableStateOf(false) }
var loading by remember { mutableStateOf(false) }

DisposableEffect(account_token) {
load_thread?.interrupt()

if (me.token != account_token) {
load_thread = thread {
catchInterrupts {
me = getDiscordAccountInfo(account_token).getOrReport("DiscordAccountPreview") ?: DiscordMeResponse.EMPTY
load_thread = null
}
}

me = DiscordMeResponse.EMPTY
LaunchedEffect(account_token) {
if (account_info.token != account_token) {
account_info = DiscordMeResponse.EMPTY
loading = true
started = true
account_info = getDiscordAccountInfo(account_token).getOrReport("DiscordAccountPreview") ?: DiscordMeResponse.EMPTY
}

onDispose {
load_thread?.interrupt()
}
loading = false
}

Crossfade(if (!me.isEmpty()) me else if (started) load_thread != null else null, modifier.fillMaxHeight()) { state ->
Crossfade(if (!account_info.isEmpty()) account_info else if (started) loading else null, modifier.fillMaxHeight()) { state ->
Row(horizontalArrangement = Arrangement.spacedBy(5.dp), verticalAlignment = Alignment.CenterVertically) {
when (state) {
true -> {
Expand All @@ -208,8 +183,7 @@ fun DiscordAccountPreview(account_token: String, modifier: Modifier = Modifier)
Image(rememberImagePainter(state.getAvatarUrl()), null, Modifier.fillMaxHeight().aspectRatio(1f).clip(CircleShape))

Column(Modifier.fillMaxHeight(), verticalArrangement = Arrangement.SpaceEvenly) {
Text(state.username!!, overflow = TextOverflow.Ellipsis, maxLines = 1)
Text("#${state.discriminator}")
Text(state.username ?: "?", overflow = TextOverflow.Ellipsis, maxLines = 1)
}
}
}
Expand Down
Loading

0 comments on commit 608cf13

Please sign in to comment.