enable creating albums (#229)

* Moved the crop button so that it doesn't take space in the activity

* Semi transparent in the middle, same position than the image

* First draft of the album creation

* choose multiple images in gallery

* Added functionalities to Album creation

* merge with master

* Gallery of images selected for the album creation

* to merge with master

* Images editable individually

* Creation of album is now possible

* Added tests

* Added test to edit picture selected

* merge PostCreation and AlbumCreation

* Merged completely PostCreation and AlbumCreation

* removed albumCreation in Manifest

* Refactored slightly

* Don't re-upload all images at each edit, only re-upload one

* Make sure all images are uploaded, correctly calculate progress

* comment assert, sorry

* fix test

* fix merge

Co-authored-by: Joachim Dunant <joachim.dunant@epfl.ch>
Co-authored-by: Matthieu <61561059+Wv5twkFEKh54vo4tta9yu7dHa3@users.noreply.github.com>
This commit is contained in:
Sanimys 2020-06-05 23:49:28 +02:00 committed by GitHub
parent a409d69c77
commit 7981ae8643
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 376 additions and 203 deletions

View File

@ -3,11 +3,9 @@ package com.h.pixeldroid
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Color
import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Environment
import android.view.View
import android.webkit.MimeTypeMap
import android.widget.SeekBar
import androidx.core.net.toUri
import androidx.lifecycle.Lifecycle
@ -28,7 +26,6 @@ import com.h.pixeldroid.adapters.ThumbnailAdapter
import com.h.pixeldroid.testUtility.CustomMatchers
import junit.framework.Assert.assertTrue
import kotlinx.android.synthetic.main.fragment_edit_image.*
import org.hamcrest.CoreMatchers
import org.hamcrest.CoreMatchers.allOf
import org.junit.Assert
import org.junit.Before
@ -37,7 +34,6 @@ import org.junit.Test
import org.junit.rules.Timeout
import org.junit.runner.RunWith
import java.io.File
import java.net.URI
@RunWith(AndroidJUnit4::class)
class EditPhotoTest {
@ -170,33 +166,9 @@ class EditPhotoTest {
@Test
fun backButton() {
Espresso.onView(withId(R.id.toolbar)).check(matches(isDisplayed()))
Espresso.onView(withContentDescription(R.string.abc_action_bar_up_description)).perform(click());
Espresso.onView(withContentDescription(R.string.abc_action_bar_up_description)).perform(click())
assertTrue(activityScenario.state == Lifecycle.State.DESTROYED) }
@Test
fun buttonUploadLaunchNewPostActivity() {
Espresso.onView(withId(R.id.action_upload)).perform(click())
Thread.sleep(1000)
Espresso.onView(withId(R.id.post_creation_picture_frame)).check(matches(isDisplayed()))
}
@Test
fun modifiedUploadLaunchesNewPostActivity() {
Espresso.onView(withId(R.id.recycler_view))
.perform(actionOnItemAtPosition<ThumbnailAdapter.MyViewHolder>(2, CustomMatchers.clickChildViewWithId(R.id.thumbnail)))
Thread.sleep(1000)
Espresso.onView(withId(R.id.tabs)).perform(selectTabAtPosition(1))
Espresso.onView(withId(R.id.seekbar_brightness)).perform(setProgress(5))
Thread.sleep(1000)
Espresso.onView(withId(R.id.action_upload)).perform(click())
Thread.sleep(1000)
Espresso.onView(withId(R.id.post_creation_picture_frame)).check(matches(isDisplayed()))
}
@Test
fun croppingIsPossible() {
Espresso.onView(withId(R.id.cropImageButton)).perform(click())

View File

@ -1,25 +1,28 @@
package com.h.pixeldroid
import android.content.Context
import android.Manifest
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Color
import android.net.Uri
import android.os.Environment
import android.util.Log
import android.view.View.VISIBLE
import androidx.core.net.toUri
import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.GrantPermissionRule
import com.h.pixeldroid.adapters.ThumbnailAdapter
import com.h.pixeldroid.db.AppDatabase
import com.h.pixeldroid.db.InstanceDatabaseEntity
import com.h.pixeldroid.db.UserDatabaseEntity
import com.h.pixeldroid.testUtility.CustomMatchers
import com.h.pixeldroid.testUtility.MockServer
import com.h.pixeldroid.utils.DBUtils
import kotlinx.android.synthetic.main.activity_post_creation.*
@ -42,6 +45,9 @@ class PostCreationActivityTest {
@get:Rule
val globalTimeout: Timeout = Timeout.seconds(30)
@get:Rule
val mRuntimePermissionRule: GrantPermissionRule = GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE)
private fun File.writeBitmap(bitmap: Bitmap) {
outputStream().use { out ->
bitmap.compress(Bitmap.CompressFormat.PNG, 85, out)
@ -76,21 +82,28 @@ class PostCreationActivityTest {
)
db.close()
var uri: Uri = "".toUri()
val scenario = ActivityScenario.launch(ProfileActivity::class.java)
var uri1: String = ""
var uri2: String = ""
val scenario = ActivityScenario.launch(MainActivity::class.java)
scenario.onActivity {
val image = Bitmap.createBitmap(500, 500, Bitmap.Config.ARGB_8888)
image.eraseColor(Color.GREEN)
val image1 = Bitmap.createBitmap(500, 500, Bitmap.Config.ARGB_8888)
image1.eraseColor(Color.GREEN)
val image2 = Bitmap.createBitmap(270, 270, Bitmap.Config.ARGB_8888)
image2.eraseColor(Color.RED)
val folder =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)
if (!folder.exists()) {
folder.mkdir()
}
val file = File.createTempFile("temp_img", ".png", folder)
file.writeBitmap(image)
uri = file.toUri()
val file1 = File.createTempFile("temp_img1", ".png", folder)
val file2 = File.createTempFile("temp_img2", ".png", folder)
file1.writeBitmap(image1)
uri1 = file1.toUri().toString()
file2.writeBitmap(image2)
uri2 = file2.toUri().toString()
Log.d("test", uri1+"\n"+uri2)
}
val intent = Intent(context, PostCreationActivity::class.java).putExtra("picture_uri", uri)
val intent = Intent(context, PostCreationActivity::class.java).putExtra("pictures_uri", arrayListOf(uri1, uri2))
testScenario = ActivityScenario.launch(intent)
}
@ -108,4 +121,38 @@ class PostCreationActivityTest {
// should send on main activity
onView(withId(R.id.retry_upload_button)).check(matches(not(isDisplayed())))
}
@Test
fun editImage() {
onView(withId(R.id.image_grid)).perform(
RecyclerViewActions.actionOnItemAtPosition<PostCreationActivity.PostCreationAdapter.ViewHolder>(
0,
CustomMatchers.clickChildViewWithId(R.id.galleryImage)
)
)
Thread.sleep(1000)
onView(withId(R.id.recycler_view))
.perform(
RecyclerViewActions.actionOnItemAtPosition<ThumbnailAdapter.MyViewHolder>(
2,
CustomMatchers.clickChildViewWithId(R.id.thumbnail)
)
)
Thread.sleep(1000)
onView(withId(R.id.action_upload)).perform(click())
Thread.sleep(1000)
onView(withId(R.id.image_grid)).check(matches(isDisplayed()))
}
@Test
fun cancelEdit() {
onView(withId(R.id.image_grid)).perform(
RecyclerViewActions.actionOnItemAtPosition<PostCreationActivity.PostCreationAdapter.ViewHolder>(
0,
CustomMatchers.clickChildViewWithId(R.id.galleryImage)
)
)
Thread.sleep(1000)
}
}

View File

@ -27,7 +27,6 @@
<activity
android:name=".PhotoEditActivity"
android:theme="@style/AppTheme.NoActionBar"/>
<activity android:name=".PostCreationActivity"
android:screenOrientation="sensorPortrait"
tools:ignore="LockedOrientationActivity"

Binary file not shown.

View File

@ -88,7 +88,6 @@ class PhotoEditActivity : AppCompatActivity(), FilterListFragmentListener, EditI
System.loadLibrary("NativeImageProcessor")
}
companion object{
private var executor: ExecutorService = newSingleThreadExecutor()
private var future: Future<*>? = null
@ -114,7 +113,7 @@ class PhotoEditActivity : AppCompatActivity(), FilterListFragmentListener, EditI
initialUri = intent.getParcelableExtra("picture_uri")
imageUri = initialUri
// set on-click listener
cropButton.setOnClickListener {
startCrop()
@ -187,12 +186,12 @@ class PhotoEditActivity : AppCompatActivity(), FilterListFragmentListener, EditI
}
}
return super.onOptionsItemSelected(item)
}
//</editor-fold>
//<editor-fold desc="FILTERS">
return super.onOptionsItemSelected(item)
}
//</editor-fold>
override fun onFilterSelected(filter: Filter) {
resetControls()
filteredImage = compressedOriginalImage!!.copy(BITMAP_CONFIG, true)
@ -346,11 +345,15 @@ class PhotoEditActivity : AppCompatActivity(), FilterListFragmentListener, EditI
return finalImage
}
private fun uploadImage(file: String) {
val intent = Intent (applicationContext, PostCreationActivity::class.java)
intent.putExtra("picture_uri", Uri.parse(file))
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
applicationContext!!.startActivity(intent)
private fun sendBackImage(file: String) {
val intent = Intent(this, PostCreationActivity::class.java)
.apply {
putExtra("result", file)
addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
}
setResult(Activity.RESULT_OK, intent)
finish()
}
private fun saveImageToGallery(save: Boolean) {
@ -464,8 +467,8 @@ class PhotoEditActivity : AppCompatActivity(), FilterListFragmentListener, EditI
}
if(saving) {
this.runOnUiThread {
if (!save) {
uploadImage(path)
if(!save) {
sendBackImage(path)
} else {
MediaScannerConnection.scanFile(
this,

View File

@ -1,56 +1,59 @@
package com.h.pixeldroid
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Bundle
import android.provider.OpenableColumns
import android.util.Log
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.net.toFile
import androidx.core.net.toUri
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.google.android.material.textfield.TextInputEditText
import com.h.pixeldroid.api.PixelfedAPI
import com.h.pixeldroid.db.UserDatabaseEntity
import com.h.pixeldroid.interfaces.PostCreationListener
import com.h.pixeldroid.objects.Attachment
import com.h.pixeldroid.objects.Instance
import com.h.pixeldroid.objects.Status
import com.h.pixeldroid.utils.DBUtils
import com.mikepenz.iconics.Iconics
import io.reactivex.Observable
import com.h.pixeldroid.utils.ProgressRequestBody
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import io.reactivex.subjects.PublishSubject
import kotlinx.android.synthetic.main.activity_post_creation.*
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import kotlinx.android.synthetic.main.image_album_creation.view.*
import okhttp3.MultipartBody
import okhttp3.RequestBody
import okio.BufferedSink
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import java.io.*
class PostCreationActivity : AppCompatActivity(){
class PostCreationActivity : AppCompatActivity(), PostCreationListener {
private val TAG = "Post Creation Activity"
private lateinit var recycler : RecyclerView
private lateinit var adapter : PostCreationAdapter
private lateinit var accessToken: String
private lateinit var pixelfedAPI: PixelfedAPI
private lateinit var pictureFrame: ImageView
private lateinit var imageUri: Uri
private var muListOfIds: MutableList<String> = mutableListOf()
private var progressList: ArrayList<Int> = arrayListOf()
private var positionResult = 0
private var user: UserDatabaseEntity? = null
private var listOfIds: List<String> = emptyList()
private var posts: ArrayList<String> = ArrayList()
private var maxLength: Int = Instance.DEFAULT_MAX_TOOT_CHARS
@ -58,14 +61,13 @@ class PostCreationActivity : AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Iconics.init(this)
setContentView(R.layout.activity_post_creation)
imageUri = intent.getParcelableExtra("picture_uri")!!
pictureFrame = findViewById(R.id.post_creation_picture_frame)
Glide.with(this).load(imageUri).into(pictureFrame)
// load images
posts = intent.getStringArrayListExtra("pictures_uri")!!
progressList = posts.map { 0 } as ArrayList<Int>
muListOfIds = posts.map { "" }.toMutableList()
val db = DBUtils.initDB(applicationContext)
user = db.userDao().getActiveUser()
@ -86,34 +88,38 @@ class PostCreationActivity : AppCompatActivity(){
accessToken = user?.accessToken.orEmpty()
pixelfedAPI = PixelfedAPI.create(domain)
// check if the pictures are alright
// TODO
//upload the picture and display progress while doing so
upload()
adapter = PostCreationAdapter(posts)
adapter.listener = this
recycler = findViewById(R.id.image_grid)
recycler.layoutManager = GridLayoutManager(this, if (posts.size > 2) 2 else 1)
recycler.adapter = adapter
// get the description and send the post
findViewById<Button>(R.id.post_creation_send_button).setOnClickListener {
if (setDescription() && listOfIds.isNotEmpty()) post()
if (setDescription() && muListOfIds.isNotEmpty()) post()
}
// Button to retry image upload when it fails
findViewById<Button>(R.id.retry_upload_button).setOnClickListener {
upload_error.visibility = GONE
upload_error.visibility = View.GONE
muListOfIds = posts.map { "" }.toMutableList()
progressList = posts.map { 0 } as ArrayList<Int>
upload()
}
}
override fun onDestroy() {
super.onDestroy()
//delete the temporary image
//image.delete()
}
private fun setDescription(): Boolean {
val textField = findViewById<TextInputEditText>(R.id.new_post_description_input_field)
val content = textField.text.toString()
if (content.length > maxLength) {
// error, too many characters
textField.error = getString(R.string.description_max_characters).format(maxLength)
// error, too much characters
textField.error = "Description must contain $maxLength characters at most."
return false
}
// store the description
@ -122,57 +128,69 @@ class PostCreationActivity : AppCompatActivity(){
}
private fun upload() {
val imageInputStream = contentResolver.openInputStream(imageUri)!!
for ((index, post) in posts.withIndex()) {
val imageUri = Uri.parse(post)
val imageInputStream = contentResolver.openInputStream(imageUri)!!
val size =
if(imageUri.toString().startsWith("content")) {
contentResolver.query(imageUri, null, null, null, null)
?.use { cursor ->
/*
* Get the column indexes of the data in the Cursor,
* move to the first row in the Cursor, get the data,
* and display it.
*/
val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
cursor.moveToFirst()
cursor.getLong(sizeIndex)
} ?: 0
} else {
imageUri.toFile().length()
}
val size =
if (imageUri.toString().startsWith("content")) {
contentResolver.query(imageUri, null, null, null, null)
?.use { cursor ->
/* Get the column indexes of the data in the Cursor,
* move to the first row in the Cursor, get the data,
* and display it.
*/
val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
cursor.moveToFirst()
cursor.getLong(sizeIndex)
} ?: 0
} else {
imageUri.toFile().length()
}
val imagePart = ProgressRequestBody(imageInputStream, size)
val requestBody = MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("file", System.currentTimeMillis().toString(), imagePart)
.build()
val imagePart = ProgressRequestBody(imageInputStream, size)
val requestBody = MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("file", System.currentTimeMillis().toString(), imagePart)
.build()
val sub = imagePart.progressSubject
.subscribeOn(Schedulers.io())
.subscribe { percentage ->
uploadProgressBar.progress = percentage.toInt()
}
val sub = imagePart.progressSubject
.subscribeOn(Schedulers.io())
.subscribe { percentage ->
progressList[index] = percentage.toInt()
uploadProgressBar.progress =
progressList.sum() / progressList.size
}
var postSub : Disposable?= null
val inter = pixelfedAPI.mediaUpload("Bearer $accessToken", requestBody.parts[0])
var postSub: Disposable? = null
val inter = pixelfedAPI.mediaUpload("Bearer $accessToken", requestBody.parts[0])
postSub = inter
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ attachment ->
listOfIds = listOf(attachment.id!!)
},{e->
upload_error.visibility = VISIBLE
e.printStackTrace()
postSub?.dispose()
sub.dispose()
}, {
uploadProgressBar.visibility = GONE
upload_completed_textview.visibility = VISIBLE
enableButton(true)
postSub?.dispose()
sub.dispose()
})
postSub = inter
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ attachment: Attachment ->
progressList[index] = 0
muListOfIds[index] = attachment.id!!
},
{ e ->
upload_error.visibility = View.VISIBLE
e.printStackTrace()
postSub?.dispose()
sub.dispose()
},
{
progressList[index] = 100
if(progressList.all{it == 100}){
enableButton(true)
uploadProgressBar.visibility = View.GONE
upload_completed_textview.visibility = View.VISIBLE
}
postSub?.dispose()
sub.dispose()
}
)
}
}
private fun post() {
@ -180,7 +198,7 @@ class PostCreationActivity : AppCompatActivity(){
pixelfedAPI.postStatus(
authorization = "Bearer $accessToken",
statusText = description,
media_ids = listOfIds
media_ids = muListOfIds.toList()
).enqueue(object: Callback<Status> {
override fun onFailure(call: Call<Status>, t: Throwable) {
enableButton(true)
@ -205,67 +223,79 @@ class PostCreationActivity : AppCompatActivity(){
}
})
}
private fun enableButton(enable: Boolean = true){
post_creation_send_button.isEnabled = enable
if(enable){
posting_progress_bar.visibility = GONE
post_creation_send_button.visibility = VISIBLE
posting_progress_bar.visibility = View.GONE
post_creation_send_button.visibility = View.VISIBLE
} else{
posting_progress_bar.visibility = VISIBLE
post_creation_send_button.visibility = GONE
posting_progress_bar.visibility = View.VISIBLE
post_creation_send_button.visibility = View.GONE
}
}
}
override fun onClick(position: Int) {
positionResult = position
class ProgressRequestBody(private val mFile: InputStream, private val length: Long) : RequestBody() {
private val getProgressSubject: PublishSubject<Float> = PublishSubject.create()
val progressSubject: Observable<Float>
get() {
return getProgressSubject
}
override fun contentType(): MediaType? {
return "image/png".toMediaTypeOrNull()
val intent = Intent(this, PhotoEditActivity::class.java)
.putExtra("picture_uri", Uri.parse(posts[position]))
.putExtra("no upload", false)
startActivityForResult(intent, positionResult)
}
@Throws(IOException::class)
override fun contentLength(): Long {
return length
}
@Throws(IOException::class)
override fun writeTo(sink: BufferedSink) {
val fileLength = contentLength()
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
var uploaded: Long = 0
mFile.use {
var read: Int
var lastProgressPercentUpdate = 0.0f
read = it.read(buffer)
while (read != -1) {
uploaded += read.toLong()
sink.write(buffer, 0, read)
read = it.read(buffer)
val progress = (uploaded.toFloat() / fileLength.toFloat()) * 100f
//prevent publishing too many updates, which slows upload, by checking if the upload has progressed by at least 1 percent
if (progress - lastProgressPercentUpdate > 1 || progress == 100f) {
// publish progress
getProgressSubject.onNext(progress)
lastProgressPercentUpdate = progress
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == positionResult) {
if (resultCode == Activity.RESULT_OK && data != null) {
posts[positionResult] = data.getStringExtra("result")!!
adapter.notifyItemChanged(positionResult)
muListOfIds.clear()
upload()
}
else if(resultCode == Activity.RESULT_CANCELED){
Toast.makeText(applicationContext, "Edition cancelled", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(applicationContext, "Error while editing", Toast.LENGTH_SHORT).show()
}
}
}
companion object {
private const val DEFAULT_BUFFER_SIZE = 2048
class PostCreationAdapter(private val posts: ArrayList<String>): RecyclerView.Adapter<PostCreationAdapter.ViewHolder>() {
private var context: Context? = null
var listener: PostCreationListener? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
context = parent.context
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.image_album_creation, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
Log.d("test", "binded")
holder.bind()
}
override fun getItemCount(): Int = posts.size
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind() {
val image = Uri.parse(posts[adapterPosition])
// load image
Glide.with(context!!)
.load(image)
.centerCrop()
.into(itemView.galleryImage)
// adding click or tap handler for the image layout
itemView.galleryImage.setOnClickListener {
Log.d("test", "clicked")
listener?.onClick(adapterPosition)
}
}
}
}
}

View File

@ -2,7 +2,6 @@ package com.h.pixeldroid.fragments
import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration
@ -30,6 +29,7 @@ import androidx.lifecycle.lifecycleScope
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.h.pixeldroid.PhotoEditActivity
import com.h.pixeldroid.PostCreationActivity
import com.h.pixeldroid.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -282,6 +282,7 @@ class CameraFragment : Fragment() {
action = Intent.ACTION_GET_CONTENT
addCategory(Intent.CATEGORY_OPENABLE)
putExtra(Intent.EXTRA_LOCAL_ONLY, true)
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
startActivityForResult(
Intent.createChooser(this, "Select a Picture"), PICK_IMAGE_REQUEST
)
@ -343,7 +344,9 @@ class CameraFragment : Fragment() {
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
val savedUri = output.savedUri ?: Uri.fromFile(photoFile)
startPostCreation(savedUri)
val uri: ArrayList<String> = ArrayList()
uri.add(savedUri.toString())
startAlbumCreation(uri)
}
})
@ -359,19 +362,30 @@ class CameraFragment : Fragment() {
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK && data != null
&& (requestCode == PICK_IMAGE_REQUEST || requestCode == CAPTURE_IMAGE_REQUEST)
&& data.data != null) {
&& (requestCode == PICK_IMAGE_REQUEST || requestCode == CAPTURE_IMAGE_REQUEST)) {
startPostCreation(data.data!!)
val images: ArrayList<String> = ArrayList()
if (data.clipData != null) {
val count = data.clipData!!.itemCount
for (i in 0 until count) {
val imageUri: String = data.clipData!!.getItemAt(i).uri.toString()
images.add(imageUri)
}
startAlbumCreation(images)
} else if (data.data != null) {
images.add(data.data!!.toString())
startAlbumCreation(images)
}
}
}
private fun startPostCreation(uri: Uri) {
startActivity(
Intent(activity, PhotoEditActivity::class.java)
.putExtra("picture_uri", uri)
)
private fun startAlbumCreation(uris: ArrayList<String>) {
startActivity(
Intent(activity, PostCreationActivity::class.java)
.putExtra("pictures_uri", uris)
)
}
companion object {

View File

@ -0,0 +1,5 @@
package com.h.pixeldroid.interfaces
interface PostCreationListener {
fun onClick(position: Int)
}

View File

@ -0,0 +1,59 @@
package com.h.pixeldroid.utils
import io.reactivex.Observable
import io.reactivex.subjects.PublishSubject
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody
import okio.BufferedSink
import java.io.*
class ProgressRequestBody(private val mFile: InputStream, private val length: Long) : RequestBody() {
private val getProgressSubject: PublishSubject<Float> = PublishSubject.create()
val progressSubject: Observable<Float>
get() {
return getProgressSubject
}
override fun contentType(): MediaType? {
return "image/png".toMediaTypeOrNull()
}
@Throws(IOException::class)
override fun contentLength(): Long {
return length
}
@Throws(IOException::class)
override fun writeTo(sink: BufferedSink) {
val fileLength = contentLength()
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
var uploaded: Long = 0
mFile.use {
var read: Int
var lastProgressPercentUpdate = 0.0f
read = it.read(buffer)
while (read != -1) {
uploaded += read.toLong()
sink.write(buffer, 0, read)
read = it.read(buffer)
val progress = (uploaded.toFloat() / fileLength.toFloat()) * 100f
//prevent publishing too many updates, which slows upload, by checking if the upload has progressed by at least 1 percent
if (progress - lastProgressPercentUpdate > 1 || progress == 100f) {
// publish progress
getProgressSubject.onNext(progress)
lastProgressPercentUpdate = progress
}
}
}
}
companion object {
private const val DEFAULT_BUFFER_SIZE = 2048
}
}

View File

@ -0,0 +1,15 @@
package com.h.pixeldroid.utils
import android.widget.RelativeLayout
import android.os.Build
import android.annotation.TargetApi
import android.content.Context
import android.util.AttributeSet
internal class SquareLayout(context: Context, attrs: AttributeSet) :
RelativeLayout(context, attrs) {
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, widthMeasureSpec)
}
}

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
</vector>

View File

@ -42,17 +42,17 @@
</androidx.constraintlayout.widget.ConstraintLayout>
<ImageView
android:id="@+id/post_creation_picture_frame"
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/image_grid"
android:layout_width="0dp"
android:layout_height="0dp"
android:contentDescription="@string/posting_image_accessibility_hint"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" />
>
</androidx.recyclerview.widget.RecyclerView>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/buttonConstraints"
@ -88,10 +88,6 @@
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<ProgressBar
android:id="@+id/uploadProgressBar"
style="?android:attr/progressBarStyleHorizontal"

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<com.h.pixeldroid.utils.SquareLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foreground="?selectableItemBackground"
android:clickable="true"
android:focusable="true">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/galleryImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"/>
<ImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:src="@drawable/ic_baseline_edit_24"/>
</RelativeLayout>
</com.h.pixeldroid.utils.SquareLayout>