add user data fetch support.
This commit is contained in:
		
							parent
							
								
									9ce20ba374
								
							
						
					
					
						commit
						6254ec4f02
					
				
					 28 changed files with 467 additions and 142 deletions
				
			
		| 
						 | 
				
			
			@ -17,8 +17,10 @@ import com.pixelized.biblib.repository.credential.CredentialRepository
 | 
			
		|||
import com.pixelized.biblib.repository.credential.ICredentialRepository
 | 
			
		||||
import com.pixelized.biblib.repository.googleSignIn.GoogleSingInRepository
 | 
			
		||||
import com.pixelized.biblib.repository.googleSignIn.IGoogleSingInRepository
 | 
			
		||||
import com.pixelized.biblib.repository.user.IUserRepository
 | 
			
		||||
import com.pixelized.biblib.repository.user.UserRepository
 | 
			
		||||
import com.pixelized.biblib.utils.BitmapCache
 | 
			
		||||
import com.pixelized.biblib.utils.injection.Bob
 | 
			
		||||
import com.pixelized.biblib.utils.injection.ServiceLocator
 | 
			
		||||
 | 
			
		||||
class BibLibApplication : Application() {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -26,21 +28,22 @@ class BibLibApplication : Application() {
 | 
			
		|||
        super.onCreate()
 | 
			
		||||
 | 
			
		||||
        // Android.
 | 
			
		||||
        Bob[BibLibDatabase::class] =
 | 
			
		||||
        ServiceLocator[BibLibDatabase::class] =
 | 
			
		||||
            Room.databaseBuilder(this, BibLibDatabase::class.java, BibLibDatabase.DATABASE_NAME)
 | 
			
		||||
                .fallbackToDestructiveMigration()
 | 
			
		||||
                .build()
 | 
			
		||||
        Bob[SharedPreferences::class] = getSharedPreferences(SHARED_PREF, Context.MODE_PRIVATE)
 | 
			
		||||
        ServiceLocator[SharedPreferences::class] = getSharedPreferences(SHARED_PREF, Context.MODE_PRIVATE)
 | 
			
		||||
        // Web service.
 | 
			
		||||
        Bob[Gson::class] = GsonBuilder().create()
 | 
			
		||||
        Bob[IBibLibClient::class] = BibLibClient()
 | 
			
		||||
        ServiceLocator[Gson::class] = GsonBuilder().create()
 | 
			
		||||
        ServiceLocator[IBibLibClient::class] = BibLibClient()
 | 
			
		||||
        // Bitmap cache.
 | 
			
		||||
        Bob[BitmapCache::class] = BitmapCache(this)
 | 
			
		||||
        ServiceLocator[BitmapCache::class] = BitmapCache(this)
 | 
			
		||||
        // Repositories.
 | 
			
		||||
        Bob[IGoogleSingInRepository::class] = GoogleSingInRepository(this)
 | 
			
		||||
        Bob[ICredentialRepository::class] = CredentialRepository()
 | 
			
		||||
        Bob[IAPICacheRepository::class] = APICacheRepository()
 | 
			
		||||
        Bob[IBookRepository::class] = BookRepository()
 | 
			
		||||
        ServiceLocator[IGoogleSingInRepository::class] = GoogleSingInRepository(this)
 | 
			
		||||
        ServiceLocator[ICredentialRepository::class] = CredentialRepository()
 | 
			
		||||
        ServiceLocator[IAPICacheRepository::class] = APICacheRepository()
 | 
			
		||||
        ServiceLocator[IBookRepository::class] = BookRepository()
 | 
			
		||||
        ServiceLocator[IUserRepository::class] = UserRepository()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
package com.pixelized.biblib.model
 | 
			
		||||
package com.pixelized.biblib.model.book
 | 
			
		||||
 | 
			
		||||
data class Author(
 | 
			
		||||
    val id: String,
 | 
			
		||||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
package com.pixelized.biblib.model
 | 
			
		||||
package com.pixelized.biblib.model.book
 | 
			
		||||
 | 
			
		||||
import java.util.*
 | 
			
		||||
import java.util.Date
 | 
			
		||||
 | 
			
		||||
data class Book(
 | 
			
		||||
    val id: Int,
 | 
			
		||||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
package com.pixelized.biblib.model
 | 
			
		||||
package com.pixelized.biblib.model.book
 | 
			
		||||
 | 
			
		||||
data class Genre(
 | 
			
		||||
    val id: String,
 | 
			
		||||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
package com.pixelized.biblib.model
 | 
			
		||||
package com.pixelized.biblib.model.book
 | 
			
		||||
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
package com.pixelized.biblib.model
 | 
			
		||||
package com.pixelized.biblib.model.book
 | 
			
		||||
 | 
			
		||||
data class Series(
 | 
			
		||||
    val id: String?,
 | 
			
		||||
							
								
								
									
										13
									
								
								app/src/main/java/com/pixelized/biblib/model/user/User.kt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								app/src/main/java/com/pixelized/biblib/model/user/User.kt
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,13 @@
 | 
			
		|||
package com.pixelized.biblib.model.user
 | 
			
		||||
 | 
			
		||||
import java.util.Date
 | 
			
		||||
 | 
			
		||||
data class User(
 | 
			
		||||
    val id: String,
 | 
			
		||||
    val created: Date,
 | 
			
		||||
    val updated: Date,
 | 
			
		||||
    val username: String,
 | 
			
		||||
    val firstname: String?,
 | 
			
		||||
    val lastname: String?,
 | 
			
		||||
    val amazonEmails: List<String>,
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -1,9 +1,11 @@
 | 
			
		|||
package com.pixelized.biblib.network.client
 | 
			
		||||
 | 
			
		||||
import com.google.gson.internal.LinkedTreeMap
 | 
			
		||||
import com.pixelized.biblib.network.data.query.AuthLoginQuery
 | 
			
		||||
import com.pixelized.biblib.network.data.response.AuthLoginResponse
 | 
			
		||||
import com.pixelized.biblib.network.data.response.BookDetailResponse
 | 
			
		||||
import com.pixelized.biblib.network.data.response.BookListResponse
 | 
			
		||||
import com.pixelized.biblib.network.data.response.UserResponse
 | 
			
		||||
import retrofit2.http.*
 | 
			
		||||
 | 
			
		||||
interface IBibLibWebServiceAPI {
 | 
			
		||||
| 
						 | 
				
			
			@ -13,8 +15,8 @@ interface IBibLibWebServiceAPI {
 | 
			
		|||
    @GET("/authent/google-id-token")
 | 
			
		||||
    suspend fun loginWithGoogle(@Query("id_token") token: String): AuthLoginResponse
 | 
			
		||||
 | 
			
		||||
//    @GET("/authent/user")
 | 
			
		||||
//    suspend fun user(): UserResponse
 | 
			
		||||
    @GET("/authent/user")
 | 
			
		||||
    suspend fun user(): UserResponse
 | 
			
		||||
 | 
			
		||||
    @GET("/api/book/new")
 | 
			
		||||
    suspend fun new(): BookListResponse
 | 
			
		||||
| 
						 | 
				
			
			@ -25,9 +27,9 @@ interface IBibLibWebServiceAPI {
 | 
			
		|||
    @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}/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
 | 
			
		||||
//
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,109 @@
 | 
			
		|||
package com.pixelized.biblib.network.data.response
 | 
			
		||||
 | 
			
		||||
import com.google.gson.annotations.SerializedName
 | 
			
		||||
 | 
			
		||||
data class UserResponse(
 | 
			
		||||
    @SerializedName("data")
 | 
			
		||||
    val data: Data? = null,
 | 
			
		||||
) : ErrorResponse() {
 | 
			
		||||
    data class Data(
 | 
			
		||||
        @SerializedName("id")
 | 
			
		||||
        val id: String? = null,
 | 
			
		||||
        @SerializedName("created")
 | 
			
		||||
        val created: String? = null,
 | 
			
		||||
        @SerializedName("updated")
 | 
			
		||||
        val updated: String? = null,
 | 
			
		||||
        @SerializedName("local")
 | 
			
		||||
        val local: Local? = null,
 | 
			
		||||
        @SerializedName("facebook")
 | 
			
		||||
        val facebook: Facebook? = null,
 | 
			
		||||
        @SerializedName("twitter")
 | 
			
		||||
        val twitter: Twitter? = null,
 | 
			
		||||
        @SerializedName("google")
 | 
			
		||||
        val google: Google? = null,
 | 
			
		||||
        @SerializedName("history")
 | 
			
		||||
        val history: History? = null,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    data class Local(
 | 
			
		||||
        @SerializedName("username")
 | 
			
		||||
        val username: String? = null,
 | 
			
		||||
        @SerializedName("firstname")
 | 
			
		||||
        val firstname: String? = null,
 | 
			
		||||
        @SerializedName("lastname")
 | 
			
		||||
        val lastname: String? = null,
 | 
			
		||||
        @SerializedName("email")
 | 
			
		||||
        val email: String? = null,
 | 
			
		||||
        @SerializedName("isAdmin")
 | 
			
		||||
        val isAdmin: String? = null,
 | 
			
		||||
        @SerializedName("amazonEmails")
 | 
			
		||||
        val amazonEmails: List<String>? = null,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    data class Facebook(
 | 
			
		||||
        @SerializedName("id")
 | 
			
		||||
        val id: String? = null,
 | 
			
		||||
        @SerializedName("email")
 | 
			
		||||
        val email: String? = null,
 | 
			
		||||
        @SerializedName("name")
 | 
			
		||||
        val name: String? = null,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    data class Twitter(
 | 
			
		||||
        @SerializedName("id")
 | 
			
		||||
        val id: String? = null,
 | 
			
		||||
        @SerializedName("email")
 | 
			
		||||
        val email: String? = null,
 | 
			
		||||
        @SerializedName("name")
 | 
			
		||||
        val name: String? = null,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    data class Google(
 | 
			
		||||
        @SerializedName("id")
 | 
			
		||||
        val id: String? = null,
 | 
			
		||||
        @SerializedName("email")
 | 
			
		||||
        val email: String? = null,
 | 
			
		||||
        @SerializedName("name")
 | 
			
		||||
        val name: String? = null,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    data class History(
 | 
			
		||||
        @SerializedName("lastConnection")
 | 
			
		||||
        val lastConnection: String? = null,
 | 
			
		||||
        @SerializedName("downloadedBooks")
 | 
			
		||||
        val downloadedBooks: List<EBook>? = null,
 | 
			
		||||
        @SerializedName("ratings")
 | 
			
		||||
        val ratings: List<Rating>? = null,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    data class EBook(
 | 
			
		||||
        @SerializedName("id")
 | 
			
		||||
        val id: Int? = null,
 | 
			
		||||
        @SerializedName("date")
 | 
			
		||||
        val date: String? = null,
 | 
			
		||||
        @SerializedName("data")
 | 
			
		||||
        val data: MetaEBook? = null,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    data class MetaEBook(
 | 
			
		||||
        @SerializedName("data_id")
 | 
			
		||||
        val data_id: String? = null,
 | 
			
		||||
        @SerializedName("data_format")
 | 
			
		||||
        val data_format: String? = null,
 | 
			
		||||
        @SerializedName("data_size")
 | 
			
		||||
        val data_size: String? = null,
 | 
			
		||||
        @SerializedName("data_name")
 | 
			
		||||
        val data_name: String? = null,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    data class Rating(
 | 
			
		||||
        @SerializedName("book_id")
 | 
			
		||||
        val book_id: Int? = null,
 | 
			
		||||
        @SerializedName("rating")
 | 
			
		||||
        val rating: Float? = null,
 | 
			
		||||
        @SerializedName("book_name")
 | 
			
		||||
        val book_name: String? = null,
 | 
			
		||||
        @SerializedName("date")
 | 
			
		||||
        val date: String? = null,
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
package com.pixelized.biblib.network.factory
 | 
			
		||||
 | 
			
		||||
import com.pixelized.biblib.model.*
 | 
			
		||||
import com.pixelized.biblib.model.book.*
 | 
			
		||||
import com.pixelized.biblib.network.data.response.BookDetailResponse
 | 
			
		||||
import com.pixelized.biblib.network.data.response.BookListResponse
 | 
			
		||||
import com.pixelized.biblib.utils.exception.MandatoryFieldMissingException
 | 
			
		||||
| 
						 | 
				
			
			@ -9,7 +9,7 @@ import java.text.SimpleDateFormat
 | 
			
		|||
import java.util.*
 | 
			
		||||
 | 
			
		||||
class BookFactory {
 | 
			
		||||
    private val parser get() = SimpleDateFormat(FORMAT, Locale.getDefault())
 | 
			
		||||
    private val parser get() = SimpleDateFormat(Factory.FORMAT, Locale.getDefault())
 | 
			
		||||
 | 
			
		||||
    fun fromListResponseToBook(
 | 
			
		||||
        response: BookListResponse.Book,
 | 
			
		||||
| 
						 | 
				
			
			@ -160,8 +160,4 @@ class BookFactory {
 | 
			
		|||
            isNew = isNew
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        private const val FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
package com.pixelized.biblib.network.factory
 | 
			
		||||
 | 
			
		||||
object Factory {
 | 
			
		||||
    const val FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,37 @@
 | 
			
		|||
package com.pixelized.biblib.network.factory
 | 
			
		||||
 | 
			
		||||
import com.pixelized.biblib.model.user.User
 | 
			
		||||
import com.pixelized.biblib.network.data.response.UserResponse
 | 
			
		||||
import com.pixelized.biblib.utils.exception.MandatoryFieldMissingException
 | 
			
		||||
import java.text.SimpleDateFormat
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
class UserFactory {
 | 
			
		||||
    private val parser get() = SimpleDateFormat(Factory.FORMAT, Locale.getDefault())
 | 
			
		||||
 | 
			
		||||
    fun fromUserResponseToUser(
 | 
			
		||||
        response: UserResponse
 | 
			
		||||
    ): User {
 | 
			
		||||
        fun error(name: String) =
 | 
			
		||||
            MandatoryFieldMissingException("#fromUserResponseToUser()", name, response)
 | 
			
		||||
 | 
			
		||||
        val id = response.data?.id
 | 
			
		||||
        val created = response.data?.created?.let { parser.parse(it) }
 | 
			
		||||
        val updated = response.data?.updated?.let { parser.parse(it) }
 | 
			
		||||
        val username = response.data?.local?.username
 | 
			
		||||
        val firstname = response.data?.local?.firstname
 | 
			
		||||
        val lastname = response.data?.local?.lastname
 | 
			
		||||
        val amazonEmails = response.data?.local?.amazonEmails
 | 
			
		||||
 | 
			
		||||
        return User(
 | 
			
		||||
            id = id ?: throw error("id"),
 | 
			
		||||
            created = created ?: throw error("created"),
 | 
			
		||||
            updated = updated ?: throw error("updated"),
 | 
			
		||||
            username = username ?: throw error("username"),
 | 
			
		||||
            firstname = firstname,
 | 
			
		||||
            lastname = lastname,
 | 
			
		||||
            amazonEmails = amazonEmails ?: listOf(),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -7,7 +7,7 @@ import com.pixelized.biblib.database.crossref.BookAuthorCrossRef
 | 
			
		|||
import com.pixelized.biblib.database.crossref.BookGenreCrossRef
 | 
			
		||||
import com.pixelized.biblib.database.data.*
 | 
			
		||||
import com.pixelized.biblib.database.relation.BookRelation
 | 
			
		||||
import com.pixelized.biblib.model.*
 | 
			
		||||
import com.pixelized.biblib.model.book.*
 | 
			
		||||
import com.pixelized.biblib.utils.injection.inject
 | 
			
		||||
 | 
			
		||||
class BookRepository : IBookRepository {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
package com.pixelized.biblib.repository.book
 | 
			
		||||
 | 
			
		||||
import androidx.paging.DataSource
 | 
			
		||||
import com.pixelized.biblib.model.Book
 | 
			
		||||
import com.pixelized.biblib.model.book.Book
 | 
			
		||||
 | 
			
		||||
interface IBookRepository {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,8 @@
 | 
			
		|||
package com.pixelized.biblib.repository.user
 | 
			
		||||
 | 
			
		||||
import com.pixelized.biblib.model.user.User
 | 
			
		||||
 | 
			
		||||
interface IUserRepository {
 | 
			
		||||
    suspend fun getUser(forceUpdate: Boolean = false): User
 | 
			
		||||
    suspend fun amazonEmails(): List<String>
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,22 @@
 | 
			
		|||
package com.pixelized.biblib.repository.user
 | 
			
		||||
 | 
			
		||||
import com.pixelized.biblib.model.user.User
 | 
			
		||||
import com.pixelized.biblib.network.client.IBibLibClient
 | 
			
		||||
import com.pixelized.biblib.network.factory.UserFactory
 | 
			
		||||
import com.pixelized.biblib.utils.injection.inject
 | 
			
		||||
 | 
			
		||||
class UserRepository : IUserRepository {
 | 
			
		||||
    private val client: IBibLibClient by inject()
 | 
			
		||||
    private var user: User? = null
 | 
			
		||||
 | 
			
		||||
    override suspend fun getUser(forceUpdate: Boolean): User {
 | 
			
		||||
        return client.service.user().let { response ->
 | 
			
		||||
            val factory = UserFactory()
 | 
			
		||||
            factory.fromUserResponseToUser(response).also { user = it }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun amazonEmails(): List<String> {
 | 
			
		||||
        return user?.amazonEmails ?: listOf()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -10,6 +10,8 @@ import androidx.compose.material.Text
 | 
			
		|||
import androidx.compose.material.icons.Icons
 | 
			
		||||
import androidx.compose.material.icons.filled.*
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.runtime.State
 | 
			
		||||
import androidx.compose.runtime.livedata.observeAsState
 | 
			
		||||
import androidx.compose.ui.Alignment
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.graphics.vector.ImageVector
 | 
			
		||||
| 
						 | 
				
			
			@ -18,12 +20,15 @@ import androidx.compose.ui.tooling.preview.Preview
 | 
			
		|||
import androidx.compose.ui.unit.dp
 | 
			
		||||
import com.pixelized.biblib.BuildConfig
 | 
			
		||||
import com.pixelized.biblib.R
 | 
			
		||||
import com.pixelized.biblib.model.user.User
 | 
			
		||||
import com.pixelized.biblib.ui.theme.BibLibTheme
 | 
			
		||||
import com.pixelized.biblib.ui.viewmodel.user.IUserViewModel
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun BibLibDrawer(
 | 
			
		||||
    userViewModel : IUserViewModel,
 | 
			
		||||
    onNewClick: () -> Unit = {},
 | 
			
		||||
    onBookClick: () -> Unit = {},
 | 
			
		||||
    onSeriesClick: () -> Unit = {},
 | 
			
		||||
| 
						 | 
				
			
			@ -45,13 +50,14 @@ fun BibLibDrawer(
 | 
			
		|||
                    .fillMaxWidth()
 | 
			
		||||
                    .padding(16.dp)
 | 
			
		||||
            ) {
 | 
			
		||||
                val user: State<User?> = userViewModel.user.observeAsState()
 | 
			
		||||
                Text(
 | 
			
		||||
                    style = typography.body1,
 | 
			
		||||
                    text = "R. Daneel Olivaw"
 | 
			
		||||
                    text = user.value?.username ?: ""
 | 
			
		||||
                )
 | 
			
		||||
                Text(
 | 
			
		||||
                    style = typography.caption,
 | 
			
		||||
                    text = "r.daneel.olivaw@biblib.com"
 | 
			
		||||
                    text = user.value?.amazonEmails?.firstOrNull() ?: ""
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -126,6 +132,8 @@ private fun DrawerItem(text: String, imageVector: ImageVector, onClick: () -> Un
 | 
			
		|||
@Composable
 | 
			
		||||
fun BibLibDrawerLightPreview() {
 | 
			
		||||
    BibLibTheme {
 | 
			
		||||
        BibLibDrawer()
 | 
			
		||||
        BibLibDrawer(
 | 
			
		||||
            userViewModel = IUserViewModel.Mock()
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -23,7 +23,7 @@ import com.pixelized.biblib.ui.data.BookThumbnailUio
 | 
			
		|||
import com.pixelized.biblib.ui.theme.BibLibTheme
 | 
			
		||||
import com.pixelized.biblib.ui.theme.Teal200
 | 
			
		||||
import com.pixelized.biblib.utils.BitmapCache
 | 
			
		||||
import com.pixelized.biblib.utils.injection.Bob
 | 
			
		||||
import com.pixelized.biblib.utils.injection.ServiceLocator
 | 
			
		||||
import com.pixelized.biblib.utils.mock.BookThumbnailMock
 | 
			
		||||
 | 
			
		||||
private val THUMBNAIL_WIDTH: Dp = 60.dp
 | 
			
		||||
| 
						 | 
				
			
			@ -157,7 +157,7 @@ private fun Placeholder(modifier: Modifier) = Surface(modifier = modifier, eleva
 | 
			
		|||
@Preview
 | 
			
		||||
@Composable
 | 
			
		||||
fun BookItemLightPreview() {
 | 
			
		||||
    Bob[BitmapCache::class] = BitmapCache(LocalContext.current)
 | 
			
		||||
    ServiceLocator[BitmapCache::class] = BitmapCache(LocalContext.current)
 | 
			
		||||
    BibLibTheme {
 | 
			
		||||
        val mock = BookThumbnailMock()
 | 
			
		||||
        FilledBookItem(thumbnail = mock.bookThumbnail)
 | 
			
		||||
| 
						 | 
				
			
			@ -167,7 +167,7 @@ fun BookItemLightPreview() {
 | 
			
		|||
@Preview
 | 
			
		||||
@Composable
 | 
			
		||||
fun BookItemDarkPreview() {
 | 
			
		||||
    Bob[BitmapCache::class] = BitmapCache(LocalContext.current)
 | 
			
		||||
    ServiceLocator[BitmapCache::class] = BitmapCache(LocalContext.current)
 | 
			
		||||
    BibLibTheme(darkTheme = true) {
 | 
			
		||||
        val mock = BookThumbnailMock()
 | 
			
		||||
        FilledBookItem(thumbnail = mock.bookThumbnail)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,12 @@
 | 
			
		|||
package com.pixelized.biblib.ui.composable.pages
 | 
			
		||||
 | 
			
		||||
import androidx.compose.animation.AnimatedVisibility
 | 
			
		||||
import androidx.compose.animation.ExperimentalAnimationApi
 | 
			
		||||
import androidx.compose.animation.animateContentSize
 | 
			
		||||
import androidx.compose.animation.core.MutableTransitionState
 | 
			
		||||
import androidx.compose.foundation.background
 | 
			
		||||
import androidx.compose.foundation.layout.*
 | 
			
		||||
import androidx.compose.foundation.lazy.LazyColumn
 | 
			
		||||
import androidx.compose.foundation.rememberScrollState
 | 
			
		||||
import androidx.compose.foundation.shape.RoundedCornerShape
 | 
			
		||||
import androidx.compose.foundation.verticalScroll
 | 
			
		||||
| 
						 | 
				
			
			@ -12,6 +17,7 @@ import androidx.compose.material.icons.filled.Send
 | 
			
		|||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.runtime.getValue
 | 
			
		||||
import androidx.compose.runtime.livedata.observeAsState
 | 
			
		||||
import androidx.compose.runtime.remember
 | 
			
		||||
import androidx.compose.ui.Alignment
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.draw.clip
 | 
			
		||||
| 
						 | 
				
			
			@ -34,14 +40,17 @@ import com.pixelized.biblib.ui.theme.BibLibTheme
 | 
			
		|||
import com.pixelized.biblib.ui.theme.Teal200
 | 
			
		||||
import com.pixelized.biblib.ui.viewmodel.book.BooksViewModel
 | 
			
		||||
import com.pixelized.biblib.ui.viewmodel.book.IBooksViewModel
 | 
			
		||||
import com.pixelized.biblib.ui.viewmodel.user.IUserViewModel
 | 
			
		||||
import com.pixelized.biblib.ui.viewmodel.user.UserViewModel
 | 
			
		||||
import com.pixelized.biblib.utils.BitmapCache
 | 
			
		||||
import com.pixelized.biblib.utils.injection.Bob
 | 
			
		||||
import com.pixelized.biblib.utils.injection.ServiceLocator
 | 
			
		||||
import com.pixelized.biblib.utils.mock.BookMock
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun DetailPage(
 | 
			
		||||
    booksViewModel: IBooksViewModel = viewModel<BooksViewModel>(),
 | 
			
		||||
    userViewModel: IUserViewModel = viewModel<UserViewModel>(),
 | 
			
		||||
    bookId: Int
 | 
			
		||||
) {
 | 
			
		||||
    Surface(
 | 
			
		||||
| 
						 | 
				
			
			@ -51,15 +60,41 @@ fun DetailPage(
 | 
			
		|||
        elevation = 4.dp
 | 
			
		||||
    ) {
 | 
			
		||||
        val book by booksViewModel.getBookDetail(bookId).observeAsState()
 | 
			
		||||
        val user by userViewModel.user.observeAsState()
 | 
			
		||||
        book?.let {
 | 
			
		||||
            DetailPage(book = it)
 | 
			
		||||
            DetailPage(
 | 
			
		||||
                book = it,
 | 
			
		||||
                onSendClick = {
 | 
			
		||||
                    val emailCount = user?.amazonEmails?.size ?: 0
 | 
			
		||||
                    when {
 | 
			
		||||
                        emailCount == 1 -> {
 | 
			
		||||
                            val email: String? = user?.amazonEmails?.firstOrNull()
 | 
			
		||||
                            if (email != null) {
 | 
			
		||||
                                booksViewModel.send(id = bookId, mail = email)
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        emailCount > 1 -> {
 | 
			
		||||
                            // TODO choose email popup
 | 
			
		||||
                        }
 | 
			
		||||
                        else -> Unit // TODO warning popup, no email.
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun DetailPage(book: BookUio) {
 | 
			
		||||
@OptIn(ExperimentalAnimationApi::class)
 | 
			
		||||
fun DetailPage(
 | 
			
		||||
    book: BookUio,
 | 
			
		||||
    onEpubClick: () -> Unit = {},
 | 
			
		||||
    onMobiClick: () -> Unit = {},
 | 
			
		||||
    onSendClick: () -> Unit = {},
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    val typography = MaterialTheme.typography
 | 
			
		||||
    val pouet = remember { MutableTransitionState(false) }
 | 
			
		||||
 | 
			
		||||
    Column(
 | 
			
		||||
        modifier = Modifier
 | 
			
		||||
| 
						 | 
				
			
			@ -86,18 +121,21 @@ fun DetailPage(book: BookUio) {
 | 
			
		|||
            Button(
 | 
			
		||||
                modifier = Modifier.padding(end = 4.dp),
 | 
			
		||||
                imageVector = Icons.Default.Download,
 | 
			
		||||
                text = stringResource(id = R.string.action_epub)
 | 
			
		||||
            ) { }
 | 
			
		||||
                text = stringResource(id = R.string.action_epub),
 | 
			
		||||
                onClick = onEpubClick,
 | 
			
		||||
            )
 | 
			
		||||
            Button(
 | 
			
		||||
                modifier = Modifier.padding(horizontal = 4.dp),
 | 
			
		||||
                imageVector = Icons.Default.Download,
 | 
			
		||||
                text = stringResource(id = R.string.action_mobi),
 | 
			
		||||
            ) { }
 | 
			
		||||
                onClick = onMobiClick,
 | 
			
		||||
            )
 | 
			
		||||
            Button(
 | 
			
		||||
                modifier = Modifier.padding(start = 4.dp),
 | 
			
		||||
                imageVector = Icons.Default.Send,
 | 
			
		||||
                text = stringResource(id = R.string.action_send),
 | 
			
		||||
            ) { }
 | 
			
		||||
                onClick = onSendClick,
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        Text(
 | 
			
		||||
            modifier = Modifier
 | 
			
		||||
| 
						 | 
				
			
			@ -141,6 +179,13 @@ fun DetailPage(book: BookUio) {
 | 
			
		|||
            modifier = Modifier.padding(bottom = 16.dp)
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    AnimatedVisibility(
 | 
			
		||||
        visibleState = pouet,
 | 
			
		||||
    ) {
 | 
			
		||||
        SendMailBottomSheet()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
| 
						 | 
				
			
			@ -183,12 +228,30 @@ private fun RowScope.Button(
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
private fun SendMailBottomSheet() {
 | 
			
		||||
    val data = listOf("pouet", "pouet")
 | 
			
		||||
    LazyColumn(modifier = Modifier.fillMaxWidth()) {
 | 
			
		||||
        items(data.size) { index: Int ->
 | 
			
		||||
            Text(text = data[index])
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Preview
 | 
			
		||||
@Composable
 | 
			
		||||
fun DetailPageLightPreview() {
 | 
			
		||||
    Bob[BitmapCache::class] = BitmapCache(LocalContext.current)
 | 
			
		||||
    ServiceLocator[BitmapCache::class] = BitmapCache(LocalContext.current)
 | 
			
		||||
    BibLibTheme {
 | 
			
		||||
        val mock = BookMock()
 | 
			
		||||
        DetailPage(mock.book)
 | 
			
		||||
        val book = BookMock()
 | 
			
		||||
        DetailPage(book.book)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Preview
 | 
			
		||||
@Composable
 | 
			
		||||
fun SendMailBottomSheetPreview() {
 | 
			
		||||
    BibLibTheme {
 | 
			
		||||
        SendMailBottomSheet()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,10 +1,7 @@
 | 
			
		|||
package com.pixelized.biblib.ui.composable.screen
 | 
			
		||||
 | 
			
		||||
import androidx.compose.animation.AnimatedVisibility
 | 
			
		||||
import androidx.compose.animation.ExperimentalAnimationApi
 | 
			
		||||
import androidx.compose.animation.*
 | 
			
		||||
import androidx.compose.animation.core.tween
 | 
			
		||||
import androidx.compose.animation.slideInVertically
 | 
			
		||||
import androidx.compose.animation.slideOutVertically
 | 
			
		||||
import androidx.compose.material.Scaffold
 | 
			
		||||
import androidx.compose.material.rememberScaffoldState
 | 
			
		||||
import androidx.compose.runtime.*
 | 
			
		||||
| 
						 | 
				
			
			@ -22,6 +19,8 @@ import com.pixelized.biblib.ui.viewmodel.book.IBooksViewModel
 | 
			
		|||
import com.pixelized.biblib.ui.viewmodel.navigation.INavigationViewModel
 | 
			
		||||
import com.pixelized.biblib.ui.viewmodel.navigation.INavigationViewModel.Navigable.Page
 | 
			
		||||
import com.pixelized.biblib.ui.viewmodel.navigation.NavigationViewModel
 | 
			
		||||
import com.pixelized.biblib.ui.viewmodel.user.IUserViewModel
 | 
			
		||||
import com.pixelized.biblib.ui.viewmodel.user.UserViewModel
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -30,6 +29,7 @@ import kotlinx.coroutines.launch
 | 
			
		|||
fun HomeScreen(
 | 
			
		||||
    navigationViewModel: INavigationViewModel = viewModel<NavigationViewModel>(),
 | 
			
		||||
    booksViewModel: IBooksViewModel = viewModel<BooksViewModel>(),
 | 
			
		||||
    userViewModel: IUserViewModel = viewModel<UserViewModel>()
 | 
			
		||||
) {
 | 
			
		||||
    // navigation
 | 
			
		||||
    val page by navigationViewModel.page.observeAsState()
 | 
			
		||||
| 
						 | 
				
			
			@ -59,6 +59,7 @@ fun HomeScreen(
 | 
			
		|||
        },
 | 
			
		||||
        drawerContent = {
 | 
			
		||||
            BibLibDrawer(
 | 
			
		||||
                userViewModel = userViewModel,
 | 
			
		||||
                onNewClick = {
 | 
			
		||||
                    coroutineScope.launch { scaffoldState.drawerState.close() }
 | 
			
		||||
                },
 | 
			
		||||
| 
						 | 
				
			
			@ -85,20 +86,19 @@ fun HomeScreen(
 | 
			
		|||
 | 
			
		||||
        AnimatedVisibility(
 | 
			
		||||
            visible = page is Page.Detail,
 | 
			
		||||
            initiallyVisible = false,
 | 
			
		||||
            enter = slideInVertically(
 | 
			
		||||
            enter = slideInHorizontally(
 | 
			
		||||
                animationSpec = tween(Animation.MEDIUM_DURATION),
 | 
			
		||||
                initialOffsetY = { height -> height },
 | 
			
		||||
                initialOffsetX = { width -> width },
 | 
			
		||||
            ),
 | 
			
		||||
            exit = slideOutVertically(
 | 
			
		||||
            exit = slideOutHorizontally(
 | 
			
		||||
                animationSpec = tween(Animation.MEDIUM_DURATION),
 | 
			
		||||
                targetOffsetY = { height -> height },
 | 
			
		||||
                targetOffsetX = { width -> width },
 | 
			
		||||
            ),
 | 
			
		||||
        ) {
 | 
			
		||||
            // Small trick to display the detail page during animation exit.
 | 
			
		||||
            var currentPage by remember { mutableStateOf<Page.Detail?>(null) }
 | 
			
		||||
            currentPage = page as? Page.Detail ?: currentPage
 | 
			
		||||
            currentPage?.let { DetailPage(booksViewModel, it.bookId) }
 | 
			
		||||
            currentPage?.let { DetailPage(booksViewModel, userViewModel, it.bookId) }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -109,7 +109,8 @@ fun MainScreenComposablePreview() {
 | 
			
		|||
    BibLibTheme {
 | 
			
		||||
        HomeScreen(
 | 
			
		||||
            INavigationViewModel.Mock(page = Page.Home.New),
 | 
			
		||||
            IBooksViewModel.Mock()
 | 
			
		||||
            IBooksViewModel.Mock(),
 | 
			
		||||
            IUserViewModel.Mock()
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -3,10 +3,7 @@ package com.pixelized.biblib.ui.composable.screen
 | 
			
		|||
 | 
			
		||||
import android.content.Intent
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import androidx.compose.animation.AnimatedVisibility
 | 
			
		||||
import androidx.compose.animation.ExperimentalAnimationApi
 | 
			
		||||
import androidx.compose.animation.expandVertically
 | 
			
		||||
import androidx.compose.animation.shrinkVertically
 | 
			
		||||
import androidx.compose.foundation.Image
 | 
			
		||||
import androidx.compose.foundation.clickable
 | 
			
		||||
import androidx.compose.foundation.layout.*
 | 
			
		||||
| 
						 | 
				
			
			@ -35,10 +32,11 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation
 | 
			
		|||
import androidx.compose.ui.text.input.VisualTransformation
 | 
			
		||||
import androidx.compose.ui.tooling.preview.Preview
 | 
			
		||||
import androidx.compose.ui.unit.dp
 | 
			
		||||
import androidx.compose.ui.window.Dialog
 | 
			
		||||
import androidx.compose.ui.window.DialogProperties
 | 
			
		||||
import androidx.lifecycle.viewmodel.compose.viewModel
 | 
			
		||||
import com.pixelized.biblib.R
 | 
			
		||||
import com.pixelized.biblib.network.client.IBibLibClient.Companion.REGISTER_URL
 | 
			
		||||
import com.pixelized.biblib.ui.composable.dialog.CrossFadeOverlay
 | 
			
		||||
import com.pixelized.biblib.ui.composable.dialog.ErrorCard
 | 
			
		||||
import com.pixelized.biblib.ui.composable.dialog.LoadingCard
 | 
			
		||||
import com.pixelized.biblib.ui.composable.dialog.SuccessCard
 | 
			
		||||
| 
						 | 
				
			
			@ -110,85 +108,37 @@ private fun Dialogs(
 | 
			
		|||
    val authState by authenticationViewModel.state.observeAsState()
 | 
			
		||||
    val bookState by booksViewModel.state.observeAsState()
 | 
			
		||||
 | 
			
		||||
    val visible = authState !is IAuthenticationViewModel.State.Initial
 | 
			
		||||
            || bookState !is IBooksViewModel.State.Initial
 | 
			
		||||
 | 
			
		||||
    CrossFadeOverlay(
 | 
			
		||||
        modifier = Modifier.clickable {
 | 
			
		||||
            if (visible) {
 | 
			
		||||
                authenticationViewModel.clearState()
 | 
			
		||||
    if (authState !is IAuthenticationViewModel.State.Initial) {
 | 
			
		||||
        val isInError = authState is IAuthenticationViewModel.State.Error
 | 
			
		||||
        Dialog(
 | 
			
		||||
            onDismissRequest = { authenticationViewModel.clearState() },
 | 
			
		||||
            properties = DialogProperties(
 | 
			
		||||
                dismissOnBackPress = isInError,
 | 
			
		||||
                dismissOnClickOutside = isInError
 | 
			
		||||
            )
 | 
			
		||||
        ) {
 | 
			
		||||
            when {
 | 
			
		||||
                bookState is IBooksViewModel.State.Error -> ErrorCard(
 | 
			
		||||
                    message = stringResource(id = R.string.error_book),
 | 
			
		||||
                    exception = (bookState as? IBooksViewModel.State.Error)?.exception
 | 
			
		||||
                )
 | 
			
		||||
                bookState is IBooksViewModel.State.Loading -> LoadingCard(
 | 
			
		||||
                    message = stringResource(id = R.string.loading_book)
 | 
			
		||||
                )
 | 
			
		||||
                bookState is IBooksViewModel.State.Finished -> SuccessCard(
 | 
			
		||||
                    message = stringResource(id = R.string.success_book)
 | 
			
		||||
                )
 | 
			
		||||
                authState is IAuthenticationViewModel.State.Error -> ErrorCard(
 | 
			
		||||
                    message = stringResource(id = R.string.error_authentication),
 | 
			
		||||
                    exception = (authState as? IAuthenticationViewModel.State.Error)?.exception
 | 
			
		||||
                )
 | 
			
		||||
                authState is IAuthenticationViewModel.State.Loading -> LoadingCard(
 | 
			
		||||
                    message = stringResource(id = R.string.loading_authentication)
 | 
			
		||||
                )
 | 
			
		||||
                authState is IAuthenticationViewModel.State.Finished -> SuccessCard(
 | 
			
		||||
                    message = stringResource(id = R.string.success_authentication)
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        visible = visible
 | 
			
		||||
    ) {
 | 
			
		||||
        AnimatedVisibility(
 | 
			
		||||
            modifier = Modifier.align(Alignment.Center),
 | 
			
		||||
            visible = authState is IAuthenticationViewModel.State.Error,
 | 
			
		||||
            initiallyVisible = false,
 | 
			
		||||
            enter = expandVertically(Alignment.CenterVertically),
 | 
			
		||||
            exit = shrinkVertically(Alignment.CenterVertically),
 | 
			
		||||
        ) {
 | 
			
		||||
            ErrorCard(
 | 
			
		||||
                message = stringResource(id = R.string.error_authentication),
 | 
			
		||||
                exception = (authState as? IAuthenticationViewModel.State.Error)?.exception
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        AnimatedVisibility(
 | 
			
		||||
            modifier = Modifier.align(Alignment.Center),
 | 
			
		||||
            visible = authState is IAuthenticationViewModel.State.Loading,
 | 
			
		||||
            initiallyVisible = false,
 | 
			
		||||
            enter = expandVertically(Alignment.CenterVertically),
 | 
			
		||||
            exit = shrinkVertically(Alignment.CenterVertically),
 | 
			
		||||
        ) {
 | 
			
		||||
            LoadingCard(
 | 
			
		||||
                message = stringResource(id = R.string.loading_authentication)
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        AnimatedVisibility(
 | 
			
		||||
            modifier = Modifier.align(Alignment.Center),
 | 
			
		||||
            visible = (authState as? IAuthenticationViewModel.State.Finished)?.logged
 | 
			
		||||
                ?: false,
 | 
			
		||||
            initiallyVisible = false,
 | 
			
		||||
            enter = expandVertically(Alignment.CenterVertically),
 | 
			
		||||
            exit = shrinkVertically(Alignment.CenterVertically),
 | 
			
		||||
        ) {
 | 
			
		||||
            SuccessCard(
 | 
			
		||||
                message = stringResource(id = R.string.success_authentication)
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        AnimatedVisibility(
 | 
			
		||||
            modifier = Modifier.align(Alignment.Center),
 | 
			
		||||
            visible = bookState is IBooksViewModel.State.Error,
 | 
			
		||||
            initiallyVisible = false,
 | 
			
		||||
            enter = expandVertically(Alignment.CenterVertically),
 | 
			
		||||
            exit = shrinkVertically(Alignment.CenterVertically),
 | 
			
		||||
        ) {
 | 
			
		||||
            ErrorCard(
 | 
			
		||||
                message = stringResource(id = R.string.error_book),
 | 
			
		||||
                exception = (bookState as? IBooksViewModel.State.Error)?.exception
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        AnimatedVisibility(
 | 
			
		||||
            modifier = Modifier.align(Alignment.Center),
 | 
			
		||||
            visible = bookState is IBooksViewModel.State.Loading,
 | 
			
		||||
            initiallyVisible = false,
 | 
			
		||||
            enter = expandVertically(Alignment.CenterVertically),
 | 
			
		||||
            exit = shrinkVertically(Alignment.CenterVertically),
 | 
			
		||||
        ) {
 | 
			
		||||
            LoadingCard(
 | 
			
		||||
                message = stringResource(id = R.string.loading_book)
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        AnimatedVisibility(
 | 
			
		||||
            modifier = Modifier.align(Alignment.Center),
 | 
			
		||||
            visible = bookState is IBooksViewModel.State.Finished,
 | 
			
		||||
            initiallyVisible = false,
 | 
			
		||||
            enter = expandVertically(Alignment.CenterVertically),
 | 
			
		||||
            exit = shrinkVertically(Alignment.CenterVertically),
 | 
			
		||||
        ) {
 | 
			
		||||
            SuccessCard(
 | 
			
		||||
                message = stringResource(id = R.string.success_book)
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,12 +28,15 @@ import com.pixelized.biblib.ui.viewmodel.book.IBooksViewModel
 | 
			
		|||
import com.pixelized.biblib.ui.viewmodel.navigation.INavigationViewModel
 | 
			
		||||
import com.pixelized.biblib.ui.viewmodel.navigation.INavigationViewModel.Navigable.Screen
 | 
			
		||||
import com.pixelized.biblib.ui.viewmodel.navigation.NavigationViewModel
 | 
			
		||||
import com.pixelized.biblib.ui.viewmodel.user.IUserViewModel
 | 
			
		||||
import com.pixelized.biblib.ui.viewmodel.user.UserViewModel
 | 
			
		||||
import kotlinx.coroutines.delay
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
private const val LAUNCH_EFFECT_ENTER = "LAUNCH_EFFECT_ENTER"
 | 
			
		||||
private const val LAUNCH_EFFECT_EXIT = "LAUNCH_EFFECT_EXIT"
 | 
			
		||||
private const val LAUNCH_EFFECT_BOOK = "LAUNCH_EFFECT_BOOK"
 | 
			
		||||
private const val LAUNCH_EFFECT_USER = "LAUNCH_EFFECT_USER"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@OptIn(ExperimentalAnimationApi::class)
 | 
			
		||||
| 
						 | 
				
			
			@ -41,11 +44,13 @@ private const val LAUNCH_EFFECT_BOOK = "LAUNCH_EFFECT_BOOK"
 | 
			
		|||
fun SplashScreen(
 | 
			
		||||
    authenticationViewModel: IAuthenticationViewModel = viewModel<AuthenticationViewModel>(),
 | 
			
		||||
    bookViewModel: IBooksViewModel = viewModel<BooksViewModel>(),
 | 
			
		||||
    userViewModel: IUserViewModel = viewModel<UserViewModel>(),
 | 
			
		||||
    navigationViewModel: INavigationViewModel = viewModel<NavigationViewModel>(),
 | 
			
		||||
    initiallyVisible: Boolean = false,
 | 
			
		||||
) {
 | 
			
		||||
    val authenticationState: IAuthenticationViewModel.State? by authenticationViewModel.state.observeAsState()
 | 
			
		||||
    val bookState by bookViewModel.state.observeAsState()
 | 
			
		||||
    val userState by userViewModel.state.observeAsState()
 | 
			
		||||
 | 
			
		||||
    val contentVisibility = remember { mutableStateOf(false) }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -73,6 +78,11 @@ fun SplashScreen(
 | 
			
		|||
                bookViewModel.updateBooks()
 | 
			
		||||
            }
 | 
			
		||||
            (bookState as? IBooksViewModel.State.Finished)?.let {
 | 
			
		||||
                LaunchedEffect(LAUNCH_EFFECT_USER) {
 | 
			
		||||
                    userViewModel.updateUser()
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            (userState as? IUserViewModel.State.Finished)?.let {
 | 
			
		||||
                HandleExitAnimation(contentVisibility) {
 | 
			
		||||
                    navigationViewModel.navigateTo(Screen.MainScreen)
 | 
			
		||||
                }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,7 +9,7 @@ import androidx.paging.Pager
 | 
			
		|||
import androidx.paging.PagingConfig
 | 
			
		||||
import androidx.paging.PagingData
 | 
			
		||||
import androidx.paging.PagingSource
 | 
			
		||||
import com.pixelized.biblib.model.Book
 | 
			
		||||
import com.pixelized.biblib.model.book.Book
 | 
			
		||||
import com.pixelized.biblib.network.client.IBibLibClient
 | 
			
		||||
import com.pixelized.biblib.network.factory.BookFactory
 | 
			
		||||
import com.pixelized.biblib.repository.apiCache.IAPICacheRepository
 | 
			
		||||
| 
						 | 
				
			
			@ -23,6 +23,10 @@ import kotlinx.coroutines.launch
 | 
			
		|||
import java.text.SimpleDateFormat
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * TODO: there is some book related code that should be inside a Repository // DataSource.
 | 
			
		||||
 */
 | 
			
		||||
class BooksViewModel : ViewModel(), IBooksViewModel {
 | 
			
		||||
    private val bookRepository: IBookRepository by inject()
 | 
			
		||||
    private val client: IBibLibClient by inject()
 | 
			
		||||
| 
						 | 
				
			
			@ -79,6 +83,10 @@ class BooksViewModel : ViewModel(), IBooksViewModel {
 | 
			
		|||
        return data
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun send(id: Int, mail: String) {
 | 
			
		||||
        // TODO()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private suspend fun loadNewBooks(): Boolean {
 | 
			
		||||
        val cached = apiCache.new
 | 
			
		||||
        val updated = client.service.new()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,6 +19,8 @@ interface IBooksViewModel {
 | 
			
		|||
 | 
			
		||||
    fun getBookDetail(id: Int): LiveData<BookUio>
 | 
			
		||||
 | 
			
		||||
    fun send(id: Int, mail:String)
 | 
			
		||||
 | 
			
		||||
    sealed class State {
 | 
			
		||||
        object Initial : State()
 | 
			
		||||
        object Loading : State()
 | 
			
		||||
| 
						 | 
				
			
			@ -37,5 +39,6 @@ interface IBooksViewModel {
 | 
			
		|||
 | 
			
		||||
        override fun updateBooks() = Unit
 | 
			
		||||
        override fun getBookDetail(id: Int) = MutableLiveData<BookUio>()
 | 
			
		||||
        override fun send(id: Int, mail: String) = Unit
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,30 @@
 | 
			
		|||
package com.pixelized.biblib.ui.viewmodel.user
 | 
			
		||||
 | 
			
		||||
import androidx.lifecycle.LiveData
 | 
			
		||||
import androidx.lifecycle.MutableLiveData
 | 
			
		||||
import com.pixelized.biblib.R
 | 
			
		||||
import com.pixelized.biblib.model.user.User
 | 
			
		||||
import com.pixelized.biblib.ui.viewmodel.credential.ICredentialViewModel
 | 
			
		||||
import com.pixelized.biblib.utils.mock.UserMock
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
interface IUserViewModel {
 | 
			
		||||
    val state: LiveData<State>
 | 
			
		||||
    val user: LiveData<User>
 | 
			
		||||
 | 
			
		||||
    fun updateUser()
 | 
			
		||||
 | 
			
		||||
    sealed class State {
 | 
			
		||||
        object Initial : State()
 | 
			
		||||
        object Loading : State()
 | 
			
		||||
        object Finished : State()
 | 
			
		||||
        data class Error(val exception: Exception) : State()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    class Mock : IUserViewModel {
 | 
			
		||||
        override val state: LiveData<State> = MutableLiveData()
 | 
			
		||||
        override val user: LiveData<User> = MutableLiveData(UserMock().user)
 | 
			
		||||
 | 
			
		||||
        override fun updateUser() = Unit
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,41 @@
 | 
			
		|||
package com.pixelized.biblib.ui.viewmodel.user
 | 
			
		||||
 | 
			
		||||
import androidx.lifecycle.LiveData
 | 
			
		||||
import androidx.lifecycle.MutableLiveData
 | 
			
		||||
import androidx.lifecycle.ViewModel
 | 
			
		||||
import androidx.lifecycle.viewModelScope
 | 
			
		||||
import com.pixelized.biblib.model.user.User
 | 
			
		||||
import com.pixelized.biblib.repository.user.IUserRepository
 | 
			
		||||
import com.pixelized.biblib.ui.viewmodel.user.IUserViewModel.State
 | 
			
		||||
import com.pixelized.biblib.utils.injection.inject
 | 
			
		||||
import kotlinx.coroutines.Dispatchers
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
 | 
			
		||||
class UserViewModel : ViewModel(), IUserViewModel {
 | 
			
		||||
 | 
			
		||||
    private val userRepository: IUserRepository by inject()
 | 
			
		||||
 | 
			
		||||
    private val _state = MutableLiveData<State>()
 | 
			
		||||
    override val state: LiveData<State> get() = _state
 | 
			
		||||
 | 
			
		||||
    override val user: LiveData<User>
 | 
			
		||||
        get() = MutableLiveData<User>().apply {
 | 
			
		||||
            viewModelScope.launch(Dispatchers.IO) {
 | 
			
		||||
                val user = userRepository.getUser(false)
 | 
			
		||||
                postValue(user)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    override fun updateUser() {
 | 
			
		||||
        viewModelScope.launch(Dispatchers.IO) {
 | 
			
		||||
            try {
 | 
			
		||||
                _state.postValue(State.Initial)
 | 
			
		||||
                _state.postValue(State.Loading)
 | 
			
		||||
                userRepository.getUser(true)
 | 
			
		||||
                _state.postValue(State.Finished)
 | 
			
		||||
            } catch (e: Exception) {
 | 
			
		||||
                _state.postValue(State.Error(e))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -4,7 +4,7 @@ import com.pixelized.biblib.utils.exception.InjectionException
 | 
			
		|||
import kotlin.reflect.KClass
 | 
			
		||||
 | 
			
		||||
@Suppress("UNCHECKED_CAST")
 | 
			
		||||
object Bob {
 | 
			
		||||
object ServiceLocator {
 | 
			
		||||
    private val components = hashMapOf<KClass<*>, Any>()
 | 
			
		||||
 | 
			
		||||
    operator fun <I : Any, O : I> set(clazz: KClass<I>, component: O) {
 | 
			
		||||
| 
						 | 
				
			
			@ -16,6 +16,6 @@ object Bob {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline fun <reified T> get(): T = Bob[T::class]
 | 
			
		||||
inline fun <reified T> get(): T = ServiceLocator[T::class]
 | 
			
		||||
 | 
			
		||||
inline fun <reified T> inject(): Lazy<T> = lazy { Bob[T::class] }
 | 
			
		||||
inline fun <reified T> inject(): Lazy<T> = lazy { ServiceLocator[T::class] }
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,16 @@
 | 
			
		|||
package com.pixelized.biblib.utils.mock
 | 
			
		||||
 | 
			
		||||
import com.pixelized.biblib.model.user.User
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
class UserMock {
 | 
			
		||||
    val user = User(
 | 
			
		||||
        id = "",
 | 
			
		||||
        created = Date(),
 | 
			
		||||
        updated = Date(),
 | 
			
		||||
        username = "R. Daneel Olivaw",
 | 
			
		||||
        firstname = "R. Daneel",
 | 
			
		||||
        lastname = "Olivaw",
 | 
			
		||||
        amazonEmails = listOf("r.daneel.olivaw@biblib.com"),
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue