aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKazuki Yamaguchi <k@rhe.jp>2017-10-28 23:41:43 +0900
committerKazuki Yamaguchi <k@rhe.jp>2017-10-28 23:41:43 +0900
commitbc38b69503dd541c7f973c768497c1e9f5d39749 (patch)
tree90e36b21f78e769aecb37d2bd9e87d12e9434166
parent0e7406507d1873ca57ab1ccf0ab45aedf32b5c2e (diff)
downloadSmileEssence-bc38b69503dd541c7f973c768497c1e9f5d39749.tar.gz
multiple image upload2017-10-28
-rw-r--r--app/src/main/java/net/lacolaco/smileessence/twitter/Tweets.kt11
-rw-r--r--app/src/main/java/net/lacolaco/smileessence/util/IntentHelper.kt10
-rw-r--r--app/src/main/java/net/lacolaco/smileessence/view/page/ComposePageFragment.kt117
-rw-r--r--app/src/main/res/layout/item_compose_media.xml22
-rw-r--r--app/src/main/res/layout/page_fragment_compose.xml34
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>