Add Spreadsheet API to the project.
This commit is contained in:
parent
f2357c6151
commit
6cfd673335
11 changed files with 274 additions and 143 deletions
|
|
@ -99,13 +99,18 @@ dependencies {
|
|||
|
||||
// Google service
|
||||
implementation("com.google.android.gms:play-services-auth:20.6.0")
|
||||
implementation(
|
||||
dependencyNotation = "com.google.api-client:google-api-client-android:1.23.0",
|
||||
dependencyConfiguration = { exclude("org.apache.httpcomponents") },
|
||||
)
|
||||
implementation(
|
||||
dependencyNotation = "com.google.apis:google-api-services-sheets:v4-rev20220927-2.0.0",
|
||||
dependencyConfiguration = { exclude("org.apache.httpcomponents") },
|
||||
)
|
||||
|
||||
// Image
|
||||
implementation("com.github.skydoves:landscapist-glide:2.1.11")
|
||||
kapt("com.github.bumptech.glide:compiler:4.14.2") // this have to be align with landscapist-glide
|
||||
|
||||
// Retrofit : Network
|
||||
implementation("com.squareup.retrofit2:retrofit:2.9.0")
|
||||
}
|
||||
|
||||
private val NamedDomainObjectContainer<SigningConfig>.pixelized get() = this.getByName("pixelized")
|
||||
|
|
@ -8,8 +8,11 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.pixelized.rplexicon.ui.navigation.ScreenNavHost
|
||||
|
|
@ -17,6 +20,8 @@ import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
|||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
val LocalActivity = staticCompositionLocalOf<Activity> { error("Activity not available") }
|
||||
val LocalSnack =
|
||||
staticCompositionLocalOf<SnackbarHostState> { error("SnackbarHostState not available") }
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
|
@ -27,7 +32,8 @@ class MainActivity : ComponentActivity() {
|
|||
setContent {
|
||||
LexiconTheme {
|
||||
CompositionLocalProvider(
|
||||
LocalActivity provides this
|
||||
LocalActivity provides this,
|
||||
LocalSnack provides remember { SnackbarHostState() }
|
||||
) {
|
||||
Scaffold(
|
||||
content = { padding ->
|
||||
|
|
@ -39,6 +45,9 @@ class MainActivity : ComponentActivity() {
|
|||
) {
|
||||
ScreenNavHost()
|
||||
}
|
||||
},
|
||||
snackbarHost = {
|
||||
SnackbarHost(hostState = LocalSnack.current)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
package com.pixelized.rplexicon.module
|
||||
|
||||
import com.pixelized.rplexicon.network.IGoogleSpreadSheet
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import retrofit2.Retrofit
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class NetworkModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGoogleSpreadSheet(): IGoogleSpreadSheet {
|
||||
val retrofit: Retrofit = Retrofit.Builder()
|
||||
.baseUrl(IGoogleSpreadSheet.HOST)
|
||||
.build()
|
||||
|
||||
return retrofit.create(IGoogleSpreadSheet::class.java)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
package com.pixelized.rplexicon.network
|
||||
|
||||
import retrofit2.http.GET
|
||||
|
||||
interface IGoogleSpreadSheet {
|
||||
|
||||
@GET("spreadsheets/d/$ID/edit#gid=$LEXICON_GID")
|
||||
fun getLexicon()
|
||||
|
||||
@GET("spreadsheets/d/$ID/edit#gid=$META_DATA_GID")
|
||||
fun getMetaData()
|
||||
|
||||
companion object {
|
||||
const val HOST = "https://docs.google.com/"
|
||||
const val ID = "1oL9Nu5y37BPEbKxHre4TN9o8nrgy2JQoON4RRkdAHMs"
|
||||
const val LEXICON_GID = 0
|
||||
const val META_DATA_GID = "957635233"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package com.pixelized.rplexicon.repository
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignIn
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
|
||||
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential
|
||||
import com.google.api.client.util.ExponentialBackOff
|
||||
import com.google.api.services.sheets.v4.SheetsScopes
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class AuthenticationRepository @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
) {
|
||||
private val _isAuthenticated = mutableStateOf(account != null)
|
||||
val isAuthenticated: State<Boolean> get() = _isAuthenticated
|
||||
|
||||
private val account: GoogleSignInAccount?
|
||||
get() = GoogleSignIn.getLastSignedInAccount(context)
|
||||
|
||||
val credential: GoogleAccountCredential
|
||||
get() {
|
||||
val credential = GoogleAccountCredential
|
||||
.usingOAuth2(
|
||||
context, listOf(
|
||||
SheetsScopes.SPREADSHEETS,
|
||||
SheetsScopes.SPREADSHEETS_READONLY,
|
||||
)
|
||||
)
|
||||
.setBackOff(ExponentialBackOff())
|
||||
|
||||
credential.selectedAccount = account?.account
|
||||
|
||||
return credential
|
||||
}
|
||||
|
||||
fun updateAuthenticationState() {
|
||||
_isAuthenticated.value = account != null
|
||||
}
|
||||
}
|
||||
|
|
@ -1,80 +1,115 @@
|
|||
package com.pixelized.rplexicon.repository
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import com.google.api.client.extensions.android.http.AndroidHttp
|
||||
import com.google.api.client.json.gson.GsonFactory
|
||||
import com.google.api.services.sheets.v4.Sheets
|
||||
import com.google.api.services.sheets.v4.model.ValueRange
|
||||
import com.pixelized.rplexicon.model.Lexicon
|
||||
import com.pixelized.rplexicon.network.IGoogleSpreadSheet
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class LexiconRepository @Inject constructor(
|
||||
private val spreadSheet: IGoogleSpreadSheet,
|
||||
private val authenticationRepository: AuthenticationRepository
|
||||
) {
|
||||
private val scope = CoroutineScope(Dispatchers.IO)
|
||||
private val sheetService: Sheets? by derivedStateOf {
|
||||
when (authenticationRepository.isAuthenticated.value) {
|
||||
true -> Sheets
|
||||
.Builder(
|
||||
AndroidHttp.newCompatibleTransport(),
|
||||
GsonFactory(),
|
||||
authenticationRepository.credential,
|
||||
)
|
||||
.setApplicationName("RP-Lexique")
|
||||
.build()
|
||||
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private val _data = MutableStateFlow<List<Lexicon>>(emptyList())
|
||||
val data: StateFlow<List<Lexicon>> get() = _data
|
||||
|
||||
init {
|
||||
scope.launch {
|
||||
_data.emit(sample())
|
||||
@Throws(ServiceNotReady::class, Exception::class)
|
||||
suspend fun fetchLexicon(): ValueRange? {
|
||||
val service = sheetService
|
||||
return if (service == null) {
|
||||
throw ServiceNotReady()
|
||||
} else {
|
||||
withContext(Dispatchers.IO) {
|
||||
val request = service.spreadsheets().values().get(ID, LEXIQUE)
|
||||
request.execute()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun sample(): List<Lexicon> {
|
||||
return listOf(
|
||||
Lexicon(
|
||||
name = "Brulkhai",
|
||||
diminutive = "Bru",
|
||||
gender = Lexicon.Gender.FEMALE,
|
||||
race = "Demi-Orc",
|
||||
portrait = listOf(
|
||||
Uri.parse("https://drive.google.com/file/d/1a31xJ6DQnzqmGBndG-uo65HNQHPEUJnI/view?usp=sharing"),
|
||||
),
|
||||
description = "Brulkhai, ou plus simplement Bru, est solidement bâti. Elle mesure 192 cm pour 110 kg de muscles lorsqu’elle est en bonne santé. Elle a les cheveux châtains, les yeux noisettes et la peau couleur gris-vert typique de son espèce. D’un tempérament taciturne, elle parle peu et de façon concise. Elle est parfois brutale, aussi bien physiquement que verbalement, Elle ne prend cependant aucun plaisir à malmener ceux qu’elle considère plus faibles qu’elle. D’une nature simple et honnête, elle ne mâche pas ses mots et ne dissimule généralement pas ses pensées. Son intelligence modeste est plus le reflet d’un manque d’éducation et d’une capacité limitée à gérer ses émotions qu’à une débilité congénitale. Elle voue à la force un culte car c’est par son expression qu’elle se sent vraiment vivante et éprouve de grandes difficultés vis à vis de ceux qu’elle nomme foshnu (bébé, chouineur en commun).",
|
||||
history = null,
|
||||
),
|
||||
Lexicon(
|
||||
name = "Léandre",
|
||||
diminutive = null,
|
||||
gender = Lexicon.Gender.MALE,
|
||||
race = "Humain",
|
||||
portrait = emptyList(),
|
||||
description = null,
|
||||
history = null,
|
||||
),
|
||||
Lexicon(
|
||||
name = "Nelia",
|
||||
diminutive = null,
|
||||
gender = Lexicon.Gender.FEMALE,
|
||||
race = "Elfe",
|
||||
portrait = emptyList(),
|
||||
description = null,
|
||||
history = null,
|
||||
),
|
||||
Lexicon(
|
||||
name = "Tigrane",
|
||||
diminutive = null,
|
||||
gender = Lexicon.Gender.MALE,
|
||||
race = "Tieffelin",
|
||||
portrait = emptyList(),
|
||||
description = null,
|
||||
history = null,
|
||||
),
|
||||
Lexicon(
|
||||
name = "Unathana",
|
||||
diminutive = "Una",
|
||||
gender = Lexicon.Gender.FEMALE,
|
||||
race = "Demi-Elfe",
|
||||
portrait = emptyList(),
|
||||
description = null,
|
||||
history = null,
|
||||
),
|
||||
)
|
||||
companion object {
|
||||
const val HOST = "https://docs.google.com/"
|
||||
const val ID = "1oL9Nu5y37BPEbKxHre4TN9o8nrgy2JQoON4RRkdAHMs"
|
||||
const val LEXICON_GID = 0
|
||||
const val META_DATA_GID = "957635233"
|
||||
const val LEXIQUE = "Lexique"
|
||||
}
|
||||
|
||||
class ServiceNotReady : Exception()
|
||||
}
|
||||
|
||||
|
||||
private fun sample(): List<Lexicon> {
|
||||
return listOf(
|
||||
Lexicon(
|
||||
name = "Brulkhai",
|
||||
diminutive = "Bru",
|
||||
gender = Lexicon.Gender.FEMALE,
|
||||
race = "Demi-Orc",
|
||||
portrait = listOf(
|
||||
Uri.parse("https://drive.google.com/file/d/1a31xJ6DQnzqmGBndG-uo65HNQHPEUJnI/view?usp=sharing"),
|
||||
),
|
||||
description = "Brulkhai, ou plus simplement Bru, est solidement bâti. Elle mesure 192 cm pour 110 kg de muscles lorsqu’elle est en bonne santé. Elle a les cheveux châtains, les yeux noisettes et la peau couleur gris-vert typique de son espèce. D’un tempérament taciturne, elle parle peu et de façon concise. Elle est parfois brutale, aussi bien physiquement que verbalement, Elle ne prend cependant aucun plaisir à malmener ceux qu’elle considère plus faibles qu’elle. D’une nature simple et honnête, elle ne mâche pas ses mots et ne dissimule généralement pas ses pensées. Son intelligence modeste est plus le reflet d’un manque d’éducation et d’une capacité limitée à gérer ses émotions qu’à une débilité congénitale. Elle voue à la force un culte car c’est par son expression qu’elle se sent vraiment vivante et éprouve de grandes difficultés vis à vis de ceux qu’elle nomme foshnu (bébé, chouineur en commun).",
|
||||
history = null,
|
||||
),
|
||||
Lexicon(
|
||||
name = "Léandre",
|
||||
diminutive = null,
|
||||
gender = Lexicon.Gender.MALE,
|
||||
race = "Humain",
|
||||
portrait = emptyList(),
|
||||
description = null,
|
||||
history = null,
|
||||
),
|
||||
Lexicon(
|
||||
name = "Nelia",
|
||||
diminutive = null,
|
||||
gender = Lexicon.Gender.FEMALE,
|
||||
race = "Elfe",
|
||||
portrait = emptyList(),
|
||||
description = null,
|
||||
history = null,
|
||||
),
|
||||
Lexicon(
|
||||
name = "Tigrane",
|
||||
diminutive = null,
|
||||
gender = Lexicon.Gender.MALE,
|
||||
race = "Tieffelin",
|
||||
portrait = emptyList(),
|
||||
description = null,
|
||||
history = null,
|
||||
),
|
||||
Lexicon(
|
||||
name = "Unathana",
|
||||
diminutive = "Una",
|
||||
gender = Lexicon.Gender.FEMALE,
|
||||
race = "Demi-Elfe",
|
||||
portrait = emptyList(),
|
||||
description = null,
|
||||
history = null,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -42,7 +42,7 @@ fun AuthenticationScreen(
|
|||
) {
|
||||
val screen = LocalScreenNavHost.current
|
||||
val activity = LocalActivity.current
|
||||
val state = viewModel.rememberAuthenticationState(activity = activity)
|
||||
val state = viewModel.rememberAuthenticationState()
|
||||
|
||||
Surface {
|
||||
AuthenticationScreenContent(
|
||||
|
|
|
|||
|
|
@ -16,51 +16,41 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.lifecycle.AndroidViewModel
|
||||
import com.google.android.gms.auth.api.identity.GetSignInIntentRequest
|
||||
import com.google.android.gms.auth.api.identity.Identity
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignIn
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
|
||||
import com.pixelized.rplexicon.R
|
||||
import com.pixelized.rplexicon.repository.AuthenticationRepository
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class AuthenticationViewModel @Inject constructor(
|
||||
application: Application,
|
||||
private val repository: AuthenticationRepository,
|
||||
) : AndroidViewModel(application) {
|
||||
|
||||
private val context: Context get() = getApplication()
|
||||
private var launcher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>? = null
|
||||
|
||||
private val account: GoogleSignInAccount? by lazy {
|
||||
GoogleSignIn.getLastSignedInAccount(application)
|
||||
}
|
||||
|
||||
private val lastGoogleToken: String? by lazy {
|
||||
GoogleSignIn.getLastSignedInAccount(application)?.idToken
|
||||
}
|
||||
|
||||
private val state = mutableStateOf(
|
||||
when (account) {
|
||||
null -> Authentication.Initial
|
||||
else -> Authentication.Success
|
||||
when (repository.isAuthenticated.value) {
|
||||
true -> Authentication.Success
|
||||
else -> Authentication.Initial
|
||||
}
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun rememberAuthenticationState(activity: Activity): State<Authentication> {
|
||||
fun rememberAuthenticationState(): State<Authentication> {
|
||||
launcher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.StartIntentSenderForResult(),
|
||||
onResult = {
|
||||
if (it.resultCode == Activity.RESULT_OK) {
|
||||
val credential = Identity
|
||||
.getSignInClient(activity)
|
||||
.getSignInCredentialFromIntent(it.data)
|
||||
state.value = Authentication.Success
|
||||
repository.updateAuthenticationState()
|
||||
} else {
|
||||
state.value = Authentication.Failure
|
||||
repository.updateAuthenticationState()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
|
|
@ -79,10 +69,12 @@ class AuthenticationViewModel @Inject constructor(
|
|||
)
|
||||
} catch (e: SendIntentException) {
|
||||
state.value = Authentication.Failure
|
||||
repository.updateAuthenticationState()
|
||||
}
|
||||
}
|
||||
.addOnFailureListener { e ->
|
||||
state.value = Authentication.Failure
|
||||
repository.updateAuthenticationState()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
|||
import android.net.Uri
|
||||
import androidx.compose.foundation.ScrollState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -77,7 +78,7 @@ fun CharacterDetailScreen(
|
|||
modifier = Modifier.fillMaxSize(),
|
||||
item = viewModel.character,
|
||||
onBack = { },
|
||||
onItem = { },
|
||||
onImage = { },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -89,7 +90,7 @@ private fun CharacterDetailScreenContent(
|
|||
state: ScrollState = rememberScrollState(),
|
||||
item: State<CharacterDetailUio>,
|
||||
onBack: () -> Unit,
|
||||
onItem: () -> Unit,
|
||||
onImage: (Uri) -> Unit,
|
||||
) {
|
||||
val colorScheme = MaterialTheme.colorScheme
|
||||
val typography = MaterialTheme.typography
|
||||
|
|
@ -227,7 +228,9 @@ private fun CharacterDetailScreenContent(
|
|||
) {
|
||||
items(items = item.value.portrait) {
|
||||
GlideImage(
|
||||
modifier = Modifier.height(320.dp),
|
||||
modifier = Modifier
|
||||
.clickable { onImage(it) }
|
||||
.height(320.dp),
|
||||
imageModel = { it },
|
||||
imageOptions = ImageOptions(
|
||||
contentScale = ContentScale.FillHeight
|
||||
|
|
@ -239,7 +242,6 @@ private fun CharacterDetailScreenContent(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -299,7 +301,7 @@ private fun CharacterDetailScreenContentPreview() {
|
|||
modifier = Modifier.fillMaxSize(),
|
||||
item = character,
|
||||
onBack = { },
|
||||
onItem = { },
|
||||
onImage = { },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
package com.pixelized.rplexicon.ui.screens.lexicon
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
|
|
@ -11,16 +15,33 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.pixelized.rplexicon.LocalSnack
|
||||
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
|
||||
import com.pixelized.rplexicon.ui.navigation.navigateToCharacterDetail
|
||||
import com.pixelized.rplexicon.ui.screens.lexicon.LexiconErrorUio.Default
|
||||
import com.pixelized.rplexicon.ui.screens.lexicon.LexiconErrorUio.Permission
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Stable
|
||||
sealed class LexiconErrorUio {
|
||||
@Stable
|
||||
data class Permission(val intent: Intent) : LexiconErrorUio()
|
||||
|
||||
@Stable
|
||||
object Default : LexiconErrorUio()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LexiconScreen(
|
||||
|
|
@ -35,6 +56,13 @@ fun LexiconScreen(
|
|||
screen.navigateToCharacterDetail(id = "")
|
||||
},
|
||||
)
|
||||
|
||||
HandleError(
|
||||
errors = viewModel.error,
|
||||
onLexiconPermissionGranted = {
|
||||
viewModel.fetchLexicon()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -60,6 +88,34 @@ private fun LexiconScreenContent(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HandleError(
|
||||
errors: SharedFlow<LexiconErrorUio>,
|
||||
onLexiconPermissionGranted: suspend () -> Unit,
|
||||
) {
|
||||
val snack = LocalSnack.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val launcher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.StartActivityForResult(),
|
||||
) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
scope.launch {
|
||||
onLexiconPermissionGranted()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(key1 = "LexiconErrorManagement") {
|
||||
errors.collect { error ->
|
||||
when (error) {
|
||||
is Permission -> launcher.launch(error.intent)
|
||||
is Default -> snack.showSnackbar(message = "Oops")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(uiMode = UI_MODE_NIGHT_NO)
|
||||
@Preview(uiMode = UI_MODE_NIGHT_YES)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,17 @@
|
|||
package com.pixelized.rplexicon.ui.screens.lexicon
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException
|
||||
import com.pixelized.rplexicon.model.Lexicon
|
||||
import com.pixelized.rplexicon.repository.LexiconRepository
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -15,28 +20,54 @@ class LexiconViewModel @Inject constructor(
|
|||
private val repository: LexiconRepository,
|
||||
) : ViewModel() {
|
||||
|
||||
// TODO : link it to a paginated DataSource
|
||||
private val _items = mutableStateOf<List<LexiconItemUio>>(emptyList())
|
||||
val items: State<List<LexiconItemUio>> get() = _items
|
||||
|
||||
private val _error = MutableSharedFlow<LexiconErrorUio>()
|
||||
val error: SharedFlow<LexiconErrorUio> get() = _error
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
repository.data.collect { items ->
|
||||
_items.value = items.mapNotNull { item ->
|
||||
item.name?.let {
|
||||
LexiconItemUio(
|
||||
name = item.name,
|
||||
diminutive = item.diminutive?.let { "./ $it" },
|
||||
gender = when (item.gender) {
|
||||
Lexicon.Gender.MALE -> "m."
|
||||
Lexicon.Gender.FEMALE -> "f."
|
||||
Lexicon.Gender.UNDETERMINED -> "u."
|
||||
},
|
||||
race = item.race,
|
||||
)
|
||||
launch {
|
||||
repository.data.collect { items ->
|
||||
_items.value = items.mapNotNull { item ->
|
||||
item.name?.let {
|
||||
LexiconItemUio(
|
||||
name = item.name,
|
||||
diminutive = item.diminutive?.let { "./ $it" },
|
||||
gender = when (item.gender) {
|
||||
Lexicon.Gender.MALE -> "m."
|
||||
Lexicon.Gender.FEMALE -> "f."
|
||||
Lexicon.Gender.UNDETERMINED -> "u."
|
||||
},
|
||||
race = item.race,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
launch {
|
||||
delay(100)
|
||||
fetchLexicon()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun fetchLexicon() {
|
||||
try {
|
||||
repository.fetchLexicon()
|
||||
} catch (exception: UserRecoverableAuthIOException) {
|
||||
Log.e(TAG, exception.message, exception)
|
||||
// user need to accept OAuth2 permission.
|
||||
_error.emit(LexiconErrorUio.Permission(intent = exception.intent))
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, exception.message, exception)
|
||||
_error.emit(LexiconErrorUio.Default)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "LexiconViewModel"
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue