2018-01-04 19:52:25 +01:00
|
|
|
package jp.juggler.subwaytooter.util
|
|
|
|
|
|
|
|
import android.annotation.SuppressLint
|
|
|
|
import android.app.Activity
|
2019-06-03 22:50:18 +02:00
|
|
|
import android.content.SharedPreferences
|
2018-01-04 19:52:25 +01:00
|
|
|
import android.os.Handler
|
2019-02-15 02:51:22 +01:00
|
|
|
import androidx.core.content.ContextCompat
|
2018-01-04 19:52:25 +01:00
|
|
|
import android.text.Spannable
|
2018-01-16 07:48:17 +01:00
|
|
|
import android.text.SpannableStringBuilder
|
2018-01-04 19:52:25 +01:00
|
|
|
import android.view.Gravity
|
|
|
|
import android.view.View
|
|
|
|
import android.widget.CheckedTextView
|
|
|
|
import android.widget.EditText
|
|
|
|
import android.widget.LinearLayout
|
|
|
|
import android.widget.PopupWindow
|
2019-06-03 22:50:18 +02:00
|
|
|
import jp.juggler.subwaytooter.Pref
|
2018-01-04 19:52:25 +01:00
|
|
|
import jp.juggler.subwaytooter.R
|
|
|
|
import jp.juggler.subwaytooter.view.MyEditText
|
2018-12-01 00:02:18 +01:00
|
|
|
import jp.juggler.util.LogCategory
|
|
|
|
import jp.juggler.util.getAttributeColor
|
|
|
|
import java.util.*
|
2018-11-06 02:29:33 +01:00
|
|
|
import java.util.regex.Pattern
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
@SuppressLint("InflateParams")
|
|
|
|
internal class PopupAutoCompleteAcct(
|
|
|
|
val activity : Activity,
|
|
|
|
private val etContent : EditText,
|
|
|
|
private val formRoot : View,
|
|
|
|
private val bMainScreen : Boolean
|
|
|
|
) {
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
|
|
|
|
internal val log = LogCategory("PopupAutoCompleteAcct")
|
2018-11-06 02:29:33 +01:00
|
|
|
|
|
|
|
// 絵文字ショートコードにマッチするとても雑な正規表現
|
|
|
|
private val reLastShortCode = Pattern.compile(""":([^\s:]+):\z""")
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private val acct_popup : PopupWindow
|
|
|
|
private val llItems : LinearLayout
|
|
|
|
val density : Float
|
|
|
|
private val popup_width : Int
|
|
|
|
val handler : Handler
|
|
|
|
|
2019-06-03 22:50:18 +02:00
|
|
|
private val pref : SharedPreferences = Pref.pref(activity)
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
private var popup_rows : Int = 0
|
|
|
|
|
|
|
|
val isShowing : Boolean
|
|
|
|
get() = acct_popup.isShowing
|
|
|
|
|
|
|
|
fun dismiss() {
|
|
|
|
try {
|
|
|
|
acct_popup.dismiss()
|
2018-01-16 07:48:17 +01:00
|
|
|
} catch(ex : Throwable) {
|
2018-01-04 19:52:25 +01:00
|
|
|
log.trace(ex)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
init {
|
|
|
|
this.density = activity.resources.displayMetrics.density
|
|
|
|
this.handler = Handler(activity.mainLooper)
|
|
|
|
|
|
|
|
popup_width = (0.5f + 240f * density).toInt()
|
|
|
|
|
2018-01-16 07:48:17 +01:00
|
|
|
val viewRoot = activity.layoutInflater.inflate(R.layout.acct_complete_popup, null, false)
|
2018-01-04 19:52:25 +01:00
|
|
|
llItems = viewRoot.findViewById(R.id.llItems)
|
|
|
|
//
|
|
|
|
acct_popup = PopupWindow(activity)
|
2018-02-11 09:20:49 +01:00
|
|
|
acct_popup.setBackgroundDrawable(
|
|
|
|
ContextCompat.getDrawable(
|
|
|
|
activity,
|
|
|
|
R.drawable.acct_popup_bg
|
|
|
|
)
|
|
|
|
)
|
2018-01-04 19:52:25 +01:00
|
|
|
acct_popup.contentView = viewRoot
|
|
|
|
acct_popup.isTouchable = true
|
|
|
|
}
|
|
|
|
|
|
|
|
fun setList(
|
2018-01-14 10:14:39 +01:00
|
|
|
et : MyEditText,
|
|
|
|
sel_start : Int,
|
|
|
|
sel_end : Int,
|
|
|
|
acct_list : ArrayList<CharSequence>?,
|
|
|
|
picker_caption : String?,
|
|
|
|
picker_callback : Runnable?
|
2018-01-04 19:52:25 +01:00
|
|
|
) {
|
|
|
|
|
|
|
|
llItems.removeAllViews()
|
|
|
|
|
|
|
|
popup_rows = 0
|
|
|
|
|
|
|
|
run {
|
|
|
|
val v = activity.layoutInflater
|
|
|
|
.inflate(R.layout.lv_spinner_dropdown, llItems, false) as CheckedTextView
|
2018-12-01 00:02:18 +01:00
|
|
|
v.setTextColor(getAttributeColor(activity, android.R.attr.textColorPrimary))
|
2018-01-04 19:52:25 +01:00
|
|
|
v.setText(R.string.close)
|
2018-01-16 07:48:17 +01:00
|
|
|
v.setOnClickListener { acct_popup.dismiss() }
|
2018-01-04 19:52:25 +01:00
|
|
|
llItems.addView(v)
|
|
|
|
++ popup_rows
|
|
|
|
}
|
|
|
|
|
|
|
|
if(picker_caption != null && picker_callback != null) {
|
|
|
|
val v = activity.layoutInflater
|
|
|
|
.inflate(R.layout.lv_spinner_dropdown, llItems, false) as CheckedTextView
|
2018-12-01 00:02:18 +01:00
|
|
|
v.setTextColor(getAttributeColor(activity, android.R.attr.textColorPrimary))
|
2018-01-04 19:52:25 +01:00
|
|
|
v.text = picker_caption
|
|
|
|
v.setOnClickListener {
|
2018-01-16 07:48:17 +01:00
|
|
|
acct_popup.dismiss()
|
2018-01-04 19:52:25 +01:00
|
|
|
picker_callback.run()
|
|
|
|
}
|
|
|
|
llItems.addView(v)
|
|
|
|
++ popup_rows
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(acct_list != null) {
|
|
|
|
var i = 0
|
|
|
|
while(true) {
|
|
|
|
if(i >= acct_list.size) break
|
|
|
|
val acct = acct_list[i]
|
|
|
|
val v = activity.layoutInflater
|
|
|
|
.inflate(R.layout.lv_spinner_dropdown, llItems, false) as CheckedTextView
|
2018-12-01 00:02:18 +01:00
|
|
|
v.setTextColor(getAttributeColor(activity, android.R.attr.textColorPrimary))
|
2018-01-04 19:52:25 +01:00
|
|
|
v.text = acct
|
|
|
|
if(acct is Spannable) {
|
|
|
|
NetworkEmojiInvalidator(handler, v).register(acct)
|
|
|
|
}
|
|
|
|
v.setOnClickListener {
|
2018-02-11 09:20:49 +01:00
|
|
|
|
2018-09-27 16:21:37 +02:00
|
|
|
val start : Int
|
|
|
|
val editable = et.text ?: ""
|
2018-01-16 07:48:17 +01:00
|
|
|
val sb = SpannableStringBuilder()
|
2018-09-27 16:21:37 +02:00
|
|
|
|
|
|
|
val src_length = editable.length
|
|
|
|
start = Math.min(src_length, sel_start)
|
|
|
|
val end = Math.min(src_length, sel_end)
|
|
|
|
sb.append(editable.subSequence(0, start))
|
|
|
|
val remain = editable.subSequence(end, src_length)
|
2018-02-11 09:20:49 +01:00
|
|
|
|
2018-08-15 04:06:25 +02:00
|
|
|
if(acct[0] == ' ') {
|
2018-02-11 09:45:15 +01:00
|
|
|
// 絵文字ショートコード
|
2019-06-03 22:50:18 +02:00
|
|
|
val separator = EmojiDecoder.customEmojiSeparator(pref)
|
|
|
|
if(! EmojiDecoder.canStartShortCode(sb, start)) sb.append(separator)
|
2019-03-14 07:55:01 +01:00
|
|
|
sb.append(findShortCode(acct.toString()))
|
2019-06-03 22:50:18 +02:00
|
|
|
// セパレータにZWSPを使う設定なら、補完した次の位置にもZWSPを追加する。連続して入力補完できるようになる。
|
|
|
|
if( separator != ' ') sb.append(separator)
|
2018-08-15 04:06:25 +02:00
|
|
|
} else {
|
2018-02-11 09:45:15 +01:00
|
|
|
// @user@host, #hashtag
|
|
|
|
// 直後に空白を付与する
|
|
|
|
sb.append(acct).append(" ")
|
2018-02-11 09:20:49 +01:00
|
|
|
}
|
2018-08-15 04:06:25 +02:00
|
|
|
|
2018-02-11 09:20:49 +01:00
|
|
|
val newSelection = sb.length
|
2018-09-27 16:21:37 +02:00
|
|
|
sb.append(remain)
|
2018-02-11 09:20:49 +01:00
|
|
|
|
2018-01-16 07:48:17 +01:00
|
|
|
et.text = sb
|
2018-02-11 09:20:49 +01:00
|
|
|
et.setSelection(newSelection)
|
2018-01-16 07:48:17 +01:00
|
|
|
acct_popup.dismiss()
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
llItems.addView(v)
|
|
|
|
++ popup_rows
|
|
|
|
++ i
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
updatePosition()
|
|
|
|
}
|
|
|
|
|
2018-11-06 02:29:33 +01:00
|
|
|
private fun findShortCode(acct : String) : String {
|
|
|
|
val m = reLastShortCode.matcher(acct)
|
2019-09-12 15:43:11 +02:00
|
|
|
if(m.find()) return m.group(0)!!
|
2018-11-06 02:29:33 +01:00
|
|
|
return acct
|
|
|
|
}
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
fun updatePosition() {
|
|
|
|
|
|
|
|
val location = IntArray(2)
|
|
|
|
etContent.getLocationOnScreen(location)
|
|
|
|
val text_top = location[1]
|
|
|
|
|
|
|
|
var popup_top : Int
|
|
|
|
var popup_height : Int
|
|
|
|
|
|
|
|
if(bMainScreen) {
|
|
|
|
val popup_bottom = text_top + etContent.totalPaddingTop - etContent.scrollY
|
|
|
|
val max = popup_bottom - (0.5f + 48f * 1f * density).toInt()
|
|
|
|
val min = (0.5f + 48f * 2f * density).toInt()
|
|
|
|
popup_height = (0.5f + 48f * popup_rows.toFloat() * density).toInt()
|
|
|
|
if(popup_height < min) popup_height = min
|
|
|
|
if(popup_height > max) popup_height = max
|
|
|
|
popup_top = popup_bottom - popup_height
|
|
|
|
|
|
|
|
} else {
|
|
|
|
formRoot.getLocationOnScreen(location)
|
|
|
|
val form_top = location[1]
|
|
|
|
val form_bottom = location[1] + formRoot.height
|
|
|
|
|
|
|
|
val layout = etContent.layout
|
|
|
|
|
2019-03-14 07:55:01 +01:00
|
|
|
popup_top = try {
|
|
|
|
(text_top
|
|
|
|
+ etContent.totalPaddingTop
|
|
|
|
+ layout.getLineBottom(layout.lineCount - 1)) - etContent.scrollY
|
|
|
|
} catch(ex : Throwable) {
|
|
|
|
// java.lang.IllegalStateException
|
|
|
|
0
|
|
|
|
}
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
if(popup_top < form_top) popup_top = form_top
|
|
|
|
|
|
|
|
popup_height = form_bottom - popup_top
|
|
|
|
|
|
|
|
val min = (0.5f + 48f * 2f * density).toInt()
|
|
|
|
val max = (0.5f + 48f * popup_rows.toFloat() * density).toInt()
|
|
|
|
|
|
|
|
if(popup_height < min) popup_height = min
|
|
|
|
if(popup_height > max) popup_height = max
|
|
|
|
}
|
|
|
|
|
|
|
|
if(acct_popup.isShowing) {
|
|
|
|
acct_popup.update(0, popup_top, popup_width, popup_height)
|
|
|
|
} else {
|
|
|
|
acct_popup.width = popup_width
|
|
|
|
acct_popup.height = popup_height
|
|
|
|
acct_popup.showAtLocation(
|
|
|
|
etContent, Gravity.CENTER_HORIZONTAL or Gravity.TOP, 0, popup_top
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|