(Misskey)show and vote enquete

This commit is contained in:
tateisu 2018-08-25 01:24:11 +09:00
parent e1d35f6ec7
commit 8a32a54041
7 changed files with 135 additions and 81 deletions

View File

@ -806,19 +806,22 @@ internal class ItemViewHolder(
// ニコフレのアンケートの表示
val enquete = status.enquete
if(enquete != null && NicoEnquete.TYPE_ENQUETE == enquete.type) {
val question = enquete.decoded_question
val items = enquete.items
if(question.isNotBlank()) content = question
if(items != null) {
val now = System.currentTimeMillis()
var n = 0
for(item in items) {
makeEnqueteChoiceView(enquete, now, n ++, item.decoded_text)
if(enquete != null ){
if( access_info.isMisskey || NicoEnquete.TYPE_ENQUETE == enquete.type) {
val question = enquete.decoded_question
val items = enquete.items
if(question.isNotBlank()) content = question
if(items != null) {
val now = System.currentTimeMillis()
var n = 0
for(item in items) {
makeEnqueteChoiceView(enquete, now, n ++, item)
}
}
if(!access_info.isMisskey) makeEnqueteTimerView(enquete)
}
makeEnqueteTimerView(enquete)
}
// カードの表示(会話ビューのみ)
@ -1760,10 +1763,14 @@ internal class ItemViewHolder(
enquete : NicoEnquete,
now : Long,
i : Int,
item : Spannable
item : NicoEnquete.Choice
) {
val remain = enquete.time_start + NicoEnquete.ENQUETE_EXPIRE - now
val canVote = if( access_info.isMisskey ){
enquete.myVoted == null
}else{
val remain = enquete.time_start + NicoEnquete.ENQUETE_EXPIRE - now
enquete.myVoted == null && remain > 0L
}
val lp = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
@ -1774,11 +1781,25 @@ internal class ItemViewHolder(
val b = Button(activity)
b.layoutParams = lp
b.setAllCaps(false)
b.text = item
val text = if( access_info.isMisskey ){
val sb = SpannableStringBuilder()
.append(item.decoded_text)
if(enquete.myVoted != null ) {
sb.append(" / ")
sb.append(activity.getString(R.string.vote_count_text, item.votes))
if(i == enquete.myVoted) sb.append(' ').append(0x2713.toChar())
}
sb
}else{
item.decoded_text
}
b.text = text
val invalidator = NetworkEmojiInvalidator(activity.handler, b)
extra_invalidator_list.add(invalidator)
invalidator.register(item)
if(remain <= 0) {
invalidator.register(text)
if(!canVote) {
b.isEnabled = false
} else {
val accessInfo = this@ItemViewHolder.access_info
@ -1807,26 +1828,40 @@ internal class ItemViewHolder(
idx : Int
) {
val now = System.currentTimeMillis()
val remain = enquete.time_start + NicoEnquete.ENQUETE_EXPIRE - now
if(remain <= 0) {
showToast(context, false, R.string.enquete_was_end)
if( enquete.myVoted != null ) {
showToast(context, false, R.string.already_voted)
return
}
if(!accessInfo.isMisskey){
val remain = enquete.time_start + NicoEnquete.ENQUETE_EXPIRE - now
if( remain <=0L) {
showToast(context, false, R.string.enquete_was_end)
return
}
}
TootTaskRunner(context).run(accessInfo, object : TootTask {
override fun background(client : TootApiClient) : TootApiResult? {
val form = JSONObject()
try {
form.put("item_index", Integer.toString(idx))
} catch(ex : Throwable) {
log.e(ex, "json encode failed.")
ex.printStackTrace()
if( accessInfo.isMisskey ){
val params = accessInfo.putMisskeyApiToken(JSONObject())
.put("noteId",enquete.status_id.toString())
.put("choice",idx)
return client.request("/api/notes/polls/vote",params.toPostRequestBuilder())
}else{
val form = JSONObject()
try {
form.put("item_index", Integer.toString(idx))
} catch(ex : Throwable) {
log.e(ex, "json encode failed.")
ex.printStackTrace()
}
val request_builder = Request.Builder()
.post(RequestBody.create(TootApiClient.MEDIA_TYPE_JSON, form.toString()))
return client.request("/api/v1/votes/" + enquete.status_id, request_builder)
}
val request_builder = Request.Builder()
.post(RequestBody.create(TootApiClient.MEDIA_TYPE_JSON, form.toString()))
return client.request("/api/v1/votes/" + enquete.status_id, request_builder)
}
override fun handleResult(result : TootApiResult?) {
@ -1834,12 +1869,25 @@ internal class ItemViewHolder(
val data = result.jsonObject
if(data != null) {
val message = data.parseString("message") ?: "?"
val valid = data.optBoolean("valid")
if(valid) {
if(accessInfo.isMisskey){
enquete.myVoted = idx
val choice = enquete.items?.get(idx)
if( choice != null ) choice.votes ++
// 204 no content
showToast(context, false, R.string.enquete_voted)
} else {
showToast(context, true, R.string.enquete_vote_failed, message)
// 1個だけ開閉するのではなく、例えば通知TLにある複数の要素をまとめて開閉するなどある
list_adapter.notifyChange(reason = "onClickEnqueteChoice", reset = true)
}else{
val message = data.parseString("message") ?: "?"
val valid = data.optBoolean("valid")
if(valid) {
showToast(context, false, R.string.enquete_voted)
} else {
showToast(context, true, R.string.enquete_vote_failed, message)
}
}
} else {
showToast(context, true, result.error)

View File

@ -200,20 +200,37 @@ class TootApiClient(
fun getScopeArrayMisskey(@Suppress("UNUSED_PARAMETER") ti : TootInstance) =
JSONArray().apply {
put("account-read")
put("account-write")
put("note-read")
put("note-write")
put("reaction-read")
put("reaction-write")
put("following-write")
put("following-read") // フォロリク申請一覧で使われていた
put("following-write")
put("drive-read")
put("drive-write")
put("notification-read")
put("notification-write")
put("favorite-read")
put("favorite-write")
put("account/read")
put("account/write")
put("messaging-read")
put("messaging-write")
put("vote-read")
put("vote-write")
// https://github.com/syuilo/misskey/issues/2341
}

View File

@ -2,6 +2,7 @@ package jp.juggler.subwaytooter.api.entity
import android.content.Context
import android.text.Spannable
import android.text.SpannableString
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.api.TootParser
import jp.juggler.subwaytooter.util.*
@ -33,6 +34,8 @@ class NicoEnquete(
// 結果の数値のテキスト // null or array of string
private val ratios_text : MutableList<String>?
var myVoted : Int? = null
// 以下はJSONには存在しないが内部で使う
val time_start : Long
val status_id : EntityId
@ -45,40 +48,30 @@ class NicoEnquete(
if(parser.serviceType == ServiceType.MISSKEY) {
this.items = parseChoiceListMisskey(
parser.context,
status,
src.optJSONArray("items")
src.optJSONArray("choices")
)
var hasVoteResult = false
val votesList = ArrayList<Int>()
var votesMax = 1
if( items != null){
for( choice in items){
val votes = choice.votes
if( votes != null ){
hasVoteResult = true
votesList.add(votes)
if( votes > votesMax) votesMax = votes
}else{
votesList.add(0)
}
}
items?.forEachIndexed { index, choice ->
if(choice.isVoted) this.myVoted = index
val votes = choice.votes
votesList.add(votes)
if(votes > votesMax) votesMax = votes
}
if( hasVoteResult ){
this.ratios = votesList.map { (it.toFloat()/votesMax.toFloat()) }.toMutableList()
this.ratios_text = votesList.map{ parser.context.getString(R.string.vote_count_text,it)}.toMutableList()
}else{
if(votesList.isNotEmpty()) {
this.ratios = votesList.map { (it.toFloat() / votesMax.toFloat()) }.toMutableList()
this.ratios_text =
votesList.map { parser.context.getString(R.string.vote_count_text, it) }
.toMutableList()
} else {
this.ratios = null
this.ratios_text = null
}
this.type = when(hasVoteResult){
true -> "enquete_result"
else-> "enquete"
}
this.type = NicoEnquete.TYPE_ENQUETE
this.question = status.content
this.decoded_question = DecodeOptions(
@ -122,9 +115,8 @@ class NicoEnquete(
class Choice(
val text : String,
val decoded_text : Spannable,
val id : EntityId? = null, // misskey
var isVoted : Boolean = false, // misskey
var votes : Int? = null // misskey
var votes : Int = 0 // misskey
)
companion object {
@ -159,11 +151,12 @@ class NicoEnquete(
null
}
}
fun parse(
parser : TootParser,
status : TootStatus,
list_attachment : ArrayList<TootAttachmentLike>?,
src:JSONObject?
src : JSONObject?
) : NicoEnquete? {
src ?: return null
return try {
@ -178,6 +171,7 @@ class NicoEnquete(
null
}
}
private fun parseStringArray(src : JSONObject, name : String) : ArrayList<String>? {
val array = src.optJSONArray(name)
if(array != null) {
@ -228,17 +222,9 @@ class NicoEnquete(
}
private fun parseChoiceListMisskey(
context : Context,
status : TootStatus,
choices : JSONArray?
) : ArrayList<Choice>? {
if(choices != null) {
val options = DecodeOptions(
context,
emojiMapCustom = status.custom_emojis,
emojiMapProfile = status.profile_emojis
)
val items = ArrayList<Choice>()
for(i in 0 until choices.length()) {
val src = choices.optJSONObject(i)
@ -246,13 +232,13 @@ class NicoEnquete(
val text = reWhitespace
.matcher(src.parseString("text")?.sanitizeBDI() ?: "")
.replaceAll(" ")
val decoded_text = options.decodeEmoji(text)
val decoded_text = SpannableString(text) // misskey ではマークダウン不可で絵文字もない
val dst = Choice(
text = text,
decoded_text = decoded_text,
id = EntityId.mayNull(src.parseString("id")),
votes = src.parseInt("votes"),
// 配列インデクスと同じだった id = EntityId.mayNull(src.parseLong("id")),
votes = src.parseInt("votes")?:0,
isVoted = src.optBoolean("isVoted")
)
items.add(dst)

View File

@ -802,7 +802,7 @@ object MisskeyMarkdownDecoder {
else -> Node(
pos
, matcher.end()
, arrayOf(matcher.group(1), matcher.group(2)) // username, host
, arrayOf(matcher.group(1), matcher.group(2)?:"") // username, host
) {
val username = data[0]
val host = data[1]

View File

@ -760,7 +760,7 @@
<string name="reaction_add">Add reaction</string>
<string name="unknown_notification_from">unknown notification from %1$s</string>
<string name="dont_show_reaction">Don\'t show reaction</string>
<string name="dont_show_vote">Don\'t show vote</string>
<string name="dont_show_vote">Don\'t show enquête</string>
<string name="boost_button_size">Boost button size( unit:dp, default=40, app restart required)</string>
<string name="boost_button_alignment">Boost button alignment (app restart required)</string>
<string name="start">Start</string>
@ -776,6 +776,7 @@
<string name="background_color_follower">Toot background color of \'follower\' visibility</string>
<string name="background_color_direct_with_user">Toot background color of \'Direct to users\' visibility</string>
<string name="background_color_direct_no_user">Toot background color of \'Direct only me\' visibility</string>
<string name="already_voted">Already voted.</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

@ -1037,7 +1037,7 @@
<string name="reaction_add">リアクションの追加</string>
<string name="unknown_notification_from">%1$s からの謎の通知</string>
<string name="dont_show_reaction">リアクションを表示しない</string>
<string name="dont_show_vote">投票を表示しない</string>
<string name="dont_show_vote">アンケートを表示しない</string>
<string name="boost_button_size">ブーストボタンのサイズ(単位:dp。デフォルト:40。アプリ再起動が必要)</string>
<string name="boost_button_alignment">ブーストボタン列の左右の配置 (アプリ再起動が必要)</string>
<string name="start">始端</string>
@ -1053,5 +1053,6 @@
<string name="background_color_follower">トゥート背景色 \'フォロワーのみ\'</string>
<string name="background_color_direct_with_user">トゥート背景色 \'ダイレクト(対象ユーザあり)\'</string>
<string name="background_color_direct_no_user">トゥート背景色 \'ダイレクト(自分のみ)\'</string>
<string name="already_voted">投票済です</string>
</resources>

View File

@ -745,7 +745,7 @@
<string name="reaction_add">Add reaction</string>
<string name="unknown_notification_from">unknown notification from %1$s</string>
<string name="dont_show_reaction">Don\'t show reaction</string>
<string name="dont_show_vote">Don\'t show vote</string>
<string name="dont_show_vote">Don\'t show enquete</string>
<string name="boost_button_size">Boost button size( unit:dp, default=40, app restart required)</string>
<string name="boost_button_alignment">Boost button alignment (app restart required)</string>
<string name="start">Start</string>
@ -761,5 +761,6 @@
<string name="background_color_follower">Toot background color of \'follower\' visibility</string>
<string name="background_color_direct_with_user">Toot background color of \'Direct to users\' visibility</string>
<string name="background_color_direct_no_user">Toot background color of \'Direct only me\' visibility</string>
<string name="already_voted">Already voted.</string>
</resources>