編集中の絵文字も画像で表示する

This commit is contained in:
tateisu 2018-01-16 15:48:17 +09:00
parent b9d24a4915
commit bd94fcc075
6 changed files with 140 additions and 102 deletions

View File

@ -12,8 +12,8 @@ android {
minSdkVersion 21
targetSdkVersion 27
versionCode 203
versionName "2.0.3"
versionCode 204
versionName "2.0.4"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

View File

@ -510,8 +510,9 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
if(Intent.ACTION_SEND == action) {
val sv = sent_intent.getStringExtra(Intent.EXTRA_TEXT)
if(sv != null) {
etContent.setText(sv)
etContent.setSelection(sv.length)
val svEmoji = DecodeOptions(decodeEmoji = true).decodeEmoji(this,sv)
etContent.setText(svEmoji)
etContent.setSelection(svEmoji.length)
}
}
@ -520,8 +521,9 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
var sv : String? = intent.getStringExtra(KEY_INITIAL_TEXT)
if(sv != null) {
etContent.setText(sv)
etContent.setSelection(sv.length)
val svEmoji = DecodeOptions(decodeEmoji = true).decodeEmoji(this,sv)
etContent.setText(svEmoji)
etContent.setSelection(svEmoji.length)
}
val account = this.account
@ -572,8 +574,9 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
}
if(sb.isNotEmpty()) {
sb.append(' ')
etContent.setText(sb.toString())
etContent.setSelection(sb.length)
val svEmoji = DecodeOptions(decodeEmoji = true).decodeEmoji(this,sb.toString())
etContent.setText(svEmoji)
etContent.setSelection(svEmoji.length)
}
// リプライ表示をつける
@ -1280,7 +1283,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
showMediaAttachment()
Utils.showToast(this, false, R.string.attachment_uploading)
TootTaskRunner(this).run(account , object : TootTask {
TootTaskRunner(this,TootTaskRunner.PROGRESS_NONE).run(account , object : TootTask {
override fun background(client : TootApiClient) : TootApiResult? {
if(mime_type.isEmpty()) {
return TootApiResult("mime_type is empty.")
@ -1832,8 +1835,9 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
val reply_image = draft.optString(DRAFT_REPLY_IMAGE, null)
val reply_url = draft.optString(DRAFT_REPLY_URL, null)
etContent.setText(content)
etContent.setSelection(content.length)
val evEmoji = DecodeOptions(decodeEmoji = true).decodeEmoji(this@ActPost,content)
etContent.setText(evEmoji)
etContent.setSelection(evEmoji.length)
etContentWarning.setText(content_warning)
etContentWarning.setSelection(content_warning.length)
cbContentWarning.isChecked = content_warning_checked

View File

@ -32,7 +32,8 @@ import jp.juggler.subwaytooter.view.NetworkEmojiView
class EmojiPicker(
private val activity : Activity,
private val instance : String?,
private val onEmojiPicked: (name : String)->Unit
private val onEmojiPicked: (name : String,instance:String?,bInstanceHasCustomEmoji:Boolean)->Unit
// onEmojiPickedのinstance引数は通常の絵文字ならnull、カスタム絵文字なら非null、
) : View.OnClickListener {
companion object {
@ -158,9 +159,11 @@ class EmojiPicker(
}
var bInstanceHasCustomEmoji = false
private fun setCustomEmojiList(list : ArrayList<CustomEmoji>?) {
if(list == null) return
bInstanceHasCustomEmoji = true
custom_list.clear()
for(emoji in list) {
custom_list.add(EmojiItem(emoji.shortcode, instance))
@ -336,8 +339,8 @@ class EmojiPicker(
// カスタム絵文字
selected(name, item.instance)
}else{
EmojiMap201709.sShortNameToImageId[name] ?: return
// 普通の絵文字
if(EmojiMap201709.sShortNameToImageId[name] == null) return
selected( if(selected_tone != 0) applySkinTone(name) else name, null)
}
}
@ -367,12 +370,18 @@ class EmojiPicker(
}
// 選択された絵文字と同じ項目を除去
for(i in list.indices.reversed()) {
val item = list[i]
if(name == Utils.optStringX(item, "name")) {
if(if(instance == null) item.isNull("instance") else instance == Utils.optStringX(item, "instance")) {
list.removeAt(i)
// 項目が増えすぎたら減らす
run{
val it = list.iterator()
var nCount = 0
while( it.hasNext()) {
val item = it.next()
if(name == Utils.optStringX(item, "name")
&& instance == Utils.optStringX(item, "instance")
) {
it.remove()
}else if( ++nCount >= 256){
it.remove()
}
}
}
@ -386,11 +395,6 @@ class EmojiPicker(
} catch(ignored : Throwable) {
}
// 項目が増えすぎたら減らす
while(list.size >= 256) {
list.removeAt(list.size - 1)
}
// 保存する
try {
val array = JSONArray()
@ -402,7 +406,7 @@ class EmojiPicker(
}
onEmojiPicked(name)
onEmojiPicked(name,instance,bInstanceHasCustomEmoji)
}
internal inner class EmojiPickerPagerAdapter : PagerAdapter() {

View File

@ -5,6 +5,7 @@ import android.app.Activity
import android.os.Handler
import android.support.v4.content.ContextCompat
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.view.Gravity
import android.view.View
import android.widget.CheckedTextView
@ -45,7 +46,7 @@ internal class PopupAutoCompleteAcct(
fun dismiss() {
try {
acct_popup.dismiss()
}catch(ex: Throwable){
} catch(ex : Throwable) {
log.trace(ex)
}
}
@ -56,7 +57,7 @@ internal class PopupAutoCompleteAcct(
popup_width = (0.5f + 240f * density).toInt()
val viewRoot = activity.layoutInflater.inflate(R.layout.acct_complete_popup, null, false)
val viewRoot = activity.layoutInflater.inflate(R.layout.acct_complete_popup, null, false)
llItems = viewRoot.findViewById(R.id.llItems)
//
acct_popup = PopupWindow(activity)
@ -83,7 +84,7 @@ internal class PopupAutoCompleteAcct(
.inflate(R.layout.lv_spinner_dropdown, llItems, false) as CheckedTextView
v.setTextColor(Styler.getAttributeColor(activity, android.R.attr.textColorPrimary))
v.setText(R.string.close)
v.setOnClickListener { acct_popup .dismiss() }
v.setOnClickListener { acct_popup.dismiss() }
llItems.addView(v)
++ popup_rows
}
@ -94,7 +95,7 @@ internal class PopupAutoCompleteAcct(
v.setTextColor(Styler.getAttributeColor(activity, android.R.attr.textColorPrimary))
v.text = picker_caption
v.setOnClickListener {
acct_popup .dismiss()
acct_popup.dismiss()
picker_callback.run()
}
llItems.addView(v)
@ -115,12 +116,17 @@ internal class PopupAutoCompleteAcct(
NetworkEmojiInvalidator(handler, v).register(acct)
}
v.setOnClickListener {
var s = et.text.toString()
val svInsert = if(acct[0] == ' ') acct.subSequence(2, acct.length) else acct
s = s.substring(0, sel_start) + svInsert + " " + if(sel_end >= s.length) "" else s.substring(sel_end)
et.setText(s)
et.setSelection(sel_start + svInsert.length + 1)
acct_popup .dismiss()
val svInsert : Spannable = SpannableStringBuilder()
.append(if(acct[0] == ' ') acct.subSequence(2, acct.length) else acct)
.append(" ")
val s = et.text
val sb = SpannableStringBuilder()
.append(s.subSequence(0, Math.min(s.length, sel_start)))
.append(svInsert)
.append(s.subSequence(Math.min(s.length, sel_end), s.length))
et.text = sb
et.setSelection(sel_start + svInsert.length)
acct_popup.dismiss()
}
llItems.addView(v)

View File

@ -5,11 +5,9 @@ import android.net.Uri
import android.os.Handler
import android.support.v7.app.AlertDialog
import android.support.v7.app.AppCompatActivity
import android.text.Editable
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.TextWatcher
import android.text.*
import android.view.View
import jp.juggler.emoji.EmojiMap201709
import org.json.JSONArray
import org.json.JSONException
@ -43,7 +41,7 @@ class PostHelper(
private val activity : AppCompatActivity,
private val pref : SharedPreferences,
private val handler : Handler
){
) {
companion object {
private val log = LogCategory("PostHelper")
@ -76,27 +74,27 @@ class PostHelper(
var enquete_items : ArrayList<String>? = null
fun post(account : SavedAccount, bConfirmTag : Boolean, bConfirmAccount : Boolean, callback : PostCompleteCallback) {
val content = this.content ?:""
val content = this.content ?: ""
val spoiler_text = this.spoiler_text
val bNSFW = this.bNSFW
val in_reply_to_id = this.in_reply_to_id
val attachment_list = this.attachment_list
val enquete_items = this.enquete_items
var visibility = this.visibility ?:""
var visibility = this.visibility ?: ""
if(content.isEmpty() ) {
if(content.isEmpty()) {
Utils.showToast(activity, true, R.string.post_error_contents_empty)
return
}
// nullはCWチェックなしを示す
// nullじゃなくてカラならエラー
if(spoiler_text != null && spoiler_text.isEmpty() ) {
if(spoiler_text != null && spoiler_text.isEmpty()) {
Utils.showToast(activity, true, R.string.post_error_contents_warning_empty)
return
}
if(visibility.isEmpty() ) {
if(visibility.isEmpty()) {
visibility = TootStatus.VISIBILITY_PUBLIC
}
@ -168,7 +166,7 @@ class PostHelper(
internal fun getInstanceInformation(client : TootApiClient) : TootApiResult? {
val result = client.request("/api/v1/instance")
instance_tmp = parseItem(::TootInstance,result?.jsonObject)
instance_tmp = parseItem(::TootInstance, result?.jsonObject)
return result
}
@ -207,12 +205,12 @@ class PostHelper(
val json = JSONObject()
try {
json.put("status", EmojiDecoder.decodeShortCode(content ))
json.put("status", EmojiDecoder.decodeShortCode(content))
if(visibility_checked != null) {
json.put("visibility", visibility_checked)
}
json.put("sensitive", bNSFW)
json.put("spoiler_text", EmojiDecoder.decodeShortCode(spoiler_text?:""))
json.put("spoiler_text", EmojiDecoder.decodeShortCode(spoiler_text ?: ""))
json.put("in_reply_to_id", if(in_reply_to_id == - 1L) null else in_reply_to_id)
var array = JSONArray()
if(attachment_list != null) {
@ -241,7 +239,7 @@ class PostHelper(
val sb = StringBuilder()
sb.append("status=")
sb.append(Uri.encode(EmojiDecoder.decodeShortCode(content )))
sb.append(Uri.encode(EmojiDecoder.decodeShortCode(content)))
if(visibility_checked != null) {
sb.append("&visibility=")
@ -252,7 +250,7 @@ class PostHelper(
sb.append("&sensitive=1")
}
if(spoiler_text?.isNotEmpty()==true) {
if(spoiler_text?.isNotEmpty() == true) {
sb.append("&spoiler_text=")
sb.append(Uri.encode(EmojiDecoder.decodeShortCode(spoiler_text)))
}
@ -346,27 +344,27 @@ class PostHelper(
private var instance : String? = null
private val onEmojiListLoad : (list : ArrayList<CustomEmoji> ) -> Unit
private val onEmojiListLoad : (list : ArrayList<CustomEmoji>) -> Unit
= { _ : ArrayList<CustomEmoji> ->
val popup = this@PostHelper.popup
if(popup?.isShowing == true) proc_text_changed.run()
}
val popup = this@PostHelper.popup
if(popup?.isShowing == true) proc_text_changed.run()
}
private val proc_text_changed = object : Runnable {
override fun run() {
val et = this@PostHelper.et
if( et==null || callback2?.canOpenPopup() != true) {
if(et == null || callback2?.canOpenPopup() != true) {
closeAcctPopup()
return
}
var start = et .selectionStart
val end = et .selectionEnd
var start = et.selectionStart
val end = et.selectionEnd
if(start != end) {
closeAcctPopup()
return
}
val src = et .text.toString()
val src = et.text.toString()
var count_atMark = 0
val pos_atMark = IntArray(2)
while(true) {
@ -413,7 +411,7 @@ class PostHelper(
if(acct_list.isEmpty()) {
closeAcctPopup()
} else {
openPopup()?.setList(et , start, end, acct_list, null, null)
openPopup()?.setList(et, start, end, acct_list, null, null)
}
}
@ -522,7 +520,6 @@ class PostHelper(
this@PostHelper.popup = popup
return popup
}
interface Callback2 {
fun onTextUpdate()
@ -566,14 +563,14 @@ class PostHelper(
this.callback2 = _callback2
this.bMainScreen = bMainScreen
et .addTextChangedListener(object : TextWatcher {
et.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s : CharSequence, start : Int, count : Int, after : Int) {
}
override fun onTextChanged(s : CharSequence, start : Int, before : Int, count : Int) {
handler.removeCallbacks(proc_text_changed)
handler.postDelayed(proc_text_changed, if(popup?.isShowing ==true ) 100L else 500L)
handler.postDelayed(proc_text_changed, if(popup?.isShowing == true) 100L else 500L)
}
override fun afterTextChanged(s : Editable) {
@ -581,7 +578,7 @@ class PostHelper(
}
})
et .setOnSelectionChangeListener(object : MyEditText.OnSelectionChangeListener {
et.setOnSelectionChangeListener(object : MyEditText.OnSelectionChangeListener {
override fun onSelectionChanged(selStart : Int, selEnd : Int) {
if(selStart != selEnd) {
// 範囲選択されてるならポップアップは閉じる
@ -596,25 +593,33 @@ class PostHelper(
}
private val open_picker_emoji :Runnable = Runnable {
EmojiPicker(activity, instance){ name ->
private val open_picker_emoji : Runnable = Runnable {
EmojiPicker(activity, instance) { name, instance, bInstanceHasCustomEmoji ->
val et = this.et ?: return@EmojiPicker
val src = et.text.toString()
val end = et.selectionEnd
val last_colon = src.lastIndexOf(':', end - 1)
if(last_colon == - 1 || end - last_colon < 1) return@EmojiPicker
val svInsert = ":$name: "
val newText = StringBuilder()
.append(src.substring(0, last_colon))
.append(svInsert)
.append( if(end >= src.length) "" else src.substring(end) )
.toString()
et.setText(newText)
et.setSelection(last_colon + svInsert.length )
val src = et.text
val src_length = src.length
val end = et.selectionEnd
val start = src.lastIndexOf(':', end - 1)
if(start == - 1 || end - start < 1) return@EmojiPicker
val item = EmojiMap201709.sShortNameToImageId[name]
val svInsert : Spannable = if(item == null || instance != null ) {
SpannableString(":$name: ")
}else if(!bInstanceHasCustomEmoji){
// 古いタンスだとshortcodeを使う。見た目は絵文字に変える。
DecodeOptions().decodeEmoji(activity, ":$name: ")
} else {
// 十分に新しいタンスなら絵文字のunicodeを使う。見た目は絵文字に変える。
DecodeOptions().decodeEmoji(activity, item.unified)
}
val newText = SpannableStringBuilder()
.append(src.subSequence(0, start))
.append(svInsert)
if( end <src_length) newText.append(src.subSequence(end, src_length))
et.text = newText
et.setSelection(start + svInsert.length)
proc_text_changed.run()
@ -624,22 +629,28 @@ class PostHelper(
}.show()
}
fun openEmojiPickerFromMore(){
EmojiPicker(activity, instance){ name->
val et = this.et?:return@EmojiPicker
val src = et.text.toString()
val end = et.selectionEnd
val insert_start = if( end >= src.length ) src.length else end
fun openEmojiPickerFromMore() {
EmojiPicker(activity, instance) { name, instance, bInstanceHasCustomEmoji ->
val et = this.et ?: return@EmojiPicker
val svInsert = ":$name: "
val newText = StringBuilder()
.append(src.substring(0,insert_start))
val src = et.text
val src_length = src.length
val start = Math.min(src_length, et.selectionStart)
val end = Math.min(src_length, et.selectionEnd)
val item = EmojiMap201709.sShortNameToImageId[name]
val svInsert : Spannable = if(item == null || instance != null || ! bInstanceHasCustomEmoji) {
SpannableString(":$name: ")
} else {
DecodeOptions(decodeEmoji = true).decodeEmoji(activity, item.unified)
}
val newText = SpannableStringBuilder()
.append(src.subSequence(0, start))
.append(svInsert)
.append( src.substring(insert_start) )
.toString()
if( end <src_length) newText.append(src.subSequence(end, src_length))
et.setText(newText)
et.setSelection(insert_start + svInsert.length )
et.text = newText
et.setSelection(start + svInsert.length)
proc_text_changed.run()
}.show()

View File

@ -38,7 +38,6 @@ import android.util.SparseBooleanArray
import android.database.Cursor
import android.net.Uri
import android.view.GestureDetector
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
@ -57,7 +56,6 @@ import javax.xml.parsers.DocumentBuilder
import javax.xml.parsers.DocumentBuilderFactory
import it.sephiroth.android.library.exif2.ExifInterface
import okhttp3.Response
@Suppress("unused")
object Utils {
@ -198,14 +196,30 @@ object Utils {
runOnMainThread {
// 前回のトーストの表示を終了する
refToast?.get()?.cancel()
try {
refToast?.get()?.cancel()
} catch(ex : Throwable) {
log.trace(ex)
} finally {
refToast = null
}
// 新しいトーストを作る
val t = Toast.makeText(
context, message, if(bLong) Toast.LENGTH_LONG else Toast.LENGTH_SHORT
)
refToast = WeakReference(t)
t.show()
try {
val t = Toast.makeText(
context, message, if(bLong) Toast.LENGTH_LONG else Toast.LENGTH_SHORT
)
t.show()
refToast = WeakReference(t)
} catch(ex : Throwable) {
log.trace(ex)
// android.view.WindowManager$BadTokenException:
// at android.view.ViewRootImpl.setView (ViewRootImpl.java:679)
// at android.view.WindowManagerGlobal.addView (WindowManagerGlobal.java:342)
// at android.view.WindowManagerImpl.addView (WindowManagerImpl.java:94)
// at android.widget.Toast$TN.handleShow (Toast.java:435)
// at android.widget.Toast$TN$2.handleMessage (Toast.java:345)
}
}
}
@ -850,7 +864,6 @@ object Utils {
return null
}
fun scanView(view : View?, callback : (view : View) -> Unit) {
view ?: return
callback(view)