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

Merge branch 'feature/select-picture-for-profile-from-gallery' into 'develop'

Feature/select picture for profile from gallery

See merge request ra3orbladez/anytype.io.mobile!88
This commit is contained in:
Evgenii Kozlov 2019-10-24 15:41:31 +00:00
commit c86534ba1a
21 changed files with 215 additions and 72 deletions

View file

@ -67,7 +67,7 @@ class KeychainLoginFragment : NavigationFragment(R.layout.fragment_keychain_logi
override fun onDestroyView() {
super.onDestroyView()
hideKeyboard(activity?.currentFocus)
activity?.currentFocus?.hideKeyboard()
}
private fun setupNavigation() {

View file

@ -1,17 +1,25 @@
package com.agileburo.anytype.ui.auth.account
import android.app.Activity.RESULT_OK
import android.content.Intent
import android.os.Bundle
import android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI
import android.view.View
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.di.common.componentManager
import com.agileburo.anytype.presentation.auth.account.CreateAccountViewModel
import com.agileburo.anytype.presentation.auth.account.CreateAccountViewModelFactory
import com.agileburo.anytype.ui.base.NavigationFragment
import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.fragment_create_account.*
import javax.inject.Inject
class CreateAccountFragment : NavigationFragment(R.layout.fragment_create_account) {
@Inject
@ -29,11 +37,13 @@ class CreateAccountFragment : NavigationFragment(R.layout.fragment_create_accoun
createProfileButton.setOnClickListener {
vm.onCreateProfileClicked(nameInputField.text.toString())
}
profileIconPlaceholder.setOnClickListener { openGallery() }
}
override fun onDestroyView() {
super.onDestroyView()
hideKeyboard(activity?.currentFocus)
activity?.currentFocus?.hideKeyboard()
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
@ -41,10 +51,42 @@ class CreateAccountFragment : NavigationFragment(R.layout.fragment_create_accoun
setupNavigation()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_OK && requestCode == SELECT_IMAGE_CODE) {
data?.data?.let { uri ->
profileIcon.apply {
visible()
Glide
.with(profileIcon)
.load(uri)
.circleCrop()
.into(profileIcon)
}
profileIconPlaceholder.invisible()
vm.onAvatarSet(uri.parsePath(requireContext()))
}
}
}
private fun setupNavigation() {
vm.observeNavigation().observe(this, navObserver)
}
private fun openGallery() {
Intent(
Intent.ACTION_PICK,
INTERNAL_CONTENT_URI
).let { intent ->
startActivityForResult(intent, SELECT_IMAGE_CODE)
}
}
override fun injectDependencies() {
componentManager().createAccountComponent.get().inject(this)
}
@ -52,4 +94,8 @@ class CreateAccountFragment : NavigationFragment(R.layout.fragment_create_accoun
override fun releaseDependencies() {
componentManager().createAccountComponent.release()
}
companion object {
const val SELECT_IMAGE_CODE = 1
}
}

View file

@ -1,12 +1,12 @@
package com.agileburo.anytype.ui.auth.account
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.AnimationUtils
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import com.agileburo.anytype.R
import com.agileburo.anytype.core_utils.ext.showSnackbar
import com.agileburo.anytype.core_utils.ui.ViewState
import com.agileburo.anytype.di.common.componentManager
import com.agileburo.anytype.presentation.auth.account.SetupNewAccountViewModel
import com.agileburo.anytype.presentation.auth.account.SetupNewAccountViewModelFactory
@ -14,7 +14,8 @@ import com.agileburo.anytype.ui.base.NavigationFragment
import kotlinx.android.synthetic.main.fragment_setup_new_account.*
import javax.inject.Inject
class SetupNewAccountFragment : NavigationFragment(R.layout.fragment_setup_new_account) {
class SetupNewAccountFragment : NavigationFragment(R.layout.fragment_setup_new_account),
Observer<ViewState<Any>> {
@Inject
lateinit var factory: SetupNewAccountViewModelFactory
@ -25,26 +26,35 @@ class SetupNewAccountFragment : NavigationFragment(R.layout.fragment_setup_new_a
.get(SetupNewAccountViewModel::class.java)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = inflater.inflate(R.layout.fragment_setup_new_account, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
icon.startAnimation(AnimationUtils.loadAnimation(requireContext(), R.anim.rotation))
private val animation by lazy {
AnimationUtils.loadAnimation(requireContext(), R.anim.rotation)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setupNavigation()
vm.state.observe(this, this)
}
private fun setupNavigation() {
vm.observeNavigation().observe(this, navObserver)
}
override fun onChanged(state: ViewState<Any>) {
when (state) {
is ViewState.Loading -> {
icon.startAnimation(animation)
}
is ViewState.Success -> {
animation.cancel()
}
is ViewState.Error -> {
animation.cancel()
root.showSnackbar(state.error)
}
}
}
override fun injectDependencies() {
componentManager().setupNewAccountComponent.get().inject(this)
}

View file

@ -16,12 +16,23 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
<ImageView
android:id="@+id/profileIcon"
android:layout_width="72dp"
android:layout_height="72dp"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:gravity="center"
android:visibility="visible"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title" />
<TextView
android:id="@+id/profileIconPlaceholder"
android:layout_width="72dp"
android:layout_height="72dp"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:background="@drawable/circle_empty_profile"
android:gravity="center"
android:text="@string/t"
@ -45,7 +56,7 @@
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/profileIcon" />
app:layout_constraintTop_toBottomOf="@+id/profileIconPlaceholder" />
<TextView
android:id="@+id/createProfileButton"
@ -66,7 +77,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_add_photo"
app:layout_constraintStart_toEndOf="@+id/profileIcon"
app:layout_constraintTop_toTopOf="@+id/profileIcon" />
app:layout_constraintStart_toEndOf="@+id/profileIconPlaceholder"
app:layout_constraintTop_toTopOf="@+id/profileIconPlaceholder" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -3,6 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/root"
android:orientation="vertical">
<FrameLayout

View file

@ -1,28 +1,33 @@
package com.agileburo.anytype.core_utils.ext
import android.content.Context
import android.view.View
import android.view.inputmethod.InputMethodManager
import androidx.constraintlayout.widget.Group
import android.net.Uri
import android.provider.MediaStore
fun Context.dimen(res: Int): Float {
return resources
.getDimension(res)
}
fun Group.setOnClickListeners(listener: View.OnClickListener?) {
referencedIds.forEach { id ->
rootView.findViewById<View>(id).setOnClickListener(listener)
}
}
fun Uri.parsePath(context: Context): String {
fun hideKeyboard(view: View?) {
view?.also {
val inputMethodManager = it.context
.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
if (!inputMethodManager.isActive) {
return
}
inputMethodManager.hideSoftInputFromWindow(it.windowToken, InputMethodManager.RESULT_UNCHANGED_SHOWN)
val result: String?
val cursor = context.contentResolver.query(
this,
null,
null,
null, null
)
if (cursor == null) {
result = this.path
} else {
cursor.moveToFirst()
val idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA)
result = cursor.getString(idx)
cursor.close()
}
return result ?: throw IllegalStateException("Cold not get real path")
}

View file

@ -1,18 +0,0 @@
package com.agileburo.anytype.core_utils.ext
import android.app.Activity
import android.content.Context
import android.view.View
import android.view.inputmethod.InputMethodManager
object UIExtensions {
fun hideSoftKeyBoard(activity: Activity, view: View?) {
(activity.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager)
?.hideSoftInputFromWindow(
view?.applicationWindowToken,
0
)
}
}

View file

@ -0,0 +1,32 @@
package com.agileburo.anytype.core_utils.ext
import android.content.Context
import android.view.View
import android.view.inputmethod.InputMethodManager
import com.google.android.material.snackbar.Snackbar
fun View.invisible() {
this.visibility = View.INVISIBLE
}
fun View.visible() {
this.visibility = View.VISIBLE
}
fun View.gone() {
this.visibility = View.GONE
}
fun View.showSnackbar(text: String) {
Snackbar.make(this, text, Snackbar.LENGTH_LONG).show()
}
fun View.hideKeyboard() {
val inputMethodManager =
context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
if (!inputMethodManager.isActive) return
inputMethodManager.hideSoftInputFromWindow(
windowToken,
InputMethodManager.RESULT_UNCHANGED_SHOWN
)
}

View file

@ -10,7 +10,7 @@ class AuthCacheDataStore(private val cache: AuthCache) : AuthDataStore {
throw UnsupportedOperationException()
}
override suspend fun createAccount(name: String): AccountEntity {
override suspend fun createAccount(name: String, avatarPath: String?): AccountEntity {
throw UnsupportedOperationException()
}

View file

@ -16,8 +16,9 @@ class AuthDataRepository(
): Account = factory.remote.selectAccount(id, path).toDomain()
override suspend fun createAccount(
name: String
): Account = factory.remote.createAccount(name).toDomain()
name: String,
avatarPath: String?
): Account = factory.remote.createAccount(name, avatarPath).toDomain()
override suspend fun recoverAccount() {
factory.remote.recoverAccount()

View file

@ -6,7 +6,7 @@ import kotlinx.coroutines.flow.Flow
interface AuthDataStore {
suspend fun selectAccount(id: String, path: String): AccountEntity
suspend fun createAccount(name: String): AccountEntity
suspend fun createAccount(name: String, avatarPath: String?): AccountEntity
suspend fun recoverAccount()
suspend fun saveAccount(account: AccountEntity)
fun observeAccounts(): Flow<AccountEntity>

View file

@ -6,7 +6,7 @@ import kotlinx.coroutines.flow.Flow
interface AuthRemote {
suspend fun selectAccount(id: String, path: String): AccountEntity
suspend fun createAccount(name: String): AccountEntity
suspend fun createAccount(name: String, avatarPath: String?): AccountEntity
suspend fun recoverAccount()
fun observeAccounts(): Flow<AccountEntity>

View file

@ -12,8 +12,9 @@ class AuthRemoteDataStore(
) = authRemote.selectAccount(id, path)
override suspend fun createAccount(
name: String
) = authRemote.createAccount(name)
name: String,
avatarPath: String?
) = authRemote.createAccount(name, avatarPath)
override suspend fun recoverAccount() {
authRemote.recoverAccount()

View file

@ -13,7 +13,8 @@ open class CreateAccount(
override suspend fun run(params: Params) = try {
repository.createAccount(
name = params.name
name = params.name,
avatarPath = params.avatarPath
).let { account ->
repository.saveAccount(account)
}.let {
@ -23,5 +24,8 @@ open class CreateAccount(
Either.Left(e)
}
class Params(val name: String)
class Params(
val name: String,
val avatarPath: String? = null
)
}

View file

@ -0,0 +1,11 @@
package com.agileburo.anytype.domain.auth.model
/**
* @property id id of the image
*/
data class Image(
val id: String,
val sizes: List<Size>
) {
enum class Size { SMALL, LARGE, THUMB }
}

View file

@ -6,7 +6,7 @@ import kotlinx.coroutines.flow.Flow
interface AuthRepository {
suspend fun selectAccount(id: String, path: String): Account
suspend fun createAccount(name: String): Account
suspend fun createAccount(name: String, avatarPath: String?): Account
suspend fun recoverAccount()
suspend fun saveAccount(account: Account)
fun observeAccounts(): Flow<Account>

View file

@ -24,8 +24,9 @@ class AuthMiddleware(
}
override suspend fun createAccount(
name: String
) = middleware.createAccount(name).let { response ->
name: String,
avatarPath: String?
) = middleware.createAccount(name, avatarPath).let { response ->
AccountEntity(
id = response.id,
name = response.name

View file

@ -1,9 +1,19 @@
package com.agileburo.anytype.middleware.interactor;
import anytype.Commands.*;
import com.agileburo.anytype.middleware.model.CreateAccountResponse;
import com.agileburo.anytype.middleware.model.CreateWalletResponse;
import com.agileburo.anytype.middleware.model.SelectAccountResponse;
import anytype.Commands.AccountCreateRequest;
import anytype.Commands.AccountCreateResponse;
import anytype.Commands.AccountRecoverRequest;
import anytype.Commands.AccountRecoverResponse;
import anytype.Commands.AccountSelectRequest;
import anytype.Commands.AccountSelectResponse;
import anytype.Commands.WalletCreateRequest;
import anytype.Commands.WalletCreateResponse;
import anytype.Commands.WalletRecoverRequest;
import anytype.Commands.WalletRecoverResponse;
import lib.Lib;
public class Middleware {
@ -26,12 +36,22 @@ public class Middleware {
}
}
public CreateAccountResponse createAccount(String name) throws Exception {
public CreateAccountResponse createAccount(String name, String path) throws Exception {
AccountCreateRequest request = AccountCreateRequest
.newBuilder()
.setUsername(name)
.build();
AccountCreateRequest request;
if (path != null) {
request = AccountCreateRequest
.newBuilder()
.setUsername(name)
.setAvatarLocalPath(path)
.build();
} else {
request = AccountCreateRequest
.newBuilder()
.setUsername(name)
.build();
}
byte[] encodedRequest = request.toByteArray();

View file

@ -6,6 +6,7 @@ import com.agileburo.anytype.core_utils.common.Event
import com.agileburo.anytype.presentation.auth.model.Session
import com.agileburo.anytype.presentation.navigation.AppNavigation
import com.agileburo.anytype.presentation.navigation.SupportNavigation
import timber.log.Timber
class CreateAccountViewModel(
val session: Session
@ -17,4 +18,9 @@ class CreateAccountViewModel(
session.name = input
navigation.postValue(Event(AppNavigation.Command.SetupNewAccountScreen))
}
fun onAvatarSet(path: String) {
session.avatarPath = path
Timber.d("Path set: $path")
}
}

View file

@ -1,9 +1,11 @@
package com.agileburo.anytype.presentation.auth.account
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.agileburo.anytype.core_utils.common.Event
import com.agileburo.anytype.core_utils.ui.ViewState
import com.agileburo.anytype.domain.auth.interactor.CreateAccount
import com.agileburo.anytype.presentation.auth.model.Session
import com.agileburo.anytype.presentation.navigation.AppNavigation
@ -17,7 +19,12 @@ class SetupNewAccountViewModel(
override val navigation: MutableLiveData<Event<AppNavigation.Command>> = MutableLiveData()
private val _state = MutableLiveData<ViewState<Any>>()
val state: LiveData<ViewState<Any>>
get() = _state
init {
_state.postValue(ViewState.Loading)
proceedWithCreatingAccount()
}
@ -29,8 +36,12 @@ class SetupNewAccountViewModel(
)
) { result ->
result.either(
fnL = { Timber.e(it, "Error while creating account") },
fnL = {
_state.postValue(ViewState.Error("Error while creating account"))
Timber.e(it, "Error while creating account")
},
fnR = {
_state.postValue(ViewState.Success(Any()))
navigation.postValue(Event(AppNavigation.Command.CongratulationScreen))
}
)

View file

@ -2,4 +2,5 @@ package com.agileburo.anytype.presentation.auth.model
class Session {
var name: String? = null
var avatarPath: String? = null
}