tmp
This commit is contained in:
parent
7898a51252
commit
8fbe3c0b7b
21 changed files with 359 additions and 35 deletions
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
|
|
@ -88,6 +88,8 @@
|
||||||
<entry key="../../../../../layout/compose-model-1620403526292.xml" value="0.16300675675675674" />
|
<entry key="../../../../../layout/compose-model-1620403526292.xml" value="0.16300675675675674" />
|
||||||
<entry key="../../../../../layout/compose-model-1620403554223.xml" value="0.23514851485148514" />
|
<entry key="../../../../../layout/compose-model-1620403554223.xml" value="0.23514851485148514" />
|
||||||
<entry key="../../../../../layout/compose-model-1620403674440.xml" value="0.2962962962962963" />
|
<entry key="../../../../../layout/compose-model-1620403674440.xml" value="0.2962962962962963" />
|
||||||
|
<entry key="../../../../../layout/compose-model-1620414619902.xml" value="0.28125" />
|
||||||
|
<entry key="../../../../../layout/compose-model-1620421200338.xml" value="0.3" />
|
||||||
<entry key="app/src/main/res/drawable-v24/ic_launcher_foreground.xml" value="0.2898148148148148" />
|
<entry key="app/src/main/res/drawable-v24/ic_launcher_foreground.xml" value="0.2898148148148148" />
|
||||||
<entry key="app/src/main/res/drawable/ic_baseline_local_library_24.xml" value="0.25462962962962965" />
|
<entry key="app/src/main/res/drawable/ic_baseline_local_library_24.xml" value="0.25462962962962965" />
|
||||||
<entry key="app/src/main/res/drawable/ic_google.xml" value="0.2962962962962963" />
|
<entry key="app/src/main/res/drawable/ic_google.xml" value="0.2962962962962963" />
|
||||||
|
|
|
||||||
|
|
@ -3,21 +3,24 @@
|
||||||
package="com.pixelized.biblib">
|
package="com.pixelized.biblib">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name=".BibLibApplication"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
|
android:fullBackupOnly="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.MaterialComponents.DayNight.NoActionBar">
|
android:theme="@style/Theme.MaterialComponents.DayNight.NoActionBar">
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.MainActivity"
|
android:name=".ui.MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:label="@string/app_name">
|
android:label="@string/app_name">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
|
||||||
22
app/src/main/java/com/pixelized/biblib/BibLibApplication.kt
Normal file
22
app/src/main/java/com/pixelized/biblib/BibLibApplication.kt
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
package com.pixelized.biblib
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.GsonBuilder
|
||||||
|
import com.pixelized.biblib.injection.Bob
|
||||||
|
import com.pixelized.biblib.network.client.BibLibClient
|
||||||
|
import com.pixelized.biblib.network.client.IBibLibClient
|
||||||
|
import com.pixelized.biblib.utils.BitmapCache
|
||||||
|
|
||||||
|
class BibLibApplication : Application() {
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
|
||||||
|
Bob[BitmapCache::class] = BitmapCache(this)
|
||||||
|
|
||||||
|
Bob[Gson::class] = GsonBuilder().create()
|
||||||
|
|
||||||
|
Bob[IBibLibClient::class] = BibLibClient()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package com.pixelized.biblib.data.network.query
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
data class AuthLoginQuery(
|
||||||
|
@SerializedName("username")
|
||||||
|
val username: String?,
|
||||||
|
@SerializedName("password")
|
||||||
|
val password: String?,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.pixelized.biblib.data.network.response
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
data class AuthLoginResponse(
|
||||||
|
@SerializedName("id_token")
|
||||||
|
val token: String? = null,
|
||||||
|
) : ErrorResponse()
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.pixelized.biblib.data.network.response
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
open class ErrorResponse(
|
||||||
|
@SerializedName("error")
|
||||||
|
val error: Error? = null,
|
||||||
|
@SerializedName("message")
|
||||||
|
val message: String? = null,
|
||||||
|
) {
|
||||||
|
val isError: Boolean get() = error != null
|
||||||
|
|
||||||
|
class Error(
|
||||||
|
@SerializedName("expose")
|
||||||
|
val expose: Boolean? = null,
|
||||||
|
@SerializedName("statusCode")
|
||||||
|
val statusCode: Int? = null,
|
||||||
|
@SerializedName("status")
|
||||||
|
val status: Int? = null,
|
||||||
|
@SerializedName("body")
|
||||||
|
val body: String? = null,
|
||||||
|
@SerializedName("type")
|
||||||
|
val type: String? = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package com.pixelized.biblib.data.ui
|
package com.pixelized.biblib.data.ui
|
||||||
|
|
||||||
import com.pixelized.biblib.utils.Constant.THUMBNAIL_URL
|
import com.pixelized.biblib.network.client.IBibLibClient.Companion.THUMBNAIL_URL
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
|
||||||
data class BookThumbnailUio(
|
data class BookThumbnailUio(
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package com.pixelized.biblib.data.ui
|
package com.pixelized.biblib.data.ui
|
||||||
|
|
||||||
import com.pixelized.biblib.utils.Constant
|
import com.pixelized.biblib.network.client.IBibLibClient.Companion.COVER_URL
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
|
||||||
data class BookUio(
|
data class BookUio(
|
||||||
|
|
@ -14,5 +14,5 @@ data class BookUio(
|
||||||
val series: String?,
|
val series: String?,
|
||||||
val description: String,
|
val description: String,
|
||||||
) {
|
) {
|
||||||
val cover: URL = URL("${Constant.COVER_URL}/$id.jpg")
|
val cover: URL = URL("${COVER_URL}/$id.jpg")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
21
app/src/main/java/com/pixelized/biblib/injection/Bob.kt
Normal file
21
app/src/main/java/com/pixelized/biblib/injection/Bob.kt
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.pixelized.biblib.injection
|
||||||
|
|
||||||
|
import com.pixelized.biblib.utils.exception.InjectionException
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
object Bob {
|
||||||
|
private val components = hashMapOf<KClass<*>, Any>()
|
||||||
|
|
||||||
|
operator fun <I : Any, O : I> set(clazz: KClass<I>, component: O) {
|
||||||
|
components[clazz] = component
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun <T> get(clazz: KClass<*>): T {
|
||||||
|
return components[clazz] as? T ?: throw InjectionException(clazz)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T> get(): T = Bob[T::class]
|
||||||
|
|
||||||
|
inline fun <reified T> inject(): Lazy<T> = lazy { Bob[T::class] }
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.pixelized.biblib.network.client
|
||||||
|
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.Response
|
||||||
|
|
||||||
|
class BearerInterceptor : Interceptor {
|
||||||
|
private val bearer get() = "$BEARER $token"
|
||||||
|
var token: String? = null
|
||||||
|
|
||||||
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
var request = chain.request()
|
||||||
|
if (request.header(NO_AUTHORIZATION) == null && token.isNullOrEmpty().not()) {
|
||||||
|
request = request.newBuilder().addHeader(AUTHORIZATION, bearer).build()
|
||||||
|
}
|
||||||
|
return chain.proceed(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val NO_AUTHORIZATION = "No-Authentication"
|
||||||
|
private const val AUTHORIZATION = "Authorization"
|
||||||
|
private const val BEARER = "Bearer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package com.pixelized.biblib.network.client
|
||||||
|
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.pixelized.biblib.injection.inject
|
||||||
|
import com.pixelized.biblib.network.client.IBibLibClient.Companion.BASE_URL
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
import retrofit2.converter.gson.GsonConverterFactory
|
||||||
|
|
||||||
|
class BibLibClient : IBibLibClient {
|
||||||
|
private val gson by inject<Gson>()
|
||||||
|
private val interceptor = BearerInterceptor()
|
||||||
|
private val retrofit: Retrofit = Retrofit.Builder()
|
||||||
|
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||||
|
.client(OkHttpClient.Builder().addInterceptor(interceptor).build())
|
||||||
|
.baseUrl(BASE_URL)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
override val service: IBibLibWebServiceAPI = retrofit.create(IBibLibWebServiceAPI::class.java)
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
///////////////////////////////////
|
||||||
|
// region BibLib webservice Auth
|
||||||
|
|
||||||
|
override fun updateBearerToken(token: String?) {
|
||||||
|
interceptor.token = token
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package com.pixelized.biblib.network.client
|
||||||
|
|
||||||
|
interface IBibLibClient {
|
||||||
|
|
||||||
|
val service: IBibLibWebServiceAPI
|
||||||
|
|
||||||
|
fun updateBearerToken(token: String?)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val BASE_URL = "https://bib.bibulle.fr"
|
||||||
|
const val THUMBNAIL_URL = "$BASE_URL/api/book/thumbnail"
|
||||||
|
const val COVER_URL = "$BASE_URL/api/book/cover"
|
||||||
|
const val REGISTER_URL = "$BASE_URL/signup"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
package com.pixelized.biblib.network.client
|
||||||
|
|
||||||
|
import com.pixelized.biblib.data.network.query.AuthLoginQuery
|
||||||
|
import com.pixelized.biblib.data.network.response.AuthLoginResponse
|
||||||
|
import retrofit2.http.Body
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.POST
|
||||||
|
import retrofit2.http.Query
|
||||||
|
|
||||||
|
interface IBibLibWebServiceAPI {
|
||||||
|
@POST("/authent/login")
|
||||||
|
suspend fun login(@Body query: AuthLoginQuery): AuthLoginResponse
|
||||||
|
|
||||||
|
@GET("/authent/google-id-token")
|
||||||
|
suspend fun loginWithGoogle(@Query("id_token") token: String): AuthLoginResponse
|
||||||
|
|
||||||
|
// @GET("/authent/user")
|
||||||
|
// suspend fun user(): UserResponse
|
||||||
|
//
|
||||||
|
// @GET("/api/book/new")
|
||||||
|
// suspend fun new(): BookListResponse
|
||||||
|
//
|
||||||
|
// @GET("/api/book")
|
||||||
|
// suspend fun list(): BookListResponse
|
||||||
|
//
|
||||||
|
// @GET("/api/book/{id}")
|
||||||
|
// suspend fun detail(@Path("id") bookId: Int): BookDetailResponse
|
||||||
|
//
|
||||||
|
// @GET("/api/book/{id}/send/kindle")
|
||||||
|
// suspend fun send(@Path("id") bookId: Int, @Query("mail") mail: String): LinkedTreeMap<String, Any>
|
||||||
|
//
|
||||||
|
// @GET("/api/book/{id}/epub/url")
|
||||||
|
// suspend fun epub(@Path("id") bookId: Int): TokenResponse
|
||||||
|
//
|
||||||
|
// @GET("/api/book/{id}/mobi/url")
|
||||||
|
// suspend fun mobi(@Path("id") bookId: Int): TokenResponse
|
||||||
|
//
|
||||||
|
// @GET("/api/book/{id}/epub")
|
||||||
|
// fun downloadEpub(@Path("id") bookId: Int, @Query("token") token: String): Call<ResponseBody>
|
||||||
|
//
|
||||||
|
// @GET("/api/book/{id}/mobi")
|
||||||
|
// fun downloadMobi(@Path("id") bookId: Int, @Query("token") token: String): Call<ResponseBody>
|
||||||
|
}
|
||||||
|
|
@ -5,28 +5,37 @@ import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.animation.core.tween
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.google.android.gms.auth.api.signin.GoogleSignIn
|
||||||
|
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
|
||||||
|
import com.pixelized.biblib.R
|
||||||
import com.pixelized.biblib.ui.composable.screen.LoginScreenComposable
|
import com.pixelized.biblib.ui.composable.screen.LoginScreenComposable
|
||||||
import com.pixelized.biblib.ui.composable.screen.MainScreenComposable
|
import com.pixelized.biblib.ui.composable.screen.MainScreenComposable
|
||||||
import com.pixelized.biblib.ui.composable.screen.SplashScreenComposable
|
import com.pixelized.biblib.ui.composable.screen.SplashScreenComposable
|
||||||
import com.pixelized.biblib.ui.theme.BibLibTheme
|
import com.pixelized.biblib.ui.theme.BibLibTheme
|
||||||
import com.pixelized.biblib.ui.viewmodel.AuthenticationViewModel
|
|
||||||
import com.pixelized.biblib.ui.viewmodel.NavigationViewModel
|
import com.pixelized.biblib.ui.viewmodel.NavigationViewModel
|
||||||
import com.pixelized.biblib.ui.viewmodel.NavigationViewModel.Screen
|
import com.pixelized.biblib.ui.viewmodel.NavigationViewModel.Screen
|
||||||
import com.pixelized.biblib.utils.BitmapCache
|
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
private val navigationViewModel: NavigationViewModel by viewModels()
|
private val navigationViewModel: NavigationViewModel by viewModels()
|
||||||
|
|
||||||
|
private val googleSignInOption by lazy {
|
||||||
|
GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
|
||||||
|
.requestIdToken(getString(R.string.biblib_server_id))
|
||||||
|
.requestEmail()
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
val googleSignIn by lazy {
|
||||||
|
GoogleSignIn.getClient(this, googleSignInOption)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
BitmapCache.init(this)
|
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
BibLibTheme {
|
BibLibTheme {
|
||||||
ContentComposable()
|
ContentComposable()
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
package com.pixelized.biblib.ui.composable.screen
|
package com.pixelized.biblib.ui.composable.screen
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
|
@ -19,6 +24,7 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
|
@ -28,7 +34,11 @@ import androidx.compose.ui.text.input.VisualTransformation
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.google.android.gms.auth.api.signin.GoogleSignIn
|
||||||
|
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
|
||||||
|
import com.google.android.gms.common.api.ApiException
|
||||||
import com.pixelized.biblib.R
|
import com.pixelized.biblib.R
|
||||||
|
import com.pixelized.biblib.ui.MainActivity
|
||||||
import com.pixelized.biblib.ui.theme.BibLibTheme
|
import com.pixelized.biblib.ui.theme.BibLibTheme
|
||||||
import com.pixelized.biblib.ui.viewmodel.AuthenticationViewModel
|
import com.pixelized.biblib.ui.viewmodel.AuthenticationViewModel
|
||||||
import com.pixelized.biblib.ui.viewmodel.NavigationViewModel
|
import com.pixelized.biblib.ui.viewmodel.NavigationViewModel
|
||||||
|
|
@ -43,10 +53,35 @@ fun LoginScreenComposablePreview() {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LoginScreenComposable(
|
fun LoginScreenComposable(
|
||||||
|
navigationViewModel: NavigationViewModel = viewModel(),
|
||||||
|
authenticationViewModel: AuthenticationViewModel = viewModel(),
|
||||||
) {
|
) {
|
||||||
val navigationViewModel = viewModel<NavigationViewModel>()
|
// TODO : c'est de la merde ça
|
||||||
val authenticationViewModel = viewModel<AuthenticationViewModel>()
|
val activity = LocalContext.current as MainActivity
|
||||||
|
val result = remember { mutableStateOf<String?>(null) }
|
||||||
|
val launcher =
|
||||||
|
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
|
try {
|
||||||
|
val task = GoogleSignIn.getSignedInAccountFromIntent(it.data)
|
||||||
|
val account: GoogleSignInAccount? = task.getResult(ApiException::class.java)
|
||||||
|
|
||||||
|
val idToken = account?.idToken
|
||||||
|
// if (idToken != null) {
|
||||||
|
// viewModel.loginWithGoogle(idToken).observeLogin()
|
||||||
|
// } else {
|
||||||
|
// Toast.makeText(requireActivity(), "GoogleSignIn missing Token", Toast.LENGTH_SHORT).show()
|
||||||
|
// }
|
||||||
|
Log.e("AuthLoginFragment", "idToken: $idToken")
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
// Toast.makeText(requireActivity(), "GoogleSignIn exception: ${exception.message}", Toast.LENGTH_SHORT).show()
|
||||||
|
Log.e("AuthLoginFragment", exception.message, exception)
|
||||||
|
}
|
||||||
|
// // Here we just update the state, but you could imagine
|
||||||
|
// // pre-processing the result, or updating a MutableSharedFlow that
|
||||||
|
// // your composable collects
|
||||||
|
// result.value = it
|
||||||
|
}
|
||||||
|
|
||||||
val typography = MaterialTheme.typography
|
val typography = MaterialTheme.typography
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
|
|
@ -102,14 +137,12 @@ fun LoginScreenComposable(
|
||||||
modifier = Modifier.padding(end = 8.dp),
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
colors = outlinedButtonColors(),
|
colors = outlinedButtonColors(),
|
||||||
onClick = {
|
onClick = {
|
||||||
// TODO:
|
authenticationViewModel.register()
|
||||||
navigationViewModel.navigateTo(NavigationViewModel.Screen.MainScreen)
|
|
||||||
}) {
|
}) {
|
||||||
Text(text = stringResource(id = R.string.action_register))
|
Text(text = stringResource(id = R.string.action_register))
|
||||||
}
|
}
|
||||||
Button(onClick = {
|
Button(onClick = {
|
||||||
// TODO:
|
authenticationViewModel.login()
|
||||||
navigationViewModel.navigateTo(NavigationViewModel.Screen.MainScreen)
|
|
||||||
}) {
|
}) {
|
||||||
Text(text = stringResource(id = R.string.action_login))
|
Text(text = stringResource(id = R.string.action_login))
|
||||||
}
|
}
|
||||||
|
|
@ -121,8 +154,7 @@ fun LoginScreenComposable(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
colors = outlinedButtonColors(),
|
colors = outlinedButtonColors(),
|
||||||
onClick = {
|
onClick = {
|
||||||
// TODO:
|
launcher.launch(activity.googleSignIn.signInIntent)
|
||||||
navigationViewModel.navigateTo(NavigationViewModel.Screen.MainScreen)
|
|
||||||
}) {
|
}) {
|
||||||
Image(
|
Image(
|
||||||
modifier = Modifier.padding(end = 8.dp),
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
|
|
@ -189,7 +221,7 @@ private fun PasswordField(
|
||||||
private fun CredentialRemember(viewModel: AuthenticationViewModel, modifier: Modifier = Modifier) {
|
private fun CredentialRemember(viewModel: AuthenticationViewModel, modifier: Modifier = Modifier) {
|
||||||
val credential = viewModel.rememberCredential.observeAsState()
|
val credential = viewModel.rememberCredential.observeAsState()
|
||||||
Row(modifier = modifier.clickable {
|
Row(modifier = modifier.clickable {
|
||||||
viewModel.updateRememberCredential(credential = credential.value?.not() ?: false)
|
viewModel.updateRememberCredential(rememberCredential = credential.value?.not() ?: false)
|
||||||
}) {
|
}) {
|
||||||
Checkbox(
|
Checkbox(
|
||||||
modifier = Modifier.align(Alignment.CenterVertically),
|
modifier = Modifier.align(Alignment.CenterVertically),
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,24 @@
|
||||||
package com.pixelized.biblib.ui.viewmodel
|
package com.pixelized.biblib.ui.viewmodel
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.core.content.edit
|
||||||
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.pixelized.biblib.data.network.query.AuthLoginQuery
|
||||||
|
import com.pixelized.biblib.injection.inject
|
||||||
|
import com.pixelized.biblib.network.client.IBibLibClient
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class AuthenticationViewModel: ViewModel() {
|
class AuthenticationViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
|
private val preferences = application.getSharedPreferences(SHARED_PREF, Context.MODE_PRIVATE)
|
||||||
|
private val client: IBibLibClient by inject()
|
||||||
|
|
||||||
private val _login = MutableLiveData<String>()
|
private val _login = MutableLiveData<String>()
|
||||||
val login: LiveData<String> get() = _login
|
val login: LiveData<String> get() = _login
|
||||||
|
|
@ -12,9 +26,17 @@ class AuthenticationViewModel: ViewModel() {
|
||||||
private val _password = MutableLiveData<String>()
|
private val _password = MutableLiveData<String>()
|
||||||
val password: LiveData<String> get() = _password
|
val password: LiveData<String> get() = _password
|
||||||
|
|
||||||
private val _rememberCredential = MutableLiveData<Boolean>(false)
|
private val _rememberCredential = MutableLiveData<Boolean>()
|
||||||
val rememberCredential: LiveData<Boolean> get() = _rememberCredential
|
val rememberCredential: LiveData<Boolean> get() = _rememberCredential
|
||||||
|
|
||||||
|
init {
|
||||||
|
viewModelScope.launch(Dispatchers.Main) {
|
||||||
|
_login.value = preferences.login
|
||||||
|
_password.value = preferences.password
|
||||||
|
_rememberCredential.value = preferences.rememberCredential
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun updateLogin(login: String) {
|
fun updateLogin(login: String) {
|
||||||
_login.postValue(login)
|
_login.postValue(login)
|
||||||
}
|
}
|
||||||
|
|
@ -23,7 +45,54 @@ class AuthenticationViewModel: ViewModel() {
|
||||||
_password.postValue(password)
|
_password.postValue(password)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateRememberCredential(credential: Boolean) {
|
fun updateRememberCredential(rememberCredential: Boolean) {
|
||||||
_rememberCredential.postValue(credential)
|
_rememberCredential.postValue(rememberCredential)
|
||||||
|
viewModelScope.launch {
|
||||||
|
preferences.rememberCredential = rememberCredential
|
||||||
|
if (rememberCredential.not()) {
|
||||||
|
preferences.login = null
|
||||||
|
preferences.password = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun register() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun login() {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
if (rememberCredential.value == true) {
|
||||||
|
preferences.login = login.value
|
||||||
|
preferences.password = password.value
|
||||||
|
}
|
||||||
|
// TODO : validation !
|
||||||
|
val query = AuthLoginQuery(
|
||||||
|
username = login.value,
|
||||||
|
password = password.value
|
||||||
|
)
|
||||||
|
// TODO : Repository (token management & co)
|
||||||
|
val response = client.service.login(query)
|
||||||
|
Log.e("pouet", response.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var SharedPreferences.login: String?
|
||||||
|
get() = getString(REMEMBER_USER, null)
|
||||||
|
set(value) = edit { putString(REMEMBER_USER, value) }
|
||||||
|
|
||||||
|
private var SharedPreferences.password: String?
|
||||||
|
get() = getString(REMEMBER_PASSWORD, null)
|
||||||
|
set(value) = edit { putString(REMEMBER_PASSWORD, value) }
|
||||||
|
|
||||||
|
private var SharedPreferences.rememberCredential: Boolean
|
||||||
|
get() = getBoolean(REMEMBER_CREDENTIAL, false)
|
||||||
|
set(value) = edit { putBoolean(REMEMBER_CREDENTIAL, value) }
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val SHARED_PREF = "BIB_LIB_SHARED_PREF"
|
||||||
|
const val REMEMBER_CREDENTIAL = "REMEMBER_CREDENTIAL"
|
||||||
|
const val REMEMBER_USER = "REMEMBER_USER"
|
||||||
|
const val REMEMBER_PASSWORD = "REMEMBER_PASSWORD"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package com.pixelized.biblib.utils
|
package com.pixelized.biblib.utils
|
||||||
|
|
||||||
import android.content.Context
|
import android.app.Application
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
|
@ -12,12 +12,11 @@ import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
|
||||||
object BitmapCache {
|
class BitmapCache(application: Application) {
|
||||||
private val scope = CoroutineScope(Dispatchers.IO)
|
|
||||||
private var cache: File? = null
|
private var cache: File? = null
|
||||||
|
|
||||||
fun init(context: Context) {
|
init {
|
||||||
cache = context.cacheDir
|
cache = application.cacheDir
|
||||||
}
|
}
|
||||||
|
|
||||||
fun writeToDisk(url: URL, bitmap: Bitmap) {
|
fun writeToDisk(url: URL, bitmap: Bitmap) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.pixelized.biblib.utils.exception
|
||||||
|
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
class InjectionException(clazz: KClass<*>) : RuntimeException("InjectionException occur for class:${clazz}")
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
package com.pixelized.biblib.utils.exception
|
||||||
|
|
||||||
|
class NoBearerException : RuntimeException("Bearer token is null")
|
||||||
|
|
@ -5,18 +5,19 @@ import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.ui.graphics.asImageBitmap
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
import androidx.compose.ui.graphics.painter.BitmapPainter
|
import androidx.compose.ui.graphics.painter.BitmapPainter
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
|
import com.pixelized.biblib.injection.get
|
||||||
import com.pixelized.biblib.utils.BitmapCache
|
import com.pixelized.biblib.utils.BitmapCache
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
|
||||||
|
|
||||||
fun URL.toImage(placeHolder: Painter): State<Painter> {
|
fun URL.toImage(placeHolder: Painter): State<Painter> {
|
||||||
val cached = BitmapCache.readFromDisk(this)?.let { BitmapPainter(it.asImageBitmap()) }
|
val cache: BitmapCache = get()
|
||||||
val state = mutableStateOf(cached ?: placeHolder)
|
val resource = cache.readFromDisk(this)?.let { BitmapPainter(it.asImageBitmap()) }
|
||||||
|
val state = mutableStateOf(resource ?: placeHolder)
|
||||||
|
|
||||||
if (cached == null) {
|
if (resource == null) {
|
||||||
BitmapCache.download(url = this) { downloaded ->
|
cache.download(url = this) { downloaded ->
|
||||||
if (downloaded != null) {
|
if (downloaded != null) {
|
||||||
BitmapCache.writeToDisk(this, downloaded)
|
cache.writeToDisk(this, downloaded)
|
||||||
state.value = BitmapPainter(downloaded.asImageBitmap())
|
state.value = BitmapPainter(downloaded.asImageBitmap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
4
app/src/main/res/values/biblib_server.xml
Normal file
4
app/src/main/res/values/biblib_server.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="biblib_server_id" translatable="false">725701605591-rr2dqeabon4kjpfevoruru65eo3rukmv.apps.googleusercontent.com</string>
|
||||||
|
</resources>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue