Refactor the Authentication Screen
introduction of new theming / navigation / animation.
This commit is contained in:
parent
9074b78046
commit
2a69b75d9d
64 changed files with 1809 additions and 395 deletions
116
app/build.gradle
116
app/build.gradle
|
|
@ -2,7 +2,7 @@ plugins {
|
|||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'kotlin-kapt'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
id 'dagger.hilt.android.plugin'
|
||||
}
|
||||
|
||||
android {
|
||||
|
|
@ -20,16 +20,20 @@ android {
|
|||
vectorDrawables {
|
||||
useSupportLibrary true
|
||||
}
|
||||
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
arguments += [
|
||||
"room.schemaLocation" : "$projectDir/schemas".toString(),
|
||||
"room.incremental" : "true",
|
||||
"room.expandProjection": "true"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
debug {
|
||||
storeFile file(project.properties["PIXELIZED_DEBUG_STORE_FILE"])
|
||||
storePassword project.properties["PIXELIZED_DEBUG_STORE_PASSWORD"]
|
||||
keyAlias project.properties["PIXELIZED_DEBUG_KEY_ALIAS"]
|
||||
keyPassword project.properties["PIXELIZED_DEBUG_KEY_PASSWORD"]
|
||||
}
|
||||
release {
|
||||
pixelized {
|
||||
storeFile file(project.properties["PIXELIZED_RELEASE_STORE_FILE"])
|
||||
storePassword project.properties["PIXELIZED_RELEASE_STORE_PASSWORD"]
|
||||
keyAlias project.properties["PIXELIZED_RELEASE_KEY_ALIAS"]
|
||||
|
|
@ -39,65 +43,81 @@ android {
|
|||
|
||||
buildTypes {
|
||||
debug {
|
||||
signingConfig signingConfigs.release
|
||||
signingConfig signingConfigs.pixelized
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
release {
|
||||
signingConfig signingConfigs.release
|
||||
minifyEnabled false
|
||||
signingConfig signingConfigs.pixelized
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
compose true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
jvmTarget = '1.8'
|
||||
useIR = true
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
compose true
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion '1.0.0'
|
||||
kotlinCompilerExtensionVersion '1.1.0'
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
resources {
|
||||
excludes += '/META-INF/{AL2.0,LGPL2.1}'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Android core
|
||||
implementation 'androidx.core:core-ktx:1.7.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.4.0'
|
||||
implementation 'com.google.android.material:material:1.4.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.4.1'
|
||||
implementation 'com.google.android.material:material:1.5.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1'
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.1"
|
||||
implementation 'androidx.activity:activity-compose:1.4.0'
|
||||
|
||||
// Android Compose
|
||||
implementation "androidx.compose.ui:ui:1.2.0-alpha03"
|
||||
implementation "androidx.compose.material:material:1.1.1"
|
||||
implementation "androidx.compose.runtime:runtime-livedata:1.1.1"
|
||||
implementation "androidx.compose.ui:ui-tooling-preview:1.1.1"
|
||||
debugImplementation "androidx.compose.ui:ui-tooling:1.1.1"
|
||||
|
||||
// Android compose
|
||||
implementation "androidx.compose.ui:ui:1.0.5"
|
||||
// Tooling support (Previews, etc.)
|
||||
implementation "androidx.compose.ui:ui-tooling:1.0.5"
|
||||
// Foundation (Border, Background, Box, Image, Scroll, shapes, animations, etc.)
|
||||
implementation "androidx.compose.foundation:foundation:1.0.5"
|
||||
// Material Design
|
||||
implementation "androidx.compose.material:material:1.0.5"
|
||||
// Material design icons
|
||||
implementation "androidx.compose.material:material-icons-core:1.0.5"
|
||||
implementation "androidx.compose.material:material-icons-extended:1.0.5"
|
||||
// Integration with activities
|
||||
implementation "androidx.activity:activity-compose:1.4.0"
|
||||
// Integration with ViewModels
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.4.0"
|
||||
// Integration with observables
|
||||
implementation "androidx.compose.runtime:runtime-livedata:1.0.5"
|
||||
// ConstraintLayout
|
||||
implementation "androidx.constraintlayout:constraintlayout-compose:1.0.0-rc02"
|
||||
implementation "androidx.compose.material:material-icons-core:1.1.1"
|
||||
implementation "androidx.compose.material:material-icons-extended:1.1.1"
|
||||
|
||||
// Injection
|
||||
implementation 'androidx.hilt:hilt-navigation-compose:1.0.0'
|
||||
implementation "com.google.dagger:hilt-android:2.40.5"
|
||||
kapt "com.google.dagger:hilt-compiler:2.40.5"
|
||||
|
||||
// Accompanist
|
||||
implementation "com.google.accompanist:accompanist-systemuicontroller:0.24.3-alpha"
|
||||
implementation "com.google.accompanist:accompanist-insets:0.24.3-alpha"
|
||||
|
||||
// Navigation
|
||||
implementation "androidx.navigation:navigation-compose:2.4.2"
|
||||
|
||||
// Splash Screen support prior to Android 12
|
||||
implementation "androidx.core:core-splashscreen:1.0.0-beta02"
|
||||
|
||||
// Google sign in.
|
||||
implementation "com.google.android.gms:play-services-auth:19.2.0"
|
||||
|
||||
// Paging
|
||||
implementation "androidx.paging:paging-compose:1.0.0-alpha14"
|
||||
implementation "com.google.android.gms:play-services-auth:20.1.0"
|
||||
|
||||
// RetroFit & Gson for webservice call
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||
|
|
@ -108,18 +128,16 @@ dependencies {
|
|||
implementation "com.squareup.okhttp3:logging-interceptor:4.8.1"
|
||||
|
||||
// Room
|
||||
implementation "androidx.room:room-runtime:2.3.0"
|
||||
implementation "androidx.room:room-ktx:2.3.0"
|
||||
kapt "androidx.room:room-compiler:2.3.0"
|
||||
|
||||
// Test
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation "androidx.compose.ui:ui-test-junit4:1.0.5"
|
||||
implementation "androidx.room:room-runtime:2.4.2"
|
||||
implementation "androidx.room:room-ktx:2.4.2"
|
||||
kapt "androidx.room:room-compiler:2.4.2"
|
||||
implementation "androidx.paging:paging-compose:1.0.0-alpha14"
|
||||
}
|
||||
|
||||
static def generateVersionCode() {
|
||||
def result = "git rev-list HEAD --count".execute().text.trim() //unix
|
||||
if (result.empty) result = "PowerShell -Command git rev-list HEAD --count".execute().text.trim() //windows
|
||||
if (result.empty) result = "PowerShell -Command git rev-list HEAD --count".execute().text.trim()
|
||||
//windows
|
||||
if (result.empty) throw new RuntimeException("Could not generate versioncode on this platform? Cmd output: ${result.text}")
|
||||
return result.toInteger()
|
||||
}
|
||||
290
app/schemas/com.pixelized.biblib.database.BibLibDatabase/1.json
Normal file
290
app/schemas/com.pixelized.biblib.database.BibLibDatabase/1.json
Normal file
|
|
@ -0,0 +1,290 @@
|
|||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 1,
|
||||
"identityHash": "ee46b997bb81036c29a482590e47e98c",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "AUTHOR",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`AUTHOR_ID` TEXT NOT NULL, `AUTHOR_NAME` TEXT NOT NULL, `AUTHOR_SORT` TEXT NOT NULL, PRIMARY KEY(`AUTHOR_ID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "AUTHOR_ID",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "AUTHOR_NAME",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "sort",
|
||||
"columnName": "AUTHOR_SORT",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"AUTHOR_ID"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "BOOK",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`BOOK_ID` INTEGER NOT NULL, `BOOK_TITLE` TEXT NOT NULL, `BOOK_SORT` TEXT NOT NULL, `BOOK_HAVE_COVER` INTEGER NOT NULL, `BOOK_RELEASE_DATE` INTEGER NOT NULL, `BOOK_LANGUAGE_ID` TEXT, `BOOK_RATING` INTEGER, `BOOK_SERIES_ID` TEXT, `BOOK_SYNOPSIS` TEXT, `BOOK_ISNEW` INTEGER NOT NULL, `BOOK_NEW_ORDER` INTEGER, PRIMARY KEY(`BOOK_ID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "BOOK_ID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "BOOK_TITLE",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "sort",
|
||||
"columnName": "BOOK_SORT",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "haveCover",
|
||||
"columnName": "BOOK_HAVE_COVER",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "releaseDate",
|
||||
"columnName": "BOOK_RELEASE_DATE",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "language",
|
||||
"columnName": "BOOK_LANGUAGE_ID",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "rating",
|
||||
"columnName": "BOOK_RATING",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "series",
|
||||
"columnName": "BOOK_SERIES_ID",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "synopsis",
|
||||
"columnName": "BOOK_SYNOPSIS",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "isNew",
|
||||
"columnName": "BOOK_ISNEW",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "newOrder",
|
||||
"columnName": "BOOK_NEW_ORDER",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"BOOK_ID"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "GENRE",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`GENRE_ID` TEXT NOT NULL, `GENRE_NAME` TEXT NOT NULL, PRIMARY KEY(`GENRE_ID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "GENRE_ID",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "GENRE_NAME",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"GENRE_ID"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "LANGUAGE",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`LANGUAGE_ID` TEXT NOT NULL, `LANGUAGE_NAME` TEXT NOT NULL, PRIMARY KEY(`LANGUAGE_ID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "LANGUAGE_ID",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "code",
|
||||
"columnName": "LANGUAGE_NAME",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"LANGUAGE_ID"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "SERIES",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`SERIES_ID` TEXT NOT NULL, `SERIES_NAME` TEXT NOT NULL, `SERIES_SORT` TEXT NOT NULL, `SERIES_INDEX` INTEGER, PRIMARY KEY(`SERIES_ID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "SERIES_ID",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "SERIES_NAME",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "sort",
|
||||
"columnName": "SERIES_SORT",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "index",
|
||||
"columnName": "SERIES_INDEX",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"SERIES_ID"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "BookAuthorCrossRef",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`BOOK_ID` INTEGER NOT NULL, `AUTHOR_ID` TEXT NOT NULL, PRIMARY KEY(`BOOK_ID`, `AUTHOR_ID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "bookId",
|
||||
"columnName": "BOOK_ID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "authorId",
|
||||
"columnName": "AUTHOR_ID",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"BOOK_ID",
|
||||
"AUTHOR_ID"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_BookAuthorCrossRef_AUTHOR_ID",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"AUTHOR_ID"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_BookAuthorCrossRef_AUTHOR_ID` ON `${TABLE_NAME}` (`AUTHOR_ID`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "BookGenreCrossRef",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`BOOK_ID` INTEGER NOT NULL, `GENRE_ID` TEXT NOT NULL, PRIMARY KEY(`BOOK_ID`, `GENRE_ID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "bookId",
|
||||
"columnName": "BOOK_ID",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "genreId",
|
||||
"columnName": "GENRE_ID",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"BOOK_ID",
|
||||
"GENRE_ID"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_BookGenreCrossRef_GENRE_ID",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"GENRE_ID"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_BookGenreCrossRef_GENRE_ID` ON `${TABLE_NAME}` (`GENRE_ID`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ee46b997bb81036c29a482590e47e98c')"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,52 +1,7 @@
|
|||
package com.pixelized.biblib
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.room.Room
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.pixelized.biblib.database.BibLibDatabase
|
||||
import com.pixelized.biblib.network.client.BibLibClient
|
||||
import com.pixelized.biblib.network.client.IBibLibClient
|
||||
import com.pixelized.biblib.repository.apiCache.APICacheRepository
|
||||
import com.pixelized.biblib.repository.apiCache.IAPICacheRepository
|
||||
import com.pixelized.biblib.repository.book.BookRepository
|
||||
import com.pixelized.biblib.repository.book.IBookRepository
|
||||
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.ServiceLocator
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
|
||||
class BibLibApplication : Application() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
// Android.
|
||||
ServiceLocator[BibLibDatabase::class] =
|
||||
Room.databaseBuilder(this, BibLibDatabase::class.java, BibLibDatabase.DATABASE_NAME)
|
||||
.fallbackToDestructiveMigration()
|
||||
.build()
|
||||
ServiceLocator[SharedPreferences::class] = getSharedPreferences(SHARED_PREF, Context.MODE_PRIVATE)
|
||||
// Web service.
|
||||
ServiceLocator[Gson::class] = GsonBuilder().create()
|
||||
ServiceLocator[IBibLibClient::class] = BibLibClient()
|
||||
// Bitmap cache.
|
||||
ServiceLocator[BitmapCache::class] = BitmapCache(this)
|
||||
// Repositories.
|
||||
ServiceLocator[IGoogleSingInRepository::class] = GoogleSingInRepository(this)
|
||||
ServiceLocator[ICredentialRepository::class] = CredentialRepository()
|
||||
ServiceLocator[IAPICacheRepository::class] = APICacheRepository()
|
||||
ServiceLocator[IBookRepository::class] = BookRepository()
|
||||
ServiceLocator[IUserRepository::class] = UserRepository()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SHARED_PREF = "BIB_LIB_SHARED_PREF"
|
||||
}
|
||||
}
|
||||
@HiltAndroidApp
|
||||
class BibLibApplication : Application()
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.pixelized.biblib.module
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.pixelized.biblib.network.client.BibLibClient
|
||||
import com.pixelized.biblib.network.client.IBibLibClient
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class NetworkModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGsonParser(): Gson {
|
||||
return GsonBuilder().create()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideBibLibClient(
|
||||
gson: Gson
|
||||
): IBibLibClient {
|
||||
return BibLibClient(
|
||||
gson = gson
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package com.pixelized.biblib.module
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.room.Room
|
||||
import com.pixelized.biblib.database.BibLibDatabase
|
||||
import com.pixelized.biblib.utils.BitmapCache
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class PersistenceModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideDatabase(
|
||||
@ApplicationContext context: Context,
|
||||
): BibLibDatabase {
|
||||
val builder = Room.databaseBuilder(
|
||||
context,
|
||||
BibLibDatabase::class.java,
|
||||
BibLibDatabase.DATABASE_NAME
|
||||
)
|
||||
return builder.fallbackToDestructiveMigration().build()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideBitmapCache(
|
||||
@ApplicationContext context: Context,
|
||||
): BitmapCache {
|
||||
return BitmapCache(context)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providePreferences(
|
||||
@ApplicationContext context: Context,
|
||||
): SharedPreferences {
|
||||
return context.getSharedPreferences(SHARED_PREF, Context.MODE_PRIVATE)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SHARED_PREF = "BIB_LIB_SHARED_PREF"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package com.pixelized.biblib.module
|
||||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import com.google.gson.Gson
|
||||
import com.pixelized.biblib.database.BibLibDatabase
|
||||
import com.pixelized.biblib.network.client.IBibLibClient
|
||||
import com.pixelized.biblib.repository.apiCache.APICacheRepository
|
||||
import com.pixelized.biblib.repository.apiCache.IAPICacheRepository
|
||||
import com.pixelized.biblib.repository.book.BookRepository
|
||||
import com.pixelized.biblib.repository.book.IBookRepository
|
||||
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 dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class RepositoryModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGoogleSingInRepository(
|
||||
application: Application,
|
||||
): IGoogleSingInRepository {
|
||||
return GoogleSingInRepository(
|
||||
application = application
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCredentialRepository(
|
||||
preferences: SharedPreferences,
|
||||
): ICredentialRepository {
|
||||
return CredentialRepository(
|
||||
preferences = preferences,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAPICacheRepository(
|
||||
gson: Gson,
|
||||
preferences: SharedPreferences,
|
||||
): IAPICacheRepository {
|
||||
return APICacheRepository(
|
||||
gson = gson,
|
||||
preferences = preferences,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideBookRepository(
|
||||
database: BibLibDatabase
|
||||
): IBookRepository {
|
||||
return BookRepository(
|
||||
database = database,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideUserRepository(
|
||||
client: IBibLibClient,
|
||||
): IUserRepository {
|
||||
return UserRepository(
|
||||
client = client
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,15 +2,16 @@ package com.pixelized.biblib.network.client
|
|||
|
||||
import com.google.gson.Gson
|
||||
import com.pixelized.biblib.network.client.IBibLibClient.Companion.BASE_URL
|
||||
import com.pixelized.biblib.utils.injection.inject
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
class BibLibClient : IBibLibClient {
|
||||
private val gson by inject<Gson>()
|
||||
class BibLibClient @Inject constructor(
|
||||
gson: Gson,
|
||||
) : IBibLibClient {
|
||||
private val bearerInterceptor = BearerInterceptor()
|
||||
private val httpInterceptor = HttpLoggingInterceptor().apply {
|
||||
this.level = HttpLoggingInterceptor.Level.BODY
|
||||
|
|
@ -33,6 +34,4 @@ class BibLibClient : IBibLibClient {
|
|||
set(value) {
|
||||
bearerInterceptor.token = value
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
|
@ -4,11 +4,12 @@ import android.content.SharedPreferences
|
|||
import androidx.core.content.edit
|
||||
import com.google.gson.Gson
|
||||
import com.pixelized.biblib.network.data.response.BookListResponse
|
||||
import com.pixelized.biblib.utils.injection.inject
|
||||
import javax.inject.Inject
|
||||
|
||||
class APICacheRepository : IAPICacheRepository {
|
||||
private val gson: Gson by inject()
|
||||
private val preferences: SharedPreferences by inject()
|
||||
class APICacheRepository @Inject constructor(
|
||||
private val gson: Gson,
|
||||
private val preferences: SharedPreferences,
|
||||
) : IAPICacheRepository {
|
||||
|
||||
override var new: BookListResponse?
|
||||
get() = preferences.new?.let { gson.fromJson(it, BookListResponse::class.java) }
|
||||
|
|
|
|||
|
|
@ -8,10 +8,11 @@ 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.book.*
|
||||
import com.pixelized.biblib.utils.injection.inject
|
||||
import javax.inject.Inject
|
||||
|
||||
class BookRepository : IBookRepository {
|
||||
val database: BibLibDatabase by inject()
|
||||
class BookRepository @Inject constructor(
|
||||
private val database: BibLibDatabase
|
||||
) : IBookRepository {
|
||||
|
||||
override fun getAll(): List<Book> =
|
||||
database.bookDao().getAll().map { it.toBook() }
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@ package com.pixelized.biblib.repository.credential
|
|||
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import com.pixelized.biblib.utils.injection.inject
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
class CredentialRepository : ICredentialRepository {
|
||||
private val preferences: SharedPreferences by inject()
|
||||
class CredentialRepository @Inject constructor(
|
||||
private val preferences: SharedPreferences,
|
||||
) : ICredentialRepository {
|
||||
|
||||
override var login
|
||||
get() = preferences.login
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@ 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
|
||||
import javax.inject.Inject
|
||||
|
||||
class UserRepository : IUserRepository {
|
||||
private val client: IBibLibClient by inject()
|
||||
class UserRepository @Inject constructor(
|
||||
private val client: IBibLibClient,
|
||||
) : IUserRepository {
|
||||
private var user: User? = null
|
||||
|
||||
override suspend fun getUser(forceUpdate: Boolean): User {
|
||||
|
|
|
|||
|
|
@ -1,55 +1,32 @@
|
|||
package com.pixelized.biblib.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.pixelized.biblib.ui.composable.screen.LoginScreen
|
||||
import com.pixelized.biblib.ui.composable.screen.HomeScreen
|
||||
import com.pixelized.biblib.ui.composable.screen.SplashScreen
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import com.pixelized.biblib.ui.navigation.FullScreenNavHost
|
||||
import com.pixelized.biblib.ui.navigation.Screen
|
||||
import com.pixelized.biblib.ui.theme.BibLibTheme
|
||||
import com.pixelized.biblib.ui.viewmodel.navigation.INavigationViewModel.Navigable.Screen
|
||||
import com.pixelized.biblib.ui.viewmodel.navigation.NavigationViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : ComponentActivity() {
|
||||
private val navigationViewModel: NavigationViewModel by viewModels()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContent {
|
||||
BibLibTheme {
|
||||
ContentComposable()
|
||||
Surface(
|
||||
color = MaterialTheme.colors.background,
|
||||
) {
|
||||
FullScreenNavHost(
|
||||
startDestination = Screen.Authentication
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (navigationViewModel.navigateBack().not()) {
|
||||
super.onBackPressed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ContentComposable() {
|
||||
val navigationViewModel = viewModel<NavigationViewModel>()
|
||||
val main by navigationViewModel.screen.observeAsState()
|
||||
|
||||
Log.e("pouet", "Navigation State $main")
|
||||
|
||||
Crossfade(targetState = main) {
|
||||
when (it) {
|
||||
is Screen.SplashScreen -> SplashScreen()
|
||||
is Screen.LoginScreen -> LoginScreen()
|
||||
is Screen.MainScreen -> HomeScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
package com.pixelized.biblib.ui.authentication
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.getValue
|
||||
|
||||
@Stable
|
||||
class AuthenticationFormUIO(
|
||||
login: State<String>,
|
||||
password: State<String>,
|
||||
remember: State<Boolean>,
|
||||
) {
|
||||
val login: String by login
|
||||
val password: String by password
|
||||
val remember: Boolean by remember
|
||||
}
|
||||
|
|
@ -0,0 +1,378 @@
|
|||
package com.pixelized.biblib.ui.authentication
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.sharp.Visibility
|
||||
import androidx.compose.material.icons.sharp.VisibilityOff
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import com.pixelized.biblib.R
|
||||
import com.pixelized.biblib.network.client.IBibLibClient
|
||||
import com.pixelized.biblib.ui.composable.*
|
||||
import com.pixelized.biblib.ui.navigation.LocalFullScreenNavHostController
|
||||
import com.pixelized.biblib.ui.navigation.Screen
|
||||
import com.pixelized.biblib.ui.theme.BibLibTheme
|
||||
import com.pixelized.biblib.ui.theme.color.GoogleColorPalette
|
||||
import com.pixelized.biblib.utils.extention.bibLib
|
||||
|
||||
@Composable
|
||||
fun AuthenticationScreen(
|
||||
viewModel: AuthenticationViewModel = hiltViewModel(),
|
||||
) {
|
||||
val navHostController = LocalFullScreenNavHostController.current
|
||||
|
||||
AuthenticationScreenContent(
|
||||
login = viewModel.form.login,
|
||||
password = viewModel.form.password,
|
||||
rememberPassword = viewModel.form.remember,
|
||||
onLoginChange = {
|
||||
viewModel.onLoginChange(it)
|
||||
},
|
||||
onPasswordChange = {
|
||||
viewModel.onPasswordChange(it)
|
||||
},
|
||||
onRememberPasswordChange = {
|
||||
viewModel.onRememberChange(it)
|
||||
},
|
||||
onGoogleSignIn = {
|
||||
viewModel.loginWithGoogle()
|
||||
},
|
||||
onSignIn = {
|
||||
viewModel.login()
|
||||
},
|
||||
onRegister = {
|
||||
navHostController.navigateToRegister()
|
||||
},
|
||||
)
|
||||
|
||||
AuthenticationHandler(
|
||||
onDismissRequest = {
|
||||
if (it is StateUio.Failure) viewModel.dismissError()
|
||||
},
|
||||
onSuccess = {
|
||||
navHostController.navigateToHome()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AuthenticationScreenContent(
|
||||
login: String,
|
||||
onLoginChange: (String) -> Unit,
|
||||
password: String,
|
||||
onPasswordChange: (String) -> Unit,
|
||||
rememberPassword: Boolean,
|
||||
onRememberPasswordChange: (Boolean) -> Unit,
|
||||
onGoogleSignIn: () -> Unit,
|
||||
onSignIn: () -> Unit,
|
||||
onRegister: () -> Unit,
|
||||
) {
|
||||
val scrollState = rememberScrollState()
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
AnimatedDelayer {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(scrollState)
|
||||
) {
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
AnimatedOffset(
|
||||
modifier = Modifier
|
||||
.padding(all = MaterialTheme.bibLib.dimen.medium)
|
||||
.align(alignment = Alignment.CenterHorizontally),
|
||||
) {
|
||||
Text(
|
||||
style = MaterialTheme.typography.h4,
|
||||
color = MaterialTheme.colors.onBackground,
|
||||
text = stringResource(id = R.string.authentication_title),
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
AnimatedOffset(
|
||||
modifier = Modifier.padding(horizontal = MaterialTheme.bibLib.dimen.medium),
|
||||
) {
|
||||
LoginField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = login,
|
||||
onValueChange = onLoginChange,
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(MaterialTheme.bibLib.dimen.small))
|
||||
|
||||
AnimatedOffset(
|
||||
modifier = Modifier.padding(horizontal = MaterialTheme.bibLib.dimen.medium),
|
||||
) {
|
||||
PasswordField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = password,
|
||||
onValueChange = onPasswordChange,
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions { focusManager.clearFocus() },
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(MaterialTheme.bibLib.dimen.medium))
|
||||
|
||||
AnimatedOffset(
|
||||
modifier = Modifier.padding(horizontal = MaterialTheme.bibLib.dimen.medium),
|
||||
) {
|
||||
CredentialRemember(
|
||||
value = rememberPassword,
|
||||
onValueChange = onRememberPasswordChange,
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(MaterialTheme.bibLib.dimen.medium))
|
||||
|
||||
AnimatedOffset(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = MaterialTheme.bibLib.dimen.medium)
|
||||
.align(Alignment.End),
|
||||
) {
|
||||
Row {
|
||||
Button(
|
||||
colors = ButtonDefaults.outlinedButtonColors(),
|
||||
onClick = onRegister,
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.action_register))
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(MaterialTheme.bibLib.dimen.small))
|
||||
|
||||
Button(
|
||||
colors = ButtonDefaults.buttonColors(),
|
||||
onClick = onSignIn,
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.action_login))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.weight(2f))
|
||||
|
||||
AnimatedOffset {
|
||||
Button(
|
||||
modifier = Modifier
|
||||
.padding(all = MaterialTheme.bibLib.dimen.medium)
|
||||
.fillMaxWidth(),
|
||||
colors = ButtonDefaults.outlinedButtonColors(),
|
||||
onClick = onGoogleSignIn,
|
||||
) {
|
||||
Text(text = googleStringResource())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////
|
||||
// region: Authentication Handlers
|
||||
|
||||
@Composable
|
||||
fun AuthenticationHandler(
|
||||
viewModel: AuthenticationViewModel = hiltViewModel(),
|
||||
onDismissRequest: (StateUio<Unit>) -> Unit = {},
|
||||
onSuccess: () -> Unit = { },
|
||||
) {
|
||||
viewModel.PrepareLoginWithGoogle()
|
||||
|
||||
val state by viewModel.authenticationProcess
|
||||
StateUioHandler(
|
||||
state = state,
|
||||
onDismissRequest = onDismissRequest,
|
||||
onSuccess = onSuccess,
|
||||
)
|
||||
}
|
||||
|
||||
//////////////////////////////////////
|
||||
// region: Content Helper Composable
|
||||
|
||||
@Composable
|
||||
private fun LoginField(
|
||||
modifier: Modifier = Modifier,
|
||||
value: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||
keyboardActions: KeyboardActions = KeyboardActions(),
|
||||
) {
|
||||
TextField(
|
||||
modifier = modifier,
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
label = { Text(text = stringResource(id = R.string.authentication_login)) },
|
||||
colors = TextFieldDefaults.outlinedTextFieldColors(),
|
||||
maxLines = 1,
|
||||
singleLine = true,
|
||||
keyboardOptions = keyboardOptions,
|
||||
keyboardActions = keyboardActions,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PasswordField(
|
||||
modifier: Modifier = Modifier,
|
||||
value: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||
keyboardActions: KeyboardActions = KeyboardActions(),
|
||||
) {
|
||||
var passwordVisibility by remember { mutableStateOf(false) }
|
||||
TextField(
|
||||
modifier = modifier,
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
label = { Text(text = stringResource(id = R.string.authentication_password)) },
|
||||
colors = TextFieldDefaults.outlinedTextFieldColors(),
|
||||
maxLines = 1,
|
||||
singleLine = true,
|
||||
visualTransformation = if (passwordVisibility) VisualTransformation.None else PasswordVisualTransformation(),
|
||||
trailingIcon = {
|
||||
IconButton(onClick = { passwordVisibility = passwordVisibility.not() }) {
|
||||
Icon(
|
||||
imageVector = if (passwordVisibility) Icons.Sharp.VisibilityOff else Icons.Sharp.Visibility,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
},
|
||||
keyboardOptions = keyboardOptions,
|
||||
keyboardActions = keyboardActions,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CredentialRemember(
|
||||
modifier: Modifier = Modifier,
|
||||
value: Boolean,
|
||||
onValueChange: (Boolean) -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null,
|
||||
onClick = { onValueChange(value.not()) }
|
||||
)
|
||||
) {
|
||||
Checkbox(
|
||||
modifier = Modifier.align(Alignment.CenterVertically),
|
||||
checked = value,
|
||||
onCheckedChange = null
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(MaterialTheme.bibLib.dimen.small))
|
||||
|
||||
Text(
|
||||
modifier = Modifier.align(Alignment.CenterVertically),
|
||||
style = MaterialTheme.typography.caption,
|
||||
color = MaterialTheme.colors.onBackground,
|
||||
text = stringResource(id = R.string.authentication_credential_remember)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
//////////////////////////////////////
|
||||
// region: Navigation Helper
|
||||
|
||||
private fun NavHostController.navigateToHome() {
|
||||
navigate(Screen.Home.route) { popUpTo(0) { inclusive = true } }
|
||||
}
|
||||
|
||||
private fun NavHostController.navigateToRegister() {
|
||||
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(IBibLibClient.REGISTER_URL)))
|
||||
}
|
||||
|
||||
// endregion
|
||||
//////////////////////////////////////
|
||||
// region: AnnotatedString
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
fun googleStringResource(): AnnotatedString = buildAnnotatedString {
|
||||
val default = LocalTextStyle.current.toSpanStyle()
|
||||
withStyle(
|
||||
style = default
|
||||
) {
|
||||
append(stringResource(id = R.string.action_google_sign_in))
|
||||
append(" ")
|
||||
}
|
||||
withStyle(
|
||||
style = default.copy(color = GoogleColorPalette.blue, fontWeight = FontWeight.ExtraBold),
|
||||
) {
|
||||
append("G")
|
||||
}
|
||||
withStyle(
|
||||
style = default.copy(color = GoogleColorPalette.red, fontWeight = FontWeight.ExtraBold),
|
||||
) {
|
||||
append("o")
|
||||
}
|
||||
withStyle(
|
||||
style = default.copy(color = GoogleColorPalette.yellow, fontWeight = FontWeight.ExtraBold),
|
||||
) {
|
||||
append("o")
|
||||
}
|
||||
withStyle(
|
||||
style = default.copy(color = GoogleColorPalette.blue, fontWeight = FontWeight.ExtraBold),
|
||||
) {
|
||||
append("g")
|
||||
}
|
||||
withStyle(
|
||||
style = default.copy(color = GoogleColorPalette.green, fontWeight = FontWeight.ExtraBold),
|
||||
) {
|
||||
append("l")
|
||||
}
|
||||
withStyle(
|
||||
style = default.copy(color = GoogleColorPalette.red, fontWeight = FontWeight.ExtraBold),
|
||||
) {
|
||||
append("e")
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun AuthenticationScreenContentPreview() {
|
||||
BibLibTheme {
|
||||
AuthenticationScreenContent(
|
||||
login = "",
|
||||
onLoginChange = { },
|
||||
password = "",
|
||||
onPasswordChange = { },
|
||||
rememberPassword = true,
|
||||
onRememberPasswordChange = { },
|
||||
onGoogleSignIn = { },
|
||||
onSignIn = { },
|
||||
onRegister = { },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,220 @@
|
|||
package com.pixelized.biblib.ui.authentication
|
||||
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignIn
|
||||
import com.google.android.gms.common.api.ApiException
|
||||
import com.pixelized.biblib.network.client.IBibLibClient
|
||||
import com.pixelized.biblib.network.data.query.AuthLoginQuery
|
||||
import com.pixelized.biblib.repository.credential.ICredentialRepository
|
||||
import com.pixelized.biblib.repository.googleSignIn.IGoogleSingInRepository
|
||||
import com.pixelized.biblib.ui.composable.StateUio
|
||||
import com.pixelized.biblib.utils.exception.MissingGoogleTokenException
|
||||
import com.pixelized.biblib.utils.exception.MissingTokenException
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class AuthenticationViewModel @Inject constructor(
|
||||
private val credentialRepository: ICredentialRepository,
|
||||
private val googleSignIn: IGoogleSingInRepository,
|
||||
private val client: IBibLibClient,
|
||||
) : ViewModel() {
|
||||
private var launcher: ActivityResultLauncher<Intent>? = null
|
||||
|
||||
private var authenticationJob: Job? = null
|
||||
private val _authenticationProcess = mutableStateOf<StateUio<Unit>?>(null)
|
||||
val authenticationProcess: State<StateUio<Unit>?> get() = _authenticationProcess
|
||||
|
||||
private val _login: MutableState<String>
|
||||
private val _password: MutableState<String>
|
||||
private val _remember = mutableStateOf(credentialRepository.rememberCredential)
|
||||
|
||||
val form: AuthenticationFormUIO
|
||||
get() = AuthenticationFormUIO(
|
||||
login = _login,
|
||||
password = _password,
|
||||
remember = _remember,
|
||||
)
|
||||
|
||||
init {
|
||||
if (credentialRepository.rememberCredential) {
|
||||
_login = mutableStateOf(credentialRepository.login ?: "")
|
||||
_password = mutableStateOf(credentialRepository.password ?: "")
|
||||
} else {
|
||||
_login = mutableStateOf("")
|
||||
_password = mutableStateOf("")
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////
|
||||
// region: Login with BibLibClient
|
||||
|
||||
fun login(
|
||||
login: String = _login.value,
|
||||
password: String = _password.value,
|
||||
) {
|
||||
authenticationJob?.cancel()
|
||||
authenticationJob = viewModelScope.launch(Dispatchers.IO) {
|
||||
val query = AuthLoginQuery(username = login, password = password)
|
||||
_authenticationProcess.value = StateUio.Progress()
|
||||
try {
|
||||
val response = client.service.login(query)
|
||||
val idToken = response.token ?: throw MissingTokenException()
|
||||
client.token = idToken
|
||||
_authenticationProcess.value = StateUio.Success(Unit)
|
||||
} catch (exception: Exception) {
|
||||
Log.e("AuthenticationViewModel", exception.message, exception)
|
||||
_authenticationProcess.value = StateUio.Failure(exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
//////////////////////////////////////
|
||||
// region: Login with Google
|
||||
|
||||
@Composable
|
||||
fun PrepareLoginWithGoogle() {
|
||||
launcher = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) {
|
||||
authenticationJob?.cancel()
|
||||
authenticationJob = viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val task = GoogleSignIn.getSignedInAccountFromIntent(it.data)
|
||||
val account = task.getResult(ApiException::class.java)
|
||||
val googleToken = account?.idToken ?: throw MissingGoogleTokenException()
|
||||
val response = client.service.loginWithGoogle(googleToken)
|
||||
val token = response.token ?: throw MissingTokenException()
|
||||
client.token = token
|
||||
_authenticationProcess.value = StateUio.Success(Unit)
|
||||
} catch (exception: Exception) {
|
||||
Log.e("AuthenticationViewModel", exception.message, exception)
|
||||
_authenticationProcess.value = StateUio.Failure(exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun loginWithGoogle() {
|
||||
_authenticationProcess.value = StateUio.Progress()
|
||||
launcher?.launch(googleSignIn.client.signInIntent)
|
||||
}
|
||||
|
||||
// endregion
|
||||
//////////////////////////////////////
|
||||
// region: AutoLogin
|
||||
|
||||
@Composable
|
||||
fun AutoLogin() {
|
||||
LaunchedEffect(key1 = "AuthenticationViewModel AutoLogin") {
|
||||
authenticationJob?.cancel()
|
||||
authenticationJob = launch(Dispatchers.IO) {
|
||||
_authenticationProcess.value = StateUio.Progress()
|
||||
try {
|
||||
autoLoginWithGoogle() || autologinWithCredential()
|
||||
_authenticationProcess.value = StateUio.Success(Unit)
|
||||
} catch (exception: Exception) {
|
||||
_authenticationProcess.value = StateUio.Failure(exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun autoLoginWithGoogle(): Boolean {
|
||||
val googleToken = googleSignIn.lastGoogleToken
|
||||
return if (googleToken != null) {
|
||||
try {
|
||||
client.service.loginWithGoogle(googleToken).let { response ->
|
||||
if (response.token != null) {
|
||||
client.token = response.token
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun autologinWithCredential(): Boolean {
|
||||
val login = credentialRepository.login
|
||||
val password = credentialRepository.password
|
||||
return if (login != null && password != null) {
|
||||
try {
|
||||
val query = AuthLoginQuery(login, password)
|
||||
client.service.login(query).let { response ->
|
||||
if (response.token != null) {
|
||||
client.token = response.token
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
//////////////////////////////////////
|
||||
// region: OnDataChange callback.
|
||||
|
||||
fun onLoginChange(login: String) {
|
||||
// update login in the repository
|
||||
if (_remember.value) {
|
||||
credentialRepository.login = login
|
||||
}
|
||||
// update the UI State
|
||||
_login.value = login
|
||||
}
|
||||
|
||||
fun onPasswordChange(password: String) {
|
||||
// update password in the repository
|
||||
if (_remember.value) {
|
||||
credentialRepository.password = password
|
||||
}
|
||||
// update the UI State
|
||||
_password.value = password
|
||||
}
|
||||
|
||||
fun onRememberChange(remember: Boolean) {
|
||||
// save the remember state in the repository
|
||||
credentialRepository.rememberCredential = remember
|
||||
// update login & password in the repository
|
||||
if (remember.not()) {
|
||||
credentialRepository.login = null
|
||||
credentialRepository.password = null
|
||||
} else {
|
||||
credentialRepository.login = _login.value
|
||||
credentialRepository.password = _password.value
|
||||
}
|
||||
// update the UI State
|
||||
_remember.value = remember
|
||||
}
|
||||
|
||||
// endregion
|
||||
//////////////////////////////////////
|
||||
// region: Dialog
|
||||
|
||||
fun dismissError() {
|
||||
_authenticationProcess.value = null
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.pixelized.biblib.ui.composable
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
@Composable
|
||||
fun AnimatedDelayer(
|
||||
content: @Composable AnimatedDelayerScope.() -> Unit
|
||||
) {
|
||||
val scope = AnimatedDelayerScope()
|
||||
scope.content()
|
||||
}
|
||||
|
||||
class AnimatedDelayerScope(
|
||||
var delay : Delay = Delay()
|
||||
)
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
package com.pixelized.biblib.ui.composable
|
||||
|
||||
import androidx.compose.animation.core.*
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.Saver
|
||||
import androidx.compose.runtime.saveable.SaverScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
|
||||
@Composable
|
||||
fun AnimatedDelayerScope.AnimatedOffset(
|
||||
modifier: Modifier = Modifier,
|
||||
transitionLabel: String = "AnimatedOffset",
|
||||
content: @Composable BoxScope.() -> Unit,
|
||||
) {
|
||||
AnimatedOffset(
|
||||
modifier = modifier,
|
||||
transitionLabel = transitionLabel,
|
||||
delay = delay++,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AnimatedOffset(
|
||||
modifier: Modifier = Modifier,
|
||||
transitionLabel: String = "AnimatedOffset",
|
||||
delay: Delay = Delay(),
|
||||
content: @Composable BoxScope.() -> Unit,
|
||||
) {
|
||||
val displayed = rememberSavableMutableTransitionState(
|
||||
initialState = LocalInspectionMode.current,
|
||||
targetState = true,
|
||||
)
|
||||
AnimatedOffset(
|
||||
modifier = modifier,
|
||||
transitionLabel = transitionLabel,
|
||||
displayed = displayed,
|
||||
delay = delay,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AnimatedOffset(
|
||||
modifier: Modifier = Modifier,
|
||||
displayed: MutableTransitionState<Boolean>,
|
||||
transitionLabel: String = "AnimatedOffset",
|
||||
delay: Delay = Delay(),
|
||||
content: @Composable BoxScope.() -> Unit,
|
||||
) {
|
||||
val transition: TransitionData = updateTransition(
|
||||
displayed = displayed,
|
||||
label = transitionLabel,
|
||||
delay = delay.value,
|
||||
)
|
||||
Box(
|
||||
modifier = modifier
|
||||
.offset(y = transition.offset)
|
||||
.graphicsLayer(alpha = transition.alpha, clip = false),
|
||||
content = content
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun updateTransition(
|
||||
displayed: MutableTransitionState<Boolean>,
|
||||
label: String,
|
||||
duration: Int = 600,
|
||||
delay: Int = 0,
|
||||
easing: CubicBezierEasing = CubicBezierEasing(0.25f, 1f, 0.5f, 1f),
|
||||
fromAlpha: Float = 0f,
|
||||
toAlpha: Float = 1f,
|
||||
fromOffset: Dp = 64.dp,
|
||||
toOffset: Dp = 0.dp,
|
||||
): TransitionData {
|
||||
val transition = updateTransition(transitionState = displayed, label = label)
|
||||
val alpha = transition.animateFloat(
|
||||
label = "$label :: alpha",
|
||||
transitionSpec = { tween(durationMillis = duration, easing = easing, delayMillis = delay) },
|
||||
) {
|
||||
when (it) {
|
||||
true -> toAlpha
|
||||
else -> fromAlpha
|
||||
}
|
||||
}
|
||||
val offset = transition.animateDp(
|
||||
label = "$label :: offset",
|
||||
transitionSpec = { tween(durationMillis = duration, easing = easing, delayMillis = delay) },
|
||||
) {
|
||||
when (it) {
|
||||
true -> toOffset
|
||||
else -> fromOffset
|
||||
}
|
||||
}
|
||||
return remember(transition) { TransitionData(alpha, offset) }
|
||||
}
|
||||
|
||||
private class TransitionData(
|
||||
alpha: State<Float>,
|
||||
offset: State<Dp>,
|
||||
) {
|
||||
val alpha by alpha
|
||||
val offset by offset
|
||||
}
|
||||
|
||||
@JvmInline
|
||||
value class Delay(
|
||||
val value: Int = DELTA,
|
||||
) {
|
||||
operator fun inc(): Delay {
|
||||
return Delay(value = value + DELTA)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val DELTA = 100
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private inline fun <reified T> rememberSavableMutableTransitionState(
|
||||
initialState: T, targetState: T
|
||||
) = rememberSaveable(saver = saver()) {
|
||||
MutableTransitionState(initialState).apply { this.targetState = targetState }
|
||||
}
|
||||
|
||||
private inline fun <reified T> saver() = object : Saver<MutableTransitionState<T>, Pair<T, T>> {
|
||||
override fun restore(value: Pair<T, T>): MutableTransitionState<T> {
|
||||
return MutableTransitionState(value.first).apply { targetState = value.second }
|
||||
}
|
||||
|
||||
override fun SaverScope.save(value: MutableTransitionState<T>): Pair<T, T> {
|
||||
return value.currentState to value.targetState
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
package com.pixelized.biblib.ui.composable
|
||||
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import com.pixelized.biblib.ui.old.composable.dialog.ErrorCard
|
||||
import com.pixelized.biblib.ui.old.composable.dialog.LoadingCard
|
||||
|
||||
sealed class StateUio<T> {
|
||||
class Progress<T>(val progress: Float? = null) : StateUio<T>()
|
||||
class Failure<T>(val exception: Exception) : StateUio<T>()
|
||||
class Success<T>(val value: T) : StateUio<T>()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
fun <T> StateUioHandler(
|
||||
state: StateUio<T>?,
|
||||
onDismissRequest: (StateUio<T>) -> Unit = {},
|
||||
onSuccess: () -> Unit = { },
|
||||
) {
|
||||
val currentOnDismissRequest by rememberUpdatedState(onDismissRequest)
|
||||
val currentOnSuccess by rememberUpdatedState(onSuccess)
|
||||
|
||||
when (state) {
|
||||
is StateUio.Progress,
|
||||
is StateUio.Failure -> {
|
||||
Dialog(onDismissRequest = { currentOnDismissRequest(state) }) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null,
|
||||
onClick = { onDismissRequest(state) }
|
||||
),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
AnimatedContent(
|
||||
targetState = state,
|
||||
contentAlignment = Alignment.Center,
|
||||
transitionSpec = {
|
||||
fadeIn(tween(delayMillis = 150)) + scaleIn(
|
||||
initialScale = 0.85f,
|
||||
animationSpec = tween(delayMillis = 150)
|
||||
) with fadeOut(tween()) + scaleOut(
|
||||
targetScale = 0.85f,
|
||||
animationSpec = tween()
|
||||
)
|
||||
},
|
||||
) {
|
||||
when (it) {
|
||||
is StateUio.Progress -> LoadingCard()
|
||||
is StateUio.Failure -> ErrorCard(exception = it.exception)
|
||||
else -> Unit // nothing to do.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is StateUio.Success -> currentOnSuccess()
|
||||
null -> Unit // nothing to do.
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.pixelized.biblib.ui.navigation
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.pixelized.biblib.ui.authentication.AuthenticationScreen
|
||||
|
||||
val LocalFullScreenNavHostController = compositionLocalOf<NavHostController> {
|
||||
error("LocalFullScreenNavHostController is not ready yet.")
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FullScreenNavHost(
|
||||
navHostController: NavHostController = rememberNavController(),
|
||||
startDestination: Screen = Screen.Authentication
|
||||
) {
|
||||
CompositionLocalProvider(LocalFullScreenNavHostController provides navHostController) {
|
||||
NavHost(
|
||||
navController = navHostController,
|
||||
startDestination = startDestination.route,
|
||||
) {
|
||||
composable(Screen.Authentication.route) {
|
||||
AuthenticationScreen()
|
||||
}
|
||||
composable(Screen.Home.route) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.pixelized.biblib.ui.navigation
|
||||
|
||||
sealed class Screen(
|
||||
val route: String,
|
||||
) {
|
||||
object Authentication : Screen(
|
||||
route = "authentication"
|
||||
)
|
||||
object Home : Screen(
|
||||
route = "home"
|
||||
)
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.biblib.ui.composable.common
|
||||
package com.pixelized.biblib.ui.old.composable.common
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -22,7 +22,7 @@ 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 com.pixelized.biblib.ui.old.viewmodel.user.IUserViewModel
|
||||
import java.util.*
|
||||
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.biblib.ui.composable.common
|
||||
package com.pixelized.biblib.ui.old.composable.common
|
||||
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.material.Icon
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.biblib.ui.composable.common
|
||||
package com.pixelized.biblib.ui.old.composable.common
|
||||
|
||||
import android.widget.TextView
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.pixelized.biblib.ui.composable.common
|
||||
package com.pixelized.biblib.ui.old.composable.common
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
|
|
@ -10,8 +11,9 @@ import androidx.compose.ui.graphics.asImageBitmap
|
|||
import androidx.compose.ui.graphics.painter.BitmapPainter
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import com.pixelized.biblib.utils.BitmapCache
|
||||
import com.pixelized.biblib.utils.injection.get
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.pixelized.biblib.module.PersistenceModule
|
||||
import dagger.hilt.android.EntryPointAccessors
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.net.URL
|
||||
|
|
@ -29,8 +31,9 @@ fun Image(
|
|||
alpha: Float = DefaultAlpha,
|
||||
colorFilter: ColorFilter? = null
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val cover by remember { download(placeHolder, coroutineScope, contentUrl) }
|
||||
val cover by remember { download(context, placeHolder, coroutineScope, contentUrl) }
|
||||
|
||||
Crossfade(modifier = modifier, targetState = cover) {
|
||||
if (it == placeHolder) {
|
||||
|
|
@ -57,14 +60,17 @@ fun Image(
|
|||
}
|
||||
|
||||
private fun download(
|
||||
context: Context,
|
||||
placeHolder: Painter,
|
||||
coroutineScope: CoroutineScope,
|
||||
url: URL,
|
||||
): State<Painter> {
|
||||
val state = mutableStateOf(placeHolder)
|
||||
val cache = EntryPointAccessors
|
||||
.fromApplication(context, PersistenceModule::class.java)
|
||||
.provideBitmapCache(context)
|
||||
|
||||
coroutineScope.launch {
|
||||
val cache: BitmapCache = get()
|
||||
val resource = cache.readFromDisk(url)?.let { BitmapPainter(it.asImageBitmap()) }
|
||||
if (resource != null) {
|
||||
state.value = resource
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.biblib.ui.composable.dialog
|
||||
package com.pixelized.biblib.ui.old.composable.dialog
|
||||
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.foundation.background
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.biblib.ui.composable.dialog
|
||||
package com.pixelized.biblib.ui.old.composable.dialog
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.height
|
||||
|
|
@ -44,19 +44,20 @@ fun ErrorCard(
|
|||
imageVector = Icons.Sharp.ErrorOutline,
|
||||
contentDescription = "error"
|
||||
)
|
||||
val typography = MaterialTheme.typography
|
||||
if (message.isNotEmpty()) {
|
||||
Text(
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||
style = typography.body1,
|
||||
style = MaterialTheme.typography.body1,
|
||||
textAlign = TextAlign.Center,
|
||||
text = message
|
||||
)
|
||||
}
|
||||
if (exception != null) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(top = 8.dp)
|
||||
.align(Alignment.CenterHorizontally),
|
||||
style = typography.caption,
|
||||
style = MaterialTheme.typography.caption,
|
||||
textAlign = TextAlign.Center,
|
||||
text = exception.message ?: exception::class.java.simpleName
|
||||
)
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.biblib.ui.composable.dialog
|
||||
package com.pixelized.biblib.ui.old.composable.dialog
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
|
|
@ -16,12 +16,14 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.biblib.R
|
||||
import com.pixelized.biblib.ui.theme.BibLibTheme
|
||||
import com.pixelized.biblib.utils.extention.bibLib
|
||||
|
||||
|
||||
@Composable
|
||||
fun LoadingCard(
|
||||
modifier: Modifier = Modifier,
|
||||
message: String? = null
|
||||
progress: Float? = null,
|
||||
message: String? = null,
|
||||
) {
|
||||
Card(elevation = 8.dp, modifier = modifier) {
|
||||
Column(
|
||||
|
|
@ -29,11 +31,20 @@ fun LoadingCard(
|
|||
.width(250.dp)
|
||||
.padding(32.dp)
|
||||
) {
|
||||
if (progress == null) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.padding(bottom = 16.dp)
|
||||
.padding(bottom = MaterialTheme.bibLib.dimen.medium)
|
||||
)
|
||||
} else {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.padding(bottom = MaterialTheme.bibLib.dimen.medium),
|
||||
progress = progress,
|
||||
)
|
||||
}
|
||||
if (message?.isNotEmpty() == true) {
|
||||
val typography = MaterialTheme.typography
|
||||
Text(
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.biblib.ui.composable.dialog
|
||||
package com.pixelized.biblib.ui.old.composable.dialog
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.height
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.biblib.ui.composable.items
|
||||
package com.pixelized.biblib.ui.old.composable.items
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -12,19 +12,16 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.biblib.R
|
||||
import com.pixelized.biblib.ui.composable.common.Image
|
||||
import com.pixelized.biblib.ui.data.BookThumbnailUio
|
||||
import com.pixelized.biblib.ui.old.composable.common.Image
|
||||
import com.pixelized.biblib.ui.old.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.ServiceLocator
|
||||
import com.pixelized.biblib.ui.theme.color.BibLibColorPalette
|
||||
import com.pixelized.biblib.utils.mock.BookThumbnailMock
|
||||
|
||||
private val THUMBNAIL_WIDTH: Dp = 60.dp
|
||||
|
|
@ -68,7 +65,7 @@ private fun FilledBookItem(
|
|||
placeHolder = painterResource(id = R.drawable.ic_launcher_foreground),
|
||||
contentScale = ContentScale.FillBounds,
|
||||
contentUrl = thumbnail.cover,
|
||||
colorFilter = if (MaterialTheme.colors.isLight) ColorFilter.tint(Teal200) else null,
|
||||
colorFilter = if (MaterialTheme.colors.isLight) ColorFilter.tint(BibLibColorPalette.Green) else null,
|
||||
contentDescription = thumbnail.title
|
||||
)
|
||||
Column(
|
||||
|
|
@ -158,7 +155,6 @@ private fun Placeholder(modifier: Modifier) = Surface(modifier = modifier, eleva
|
|||
@Preview
|
||||
@Composable
|
||||
fun BookItemLightPreview() {
|
||||
ServiceLocator[BitmapCache::class] = BitmapCache(LocalContext.current)
|
||||
BibLibTheme {
|
||||
val mock = BookThumbnailMock()
|
||||
FilledBookItem(thumbnail = mock.bookThumbnail)
|
||||
|
|
@ -168,7 +164,6 @@ fun BookItemLightPreview() {
|
|||
@Preview
|
||||
@Composable
|
||||
fun BookItemDarkPreview() {
|
||||
ServiceLocator[BitmapCache::class] = BitmapCache(LocalContext.current)
|
||||
BibLibTheme(darkTheme = true) {
|
||||
val mock = BookThumbnailMock()
|
||||
FilledBookItem(thumbnail = mock.bookThumbnail)
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.biblib.ui.composable.pages
|
||||
package com.pixelized.biblib.ui.old.composable.pages
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
|
|
@ -32,18 +32,16 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.pixelized.biblib.R
|
||||
import com.pixelized.biblib.ui.composable.common.HtmlText
|
||||
import com.pixelized.biblib.ui.composable.common.Image
|
||||
import com.pixelized.biblib.ui.data.BookUio
|
||||
import com.pixelized.biblib.ui.data.MailUio
|
||||
import com.pixelized.biblib.ui.old.composable.common.HtmlText
|
||||
import com.pixelized.biblib.ui.old.composable.common.Image
|
||||
import com.pixelized.biblib.ui.old.data.BookUio
|
||||
import com.pixelized.biblib.ui.old.data.MailUio
|
||||
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.ServiceLocator
|
||||
import com.pixelized.biblib.ui.theme.color.BibLibColorPalette
|
||||
import com.pixelized.biblib.ui.old.viewmodel.book.BooksViewModel
|
||||
import com.pixelized.biblib.ui.old.viewmodel.book.IBooksViewModel
|
||||
import com.pixelized.biblib.ui.old.viewmodel.user.IUserViewModel
|
||||
import com.pixelized.biblib.ui.old.viewmodel.user.UserViewModel
|
||||
import com.pixelized.biblib.utils.mock.BookMock
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
|
@ -134,7 +132,7 @@ fun DetailPage(
|
|||
contentScale = ContentScale.FillWidth,
|
||||
placeHolder = painterResource(id = R.drawable.ic_launcher_foreground),
|
||||
contentUrl = book.cover,
|
||||
colorFilter = if (MaterialTheme.colors.isLight) ColorFilter.tint(Teal200) else null,
|
||||
colorFilter = if (MaterialTheme.colors.isLight) ColorFilter.tint(BibLibColorPalette.Green) else null,
|
||||
contentDescription = book.title
|
||||
)
|
||||
Row(modifier = Modifier.padding(bottom = 16.dp)) {
|
||||
|
|
@ -296,7 +294,6 @@ private fun SendMailItem(
|
|||
@Preview
|
||||
@Composable
|
||||
fun DetailPageLightPreview() {
|
||||
ServiceLocator[BitmapCache::class] = BitmapCache(LocalContext.current)
|
||||
BibLibTheme {
|
||||
val book = BookMock()
|
||||
DetailPage(book.book)
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.biblib.ui.composable.pages
|
||||
package com.pixelized.biblib.ui.old.composable.pages
|
||||
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.gestures.scrollable
|
||||
|
|
@ -13,11 +13,11 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.paging.compose.LazyPagingItems
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import androidx.paging.compose.items
|
||||
import com.pixelized.biblib.ui.composable.items.BookItem
|
||||
import com.pixelized.biblib.ui.data.BookThumbnailUio
|
||||
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.old.composable.items.BookItem
|
||||
import com.pixelized.biblib.ui.old.data.BookThumbnailUio
|
||||
import com.pixelized.biblib.ui.old.viewmodel.book.IBooksViewModel
|
||||
import com.pixelized.biblib.ui.old.viewmodel.navigation.INavigationViewModel
|
||||
import com.pixelized.biblib.ui.old.viewmodel.navigation.INavigationViewModel.Navigable.Page
|
||||
|
||||
|
||||
@Composable
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.biblib.ui.composable.screen
|
||||
package com.pixelized.biblib.ui.old.composable.screen
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.animation.*
|
||||
|
|
@ -12,19 +12,19 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.pixelized.biblib.R
|
||||
import com.pixelized.biblib.ui.composable.common.BibLibDrawer
|
||||
import com.pixelized.biblib.ui.composable.common.BibLibToolbar
|
||||
import com.pixelized.biblib.ui.composable.pages.DetailPage
|
||||
import com.pixelized.biblib.ui.composable.pages.HomePage
|
||||
import com.pixelized.biblib.ui.theme.Animation
|
||||
import com.pixelized.biblib.ui.old.composable.common.BibLibDrawer
|
||||
import com.pixelized.biblib.ui.old.composable.common.BibLibToolbar
|
||||
import com.pixelized.biblib.ui.old.composable.pages.DetailPage
|
||||
import com.pixelized.biblib.ui.old.composable.pages.HomePage
|
||||
import com.pixelized.biblib.ui.theme.animation.Animation
|
||||
import com.pixelized.biblib.ui.theme.BibLibTheme
|
||||
import com.pixelized.biblib.ui.viewmodel.book.BooksViewModel
|
||||
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 com.pixelized.biblib.ui.old.viewmodel.book.BooksViewModel
|
||||
import com.pixelized.biblib.ui.old.viewmodel.book.IBooksViewModel
|
||||
import com.pixelized.biblib.ui.old.viewmodel.navigation.INavigationViewModel
|
||||
import com.pixelized.biblib.ui.old.viewmodel.navigation.INavigationViewModel.Navigable.Page
|
||||
import com.pixelized.biblib.ui.old.viewmodel.navigation.NavigationViewModel
|
||||
import com.pixelized.biblib.ui.old.viewmodel.user.IUserViewModel
|
||||
import com.pixelized.biblib.ui.old.viewmodel.user.UserViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.biblib.ui.composable.screen
|
||||
package com.pixelized.biblib.ui.old.composable.screen
|
||||
|
||||
|
||||
import android.content.Intent
|
||||
|
|
@ -37,19 +37,19 @@ 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.ErrorCard
|
||||
import com.pixelized.biblib.ui.composable.dialog.LoadingCard
|
||||
import com.pixelized.biblib.ui.composable.dialog.SuccessCard
|
||||
import com.pixelized.biblib.ui.old.composable.dialog.ErrorCard
|
||||
import com.pixelized.biblib.ui.old.composable.dialog.LoadingCard
|
||||
import com.pixelized.biblib.ui.old.composable.dialog.SuccessCard
|
||||
import com.pixelized.biblib.ui.theme.BibLibTheme
|
||||
import com.pixelized.biblib.ui.viewmodel.authentication.AuthenticationViewModel
|
||||
import com.pixelized.biblib.ui.viewmodel.authentication.IAuthenticationViewModel
|
||||
import com.pixelized.biblib.ui.viewmodel.book.BooksViewModel
|
||||
import com.pixelized.biblib.ui.viewmodel.book.IBooksViewModel
|
||||
import com.pixelized.biblib.ui.viewmodel.credential.CredentialViewModel
|
||||
import com.pixelized.biblib.ui.viewmodel.credential.ICredentialViewModel
|
||||
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.old.viewmodel.authentication.AuthenticationViewModel
|
||||
import com.pixelized.biblib.ui.old.viewmodel.authentication.IAuthenticationViewModel
|
||||
import com.pixelized.biblib.ui.old.viewmodel.book.BooksViewModel
|
||||
import com.pixelized.biblib.ui.old.viewmodel.book.IBooksViewModel
|
||||
import com.pixelized.biblib.ui.old.viewmodel.credential.CredentialViewModel
|
||||
import com.pixelized.biblib.ui.old.viewmodel.credential.ICredentialViewModel
|
||||
import com.pixelized.biblib.ui.old.viewmodel.navigation.INavigationViewModel
|
||||
import com.pixelized.biblib.ui.old.viewmodel.navigation.INavigationViewModel.Navigable.Screen
|
||||
import com.pixelized.biblib.ui.old.viewmodel.navigation.NavigationViewModel
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
|
||||
|
|
@ -243,7 +243,8 @@ private fun SignIn(
|
|||
colors = outlinedButtonColors(),
|
||||
onClick = {
|
||||
authenticationViewModel.loginWithGoogle()
|
||||
}) {
|
||||
}
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
painter = painterResource(id = R.drawable.ic_google), contentDescription = ""
|
||||
|
|
@ -329,8 +330,8 @@ private fun CredentialRemember(
|
|||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
@Preview(showBackground = true)
|
||||
fun LoginScreenComposablePreview() {
|
||||
BibLibTheme {
|
||||
LoginScreen(
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.biblib.ui.composable.screen
|
||||
package com.pixelized.biblib.ui.old.composable.screen
|
||||
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.tween
|
||||
|
|
@ -17,19 +17,19 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.pixelized.biblib.BuildConfig
|
||||
import com.pixelized.biblib.R
|
||||
import com.pixelized.biblib.ui.composable.dialog.CrossFadeOverlay
|
||||
import com.pixelized.biblib.ui.composable.dialog.ErrorCard
|
||||
import com.pixelized.biblib.ui.theme.Animation
|
||||
import com.pixelized.biblib.ui.old.composable.dialog.CrossFadeOverlay
|
||||
import com.pixelized.biblib.ui.old.composable.dialog.ErrorCard
|
||||
import com.pixelized.biblib.ui.theme.animation.Animation
|
||||
import com.pixelized.biblib.ui.theme.BibLibTheme
|
||||
import com.pixelized.biblib.ui.viewmodel.authentication.AuthenticationViewModel
|
||||
import com.pixelized.biblib.ui.viewmodel.authentication.IAuthenticationViewModel
|
||||
import com.pixelized.biblib.ui.viewmodel.book.BooksViewModel
|
||||
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 com.pixelized.biblib.ui.old.viewmodel.authentication.AuthenticationViewModel
|
||||
import com.pixelized.biblib.ui.old.viewmodel.authentication.IAuthenticationViewModel
|
||||
import com.pixelized.biblib.ui.old.viewmodel.book.BooksViewModel
|
||||
import com.pixelized.biblib.ui.old.viewmodel.book.IBooksViewModel
|
||||
import com.pixelized.biblib.ui.old.viewmodel.navigation.INavigationViewModel
|
||||
import com.pixelized.biblib.ui.old.viewmodel.navigation.INavigationViewModel.Navigable.Screen
|
||||
import com.pixelized.biblib.ui.old.viewmodel.navigation.NavigationViewModel
|
||||
import com.pixelized.biblib.ui.old.viewmodel.user.IUserViewModel
|
||||
import com.pixelized.biblib.ui.old.viewmodel.user.UserViewModel
|
||||
import kotlinx.coroutines.delay
|
||||
import java.util.*
|
||||
|
||||
|
|
@ -184,20 +184,12 @@ private fun AuthenticationError(state: IAuthenticationViewModel.State.Error?) {
|
|||
CrossFadeOverlay(
|
||||
modifier = Modifier.clickable {},
|
||||
visible = state != null
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
visible = true,
|
||||
initiallyVisible = false,
|
||||
enter = expandVertically(Alignment.CenterVertically),
|
||||
exit = shrinkVertically(Alignment.CenterVertically),
|
||||
) {
|
||||
ErrorCard(
|
||||
message = stringResource(id = R.string.error_generic),
|
||||
exception = state?.exception
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
|
|
@ -206,20 +198,12 @@ private fun BookError(state: IBooksViewModel.State.Error?) {
|
|||
CrossFadeOverlay(
|
||||
modifier = Modifier.clickable {},
|
||||
visible = state != null
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
visible = true,
|
||||
initiallyVisible = false,
|
||||
enter = expandVertically(Alignment.CenterVertically),
|
||||
exit = shrinkVertically(Alignment.CenterVertically),
|
||||
) {
|
||||
ErrorCard(
|
||||
message = stringResource(id = R.string.error_generic),
|
||||
exception = state?.exception
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.biblib.ui.data
|
||||
package com.pixelized.biblib.ui.old.data
|
||||
|
||||
import com.pixelized.biblib.network.client.IBibLibClient.Companion.THUMBNAIL_URL
|
||||
import java.net.URL
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.biblib.ui.data
|
||||
package com.pixelized.biblib.ui.old.data
|
||||
|
||||
import com.pixelized.biblib.network.client.IBibLibClient.Companion.COVER_URL
|
||||
import java.net.URL
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.biblib.ui.data
|
||||
package com.pixelized.biblib.ui.old.data
|
||||
|
||||
data class MailUio(
|
||||
val mail: String,
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.biblib.ui.viewmodel.authentication
|
||||
package com.pixelized.biblib.ui.old.viewmodel.authentication
|
||||
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
|
|
@ -16,17 +16,20 @@ import com.pixelized.biblib.network.client.IBibLibClient
|
|||
import com.pixelized.biblib.network.data.query.AuthLoginQuery
|
||||
import com.pixelized.biblib.repository.credential.ICredentialRepository
|
||||
import com.pixelized.biblib.repository.googleSignIn.IGoogleSingInRepository
|
||||
import com.pixelized.biblib.ui.viewmodel.authentication.IAuthenticationViewModel.State
|
||||
import com.pixelized.biblib.ui.old.viewmodel.authentication.IAuthenticationViewModel.State
|
||||
import com.pixelized.biblib.utils.exception.MissingGoogleTokenException
|
||||
import com.pixelized.biblib.utils.exception.MissingTokenException
|
||||
import com.pixelized.biblib.utils.injection.inject
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class AuthenticationViewModel : ViewModel(), IAuthenticationViewModel {
|
||||
private val credentialRepository: ICredentialRepository by inject()
|
||||
private val googleSignIn: IGoogleSingInRepository by inject()
|
||||
private val client: IBibLibClient by inject()
|
||||
@HiltViewModel
|
||||
class AuthenticationViewModel @Inject constructor(
|
||||
private val credentialRepository: ICredentialRepository,
|
||||
private val googleSignIn: IGoogleSingInRepository,
|
||||
private val client: IBibLibClient,
|
||||
) : ViewModel(), IAuthenticationViewModel {
|
||||
private var launcher: ActivityResultLauncher<Intent>? = null
|
||||
|
||||
private val _state = MutableLiveData<State>(State.Initial)
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.biblib.ui.viewmodel.authentication
|
||||
package com.pixelized.biblib.ui.old.viewmodel.authentication
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.lifecycle.LiveData
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.biblib.ui.viewmodel.book
|
||||
package com.pixelized.biblib.ui.old.viewmodel.book
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.LiveData
|
||||
|
|
@ -14,23 +14,26 @@ import com.pixelized.biblib.network.client.IBibLibClient
|
|||
import com.pixelized.biblib.network.factory.BookFactory
|
||||
import com.pixelized.biblib.repository.apiCache.IAPICacheRepository
|
||||
import com.pixelized.biblib.repository.book.IBookRepository
|
||||
import com.pixelized.biblib.ui.data.BookThumbnailUio
|
||||
import com.pixelized.biblib.ui.data.BookUio
|
||||
import com.pixelized.biblib.utils.injection.inject
|
||||
import com.pixelized.biblib.ui.old.data.BookThumbnailUio
|
||||
import com.pixelized.biblib.ui.old.data.BookUio
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.launch
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
/**
|
||||
* 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()
|
||||
private val apiCache: IAPICacheRepository by inject()
|
||||
@HiltViewModel
|
||||
class BooksViewModel @Inject constructor(
|
||||
private val bookRepository: IBookRepository,
|
||||
private val client: IBibLibClient,
|
||||
private val apiCache: IAPICacheRepository,
|
||||
) : ViewModel(), IBooksViewModel {
|
||||
|
||||
private val formatterLong = SimpleDateFormat("MMMM yyyy", Locale.getDefault())
|
||||
private val formatterShort = SimpleDateFormat("MMM yyyy", Locale.getDefault())
|
||||
|
|
@ -122,7 +125,7 @@ class BooksViewModel : ViewModel(), IBooksViewModel {
|
|||
date = if (releaseDate.time < 0) {
|
||||
null
|
||||
} else {
|
||||
formatterLong.format(releaseDate).capitalize(Locale.getDefault())
|
||||
formatterLong.format(releaseDate).capitalize()
|
||||
},
|
||||
isNew = isNew,
|
||||
)
|
||||
|
|
@ -132,16 +135,20 @@ class BooksViewModel : ViewModel(), IBooksViewModel {
|
|||
title = title,
|
||||
author = author.joinToString { it.name },
|
||||
rating = rating?.toFloat() ?: 0.0f,
|
||||
language = language?.displayLanguage?.capitalize(Locale.getDefault()) ?: "",
|
||||
language = language?.displayLanguage?.capitalize() ?: "",
|
||||
date = if (releaseDate.time < 0) {
|
||||
null
|
||||
} else {
|
||||
formatterShort.format(releaseDate).capitalize(Locale.getDefault())
|
||||
formatterShort.format(releaseDate).capitalize()
|
||||
},
|
||||
series = series?.name,
|
||||
description = synopsis ?: "",
|
||||
)
|
||||
|
||||
private fun String.capitalize() = this.replaceFirstChar {
|
||||
if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PAGING_SIZE = 30
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
package com.pixelized.biblib.ui.viewmodel.book
|
||||
package com.pixelized.biblib.ui.old.viewmodel.book
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.paging.PagingData
|
||||
import com.pixelized.biblib.ui.data.BookThumbnailUio
|
||||
import com.pixelized.biblib.ui.data.BookUio
|
||||
import com.pixelized.biblib.ui.old.data.BookThumbnailUio
|
||||
import com.pixelized.biblib.ui.old.data.BookUio
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
||||
|
|
@ -1,16 +1,19 @@
|
|||
package com.pixelized.biblib.ui.viewmodel.credential
|
||||
package com.pixelized.biblib.ui.old.viewmodel.credential
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.pixelized.biblib.repository.credential.ICredentialRepository
|
||||
import com.pixelized.biblib.utils.injection.inject
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class CredentialViewModel : ViewModel(), ICredentialViewModel {
|
||||
private val credentialRepository: ICredentialRepository by inject()
|
||||
@HiltViewModel
|
||||
class CredentialViewModel @Inject constructor(
|
||||
private val credentialRepository: ICredentialRepository,
|
||||
) : ViewModel(), ICredentialViewModel {
|
||||
|
||||
private val _login = MutableLiveData<String>()
|
||||
override val login: LiveData<String?> get() = _login
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.biblib.ui.viewmodel.credential
|
||||
package com.pixelized.biblib.ui.old.viewmodel.credential
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.biblib.ui.viewmodel.navigation
|
||||
package com.pixelized.biblib.ui.old.viewmodel.navigation
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
package com.pixelized.biblib.ui.viewmodel.navigation
|
||||
package com.pixelized.biblib.ui.old.viewmodel.navigation
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.pixelized.biblib.ui.viewmodel.navigation.INavigationViewModel.Navigable
|
||||
import com.pixelized.biblib.ui.viewmodel.navigation.INavigationViewModel.Navigable.Page
|
||||
import com.pixelized.biblib.ui.viewmodel.navigation.INavigationViewModel.Navigable.Screen
|
||||
import com.pixelized.biblib.ui.old.viewmodel.navigation.INavigationViewModel.Navigable
|
||||
import com.pixelized.biblib.ui.old.viewmodel.navigation.INavigationViewModel.Navigable.Page
|
||||
import com.pixelized.biblib.ui.old.viewmodel.navigation.INavigationViewModel.Navigable.Screen
|
||||
import java.util.*
|
||||
|
||||
class NavigationViewModel : ViewModel(), INavigationViewModel {
|
||||
|
|
@ -1,12 +1,9 @@
|
|||
package com.pixelized.biblib.ui.viewmodel.user
|
||||
package com.pixelized.biblib.ui.old.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>
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.biblib.ui.viewmodel.user
|
||||
package com.pixelized.biblib.ui.old.viewmodel.user
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
|
|
@ -6,14 +6,16 @@ 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 com.pixelized.biblib.ui.old.viewmodel.user.IUserViewModel.State
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class UserViewModel : ViewModel(), IUserViewModel {
|
||||
|
||||
private val userRepository: IUserRepository by inject()
|
||||
@HiltViewModel
|
||||
class UserViewModel @Inject constructor(
|
||||
private val userRepository: IUserRepository,
|
||||
) : ViewModel(), IUserViewModel {
|
||||
|
||||
private val _state = MutableLiveData<State>()
|
||||
override val state: LiveData<State> get() = _state
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.pixelized.biblib.ui.theme
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import com.pixelized.biblib.ui.theme.color.BibLibColor
|
||||
import com.pixelized.biblib.ui.theme.dimen.BibLibDimen
|
||||
|
||||
val LocalBibLibTheme = compositionLocalOf<BibLibTheme> {
|
||||
error("BibLibTheme not ready yet.")
|
||||
}
|
||||
|
||||
@Stable
|
||||
@Immutable
|
||||
data class BibLibTheme(
|
||||
val dimen: BibLibDimen,
|
||||
val color: BibLibColor,
|
||||
)
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
package com.pixelized.biblib.ui.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
val Purple200 = Color(0xFFBB86FC)
|
||||
val Purple500 = Color(0xFF6200EE)
|
||||
val Purple700 = Color(0xFF3700B3)
|
||||
val Teal200 = Color(0xFF03DAC5)
|
||||
|
||||
val Green600 = Color(0xFF43a047)
|
||||
val Green600L = Color(0xFF76d275)
|
||||
val Green600D = Color(0xFF00701a)
|
||||
|
|
@ -2,39 +2,29 @@ package com.pixelized.biblib.ui.theme
|
|||
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.darkColors
|
||||
import androidx.compose.material.lightColors
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
private val DarkColorPalette = darkColors(
|
||||
primary = Green600,
|
||||
primaryVariant = Green600D,
|
||||
onPrimary = Color.White,
|
||||
)
|
||||
|
||||
private val LightColorPalette = lightColors(
|
||||
primary = Green600,
|
||||
primaryVariant = Green600D,
|
||||
onPrimary = Color.White,
|
||||
)
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import com.pixelized.biblib.ui.theme.color.bibLibDarkColors
|
||||
import com.pixelized.biblib.ui.theme.color.bibLibLightColors
|
||||
import com.pixelized.biblib.ui.theme.dimen.BibLibDimen
|
||||
import com.pixelized.biblib.ui.theme.shape.Shapes
|
||||
import com.pixelized.biblib.ui.theme.typography.Typography
|
||||
|
||||
@Composable
|
||||
fun BibLibTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) {
|
||||
val colors = if (darkTheme) {
|
||||
DarkColorPalette
|
||||
} else {
|
||||
LightColorPalette
|
||||
}
|
||||
fun BibLibTheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val theme = BibLibTheme(
|
||||
dimen = BibLibDimen(),
|
||||
color = if (darkTheme) bibLibDarkColors() else bibLibLightColors()
|
||||
)
|
||||
|
||||
CompositionLocalProvider(LocalBibLibTheme provides theme) {
|
||||
MaterialTheme(
|
||||
colors = colors,
|
||||
colors = theme.color.base,
|
||||
typography = Typography,
|
||||
shapes = Shapes,
|
||||
) {
|
||||
Surface(
|
||||
color = MaterialTheme.colors.background,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.biblib.ui.theme
|
||||
package com.pixelized.biblib.ui.theme.animation
|
||||
|
||||
|
||||
object Animation {
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package com.pixelized.biblib.ui.theme.color
|
||||
|
||||
import androidx.compose.material.Colors
|
||||
import androidx.compose.material.darkColors
|
||||
import androidx.compose.material.lightColors
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import javax.annotation.concurrent.Immutable
|
||||
|
||||
|
||||
@Stable
|
||||
@Immutable
|
||||
data class BibLibColor(
|
||||
val base: Colors
|
||||
)
|
||||
|
||||
fun bibLibDarkColors(
|
||||
base: Colors = darkColors(
|
||||
primary = BibLibColorPalette.Green,
|
||||
secondary = BibLibColorPalette.VeryLightGreen,
|
||||
onPrimary = Color.White,
|
||||
onSecondary = Color.White,
|
||||
error = BibLibColorPalette.Red,
|
||||
),
|
||||
) = BibLibColor(
|
||||
base = base,
|
||||
)
|
||||
|
||||
fun bibLibLightColors(
|
||||
base: Colors = lightColors(
|
||||
primary = BibLibColorPalette.Green,
|
||||
secondary = BibLibColorPalette.VeryLightGreen,
|
||||
onPrimary = Color.White,
|
||||
onSecondary = Color.White,
|
||||
error = BibLibColorPalette.Red,
|
||||
)
|
||||
) = BibLibColor(
|
||||
base = base,
|
||||
)
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package com.pixelized.biblib.ui.theme.color
|
||||
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import javax.annotation.concurrent.Immutable
|
||||
|
||||
@Immutable
|
||||
object BibLibColorPalette {
|
||||
val VeryDarkBlue: Color = Color(0xFF09179D)
|
||||
val DarkBlue: Color = Color(0xFF1A2BDB)
|
||||
val Blue: Color = Color(0xFF2970F2)
|
||||
val LightBlue: Color = Color(0xFF1A91DB)
|
||||
val VeryLightBlue: Color = Color(0xFF1EDDEF)
|
||||
val VeryDarkPurple: Color = Color(0xFF5F0E9E)
|
||||
val DarkPurple: Color = Color(0xFF8330DB)
|
||||
val Purple: Color = Color(0xFF9B54C3)
|
||||
val LightPurple: Color = Color(0xFFBC52D9)
|
||||
val VeryLightPurple: Color = Color(0xFFC856D1)
|
||||
val VeryDarkGreen: Color = Color(0xFF16544A)
|
||||
val DarkGreen: Color = Color(0xFF207A6B)
|
||||
val Green: Color = Color(0xFF269482)
|
||||
val LightGreen: Color = Color(0xFF2AA18D)
|
||||
val VeryLightGreen: Color = Color(0xFF3AE0C5)
|
||||
val VeryDarkRed: Color = Color(0xFF631221)
|
||||
val DarkRed: Color = Color(0xFFA21D36)
|
||||
val Red: Color = Color(0xFFC92443)
|
||||
val LightRed: Color = Color(0xFFE32849)
|
||||
val VeryLightRed: Color = Color(0xFFF02B4F)
|
||||
val VeryDarkPink: Color = Color(0xFF960064)
|
||||
val DarkPink: Color = Color(0xFFBD007E)
|
||||
val Pink: Color = Color(0xFFD6008F)
|
||||
val LightPink: Color = Color(0xFFE35BB5)
|
||||
val VeryLightPink: Color = Color(0xFFFF66CC)
|
||||
val VeryDarkYellow: Color = Color(0xFFB76036)
|
||||
val DarkYellow: Color = Color(0xFFD48341)
|
||||
val Yellow: Color = Color(0xFFF3A850)
|
||||
val LightYellow: Color = Color(0xFFF5BF63)
|
||||
val VeryLightYellow: Color = Color(0xFFF9D679)
|
||||
val VeryDarkGrey: Color = Color(0xFF1D1D1D)
|
||||
val DarkGrey: Color = Color(0xFF727272)
|
||||
val Grey: Color = Color(0xFF919195)
|
||||
val LightGrey: Color = Color(0xFFDFDFDF)
|
||||
val VeryLightGrey: Color = Color(0xFFF9F9F9)
|
||||
}
|
||||
|
||||
@Immutable
|
||||
object GoogleColorPalette {
|
||||
val blue: Color = Color(0xFF4285F4)
|
||||
val red: Color = Color(0xFFEA4335)
|
||||
val yellow: Color = Color(0xFFFBBC05)
|
||||
val green: Color = Color(0xFF34A853)
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.pixelized.biblib.ui.theme.dimen
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Stable
|
||||
@Immutable
|
||||
data class BibLibDimen(
|
||||
val default: Dp = 0.dp,
|
||||
val extraSmall: Dp = 4.dp,
|
||||
val small: Dp = 8.dp,
|
||||
val medium: Dp = 16.dp,
|
||||
val large: Dp = 32.dp,
|
||||
val extraLarge: Dp = 64.dp,
|
||||
)
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
package com.pixelized.biblib.ui.theme
|
||||
package com.pixelized.biblib.ui.theme.shape
|
||||
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Shapes
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
val Shapes = Shapes(
|
||||
small = RoundedCornerShape(4.dp),
|
||||
small = RoundedCornerShape(50),
|
||||
medium = RoundedCornerShape(4.dp),
|
||||
large = RoundedCornerShape(0.dp)
|
||||
)
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.pixelized.biblib.ui.theme
|
||||
package com.pixelized.biblib.ui.theme.typography
|
||||
|
||||
import androidx.compose.material.Typography
|
||||
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.pixelized.biblib.utils.extention
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
|
||||
fun Context.showToast(message: String, duration: Int = Toast.LENGTH_SHORT) =
|
||||
Toast.makeText(this, message, duration).show()
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.pixelized.biblib.utils.extention
|
||||
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import com.pixelized.biblib.ui.theme.BibLibTheme
|
||||
import com.pixelized.biblib.ui.theme.LocalBibLibTheme
|
||||
|
||||
val MaterialTheme.bibLib : BibLibTheme
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = LocalBibLibTheme.current
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
package com.pixelized.biblib.utils.injection
|
||||
|
||||
import com.pixelized.biblib.utils.exception.InjectionException
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
object ServiceLocator {
|
||||
private val components = hashMapOf<KClass<*>, Any>()
|
||||
|
||||
operator fun <I : Any, O : I> set(clazz: KClass<I>, component: O) {
|
||||
components[clazz] = component
|
||||
}
|
||||
|
||||
operator fun <T> get(clazz: KClass<*>): T {
|
||||
return components[clazz] as? T ?: throw InjectionException(clazz)
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T> get(): T = ServiceLocator[T::class]
|
||||
|
||||
inline fun <reified T> inject(): Lazy<T> = lazy { ServiceLocator[T::class] }
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package com.pixelized.biblib.utils.mock
|
||||
|
||||
import com.pixelized.biblib.ui.data.BookUio
|
||||
import com.pixelized.biblib.ui.old.data.BookUio
|
||||
|
||||
class BookMock {
|
||||
val book: BookUio = BookUio(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package com.pixelized.biblib.utils.mock
|
||||
|
||||
import com.pixelized.biblib.ui.data.BookThumbnailUio
|
||||
import com.pixelized.biblib.ui.old.data.BookThumbnailUio
|
||||
|
||||
|
||||
class BookThumbnailMock {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
<string name="action_epub">EPUB</string>
|
||||
<string name="action_mobi">MOBI</string>
|
||||
<string name="action_send">SEND</string>
|
||||
<string name="action_google_sign_in">Sign in with Google</string>
|
||||
<string name="action_google_sign_in">Sign in with</string>
|
||||
|
||||
<!-- Dialogs -->
|
||||
|
||||
|
|
|
|||
13
build.gradle
13
build.gradle
|
|
@ -1,15 +1,18 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
buildscript {
|
||||
ext {
|
||||
kotlin_version = "1.6.10"
|
||||
}
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.0.4'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.10"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.1.3'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "com.google.dagger:hilt-android-gradle-plugin:2.40.5"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
|
|
@ -1,6 +1,6 @@
|
|||
#Wed Apr 28 21:29:33 CEST 2021
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue