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.credential.ICredentialRepository
|
||||||
import com.pixelized.biblib.repository.googleSignIn.GoogleSingInRepository
|
import com.pixelized.biblib.repository.googleSignIn.GoogleSingInRepository
|
||||||
import com.pixelized.biblib.repository.googleSignIn.IGoogleSingInRepository
|
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.BitmapCache
|
||||||
import com.pixelized.biblib.utils.injection.Bob
|
import com.pixelized.biblib.utils.injection.ServiceLocator
|
||||||
|
|
||||||
class BibLibApplication : Application() {
|
class BibLibApplication : Application() {
|
||||||
|
|
||||||
|
|
@ -26,21 +28,22 @@ class BibLibApplication : Application() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
|
||||||
// Android.
|
// Android.
|
||||||
Bob[BibLibDatabase::class] =
|
ServiceLocator[BibLibDatabase::class] =
|
||||||
Room.databaseBuilder(this, BibLibDatabase::class.java, BibLibDatabase.DATABASE_NAME)
|
Room.databaseBuilder(this, BibLibDatabase::class.java, BibLibDatabase.DATABASE_NAME)
|
||||||
.fallbackToDestructiveMigration()
|
.fallbackToDestructiveMigration()
|
||||||
.build()
|
.build()
|
||||||
Bob[SharedPreferences::class] = getSharedPreferences(SHARED_PREF, Context.MODE_PRIVATE)
|
ServiceLocator[SharedPreferences::class] = getSharedPreferences(SHARED_PREF, Context.MODE_PRIVATE)
|
||||||
// Web service.
|
// Web service.
|
||||||
Bob[Gson::class] = GsonBuilder().create()
|
ServiceLocator[Gson::class] = GsonBuilder().create()
|
||||||
Bob[IBibLibClient::class] = BibLibClient()
|
ServiceLocator[IBibLibClient::class] = BibLibClient()
|
||||||
// Bitmap cache.
|
// Bitmap cache.
|
||||||
Bob[BitmapCache::class] = BitmapCache(this)
|
ServiceLocator[BitmapCache::class] = BitmapCache(this)
|
||||||
// Repositories.
|
// Repositories.
|
||||||
Bob[IGoogleSingInRepository::class] = GoogleSingInRepository(this)
|
ServiceLocator[IGoogleSingInRepository::class] = GoogleSingInRepository(this)
|
||||||
Bob[ICredentialRepository::class] = CredentialRepository()
|
ServiceLocator[ICredentialRepository::class] = CredentialRepository()
|
||||||
Bob[IAPICacheRepository::class] = APICacheRepository()
|
ServiceLocator[IAPICacheRepository::class] = APICacheRepository()
|
||||||
Bob[IBookRepository::class] = BookRepository()
|
ServiceLocator[IBookRepository::class] = BookRepository()
|
||||||
|
ServiceLocator[IUserRepository::class] = UserRepository()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.pixelized.biblib.model
|
package com.pixelized.biblib.model.book
|
||||||
|
|
||||||
data class Author(
|
data class Author(
|
||||||
val id: String,
|
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(
|
data class Book(
|
||||||
val id: Int,
|
val id: Int,
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.pixelized.biblib.model
|
package com.pixelized.biblib.model.book
|
||||||
|
|
||||||
data class Genre(
|
data class Genre(
|
||||||
val id: String,
|
val id: String,
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.pixelized.biblib.model
|
package com.pixelized.biblib.model.book
|
||||||
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.pixelized.biblib.model
|
package com.pixelized.biblib.model.book
|
||||||
|
|
||||||
data class Series(
|
data class Series(
|
||||||
val id: String?,
|
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
|
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.query.AuthLoginQuery
|
||||||
import com.pixelized.biblib.network.data.response.AuthLoginResponse
|
import com.pixelized.biblib.network.data.response.AuthLoginResponse
|
||||||
import com.pixelized.biblib.network.data.response.BookDetailResponse
|
import com.pixelized.biblib.network.data.response.BookDetailResponse
|
||||||
import com.pixelized.biblib.network.data.response.BookListResponse
|
import com.pixelized.biblib.network.data.response.BookListResponse
|
||||||
|
import com.pixelized.biblib.network.data.response.UserResponse
|
||||||
import retrofit2.http.*
|
import retrofit2.http.*
|
||||||
|
|
||||||
interface IBibLibWebServiceAPI {
|
interface IBibLibWebServiceAPI {
|
||||||
|
|
@ -13,8 +15,8 @@ interface IBibLibWebServiceAPI {
|
||||||
@GET("/authent/google-id-token")
|
@GET("/authent/google-id-token")
|
||||||
suspend fun loginWithGoogle(@Query("id_token") token: String): AuthLoginResponse
|
suspend fun loginWithGoogle(@Query("id_token") token: String): AuthLoginResponse
|
||||||
|
|
||||||
// @GET("/authent/user")
|
@GET("/authent/user")
|
||||||
// suspend fun user(): UserResponse
|
suspend fun user(): UserResponse
|
||||||
|
|
||||||
@GET("/api/book/new")
|
@GET("/api/book/new")
|
||||||
suspend fun new(): BookListResponse
|
suspend fun new(): BookListResponse
|
||||||
|
|
@ -25,9 +27,9 @@ interface IBibLibWebServiceAPI {
|
||||||
@GET("/api/book/{id}")
|
@GET("/api/book/{id}")
|
||||||
suspend fun detail(@Path("id") bookId: Int): BookDetailResponse
|
suspend fun detail(@Path("id") bookId: Int): BookDetailResponse
|
||||||
|
|
||||||
// @GET("/api/book/{id}/send/kindle")
|
@GET("/api/book/{id}/send/kindle")
|
||||||
// suspend fun send(@Path("id") bookId: Int, @Query("mail") mail: String): LinkedTreeMap<String, Any>
|
suspend fun send(@Path("id") bookId: Int, @Query("mail") mail: String): LinkedTreeMap<String, Any>
|
||||||
//
|
|
||||||
// @GET("/api/book/{id}/epub/url")
|
// @GET("/api/book/{id}/epub/url")
|
||||||
// suspend fun epub(@Path("id") bookId: Int): TokenResponse
|
// 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
|
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.BookDetailResponse
|
||||||
import com.pixelized.biblib.network.data.response.BookListResponse
|
import com.pixelized.biblib.network.data.response.BookListResponse
|
||||||
import com.pixelized.biblib.utils.exception.MandatoryFieldMissingException
|
import com.pixelized.biblib.utils.exception.MandatoryFieldMissingException
|
||||||
|
|
@ -9,7 +9,7 @@ import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class BookFactory {
|
class BookFactory {
|
||||||
private val parser get() = SimpleDateFormat(FORMAT, Locale.getDefault())
|
private val parser get() = SimpleDateFormat(Factory.FORMAT, Locale.getDefault())
|
||||||
|
|
||||||
fun fromListResponseToBook(
|
fun fromListResponseToBook(
|
||||||
response: BookListResponse.Book,
|
response: BookListResponse.Book,
|
||||||
|
|
@ -160,8 +160,4 @@ class BookFactory {
|
||||||
isNew = isNew
|
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.crossref.BookGenreCrossRef
|
||||||
import com.pixelized.biblib.database.data.*
|
import com.pixelized.biblib.database.data.*
|
||||||
import com.pixelized.biblib.database.relation.BookRelation
|
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
|
import com.pixelized.biblib.utils.injection.inject
|
||||||
|
|
||||||
class BookRepository : IBookRepository {
|
class BookRepository : IBookRepository {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package com.pixelized.biblib.repository.book
|
package com.pixelized.biblib.repository.book
|
||||||
|
|
||||||
import androidx.paging.DataSource
|
import androidx.paging.DataSource
|
||||||
import com.pixelized.biblib.model.Book
|
import com.pixelized.biblib.model.book.Book
|
||||||
|
|
||||||
interface IBookRepository {
|
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.Icons
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.runtime.Composable
|
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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
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 androidx.compose.ui.unit.dp
|
||||||
import com.pixelized.biblib.BuildConfig
|
import com.pixelized.biblib.BuildConfig
|
||||||
import com.pixelized.biblib.R
|
import com.pixelized.biblib.R
|
||||||
|
import com.pixelized.biblib.model.user.User
|
||||||
import com.pixelized.biblib.ui.theme.BibLibTheme
|
import com.pixelized.biblib.ui.theme.BibLibTheme
|
||||||
|
import com.pixelized.biblib.ui.viewmodel.user.IUserViewModel
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BibLibDrawer(
|
fun BibLibDrawer(
|
||||||
|
userViewModel : IUserViewModel,
|
||||||
onNewClick: () -> Unit = {},
|
onNewClick: () -> Unit = {},
|
||||||
onBookClick: () -> Unit = {},
|
onBookClick: () -> Unit = {},
|
||||||
onSeriesClick: () -> Unit = {},
|
onSeriesClick: () -> Unit = {},
|
||||||
|
|
@ -45,13 +50,14 @@ fun BibLibDrawer(
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
) {
|
) {
|
||||||
|
val user: State<User?> = userViewModel.user.observeAsState()
|
||||||
Text(
|
Text(
|
||||||
style = typography.body1,
|
style = typography.body1,
|
||||||
text = "R. Daneel Olivaw"
|
text = user.value?.username ?: ""
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
style = typography.caption,
|
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
|
@Composable
|
||||||
fun BibLibDrawerLightPreview() {
|
fun BibLibDrawerLightPreview() {
|
||||||
BibLibTheme {
|
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.BibLibTheme
|
||||||
import com.pixelized.biblib.ui.theme.Teal200
|
import com.pixelized.biblib.ui.theme.Teal200
|
||||||
import com.pixelized.biblib.utils.BitmapCache
|
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
|
import com.pixelized.biblib.utils.mock.BookThumbnailMock
|
||||||
|
|
||||||
private val THUMBNAIL_WIDTH: Dp = 60.dp
|
private val THUMBNAIL_WIDTH: Dp = 60.dp
|
||||||
|
|
@ -157,7 +157,7 @@ private fun Placeholder(modifier: Modifier) = Surface(modifier = modifier, eleva
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun BookItemLightPreview() {
|
fun BookItemLightPreview() {
|
||||||
Bob[BitmapCache::class] = BitmapCache(LocalContext.current)
|
ServiceLocator[BitmapCache::class] = BitmapCache(LocalContext.current)
|
||||||
BibLibTheme {
|
BibLibTheme {
|
||||||
val mock = BookThumbnailMock()
|
val mock = BookThumbnailMock()
|
||||||
FilledBookItem(thumbnail = mock.bookThumbnail)
|
FilledBookItem(thumbnail = mock.bookThumbnail)
|
||||||
|
|
@ -167,7 +167,7 @@ fun BookItemLightPreview() {
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun BookItemDarkPreview() {
|
fun BookItemDarkPreview() {
|
||||||
Bob[BitmapCache::class] = BitmapCache(LocalContext.current)
|
ServiceLocator[BitmapCache::class] = BitmapCache(LocalContext.current)
|
||||||
BibLibTheme(darkTheme = true) {
|
BibLibTheme(darkTheme = true) {
|
||||||
val mock = BookThumbnailMock()
|
val mock = BookThumbnailMock()
|
||||||
FilledBookItem(thumbnail = mock.bookThumbnail)
|
FilledBookItem(thumbnail = mock.bookThumbnail)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
package com.pixelized.biblib.ui.composable.pages
|
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.background
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.verticalScroll
|
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.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.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
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.theme.Teal200
|
||||||
import com.pixelized.biblib.ui.viewmodel.book.BooksViewModel
|
import com.pixelized.biblib.ui.viewmodel.book.BooksViewModel
|
||||||
import com.pixelized.biblib.ui.viewmodel.book.IBooksViewModel
|
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.BitmapCache
|
||||||
import com.pixelized.biblib.utils.injection.Bob
|
import com.pixelized.biblib.utils.injection.ServiceLocator
|
||||||
import com.pixelized.biblib.utils.mock.BookMock
|
import com.pixelized.biblib.utils.mock.BookMock
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DetailPage(
|
fun DetailPage(
|
||||||
booksViewModel: IBooksViewModel = viewModel<BooksViewModel>(),
|
booksViewModel: IBooksViewModel = viewModel<BooksViewModel>(),
|
||||||
|
userViewModel: IUserViewModel = viewModel<UserViewModel>(),
|
||||||
bookId: Int
|
bookId: Int
|
||||||
) {
|
) {
|
||||||
Surface(
|
Surface(
|
||||||
|
|
@ -51,15 +60,41 @@ fun DetailPage(
|
||||||
elevation = 4.dp
|
elevation = 4.dp
|
||||||
) {
|
) {
|
||||||
val book by booksViewModel.getBookDetail(bookId).observeAsState()
|
val book by booksViewModel.getBookDetail(bookId).observeAsState()
|
||||||
|
val user by userViewModel.user.observeAsState()
|
||||||
book?.let {
|
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
|
@Composable
|
||||||
fun DetailPage(book: BookUio) {
|
@OptIn(ExperimentalAnimationApi::class)
|
||||||
|
fun DetailPage(
|
||||||
|
book: BookUio,
|
||||||
|
onEpubClick: () -> Unit = {},
|
||||||
|
onMobiClick: () -> Unit = {},
|
||||||
|
onSendClick: () -> Unit = {},
|
||||||
|
) {
|
||||||
|
|
||||||
val typography = MaterialTheme.typography
|
val typography = MaterialTheme.typography
|
||||||
|
val pouet = remember { MutableTransitionState(false) }
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -86,18 +121,21 @@ fun DetailPage(book: BookUio) {
|
||||||
Button(
|
Button(
|
||||||
modifier = Modifier.padding(end = 4.dp),
|
modifier = Modifier.padding(end = 4.dp),
|
||||||
imageVector = Icons.Default.Download,
|
imageVector = Icons.Default.Download,
|
||||||
text = stringResource(id = R.string.action_epub)
|
text = stringResource(id = R.string.action_epub),
|
||||||
) { }
|
onClick = onEpubClick,
|
||||||
|
)
|
||||||
Button(
|
Button(
|
||||||
modifier = Modifier.padding(horizontal = 4.dp),
|
modifier = Modifier.padding(horizontal = 4.dp),
|
||||||
imageVector = Icons.Default.Download,
|
imageVector = Icons.Default.Download,
|
||||||
text = stringResource(id = R.string.action_mobi),
|
text = stringResource(id = R.string.action_mobi),
|
||||||
) { }
|
onClick = onMobiClick,
|
||||||
|
)
|
||||||
Button(
|
Button(
|
||||||
modifier = Modifier.padding(start = 4.dp),
|
modifier = Modifier.padding(start = 4.dp),
|
||||||
imageVector = Icons.Default.Send,
|
imageVector = Icons.Default.Send,
|
||||||
text = stringResource(id = R.string.action_send),
|
text = stringResource(id = R.string.action_send),
|
||||||
) { }
|
onClick = onSendClick,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -141,6 +179,13 @@ fun DetailPage(book: BookUio) {
|
||||||
modifier = Modifier.padding(bottom = 16.dp)
|
modifier = Modifier.padding(bottom = 16.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
AnimatedVisibility(
|
||||||
|
visibleState = pouet,
|
||||||
|
) {
|
||||||
|
SendMailBottomSheet()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@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
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun DetailPageLightPreview() {
|
fun DetailPageLightPreview() {
|
||||||
Bob[BitmapCache::class] = BitmapCache(LocalContext.current)
|
ServiceLocator[BitmapCache::class] = BitmapCache(LocalContext.current)
|
||||||
BibLibTheme {
|
BibLibTheme {
|
||||||
val mock = BookMock()
|
val book = BookMock()
|
||||||
DetailPage(mock.book)
|
DetailPage(book.book)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun SendMailBottomSheetPreview() {
|
||||||
|
BibLibTheme {
|
||||||
|
SendMailBottomSheet()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
package com.pixelized.biblib.ui.composable.screen
|
package com.pixelized.biblib.ui.composable.screen
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.*
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
|
||||||
import androidx.compose.animation.core.tween
|
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.Scaffold
|
||||||
import androidx.compose.material.rememberScaffoldState
|
import androidx.compose.material.rememberScaffoldState
|
||||||
import androidx.compose.runtime.*
|
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
|
||||||
import com.pixelized.biblib.ui.viewmodel.navigation.INavigationViewModel.Navigable.Page
|
import com.pixelized.biblib.ui.viewmodel.navigation.INavigationViewModel.Navigable.Page
|
||||||
import com.pixelized.biblib.ui.viewmodel.navigation.NavigationViewModel
|
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
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -30,6 +29,7 @@ import kotlinx.coroutines.launch
|
||||||
fun HomeScreen(
|
fun HomeScreen(
|
||||||
navigationViewModel: INavigationViewModel = viewModel<NavigationViewModel>(),
|
navigationViewModel: INavigationViewModel = viewModel<NavigationViewModel>(),
|
||||||
booksViewModel: IBooksViewModel = viewModel<BooksViewModel>(),
|
booksViewModel: IBooksViewModel = viewModel<BooksViewModel>(),
|
||||||
|
userViewModel: IUserViewModel = viewModel<UserViewModel>()
|
||||||
) {
|
) {
|
||||||
// navigation
|
// navigation
|
||||||
val page by navigationViewModel.page.observeAsState()
|
val page by navigationViewModel.page.observeAsState()
|
||||||
|
|
@ -59,6 +59,7 @@ fun HomeScreen(
|
||||||
},
|
},
|
||||||
drawerContent = {
|
drawerContent = {
|
||||||
BibLibDrawer(
|
BibLibDrawer(
|
||||||
|
userViewModel = userViewModel,
|
||||||
onNewClick = {
|
onNewClick = {
|
||||||
coroutineScope.launch { scaffoldState.drawerState.close() }
|
coroutineScope.launch { scaffoldState.drawerState.close() }
|
||||||
},
|
},
|
||||||
|
|
@ -85,20 +86,19 @@ fun HomeScreen(
|
||||||
|
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = page is Page.Detail,
|
visible = page is Page.Detail,
|
||||||
initiallyVisible = false,
|
enter = slideInHorizontally(
|
||||||
enter = slideInVertically(
|
|
||||||
animationSpec = tween(Animation.MEDIUM_DURATION),
|
animationSpec = tween(Animation.MEDIUM_DURATION),
|
||||||
initialOffsetY = { height -> height },
|
initialOffsetX = { width -> width },
|
||||||
),
|
),
|
||||||
exit = slideOutVertically(
|
exit = slideOutHorizontally(
|
||||||
animationSpec = tween(Animation.MEDIUM_DURATION),
|
animationSpec = tween(Animation.MEDIUM_DURATION),
|
||||||
targetOffsetY = { height -> height },
|
targetOffsetX = { width -> width },
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
// Small trick to display the detail page during animation exit.
|
// Small trick to display the detail page during animation exit.
|
||||||
var currentPage by remember { mutableStateOf<Page.Detail?>(null) }
|
var currentPage by remember { mutableStateOf<Page.Detail?>(null) }
|
||||||
currentPage = page as? Page.Detail ?: currentPage
|
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 {
|
BibLibTheme {
|
||||||
HomeScreen(
|
HomeScreen(
|
||||||
INavigationViewModel.Mock(page = Page.Home.New),
|
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.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
import androidx.compose.animation.expandVertically
|
|
||||||
import androidx.compose.animation.shrinkVertically
|
|
||||||
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.*
|
||||||
|
|
@ -35,10 +32,11 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
import androidx.compose.ui.text.input.VisualTransformation
|
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.compose.ui.window.Dialog
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.pixelized.biblib.R
|
import com.pixelized.biblib.R
|
||||||
import com.pixelized.biblib.network.client.IBibLibClient.Companion.REGISTER_URL
|
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.ErrorCard
|
||||||
import com.pixelized.biblib.ui.composable.dialog.LoadingCard
|
import com.pixelized.biblib.ui.composable.dialog.LoadingCard
|
||||||
import com.pixelized.biblib.ui.composable.dialog.SuccessCard
|
import com.pixelized.biblib.ui.composable.dialog.SuccessCard
|
||||||
|
|
@ -110,85 +108,37 @@ private fun Dialogs(
|
||||||
val authState by authenticationViewModel.state.observeAsState()
|
val authState by authenticationViewModel.state.observeAsState()
|
||||||
val bookState by booksViewModel.state.observeAsState()
|
val bookState by booksViewModel.state.observeAsState()
|
||||||
|
|
||||||
val visible = authState !is IAuthenticationViewModel.State.Initial
|
if (authState !is IAuthenticationViewModel.State.Initial) {
|
||||||
|| bookState !is IBooksViewModel.State.Initial
|
val isInError = authState is IAuthenticationViewModel.State.Error
|
||||||
|
Dialog(
|
||||||
CrossFadeOverlay(
|
onDismissRequest = { authenticationViewModel.clearState() },
|
||||||
modifier = Modifier.clickable {
|
properties = DialogProperties(
|
||||||
if (visible) {
|
dismissOnBackPress = isInError,
|
||||||
authenticationViewModel.clearState()
|
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
|
||||||
import com.pixelized.biblib.ui.viewmodel.navigation.INavigationViewModel.Navigable.Screen
|
import com.pixelized.biblib.ui.viewmodel.navigation.INavigationViewModel.Navigable.Screen
|
||||||
import com.pixelized.biblib.ui.viewmodel.navigation.NavigationViewModel
|
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 kotlinx.coroutines.delay
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
private const val LAUNCH_EFFECT_ENTER = "LAUNCH_EFFECT_ENTER"
|
private const val LAUNCH_EFFECT_ENTER = "LAUNCH_EFFECT_ENTER"
|
||||||
private const val LAUNCH_EFFECT_EXIT = "LAUNCH_EFFECT_EXIT"
|
private const val LAUNCH_EFFECT_EXIT = "LAUNCH_EFFECT_EXIT"
|
||||||
private const val LAUNCH_EFFECT_BOOK = "LAUNCH_EFFECT_BOOK"
|
private const val LAUNCH_EFFECT_BOOK = "LAUNCH_EFFECT_BOOK"
|
||||||
|
private const val LAUNCH_EFFECT_USER = "LAUNCH_EFFECT_USER"
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalAnimationApi::class)
|
@OptIn(ExperimentalAnimationApi::class)
|
||||||
|
|
@ -41,11 +44,13 @@ private const val LAUNCH_EFFECT_BOOK = "LAUNCH_EFFECT_BOOK"
|
||||||
fun SplashScreen(
|
fun SplashScreen(
|
||||||
authenticationViewModel: IAuthenticationViewModel = viewModel<AuthenticationViewModel>(),
|
authenticationViewModel: IAuthenticationViewModel = viewModel<AuthenticationViewModel>(),
|
||||||
bookViewModel: IBooksViewModel = viewModel<BooksViewModel>(),
|
bookViewModel: IBooksViewModel = viewModel<BooksViewModel>(),
|
||||||
|
userViewModel: IUserViewModel = viewModel<UserViewModel>(),
|
||||||
navigationViewModel: INavigationViewModel = viewModel<NavigationViewModel>(),
|
navigationViewModel: INavigationViewModel = viewModel<NavigationViewModel>(),
|
||||||
initiallyVisible: Boolean = false,
|
initiallyVisible: Boolean = false,
|
||||||
) {
|
) {
|
||||||
val authenticationState: IAuthenticationViewModel.State? by authenticationViewModel.state.observeAsState()
|
val authenticationState: IAuthenticationViewModel.State? by authenticationViewModel.state.observeAsState()
|
||||||
val bookState by bookViewModel.state.observeAsState()
|
val bookState by bookViewModel.state.observeAsState()
|
||||||
|
val userState by userViewModel.state.observeAsState()
|
||||||
|
|
||||||
val contentVisibility = remember { mutableStateOf(false) }
|
val contentVisibility = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
|
@ -73,6 +78,11 @@ fun SplashScreen(
|
||||||
bookViewModel.updateBooks()
|
bookViewModel.updateBooks()
|
||||||
}
|
}
|
||||||
(bookState as? IBooksViewModel.State.Finished)?.let {
|
(bookState as? IBooksViewModel.State.Finished)?.let {
|
||||||
|
LaunchedEffect(LAUNCH_EFFECT_USER) {
|
||||||
|
userViewModel.updateUser()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(userState as? IUserViewModel.State.Finished)?.let {
|
||||||
HandleExitAnimation(contentVisibility) {
|
HandleExitAnimation(contentVisibility) {
|
||||||
navigationViewModel.navigateTo(Screen.MainScreen)
|
navigationViewModel.navigateTo(Screen.MainScreen)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import androidx.paging.Pager
|
||||||
import androidx.paging.PagingConfig
|
import androidx.paging.PagingConfig
|
||||||
import androidx.paging.PagingData
|
import androidx.paging.PagingData
|
||||||
import androidx.paging.PagingSource
|
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.client.IBibLibClient
|
||||||
import com.pixelized.biblib.network.factory.BookFactory
|
import com.pixelized.biblib.network.factory.BookFactory
|
||||||
import com.pixelized.biblib.repository.apiCache.IAPICacheRepository
|
import com.pixelized.biblib.repository.apiCache.IAPICacheRepository
|
||||||
|
|
@ -23,6 +23,10 @@ import kotlinx.coroutines.launch
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: there is some book related code that should be inside a Repository // DataSource.
|
||||||
|
*/
|
||||||
class BooksViewModel : ViewModel(), IBooksViewModel {
|
class BooksViewModel : ViewModel(), IBooksViewModel {
|
||||||
private val bookRepository: IBookRepository by inject()
|
private val bookRepository: IBookRepository by inject()
|
||||||
private val client: IBibLibClient by inject()
|
private val client: IBibLibClient by inject()
|
||||||
|
|
@ -79,6 +83,10 @@ class BooksViewModel : ViewModel(), IBooksViewModel {
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun send(id: Int, mail: String) {
|
||||||
|
// TODO()
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun loadNewBooks(): Boolean {
|
private suspend fun loadNewBooks(): Boolean {
|
||||||
val cached = apiCache.new
|
val cached = apiCache.new
|
||||||
val updated = client.service.new()
|
val updated = client.service.new()
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ interface IBooksViewModel {
|
||||||
|
|
||||||
fun getBookDetail(id: Int): LiveData<BookUio>
|
fun getBookDetail(id: Int): LiveData<BookUio>
|
||||||
|
|
||||||
|
fun send(id: Int, mail:String)
|
||||||
|
|
||||||
sealed class State {
|
sealed class State {
|
||||||
object Initial : State()
|
object Initial : State()
|
||||||
object Loading : State()
|
object Loading : State()
|
||||||
|
|
@ -37,5 +39,6 @@ interface IBooksViewModel {
|
||||||
|
|
||||||
override fun updateBooks() = Unit
|
override fun updateBooks() = Unit
|
||||||
override fun getBookDetail(id: Int) = MutableLiveData<BookUio>()
|
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
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
object Bob {
|
object ServiceLocator {
|
||||||
private val components = hashMapOf<KClass<*>, Any>()
|
private val components = hashMapOf<KClass<*>, Any>()
|
||||||
|
|
||||||
operator fun <I : Any, O : I> set(clazz: KClass<I>, component: O) {
|
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