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:
commit
c86534ba1a
21 changed files with 215 additions and 72 deletions
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
|
@ -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 }
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
)
|
||||
|
|
|
@ -2,4 +2,5 @@ package com.agileburo.anytype.presentation.auth.model
|
|||
|
||||
class Session {
|
||||
var name: String? = null
|
||||
var avatarPath: String? = null
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue