v269。リアクションの表示と追加

This commit is contained in:
tateisu 2018-08-21 10:53:52 +09:00
parent 80e6242f58
commit 4d9c9068d7
9 changed files with 278 additions and 69 deletions

View File

@ -12,8 +12,8 @@ android {
minSdkVersion 21
targetSdkVersion 27
versionCode 268
versionName "2.6.8"
versionCode 269
versionName "2.6.9"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
// https://stackoverflow.com/questions/47791227/java-lang-illegalstateexception-dex-archives-setting-dex-extension-only-for

View File

@ -15,6 +15,9 @@ import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.*
import com.google.android.flexbox.AlignItems
import com.google.android.flexbox.FlexWrap
import com.google.android.flexbox.FlexboxLayout
import jp.juggler.subwaytooter.action.*
import java.util.ArrayList
@ -46,7 +49,6 @@ internal class ItemViewHolder(
companion object {
private val log = LogCategory("ItemViewHolder")
}
val viewRoot : View
@ -246,7 +248,7 @@ internal class ItemViewHolder(
bSimpleList : Boolean,
item : TimelineItem
) {
this.list_adapter = list_adapter
this.column = column
this.bSimpleList = bSimpleList
@ -406,8 +408,8 @@ internal class ItemViewHolder(
is TootList -> showList(item)
is TootMessageHolder -> showMessageHolder(item)
// TootTrendTag の後に TootTagを判定すること
// TootTrendTag の後に TootTagを判定すること
is TootTrendTag -> showTrendTag(item)
is TootTag -> showSearchTag(item)
@ -596,8 +598,13 @@ internal class ItemViewHolder(
this.status_showing = status
llStatus.visibility = View.VISIBLE
if( status.conversation_main) {
this.viewRoot.setBackgroundColor( (Styler.getAttributeColor(activity,R.attr.colorImageButtonAccent) and 0xffffff ) or 0x20000000)
if(status.conversation_main) {
this.viewRoot.setBackgroundColor(
(Styler.getAttributeColor(
activity,
R.attr.colorImageButtonAccent
) and 0xffffff) or 0x20000000
)
}
showStatusTime(activity, tvTime, who = status.account, status = status)
@ -726,6 +733,8 @@ internal class ItemViewHolder(
setMedia(ivMedia4, status, media_attachments, 3)
}
makeReactionsView(status.reactionCounts)
buttons_for_status?.bind(status, (item as? TootNotification))
val application = status.application
@ -770,12 +779,12 @@ internal class ItemViewHolder(
if(sb.isNotEmpty()) sb.append('\u200B')
sb.appendColorShadeIcon(activity, R.drawable.ic_shield, "admin")
}
if(status.account.isCat) {
if(sb.isNotEmpty()) sb.append('\u200B')
sb.appendColorShadeIcon(activity, R.drawable.ic_cat, "cat")
}
// botマーク
if(status.account.bot) {
if(sb.isNotEmpty()) sb.append('\u200B')
@ -805,11 +814,18 @@ internal class ItemViewHolder(
}
// visibility
val visIconAttrId = Styler.getVisibilityIconAttr(access_info.isMisskey,status.visibility)
val visIconAttrId =
Styler.getVisibilityIconAttr(access_info.isMisskey, status.visibility)
if(R.attr.ic_public != visIconAttrId) {
if(sb.isNotEmpty()) sb.append('\u200B')
val start = sb.length
sb.append(Styler.getVisibilityString(activity,access_info.isMisskey,status.visibility))
sb.append(
Styler.getVisibilityString(
activity,
access_info.isMisskey,
status.visibility
)
)
val end = sb.length
val iconResId = Styler.getAttributeResourceId(activity, visIconAttrId)
sb.setSpan(
@ -1009,8 +1025,19 @@ internal class ItemViewHolder(
ivThumbnail -> status_account?.let { whoRef ->
when {
access_info.isMisskey -> Action_User.profileLocal(activity, pos, access_info, whoRef.get())
access_info.isPseudo -> DlgContextMenu(activity, column, whoRef, null, notification).show()
access_info.isMisskey -> Action_User.profileLocal(
activity,
pos,
access_info,
whoRef.get()
)
access_info.isPseudo -> DlgContextMenu(
activity,
column,
whoRef,
null,
notification
).show()
else -> Action_User.profileLocal(activity, pos, access_info, whoRef.get())
}
}
@ -1194,16 +1221,16 @@ internal class ItemViewHolder(
btnSearchTag, llTrendTag -> {
val item = this.item
when(item) {
// is TootGap -> column.startGap(item)
//
// is TootDomainBlock -> {
// val domain = item.domain
// AlertDialog.Builder(activity)
// .setMessage(activity.getString(R.string.confirm_unblock_domain, domain))
// .setNegativeButton(R.string.cancel, null)
// .setPositiveButton(R.string.ok) { _, _ -> Action_Instance.blockDomain(activity, access_info, domain, false) }
// .show()
// }
// is TootGap -> column.startGap(item)
//
// is TootDomainBlock -> {
// val domain = item.domain
// AlertDialog.Builder(activity)
// .setMessage(activity.getString(R.string.confirm_unblock_domain, domain))
// .setNegativeButton(R.string.cancel, null)
// .setPositiveButton(R.string.ok) { _, _ -> Action_Instance.blockDomain(activity, access_info, domain, false) }
// .show()
// }
is TootTag -> {
// search_tag は#を含まない
@ -1246,7 +1273,7 @@ internal class ItemViewHolder(
is TootAttachment -> {
if(Pref.bpUseInternalMediaViewer(App1.pref)) {
// 内蔵メディアビューア
val serviceType = when(access_info.isMisskey){
val serviceType = when(access_info.isMisskey) {
true -> ServiceType.MISSKEY
else -> ServiceType.MASTODON
}
@ -1362,6 +1389,148 @@ internal class ItemViewHolder(
}
private fun makeReactionsView(reactionsCount : HashMap<String, Int>?) {
if( ! access_info.isMisskey) return
// reactionsCount?:return
// MisskeyReaction.values().find {
// val c = reactionsCount[it.shortcode]
// c != null && c > 0
// } ?: return
val density = activity.resources.displayMetrics.density
val compoundPadding = (density * 0.5f + 0.5f).toInt()
val endMargin = (density * 3f + 0.5f).toInt()
val paddingHorizontal = (density * 4f + 0.5f).toInt()
val btnHeight = (density * 40f + 0.5f).toInt()
val box = FlexboxLayout(activity)
val boxLp = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
box.layoutParams = boxLp
boxLp.topMargin = (0.5f + density * 3f).toInt()
box.flexWrap = FlexWrap.WRAP
box.alignItems = AlignItems.FLEX_START
// +ボタン
run{
val b = ImageButton(activity)
val blp = FlexboxLayout.LayoutParams(
btnHeight,
btnHeight
)
b.layoutParams = blp
blp.endMargin = endMargin
b.background = ContextCompat.getDrawable(
activity,
R.drawable.btn_bg_transparent
)
b.minimumWidth = (density * 40f + 0.5f).toInt()
b.contentDescription = activity.getString(R.string.reaction_add)
b.imageResource = Styler.getAttributeResourceId(activity,R.attr.ic_add)
b.padding= paddingHorizontal
b.setOnClickListener{ addReaction(status_showing,null) }
box.addView(b)
}
var lastButton : Button? = null
for(mr in MisskeyReaction.values()) {
val count = reactionsCount?.get(mr.shortcode)
if(count == null || count <= 0) continue
val b = Button(activity)
val blp = FlexboxLayout.LayoutParams(
FlexboxLayout.LayoutParams.WRAP_CONTENT,
btnHeight
)
b.layoutParams = blp
blp.endMargin = endMargin
b.background = ContextCompat.getDrawable(
activity,
R.drawable.btn_bg_transparent
)
b.minWidthCompat = (density * 40f + 0.5f).toInt()
b.text = count.toString()
b.compoundDrawablePadding = compoundPadding
b.padding= paddingHorizontal
b.tag = mr.shortcode
b.setOnClickListener{addReaction(status_showing,it.tag as? String) }
val d = ContextCompat.getDrawable(activity, mr.drawableId)
b.setCompoundDrawablesRelativeWithIntrinsicBounds(d, null, null, null)
box.addView(b)
lastButton = b
}
if( lastButton != null ){
val lp = lastButton.layoutParams
if( lp is ViewGroup.MarginLayoutParams){
lp.endMargin = 0
}
}
llExtra.addView(box)
}
private fun addReaction(status:TootStatus?,code : String?) {
status?:return
if( access_info.isPseudo || !access_info.isMisskey) return
if(code == null ){
val ad = ActionsDialog()
for( mr in MisskeyReaction.values()){
val code= mr.shortcode
val sb = SpannableStringBuilder()
.appendDrawableIcon(activity,mr.drawableId," ")
.append(' ')
.append(mr.shortcode)
ad.addAction(sb){
addReaction(status,code)
}
}
ad.show(activity)
return
}
TootTaskRunner(activity,progress_style = TootTaskRunner.PROGRESS_NONE).run(access_info,object :TootTask{
override fun background(client : TootApiClient) : TootApiResult? {
val params = access_info.putMisskeyApiToken(JSONObject())
.put("noteId",status.id.toString())
.put("reaction",code)
val result = client.request("/api/notes/reactions/create",params.toPostRequestBuilder())
// 成功すると204 no content
return result
}
override fun handleResult(result : TootApiResult?) {
result?: return
val error = result.error
if( error!=null){
showToast(activity,false,error)
return
}
if( (result.response?.code()?:-1) in 200 until 300 ){
if( status.reactionCounts == null ){
status.reactionCounts = HashMap()
}
val count = status.reactionCounts?.get(code) ?: 0
status.reactionCounts?.put(code,count+1)
// 1個だけ描画更新するのではなく、TLにある複数の要素をまとめて更新する
list_adapter.notifyChange(reason = "addReaction complete", reset = true)
}
}
})
}
private fun makeEnqueteChoiceView(
enquete : NicoEnquete,
now : Long,
@ -1903,7 +2072,7 @@ internal class ItemViewHolder(
}
val marginBetween = dip(2)
val compoundPadding= dip(0.5f)
val compoundPadding = dip(0.5f)
btnConversation = imageButton {

View File

@ -291,7 +291,7 @@ fun SpannableStringBuilder.appendColorShadeIcon(
context:Context,
drawable_id:Int,
text:String
){
):SpannableStringBuilder{
val start = this.length
this.append(text)
val end = this.length
@ -301,4 +301,22 @@ fun SpannableStringBuilder.appendColorShadeIcon(
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
return this
}
fun SpannableStringBuilder.appendDrawableIcon(
context:Context,
drawable_id:Int,
text:String
):SpannableStringBuilder{
val start = this.length
this.append(text)
val end = this.length
this.setSpan(
EmojiImageSpan(context, drawable_id),
start,
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
return this
}

View File

@ -1,24 +1,28 @@
//package jp.juggler.subwaytooter.api.entity
//
//enum class MisskeyReaction(val name:String,val drawableId:Int){
//
//
// Like("like"),
// Love("love"),
// Laugh("laugh"),
// Hmm("hmm"),
// Surprise("surprise"),
// Congrats("congrats"),
// Angry("angry"),
// Confused("confused"),
// Rip("rip"),
// Pudding("pudding") ;
//
// companion object {
// val map: HashMap<String,MisskeyReaction> by lazy {
// HashMap<String,MisskeyReaction>().apply {
//
// }
// }
// }
//}
package jp.juggler.subwaytooter.api.entity
import jp.juggler.subwaytooter.R
enum class MisskeyReaction(val shortcode:String,val drawableId:Int){
Like("like", R.drawable.emj_1f44d),
Love("love",R.drawable.emj_2665),
Laugh("laugh",R.drawable.emj_1f606),
Hmm("hmm", R.drawable.emj_1f914),
Surprise("surprise",R.drawable.emj_1f62e),
Congrats("congrats",R.drawable.emj_1f389),
Angry("angry",R.drawable.emj_1f4a2),
Confused("confused",R.drawable.emj_1f625),
Rip("rip", R.drawable.emj_1f607),
Pudding("pudding", R.drawable.emj_1f36e) ;
companion object {
val shortcodeMap: HashMap<String,MisskeyReaction> by lazy {
HashMap<String,MisskeyReaction>().apply {
for( e in MisskeyReaction.values()){
put( e.shortcode,e)
}
}
}
}
}

View File

@ -140,6 +140,8 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() {
var viaMobile : Boolean = false
var reactionCounts : HashMap<String, Int>? = null
///////////////////////////////////////////////////////////////////
// 以下はentityから取得したデータではなく、アプリ内部で使う
@ -223,7 +225,6 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() {
// "mentionedRemoteUsers" -> "[{"uri":"https:\/\/mastodon.juggler.jp\/users\/tateisu","username":"tateisu","host":"mastodon.juggler.jp"}]"
this.tags = parseMisskeyTags(src.optJSONArray("tags"))
this.application = parseItem(::TootApplication, parser, src.optJSONObject("app"), log)
@ -286,11 +287,12 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() {
src.optJSONObject("poll")
)
this.reactionCounts = parseReactionCounts(src.optJSONObject("reactionCounts"))
this.reblog = parser.status(src.optJSONObject("renote"))
} else {
misskeyVisibleIds = null
this.uri = src.parseString("uri") // MSPだとuriは提供されない
this.url = src.parseString("url") // 頻繁にnullになる
this.created_at = src.parseString("created_at")
@ -437,20 +439,6 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() {
}
}
private fun parseMisskeyTags(src : JSONArray?) : ArrayList<TootTag>? {
var rv : ArrayList<TootTag>? = null
if(src != null) {
for(i in 0 until src.length()) {
val sv = src.optString(i, null)
if(sv?.isNotEmpty() == true) {
if(rv == null) rv = ArrayList()
rv.add(TootTag(name = sv))
}
}
}
return rv
}
///////////////////////////////////////////////////
// ユーティリティ
@ -698,6 +686,33 @@ class TootStatus(parser : TootParser, src : JSONObject) : TimelineItem() {
return rv
}
private fun parseReactionCounts(src : JSONObject?) : HashMap<String, Int>? {
var rv : HashMap<String, Int>? = null
if(src != null) {
for(key in src.keys()) {
val v = src.parseInt(key) ?: continue
MisskeyReaction.shortcodeMap[key] ?: continue
if(rv == null) rv = HashMap()
rv[key] = v
}
}
return rv
}
private fun parseMisskeyTags(src : JSONArray?) : ArrayList<TootTag>? {
var rv : ArrayList<TootTag>? = null
if(src != null) {
for(i in 0 until src.length()) {
val sv = src.optString(i, null)
if(sv?.isNotEmpty() == true) {
if(rv == null) rv = ArrayList()
rv.add(TootTag(name = sv))
}
}
}
return rv
}
private fun validHost(host : String?) : String? {
return if(host != null && host.isNotEmpty() && host != "?") host else null
}

View File

@ -21,7 +21,7 @@ class ActionsDialog {
return this
}
fun show(context : Context, title : CharSequence?) : ActionsDialog {
fun show(context : Context, title : CharSequence? = null ) : ActionsDialog {
val caption_list = arrayOfNulls<CharSequence>(action_list.size)
var i = 0
val ie = caption_list.size

View File

@ -748,6 +748,7 @@
<string name="visibility_home">Home</string>
<string name="visibility_followers">Followers</string>
<string name="vote_count_text">%1$d votes</string>
<string name="reaction_add">Add reaction</string>
<!--<string name="abc_action_bar_home_description">Revenir à l\'accueil</string>-->
<!--<string name="abc_action_bar_home_description_format">%1$s, %2$s</string>-->

View File

@ -1026,6 +1026,7 @@
<string name="misskey">Misskey</string>
<string name="visibility_home">ホーム</string>
<string name="visibility_followers">フォロワー</string>
<string name="vote_count_text">%1$d votes</string>
<string name="vote_count_text">%1$d票</string>
<string name="reaction_add">リアクションの追加</string>
</resources>

View File

@ -734,4 +734,5 @@
<string name="visibility_home">Home</string>
<string name="visibility_followers">Followers</string>
<string name="vote_count_text">%1$d votes</string>
<string name="reaction_add">Add reaction</string>
</resources>