WIP subir foto

This commit is contained in:
Erik Everardo 2023-11-23 00:10:57 -06:00
parent 1cca08df0b
commit 5992f2a07a
27 changed files with 246 additions and 76 deletions

View File

@ -1,12 +1,17 @@
package com.isolaatti.images package com.isolaatti.images
import android.app.Application
import android.content.ContentResolver
import android.content.Context
import com.isolaatti.MyApplication
import com.isolaatti.connectivity.RetrofitClient import com.isolaatti.connectivity.RetrofitClient
import com.isolaatti.images.image_list.data.remote.ImagesApi import com.isolaatti.images.common.data.remote.ImagesApi
import com.isolaatti.images.image_list.data.repository.ImagesRepositoryImpl import com.isolaatti.images.common.data.repository.ImagesRepositoryImpl
import com.isolaatti.images.image_list.domain.repository.ImagesRepository import com.isolaatti.images.common.domain.repository.ImagesRepository
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
@Module @Module
@ -18,7 +23,12 @@ class Module {
} }
@Provides @Provides
fun provideImagesRepository(imagesApi: ImagesApi): ImagesRepository { fun provideContentResolver(@ApplicationContext application: Context): ContentResolver {
return ImagesRepositoryImpl(imagesApi) return application.contentResolver
}
@Provides
fun provideImagesRepository(imagesApi: ImagesApi, contentResolver: ContentResolver): ImagesRepository {
return ImagesRepositoryImpl(imagesApi, contentResolver)
} }
} }

View File

@ -1,4 +1,4 @@
package com.isolaatti.images.image_list.data.remote package com.isolaatti.images.common.data.remote
data class DeleteImagesDto( data class DeleteImagesDto(
val imageIds: List<String> val imageIds: List<String>

View File

@ -1,4 +1,4 @@
package com.isolaatti.images.image_list.data.remote package com.isolaatti.images.common.data.remote
data class DeleteImagesResultDto( data class DeleteImagesResultDto(
val success: Boolean, val success: Boolean,

View File

@ -1,4 +1,4 @@
package com.isolaatti.images.image_list.data.remote package com.isolaatti.images.common.data.remote
data class ImageDto( data class ImageDto(
val id: String, val id: String,

View File

@ -1,6 +1,7 @@
package com.isolaatti.images.image_list.data.remote package com.isolaatti.images.common.data.remote
import com.isolaatti.utils.SimpleData import com.isolaatti.utils.SimpleData
import okhttp3.MultipartBody
import okhttp3.RequestBody import okhttp3.RequestBody
import retrofit2.Call import retrofit2.Call
import retrofit2.http.Body import retrofit2.http.Body
@ -19,7 +20,7 @@ interface ImagesApi {
@POST("images/create") @POST("images/create")
@Multipart @Multipart
fun postImage(@Part("file") file: RequestBody, fun postImage(@Part file: MultipartBody.Part,
@Part("name") name: String, @Part("name") name: String,
@Part("setAsProfile") setAsProfile: Boolean? = null, @Part("setAsProfile") setAsProfile: Boolean? = null,
@Part("squadId") squadId: String? = null): Call<ImageDto> @Part("squadId") squadId: String? = null): Call<ImageDto>

View File

@ -0,0 +1,5 @@
package com.isolaatti.images.common.data.remote
data class ImagesDto(
val data: List<ImageDto>
)

View File

@ -0,0 +1,76 @@
package com.isolaatti.images.common.data.repository
import android.content.ContentResolver
import android.net.Uri
import android.util.Log
import com.isolaatti.images.common.data.remote.ImagesApi
import com.isolaatti.images.common.domain.entity.Image
import com.isolaatti.images.common.domain.repository.ImagesRepository
import com.isolaatti.utils.Resource
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import okhttp3.MultipartBody
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import retrofit2.awaitResponse
import java.io.InputStream
import javax.inject.Inject
class ImagesRepositoryImpl @Inject constructor(private val imagesApi: ImagesApi, private val contentResolver: ContentResolver) :
ImagesRepository {
override fun getImagesOfUser(userId: Int, lastId: String?): Flow<Resource<List<Image>>> = flow {
emit(Resource.Loading())
try {
val response = imagesApi.getImagesOfUser(userId, lastId).awaitResponse()
if(response.isSuccessful) {
val imagesDto = response.body()
val images = imagesDto?.data?.map { Image.fromDto(it) }
emit(Resource.Success(images))
} else {
emit(Resource.Error(Resource.Error.mapErrorCode(response.code())))
}
} catch(_: Exception) {
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
}
}
override fun deleteImages(images: List<Image>): Flow<Resource<Boolean>> = flow {
}
override fun uploadImage(name: String, imageUri: Uri, squadId: String?): Flow<Resource<Image>> = flow {
var imageInputStream: InputStream? = null
try {
imageInputStream = contentResolver.openInputStream(imageUri)
val imageBytes = imageInputStream?.readBytes()
if(imageBytes == null) {
emit(Resource.Error(Resource.Error.ErrorType.InputError))
return@flow
}
Log.d("ImagesRepository", "${imageBytes.size} bytes")
val response = imagesApi.postImage(MultipartBody.Part.createFormData("file", name,imageBytes.toRequestBody()), name).awaitResponse()
if(response.isSuccessful) {
val imageDto = response.body()
if(imageDto == null) {
emit(Resource.Error(Resource.Error.ErrorType.ServerError))
return@flow
}
val image = Image.fromDto(imageDto)
emit(Resource.Success(image))
} else {
emit(Resource.Error(Resource.Error.mapErrorCode(response.code())))
}
} catch(e: Exception) {
Log.e("ImagesRepository", e.toString())
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
} finally {
imageInputStream?.close()
}
}
}

View File

@ -1,6 +1,6 @@
package com.isolaatti.images.image_list.domain.entity package com.isolaatti.images.common.domain.entity
import com.isolaatti.images.image_list.data.remote.ImageDto import com.isolaatti.images.common.data.remote.ImageDto
import com.isolaatti.utils.UrlGen import com.isolaatti.utils.UrlGen
import java.io.Serializable import java.io.Serializable

View File

@ -1,10 +1,12 @@
package com.isolaatti.images.image_list.domain.repository package com.isolaatti.images.common.domain.repository
import com.isolaatti.images.image_list.domain.entity.Image import android.net.Uri
import com.isolaatti.images.common.domain.entity.Image
import com.isolaatti.utils.Resource import com.isolaatti.utils.Resource
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
interface ImagesRepository { interface ImagesRepository {
fun getImagesOfUser(userId: Int, lastId: String? = null): Flow<Resource<List<Image>>> fun getImagesOfUser(userId: Int, lastId: String? = null): Flow<Resource<List<Image>>>
fun deleteImages(images: List<Image>): Flow<Resource<Boolean>> fun deleteImages(images: List<Image>): Flow<Resource<Boolean>>
fun uploadImage(name: String, imageUri: Uri, squadId: String?): Flow<Resource<Image>>
} }

View File

@ -1,5 +0,0 @@
package com.isolaatti.images.image_list.data.remote
data class ImagesDto(
val data: List<ImageDto>
)

View File

@ -1,33 +0,0 @@
package com.isolaatti.images.image_list.data.repository
import com.isolaatti.images.image_list.data.remote.ImagesApi
import com.isolaatti.images.image_list.domain.entity.Image
import com.isolaatti.images.image_list.domain.repository.ImagesRepository
import com.isolaatti.utils.Resource
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import retrofit2.awaitResponse
import javax.inject.Inject
class ImagesRepositoryImpl @Inject constructor(private val imagesApi: ImagesApi) : ImagesRepository {
override fun getImagesOfUser(userId: Int, lastId: String?): Flow<Resource<List<Image>>> = flow {
try {
val response = imagesApi.getImagesOfUser(userId, lastId).awaitResponse()
if(response.isSuccessful) {
val imagesDto = response.body()
val images = imagesDto?.data?.map { Image.fromDto(it) }
emit(Resource.Success(images))
} else {
emit(Resource.Error(Resource.Error.mapErrorCode(response.code())))
}
} catch(_: Exception) {
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
}
}
override fun deleteImages(images: List<Image>): Flow<Resource<Boolean>> = flow {
}
}

View File

@ -3,8 +3,8 @@ package com.isolaatti.images.image_list.presentation
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.isolaatti.images.image_list.domain.entity.Image import com.isolaatti.images.common.domain.entity.Image
import com.isolaatti.images.image_list.domain.repository.ImagesRepository import com.isolaatti.images.common.domain.repository.ImagesRepository
import com.isolaatti.utils.Resource import com.isolaatti.utils.Resource
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers

View File

@ -9,7 +9,7 @@ import androidx.recyclerview.widget.RecyclerView.Adapter
import coil.load import coil.load
import com.isolaatti.common.CoilImageLoader.imageLoader import com.isolaatti.common.CoilImageLoader.imageLoader
import com.isolaatti.databinding.ImageItemBinding import com.isolaatti.databinding.ImageItemBinding
import com.isolaatti.images.image_list.domain.entity.Image import com.isolaatti.images.common.domain.entity.Image
class ImagesAdapter( class ImagesAdapter(
private val imageOnClick: ((images: List<Image>, position: Int) -> Unit), private val imageOnClick: ((images: List<Image>, position: Int) -> Unit),

View File

@ -9,6 +9,7 @@ import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
@ -22,7 +23,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.isolaatti.MyApplication import com.isolaatti.MyApplication
import com.isolaatti.R import com.isolaatti.R
import com.isolaatti.databinding.FragmentImagesBinding import com.isolaatti.databinding.FragmentImagesBinding
import com.isolaatti.images.image_list.domain.entity.Image import com.isolaatti.images.common.domain.entity.Image
import com.isolaatti.images.image_list.presentation.ImageListViewModel import com.isolaatti.images.image_list.presentation.ImageListViewModel
import com.isolaatti.images.image_list.presentation.ImagesAdapter import com.isolaatti.images.image_list.presentation.ImagesAdapter
import com.isolaatti.images.image_maker.ui.ImageMakerContract import com.isolaatti.images.image_maker.ui.ImageMakerContract
@ -46,7 +47,7 @@ class ImagesFragment : Fragment() {
} }
private val imageMakerLauncher = registerForActivityResult(ImageMakerContract()) { private val imageMakerLauncher = registerForActivityResult(ImageMakerContract()) {
Toast.makeText(requireContext(), "se subio la imagen ${it?.id}", Toast.LENGTH_SHORT).show()
} }
private val choosePictureLauncher = registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { private val choosePictureLauncher = registerForActivityResult(ActivityResultContracts.PickVisualMedia()) {
@ -191,9 +192,12 @@ class ImagesFragment : Fragment() {
viewModel.list.observe(viewLifecycleOwner) { resource -> viewModel.list.observe(viewLifecycleOwner) { resource ->
when(resource) { when(resource) {
is Resource.Error -> {} is Resource.Error -> {}
is Resource.Loading -> {} is Resource.Loading -> {
viewBinding.progressBarLoading.visibility = View.VISIBLE
}
is Resource.Success -> { is Resource.Success -> {
resource.data?.let { resource.data?.let {
viewBinding.progressBarLoading.visibility = View.GONE
adapter.setData(it) adapter.setData(it)
} }
} }

View File

@ -1,9 +1,33 @@
package com.isolaatti.images.image_maker.presentation package com.isolaatti.images.image_maker.presentation
import android.net.Uri
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.isolaatti.images.common.domain.entity.Image
import com.isolaatti.images.common.domain.repository.ImagesRepository
import com.isolaatti.utils.Resource
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
class ImageMakerViewModel @Inject constructor() : ViewModel() { class ImageMakerViewModel @Inject constructor(private val imagesRepository: ImagesRepository) : ViewModel() {
var imageUri: Uri? = null
var name: String? = null
val image: MutableLiveData<Resource<Image>> = MutableLiveData()
fun uploadPicture() {
if(imageUri == null || name == null) {
return
}
viewModelScope.launch {
imagesRepository.uploadImage(name!!, imageUri!!, null).onEach {
image.postValue(it)
}.flowOn(Dispatchers.IO).launchIn(this)
}
}
} }

View File

@ -1,21 +1,73 @@
package com.isolaatti.images.image_maker.ui package com.isolaatti.images.image_maker.ui
import android.app.Activity
import android.content.ContentProvider
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.activity.viewModels
import androidx.core.content.ContentProviderCompat
import androidx.core.widget.doOnTextChanged
import coil.load import coil.load
import com.isolaatti.R
import com.isolaatti.common.IsolaattiBaseActivity import com.isolaatti.common.IsolaattiBaseActivity
import com.isolaatti.databinding.ActivityImageMakerBinding import com.isolaatti.databinding.ActivityImageMakerBinding
import com.isolaatti.images.image_maker.presentation.ImageMakerViewModel
import com.isolaatti.utils.Resource
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint @AndroidEntryPoint
class ImageMakerActivity : IsolaattiBaseActivity() { class ImageMakerActivity : IsolaattiBaseActivity() {
private lateinit var binding: ActivityImageMakerBinding private lateinit var binding: ActivityImageMakerBinding
private val viewModel: ImageMakerViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityImageMakerBinding.inflate(layoutInflater) binding = ActivityImageMakerBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
viewModel.imageUri = intent.data
binding.imagePreview.load(intent.data) binding.imagePreview.load(intent.data)
setupListeners()
setupObservers()
}
private fun setupListeners() {
binding.materialToolbar.setOnMenuItemClickListener {
when(it.itemId) {
R.id.upload_picture_item -> {
viewModel.uploadPicture()
true
}
else -> false
}
}
binding.textImageName.editText?.doOnTextChanged { text, _, _, _ ->
viewModel.name = text.toString()
}
}
private fun setupObservers() {
viewModel.image.observe(this) {
when(it) {
is Resource.Error -> {
errorViewModel.error.value = it.errorType
}
is Resource.Loading -> {
binding.progressBarLoading.visibility = View.VISIBLE
}
is Resource.Success -> {
binding.progressBarLoading.visibility = View.GONE
setResult(Activity.RESULT_OK)
intent = Intent().putExtra(EXTRA_IMAGE, it.data)
finish()
}
}
}
}
companion object {
const val EXTRA_IMAGE = "image"
} }
} }

View File

@ -1,10 +1,12 @@
package com.isolaatti.images.image_maker.ui package com.isolaatti.images.image_maker.ui
import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build
import androidx.activity.result.contract.ActivityResultContract import androidx.activity.result.contract.ActivityResultContract
import com.isolaatti.images.image_list.domain.entity.Image import com.isolaatti.images.common.domain.entity.Image
class ImageMakerContract : ActivityResultContract<Uri, Image?>() { class ImageMakerContract : ActivityResultContract<Uri, Image?>() {
override fun createIntent(context: Context, input: Uri): Intent { override fun createIntent(context: Context, input: Uri): Intent {
@ -15,6 +17,13 @@ class ImageMakerContract : ActivityResultContract<Uri, Image?>() {
} }
override fun parseResult(resultCode: Int, intent: Intent?): Image? { override fun parseResult(resultCode: Int, intent: Intent?): Image? {
if(resultCode == Activity.RESULT_OK) {
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
intent?.getSerializableExtra(ImageMakerActivity.EXTRA_IMAGE) as Image?
} else {
intent?.getSerializableExtra(ImageMakerActivity.EXTRA_IMAGE, Image::class.java)
}
}
return null return null
} }
} }

View File

@ -2,7 +2,7 @@ package com.isolaatti.images.picture_viewer.presentation
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.adapter.FragmentStateAdapter
import com.isolaatti.images.image_list.domain.entity.Image import com.isolaatti.images.common.domain.entity.Image
import com.isolaatti.images.picture_viewer.ui.PictureViewerImageWrapperFragment import com.isolaatti.images.picture_viewer.ui.PictureViewerImageWrapperFragment
class PictureViewerViewPagerAdapter(fragment: Fragment, private val images: Array<Image>) : FragmentStateAdapter(fragment) { class PictureViewerViewPagerAdapter(fragment: Fragment, private val images: Array<Image>) : FragmentStateAdapter(fragment) {

View File

@ -5,7 +5,7 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import com.isolaatti.databinding.ActivityPictureViewerBinding import com.isolaatti.databinding.ActivityPictureViewerBinding
import com.isolaatti.images.image_list.domain.entity.Image import com.isolaatti.images.common.domain.entity.Image
import com.isolaatti.images.picture_viewer.presentation.PictureViewerViewPagerAdapter import com.isolaatti.images.picture_viewer.presentation.PictureViewerViewPagerAdapter
class PictureViewerActivity : AppCompatActivity() { class PictureViewerActivity : AppCompatActivity() {

View File

@ -8,7 +8,7 @@ import androidx.fragment.app.Fragment
import coil.load import coil.load
import com.isolaatti.common.CoilImageLoader.imageLoader import com.isolaatti.common.CoilImageLoader.imageLoader
import com.isolaatti.databinding.FragmentTouchImageViewWrapperBinding import com.isolaatti.databinding.FragmentTouchImageViewWrapperBinding
import com.isolaatti.images.image_list.domain.entity.Image import com.isolaatti.images.common.domain.entity.Image
import com.ortiz.touchview.OnTouchImageViewListener import com.ortiz.touchview.OnTouchImageViewListener

View File

@ -7,7 +7,7 @@ import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2
import com.isolaatti.databinding.FragmentMainPictureViewerBinding import com.isolaatti.databinding.FragmentMainPictureViewerBinding
import com.isolaatti.images.image_list.domain.entity.Image import com.isolaatti.images.common.domain.entity.Image
import com.isolaatti.images.picture_viewer.presentation.PictureViewerViewPagerAdapter import com.isolaatti.images.picture_viewer.presentation.PictureViewerViewPagerAdapter
class PictureViewerMainFragment : Fragment() { class PictureViewerMainFragment : Fragment() {

View File

@ -60,6 +60,7 @@ class LogInActivity: AppCompatActivity() {
Resource.Error.ErrorType.ServerError -> showServerErrorMessage() Resource.Error.ErrorType.ServerError -> showServerErrorMessage()
Resource.Error.ErrorType.OtherError -> showUnknownErrorMessage() Resource.Error.ErrorType.OtherError -> showUnknownErrorMessage()
null -> {} null -> {}
Resource.Error.ErrorType.InputError -> {}
} }
} }

View File

@ -5,7 +5,7 @@ sealed class Resource<T> {
class Loading<T>: Resource<T>() class Loading<T>: Resource<T>()
class Error<T>(val errorType: ErrorType? = null, val message: String? = null): Resource<T>() { class Error<T>(val errorType: ErrorType? = null, val message: String? = null): Resource<T>() {
enum class ErrorType { enum class ErrorType {
NetworkError, AuthError, NotFoundError, ServerError, OtherError NetworkError, AuthError, NotFoundError, ServerError, InputError, OtherError
} }
companion object { companion object {
fun mapErrorCode(errorCode: Int): ErrorType { fun mapErrorCode(errorCode: Int): ErrorType {

View File

@ -13,7 +13,8 @@
app:title="@string/upload_a_picture" app:title="@string/upload_a_picture"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent"
app:menu="@menu/image_maker_menu"/>
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/textImageName" android:id="@+id/textImageName"
@ -24,6 +25,7 @@
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:hint="@string/picture_name"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/materialToolbar"> app:layout_constraintTop_toBottomOf="@+id/materialToolbar">
@ -39,20 +41,21 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_margin="32dp" android:layout_margin="32dp"
app:layout_constraintBottom_toTopOf="@+id/postButton" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textImageName" /> app:layout_constraintTop_toBottomOf="@+id/textImageName" />
<com.google.android.material.button.MaterialButton <ProgressBar
android:id="@+id/postButton" android:id="@+id/progress_bar_loading"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/upload_photo" android:visibility="gone"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/materialToolbar"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
@ -22,6 +23,13 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" /> app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
<ProgressBar
android:id="@+id/progress_bar_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone"
tools:visibility="visible"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/new_picture_button" android:id="@+id/new_picture_button"

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/upload_picture_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:icon="@drawable/baseline_check_24"
android:title="@string/upload_photo"
app:showAsAction="always"/>
</menu>

View File

@ -120,4 +120,5 @@
<string name="upload_photo">Upload picture</string> <string name="upload_photo">Upload picture</string>
<string name="delete_images_dialog_message">Remove %d images?</string> <string name="delete_images_dialog_message">Remove %d images?</string>
<string name="selected_images_count">Images selected: %d</string> <string name="selected_images_count">Images selected: %d</string>
<string name="picture_name">Picture name</string>
</resources> </resources>