1
0
Fork 0
mirror of https://github.com/anyproto/anytype-kotlin.git synced 2025-06-08 05:47:05 +09:00

Feature/save avatar image from middleware

This commit is contained in:
Evgenii Kozlov 2019-10-29 15:12:54 +00:00
parent 654f56e77e
commit 90da3e6149
39 changed files with 535 additions and 80 deletions

View file

@ -4,6 +4,7 @@ apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply plugin: 'io.fabric'
apply from: "$rootDir/versioning.gradle"
android {

View file

@ -4,6 +4,8 @@
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:name=".app.AndroidApplication"

View file

@ -3,6 +3,8 @@ package com.agileburo.anytype.di.feature
import com.agileburo.anytype.core_utils.di.scope.PerScreen
import com.agileburo.anytype.domain.auth.repo.AuthRepository
import com.agileburo.anytype.domain.desktop.interactor.GetAccount
import com.agileburo.anytype.domain.image.ImageLoader
import com.agileburo.anytype.domain.image.LoadImage
import com.agileburo.anytype.presentation.desktop.DesktopViewModelFactory
import com.agileburo.anytype.ui.desktop.DesktopFragment
import dagger.Module
@ -31,9 +33,11 @@ class DesktopModule {
@Provides
@PerScreen
fun provideDesktopViewModelFactory(
getAccount: GetAccount
getAccount: GetAccount,
loadImage: LoadImage
): DesktopViewModelFactory = DesktopViewModelFactory(
getAccount = getAccount
getAccount = getAccount,
loadImage = loadImage
)
@Provides
@ -43,4 +47,12 @@ class DesktopModule {
): GetAccount = GetAccount(
repository = repository
)
@Provides
@PerScreen
fun provideLoadImageUseCase(
loader: ImageLoader
): LoadImage = LoadImage(
loader = loader
)
}

View file

@ -3,6 +3,9 @@ package com.agileburo.anytype.di.feature
import com.agileburo.anytype.core_utils.di.scope.PerScreen
import com.agileburo.anytype.domain.auth.interactor.Logout
import com.agileburo.anytype.domain.auth.repo.AuthRepository
import com.agileburo.anytype.domain.desktop.interactor.GetAccount
import com.agileburo.anytype.domain.image.ImageLoader
import com.agileburo.anytype.domain.image.LoadImage
import com.agileburo.anytype.presentation.profile.ProfileViewModelFactory
import com.agileburo.anytype.ui.profile.ProfileFragment
import dagger.Module
@ -31,16 +34,34 @@ class ProfileModule {
@Provides
@PerScreen
fun provideProfileViewModelFactory(
logout: Logout
): ProfileViewModelFactory {
return ProfileViewModelFactory(
logout = logout
)
}
logout: Logout,
loadImage: LoadImage,
getAccount: GetAccount
): ProfileViewModelFactory = ProfileViewModelFactory(
logout = logout,
loadImage = loadImage,
getAccount = getAccount
)
@Provides
@PerScreen
fun provideLogoutUseCase(repository: AuthRepository): Logout {
return Logout(repository)
}
fun provideLogoutUseCase(
repository: AuthRepository
): Logout = Logout(repository)
@Provides
@PerScreen
fun provideLoadImageUseCase(
loader: ImageLoader
): LoadImage = LoadImage(
loader = loader
)
@Provides
@PerScreen
fun provideGetAccountUseCase(
authRepository: AuthRepository
): GetAccount = GetAccount(
repository = authRepository
)
}

View file

@ -0,0 +1,30 @@
package com.agileburo.anytype.di.main
import com.agileburo.anytype.data.auth.other.ImageDataLoader
import com.agileburo.anytype.data.auth.other.ImageLoaderRemote
import com.agileburo.anytype.domain.image.ImageLoader
import com.agileburo.anytype.middleware.interactor.Middleware
import com.agileburo.anytype.middleware.interactor.MiddlewareImageLoader
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
@Module
class ImageModule {
@Provides
@Singleton
fun provideImageLoader(remote: ImageLoaderRemote): ImageLoader {
return ImageDataLoader(
remote = remote
)
}
@Provides
@Singleton
fun provideImageLoaderRemote(middleware: Middleware): ImageLoaderRemote {
return MiddlewareImageLoader(
middleware = middleware
)
}
}

View file

@ -1,10 +1,6 @@
package com.agileburo.anytype.di.main
import com.agileburo.anytype.di.feature.AuthSubComponent
import com.agileburo.anytype.di.feature.DesktopSubComponent
import com.agileburo.anytype.di.feature.KeychainPhraseSubComponent
import com.agileburo.anytype.di.feature.ProfileSubComponent
import com.agileburo.anytype.di.feature.SplashSubComponent
import com.agileburo.anytype.di.feature.*
import dagger.Component
import javax.inject.Singleton
@ -12,7 +8,8 @@ import javax.inject.Singleton
@Component(
modules = [
ContextModule::class,
DataModule::class
DataModule::class,
ImageModule::class
]
)
interface MainComponent {

View file

@ -1,16 +1,17 @@
package com.agileburo.anytype.ui.auth.account
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import android.app.Activity.RESULT_OK
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
import android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI
import android.view.View
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.ViewModelProviders
import com.agileburo.anytype.R
import com.agileburo.anytype.core_utils.ext.hideKeyboard
import com.agileburo.anytype.core_utils.ext.invisible
import com.agileburo.anytype.core_utils.ext.parsePath
import com.agileburo.anytype.core_utils.ext.visible
import com.agileburo.anytype.core_utils.ext.*
import com.agileburo.anytype.di.common.componentManager
import com.agileburo.anytype.presentation.auth.account.CreateAccountViewModel
import com.agileburo.anytype.presentation.auth.account.CreateAccountViewModelFactory
@ -69,6 +70,22 @@ class CreateAccountFragment : NavigationFragment(R.layout.fragment_create_accoun
profileIconPlaceholder.invisible()
val writeExternalStoragePermission = ContextCompat.checkSelfPermission(
requireActivity(),
WRITE_EXTERNAL_STORAGE
)
if (writeExternalStoragePermission != PackageManager.PERMISSION_GRANTED) {
profileIcon.showSnackbar("Do not have permission")
ActivityCompat.requestPermissions(
requireActivity(),
arrayOf(WRITE_EXTERNAL_STORAGE),
REQUEST_PERMISSION_CODE
)
}
vm.onAvatarSet(uri.parsePath(requireContext()))
}
}
@ -97,5 +114,6 @@ class CreateAccountFragment : NavigationFragment(R.layout.fragment_create_accoun
companion object {
const val SELECT_IMAGE_CODE = 1
const val REQUEST_PERMISSION_CODE = 2
}
}

View file

@ -1,5 +1,6 @@
package com.agileburo.anytype.ui.desktop
import android.graphics.BitmapFactory
import android.os.Bundle
import android.view.View
import androidx.lifecycle.Observer
@ -13,9 +14,12 @@ import com.agileburo.anytype.presentation.desktop.DesktopViewModel
import com.agileburo.anytype.presentation.desktop.DesktopViewModelFactory
import com.agileburo.anytype.presentation.profile.ProfileView
import com.agileburo.anytype.ui.base.ViewStateFragment
import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.fragment_desktop.*
import java.io.ByteArrayInputStream
import javax.inject.Inject
class DesktopFragment : ViewStateFragment<ViewState<List<DesktopView>>>(R.layout.fragment_desktop) {
private val profileObserver = Observer<ProfileView> { profile ->
@ -44,6 +48,20 @@ class DesktopFragment : ViewStateFragment<ViewState<List<DesktopView>>>(R.layout
vm.navigation.observe(this, navObserver)
vm.profile.observe(this, profileObserver)
vm.onViewCreated()
vm.image.observe(this, Observer { blob ->
val stream = ByteArrayInputStream(blob)
Glide
.with(this)
.load(BitmapFactory.decodeStream(stream))
.centerInside()
.circleCrop()
.into(profileImage)
stream.close()
})
}
override fun render(state: ViewState<List<DesktopView>>) {

View file

@ -1,7 +1,9 @@
package com.agileburo.anytype.ui.profile
import android.graphics.BitmapFactory
import android.os.Bundle
import android.view.View
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import com.agileburo.anytype.R
import com.agileburo.anytype.core_utils.ui.ViewState
@ -10,7 +12,9 @@ import com.agileburo.anytype.presentation.profile.ProfileView
import com.agileburo.anytype.presentation.profile.ProfileViewModel
import com.agileburo.anytype.presentation.profile.ProfileViewModelFactory
import com.agileburo.anytype.ui.base.ViewStateFragment
import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.fragment_profile.*
import java.io.ByteArrayInputStream
import javax.inject.Inject
class ProfileFragment : ViewStateFragment<ViewState<ProfileView>>(R.layout.fragment_profile) {
@ -29,6 +33,21 @@ class ProfileFragment : ViewStateFragment<ViewState<ProfileView>>(R.layout.fragm
vm.state.observe(this, this)
vm.navigation.observe(this, navObserver)
vm.onViewCreated()
vm.image.observe(this, Observer { blob ->
val stream = ByteArrayInputStream(blob)
Glide
.with(this)
.load(BitmapFactory.decodeStream(stream))
.centerInside()
.circleCrop()
.into(pic)
stream.close()
})
}
override fun render(state: ViewState<ProfileView>) {

View file

@ -14,9 +14,9 @@
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right"
android:id="@+id/action_open_profile"
app:destination="@id/profileScreen" />
app:destination="@id/profileScreen"
app:popExitAnim="@anim/slide_out_right" />
</fragment>
<fragment
@ -56,7 +56,7 @@
</fragment>
<navigation
android:id="@+id/login_nav"
app:startDestination="@id/startLoginScreen" >
app:startDestination="@id/startLoginScreen">
<fragment
android:id="@+id/selectAccountScreen"
android:name="com.agileburo.anytype.ui.auth.account.SelectAccountFragment"
@ -119,7 +119,7 @@
android:id="@+id/congratulationScreen"
android:name="com.agileburo.anytype.ui.auth.CongratulationFragment"
android:label="StartLoginFragment"
tools:layout="@layout/fragment_congratulation"/>
tools:layout="@layout/fragment_congratulation" />
<fragment
android:id="@+id/setupSelectedAccountScreen"
android:name="com.agileburo.anytype.ui.auth.account.SetupSelectedAccountFragment"

View file

@ -1,21 +1,25 @@
package com.agileburo.anytype.data.auth.mapper
import com.agileburo.anytype.data.auth.model.AccountEntity
import com.agileburo.anytype.data.auth.model.ImageEntity
import com.agileburo.anytype.data.auth.model.WalletEntity
import com.agileburo.anytype.domain.auth.model.Account
import com.agileburo.anytype.domain.auth.model.Image
import com.agileburo.anytype.domain.auth.model.Wallet
fun AccountEntity.toDomain(): Account {
return Account(
id = id,
name = name
name = name,
avatar = avatar?.toDomain()
)
}
fun Account.toEntity(): AccountEntity {
return AccountEntity(
id = id,
name = name
name = name,
avatar = avatar?.toEntity()
)
}
@ -23,4 +27,34 @@ fun WalletEntity.toDomain(): Wallet {
return Wallet(
mnemonic = mnemonic
)
}
fun ImageEntity.toDomain(): Image {
return Image(
id = id,
sizes = sizes.map { it.toDomain() }
)
}
fun Image.toEntity(): ImageEntity {
return ImageEntity(
id = id,
sizes = sizes.map { it.toEntity() }
)
}
fun ImageEntity.Size.toDomain(): Image.Size {
return when (this) {
ImageEntity.Size.THUMB -> Image.Size.THUMB
ImageEntity.Size.LARGE -> Image.Size.LARGE
ImageEntity.Size.SMALL -> Image.Size.SMALL
}
}
fun Image.Size.toEntity(): ImageEntity.Size {
return when (this) {
Image.Size.THUMB -> ImageEntity.Size.THUMB
Image.Size.SMALL -> ImageEntity.Size.SMALL
Image.Size.LARGE -> ImageEntity.Size.LARGE
}
}

View file

@ -2,5 +2,6 @@ package com.agileburo.anytype.data.auth.model
data class AccountEntity(
val id: String,
val name: String
val name: String,
val avatar: ImageEntity?
)

View file

@ -0,0 +1,8 @@
package com.agileburo.anytype.data.auth.model
data class ImageEntity(
val id: String,
val sizes: List<Size>
) {
enum class Size { SMALL, LARGE, THUMB }
}

View file

@ -0,0 +1,17 @@
package com.agileburo.anytype.data.auth.other
import com.agileburo.anytype.data.auth.mapper.toEntity
import com.agileburo.anytype.domain.auth.model.Image
import com.agileburo.anytype.domain.image.ImageLoader
class ImageDataLoader(
private val remote: ImageLoaderRemote
) : ImageLoader {
override suspend fun load(
id: String, size: Image.Size
): ByteArray = remote.load(
id = id,
size = size.toEntity()
)
}

View file

@ -0,0 +1,7 @@
package com.agileburo.anytype.data.auth.other
import com.agileburo.anytype.data.auth.model.ImageEntity
interface ImageLoaderRemote {
suspend fun load(id: String, size: ImageEntity.Size): ByteArray
}

View file

@ -30,7 +30,7 @@ ext {
okhttp_logging_interceptor_version = '3.8.1'
rxjava2_version = '2.1.1'
moshi_version = '1.8.0'
gson_version = '2.8.5'
gson_version = '2.8.6'
rxrelay_version = '2.1.0'
better_link_method_version = '2.2.0'

View file

@ -2,5 +2,6 @@ package com.agileburo.anytype.domain.auth.model
data class Account(
val id: String,
val name: String
val name: String,
val avatar: Image?
)

View file

@ -0,0 +1,12 @@
package com.agileburo.anytype.domain.image
import com.agileburo.anytype.domain.auth.model.Image
interface ImageLoader {
/**
* @param id id of the image
* @param size requested image size
*/
suspend fun load(id: String, size: Image.Size): ByteArray
}

View file

@ -0,0 +1,22 @@
package com.agileburo.anytype.domain.image
import com.agileburo.anytype.domain.auth.model.Image
import com.agileburo.anytype.domain.base.BaseUseCase
import com.agileburo.anytype.domain.base.Either
class LoadImage(private val loader: ImageLoader) : BaseUseCase<ByteArray, LoadImage.Param>() {
override suspend fun run(params: Param) = try {
loader.load(
id = params.id,
size = params.size
).let {
Either.Right(it)
}
} catch (e: Throwable) {
Either.Left(e)
}
class Param(val id: String, val size: Image.Size = Image.Size.SMALL)
}

View file

@ -36,20 +36,26 @@ class CreateAccountTest {
val name = DataFactory.randomString()
val path = null
val account = Account(
id = DataFactory.randomUuid(),
name = DataFactory.randomString()
name = DataFactory.randomString(),
avatar = null
)
val param = CreateAccount.Params(name)
val param = CreateAccount.Params(
name = name,
avatarPath = path
)
repo.stub {
onBlocking { createAccount(name) } doReturn account
onBlocking { createAccount(name, path) } doReturn account
}
createAccount.run(param)
verify(repo, times(1)).createAccount(name)
verify(repo, times(1)).createAccount(name, path)
verify(repo, times(1)).saveAccount(any())
verifyNoMoreInteractions(repo)
}

View file

@ -0,0 +1,27 @@
package com.agileburo.anytype.middleware
import anytype.Models
import com.agileburo.anytype.data.auth.model.ImageEntity
fun Models.Image.toEntity(): ImageEntity? {
return if (id.isNullOrBlank())
null
else
ImageEntity(
id = id,
sizes = sizesList.map { size -> size.toEntity() }
)
}
fun ImageEntity.Size.toMiddleware(): Models.ImageSize = when (this) {
ImageEntity.Size.SMALL -> Models.ImageSize.SMALL
ImageEntity.Size.LARGE -> Models.ImageSize.LARGE
ImageEntity.Size.THUMB -> Models.ImageSize.THUMB
}
fun Models.ImageSize.toEntity(): ImageEntity.Size = when (this) {
Models.ImageSize.SMALL -> ImageEntity.Size.SMALL
Models.ImageSize.LARGE -> ImageEntity.Size.LARGE
Models.ImageSize.THUMB -> ImageEntity.Size.THUMB
else -> throw IllegalStateException("Unexpected image size from middleware")
}

View file

@ -6,8 +6,11 @@ import com.agileburo.anytype.data.auth.repo.AuthRemote
import com.agileburo.anytype.middleware.Event
import com.agileburo.anytype.middleware.EventProxy
import com.agileburo.anytype.middleware.interactor.Middleware
import com.agileburo.anytype.middleware.toEntity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
class AuthMiddleware(
private val middleware: Middleware,
@ -19,18 +22,23 @@ class AuthMiddleware(
) = middleware.selectAccount(id, path).let { response ->
AccountEntity(
id = response.id,
name = response.name
name = response.name,
avatar = null
)
}
override suspend fun createAccount(
name: String,
avatarPath: String?
) = middleware.createAccount(name, avatarPath).let { response ->
AccountEntity(
id = response.id,
name = response.name
)
) = withContext(Dispatchers.IO) {
middleware.createAccount(name, avatarPath).let { response ->
AccountEntity(
id = response.id,
name = response.name,
avatar = response.avatar.toEntity()
)
}
}
override suspend fun recoverAccount() {
@ -44,7 +52,8 @@ class AuthMiddleware(
.map { event ->
AccountEntity(
id = event.id,
name = event.name
name = event.name,
avatar = null
)
}

View file

@ -10,10 +10,13 @@ import anytype.Commands.AccountRecoverRequest;
import anytype.Commands.AccountRecoverResponse;
import anytype.Commands.AccountSelectRequest;
import anytype.Commands.AccountSelectResponse;
import anytype.Commands.ImageGetBlobRequest;
import anytype.Commands.ImageGetBlobResponse;
import anytype.Commands.WalletCreateRequest;
import anytype.Commands.WalletCreateResponse;
import anytype.Commands.WalletRecoverRequest;
import anytype.Commands.WalletRecoverResponse;
import anytype.Models;
import lib.Lib;
public class Middleware {
@ -64,7 +67,8 @@ public class Middleware {
} else {
return new CreateAccountResponse(
response.getAccount().getId(),
response.getAccount().getName()
response.getAccount().getName(),
response.getAccount().getAvatar()
);
}
}
@ -136,4 +140,25 @@ public class Middleware {
);
}
}
public byte[] loadImage(String id, Models.ImageSize size) throws Exception {
ImageGetBlobRequest request = ImageGetBlobRequest
.newBuilder()
.setId(id)
.setSize(size)
.build();
byte[] encodedRequest = request.toByteArray();
byte[] encodedResponse = Lib.imageGetBlob(encodedRequest);
ImageGetBlobResponse response = ImageGetBlobResponse.parseFrom(encodedResponse);
if (response.getError() != null && response.getError().getCode() != ImageGetBlobResponse.Error.Code.NULL) {
throw new Exception(response.getError().getDescription());
} else {
return response.getBlob().toByteArray();
}
}
}

View file

@ -0,0 +1,12 @@
package com.agileburo.anytype.middleware.interactor
import com.agileburo.anytype.data.auth.model.ImageEntity
import com.agileburo.anytype.data.auth.other.ImageLoaderRemote
import com.agileburo.anytype.middleware.toMiddleware
class MiddlewareImageLoader(private val middleware: Middleware) : ImageLoaderRemote {
override suspend fun load(
id: String, size: ImageEntity.Size
): ByteArray = middleware.loadImage(id, size.toMiddleware())
}

View file

@ -1,6 +1,9 @@
package com.agileburo.anytype.middleware.model
import anytype.Models
class CreateAccountResponse(
val id: String,
val name: String
val name: String,
val avatar: Models.Image
)

View file

@ -32,6 +32,7 @@ dependencies {
implementation applicationDependencies.kotlin
implementation applicationDependencies.coroutines
implementation applicationDependencies.gson
implementation databaseDependencies.room
implementation databaseDependencies.roomKtx

View file

@ -4,16 +4,19 @@ import android.content.Context
import androidx.room.Database
import androidx.room.Room.databaseBuilder
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import com.agileburo.anytype.common.Config
import com.agileburo.anytype.common.Provider
import com.agileburo.anytype.dao.AccountDao
import com.agileburo.anytype.model.AccountTable
import com.agileburo.anytype.util.Converters
@Database(
entities = [AccountTable::class],
exportSchema = false,
version = 1
)
@TypeConverters(Converters::class)
abstract class AnytypeDatabase : RoomDatabase() {
abstract fun accountDao(): AccountDao

View file

@ -0,0 +1,30 @@
package com.agileburo.anytype.mapper
import com.agileburo.anytype.data.auth.model.AccountEntity
import com.agileburo.anytype.data.auth.model.ImageEntity
import com.agileburo.anytype.model.AccountTable
fun AccountTable.toEntity(): AccountEntity {
return AccountEntity(
id = id,
name = name,
avatar = avatar?.let { cached ->
ImageEntity(
id = cached.avatarId,
sizes = cached.sizes.map { it.toEntity() }
)
}
)
}
fun ImageEntity.Size.toTable(): AccountTable.Size = when (this) {
ImageEntity.Size.SMALL -> AccountTable.Size.SMALL
ImageEntity.Size.THUMB -> AccountTable.Size.THUMB
ImageEntity.Size.LARGE -> AccountTable.Size.LARGE
}
fun AccountTable.Size.toEntity(): ImageEntity.Size = when (this) {
AccountTable.Size.SMALL -> ImageEntity.Size.SMALL
AccountTable.Size.LARGE -> ImageEntity.Size.LARGE
AccountTable.Size.THUMB -> ImageEntity.Size.THUMB
}

View file

@ -1,24 +0,0 @@
package com.agileburo.anytype.mapper
import com.agileburo.anytype.data.auth.model.AccountEntity
import com.agileburo.anytype.model.AccountTable
/**
* Created by Konstantin Ivanov
* email : ki@agileburo.com
* on 2019-10-22.
*/
fun AccountTable.toEntity(): AccountEntity =
AccountEntity(
id = this.id,
name = this.name
)
//Todo исправить timestamp
fun AccountEntity.toTable(): AccountTable =
AccountTable(
id = this.id,
name = this.name,
timestamp = 0
)

View file

@ -1,12 +1,26 @@
package com.agileburo.anytype.model
import androidx.room.Embedded
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.TypeConverters
import com.agileburo.anytype.common.Config
import com.agileburo.anytype.util.Converters
@Entity(tableName = Config.ACCOUNT_TABLE_NAME)
data class AccountTable(
@PrimaryKey val id: String,
val name: String,
val timestamp: Long
)
val timestamp: Long,
@Embedded val avatar: Avatar? = null
) {
data class Avatar(
val avatarId: String,
val sizes: List<Size>
)
@TypeConverters(Converters::class)
enum class Size { SMALL, LARGE, THUMB }
}

View file

@ -5,6 +5,7 @@ import com.agileburo.anytype.data.auth.model.AccountEntity
import com.agileburo.anytype.data.auth.repo.AuthCache
import com.agileburo.anytype.db.AnytypeDatabase
import com.agileburo.anytype.mapper.toEntity
import com.agileburo.anytype.mapper.toTable
import com.agileburo.anytype.model.AccountTable
import timber.log.Timber
@ -18,7 +19,13 @@ class DefaultAuthCache(
AccountTable(
id = account.id,
name = account.name,
timestamp = System.currentTimeMillis()
timestamp = System.currentTimeMillis(),
avatar = account.avatar?.let { avatar ->
AccountTable.Avatar(
avatarId = avatar.id,
sizes = avatar.sizes.map { it.toTable() }
)
}
)
)
}

View file

@ -0,0 +1,24 @@
package com.agileburo.anytype.util
import androidx.room.TypeConverter
import com.agileburo.anytype.model.AccountTable
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
object Converters {
@TypeConverter
@JvmStatic
fun fromImageSizeList(sizes: List<AccountTable.Size>): String {
return Gson().toJson(sizes)
}
@TypeConverter
@JvmStatic
fun toImageSizeList(string: String): List<AccountTable.Size> {
val type = object : TypeToken<List<AccountTable.Size>>() {}.type
return Gson().fromJson(string, type)
}
}

View file

@ -65,4 +65,25 @@ class AccountDaoTest {
assertTrue { result.size == 1 }
assertTrue { result.first() == secondAccount }
}
@Test
fun `should save account with its avatar`() = runBlocking {
val account = AccountTable(
id = MockDataFactory.randomString(),
name = MockDataFactory.randomString(),
timestamp = System.currentTimeMillis(),
avatar = AccountTable.Avatar(
avatarId = MockDataFactory.randomString(),
sizes = listOf(AccountTable.Size.LARGE, AccountTable.Size.SMALL)
)
)
database.accountDao().insert(account)
val result = database.accountDao().lastAccount()
assertTrue { result.size == 1 }
assertTrue { result.first() == account }
}
}

View file

@ -29,10 +29,14 @@ class SetupNewAccountViewModel(
}
private fun proceedWithCreatingAccount() {
Timber.d("Starting setting up new account")
createAccount.invoke(
scope = viewModelScope,
params = CreateAccount.Params(
name = session.name ?: throw IllegalStateException("Name not set")
name = session.name ?: throw IllegalStateException("Name not set"),
avatarPath = session.avatarPath
)
) { result ->
result.either(

View file

@ -6,14 +6,17 @@ import androidx.lifecycle.viewModelScope
import com.agileburo.anytype.core_utils.common.Event
import com.agileburo.anytype.core_utils.ui.ViewState
import com.agileburo.anytype.core_utils.ui.ViewStateViewModel
import com.agileburo.anytype.domain.auth.model.Account
import com.agileburo.anytype.domain.base.BaseUseCase
import com.agileburo.anytype.domain.desktop.interactor.GetAccount
import com.agileburo.anytype.domain.image.LoadImage
import com.agileburo.anytype.presentation.navigation.AppNavigation
import com.agileburo.anytype.presentation.navigation.SupportNavigation
import com.agileburo.anytype.presentation.profile.ProfileView
import timber.log.Timber
class DesktopViewModel(
private val loadImage: LoadImage,
private val getAccount: GetAccount
) : ViewStateViewModel<ViewState<List<DesktopView>>>(),
SupportNavigation<Event<AppNavigation.Command>> {
@ -22,9 +25,10 @@ class DesktopViewModel(
val profile: LiveData<ProfileView>
get() = _profile
init {
proceedWithGettingAccount()
}
private val _image = MutableLiveData<ByteArray>()
val image: LiveData<ByteArray>
get() = _image
override val navigation = MutableLiveData<Event<AppNavigation.Command>>()
@ -32,13 +36,33 @@ class DesktopViewModel(
getAccount.invoke(viewModelScope, BaseUseCase.None) { result ->
result.either(
fnL = { e -> Timber.e(e, "Error while getting account") },
fnR = { account -> _profile.postValue(ProfileView(name = account.name)) }
fnR = { account ->
_profile.postValue(ProfileView(name = account.name))
loadAvatarImage(account)
}
)
}
}
private fun loadAvatarImage(account: Account) {
account.avatar?.let { image ->
loadImage.invoke(
scope = viewModelScope,
params = LoadImage.Param(
id = image.id
)
) { result ->
result.either(
fnL = { e -> Timber.e(e, "Error while loading image") },
fnR = { blob -> _image.postValue(blob) }
)
}
} ?: Timber.d("Avatar not loaded: null value")
}
fun onViewCreated() {
stateData.postValue(ViewState.Init)
proceedWithGettingAccount()
}
fun onAddNewDocumentClicked() {

View file

@ -3,15 +3,18 @@ package com.agileburo.anytype.presentation.desktop
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.agileburo.anytype.domain.desktop.interactor.GetAccount
import com.agileburo.anytype.domain.image.LoadImage
class DesktopViewModelFactory(
private val getAccount: GetAccount
private val getAccount: GetAccount,
private val loadImage: LoadImage
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return DesktopViewModel(
getAccount = getAccount
getAccount = getAccount,
loadImage = loadImage
) as T
}
}

View file

@ -1,30 +1,69 @@
package com.agileburo.anytype.presentation.profile
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.agileburo.anytype.core_utils.common.Event
import com.agileburo.anytype.core_utils.ui.ViewState
import com.agileburo.anytype.core_utils.ui.ViewStateViewModel
import com.agileburo.anytype.domain.auth.interactor.Logout
import com.agileburo.anytype.domain.auth.model.Account
import com.agileburo.anytype.domain.base.BaseUseCase
import com.agileburo.anytype.domain.desktop.interactor.GetAccount
import com.agileburo.anytype.domain.image.LoadImage
import com.agileburo.anytype.presentation.navigation.AppNavigation
import com.agileburo.anytype.presentation.navigation.SupportNavigation
import timber.log.Timber
class ProfileViewModel(
private val getAccount: GetAccount,
private val loadImage: LoadImage,
private val logout: Logout
) : ViewStateViewModel<ViewState<ProfileView>>(), SupportNavigation<Event<AppNavigation.Command>> {
private val _image = MutableLiveData<ByteArray>()
val image: LiveData<ByteArray>
get() = _image
override val navigation: MutableLiveData<Event<AppNavigation.Command>> = MutableLiveData()
fun onViewCreated() {
stateData.postValue(ViewState.Init)
proceedWithGettingAccount()
}
fun onBackButtonClicked() {
// TODO dispatch navigation command
}
private fun proceedWithGettingAccount() {
getAccount.invoke(viewModelScope, BaseUseCase.None) { result ->
result.either(
fnL = { e -> Timber.e(e, "Error while getting account") },
fnR = { account ->
stateData.postValue(ViewState.Success(ProfileView(name = account.name)))
loadAvatarImage(account)
}
)
}
}
private fun loadAvatarImage(account: Account) {
account.avatar?.let { image ->
loadImage.invoke(
scope = viewModelScope,
params = LoadImage.Param(
id = image.id
)
) { result ->
result.either(
fnL = { e -> Timber.e(e, "Error while loading image") },
fnR = { blob -> _image.postValue(blob) }
)
}
} ?: Timber.d("Avatar not loaded: null value")
}
fun onLogoutClicked() {
logout.invoke(viewModelScope, BaseUseCase.None) { result ->
result.either(

View file

@ -3,15 +3,21 @@ package com.agileburo.anytype.presentation.profile
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.agileburo.anytype.domain.auth.interactor.Logout
import com.agileburo.anytype.domain.desktop.interactor.GetAccount
import com.agileburo.anytype.domain.image.LoadImage
class ProfileViewModelFactory(
private val logout: Logout
private val logout: Logout,
private val getAccount: GetAccount,
private val loadImage: LoadImage
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return ProfileViewModel(
logout = logout
logout = logout,
getAccount = getAccount,
loadImage = loadImage
) as T
}
}

View file

@ -14,7 +14,8 @@ import timber.log.Timber
* email : ki@agileburo.com
* on 2019-10-21.
*/
class SplashViewModel(private val checkAuthorizationStatus: CheckAuthorizationStatus) : ViewModel() {
class SplashViewModel(private val checkAuthorizationStatus: CheckAuthorizationStatus) :
ViewModel() {
val navigation: MutableLiveData<Event<AppNavigation.Command>> = MutableLiveData()