Skip to content

Commit

Permalink
signup screen with avatar image uploading
Browse files Browse the repository at this point in the history
  • Loading branch information
Ruslan Faizullin committed Apr 2, 2021
1 parent 43a49c3 commit 85c7625
Show file tree
Hide file tree
Showing 45 changed files with 873 additions and 24 deletions.
5 changes: 5 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ android {

apollo {
generateKotlinModels.set(true)
customTypeMapping = [
"Upload" : "com.apollographql.apollo.api.FileUpload"
]
}

repositories {
Expand All @@ -107,10 +110,12 @@ dependencies {
implementation 'com.google.code.gson:gson:2.8.6'

implementation androidLibs
implementation retrofitLibs
implementation apolloLibs
implementation coroutinesLibs
implementation kodeinLibs
implementation roomLibs
implementation glideLibs
kapt compilerLibs

testImplementation unitTestLibs
Expand Down
3 changes: 3 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,7 @@
-keepattributes SourceFile,LineNumberTable


#apollo input types
-keep class * implements com.apollographql.apollo.api.InputType { *; }


Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public void whenAppLaunch_androidBaseVisible() throws Exception {

@Test
public void whenButtonClick_startedActivity() {
onView(withId(R.id.button)).perform(click());
onView(withId(R.id.selectAvatar)).perform(click());
intended(hasComponent(SecondActivity.class.getName()));
}
}
4 changes: 4 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package="com.flatstack.android">

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

<application
android:name=".App"
Expand All @@ -20,6 +21,9 @@
<activity
android:name=".login.LoginActivity"
android:windowSoftInputMode="adjustResize" />
<activity
android:name=".registration.RegistrationActivity"
android:windowSoftInputMode="adjustResize" />
<activity
android:name=".MainActivity"
android:theme="@android:style/Theme.NoDisplay">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
fragment UserGqlFragment on User {
firstName
lastName
avatarUrl
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
mutation PresignAvatar(
$input: PresignDataInput!
) {
presignData(input: $input) {
fields {
key
value
}
url
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
mutation Login($email: String!, $password: String!) {
signin(email: $email, password: $password) {
mutation Login($input: SignInInput!) {
signin(input: $input) {
accessToken
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mutation Register($input: SignUpInput!) {
signup(input: $input) {
accessToken
}
}
9 changes: 9 additions & 0 deletions app/src/main/java/com/flatstack/android/Router.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.content.Context
import android.content.Intent
import com.flatstack.android.login.LoginActivity
import com.flatstack.android.profile.ProfileActivity
import com.flatstack.android.registration.RegistrationActivity

class Router(
private val appContext: Context
Expand All @@ -23,4 +24,12 @@ class Router(
}
})
}

fun registration(context: Context, clearStack: Boolean = false) {
context.startActivity(Intent(context, RegistrationActivity::class.java).apply {
if (clearStack) {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
})
}
}
1 change: 1 addition & 0 deletions app/src/main/java/com/flatstack/android/di/kodein.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ fun initKodein(app: Application) =
import(viewModelModule)
import(netModule)
import(repoModule)
import(mapperModule)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.flatstack.android.di.modules
import com.flatstack.android.Router
import com.flatstack.android.login.LoginMapper
import com.flatstack.android.model.network.errors.ErrorHandler
import com.flatstack.android.util.FileUtils
import com.flatstack.android.util.StringResource
import org.kodein.di.Kodein
import org.kodein.di.generic.bind
Expand All @@ -13,5 +14,6 @@ val appModule = Kodein.Module(name = "appModule") {
bind<StringResource>() with singleton { StringResource(instance()) }
bind<ErrorHandler>() with singleton { ErrorHandler(instance(), instance(), instance()) }
bind<Router>() with singleton { Router(instance()) }
bind<FileUtils>() with singleton { FileUtils(instance()) }
bind<LoginMapper>() with singleton { LoginMapper() }
}
16 changes: 16 additions & 0 deletions app/src/main/java/com/flatstack/android/di/modules/mapperModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.flatstack.android.di.modules

import com.flatstack.android.registration.SessionFromRegistrationMapper
import com.flatstack.android.registration.mapper.PresignDataFromNetworkMapper
import com.flatstack.android.registration.mapper.PresignDataToNetworkMapper
import com.flatstack.android.registration.mapper.RegisterRequestToNetworkMapper
import org.kodein.di.Kodein
import org.kodein.di.generic.bind
import org.kodein.di.generic.provider

val mapperModule = Kodein.Module(name = "mapperModule") {
bind<SessionFromRegistrationMapper>() with provider { SessionFromRegistrationMapper() }
bind<RegisterRequestToNetworkMapper>() with provider { RegisterRequestToNetworkMapper() }
bind<PresignDataToNetworkMapper>() with provider { PresignDataToNetworkMapper() }
bind<PresignDataFromNetworkMapper>() with provider { PresignDataFromNetworkMapper() }
}
13 changes: 12 additions & 1 deletion app/src/main/java/com/flatstack/android/di/modules/netModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,22 @@ package com.flatstack.android.di.modules

import com.apollographql.apollo.ApolloClient
import com.flatstack.android.BuildConfig
import com.flatstack.android.model.network.NetworkManager
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import org.kodein.di.Kodein
import org.kodein.di.generic.bind
import org.kodein.di.generic.instance
import org.kodein.di.generic.singleton
import org.kodein.di.generic.*

val netModule = Kodein.Module(name = "netModule") {
bind<Interceptor>() with singleton { AuthorizationInterceptor(instance()) }

bind<OkHttpClient>() with singleton { OkHttpClient.Builder()
.addInterceptor(instance())
.addNetworkInterceptor(HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY })
.addInterceptor(instance<Interceptor>())
.build() }

bind<ApolloClient>() with singleton {
Expand All @@ -22,4 +26,11 @@ val netModule = Kodein.Module(name = "netModule") {
.okHttpClient(instance())
.build()
}

bind<OkHttpClient>("restClient") with singleton { OkHttpClient.Builder()
.addNetworkInterceptor(HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY })
.build()
}

bind<NetworkManager>() with provider { NetworkManager(instance("restClient")) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package com.flatstack.android.di.modules

import com.flatstack.android.login.LoginRepository
import com.flatstack.android.profile.ProfileRepository
import com.flatstack.android.registration.PresignDataRepository
import com.flatstack.android.registration.RegistrationRepository
import org.kodein.di.Kodein
import org.kodein.di.generic.bind
import org.kodein.di.generic.instance
Expand All @@ -10,4 +12,7 @@ import org.kodein.di.generic.provider
val repoModule = Kodein.Module(name = "repoModule") {
bind<LoginRepository>() with provider { LoginRepository(instance(), instance(), instance(), instance()) }
bind<ProfileRepository>() with provider { ProfileRepository(instance(), instance(), instance()) }
bind<RegistrationRepository>() with provider { RegistrationRepository(instance(), instance(), instance()) }
bind<PresignDataRepository>() with provider {
PresignDataRepository(instance(), instance(), instance(), instance()) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.flatstack.android.di.modules
import androidx.lifecycle.ViewModelProvider
import com.flatstack.android.login.LoginViewModel
import com.flatstack.android.profile.ProfileViewModel
import com.flatstack.android.registration.RegistrationViewModel
import com.flatstack.android.util.ViewModelFactory
import com.flatstack.android.util.bindViewModel
import org.kodein.di.Kodein
Expand All @@ -16,4 +17,6 @@ val viewModelModule = Kodein.Module(name = "viewModelModule") {

bindViewModel<LoginViewModel>() with provider { LoginViewModel(instance(), instance()) }
bindViewModel<ProfileViewModel>() with provider { ProfileViewModel(instance(), instance()) }
bindViewModel<RegistrationViewModel>() with provider {
RegistrationViewModel(instance(), instance(), instance(), instance(), instance()) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,14 @@ class LoginActivity : AppCompatActivity(), KodeinAware {
router.profile(context = this, clearStack = true)
}

private fun navigateToRegistration() {
val router by kodein.instance<Router>()
router.registration(context = this, clearStack = false)
}

private fun initListeners() {
bt_login.setOnClickListener { login() }
bt_sign_up.setOnClickListener { navigateToRegistration() }
et_password.apply {
setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_GO) {
Expand All @@ -54,7 +60,7 @@ class LoginActivity : AppCompatActivity(), KodeinAware {
}

private fun login() {
val username = et_login.text.toString()
val username = et_email.text.toString()
val password = et_password.text.toString()
viewModel.login(username, password)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.flatstack.android.model.entities.Session
import com.flatstack.android.model.network.NetworkBoundResource
import com.flatstack.android.model.network.errors.ErrorHandler
import com.flatstack.android.profile.AuthorizationModel
import com.flatstack.android.type.SignInInput
import kotlinx.coroutines.*

class LoginRepository(
Expand Down Expand Up @@ -41,5 +42,5 @@ class LoginRepository(
}

private fun loginMutation(email: String, password: String) =
LoginMutation(email = email, password = password)
LoginMutation(SignInInput(email, password))
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import com.flatstack.android.profile.entities.Profile

@Database(
entities = [Profile::class, Session::class],
version = 1,
version = 3,
exportSchema = false
)
abstract class AppDatabase : RoomDatabase() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.flatstack.android.model.network

import okhttp3.*
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody.Companion.asRequestBody
import java.io.File
import java.io.IOException

class NetworkManager(private val okHttpClient: OkHttpClient) {

fun uploadImageToAws(url: String, fields: Map<String, String>, file: File) : Boolean {
val requestBody = MultipartBody.Builder().setType(MultipartBody.FORM)
fields.forEach {
requestBody.addFormDataPart(it.key, it.value)
}
requestBody.addFormDataPart(
FORM_DATA_KEY,
file.name,
file.asRequestBody(contentType = REQUEST_BODY_CONTENT_TYPE.toMediaTypeOrNull())
)
val request = Request.Builder()
.url(url)
.method(REQUEST_METHOD, requestBody.build())
.build()
var isSuccessful = false
okHttpClient
.newCall(request)
.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
isSuccessful = false
}

override fun onResponse(call: Call, response: Response) {
isSuccessful = true
}

})
return isSuccessful
}

companion object {
const val FORM_DATA_KEY = "file"
const val REQUEST_BODY_CONTENT_TYPE = "application/octet-stream"
const val REQUEST_METHOD = "POST"
}
}
11 changes: 11 additions & 0 deletions app/src/main/java/com/flatstack/android/profile/ProfileActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
import com.bumptech.glide.Glide
import com.flatstack.android.R
import com.flatstack.android.Router
import com.flatstack.android.util.observeBy
Expand Down Expand Up @@ -38,6 +39,10 @@ class ProfileActivity : AppCompatActivity(), KodeinAware, OnRefreshListener {
showProfile()
showFirstName(it.firstName)
showLastName(it.lastName)
if (it.avatarUrl.isEmpty()) {
print("EMPTY AVATAR URL!!!!!!!!!!!!!!!!!!!!!!!!!!")
}
showAvatar(it.avatarUrl)
},
onError = ::showError,
onLoading = ::visibleProgress)
Expand Down Expand Up @@ -68,6 +73,12 @@ class ProfileActivity : AppCompatActivity(), KodeinAware, OnRefreshListener {
tv_last_name.text = lastName
}

private fun showAvatar(avatarUrl: String) {
Glide.with(this)
.load(avatarUrl)
.into(iv_avatar)
}

private fun logout() {
viewModel.logout()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ object ProfileMapper {
fun mapProfile(me: GetUserQuery.Me?) = me?.fragments?.userGqlFragment.run {
Profile(
firstName = this?.firstName ?: "",
lastName = this?.lastName ?: ""
lastName = this?.lastName ?: "",
avatarUrl = this?.avatarUrl ?: ""
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import androidx.room.PrimaryKey
data class Profile(
val firstName: String,
val lastName: String,
val avatarUrl: String,
@PrimaryKey(autoGenerate = true)
val id: Int = 0
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.flatstack.android.registration

import com.apollographql.apollo.ApolloClient
import com.apollographql.apollo.coroutines.toDeferred
import com.flatstack.android.graphql.mutation.PresignAvatarMutation
import com.flatstack.android.model.network.NetworkManager
import com.flatstack.android.registration.entities.request.PresignDataRequest
import com.flatstack.android.registration.mapper.PresignDataFromNetworkMapper
import com.flatstack.android.registration.mapper.PresignDataToNetworkMapper
import java.io.File


class PresignDataRepository(
private val apolloClient: ApolloClient,
private val networkManager: NetworkManager,
private val mapPresignDataToNetwork: PresignDataToNetworkMapper,
private val mapPresignDataFromNetwork: PresignDataFromNetworkMapper
) {
suspend fun presignData(fileName: String, type: String) =
apolloClient.mutate(
PresignAvatarMutation(
PresignDataRequest(fileName, type).run(mapPresignDataToNetwork)
)
)
.toDeferred()
.await()
.data?.presignData
?.run(mapPresignDataFromNetwork)

fun uploadImageToAws(url: String, fields: Map<String, String>, file: File) : Boolean =
networkManager.uploadImageToAws(url, fields, file)
}
Loading

0 comments on commit 85c7625

Please sign in to comment.