package net.lacolaco.smileessence.view.page import android.annotation.SuppressLint import android.app.Activity import android.content.Intent import android.net.Uri import android.os.Bundle import android.os.Parcelable import android.provider.MediaStore import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView import android.text.Editable import android.text.Spannable import android.text.TextWatcher import android.text.method.ArrowKeyMovementMethod import android.view.* import android.widget.TextView import com.bumptech.glide.Glide 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 import net.lacolaco.smileessence.data.PageInfo 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 class ComposePageFragment : PageFragment(), TextWatcher { private lateinit var postState: PostState private val mediaAdapter by lazy { MediaAdapter() } fun setMediaFile(uri: Uri) { 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 } postState.media.add(PostState.Medium(uri, mediaId)) mediaAdapter.notifyItemInserted(postState.media.size - 1) } } fun setInReplyTo(tweet: Tweet, prefix: String) { postState.inReplyTo = tweet postState.text = prefix + postState.text postState.selectionStart = prefix.length postState.selectionEnd = prefix.length onPostStateChange() } fun setInReplyTo(user: User) { val prefix = "@${user.screenName} " postState.text = prefix + postState.text postState.selectionStart += prefix.length postState.selectionEnd += prefix.length onPostStateChange() } fun setText(text: String) { postState.text = text onPostStateChange() } fun appendText(text: String) { postState.text += text onPostStateChange() } private fun onPostStateChange() { Logger.debug("onPostStateChange") post_edit_text.removeTextChangedListener(this) post_edit_text.setTextKeepState(postState.text) post_edit_text.addTextChangedListener(this) updateTextCount(post_edit_text.text) launchUi { post_edit_text.setSelection(postState.selectionStart, postState.selectionEnd) } if (postState.inReplyTo != null) { layout_post_reply_status.visibility = View.VISIBLE button_post_reply_delete.visibility = View.VISIBLE val header = Partials.getTweetView(postState.inReplyTo!!, world, activity, layout_post_reply_status) header.isClickable = false } else { layout_post_reply_status.visibility = View.GONE button_post_reply_delete.visibility = View.GONE } } override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { updateTextCount(s) postState.text = s.toString() } override fun afterTextChanged(s: Editable) {} private fun updateTextCount(s: CharSequence) { val length = s.length post_text_count.text = length.toString() button_post_tweet.isEnabled = length != 0 post_text_count.isEnabled = length == 0 || length >= 140 } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (savedInstanceState != null) postState = savedInstanceState.getParcelable(KEY_POST_STATE) else postState = PostState() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putParcelable(KEY_POST_STATE, postState) } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { super.onCreateOptionsMenu(menu, inflater) SystemServiceHelper.showIM(activity, post_edit_text) } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { return inflater.inflate(R.layout.page_fragment_compose, container, false) } override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) post_edit_text.addTextChangedListener(this) post_edit_text.setOnFocusChangeListener { _, hasFocus -> if (hasFocus) SystemServiceHelper.showIM(activity, post_edit_text) else 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 { return widget.selectionEnd == widget.length() || super.right(widget, buffer) } } button_post_tweet.setOnClickListener { SystemServiceHelper.hideIM(activity, post_edit_text) launchUi { try { 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) } } mainActivity.openHomePage() } button_post_reply_delete.setOnClickListener { postState.inReplyTo = null onPostStateChange() } button_post_delete.setOnClickListener { postState.clear() mediaAdapter.notifyDataSetChanged() onPostStateChange() } attach_media.setOnClickListener { val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI) intent.type = "image/*" 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_OPEN_DOCUMENT -> { if (resultCode != Activity.RESULT_OK) return setMediaFile(data!!.data) } else -> super.onActivityResult(requestCode, resultCode, data) } } @SuppressLint("ParcelCreator") @Parcelize private class PostState( var text: String = "", private var inReplyToId: Long = -1, var media: MutableList = mutableListOf(), var selectionStart: Int = 0, var selectionEnd: Int = 0 ) : Parcelable { var inReplyTo: Tweet? get() = if (inReplyToId != -1L) Tweet.fetch(inReplyToId) else null set(value) { inReplyToId = value?.id ?: -1 } fun clear() { text = "" inReplyTo = null selectionStart = 0 selectionEnd = 0 media.clear() } @Parcelize data class Medium(val uri: Uri, val id: Long) : Parcelable } companion object { private const val REQUEST_OPEN_DOCUMENT = 10 const val KEY_POST_STATE = "POST_STATE" } private inner class MediaAdapter : RecyclerView.Adapter() { 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) } }