From 12638e2e17ed84c6cc12cb836cea53d87703cd24 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Tue, 26 Mar 2019 00:17:31 +0900 Subject: expandable tweets --- .../net/lacolaco/smileessence/view/Partials.kt | 174 +++++++++++++++++++-- .../smileessence/view/page/ComposePageFragment.kt | 2 +- .../smileessence/view/page/ListPageFragment.kt | 4 +- .../smileessence/view/page/PageFragment.kt | 2 +- .../smileessence/view/page/SearchPageFragment.kt | 4 +- .../smileessence/view/page/TweetPageFragment.kt | 154 +----------------- .../smileessence/view/page/TweetsPageFragment.kt | 4 +- .../smileessence/view/page/UserPageFragment.kt | 4 +- 8 files changed, 180 insertions(+), 168 deletions(-) (limited to 'app/src/main/java/net/lacolaco/smileessence') diff --git a/app/src/main/java/net/lacolaco/smileessence/view/Partials.kt b/app/src/main/java/net/lacolaco/smileessence/view/Partials.kt index fd73b447..b2c6a6cf 100644 --- a/app/src/main/java/net/lacolaco/smileessence/view/Partials.kt +++ b/app/src/main/java/net/lacolaco/smileessence/view/Partials.kt @@ -3,30 +3,37 @@ package net.lacolaco.smileessence.view import android.text.Html import android.view.LayoutInflater import android.view.View -import androidx.fragment.app.FragmentActivity import com.bumptech.glide.Glide import kotlinx.android.synthetic.main.item_status_base.view.* +import kotlinx.android.synthetic.main.list_item_tweet_rich.view.* import net.lacolaco.smileessence.R import net.lacolaco.smileessence.World import net.lacolaco.smileessence.activity.MainActivity import net.lacolaco.smileessence.entity.Tweet -import net.lacolaco.smileessence.twitter.TwitterTaskException -import net.lacolaco.smileessence.twitter.getTweetAsync -import net.lacolaco.smileessence.util.launchUi -import net.lacolaco.smileessence.util.toCompactString +import net.lacolaco.smileessence.twitter.* +import net.lacolaco.smileessence.util.* object Partials { - fun getTweetView(tweet: Tweet, world: World, activity: FragmentActivity, view: View, - maxRecursion: Int = 2): View { + fun getTweetView(tweet: Tweet, world: World, activity: MainActivity, view: View, + maxRecursion: Int = 2, defaultExpanded: Boolean = false): View { + var isExpanded = defaultExpanded + val updateExpansionVisibility = { + view.layout_status_expansion?.visibility = if (isExpanded) View.VISIBLE else View.GONE + } view.setOnClickListener { - (activity as MainActivity).addEphemeralTweetPage(tweet.id) + isExpanded = !isExpanded + updateExpansionVisibility() + } + view.setOnLongClickListener { + activity.addEphemeralTweetPage(tweet.id) + true } // Display source val user = tweet.originalTweet.user Glide.with(view).load(user.profileImageUrl).into(view.imageview_status_icon) view.imageview_status_icon.setOnClickListener { - (activity as MainActivity).addEphemeralUserPage(user.id) + activity.addEphemeralUserPage(user.id) } view.source_user_protected.visibility = if (user.isProtected) View.VISIBLE else View.INVISIBLE @@ -103,6 +110,155 @@ object Partials { } else layout.visibility = View.GONE + // Expansions + if (view.layout_status_expansion != null) { + view.button_status_detail_favorite.isActivated = world.id in tweet.favoriters + view.button_status_detail_retweet.isActivated = world.id in tweet.retweets + updateViewButtons(tweet, world, activity, view) + setupViewMenu(tweet, world, activity, view) + updateExpansionVisibility() + } + return view } + + private fun updateViewButtons(tweet: Tweet, world: World, activity: MainActivity, view: View) { + view.button_status_detail_reply.setOnClickListener { + val originalTweet = tweet.originalTweet + + val builder = StringBuilder() + builder.append("@${originalTweet.user.screenName} ") + + originalTweet.entities.mentions + .filter { it != world.user.screenName } + .forEach { builder.append("@$it ") } + val text = builder.toString() + val selStart = originalTweet.user.screenName.length + 2 // "@" and " " + + activity.openPostPageAndReplyTo(originalTweet, text) + } + view.button_status_detail_retweet.setOnClickListener { + activity.confirm(R.string.dialog_confirm_commands) { + if (world.id in tweet.retweets) { + launchUi { + try { + world.deleteTweetAsync(tweet.retweets[world.id]!!).await() + world.notify(R.string.notice_status_delete_succeeded) + updateViewButtons(tweet, world, activity, view) + } catch (e: TwitterTaskException) { + world.notifyError(R.string.notice_status_delete_failed) + } + } + } else { + launchUi { + try { + world.retweetAsync(tweet.id).await() + world.notify(R.string.notice_retweet_succeeded) + updateViewButtons(tweet, world, activity, view) + } catch (e: TwitterTaskException) { + world.notifyError(R.string.notice_retweet_failed) + } + } + } + } + } + view.button_status_detail_favorite.setOnClickListener { + val favoriting = world.id !in tweet.favoriters + + launchUi { + try { + if (favoriting) { + world.favoriteAsync(tweet.id).await() + world.notify(R.string.notice_favorite_succeeded) + } else { + world.unfavoriteAsync(tweet.id).await() + world.notify(R.string.notice_unfavorite_succeeded) + } + } catch (e: TwitterTaskException) { + if (favoriting) + world.notifyError(R.string.notice_favorite_failed) + else + world.notifyError(R.string.notice_unfavorite_failed) + } + } + } + if (tweet.originalTweet.user === world.user) + view.button_status_detail_delete.visibility = View.VISIBLE + else + view.button_status_detail_delete.visibility = View.GONE + view.button_status_detail_delete.setOnClickListener { + activity.confirm(R.string.dialog_confirm_commands) { + launchBg { + try { + world.deleteTweetAsync(tweet.originalTweet.id).await() + world.notify(R.string.notice_status_delete_succeeded) + } catch (e: TwitterTaskException) { + world.notifyError(R.string.notice_status_delete_failed) + } + } + activity.closeCurrentEphemeralPage() + } + } + view.button_status_detail_menu.setOnClickListener { + val popup = PopupMenu(activity, view.button_status_detail_menu) + popup.add(R.string.command_status_add_to_reply) { + val text = String.format("@%s ", tweet.originalTweet.user.screenName) + activity.openPostPageAndAppendText(text) + world.notify(R.string.notice_add_to_reply) + } + popup.add(R.string.command_status_open_in_browser) { + activity.browse("https://twitter.com/${tweet.originalTweet.user.screenName}/status/${tweet.originalTweet.id}") + } + popup.add(R.string.command_status_copy_text_to_clipboard) { + SystemServiceHelper.copyToClipboard(activity, "tweet text", tweet.originalTweet.text) + world.notify(R.string.notice_copy_clipboard) + } + popup.add(R.string.command_status_copy_url_to_clipboard) { + SystemServiceHelper.copyToClipboard(activity, "tweet url", "https://twitter.com/${tweet.originalTweet.user.screenName}/status/${tweet.originalTweet.id}") + world.notify(R.string.notice_copy_clipboard) + } + popup.show() + } + } + + private fun setupViewMenu(tweet: Tweet, world: World, activity: MainActivity, view: View) { + view.embedded_menu_items.removeAllViews() + val addUserItem = { screenName: String, title: String -> + view.embedded_menu_items.add("@$screenName", title) { + val ref = ref(activity) + launchUi { + try { + val user = world.getUserAsync(screenName).await() + ref.get().addEphemeralUserPage(user.id) + } catch (e: TwitterTaskException) { + world.notifyError(R.string.notice_error_show_user) + } + } + } + } + if (tweet.user !== tweet.originalTweet.user) + addUserItem(tweet.user.screenName, "RT by") + for (screenName in tweet.entities.mentions.distinct().minus(tweet.user.screenName)) + addUserItem(screenName, "Mention") + for (hashtag in tweet.entities.hashtags) + view.embedded_menu_items.add("#$hashtag", "Hashtag") { + // XXX + activity.openPostPageAndAppendText(" #$hashtag") + } + for (url in tweet.entities.urlsExpanded) + view.embedded_menu_items.add(url, "URL") { + activity.browse(url) + } + for (url in tweet.entities.mediaUrls) + view.embedded_menu_items.add(url, "Media") { + activity.browse(url) + } + if (view.embedded_menu_items.childCount != 0) { + view.detail_dialog_divider_bottom.visibility = View.VISIBLE + view.embedded_menu_items.visibility = View.VISIBLE + } else { + view.detail_dialog_divider_bottom.visibility = View.GONE + view.embedded_menu_items.visibility = View.GONE + } + } } 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 417e6174..81ffda73 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 @@ -88,7 +88,7 @@ class ComposePageFragment : PageFragment(), TextWatche 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!!, + val header = Partials.getTweetView(postState.inReplyTo!!, world, mainActivity, layout_post_reply_status) header.isClickable = false } else { diff --git a/app/src/main/java/net/lacolaco/smileessence/view/page/ListPageFragment.kt b/app/src/main/java/net/lacolaco/smileessence/view/page/ListPageFragment.kt index 9b7b1708..6c282c5a 100644 --- a/app/src/main/java/net/lacolaco/smileessence/view/page/ListPageFragment.kt +++ b/app/src/main/java/net/lacolaco/smileessence/view/page/ListPageFragment.kt @@ -67,12 +67,12 @@ class ListPageFragment : RefreshableTimelinePageFragment>, onFinish: () -> Unit) = launchUi { diff --git a/app/src/main/java/net/lacolaco/smileessence/view/page/PageFragment.kt b/app/src/main/java/net/lacolaco/smileessence/view/page/PageFragment.kt index 49ffad87..37bbfd4a 100644 --- a/app/src/main/java/net/lacolaco/smileessence/view/page/PageFragment.kt +++ b/app/src/main/java/net/lacolaco/smileessence/view/page/PageFragment.kt @@ -20,7 +20,7 @@ abstract class PageFragment : Fragment() { world.getPageById(uid) as T } - val mainActivity + protected val mainActivity get() = super.getActivity() as MainActivity // Invoked when user click the refresh button. diff --git a/app/src/main/java/net/lacolaco/smileessence/view/page/SearchPageFragment.kt b/app/src/main/java/net/lacolaco/smileessence/view/page/SearchPageFragment.kt index 32b9a2aa..5a61db51 100644 --- a/app/src/main/java/net/lacolaco/smileessence/view/page/SearchPageFragment.kt +++ b/app/src/main/java/net/lacolaco/smileessence/view/page/SearchPageFragment.kt @@ -125,12 +125,12 @@ class SearchPageFragment : RefreshableTimelinePageFragment() { private lateinit var tweet: Tweet @@ -36,27 +33,22 @@ class TweetPageFragment : PageFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val statusHeader = Partials.getTweetView(tweet, world, activity!!, view.detail_current_status) + val statusHeader = Partials.getTweetView(tweet, world, mainActivity, view.detail_current_status, + defaultExpanded = true) statusHeader.isClickable = false - // XXX - view.button_status_detail_favorite.isActivated = world.id in tweet.favoriters - view.button_status_detail_retweet.isActivated = world.id in tweet.retweets - updateViewButtons(view) - setupViewMenu(view) - if (tweet.inReplyToStatusId != null) { view.tweet_in_reply_to.visibility = View.VISIBLE view.detail_dialog_divider_top.visibility = View.VISIBLE val cached = Tweet.cached(tweet.inReplyToStatusId!!) if (cached != null) - Partials.getTweetView(cached, world, activity!!, view.tweet_in_reply_to) + Partials.getTweetView(cached, world, mainActivity, view.tweet_in_reply_to) else { - Partials.getTweetView(Tweet.placeHolder, world, activity!!, view.tweet_in_reply_to) + Partials.getTweetView(Tweet.placeHolder, world, mainActivity, view.tweet_in_reply_to) launchUi { try { val tweet = world.getTweetAsync(tweet.inReplyToStatusId!!, false).await() - Partials.getTweetView(tweet, world, activity!!, view.tweet_in_reply_to) + Partials.getTweetView(tweet, world, mainActivity, view.tweet_in_reply_to) } catch (e: TwitterTaskException) { } } @@ -66,140 +58,4 @@ class TweetPageFragment : PageFragment() { view.detail_dialog_divider_top.visibility = View.GONE } } - - private fun updateViewButtons(view: View) { - view.button_status_detail_reply.setOnClickListener { - val originalTweet = tweet.originalTweet - - val builder = StringBuilder() - builder.append("@${originalTweet.user.screenName} ") - - originalTweet.entities.mentions - .filter { it != world.user.screenName } - .forEach { builder.append("@$it ") } - val text = builder.toString() - val selStart = originalTweet.user.screenName.length + 2 // "@" and " " - - (activity as MainActivity).openPostPageAndReplyTo(originalTweet, text) - } - view.button_status_detail_retweet.setOnClickListener { - confirm(R.string.dialog_confirm_commands) { - if (world.id in tweet.retweets) { - launchUi { - try { - world.deleteTweetAsync(tweet.retweets[world.id]!!).await() - world.notify(R.string.notice_status_delete_succeeded) - updateViewButtons(view) - } catch (e: TwitterTaskException) { - world.notifyError(R.string.notice_status_delete_failed) - } - } - } else { - launchUi { - try { - world.retweetAsync(tweet.id).await() - world.notify(R.string.notice_retweet_succeeded) - updateViewButtons(view) - } catch (e: TwitterTaskException) { - world.notifyError(R.string.notice_retweet_failed) - } - } - } - } - } - view.button_status_detail_favorite.setOnClickListener { - val favoriting = world.id !in tweet.favoriters - - launchUi { - try { - if (favoriting) { - world.favoriteAsync(tweet.id).await() - world.notify(R.string.notice_favorite_succeeded) - } else { - world.unfavoriteAsync(tweet.id).await() - world.notify(R.string.notice_unfavorite_succeeded) - } - } catch (e: TwitterTaskException) { - if (favoriting) - world.notifyError(R.string.notice_favorite_failed) - else - world.notifyError(R.string.notice_unfavorite_failed) - } - } - } - if (tweet.originalTweet.user === world.user) - view.button_status_detail_delete.visibility = View.VISIBLE - else - view.button_status_detail_delete.visibility = View.GONE - view.button_status_detail_delete.setOnClickListener { - confirm(R.string.dialog_confirm_commands) { - launchBg { - try { - world.deleteTweetAsync(tweet.originalTweet.id).await() - world.notify(R.string.notice_status_delete_succeeded) - } catch (e: TwitterTaskException) { - world.notifyError(R.string.notice_status_delete_failed) - } - } - (activity as MainActivity).closeCurrentEphemeralPage() - } - } - view.button_status_detail_menu.setOnClickListener { - val popup = PopupMenu(activity!!, view.button_status_detail_menu) - popup.add(R.string.command_status_add_to_reply) { - val text = String.format("@%s ", tweet.originalTweet.user.screenName) - (activity as MainActivity).openPostPageAndAppendText(text) - world.notify(R.string.notice_add_to_reply) - } - popup.add(R.string.command_status_open_in_browser) { - browse("https://twitter.com/${tweet.originalTweet.user.screenName}/status/${tweet.originalTweet.id}") - } - popup.add(R.string.command_status_copy_text_to_clipboard) { - SystemServiceHelper.copyToClipboard(activity!!, "tweet text", tweet.originalTweet.text) - world.notify(R.string.notice_copy_clipboard) - } - popup.add(R.string.command_status_copy_url_to_clipboard) { - SystemServiceHelper.copyToClipboard(activity!!, "tweet url", "https://twitter.com/${tweet.originalTweet.user.screenName}/status/${tweet.originalTweet.id}") - world.notify(R.string.notice_copy_clipboard) - } - popup.show() - } - } - - private fun setupViewMenu(view: View) { - val addUserItem = { screenName: String, title: String -> - view.embedded_menu_items.add("@$screenName", title) { - val ref = ref(activity as MainActivity) - launchUi { - try { - val user = world.getUserAsync(screenName).await() - (activity as MainActivity).addEphemeralUserPage(user.id) - } catch (e: TwitterTaskException) { - world.notifyError(R.string.notice_error_show_user) - } - } - } - } - if (tweet.user !== tweet.originalTweet.user) - addUserItem(tweet.user.screenName, "RT by") - for (screenName in tweet.entities.mentions.distinct().minus(tweet.user.screenName)) - addUserItem(screenName, "Mention") - for (hashtag in tweet.entities.hashtags) - view.embedded_menu_items.add("#$hashtag", "Hashtag") { - // XXX - (activity as MainActivity).openPostPageAndAppendText(" #$hashtag") - } - for (url in tweet.entities.urlsExpanded) - view.embedded_menu_items.add(url, "URL") { - activity!!.browse(url) - } - for (url in tweet.entities.mediaUrls) - view.embedded_menu_items.add(url, "Media") { - activity!!.browse(url) - } - if (view.embedded_menu_items.childCount == 0) { - view.detail_dialog_divider_bottom.visibility = View.GONE - view.embedded_menu_items.visibility = View.GONE - } - } } diff --git a/app/src/main/java/net/lacolaco/smileessence/view/page/TweetsPageFragment.kt b/app/src/main/java/net/lacolaco/smileessence/view/page/TweetsPageFragment.kt index f2ad1998..f9a58ca0 100644 --- a/app/src/main/java/net/lacolaco/smileessence/view/page/TweetsPageFragment.kt +++ b/app/src/main/java/net/lacolaco/smileessence/view/page/TweetsPageFragment.kt @@ -26,11 +26,11 @@ class TweetsPageFragment : TimelinePageFragment( } override fun onBindItemView(item: Tweet, itemView: View) { - Partials.getTweetView(item, world, activity!!, itemView) + Partials.getTweetView(item, world, mainActivity, itemView) } override fun onCreateItemView(parent: ViewGroup): View { val layoutInflater = LayoutInflater.from(parent.context) - return layoutInflater.inflate(R.layout.list_item_tweet, parent, false) + return layoutInflater.inflate(R.layout.list_item_tweet_rich, parent, false) } } diff --git a/app/src/main/java/net/lacolaco/smileessence/view/page/UserPageFragment.kt b/app/src/main/java/net/lacolaco/smileessence/view/page/UserPageFragment.kt index f417aeb8..703ca9b3 100644 --- a/app/src/main/java/net/lacolaco/smileessence/view/page/UserPageFragment.kt +++ b/app/src/main/java/net/lacolaco/smileessence/view/page/UserPageFragment.kt @@ -221,11 +221,11 @@ class UserPageFragment : RefreshableTimelinePageFragment