Remove the authentication requirment to read the spreadsheets.

This commit is contained in:
Thomas Andres Gomez 2023-09-25 14:29:32 +02:00
parent b6c7009697
commit c82ea04375
16 changed files with 86 additions and 95 deletions

View file

@ -7,7 +7,7 @@
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:62913404482:android:25aff6034a72116b7c57db",
"mobilesdk_app_id": "1:62913404482:android:c504d0b32647de717c57db",
"android_client_info": {
"package_name": "com.pixelized.rplexicon"
}
@ -20,15 +20,11 @@
"package_name": "com.pixelized.rplexicon",
"certificate_hash": "39ee1766d2225263052d4a4e82182d1c3e886f35"
}
},
{
"client_id": "62913404482-ergqkjiuvint49q8lm555j21vvb6af7s.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyB2Gkrisrjk1s-5U1ufXGambFRBbFFfLuo"
"current_key": "AIzaSyAsRKJD63wwyr8x_l-n7Hv5T7GZ2M8AT-Q"
}
],
"services": {
@ -44,7 +40,7 @@
},
{
"client_info": {
"mobilesdk_app_id": "1:62913404482:android:8d40bf56f21618e27c57db",
"mobilesdk_app_id": "1:62913404482:android:4de84922f319d24d7c57db",
"android_client_info": {
"package_name": "com.pixelized.rplexicon.dev"
}
@ -57,15 +53,11 @@
"package_name": "com.pixelized.rplexicon.dev",
"certificate_hash": "b5bd860f1f89de118613c3e94b3f6c8cbd6d876a"
}
},
{
"client_id": "62913404482-ergqkjiuvint49q8lm555j21vvb6af7s.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyB2Gkrisrjk1s-5U1ufXGambFRBbFFfLuo"
"current_key": "AIzaSyAsRKJD63wwyr8x_l-n7Hv5T7GZ2M8AT-Q"
}
],
"services": {

View file

@ -1,12 +1,11 @@
package com.pixelized.rplexicon.repository
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import android.app.Application
import com.google.api.client.extensions.android.http.AndroidHttp
import com.google.api.client.http.HttpRequest
import com.google.api.client.json.gson.GsonFactory
import com.google.api.services.sheets.v4.Sheets
import com.pixelized.rplexicon.repository.authentication.AuthenticationRepository
import com.pixelized.rplexicon.utilitary.exceptions.ServiceNotReady
import com.pixelized.rplexicon.R
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ -15,31 +14,27 @@ import javax.inject.Singleton
@Singleton
class GoogleSheetServiceRepository @Inject constructor(
authenticationRepository: AuthenticationRepository,
application: Application,
) {
private val service: Sheets? by derivedStateOf {
when (authenticationRepository.isAuthenticated.value) {
true -> Sheets
.Builder(
AndroidHttp.newCompatibleTransport(),
GsonFactory(),
authenticationRepository.credential,
)
.build()
else -> null
private val service: Sheets = Sheets
.Builder(
AndroidHttp.newCompatibleTransport(),
GsonFactory(),
) { request: HttpRequest? ->
request?.url?.set(
API_PARAM_KEY,
application.getString(R.string.google_spreadsheet_api_key)
)
}
}
.build()
@Throws(ServiceNotReady::class)
suspend fun fetch(
lambda: suspend CoroutineScope.(service: Sheets.Spreadsheets.Values) -> Unit,
) {
when (val service = service) {
null -> throw ServiceNotReady()
else -> withContext(Dispatchers.IO) {
lambda(service.spreadsheets().values())
}
}
): Unit = withContext(Dispatchers.IO) {
lambda(service.spreadsheets().values())
}
companion object {
private const val API_PARAM_KEY = "key"
}
}

View file

@ -4,7 +4,6 @@ import com.pixelized.rplexicon.repository.parser.AttackParser
import com.pixelized.rplexicon.model.Attack
import com.pixelized.rplexicon.repository.GoogleSheetServiceRepository
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
import com.pixelized.rplexicon.utilitary.exceptions.ServiceNotReady
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Inject
@ -23,7 +22,7 @@ class ActionRepository @Inject constructor(
return name?.let { _data.value[it] }
}
@Throws(ServiceNotReady::class, IncompatibleSheetStructure::class, Exception::class)
@Throws(IncompatibleSheetStructure::class, Exception::class)
suspend fun fetchActions() {
googleRepository.fetch { sheet ->
val request = sheet.get(Sheet.Character.ID, Sheet.Character.ATTACK)

View file

@ -8,7 +8,6 @@ import com.pixelized.rplexicon.model.Counter
import com.pixelized.rplexicon.model.Property
import com.pixelized.rplexicon.repository.GoogleSheetServiceRepository
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
import com.pixelized.rplexicon.utilitary.exceptions.ServiceNotReady
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Inject
@ -44,7 +43,7 @@ class AlterationRepository @Inject constructor(
return name?.let { counter.value[it] }
}
@Throws(ServiceNotReady::class, IncompatibleSheetStructure::class, Exception::class)
@Throws(IncompatibleSheetStructure::class, Exception::class)
suspend fun fetchAlterationSheet() {
googleRepository.fetch { sheet ->
val request = sheet.get(Sheet.Character.ID, Sheet.Character.ALTERATION)
@ -53,7 +52,7 @@ class AlterationRepository @Inject constructor(
}
}
@Throws(ServiceNotReady::class, IncompatibleSheetStructure::class, Exception::class)
@Throws(IncompatibleSheetStructure::class, Exception::class)
suspend fun fetchStatusSheet() {
googleRepository.fetch { sheet ->
val request = sheet.get(Sheet.Character.ID, Sheet.Character.STATUS)

View file

@ -4,7 +4,6 @@ import com.pixelized.rplexicon.repository.parser.CharacterSheetParser
import com.pixelized.rplexicon.model.CharacterSheet
import com.pixelized.rplexicon.repository.GoogleSheetServiceRepository
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
import com.pixelized.rplexicon.utilitary.exceptions.ServiceNotReady
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Inject
@ -22,7 +21,7 @@ class CharacterSheetRepository @Inject constructor(
return name?.let { _data.value[name] }
}
@Throws(ServiceNotReady::class, IncompatibleSheetStructure::class, Exception::class)
@Throws(IncompatibleSheetStructure::class, Exception::class)
suspend fun fetchCharacterSheet() {
googleRepository.fetch { sheet ->
val request = sheet.get(Sheet.Character.ID, Sheet.Character.CHARACTER)
@ -30,8 +29,4 @@ class CharacterSheetRepository @Inject constructor(
_data.emit(data)
}
}
companion object {
const val TAG = "CharacterSheetRepository"
}
}

View file

@ -4,7 +4,6 @@ import com.pixelized.rplexicon.repository.parser.LexiconParser
import com.pixelized.rplexicon.model.Lexicon
import com.pixelized.rplexicon.repository.GoogleSheetServiceRepository
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
import com.pixelized.rplexicon.utilitary.exceptions.ServiceNotReady
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Inject
@ -22,7 +21,7 @@ class LexiconRepository @Inject constructor(
return name?.let { _data.value.firstOrNull { item -> item.name == it }?.id }
}
@Throws(ServiceNotReady::class, IncompatibleSheetStructure::class, Exception::class)
@Throws(IncompatibleSheetStructure::class, Exception::class)
suspend fun fetchLexicon() {
googleRepository.fetch { sheet ->
val request = sheet.get(Sheet.Lexicon.ID, Sheet.Lexicon.LEXICON)
@ -30,9 +29,4 @@ class LexiconRepository @Inject constructor(
_data.tryEmit(data)
}
}
companion object {
const val TAG = "LexiconRepository"
}
}

View file

@ -6,7 +6,6 @@ import com.pixelized.rplexicon.repository.parser.MarqueeParser
import com.pixelized.rplexicon.model.Location
import com.pixelized.rplexicon.repository.GoogleSheetServiceRepository
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
import com.pixelized.rplexicon.utilitary.exceptions.ServiceNotReady
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.MutableStateFlow
@ -27,7 +26,7 @@ class LocationRepository @Inject constructor(
return name?.let { _data.value.firstOrNull { item -> item.name == it }?.id }
}
@Throws(ServiceNotReady::class, IncompatibleSheetStructure::class, Exception::class)
@Throws(IncompatibleSheetStructure::class, Exception::class)
suspend fun fetchLocation() {
googleRepository.fetch { sheet ->
val (map, marquee) = awaitAll(
@ -59,7 +58,6 @@ class LocationRepository @Inject constructor(
}
companion object {
private const val TAG = "LocationRepository"
const val SHEET_URL =
"https://docs.google.com/spreadsheets/d/${Sheet.Lexicon.ID}/edit#gid=1985553511"
}

View file

@ -5,7 +5,6 @@ import com.pixelized.rplexicon.repository.parser.QuestParser
import com.pixelized.rplexicon.model.Quest
import com.pixelized.rplexicon.repository.GoogleSheetServiceRepository
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
import com.pixelized.rplexicon.utilitary.exceptions.ServiceNotReady
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Inject
@ -19,7 +18,7 @@ class QuestRepository @Inject constructor(
private val _data = MutableStateFlow<List<Quest>>(emptyList())
val data: StateFlow<List<Quest>> get() = _data
@Throws(ServiceNotReady::class, IncompatibleSheetStructure::class, Exception::class)
@Throws(IncompatibleSheetStructure::class, Exception::class)
suspend fun fetchQuests() {
googleRepository.fetch { sheet ->
val request = sheet.get(Sheet.Lexicon.ID, Sheet.Lexicon.QUEST_JOURNAL)

View file

@ -6,7 +6,6 @@ import com.pixelized.rplexicon.model.AssignedSpell
import com.pixelized.rplexicon.model.Spell
import com.pixelized.rplexicon.repository.GoogleSheetServiceRepository
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
import com.pixelized.rplexicon.utilitary.exceptions.ServiceNotReady
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.MutableStateFlow
@ -29,10 +28,10 @@ class SpellRepository @Inject constructor(
}
fun findAssignedSpell(character: String?, spell: String): AssignedSpell? {
return character?.let { _spells.value[it]?.find { assigned -> assigned.spell.name == spell } }
return character?.let { _spells.value[it]?.find { assigned -> assigned.spell.name == spell } }
}
@Throws(ServiceNotReady::class, IncompatibleSheetStructure::class, Exception::class)
@Throws(IncompatibleSheetStructure::class, Exception::class)
suspend fun fetchSpells() {
googleRepository.fetch { sheet ->
val (lexicon, magic) = awaitAll(

View file

@ -10,6 +10,7 @@ import androidx.navigation.NavOptionsBuilder
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.rememberNavController
import com.pixelized.rplexicon.ui.navigation.screens.AUTHENTICATION_ROUTE
import com.pixelized.rplexicon.ui.navigation.screens.HOME_ROUTE
import com.pixelized.rplexicon.ui.navigation.screens.composableAuthentication
import com.pixelized.rplexicon.ui.navigation.screens.composableCharacterSheet
import com.pixelized.rplexicon.ui.navigation.screens.composableHome
@ -26,7 +27,7 @@ val LocalScreenNavHost = staticCompositionLocalOf<NavHostController> {
@Composable
fun ScreenNavHost(
navHostController: NavHostController = rememberNavController(),
startDestination: String = AUTHENTICATION_ROUTE,
startDestination: String = HOME_ROUTE,
) {
val lexiconListState = rememberLazyListState()
val questListState = rememberLazyListState()

View file

@ -27,6 +27,7 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.remember
@ -37,6 +38,7 @@ import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.ColorMatrix
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
@ -49,10 +51,10 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.pixelized.rplexicon.LocalActivity
import com.pixelized.rplexicon.LocalSnack
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
import com.pixelized.rplexicon.ui.navigation.rootOption
import com.pixelized.rplexicon.ui.navigation.screens.navigateToCharacterSheet
import com.pixelized.rplexicon.ui.navigation.screens.navigateToHome
import com.pixelized.rplexicon.ui.theme.LexiconTheme
import com.pixelized.rplexicon.ui.theme.colors.GoogleColorPalette
@ -63,13 +65,27 @@ import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
@Stable
sealed class AuthenticationStateUio {
@Stable
data object Initial : AuthenticationStateUio()
@Stable
data object Success : AuthenticationStateUio()
@Stable
data class Failure(val exception: Exception?) : AuthenticationStateUio()
}
@Composable
fun AuthenticationScreen(
authenticationVM: AuthenticationViewModel = hiltViewModel(),
versionVM: VersionViewModel = hiltViewModel(),
) {
val screen = LocalScreenNavHost.current
val snack = LocalSnack.current
val context = LocalContext.current
val activity = LocalActivity.current
val screen = LocalScreenNavHost.current
val state = authenticationVM.rememberAuthenticationState()
Surface {
@ -91,17 +107,32 @@ fun AuthenticationScreen(
onSignIn = {
screen.navigateToHome(option = rootOption())
},
onSignInError = {
snack.showSnackbar(
message = it?.message ?: context.getString(R.string.error_generic)
)
}
)
}
}
@Composable
fun HandleAuthenticationState(
state: State<AuthenticationViewModel.Authentication>,
onSignIn: suspend CoroutineScope.() -> Unit
state: State<AuthenticationStateUio>,
onSignIn: suspend CoroutineScope.() -> Unit,
onSignInError: suspend CoroutineScope.(exception: Exception?) -> Unit,
) {
if (state.value == AuthenticationViewModel.Authentication.Success) {
LaunchedEffect(key1 = "Authentication.Success", block = onSignIn)
when (val dummy = state.value) {
AuthenticationStateUio.Initial -> Unit
is AuthenticationStateUio.Success -> {
LaunchedEffect(key1 = "Authentication.Success", block = onSignIn)
}
is AuthenticationStateUio.Failure -> {
LaunchedEffect(key1 = "Authentication.Error") {
onSignInError(dummy.exception)
}
}
}
}

View file

@ -10,7 +10,6 @@ import androidx.activity.result.ActivityResult
import androidx.activity.result.IntentSenderRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.AndroidViewModel
@ -33,13 +32,13 @@ class AuthenticationViewModel @Inject constructor(
private val state = mutableStateOf(
when (repository.isAuthenticated.value) {
true -> Authentication.Success
else -> Authentication.Initial
true -> AuthenticationStateUio.Success
else -> AuthenticationStateUio.Initial
}
)
@Composable
fun rememberAuthenticationState(): State<Authentication> {
fun rememberAuthenticationState(): State<AuthenticationStateUio> {
launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartIntentSenderForResult(),
onResult = {
@ -48,11 +47,10 @@ class AuthenticationViewModel @Inject constructor(
.getSignInClient(context)
.getSignInCredentialFromIntent(it.data)
state.value = Authentication.Success
repository.updateAuthenticationState(credential)
state.value = AuthenticationStateUio.Success
repository.updateAuthenticationState(credential = credential)
} else {
state.value = Authentication.Failure
repository.updateAuthenticationState()
repository.updateAuthenticationState(credential = null)
}
},
)
@ -60,7 +58,7 @@ class AuthenticationViewModel @Inject constructor(
}
fun signIn(activity: Activity) {
state.value = Authentication.Initial
state.value = AuthenticationStateUio.Initial
val request: GetSignInIntentRequest = GetSignInIntentRequest.builder()
.setServerClientId(context.getString(R.string.google_sign_in_id))
@ -72,21 +70,14 @@ class AuthenticationViewModel @Inject constructor(
launcher?.launch(
IntentSenderRequest.Builder(result.intentSender).build()
)
} catch (e: SendIntentException) {
state.value = Authentication.Failure
} catch (exception: SendIntentException) {
state.value = AuthenticationStateUio.Failure(exception = exception)
repository.updateAuthenticationState()
}
}
.addOnFailureListener {
state.value = Authentication.Failure
.addOnFailureListener { exception ->
state.value = AuthenticationStateUio.Failure(exception = exception)
repository.updateAuthenticationState()
}
}
@Stable
sealed class Authentication {
object Initial : Authentication()
object Success : Authentication()
object Failure : Authentication()
}
}

View file

@ -1,3 +0,0 @@
package com.pixelized.rplexicon.utilitary.exceptions
class ServiceNotReady : Exception()

View file

@ -1,7 +1,7 @@
<resources>
<string name="app_name">Rp-Compagnon</string>
<string name="error_generic">Ah !? y\'a un truc qui foire quelque part.</string>
<string name="error_generic">Une erreur s\'est produite.</string>
<string name="error_structure">La structure du fichier semble avoir changé et n\'est plus compatible avec cette application.</string>
<string name="gender_male">Mâle</string>

View file

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="google_spreadsheet_api_key">AIzaSyBmagVOEyB68tTJ5QMFMzMQZIHG_4XVCOo</string>
<string name="google_sign_in_id" translatable="false">62913404482-ergqkjiuvint49q8lm555j21vvb6af7s.apps.googleusercontent.com</string>
</resources>

View file

@ -1,7 +1,7 @@
<resources>
<string name="app_name">Rp-Companion</string>
<string name="error_generic">Oups, it should not be rocket science.</string>
<string name="error_generic">An error occur.</string>
<string name="error_structure">The file structure appears to have changed and is no longer compatible with this application</string>
<string name="gender_male">Male</string>