diff options
author | Kazuki Yamaguchi <k@rhe.jp> | 2017-10-28 23:41:43 +0900 |
---|---|---|
committer | Kazuki Yamaguchi <k@rhe.jp> | 2017-10-28 23:41:43 +0900 |
commit | bc38b69503dd541c7f973c768497c1e9f5d39749 (patch) | |
tree | 90e36b21f78e769aecb37d2bd9e87d12e9434166 | |
parent | 0e7406507d1873ca57ab1ccf0ab45aedf32b5c2e (diff) | |
download | SmileEssence-bc38b69503dd541c7f973c768497c1e9f5d39749.tar.gz |
multiple image upload2017-10-28
5 files changed, 114 insertions, 80 deletions
diff --git a/app/src/main/java/net/lacolaco/smileessence/twitter/Tweets.kt b/app/src/main/java/net/lacolaco/smileessence/twitter/Tweets.kt index c37b1b25..236289af 100644 --- a/app/src/main/java/net/lacolaco/smileessence/twitter/Tweets.kt +++ b/app/src/main/java/net/lacolaco/smileessence/twitter/Tweets.kt @@ -6,7 +6,7 @@ import net.lacolaco.smileessence.World import net.lacolaco.smileessence.entity.Tweet import net.lacolaco.smileessence.util.bg import twitter4j.StatusUpdate -import java.io.File +import java.io.InputStream fun World.getTweetAsync(id: Long, fetchAlways: Boolean = true) = async(CommonPool) { if (!fetchAlways) { @@ -24,11 +24,14 @@ fun World.deleteTweetAsync(id: Long) = async(CommonPool) { }, id) } -fun World.createTweetAsync(text: String, inReplyTo: Long?, mediaPath: String?) = bg { +fun World.createTweetAsync(text: String, inReplyTo: Long?, mediaIds: LongArray) = bg { val su = StatusUpdate(text) if (inReplyTo != null) su.inReplyToStatusId = inReplyTo - if (mediaPath != null) - su.setMedia(File(mediaPath)) + su.setMediaIds(*mediaIds) Tweet.fromTwitter(TwitterTaskException.wrap { twitter.tweets().updateStatus(su) }, id) } + +fun World.uploadMedia(stream: InputStream) = bg { + TwitterTaskException.wrap { twitter.tweets().uploadMedia("image.blob", stream) }.mediaId +} diff --git a/app/src/main/java/net/lacolaco/smileessence/util/IntentHelper.kt b/app/src/main/java/net/lacolaco/smileessence/util/IntentHelper.kt index 5614ec65..53045d1f 100644 --- a/app/src/main/java/net/lacolaco/smileessence/util/IntentHelper.kt +++ b/app/src/main/java/net/lacolaco/smileessence/util/IntentHelper.kt @@ -6,14 +6,18 @@ import android.content.Context import android.content.Intent import android.net.Uri -fun Context.browse(url: String) { +fun Context.browse(uri: Uri, type: String? = null) { try { val intent = Intent(Intent.ACTION_VIEW) - intent.data = Uri.parse(url) + intent.setDataAndType(uri, type) startActivity(intent) } catch (e: ActivityNotFoundException) { e.printStackTrace() } } -fun Fragment.browse(url: String) = activity.browse(url) +fun Context.browse(uri: String, type: String? = null) = browse(Uri.parse(uri), type) + +fun Fragment.browse(uri: Uri, type: String? = null) = activity.browse(uri, type) + +fun Fragment.browse(uri: String, type: String? = null) = activity.browse(uri, type) diff --git a/app/src/main/java/net/lacolaco/smileessence/view/page/ComposePageFragment.kt b/app/src/main/java/net/lacolaco/smileessence/view/page/ComposePageFragment.kt index 3fbaf027..cff5acd0 100644 --- a/app/src/main/java/net/lacolaco/smileessence/view/page/ComposePageFragment.kt +++ b/app/src/main/java/net/lacolaco/smileessence/view/page/ComposePageFragment.kt @@ -8,6 +8,8 @@ import android.os.Bundle import android.os.Parcelable import android.provider.MediaStore import android.support.v4.content.ContextCompat +import android.support.v7.widget.LinearLayoutManager +import android.support.v7.widget.RecyclerView import android.text.Editable import android.text.Spannable import android.text.TextWatcher @@ -17,6 +19,7 @@ import android.widget.TextView import com.bumptech.glide.Glide import com.twitter.Validator import kotlinx.android.parcel.Parcelize +import kotlinx.android.synthetic.main.item_compose_media.view.* import kotlinx.android.synthetic.main.page_fragment_compose.* import net.lacolaco.smileessence.Logger import net.lacolaco.smileessence.R @@ -25,26 +28,26 @@ import net.lacolaco.smileessence.entity.Tweet import net.lacolaco.smileessence.entity.User import net.lacolaco.smileessence.twitter.TwitterTaskException import net.lacolaco.smileessence.twitter.createTweetAsync +import net.lacolaco.smileessence.twitter.uploadMedia import net.lacolaco.smileessence.util.SystemServiceHelper +import net.lacolaco.smileessence.util.browse import net.lacolaco.smileessence.util.launchUi import net.lacolaco.smileessence.view.Partials -import java.io.File class ComposePageFragment : PageFragment<PageInfo.ComposePageInfo>(), TextWatcher { private lateinit var postState: PostState + private val mediaAdapter by lazy { MediaAdapter() } fun setMediaFile(uri: Uri) { - try { - activity.contentResolver.query(uri, null, null, null, null).use { c -> - c.moveToFirst() - val path = c.getString(c.getColumnIndex(MediaStore.MediaColumns.DATA)) - postState.mediaFilePath = path - onPostStateChange() - world.notify(R.string.notice_select_image_succeeded) + launchUi { + val mediaId = try { + activity.contentResolver.openInputStream(uri).use { world.uploadMedia(it).await() } + } catch (e: TwitterTaskException) { + world.notifyError("Failed to upload media (${e.message}") + return@launchUi } - } catch (e: Exception) { - e.printStackTrace() - world.notifyError(R.string.notice_select_image_failed) + postState.media.add(PostState.Medium(uri, mediaId)) + mediaAdapter.notifyItemInserted(postState.media.size - 1) } } @@ -95,12 +98,6 @@ class ComposePageFragment : PageFragment<PageInfo.ComposePageInfo>(), TextWatche layout_post_reply_status.visibility = View.GONE button_post_reply_delete.visibility = View.GONE } - if (postState.mediaFilePath == null) - post_media_parent.visibility = View.GONE - else { - post_media_parent.visibility = View.VISIBLE - Glide.with(image_post_media).load(File(postState.mediaFilePath!!)).into(image_post_media) - } } override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} @@ -115,7 +112,7 @@ class ComposePageFragment : PageFragment<PageInfo.ComposePageInfo>(), TextWatche private fun updateTextCount(s: CharSequence) { val validator = Validator() var length = validator.getTweetLength(s.toString()) - if (postState.mediaFilePath != null) + if (postState.media.isNotEmpty()) length += validator.shortUrlLength post_text_count.text = length.toString() @@ -123,7 +120,9 @@ class ComposePageFragment : PageFragment<PageInfo.ComposePageInfo>(), TextWatche if (length == 0 || length >= 140) { post_text_count.setTextColor(ContextCompat.getColor(activity, R.color.red)) } else { - post_text_count.setTextAppearance(activity, android.R.style.TextAppearance_Widget_TextView) + @Suppress("DEPRECATION") + post_text_count.setTextAppearance(activity, + android.R.style.TextAppearance_Widget_TextView) } } @@ -160,8 +159,11 @@ class ComposePageFragment : PageFragment<PageInfo.ComposePageInfo>(), TextWatche SystemServiceHelper.hideIM(activity, post_edit_text) } post_edit_text.movementMethod = object : ArrowKeyMovementMethod() { + override fun left(widget: TextView, buffer: Spannable): Boolean { + return widget.selectionStart == 0 || super.left(widget, buffer) + } + override fun right(widget: TextView, buffer: Spannable): Boolean { - // Don't back to Home return widget.selectionEnd == widget.length() || super.right(widget, buffer) } } @@ -169,10 +171,11 @@ class ComposePageFragment : PageFragment<PageInfo.ComposePageInfo>(), TextWatche SystemServiceHelper.hideIM(activity, post_edit_text) launchUi { try { - world.createTweetAsync(postState.text, postState.inReplyTo!!.id, - postState.mediaFilePath).await() + world.createTweetAsync(postState.text, postState.inReplyTo?.id, + postState.media.map { it.id }.toLongArray()).await() world.notify(R.string.notice_tweet_succeeded) postState.clear() + mediaAdapter.notifyDataSetChanged() onPostStateChange() } catch (e: TwitterTaskException) { world.notifyError(R.string.notice_tweet_failed, e) @@ -186,38 +189,28 @@ class ComposePageFragment : PageFragment<PageInfo.ComposePageInfo>(), TextWatche } button_post_delete.setOnClickListener { postState.clear() + mediaAdapter.notifyDataSetChanged() onPostStateChange() } - button_post_media.setOnClickListener { - SystemServiceHelper.hideIM(activity, post_edit_text) - - val intent = Intent(Intent.ACTION_PICK) + attach_media.setOnClickListener { + val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI) intent.type = "image/*" - startActivityForResult(intent, REQUEST_GET_PICTURE_FROM_GALLERY) - } - image_post_media.setOnClickListener { - val intent = Intent(Intent.ACTION_VIEW) - intent.addCategory(Intent.CATEGORY_DEFAULT) - intent.setDataAndType(Uri.fromFile(File(postState.mediaFilePath!!)), "image/*") - startActivity(intent) - } - button_post_media_delete.setOnClickListener { - SystemServiceHelper.hideIM(activity, post_edit_text) - post_media_parent.visibility = View.GONE - image_post_media.setImageBitmap(null) - postState.mediaFilePath = null - onPostStateChange() + startActivityForResult(Intent.createChooser(intent, "Choose an image"), + REQUEST_OPEN_DOCUMENT) } + val layoutManager = LinearLayoutManager(activity) + layoutManager.orientation = LinearLayoutManager.HORIZONTAL + media_recycler_view.layoutManager = layoutManager + media_recycler_view.adapter = mediaAdapter + onPostStateChange() } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { when (requestCode) { - REQUEST_GET_PICTURE_FROM_GALLERY -> { - if (resultCode != Activity.RESULT_OK) { - world.notifyError(R.string.notice_select_image_failed) + REQUEST_OPEN_DOCUMENT -> { + if (resultCode != Activity.RESULT_OK) return - } setMediaFile(data!!.data) } else -> super.onActivityResult(requestCode, resultCode, data) @@ -229,7 +222,7 @@ class ComposePageFragment : PageFragment<PageInfo.ComposePageInfo>(), TextWatche private class PostState( var text: String = "", private var inReplyToId: Long = -1, - var mediaFilePath: String? = null, + var media: MutableList<Medium> = mutableListOf(), var selectionStart: Int = 0, var selectionEnd: Int = 0 ) : Parcelable { @@ -242,14 +235,44 @@ class ComposePageFragment : PageFragment<PageInfo.ComposePageInfo>(), TextWatche fun clear() { text = "" inReplyTo = null - mediaFilePath = null selectionStart = 0 selectionEnd = 0 + media.clear() } + + @Parcelize + data class Medium(val uri: Uri, val id: Long) : Parcelable } companion object { - private val REQUEST_GET_PICTURE_FROM_GALLERY = 11 + private val REQUEST_OPEN_DOCUMENT = 10 val KEY_POST_STATE = "POST_STATE" } + + private inner class MediaAdapter : RecyclerView.Adapter<MediaAdapter.ViewHolder>() { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + return ViewHolder(layoutInflater.inflate(R.layout.item_compose_media, parent, false)) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val medium = postState.media[position] + val itemView = holder.itemView + Glide.with(itemView).load(medium.uri).into(itemView.image) + itemView.image.setOnClickListener { + browse(medium.uri) + } + itemView.delete.setOnClickListener { + val index = postState.media.indexOf(medium) + postState.media.removeAt(index) + notifyItemRemoved(index) + } + } + + override fun getItemCount(): Int { + return postState.media.size + } + + private inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) + } } diff --git a/app/src/main/res/layout/item_compose_media.xml b/app/src/main/res/layout/item_compose_media.xml new file mode 100644 index 00000000..78db789f --- /dev/null +++ b/app/src/main/res/layout/item_compose_media.xml @@ -0,0 +1,22 @@ +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="120dp" + android:layout_height="120dp" + android:layout_marginEnd="8dp" + android:layout_marginBottom="8dp"> + + <ImageView + android:id="@+id/image" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:contentDescription="@null" /> + + <ImageButton + android:id="@+id/delete" + android:layout_width="30dp" + android:layout_height="30dp" + android:layout_gravity="end|top" + android:background="?selectableItemBackgroundBorderless" + android:src="@drawable/ic_clear_black_24dp" + android:tint="?android:textColorSecondary" + android:contentDescription="@null" /> +</FrameLayout> diff --git a/app/src/main/res/layout/page_fragment_compose.xml b/app/src/main/res/layout/page_fragment_compose.xml index ab3d05f2..7bc214c0 100644 --- a/app/src/main/res/layout/page_fragment_compose.xml +++ b/app/src/main/res/layout/page_fragment_compose.xml @@ -41,7 +41,7 @@ android:inputType="text|textMultiLine" android:minHeight="60dp" android:textAppearance="@style/TextAppearance.AppCompat.Large" - app:layout_constraintBottom_toTopOf="@+id/post_media_parent" + app:layout_constraintBottom_toTopOf="@+id/media_recycler_view" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/layout_post_reply_status"> @@ -69,36 +69,18 @@ app:layout_constraintBottom_toBottomOf="@id/post_edit_text" app:layout_constraintEnd_toEndOf="@id/post_edit_text" /> - <LinearLayout - android:id="@+id/post_media_parent" - android:layout_width="match_parent" + <android.support.v7.widget.RecyclerView + android:id="@+id/media_recycler_view" + android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginBottom="8dp" android:layout_marginEnd="8dp" android:layout_marginStart="8dp" - app:layout_constraintBottom_toTopOf="@+id/button_post_media" + app:layout_constraintBottom_toTopOf="@+id/attach_media" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent"> - - <ImageView - android:id="@+id/image_post_media" - android:layout_width="100dp" - android:layout_height="100dp" - android:clickable="true" /> - - <ImageButton - android:id="@+id/button_post_media_delete" - android:layout_width="30dp" - android:layout_height="30dp" - android:layout_marginStart="10dp" - android:background="?selectableItemBackgroundBorderless" - android:scaleType="fitStart" - android:src="@drawable/ic_clear_black_24dp" - android:tint="?android:textColorSecondary" /> - </LinearLayout> + app:layout_constraintStart_toStartOf="parent" /> <ImageButton - android:id="@+id/button_post_media" + android:id="@+id/attach_media" android:layout_width="40dp" android:layout_height="40dp" android:layout_marginBottom="8dp" @@ -120,5 +102,5 @@ android:text="@string/post_button_tweet" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toEndOf="@+id/button_post_media" /> + app:layout_constraintStart_toEndOf="@+id/attach_media" /> </android.support.constraint.ConstraintLayout> |