diff --git a/composeApp/src/androidMain/kotlin/me/matsumo/fanbox/PixiViewNavHost.android.kt b/composeApp/src/androidMain/kotlin/me/matsumo/fanbox/PixiViewNavHost.android.kt
index 5abeae6b..c38dcd6f 100644
--- a/composeApp/src/androidMain/kotlin/me/matsumo/fanbox/PixiViewNavHost.android.kt
+++ b/composeApp/src/androidMain/kotlin/me/matsumo/fanbox/PixiViewNavHost.android.kt
@@ -6,6 +6,7 @@ import android.content.pm.verify.domain.DomainVerificationUserState
import android.net.Uri
import android.os.Build
import android.provider.Settings
+import android.widget.Toast
import androidx.activity.compose.LocalActivity
import androidx.annotation.RequiresApi
import androidx.compose.foundation.background
@@ -49,6 +50,7 @@ import me.matsumo.fanbox.core.resources.domain_validation_request_button
import me.matsumo.fanbox.core.resources.domain_validation_request_message
import me.matsumo.fanbox.core.resources.domain_validation_request_never_ask
import me.matsumo.fanbox.core.resources.domain_validation_request_title
+import me.matsumo.fanbox.core.resources.error_no_available_this_device
import me.matsumo.fanbox.core.ui.extensition.padding
import me.matsumo.fanbox.core.ui.theme.bold
import org.jetbrains.compose.resources.stringResource
@@ -82,6 +84,7 @@ private fun RequestDomainVerification(
) {
val scope = rememberCoroutineScope()
var shouldRequest by remember { mutableStateOf(false) }
+ val errorMessage = stringResource(Res.string.error_no_available_this_device)
LaunchedEffect(Unit) {
val manager = activity.getSystemService(DomainVerificationManager::class.java)
@@ -100,8 +103,13 @@ private fun RequestDomainVerification(
if (shouldRequest) {
RequestDomainVerificationDialog(
onRequestClicked = {
- activity.startActivity(Intent(Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS, Uri.parse("package:${activity.packageName}")))
- shouldRequest = false
+ runCatching {
+ activity.startActivity(Intent(Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS, Uri.parse("package:${activity.packageName}")))
+ }.onFailure {
+ Toast.makeText(activity, errorMessage, Toast.LENGTH_SHORT).show()
+ }.onSuccess {
+ shouldRequest = false
+ }
},
onCancelClicked = { isChecked ->
if (isChecked) {
diff --git a/composeApp/src/commonMain/kotlin/me/matsumo/fanbox/PixiViewApp.kt b/composeApp/src/commonMain/kotlin/me/matsumo/fanbox/PixiViewApp.kt
index 9ba98e89..4719879a 100644
--- a/composeApp/src/commonMain/kotlin/me/matsumo/fanbox/PixiViewApp.kt
+++ b/composeApp/src/commonMain/kotlin/me/matsumo/fanbox/PixiViewApp.kt
@@ -11,7 +11,6 @@ import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
@@ -23,7 +22,6 @@ import com.svenjacobs.reveal.RevealCanvas
import com.svenjacobs.reveal.rememberRevealCanvasState
import dev.icerock.moko.biometry.compose.BindBiometryAuthenticatorEffect
import dev.icerock.moko.biometry.compose.rememberBiometryAuthenticatorFactory
-import io.github.aakira.napier.Napier
import kotlinx.collections.immutable.ImmutableMap
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -129,25 +127,26 @@ fun PixiViewApp(
DisposableEffect(lifecycleOwner) {
val observer = object : DefaultLifecycleObserver {
- override fun onResume(owner: LifecycleOwner) {
- Napier.d { "onResume" }
+ override fun onCreate(owner: LifecycleOwner) {
+ viewModel.billingClientInitialize()
+ }
+ override fun onResume(owner: LifecycleOwner) {
viewModel.setAppLock(true)
viewModel.billingClientUpdate()
}
+
+ override fun onDestroy(owner: LifecycleOwner) {
+ viewModel.billingClientFinish()
+ }
}
lifecycleOwner.lifecycle.addObserver(observer)
-
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
}
}
}
}
-
- LaunchedEffect(true) {
- viewModel.billingClientInitialize()
- }
}
@Composable
diff --git a/composeApp/src/commonMain/kotlin/me/matsumo/fanbox/PixiViewViewModel.kt b/composeApp/src/commonMain/kotlin/me/matsumo/fanbox/PixiViewViewModel.kt
index b57926d1..bed11c30 100644
--- a/composeApp/src/commonMain/kotlin/me/matsumo/fanbox/PixiViewViewModel.kt
+++ b/composeApp/src/commonMain/kotlin/me/matsumo/fanbox/PixiViewViewModel.kt
@@ -62,7 +62,7 @@ class PixiViewViewModel(
),
) { flows ->
val userData = flows[0] as UserData
- val sessionId = flows[1] as String
+ val sessionId = flows[1] as String?
val downloadState = flows[2] as DownloadState
val isLoggedIn = flows[3] as Boolean
val isAppLocked = flows[4] as Boolean
@@ -70,7 +70,7 @@ class PixiViewViewModel(
ScreenState.Idle(
MainUiState(
userData = userData,
- sessionId = sessionId,
+ sessionId = sessionId.orEmpty(),
fanboxMetadata = suspendRunCatching { fanboxRepository.getMetadata() }.getOrElse { getFanboxMetadataDummy() },
downloadState = downloadState,
isLoggedIn = isLoggedIn,
@@ -113,6 +113,10 @@ class PixiViewViewModel(
billingStatus.init()
}
+ fun billingClientFinish() {
+ billingStatus.finish()
+ }
+
fun billingClientUpdate() {
billingStatus.update()
}
diff --git a/core/billing/src/androidMain/kotlin/me/matsumo/fanbox/core/billing/BillingStatus.kt b/core/billing/src/androidMain/kotlin/me/matsumo/fanbox/core/billing/BillingStatus.kt
index 0b697fce..a41f7554 100644
--- a/core/billing/src/androidMain/kotlin/me/matsumo/fanbox/core/billing/BillingStatus.kt
+++ b/core/billing/src/androidMain/kotlin/me/matsumo/fanbox/core/billing/BillingStatus.kt
@@ -29,6 +29,6 @@ class BillingStatusImpl(
}
override fun finish() {
- // do nothing
+ billingClient.dispose()
}
}
diff --git a/core/resources/src/commonMain/composeResources/files/versions.json b/core/resources/src/commonMain/composeResources/files/versions.json
index 8278b476..82cb404b 100644
--- a/core/resources/src/commonMain/composeResources/files/versions.json
+++ b/core/resources/src/commonMain/composeResources/files/versions.json
@@ -183,9 +183,16 @@
},
{
"versionName": "2.2.0",
- "versionCode": 51,
+ "versionCode": 52,
"date": "2025/01/20",
"logJp": "・クリエイターの投稿をキーワードから検索できる機能を追加\n・ダウンロードキュー画面を追加\n・Android App Links に対応\n・UIの調整\n・投稿詳細画面にバナー広告を追加(アプリ維持費のためです。ごめんなさい!)",
"logEn": "・Added the ability to search creators' posts by keyword\n・Added a download queue screen\n・Supports Android App Links\n・Added banner ads to the post details screen (to cover app maintenance costs. Sorry!)"
+ },
+ {
+ "versionName": "2.2.1",
+ "versionCode": 53,
+ "date": "2025/01/23",
+ "logJp": "・ログインができない場合の救済導線を追加\n・アプリがクラッシュする恐れのある不具合の修正",
+ "logEn": "・Added a rescue guide for when you can't log in\n・Fixed a bug that could cause the app to crash"
}
]
diff --git a/core/resources/src/commonMain/composeResources/values-ja/strings.xml b/core/resources/src/commonMain/composeResources/values-ja/strings.xml
index 2808fa65..9b60f7b8 100644
--- a/core/resources/src/commonMain/composeResources/values-ja/strings.xml
+++ b/core/resources/src/commonMain/composeResources/values-ja/strings.xml
@@ -62,6 +62,7 @@
サービス稼働状況
広告の読み込みに失敗しました
サービスの品質維持のために広告を表示しています。
+ このデバイスでは利用できない機能です
%1$d日前
@@ -354,8 +355,10 @@
GoogleやX(Twitter)のOAuthを利用した認証はセキュリティ上の懸念により対応していません。\n\nメールアドレスとパスワードを直接入力してログインを行ってください。
注意
デバッグログインを実行しますか?\n\nデバッグアカウントは事前に準備されたアカウントで、通常ユーザーの挙動を模倣します。ステージング環境保全のため、一部操作ができないもしくはエラーになる場合があります。
- ログインできない場合
+ FANBOXSESSID 直接入力
このアプリが動作するためには FANBOXSESSID というセッションIDが必要です。以下のリンクの手順に従って FANBOXSESSID を取得し、入力してください。
+ ログインできない場合
+ email と passwordを入力し、画面上ではログインできているにも関わらず、自動的に画面が切り替わらない場合のみ以下をお試しください。\n\nこのダイアログを一旦閉じ、ログインしていないと閲覧することができない情報があるページまで、画面上で遷移してください(ex: 支援しているクリエイターの投稿詳細画面)。\n\nその画面まで遷移したら再度画面下部の「ログインできない場合」のボタンを押し、このダイアログを表示してください。\n\nこの状態でダイアログ下部のログインボタンをタップしてください。WebView からログイン情報を取得し、ログインを試みます。
権限
アプリの利用には以下の権限が必要です\n下のボタンを押して権限を許可してください
準備完了
diff --git a/core/resources/src/commonMain/composeResources/values-ko/strings.xml b/core/resources/src/commonMain/composeResources/values-ko/strings.xml
index fd7e9157..c20e97a8 100644
--- a/core/resources/src/commonMain/composeResources/values-ko/strings.xml
+++ b/core/resources/src/commonMain/composeResources/values-ko/strings.xml
@@ -58,6 +58,7 @@
서비스 운영 현황
광고를 로드하지 못했습니다.
서비스 품질 유지를 위해 광고를 게재하고 있습니다.
+ 이 기기에서는 사용할 수 없는 기능입니다
%1$d일 전
@@ -343,8 +344,10 @@
구글이나 X의 OAuth를 이용한 인증은 보안상의 문제로 인해 지원하지 않습니다.\n\n 이메일 주소와 비밀번호를 직접 입력하여 로그인하십시오.
주의
디버그 로그인을 실행하시겠습니까?\n\n디버그 계정은 미리 준비된 계정으로, 일반 사용자의 행동을 모방합니다. 스테이징 환경 보존을 위해 일부 조작이 불가능하거나 오류가 발생할 수 있습니다.
- 로그인할 수 없는 경우
- 이 앱이 작동하려면 FANBOXSESSID라는 세션 ID가 필요합니다. 아래 링크의 절차에 따라 FANBOXSESSID를 발급받아 입력하세요.
+ FANBOXSESSID 직접 입력
+ 이 앱이 작동하려면 FANBOXSESSID라는 세션 ID가 필요합니다. 아래 링크의 지침에 따라 FANBOXSESSID를 얻은 후 입력해주세요.
+ 로그인할 수 없는 경우
+ 이메일과 비밀번호를 입력하고 화면에서 로그인된 것처럼 보이지만 화면이 자동으로 전환되지 않을 경우 아래 단계를 시도해보세요:\n\n이 대화 상자를 한 번 닫고 로그인하지 않으면 볼 수 없는 페이지(예: 후원 중인 창작자의 게시물 상세 페이지)로 화면을 이동하세요.\n\n그 화면으로 이동한 후 다시 화면 하단의 '로그인할 수 없는 경우' 버튼을 눌러 이 대화 상자를 표시해주세요.\n\n이 상태에서 대화 상자 하단의 로그인 버튼을 눌러주세요. WebView에서 로그인 정보를 가져와 로그인을 시도합니다."
권한 설정
이 앱을 이용하려면 다음 권한이 필요합니다.\n아래의 버튼을 눌러 권한을 허용해주세요
준비 완료
diff --git a/core/resources/src/commonMain/composeResources/values-zh-rCN/strings.xml b/core/resources/src/commonMain/composeResources/values-zh-rCN/strings.xml
index a5e8f062..cb25a207 100644
--- a/core/resources/src/commonMain/composeResources/values-zh-rCN/strings.xml
+++ b/core/resources/src/commonMain/composeResources/values-zh-rCN/strings.xml
@@ -62,6 +62,7 @@
服务可用性
广告加载失败
我们展示广告来维护我们服务的质量。
+ 此设备无法使用该功能
%1$d天前
@@ -355,8 +356,10 @@
由于安全问题,不支持使用Google或X(Twitter)的OAuth进行身份验证。\n\n请直接输入您的电子邮件地址和密码登录。
注意
是否要执行调试登录?\n\n调试帐户是模拟常规用户行为的预先设置的帐户。为了保留暂存环境,某些操作可能无法执行或可能会发生错误。
- 如果无法登录
- 此应用需要名为FANBOXSESSID的会话ID才能运行。请按照下面链接中的步骤获取并输入您的FANBOXSESSID。
+ 直接输入 FANBOXSESSID
+ 此应用需要一个名为 FANBOXSESSID 的会话 ID 才能运行。请按照以下链接中的步骤获取 FANBOXSESSID 并输入。
+ 无法登录时
+ 如果输入邮箱和密码后,虽然屏幕显示已登录,但界面没有自动切换,请尝试以下步骤:\n\n先关闭此对话框,并在屏幕上导航到需要登录才能查看的页面(例如:支持的创作者的帖子详细页面)。\n\n导航到该页面后,再次点击屏幕底部的“无法登录时”按钮,显示此对话框。\n\n在此状态下,点击对话框底部的登录按钮。应用将尝试从 WebView 获取登录信息并进行登录。”
权限
使用应用程序需要以下权限。\n单击下面的按钮以授予权限。
准备就绪
diff --git a/core/resources/src/commonMain/composeResources/values/strings.xml b/core/resources/src/commonMain/composeResources/values/strings.xml
index 958b82e6..d37d0be9 100644
--- a/core/resources/src/commonMain/composeResources/values/strings.xml
+++ b/core/resources/src/commonMain/composeResources/values/strings.xml
@@ -62,6 +62,7 @@
Service operation status
Failed to load ads.
We display advertisements to maintain the quality of our service.
+ This feature is not available on this device
%1$d days ago
@@ -355,8 +356,10 @@
Authentication using Google or X (Twitter)'s OAuth is not supported due to security concerns. \n\nPlease log in by directly entering your email address and password.
Caution
Do you want to perform a debug login?\n\nDebug accounts are pre-provisioned accounts that mimic the behavior of regular users. In order to preserve the staging environment, some operations may not be possible or errors may occur.
- When can't log in
- A session ID called FANBOXSESSID is required for this app to work. Please follow the steps in the link below to obtain and enter your FANBOXSESSID.
+ Direct Input of FANBOXSESSID
+ This app requires a session ID called FANBOXSESSID to function. Please follow the instructions in the link below to obtain FANBOXSESSID and input it.
+ If You Cannot Log In
+ If you enter your email and password, and although it seems like you are logged in on the screen, the screen does not automatically transition, please try the following:\n\nClose this dialog once and navigate to a page that requires login to view content (e.g., the post details of a creator you are supporting).\n\nOnce you have navigated to such a page, press the 'If You Cannot Log In' button at the bottom of the screen again to display this dialog.\n\nIn this state, tap the login button at the bottom of the dialog. The app will attempt to retrieve login information from WebView and log you in."
Permissions
The following permissions are required to use the app\nClick the button below to grant permission
Ready
diff --git a/feature/setting/src/commonMain/kotlin/me/matsumo/fanbox/feature/setting/top/SettingTopViewModel.kt b/feature/setting/src/commonMain/kotlin/me/matsumo/fanbox/feature/setting/top/SettingTopViewModel.kt
index 8f6ddb8b..1574d08f 100644
--- a/feature/setting/src/commonMain/kotlin/me/matsumo/fanbox/feature/setting/top/SettingTopViewModel.kt
+++ b/feature/setting/src/commonMain/kotlin/me/matsumo/fanbox/feature/setting/top/SettingTopViewModel.kt
@@ -39,7 +39,7 @@ class SettingTopViewModel(
SettingTopUiState(
userData = userData,
metaData = suspendRunCatching { fanboxRepository.getMetadata() }.getOrElse { getFanboxMetadataDummy() },
- fanboxSessionId = sessionId ?: "unknown",
+ fanboxSessionId = sessionId ?: "Unknown",
config = pixiViewConfig,
),
)
diff --git a/feature/welcome/src/commonMain/kotlin/me/matsumo/fanbox/feature/welcome/web/WelcomeWebDialog.kt b/feature/welcome/src/commonMain/kotlin/me/matsumo/fanbox/feature/welcome/web/WelcomeWebDialog.kt
new file mode 100644
index 00000000..2a91ff80
--- /dev/null
+++ b/feature/welcome/src/commonMain/kotlin/me/matsumo/fanbox/feature/welcome/web/WelcomeWebDialog.kt
@@ -0,0 +1,123 @@
+package me.matsumo.fanbox.feature.welcome.web
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.horizontalScroll
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.selection.SelectionContainer
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.MaterialTheme.colors
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedButton
+import androidx.compose.material3.Text
+import androidx.compose.material3.surfaceColorAtElevation
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import kotlinx.collections.immutable.ImmutableList
+import me.matsumo.fanbox.core.resources.Res
+import me.matsumo.fanbox.core.resources.common_cancel
+import me.matsumo.fanbox.core.resources.welcome_login_title
+import me.matsumo.fanbox.core.resources.welcome_login_web_help
+import me.matsumo.fanbox.core.resources.welcome_login_web_help_description
+import me.matsumo.fanbox.core.ui.theme.bold
+import org.jetbrains.compose.resources.stringResource
+
+@Composable
+internal fun WelcomeWebDialog(
+ currentUrl: String,
+ currentCookies: ImmutableList,
+ onClickLogin: () -> Unit,
+ onDismissRequest: () -> Unit,
+) {
+ Dialog(
+ onDismissRequest = onDismissRequest,
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .wrapContentHeight()
+ .clip(RoundedCornerShape(16.dp))
+ .background(MaterialTheme.colorScheme.surface)
+ .padding(24.dp),
+ verticalArrangement = Arrangement.spacedBy(16.dp),
+ ) {
+ Text(
+ modifier = Modifier.fillMaxWidth(),
+ text = stringResource(Res.string.welcome_login_web_help),
+ style = MaterialTheme.typography.titleMedium.bold(),
+ color = MaterialTheme.colorScheme.onSurface,
+ )
+
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .verticalScroll(rememberScrollState()),
+ verticalArrangement = Arrangement.spacedBy(16.dp),
+ ) {
+ Text(
+ modifier = Modifier.fillMaxWidth(),
+ text = stringResource(Res.string.welcome_login_web_help_description),
+ style = MaterialTheme.typography.bodyMedium,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ )
+
+ Card(
+ modifier = Modifier
+ .fillMaxWidth()
+ .clip(RoundedCornerShape(8.dp)),
+ shape = RoundedCornerShape(8.dp),
+ colors = CardDefaults.cardColors(MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp)),
+ ) {
+ SelectionContainer {
+ Text(
+ modifier = Modifier
+ .horizontalScroll(rememberScrollState())
+ .padding(16.dp),
+ text = "URL: $currentUrl\nCookies: $currentCookies",
+ style = MaterialTheme.typography.bodySmall,
+ )
+ }
+ }
+ }
+
+ Row(
+ modifier = Modifier
+ .padding(top = 8.dp)
+ .fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ OutlinedButton(
+ modifier = Modifier.weight(1f),
+ shape = RoundedCornerShape(4.dp),
+ onClick = onDismissRequest,
+ ) {
+ Text(text = stringResource(Res.string.common_cancel))
+ }
+
+ Button(
+ modifier = Modifier.weight(1f),
+ shape = RoundedCornerShape(4.dp),
+ colors = ButtonDefaults.buttonColors(MaterialTheme.colorScheme.primary),
+ onClick = onClickLogin,
+ ) {
+ Text(text = stringResource(Res.string.welcome_login_title))
+ }
+ }
+ }
+ }
+}
diff --git a/feature/welcome/src/commonMain/kotlin/me/matsumo/fanbox/feature/welcome/web/WelcomeWebScreen.kt b/feature/welcome/src/commonMain/kotlin/me/matsumo/fanbox/feature/welcome/web/WelcomeWebScreen.kt
index 9f0136c3..b7467df6 100644
--- a/feature/welcome/src/commonMain/kotlin/me/matsumo/fanbox/feature/welcome/web/WelcomeWebScreen.kt
+++ b/feature/welcome/src/commonMain/kotlin/me/matsumo/fanbox/feature/welcome/web/WelcomeWebScreen.kt
@@ -4,26 +4,53 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.Text
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.outlined.HelpOutline
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.Icon
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
+import androidx.compose.material3.SnackbarHost
+import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateListOf
+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.unit.dp
+import com.multiplatform.webview.cookie.Cookie
import com.multiplatform.webview.cookie.WebViewCookieManager
import com.multiplatform.webview.web.LoadingState
import com.multiplatform.webview.web.WebView
import com.multiplatform.webview.web.rememberWebViewState
+import io.github.aakira.napier.Napier
+import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import me.matsumo.fanbox.core.resources.Res
import me.matsumo.fanbox.core.resources.welcome_login_title
+import me.matsumo.fanbox.core.resources.welcome_login_toast_failed
+import me.matsumo.fanbox.core.resources.welcome_login_web_help
import me.matsumo.fanbox.core.ui.component.PixiViewTopBar
+import me.matsumo.fanbox.core.ui.extensition.ToastExtension
import me.matsumo.fanbox.core.ui.view.SimpleAlertContents
+import org.jetbrains.compose.resources.getString
import org.jetbrains.compose.resources.stringResource
+import org.koin.compose.koinInject
import org.koin.compose.viewmodel.koinViewModel
@OptIn(ExperimentalMaterial3Api::class)
@@ -34,11 +61,32 @@ internal fun WelcomeWebScreen(
terminate: () -> Unit,
modifier: Modifier = Modifier,
viewModel: WelcomeWebViewModel = koinViewModel(),
+ snackExtension: ToastExtension = koinInject(),
) {
val fanboxUrl = "https://www.fanbox.cc/login"
val fanboxRedirectUrl = "https://www.fanbox.cc/creators/find"
val webViewState = rememberWebViewState("$fanboxUrl?return_to=$fanboxRedirectUrl")
+ val scope = rememberCoroutineScope()
+
+ val snackbarHostState = remember { SnackbarHostState() }
+ val currentCookies = remember { mutableStateListOf() }
+ var isDisplayHelpDialog by remember { mutableStateOf(false) }
+
+ suspend fun tryLogin() {
+ val sessionId = currentCookies.find { it.name == "FANBOXSESSID" }
+
+ if (sessionId != null && viewModel.checkSessionId(sessionId.value)) {
+ viewModel.saveSessionId(sessionId.value)
+ terminate.invoke()
+ } else {
+ snackExtension.show(
+ snackbarHostState = snackbarHostState,
+ message = getString(Res.string.welcome_login_toast_failed),
+ isSnackbar = true,
+ )
+ }
+ }
webViewState.webSettings.apply {
isJavaScriptEnabled = true
@@ -56,13 +104,16 @@ internal fun WelcomeWebScreen(
}
LaunchedEffect(webViewState.lastLoadedUrl) {
- if (webViewState.lastLoadedUrl == fanboxRedirectUrl) {
- val oauthCookies = webViewState.cookieManager.getCookies("https://oauth.secure.pixiv.net")
- val fanboxCookies = webViewState.cookieManager.getCookies("https://www.fanbox.cc")
- val sessionId = (fanboxCookies + oauthCookies).find { it.name == "FANBOXSESSID" }
+ val oauthCookies = webViewState.cookieManager.getCookies("https://oauth.secure.pixiv.net")
+ val fanboxCookies = webViewState.cookieManager.getCookies("https://www.fanbox.cc")
- viewModel.saveSessionId(sessionId?.value.orEmpty())
- terminate.invoke()
+ currentCookies.clear()
+ currentCookies.addAll(fanboxCookies + oauthCookies)
+
+ Napier.d { "WebView current url: ${webViewState.lastLoadedUrl} == $fanboxRedirectUrl" }
+
+ if (webViewState.lastLoadedUrl == fanboxRedirectUrl) {
+ tryLogin()
}
}
@@ -82,6 +133,34 @@ internal fun WelcomeWebScreen(
},
)
},
+ bottomBar = {
+ HorizontalDivider()
+
+ Button(
+ modifier = Modifier
+ .fillMaxWidth()
+ .navigationBarsPadding()
+ .padding(16.dp),
+ onClick = { isDisplayHelpDialog = true },
+ contentPadding = ButtonDefaults.ButtonWithIconContentPadding,
+ ) {
+ Icon(
+ modifier = Modifier.size(18.dp),
+ imageVector = Icons.AutoMirrored.Outlined.HelpOutline,
+ contentDescription = null,
+ )
+
+ Text(
+ modifier = Modifier.padding(start = 8.dp),
+ text = stringResource(Res.string.welcome_login_web_help),
+ )
+ }
+ },
+ snackbarHost = {
+ SnackbarHost(
+ hostState = snackbarHostState,
+ )
+ },
) { padding ->
Box(
modifier = Modifier
@@ -98,9 +177,23 @@ internal fun WelcomeWebScreen(
modifier = Modifier
.align(Alignment.TopCenter)
.fillMaxWidth(),
- progress = it.progress,
+ progress = { it.progress },
)
}
}
}
+
+ if (isDisplayHelpDialog) {
+ WelcomeWebDialog(
+ currentUrl = webViewState.lastLoadedUrl.orEmpty(),
+ currentCookies = currentCookies.map { "${it.name}=${it.value}" }.toImmutableList(),
+ onDismissRequest = { isDisplayHelpDialog = false },
+ onClickLogin = {
+ scope.launch {
+ isDisplayHelpDialog = false
+ tryLogin()
+ }
+ },
+ )
+ }
}
diff --git a/feature/welcome/src/commonMain/kotlin/me/matsumo/fanbox/feature/welcome/web/WelcomeWebViewModel.kt b/feature/welcome/src/commonMain/kotlin/me/matsumo/fanbox/feature/welcome/web/WelcomeWebViewModel.kt
index 97fdb80d..63a6849a 100644
--- a/feature/welcome/src/commonMain/kotlin/me/matsumo/fanbox/feature/welcome/web/WelcomeWebViewModel.kt
+++ b/feature/welcome/src/commonMain/kotlin/me/matsumo/fanbox/feature/welcome/web/WelcomeWebViewModel.kt
@@ -3,6 +3,7 @@ package me.matsumo.fanbox.feature.welcome.web
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
+import me.matsumo.fanbox.core.common.util.suspendRunCatching
import me.matsumo.fanbox.core.repository.FanboxRepository
import me.matsumo.fanbox.core.repository.UserDataRepository
@@ -11,10 +12,19 @@ class WelcomeWebViewModel(
private val userDataRepository: UserDataRepository,
) : ViewModel() {
- fun saveSessionId(sessionId: String) {
- viewModelScope.launch {
- fanboxRepository.setSessionId(sessionId)
- }
+ suspend fun saveSessionId(sessionId: String) {
+ fanboxRepository.setSessionId(sessionId)
+ }
+
+ suspend fun checkSessionId(sessionId: String): Boolean {
+ saveSessionId(sessionId)
+
+ return suspendRunCatching {
+ fanboxRepository.updateCsrfToken()
+ fanboxRepository.getNewsLetters()
+ }.onFailure {
+ saveSessionId("")
+ }.isSuccess
}
fun debugLogin() {
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index ac58ab4f..74da4aad 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,7 +1,7 @@
[versions]
# Application
-versionName = "2.2.0"
-versionCode = "51"
+versionName = "2.2.1"
+versionCode = "53"
# SDK
minSdk = "26"