improve IDN domain support

This commit is contained in:
tateisu 2020-02-02 03:28:16 +09:00
parent 9b696c60f1
commit 23fb9cec43
45 changed files with 427 additions and 363 deletions

View File

@ -119,7 +119,6 @@ class ActAccountSetting
private lateinit var tvUserCustom : TextView
private lateinit var btnUserCustom : View
private lateinit var full_acct : String
private lateinit var btnNotificationSoundEdit : Button
private lateinit var btnNotificationSoundReset : Button
@ -173,13 +172,13 @@ class ActAccountSetting
finish()
return
}
this.account = a
loadUIFromData(account)
loadUIFromData(a)
initializeProfile()
btnOpenBrowser.text = getString(R.string.open_instance_website, account.host)
btnOpenBrowser.text = getString(R.string.open_instance_website, account.hostAscii)
}
override fun onStop() {
@ -443,11 +442,10 @@ class ActAccountSetting
}
private fun loadUIFromData(a : SavedAccount) {
this.account = a
this.full_acct = a.acct
tvInstance.text = a.host
tvUser.text = a.acct
tvInstance.text = a.hostPretty
tvUser.text = a.acctPretty
this.visibility = a.visibility
@ -518,9 +516,10 @@ class ActAccountSetting
}
private fun showAcctColor() {
val ac = AcctColor.load(full_acct)
val sa = this.account
val ac = AcctColor.load(sa.acctAscii)
tvUserCustom.backgroundColor = ac.color_bg
tvUserCustom.text = ac.nickname?.notEmpty() ?: full_acct
tvUserCustom.text = ac.nickname?.notEmpty() ?: sa.acctPretty
tvUserCustom.textColor = ac.color_fg.notZero()
?: getAttributeColor(this, R.attr.colorTimeSmall)
}
@ -584,13 +583,14 @@ class ActAccountSetting
R.id.btnVisibility -> performVisibility()
R.id.btnOpenBrowser -> App1.openBrowser(
this@ActAccountSetting,
"https://" + account.host + "/"
"https://" + account.hostAscii + "/"
)
R.id.btnPushSubscription -> startTest()
R.id.btnUserCustom -> ActNickname.open(
this,
full_acct,
account.acctAscii,
account.acctPretty,
false,
REQUEST_CODE_ACCT_CUSTOMIZE
)
@ -764,7 +764,7 @@ class ActAccountSetting
}
val call = App1.ok_http_client.newCall(
("instance_url=" + ("https://" + account.host).encodePercent()
("instance_url=" + ("https://" + account.hostAscii).encodePercent()
+ "&app_id=" + packageName.encodePercent()
+ "&tag=" + tag
)

View File

@ -913,14 +913,14 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
fun formatFontSize(fv : Float) : String =
when {
fv.isFinite() -> String.format(Locale.getDefault(), "%.1f", fv)
fv.isFinite() -> String.format(defaultLocale(this), "%.1f", fv)
else -> ""
}
fun parseFontSize(src : String) : Float {
try {
if(src.isNotEmpty()) {
val f = NumberFormat.getInstance(Locale.getDefault()).parse(src)?.toFloat()
val f = NumberFormat.getInstance(defaultLocale(this)).parse(src)?.toFloat()
return when {
f == null -> Float.NaN
f.isNaN() -> Float.NaN

View File

@ -351,7 +351,7 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
ivColumnBackground.alpha = column.column_bg_image_alpha
etAlpha.setText(
String.format(
Locale.getDefault(),
defaultLocale(this@ActColumnCustomize),
"%.4f",
column.column_bg_image_alpha
)
@ -379,7 +379,7 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
if(loading_busy) return
try {
var f = NumberFormat.getInstance(Locale.getDefault())
var f = NumberFormat.getInstance(defaultLocale(this@ActColumnCustomize))
.parse(etAlpha.text.toString())?.toFloat()
if(f != null && ! f.isNaN()) {
@ -426,7 +426,7 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
etAlpha.setText(
String.format(
Locale.getDefault(),
defaultLocale(this@ActColumnCustomize),
"%.4f",
column.column_bg_image_alpha
)

View File

@ -221,7 +221,7 @@ class ActKeywordFilter
private fun save() {
if(loading) return
val params = jsonObject{
val params = jsonObject {
put("phrase", etPhrase.text.toString())
@ -254,7 +254,7 @@ class ActKeywordFilter
filter_expire <= 0L -> {
}
// FIXME: currently there is no way to remove expires from existing filter.
else -> put("expires_in", Int.MAX_VALUE)
else -> put("expires_in", Int.MAX_VALUE)
}
// set seconds
@ -285,9 +285,7 @@ class ActKeywordFilter
} else {
val app_state = App1.prepare(applicationContext)
for(column in app_state.column_list) {
if(column.access_info.acct == account.acct
&& column.type == ColumnType.KEYWORD_FILTER
) {
if(column.type == ColumnType.KEYWORD_FILTER && column.access_info == account) {
column.filter_reload_required = true
}
}

View File

@ -124,7 +124,7 @@ class ActMain : AppCompatActivity()
// onActivityResultで設定されてonResumeで消化される
// 状態保存の必要なし
private var posted_acct : String? = null
private var posted_acct : String? = null // acctAscii
private var posted_status_id : EntityId? = null
private var posted_reply_id : EntityId? = null
private var posted_redraft_id : EntityId? = null
@ -401,7 +401,7 @@ class ActMain : AppCompatActivity()
break
}
// 既出でなければ追加する
if(null == accounts.find { it.acct == a.acct }) accounts.add(a)
if(null == accounts.find { it.acctAscii == a.acctAscii }) accounts.add(a)
} catch(ex : Throwable) {
}
@ -795,7 +795,7 @@ class ActMain : AppCompatActivity()
when {
column.access_info.isNA -> post_helper.setInstance(null, false)
else -> post_helper.setInstance(
column.access_info.host,
column.access_info.hostAscii,
column.access_info.isMisskey
)
}
@ -865,7 +865,7 @@ class ActMain : AppCompatActivity()
// 予約投稿なら予約投稿リストをリロードする
for(column in app_state.column_list) {
if(column.type == ColumnType.SCHEDULED_STATUS
&& column.access_info.acct == posted_acct
&& column.access_info.acctAscii == posted_acct
) {
column.startLoading()
}
@ -888,7 +888,7 @@ class ActMain : AppCompatActivity()
val refresh_after_toot = Pref.ipRefreshAfterToot(pref)
if(refresh_after_toot != Pref.RAT_DONT_REFRESH) {
for(column in app_state.column_list) {
if(column.access_info.acct != posted_acct) continue
if(column.access_info.acctAscii != posted_acct) continue
column.startRefreshForPost(
refresh_after_toot,
posted_status_id,
@ -967,7 +967,7 @@ class ActMain : AppCompatActivity()
post_helper.in_reply_to_id = null
post_helper.attachment_list = null
post_helper.emojiMapCustom =
App1.custom_emoji_lister.getMap(account.host, account.isMisskey)
App1.custom_emoji_lister.getMap(account.hostAscii, account.isMisskey)
etQuickToot.hideKeyboard()
@ -978,7 +978,7 @@ class ActMain : AppCompatActivity()
status : TootStatus
) {
etQuickToot.setText("")
posted_acct = target_account.acct
posted_acct = target_account.acctAscii
posted_status_id = status.id
posted_reply_id = status.in_reply_to_id
posted_redraft_id = null
@ -1600,7 +1600,7 @@ class ActMain : AppCompatActivity()
ivIcon.imageTintList = ColorStateList.valueOf(column.getHeaderNameColor())
//
val ac = AcctColor.load(column.access_info.acct)
val ac = AcctColor.load(column.access_info.acctAscii)
if(AcctColor.hasColorForeground(ac)) {
vAcctColor.setBackgroundColor(ac.color_fg)
} else {
@ -1829,7 +1829,7 @@ class ActMain : AppCompatActivity()
if(account != null) {
var column = app_state.column_list.firstOrNull {
it.type == ColumnType.NOTIFICATIONS
&& account.acct == it.access_info.acct
&& account.acctAscii == it.access_info.acctAscii
&& ! it.system_notification_not_related
}
if(column != null) {
@ -2029,7 +2029,7 @@ class ActMain : AppCompatActivity()
if(sa.username != ta.username) {
showToast(this@ActMain, true, R.string.user_name_not_match)
} else {
showToast(this@ActMain, false, R.string.access_token_updated_for, sa.acct)
showToast(this@ActMain, false, R.string.access_token_updated_for, sa.acctPretty)
// DBの情報を更新する
sa.updateTokenInfo(token_info)
@ -2039,7 +2039,7 @@ class ActMain : AppCompatActivity()
// 自動でリロードする
for(it in app_state.column_list) {
if(it.access_info.acct == sa.acct) {
if(it.access_info.acctAscii == sa.acctAscii) {
it.startLoading()
}
}
@ -2156,7 +2156,7 @@ class ActMain : AppCompatActivity()
null,
object : DlgTextInput.Callback {
override fun onOK(dialog : Dialog, text : String) {
checkAccessToken(null, dialog, sa.host, text, sa)
checkAccessToken(null, dialog, sa.hostAscii, text, sa)
}
override fun onEmptyError() {
@ -2180,7 +2180,7 @@ class ActMain : AppCompatActivity()
val done_list = ArrayList<SavedAccount>()
for(column in app_state.column_list) {
val a = column.access_info
if(a.acct != account.acct) continue
if(a.acctAscii != account.acctAscii) continue
if(done_list.contains(a)) continue
done_list.add(a)
if(! a.isNA) a.reloadSetting(this@ActMain)
@ -2361,7 +2361,7 @@ class ActMain : AppCompatActivity()
if(statusInfo != null) {
if(accessInfo.isNA ||
statusInfo.statusId == null ||
! statusInfo.host.equals(accessInfo.host, ignoreCase = true)
! statusInfo.host.equals(accessInfo.hostAscii, ignoreCase = true)
) {
Action_Toot.conversationOtherInstance(
this@ActMain,
@ -2385,7 +2385,7 @@ class ActMain : AppCompatActivity()
// opener.linkInfo をチェックしてメンションを判別する
val mention = opener.linkInfo?.mention
if(mention != null) {
val fullAcct = getFullAcctOrNull(accessInfo, mention.acct, mention.url)
val fullAcct = getFullAcctOrNull(accessInfo, mention.acctAscii, mention.url)
if(fullAcct != null) {
val (user, host) = fullAcct.splitFullAcct()
if(host != null) {
@ -2487,7 +2487,7 @@ class ActMain : AppCompatActivity()
fun showColumnMatchAccount(account : SavedAccount) {
for(column in app_state.column_list) {
if(account.acct == column.access_info.acct) {
if(account.acctAscii == column.access_info.acctAscii) {
column.fireRebindAdapterItems()
}
}

View File

@ -25,18 +25,21 @@ class ActNickname : AppCompatActivity(), View.OnClickListener, ColorPickerDialog
companion object {
internal const val EXTRA_ACCT = "acct"
internal const val EXTRA_ACCT_ASCII = "acctAscii"
internal const val EXTRA_ACCT_PRETTY = "acctPretty"
internal const val EXTRA_SHOW_NOTIFICATION_SOUND = "show_notification_sound"
internal const val REQUEST_CODE_NOTIFICATION_SOUND = 2
fun open(
activity : Activity,
full_acct : String,
fullAcctAscii : String,
fullAcctPretty: String,
bShowNotificationSound : Boolean,
requestCode : Int
) {
val intent = Intent(activity, ActNickname::class.java)
intent.putExtra(EXTRA_ACCT, full_acct)
intent.putExtra(EXTRA_ACCT_ASCII, fullAcctAscii)
intent.putExtra(EXTRA_ACCT_PRETTY, fullAcctPretty)
intent.putExtra(EXTRA_SHOW_NOTIFICATION_SOUND, bShowNotificationSound)
activity.startActivityForResult(intent, requestCode)
}
@ -44,7 +47,8 @@ class ActNickname : AppCompatActivity(), View.OnClickListener, ColorPickerDialog
}
private var show_notification_sound : Boolean = false
private lateinit var acct : String
private lateinit var acctAscii : String
private lateinit var acctPretty : String
private var color_fg : Int = 0
private var color_bg : Int = 0
private var notification_sound_uri : String? = null
@ -73,7 +77,8 @@ class ActNickname : AppCompatActivity(), View.OnClickListener, ColorPickerDialog
App1.setActivityTheme(this)
val intent = intent
this.acct = intent.getStringExtra(EXTRA_ACCT) !!
this.acctAscii = intent.getStringExtra(EXTRA_ACCT_ASCII) !!
this.acctPretty = intent.getStringExtra(EXTRA_ACCT_PRETTY) !!
this.show_notification_sound = intent.getBooleanExtra(EXTRA_SHOW_NOTIFICATION_SOUND, false)
initUI()
@ -148,9 +153,9 @@ class ActNickname : AppCompatActivity(), View.OnClickListener, ColorPickerDialog
findViewById<View>(R.id.llNotificationSound).visibility =
if(show_notification_sound) View.VISIBLE else View.GONE
tvAcct.text = acct
tvAcct.text = acctPretty
val ac = AcctColor.load(acct)
val ac = AcctColor.load(acctAscii)
color_bg = ac.color_bg
color_fg = ac.color_fg
etNickname.setText(if(ac.nickname == null) "" else ac.nickname)
@ -163,7 +168,7 @@ class ActNickname : AppCompatActivity(), View.OnClickListener, ColorPickerDialog
private fun save() {
if(bLoading) return
AcctColor(
acct,
acctAscii,
etNickname.text.toString().trim { it <= ' ' },
color_fg,
color_bg,
@ -173,7 +178,7 @@ class ActNickname : AppCompatActivity(), View.OnClickListener, ColorPickerDialog
private fun show() {
val s = etNickname.text.toString().trim { it <= ' ' }
tvPreview.text = s.notEmpty() ?: acct
tvPreview.text = s.notEmpty() ?: acctPretty
tvPreview.textColor = color_fg.notZero() ?: getAttributeColor(this, R.attr.colorTimeSmall)
tvPreview.backgroundColor = color_bg
}

View File

@ -507,7 +507,7 @@ class ActPost : AppCompatActivity(),
R.id.btnPlugin -> openMushroom()
R.id.btnEmojiPicker -> post_helper.openEmojiPickerFromMore()
R.id.btnFeaturedTag -> post_helper.openFeaturedTagList(
featuredTagCache[account?.acct ?: ""]?.list
featuredTagCache[account?.acctAscii ?: ""]?.list
)
R.id.ibSchedule -> performSchedule()
R.id.ibScheduleReset -> resetSchedule()
@ -774,7 +774,7 @@ class ActPost : AppCompatActivity(),
// 元レスに含まれていたメンションを複製
reply_status.mentions?.forEach { mention ->
val who_acct = mention.acct
val who_acct = mention.acctAscii
// 空データなら追加しない
if(who_acct.isEmpty()) return@forEach
// 自分なら追加しない
@ -783,6 +783,12 @@ class ActPost : AppCompatActivity(),
// 既出なら追加しない
if(mention_list.contains(strMention)) return@forEach
mention_list.add(strMention)
/*
FIXME インスタンスのバージョンが3.1.0 以降ならメンションのホスト部分にIDNドメインを使いたいが
投稿画面でのアカウント切り替え時にタンスのバージョンが異なると破綻する可能性が高い
*/
}
if(mention_list.isNotEmpty()) {
@ -1299,7 +1305,7 @@ class ActPost : AppCompatActivity(),
else -> {
// インスタンス情報を確認する
val info = TootInstance.getCached(account.host)
val info = TootInstance.getCached(account.hostAscii)
if(info == null || info.isExpire) {
// 情報がないか古いなら再取得
@ -1390,6 +1396,7 @@ class ActPost : AppCompatActivity(),
val time : Long
)
// key is SavedfAccount.acctAscii
private val featuredTagCache = ConcurrentHashMap<String, FeaturedTagCache>()
private var lastFeaturedTagTask : TootTaskRunner? = null
@ -1401,7 +1408,7 @@ class ActPost : AppCompatActivity(),
return
}
val cache = featuredTagCache[account.acct]
val cache = featuredTagCache[account.acctAscii]
btnFeaturedTag.vg(cache?.list?.isNotEmpty() == true)
@ -1417,13 +1424,13 @@ class ActPost : AppCompatActivity(),
override fun background(client : TootApiClient) : TootApiResult? {
return if(account.isMisskey) {
featuredTagCache[account.acct] =
featuredTagCache[account.acctAscii] =
FeaturedTagCache(emptyList(), SystemClock.elapsedRealtime())
TootApiResult()
} else {
client.request("/api/v1/featured_tags")?.also { result ->
val list = parseList(::TootTag, result.jsonArray)
featuredTagCache[account.acct] =
featuredTagCache[account.acctAscii] =
FeaturedTagCache(list, SystemClock.elapsedRealtime())
}
}
@ -1449,16 +1456,15 @@ class ActPost : AppCompatActivity(),
btnAccount.setTextColor(getAttributeColor(this, android.R.attr.textColorPrimary))
btnAccount.setBackgroundResource(R.drawable.btn_bg_transparent)
} else {
post_helper.setInstance(a.host, a.isMisskey)
post_helper.setInstance(a.hostAscii, a.isMisskey)
// 先読みしてキャッシュに保持しておく
App1.custom_emoji_lister.getList(a.host, a.isMisskey) {
App1.custom_emoji_lister.getList(a.hostAscii, a.isMisskey) {
// 何もしない
}
val acct = a.acct
val ac = AcctColor.load(acct)
val nickname = if(AcctColor.hasNickname(ac)) ac.nickname else acct
val ac = AcctColor.load(a.acctAscii)
val nickname = if(AcctColor.hasNickname(ac)) ac.nickname else a.acctPretty
btnAccount.text = nickname
if(AcctColor.hasColorBackground(ac)) {
@ -1502,7 +1508,7 @@ class ActPost : AppCompatActivity(),
) { ai ->
// 別タンスのアカウントに変更したならならin_reply_toの変換が必要
if(in_reply_to_id != null && ! ai.host.equals(account?.host, ignoreCase = true)) {
if(in_reply_to_id != null && ! ai.matchHost(account?.hostAscii) ) {
startReplyConversion(ai)
} else {
setAccountWithVisibilityConversion(ai)
@ -2031,7 +2037,7 @@ class ActPost : AppCompatActivity(),
return
}
val instance = TootInstance.getCached(account.host)
val instance = TootInstance.getCached(account.hostAscii)
if(instance?.instanceType == TootInstance.InstanceType.Pixelfed) {
if(in_reply_to_id != null) {
showToast(this, true, R.string.pixelfed_does_not_allow_reply_with_media)
@ -2569,7 +2575,7 @@ class ActPost : AppCompatActivity(),
post_helper.attachment_list = this.attachment_list
post_helper.emojiMapCustom =
App1.custom_emoji_lister.getMap(account.host, account.isMisskey)
App1.custom_emoji_lister.getMap(account.hostAscii, account.isMisskey)
post_helper.redraft_status_id = redraft_status_id
@ -2585,7 +2591,7 @@ class ActPost : AppCompatActivity(),
status : TootStatus
) {
val data = Intent()
data.putExtra(EXTRA_POSTED_ACCT, target_account.acct)
data.putExtra(EXTRA_POSTED_ACCT, target_account.acctAscii)
status.id.putTo(data, EXTRA_POSTED_STATUS_ID)
redraft_status_id?.putTo(data, EXTRA_POSTED_REDRAFT_ID)
status.in_reply_to_id?.putTo(data, EXTRA_POSTED_REPLY_ID)
@ -2597,7 +2603,7 @@ class ActPost : AppCompatActivity(),
override fun onScheduledPostComplete(target_account : SavedAccount) {
showToast(this@ActPost, false, getString(R.string.scheduled_status_sent))
val data = Intent()
data.putExtra(EXTRA_POSTED_ACCT, target_account.acct)
data.putExtra(EXTRA_POSTED_ACCT, target_account.acctAscii)
setResult(RESULT_OK, data)
isPostComplete = true
this@ActPost.finish()

View File

@ -301,47 +301,47 @@ class AppState(internal val context : Context, internal val pref : SharedPrefere
}
fun isBusyFav(account : SavedAccount, status : TootStatus) : Boolean {
val key = account.acct + ":" + status.busyKey
val key = account.acctAscii + ":" + status.busyKey
return map_busy_fav.contains(key)
}
fun setBusyFav(account : SavedAccount, status : TootStatus) {
val key = account.acct + ":" + status.busyKey
val key = account.acctAscii + ":" + status.busyKey
map_busy_fav.add(key)
}
fun resetBusyFav(account : SavedAccount, status : TootStatus) {
val key = account.acct + ":" + status.busyKey
val key = account.acctAscii + ":" + status.busyKey
map_busy_fav.remove(key)
}
fun isBusyBookmark(account : SavedAccount, status : TootStatus) : Boolean {
val key = account.acct + ":" + status.busyKey
val key = account.acctAscii + ":" + status.busyKey
return map_busy_bookmark.contains(key)
}
fun setBusyBookmark(account : SavedAccount, status : TootStatus) {
val key = account.acct + ":" + status.busyKey
val key = account.acctAscii + ":" + status.busyKey
map_busy_bookmark.add(key)
}
fun resetBusyBookmark(account : SavedAccount, status : TootStatus) {
val key = account.acct + ":" + status.busyKey
val key = account.acctAscii + ":" + status.busyKey
map_busy_bookmark.remove(key)
}
fun isBusyBoost(account : SavedAccount, status : TootStatus) : Boolean {
val key = account.acct + ":" + status.busyKey
val key = account.acctAscii + ":" + status.busyKey
return map_busy_boost.contains(key)
}
fun setBusyBoost(account : SavedAccount, status : TootStatus) {
val key = account.acct + ":" + status.busyKey
val key = account.acctAscii + ":" + status.busyKey
map_busy_boost.add(key)
}
fun resetBusyBoost(account : SavedAccount, status : TootStatus) {
val key = account.acct + ":" + status.busyKey
val key = account.acctAscii + ":" + status.busyKey
map_busy_boost.remove(key)
}
@ -400,7 +400,7 @@ class AppState(internal val context : Context, internal val pref : SharedPrefere
if(voice_set == null || voice_set.isEmpty()) {
log.d("TextToSpeech.getVoices returns null or empty set.")
} else {
val lang = Locale.getDefault().toLanguageTag()
val lang = defaultLocale(context).toLanguageTag()
for(v in voice_set) {
log.d(
"Voice %s %s %s",

View File

@ -263,10 +263,9 @@ class Column(
override fun handleResult(result : TootApiResult?) {
val filter_list = this.filter_list
if(filter_list != null) {
val stream_acct = access_info.acct
log.d("update filters for $stream_acct")
log.d("update filters for ${access_info.acctAscii}")
for(column in App1.getAppState(context).column_list) {
if(column.access_info.acct == stream_acct) {
if(column.access_info == access_info) {
column.onFiltersChanged2(filter_list)
}
}
@ -361,8 +360,7 @@ class Column(
val type = ColumnType.parse(typeId)
fun getIconId() : Int =
type.iconId(access_info.acct)
fun getIconId() : Int = type.iconId(access_info.acctAscii)
fun getColumnName(long : Boolean) =
type.name2(this, long) ?: type.name1(context)
@ -903,8 +901,8 @@ class Column(
}
// 以下は保存には必要ないが、カラムリスト画面で使う
val ac = AcctColor.load(access_info.acct)
dst[KEY_COLUMN_ACCESS] = if(AcctColor.hasNickname(ac)) ac.nickname else access_info.acct
val ac = AcctColor.load(access_info.acctAscii)
dst[KEY_COLUMN_ACCESS] = if(AcctColor.hasNickname(ac)) ac.nickname else access_info.acctPretty
dst[KEY_COLUMN_ACCESS_COLOR] = ac.color_fg
dst[KEY_COLUMN_ACCESS_COLOR_BG] = ac.color_bg
dst[KEY_COLUMN_NAME] = getColumnName(true)
@ -916,7 +914,7 @@ class Column(
type : ColumnType,
params : Array<out Any>
) : Boolean {
if(type != this.type || ai.acct != access_info.acct) return false
if(type != this.type || ai != access_info) return false
return try {
when(type) {
@ -1038,7 +1036,7 @@ class Column(
callback : (account : SavedAccount, status : TootStatus) -> Boolean
// callback return true if rebind view required
) {
if(! access_info.host.equals(target_instance, ignoreCase = true)) return
if(! access_info.matchHost(target_instance)) return
var bChanged = false
@ -1068,7 +1066,7 @@ class Column(
who_id : EntityId,
removeFromUserList : Boolean = false
) {
if(target_account.acct != access_info.acct) return
if(target_account != access_info) return
val INVALID_ACCOUNT = - 1L
@ -1121,13 +1119,13 @@ class Column(
// misskeyカラムやプロフカラムでブロック成功した時に呼ばれる
fun updateFollowIcons(target_account : SavedAccount) {
if(target_account.acct != access_info.acct) return
if(target_account != access_info) return
fireShowContent(reason = "updateFollowIcons", reset = true)
}
fun removeUser(targetAccount : SavedAccount, columnType : ColumnType, who_id : EntityId) {
if(type == columnType && targetAccount.acct == access_info.acct) {
if(type == columnType && targetAccount == access_info ) {
val tmp_list = ArrayList<TimelineItem>(list_data.size)
for(o in list_data) {
if(o is TootAccountRef) {
@ -1148,7 +1146,7 @@ class Column(
if(is_dispose.get() || bInitialLoading || bRefreshLoading) return
if(tl_host.equals(access_info.host, ignoreCase = true)) {
if(tl_host.equals(access_info.hostAscii, ignoreCase = true)) {
val tmp_list = ArrayList<TimelineItem>(list_data.size)
for(o in list_data) {
if(o is TootStatus) {
@ -1203,7 +1201,7 @@ class Column(
fun removeNotificationOne(target_account : SavedAccount, notification : TootNotification) {
if(! isNotificationColumn) return
if(access_info.acct != target_account.acct) return
if(access_info != target_account) return
val tmp_list = ArrayList<TimelineItem>(list_data.size)
for(o in list_data) {
@ -1268,7 +1266,7 @@ class Column(
val domain = IDN.toASCII(domainArg,IDN.ALLOW_UNASSIGNED)
if(target_account.host != access_info.host) return
if(target_account.hostAscii != access_info.hostAscii) return
if(access_info.isPseudo) return
if(type == ColumnType.DOMAIN_BLOCKS) {
@ -1281,7 +1279,7 @@ class Column(
// ブロックしたのとドメイン部分が一致するアカウントからのステータスと通知をすべて除去する
val reDomain = Pattern.compile("\\A[^@]+@\\Q$domain\\E\\z", Pattern.CASE_INSENSITIVE)
val checker =
{ account : TootAccount? -> if(account == null ) false else reDomain.matcher(account.acct).find() }
{ account : TootAccount? -> if(account == null ) false else reDomain.matcher(account.acctAscii).find() }
val tmp_list = ArrayList<TimelineItem>(list_data.size)
@ -1307,7 +1305,7 @@ class Column(
}
fun onListListUpdated(account : SavedAccount) {
if(type == ColumnType.LIST_LIST && access_info.acct == account.acct) {
if(type == ColumnType.LIST_LIST && access_info == account) {
startLoading()
val vh = viewHolder
vh?.onListListUpdated()
@ -1315,7 +1313,7 @@ class Column(
}
fun onListNameUpdated(account : SavedAccount, item : TootList) {
if(access_info.acct != account.acct) return
if(access_info != account) return
when(type) {
ColumnType.LIST_LIST -> {
startLoading()
@ -1340,11 +1338,11 @@ class Column(
who : TootAccount,
bAdd : Boolean
) {
if(type == ColumnType.LIST_TL && access_info.acct == account.acct && list_id == profile_id) {
if(type == ColumnType.LIST_TL && access_info == account && list_id == profile_id) {
if(! bAdd) {
removeAccountInTimeline(account, who.id)
}
} else if(type == ColumnType.LIST_MEMBER && access_info.acct == account.acct && list_id == profile_id) {
} else if(type == ColumnType.LIST_MEMBER && access_info == account && list_id == profile_id) {
if(! bAdd) {
removeAccountInTimeline(account, who.id)
}

View File

@ -32,7 +32,7 @@ private val unsupportedRefresh : ColumnTask_Refresh.(client : TootApiClient) ->
private val unsupportedGap : ColumnTask_Gap.(client : TootApiClient) -> TootApiResult? =
{ TootApiResult("gap reading not supported.") }
private val unusedIconId : (acct : String) -> Int =
private val unusedIconId : (String) -> Int =
{ R.drawable.ic_question }
private val unusedName : (context : Context) -> String =
@ -43,7 +43,7 @@ private val unusedName2 : Column.(long : Boolean) -> String? =
enum class ColumnType(
val id : Int = 0,
val iconId : (acct : String) -> Int = unusedIconId,
val iconId : (acct:String) -> Int = unusedIconId,
val name1 : (context : Context) -> String = unusedName,
val name2 : Column.(long : Boolean) -> String? = unusedName2,
val loading : ColumnTask_Loading.(client : TootApiClient) -> TootApiResult?,

View File

@ -1171,15 +1171,13 @@ class ColumnViewHolder(
val column = this.column
if(column == null || column.is_dispose.get()) return@Runnable
val acct = column.access_info.acct
val ac = AcctColor.load(acct)
val ac = AcctColor.load(column.access_info.acctAscii)
val nickname = ac.nickname
tvColumnContext.text = if(nickname != null && nickname.isNotEmpty())
nickname
else
column.access_info.prettyAcct
column.access_info.acctPretty
tvColumnContext.setTextColor(
ac.color_fg.notZero()
@ -2640,7 +2638,7 @@ class ColumnViewHolder(
private fun addReaction(item : TootAnnouncement, sample : TootAnnouncement.Reaction?) {
val column = column ?: return
val host = column.access_info.host
val host = column.access_info.hostAscii
val isMisskey = column.isMisskey
if(sample == null) {
EmojiPicker(activity, host, isMisskey) { name, _, _, unicode, customEmoji ->

View File

@ -412,7 +412,7 @@ internal class DlgContextMenu(
// 疑似アカウントではドメインブロックできない
// 自ドメインはブロックできない
btnDomainBlock.vg(
! (access_info.isPseudo || access_info.host.equals(who_host, ignoreCase = true))
! (access_info.isPseudo || access_info.matchHost(who_host))
)
btnDomainTimeline.vg(
@ -525,9 +525,9 @@ internal class DlgContextMenu(
}
private fun getUserHost() : String {
return when(val who_host = whoRef?.get()?.host) {
return when(val who_host = whoRef?.get()?.hostAscii) {
"?" -> column.instance_uri
null, "" -> access_info.host
null, "" -> access_info.hostAscii
else -> who_host
}
}
@ -766,6 +766,7 @@ internal class DlgContextMenu(
ActNickname.open(
activity,
access_info.getFullAcct(who),
access_info.getFullPrettyAcct(who),
true,
ActMain.REQUEST_CODE_NICKNAME
)
@ -783,10 +784,10 @@ internal class DlgContextMenu(
showToast(activity, false, R.string.domain_block_from_pseudo)
return
} else {
val who_host = who.host
val who_host = who.hostAscii
// 自分のドメインではブロックできない
if(access_info.host.equals(who_host, ignoreCase = true)) {
if(access_info.matchHost(who_host)) {
showToast(activity, false, R.string.domain_block_from_local)
return
}
@ -800,7 +801,7 @@ internal class DlgContextMenu(
}
R.id.btnOpenTimeline -> {
val who_host = who.host
val who_host = who.hostAscii
@Suppress("ControlFlowWithEmptyBody")
if(who_host.isEmpty() || who_host == "?") {
// 何もしない
@ -810,7 +811,7 @@ internal class DlgContextMenu(
}
R.id.btnDomainTimeline -> {
val who_host = who.host
val who_host = who.hostAscii
@Suppress("ControlFlowWithEmptyBody")
if(who_host.isEmpty() || who_host == "?") {
// 何もしない
@ -885,7 +886,7 @@ internal class DlgContextMenu(
activity,
access_info,
pos,
who.host,
who.hostAscii,
status,
ColumnType.ACCOUNT_AROUND
, allowPseudo = false
@ -895,7 +896,7 @@ internal class DlgContextMenu(
activity,
access_info,
pos,
who.host,
who.hostAscii,
status,
ColumnType.LOCAL_AROUND
)
@ -904,7 +905,7 @@ internal class DlgContextMenu(
activity,
access_info,
pos,
who.host,
who.hostAscii,
status,
ColumnType.FEDERATED_AROUND
)
@ -914,13 +915,13 @@ internal class DlgContextMenu(
R.id.btnOpenAccountInAdminWebUi ->
App1.openBrowser(
activity,
"https://${access_info.host}/admin/accounts/${who.id}"
"https://${access_info.hostAscii}/admin/accounts/${who.id}"
)
R.id.btnOpenInstanceInAdminWebUi ->
App1.openBrowser(
activity,
"https://${access_info.host}/admin/instances/${who.host}"
"https://${access_info.hostAscii}/admin/instances/${who.hostAscii}"
)
R.id.btnBoostWithVisibility -> {

View File

@ -1093,7 +1093,7 @@ internal class ItemViewHolder(
} else {
val m = reply.mentions?.find { it.id == accountId }
if(m != null) {
AcctColor.getNicknameWithColor(access_info,m.acct)
AcctColor.getNicknameWithColor(access_info,m.acctAscii)
} else {
SpannableString("ID(${accountId})")
}
@ -1409,12 +1409,12 @@ internal class ItemViewHolder(
try {
if(! Column.useInstanceTicker) return
val host = who.host
val host = who.hostAscii
// LTLでホスト名が同じならTickerを表示しない
when(column.type) {
ColumnType.LOCAL, ColumnType.LOCAL_AROUND -> {
if(host == access_info.host) return
if(host == access_info.hostAscii) return
}
else -> {
@ -2142,7 +2142,7 @@ internal class ItemViewHolder(
is TootTag -> {
// search_tag は#を含まない
val tagEncoded = item.name.encodePercent()
val host = access_info.host
val host = access_info.hostAscii
val url = "https://$host/tags/$tagEncoded"
Action_HashTag.timelineOtherInstance(
activity = activity,
@ -2469,7 +2469,7 @@ internal class ItemViewHolder(
setPadding(paddingH, paddingV, paddingH, paddingV)
val emoji = status.custom_emojis?.get(customCode)
?: App1.custom_emoji_lister.getMap(access_info.host, true)
?: App1.custom_emoji_lister.getMap(access_info.hostAscii, true)
?.get(customCode)
val emojiUrl = emoji?.let {

View File

@ -991,7 +991,7 @@ class PollingWorker private constructor(contextArg : Context) {
for(t in thread_list) {
if(! t.isAlive) continue
if(job.isJobCancelled) t.cancel()
liveSet.add(t.account.host)
liveSet.add(t.account.hostAscii)
}
if(liveSet.isEmpty()) break
@ -1076,7 +1076,7 @@ class PollingWorker private constructor(contextArg : Context) {
val wps_log = wps.log
if(wps_log.isNotEmpty())
log.d("PushSubscriptionHelper: ${account.acct} $wps_log")
log.d("PushSubscriptionHelper: ${account.acctPretty} $wps_log")
if(job.isJobCancelled) return
@ -1243,14 +1243,14 @@ class PollingWorker private constructor(contextArg : Context) {
val dataList = dstListData
val first = dataList.firstOrNull()
if(first == null) {
log.d("showNotification[${account.acct}/$notification_tag] cancel notification.")
log.d("showNotification[${account.acctPretty}/$notification_tag] cancel notification.")
if(Build.VERSION.SDK_INT >= 23 && Pref.bpDivideNotification(pref)) {
notification_manager.activeNotifications?.forEach {
if(it != null &&
it.id == NOTIFICATION_ID &&
it.tag.startsWith("$notification_tag/")
) {
log.d("cancel: ${it.tag} context=${account.acct} $notification_tag")
log.d("cancel: ${it.tag} context=${account.acctPretty} $notification_tag")
notification_manager.cancel(it.tag, NOTIFICATION_ID)
}
}
@ -1267,7 +1267,7 @@ class PollingWorker private constructor(contextArg : Context) {
) {
// 先頭にあるデータが同じなら、通知を更新しない
// このマーカーは端末再起動時にリセットされるので、再起動後は通知が出るはず
log.d("showNotification[${account.acct}] id=${first.notification.id} is already shown.")
log.d("showNotification[${account.acctPretty}] id=${first.notification.id} is already shown.")
return
}
@ -1302,8 +1302,6 @@ class PollingWorker private constructor(contextArg : Context) {
createNotification(itemTag,notificationId = item.notification.id.toString()) { builder ->
val myAcct = item.access_info.acct
builder.setWhen(item.notification.time_created_at)
val summary = getNotificationLine(item)
@ -1313,11 +1311,11 @@ class PollingWorker private constructor(contextArg : Context) {
builder.setStyle(
NotificationCompat.BigTextStyle()
.setBigContentTitle(summary)
.setSummaryText(myAcct)
.setSummaryText(item.access_info.acctPretty)
.bigText(content)
)
} else {
builder.setContentText(myAcct)
builder.setContentText(item.access_info.acctPretty)
}
if(Build.VERSION.SDK_INT < 26) {
@ -1369,18 +1367,16 @@ class PollingWorker private constructor(contextArg : Context) {
}
// リストにない通知は消さない。ある通知をユーザが指で削除した際に他の通知が残ってほしい場合がある
} else {
log.d("showNotification[${account.acct}] creating notification(1)")
log.d("showNotification[${account.acctPretty}] creating notification(1)")
createNotification(notification_tag) { builder ->
builder.setWhen(first.notification.time_created_at)
var a = getNotificationLine(first)
val myAcct = account.acct
if(dataList.size == 1) {
builder.setContentTitle(a)
builder.setContentText(myAcct)
builder.setContentText( account.acctPretty)
} else {
val header =
context.getString(R.string.notification_count, dataList.size)
@ -1389,7 +1385,7 @@ class PollingWorker private constructor(contextArg : Context) {
val style = NotificationCompat.InboxStyle()
.setBigContentTitle(header)
.setSummaryText(myAcct)
.setSummaryText( account.acctPretty)
for(i in 0 .. 4) {
if(i >= dataList.size) break
val item = dataList[i]
@ -1454,7 +1450,7 @@ class PollingWorker private constructor(contextArg : Context) {
notificationId : String? = null,
setContent : (builder : NotificationCompat.Builder) -> Unit
) {
log.d("showNotification[${account.acct}] creating notification(1)")
log.d("showNotification[${account.acctPretty}] creating notification(1)")
val builder = if(Build.VERSION.SDK_INT >= 26) {
// Android 8 から、通知のスタイルはユーザが管理することになった
@ -1523,15 +1519,15 @@ class PollingWorker private constructor(contextArg : Context) {
// Android 7.0 ではグループを指定しないと勝手に通知が束ねられてしまう。
// 束ねられた通知をタップしても pi_click が実行されないので困るため、
// アカウント別にグループキーを設定する
setGroup(context.packageName + ":" + account.acct)
setGroup(context.packageName + ":" + account.acctAscii)
}
log.d("showNotification[${account.acct}] creating notification(3)")
log.d("showNotification[${account.acctPretty}] creating notification(3)")
setContent(builder)
log.d("showNotification[${account.acct}] set notification...")
log.d("showNotification[${account.acctPretty}] set notification...")
notification_manager.notify(notification_tag, NOTIFICATION_ID, builder.build())
}

View File

@ -158,7 +158,7 @@ internal class StreamReader(
private fun fireDeleteId(id : EntityId) {
val tl_host = access_info.host
val tl_host = access_info.hostAscii
runOnMainLooper {
synchronized(this) {
if(bDisposed.get()) return@synchronized

View File

@ -113,7 +113,7 @@ internal class ViewHolderHeaderInstance(
btnEmail.isEnabled = email.isNotEmpty()
val contact_acct =
instance.contact_account?.let { who -> "@" + who.username + "@" + who.host } ?: ""
instance.contact_account?.let { who -> "@" + who.username + "@" + who.hostAscii } ?: ""
btnContact.text = contact_acct
btnContact.isEnabled = contact_acct.isNotEmpty()
@ -218,7 +218,7 @@ internal class ViewHolderHeaderInstance(
, activity.nextPosition(column)
, ColumnType.SEARCH
, args = arrayOf("@" + who.username + "@" + who.host, true)
, args = arrayOf("@" + who.username + "@" + who.hostAscii, true)
)
}

View File

@ -99,10 +99,11 @@ fun makeAccountListNonPseudo(
val list_other_host = ArrayList<SavedAccount>()
for(a in SavedAccount.loadAccountList(context)) {
if(a.isPseudo) continue
(if(pickup_host == null || pickup_host.equals(
a.host,
ignoreCase = true
)) list_same_host else list_other_host).add(a)
(if(pickup_host == null || pickup_host.equals(a.hostAscii, ignoreCase = true) || pickup_host.equals(a.hostPretty, ignoreCase = true))
list_same_host
else
list_other_host
).add(a)
}
SavedAccount.sort(list_same_host)
SavedAccount.sort(list_other_host)
@ -154,12 +155,8 @@ const val CROSS_ACCOUNT_REMOTE_INSTANCE = 3
internal fun calcCrossAccountMode(
timeline_account : SavedAccount,
action_account : SavedAccount
) : Int {
return if(! timeline_account.host.equals(action_account.host, ignoreCase = true)) {
CROSS_ACCOUNT_REMOTE_INSTANCE
} else if(! timeline_account.acct.equals(action_account.acct, ignoreCase = true)) {
CROSS_ACCOUNT_SAME_INSTANCE
} else {
NOT_CROSS_ACCOUNT
}
) : Int =when{
timeline_account == action_account -> NOT_CROSS_ACCOUNT
timeline_account.matchHost(action_account.hostAscii) -> CROSS_ACCOUNT_SAME_INSTANCE
else -> CROSS_ACCOUNT_REMOTE_INSTANCE
}

View File

@ -240,7 +240,7 @@ object Action_Account {
}
ColumnType.PROFILE_DIRECTORY -> {
activity.addColumn(pos, ai, type, ai.host)
activity.addColumn(pos, ai, type, ai.hostAscii)
}
else -> activity.addColumn(pos, ai, type, *args)

View File

@ -47,7 +47,7 @@ object Action_Filter {
if( filterList != null) {
showToast(activity, false, R.string.delete_succeeded)
for(column in App1.getAppState(activity).column_list) {
if( column.access_info.acct == access_info.acct){
if( column.access_info == access_info ){
column.onFilterDeleted(filter,filterList)
}
}

View File

@ -11,7 +11,6 @@ import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.table.UserRelation
import jp.juggler.subwaytooter.util.EmptyCallback
import jp.juggler.util.*
import java.net.IDN
object Action_Follow {
@ -193,12 +192,12 @@ object Action_Follow {
// 検索APIを呼び出さないようにする
val result = client.request("/api/v1/accounts/${userId}")
?: return null
who.acct == parser.account(result.jsonObject)?.acct
who.acctAscii == parser.account(result.jsonObject)?.acctAscii
}
if(! skipAccountSync) {
// 同タンスのIDではなかった場合、検索APIを使う
val (result, ar) = client.syncAccountByAcct(access_info, who.acct)
val (result, ar) = client.syncAccountByAcct(access_info, who.acctAscii)
val user = ar?.get() ?: return result
userId = user.id
}
@ -346,7 +345,7 @@ object Action_Follow {
// リモートユーザの同期
if(who.isRemote) {
val (result, ar) = client.syncAccountByAcct(access_info, who.acct)
val (result, ar) = client.syncAccountByAcct(access_info, who.acctAscii)
val user = ar?.get() ?: return result
userId = user.id
}
@ -573,7 +572,7 @@ object Action_Follow {
activity,
bAuto = false,
message = activity.getString(R.string.account_picker_follow),
accountListArg = makeAccountListNonPseudo(activity, account.host)
accountListArg = makeAccountListNonPseudo(activity, account.hostAscii)
) { ai ->
followRemote(
activity,
@ -643,7 +642,7 @@ object Action_Follow {
column.removeUser(access_info, ColumnType.FOLLOW_REQUESTS, who.id)
// 他のカラムでもフォロー状態の表示更新が必要
if(column.access_info.acct == access_info.acct
if(column.access_info == access_info
&& column.type != ColumnType.FOLLOW_REQUESTS
) {
column.fireRebindAdapterItems()

View File

@ -122,22 +122,34 @@ object Action_HashTag {
val list_other = ArrayList<SavedAccount>()
for(a in account_list) {
if( acctAscii == null){
if(! host.equals(a.host, ignoreCase = true)) {
list_other.add(a)
} else if(a.isPseudo) {
list_original_pseudo.add(a)
} else {
list_original.add(a)
when {
! host.equals(a.hostAscii, ignoreCase = true) -> {
list_other.add(a)
}
a.isPseudo -> {
list_original_pseudo.add(a)
}
else -> {
list_original.add(a)
}
}
}else{
if(a.isPseudo) {
when {
// acctからidを取得できない
}else if(a.isMisskey) {
a.isPseudo -> {
}
// ミスキーのアカウント別タグTLは未対応
}else if(! host.equals(a.host, ignoreCase = true)) {
list_other.add(a)
} else {
list_original.add(a)
a.isMisskey -> {
}
! host.equals(a.hostAscii, ignoreCase = true) -> {
list_other.add(a)
}
else -> {
list_original.add(a)
}
}
}
}
@ -163,8 +175,8 @@ object Action_HashTag {
AcctColor.getStringWithNickname(
activity,
R.string.open_in_account,
a.acct,
a.prettyAcct
a.acctAscii,
a.acctPretty
)
)
{ timeline(activity, pos, a, tag_without_sharp,acctAscii) }
@ -174,8 +186,8 @@ object Action_HashTag {
AcctColor.getStringWithNickname(
activity,
R.string.open_in_account,
a.acct,
a.prettyAcct
a.acctAscii,
a.acctPretty
)
)
{ timeline(activity, pos, a, tag_without_sharp,acctAscii) }
@ -185,8 +197,8 @@ object Action_HashTag {
AcctColor.getStringWithNickname(
activity,
R.string.open_in_account,
a.acct,
a.prettyAcct
a.acctAscii,
a.acctPretty
)
)
{ timeline(activity, pos, a, tag_without_sharp,acctAscii) }

View File

@ -48,7 +48,7 @@ object Action_Instance {
App1.openBrowser(activity, "https://$host/explore")
// ホスト名部分が一致するならそのアカウントで開く
accessInfo.host == host ->
accessInfo.hostAscii == host ->
activity.addColumn(
false,
pos,
@ -83,7 +83,7 @@ object Action_Instance {
ColumnType.PROFILE_DIRECTORY.name1(activity)
)
) { ai ->
profileDirectory(activity, ai, ai.host)
profileDirectory(activity, ai, ai.hostAscii)
}
}
@ -134,7 +134,7 @@ object Action_Instance {
// 指定タンスのアカウントを持ってるか?
val account_list = ArrayList<SavedAccount>()
for(a in SavedAccount.loadAccountList(activity)) {
if(host.equals(a.host, ignoreCase = true)) account_list.add(a)
if(host.equals(a.hostAscii, ignoreCase = true)) account_list.add(a)
}
if(account_list.isEmpty()) {
// 持ってないなら疑似アカウントを追加する
@ -159,7 +159,7 @@ object Action_Instance {
activity : ActMain, access_info : SavedAccount, domain : String, bBlock : Boolean
) {
if(access_info.host.equals(domain, ignoreCase = true)) {
if(access_info.matchHost(domain)) {
showToast(activity, false, R.string.it_is_you)
return
}
@ -260,7 +260,7 @@ object Action_Instance {
a.isMisskey -> continue@label
// 閲覧アカウントとホスト名が同じならステータスIDの変換が必要ない
a.host.equals(access_info.host, ignoreCase = true) -> {
a.matchHost(access_info.hostAscii) -> {
if(! allowPseudo && a.isPseudo) continue@label
account_list1.add(a)
}
@ -282,7 +282,7 @@ object Action_Instance {
message = "select account to read timeline",
accountListArg = account_list1
) { ai ->
if(! ai.isNA && ai.host.equals(access_info.host, ignoreCase = true)) {
if(! ai.isNA && ai.matchHost(access_info.hostAscii)) {
timelinePublicAround2(activity, ai, pos, status.id, type)
} else {
timelinePublicAround3(activity, ai, pos, status, type)

View File

@ -60,7 +60,7 @@ object Action_ListMember {
// リモートユーザの解決
if(! access_info.isLocalUser(local_who)) {
val (r2, ar) = client.syncAccountByAcct(access_info, local_who.acct)
val (r2, ar) = client.syncAccountByAcct(access_info, local_who.acctAscii)
val user = ar?.get() ?: return r2
userId = user.id
}

View File

@ -46,7 +46,7 @@ object Action_Notification {
if(result.jsonObject != null) {
// ok. api have return empty object.
for(column in App1.getAppState(activity).column_list) {
if(column.isNotificationColumn && column.access_info.acct == target_account.acct) {
if(column.isNotificationColumn && column.access_info == target_account ) {
column.removeNotifications()
}
}

View File

@ -32,7 +32,7 @@ object Action_Toot {
status : TootStatus?
) {
if(status == null) return
val who_host = timeline_account.host
val who_host = timeline_account.hostAscii
AccountPicker.pick(
activity,
@ -194,13 +194,13 @@ object Action_Toot {
}
for(column in App1.getAppState(activity).column_list) {
column.findStatus(access_info.host, new_status.id) { account, status ->
column.findStatus(access_info.hostAscii, new_status.id) { account, status ->
// 同タンス別アカウントでもカウントは変化する
status.favourites_count = new_status.favourites_count
// 同アカウントならfav状態を変化させる
if(access_info.acct == account.acct) {
if(access_info == account ) {
status.favourited = new_status.favourited
}
@ -230,7 +230,7 @@ object Action_Toot {
status : TootStatus?
) {
if(status == null) return
val who_host = timeline_account.host
val who_host = timeline_account.hostAscii
AccountPicker.pick(
activity,
@ -330,10 +330,10 @@ object Action_Toot {
new_status != null -> {
for(column in App1.getAppState(activity).column_list) {
column.findStatus(access_info.host, new_status.id) { account, status ->
column.findStatus(access_info.hostAscii, new_status.id) { account, status ->
// 同アカウントならブックマーク状態を伝播する
if(access_info.acct == account.acct) {
if(access_info == account ) {
status.bookmarked = new_status.bookmarked
}
@ -362,7 +362,7 @@ object Action_Toot {
) {
status ?: return
val who_host = timeline_account.host
val who_host = timeline_account.hostAscii
val status_owner = timeline_account.getFullAcct(status.account)
val isPrivateToot = timeline_account.isMastodon &&
@ -371,7 +371,7 @@ object Action_Toot {
if(isPrivateToot) {
val list = ArrayList<SavedAccount>()
for(a in SavedAccount.loadAccountList(activity)) {
if(a.acct == status_owner) list.add(a)
if(a.acctAscii == status_owner) list.add(a)
}
if(list.isEmpty()) {
showToast(activity, false, R.string.boost_private_toot_not_allowed)
@ -434,7 +434,7 @@ object Action_Toot {
// Mastodonは非公開トゥートをブーストできるのは本人だけ
val isPrivateToot = access_info.isMastodon &&
arg_status.visibility == TootVisibility.PrivateFollowers
if(isPrivateToot && access_info.acct != status_owner_acct) {
if(isPrivateToot && access_info.acctAscii != status_owner_acct) {
showToast(activity, false, R.string.boost_private_toot_not_allowed)
return
}
@ -587,15 +587,13 @@ object Action_Toot {
val count = max(0, (arg_status.reblogs_count ?: 1) - 1)
for(column in App1.getAppState(activity).column_list) {
column.findStatus(access_info.host, arg_status.id) { account, status ->
column.findStatus(access_info.hostAscii, arg_status.id) { account, status ->
// 同タンス別アカウントでもカウントは変化する
status.reblogs_count = count
// 同アカウントならreblogged状態を変化させる
if(access_info.acct == account.acct &&
status.myRenoteId == unrenoteId
) {
if(access_info == account && status.myRenoteId == unrenoteId ) {
status.myRenoteId = null
status.reblogged = false
}
@ -623,12 +621,12 @@ object Action_Toot {
}
for(column in App1.getAppState(activity).column_list) {
column.findStatus(access_info.host, new_status.id) { account, status ->
column.findStatus(access_info.hostAscii, new_status.id) { account, status ->
// 同タンス別アカウントでもカウントは変化する
status.reblogs_count = new_status.reblogs_count
if(access_info.acct == account.acct) {
if(access_info == account ) {
// 同アカウントならreblog状態を変化させる
when {
@ -689,7 +687,7 @@ object Action_Toot {
if(result.jsonObject != null) {
showToast(activity, false, R.string.delete_succeeded)
for(column in App1.getAppState(activity).column_list) {
column.onStatusRemoved(access_info.host, status_id)
column.onStatusRemoved(access_info.hostAscii, status_id)
}
} else {
showToast(activity, false, result.error)
@ -731,7 +729,7 @@ object Action_Toot {
access_info : SavedAccount,
status : TootStatus
) {
if(access_info.isNA || ! access_info.host.equals(status.host_access, ignoreCase = true)) {
if(access_info.isNA || ! access_info.matchHost(status.host_access)) {
conversationOtherInstance(activity, pos, status)
} else {
@ -845,11 +843,11 @@ object Action_Toot {
// 疑似アカウントは後でまとめて処理する
if(a.isPseudo) continue
if(status_id_original != null && a.host.equals(host_original, ignoreCase = true)) {
if(status_id_original != null && a.matchHost(host_original)) {
// アクセス情報ステータスID でアクセスできるなら
// 同タンスのアカウントならステータスIDの変換なしに表示できる
local_account_list.add(a)
} else if(status_id_access != null && a.host.equals(host_access, ignoreCase = true)) {
} else if(status_id_access != null && a.matchHost(host_access)) {
// 既に変換済みのステータスIDがあるなら、そのアカウントでもステータスIDの変換は必要ない
access_account_list.add(a)
} else {
@ -887,8 +885,8 @@ object Action_Toot {
AcctColor.getStringWithNickname(
activity,
R.string.open_in_account,
a.acct,
a.prettyAcct
a.acctAscii,
a.acctPretty
)
) { conversationLocal(activity, pos, a, status_id_original) }
}
@ -902,7 +900,8 @@ object Action_Toot {
AcctColor.getStringWithNickname(
activity,
R.string.open_in_account,
a.acct,a.prettyAcct
a.acctAscii,
a.acctPretty
)
) { conversationLocal(activity, pos, a, status_id_access) }
}
@ -915,7 +914,8 @@ object Action_Toot {
AcctColor.getStringWithNickname(
activity,
R.string.open_in_account,
a.acct,a.prettyAcct
a.acctAscii,
a.acctPretty
)
) { conversationRemote(activity, pos, a, url) }
}
@ -1010,7 +1010,7 @@ object Action_Toot {
// step 1: choose account
val host = statusArg.account.host
val host = statusArg.account.hostAscii
val local_account_list = ArrayList<SavedAccount>()
val other_account_list = ArrayList<SavedAccount>()
@ -1019,7 +1019,7 @@ object Action_Toot {
// 検索APIはログイン必須なので疑似アカウントは使えない
if(a.isPseudo) continue
if(a.host.equals(host, ignoreCase = true)) {
if(a.matchHost(host)) {
local_account_list.add(a)
} else {
other_account_list.add(a)
@ -1034,7 +1034,8 @@ object Action_Toot {
AcctColor.getStringWithNickname(
activity,
R.string.open_in_account,
a.acct,a.prettyAcct
a.acctAscii,
a.acctPretty
)
) { step2(a) }
}
@ -1045,7 +1046,8 @@ object Action_Toot {
AcctColor.getStringWithNickname(
activity,
R.string.open_in_account,
a.acct,a.prettyAcct
a.acctAscii,
a.acctPretty
)
) { step2(a) }
}
@ -1089,9 +1091,9 @@ object Action_Toot {
new_status != null -> {
for(column in App1.getAppState(activity).column_list) {
if(access_info.acct == column.access_info.acct) {
if(access_info == column.access_info ) {
column.findStatus(
access_info.host,
access_info.hostAscii,
new_status.id
) { _, status ->
status.pinned = bSet
@ -1171,10 +1173,10 @@ object Action_Toot {
quotedRenote : Boolean = false
) {
status ?: return
val who_host = timeline_account.host
val who_host = timeline_account.hostAscii
val accountCallback : SavedAccountCallback = { ai ->
if(ai.host.equals(status.host_access, ignoreCase = true)) {
if(ai.matchHost(status.host_access)) {
// アクセス元ホストが同じならステータスIDを使って返信できる
reply(activity, ai, status, quotedRenote = quotedRenote)
} else {
@ -1292,8 +1294,8 @@ object Action_Toot {
val ls = local_status
if(ls != null) {
for(column in App1.getAppState(activity).column_list) {
if(access_info.acct == column.access_info.acct) {
column.findStatus(access_info.host, ls.id) { _, status ->
if(access_info == column.access_info ) {
column.findStatus(access_info.hostAscii, ls.id) { _, status ->
status.muted = bMute
true
}
@ -1315,7 +1317,7 @@ object Action_Toot {
activity : ActMain,
access_info : SavedAccount,
arg_status : TootStatus,
status_owner_acct : String,
status_owner_acct : String, // acctAscii
nCrossAccountMode : Int,
callback : EmptyCallback?,
bSet : Boolean = true,
@ -1324,7 +1326,7 @@ object Action_Toot {
if(access_info.isPseudo || ! access_info.isMisskey) return
// 自分の投稿にはリアクション出来ない
if(access_info.acct == status_owner_acct) {
if(access_info.acctAscii == status_owner_acct) {
showToast(activity, false, R.string.it_is_you)
return
}

View File

@ -18,6 +18,7 @@ import jp.juggler.subwaytooter.table.UserRelation
import jp.juggler.subwaytooter.util.TootApiResultCallback
import jp.juggler.util.*
import okhttp3.Request
import java.net.IDN
object Action_User {
@ -129,7 +130,7 @@ object Action_User {
}
// フォローアイコンの表示更新が走る
column.updateFollowIcons(access_info)
} else if(column.access_info.acct == access_info.acct) {
} else if(column.access_info == access_info ) {
when {
! relation.muting -> {
if(column.type == ColumnType.MUTES) {
@ -288,7 +289,7 @@ object Action_User {
}
// フォローアイコンの表示更新が走る
column.updateFollowIcons(access_info)
} else if(column.access_info.acct == access_info.acct) {
} else if(column.access_info == access_info ) {
when {
@ -392,7 +393,7 @@ object Action_User {
who : TootAccount?
) {
if(who?.url == null) return
val who_host = who.host
val who_host = who.hostAscii
AccountPicker.pick(
activity,
@ -404,7 +405,7 @@ object Action_User {
),
accountListArg = makeAccountListNonPseudo(activity, who_host)
) { ai ->
if(ai.host.equals(access_info.host, ignoreCase = true)) {
if(ai.matchHost(access_info.hostAscii)) {
activity.addColumn(pos, ai, ColumnType.PROFILE, who.id)
} else {
profileFromUrlOrAcct(activity, pos, ai, who.url, access_info.getFullAcct(who))
@ -432,16 +433,17 @@ object Action_User {
pos : Int,
access_info : SavedAccount?,
url : String,
host : String,
hostArg : String,
user : String,
original_url : String = url
) {
val acct = "$user@$host"
val hostAscii = IDN.toASCII(hostArg,IDN.ALLOW_UNASSIGNED)
val acctAscii = "$user@$hostAscii"
if(access_info?.isPseudo == false) {
// 文脈のアカウントがあり、疑似アカウントではない
if(access_info.host.equals(host, ignoreCase = true)) {
if(access_info.matchHost(hostAscii)) {
// 文脈のアカウントと同じインスタンスなら、アカウントIDを探して開いてしまう
TootTaskRunner(activity).run(access_info, object : TootTask {
@ -449,7 +451,7 @@ object Action_User {
var who : TootAccount? = null
override fun background(client : TootApiClient) : TootApiResult? {
val (result, ar) = client.syncAccountByAcct(access_info, acct)
val (result, ar) = client.syncAccountByAcct(access_info, acctAscii)
who = ar?.get()
return result
}
@ -468,7 +470,7 @@ object Action_User {
})
} else {
// 文脈のアカウントと異なるインスタンスなら、別アカウントで開く
profileFromUrlOrAcct(activity, pos, access_info, url, acct)
profileFromUrlOrAcct(activity, pos, access_info, url, acctAscii)
}
return
}
@ -481,16 +483,16 @@ object Action_User {
// chrome tab で開く
App1.openCustomTab(activity, original_url)
} else {
val(asciiAcct,prettyAcct)=TootAccount.acctAndPrettyAcct("$user@$host")
val(_,acctPretty)=TootAccount.acctAndPrettyAcct(acctAscii)
AccountPicker.pick(
activity,
bAllowPseudo = false,
bAuto = false,
message = activity.getString(
R.string.account_picker_open_user_who,
AcctColor.getNickname(asciiAcct,prettyAcct)
AcctColor.getNickname(acctAscii,acctPretty)
),
accountListArg = makeAccountListNonPseudo(activity, host),
accountListArg = makeAccountListNonPseudo(activity, hostAscii),
extra_callback = { ll, pad_se, pad_tb ->
// chrome tab で開くアクションを追加
@ -514,7 +516,7 @@ object Action_User {
ll.addView(b, 0)
}
) {
profileFromUrlOrAcct(activity, pos, it, url, acct)
profileFromUrlOrAcct(activity, pos, it, url, acctAscii)
}
}
}
@ -658,7 +660,7 @@ object Action_User {
activity : ActMain, access_info : SavedAccount, who : TootAccount?
) {
if(who == null) return
val who_host = who.host
val who_host = who.hostAscii
val initial_text = "@" + access_info.getFullAcct(who) + " "
AccountPicker.pick(

View File

@ -12,7 +12,7 @@ object TootAccountMap{
val watcher: String
init{
this.acct = parser.getFullAcct(who.acct)
this.acct = parser.getFullAcct(who.acctAscii)
this.watcher =when(parser.serviceType){
ServiceType.MASTODON -> requireNotNull(parser.accessHost)

View File

@ -30,7 +30,7 @@ class TootApiClient(
// アカウントがある場合に使用する
var account : SavedAccount? = null
set(value) {
instance = value?.host
instance = value?.hostAscii
field = value
}
@ -1534,7 +1534,7 @@ fun TootApiClient.syncStatus(
)
.status(result.jsonObject)
?.apply {
if(host.equals(accessInfo.host, ignoreCase = true)) {
if(host.equals(accessInfo.hostAscii, ignoreCase = true)) {
return Pair(result, this)
}
uri.letNotEmpty { url = it }

View File

@ -23,7 +23,7 @@ class TootParser(
val misskeyAccountDetailMap = HashMap<EntityId, TootAccount>()
val accessHost : String?
get() = linkHelper.host
get() = linkHelper.hostAscii
init {
if(linkHelper.isMisskey) serviceType = ServiceType.MISSKEY

View File

@ -30,13 +30,13 @@ open class TootAccount(parser : TootParser, src : JsonObject) {
val id : EntityId
// Equals username for local users, includes @domain for remote ones
val acct : String // punycode
val acctAscii : String // punycode
val prettyAcct : String // unicode
// The username of the account /[A-Za-z0-9_]{1,30}/
val username : String
val host : String // punycode
val hostAscii : String // punycode
// The account's display name
val display_name : String
@ -99,10 +99,10 @@ open class TootAccount(parser : TootParser, src : JsonObject) {
val isPro : Boolean
val isLocal :Boolean
get() = !acct.contains('@')
get() = !acctAscii.contains('@')
val isRemote :Boolean
get() = acct.contains('@')
get() = acctAscii.contains('@')
// user_hides_network is preference, not exposed in API
// val user_hides_network : Boolean
@ -129,7 +129,7 @@ open class TootAccount(parser : TootParser, src : JsonObject) {
val remoteHost = src.string("host")
val tmpHost = (remoteHost ?: parser.accessHost ?: error("missing host")).toLowerCase(Locale.JAPAN)
this.host = IDN.toASCII(tmpHost,IDN.ALLOW_UNASSIGNED)
this.hostAscii = IDN.toASCII(tmpHost,IDN.ALLOW_UNASSIGNED)
val prettyHost = IDN.toUnicode(tmpHost,IDN.ALLOW_UNASSIGNED)
this.custom_emojis =
@ -138,7 +138,7 @@ open class TootAccount(parser : TootParser, src : JsonObject) {
this.profile_emojis = null
this.username = src.notEmptyOrThrow("username")
this.url = "https://$host/@$username"
this.url = "https://$hostAscii/@$username"
//
sv = src.string("name")
@ -162,7 +162,7 @@ open class TootAccount(parser : TootParser, src : JsonObject) {
this.id = EntityId.mayDefault(src.string("id"))
this.acct = when {
this.acctAscii = when {
// アクセス元から見て内部ユーザなら short acct
remoteHost?.equals(
@ -171,7 +171,7 @@ open class TootAccount(parser : TootParser, src : JsonObject) {
) != false -> username
// アクセス元から見て外部ユーザならfull acct
else -> "$username@$host"
else -> "$username@$hostAscii"
}
this.prettyAcct = when {
@ -270,9 +270,9 @@ open class TootAccount(parser : TootParser, src : JsonObject) {
findHostFromUrl(tmpAcct, hostAccess, url)
?: throw RuntimeException("can't get host from acct or url")
).toLowerCase(Locale.JAPAN)
this.host = IDN.toASCII(tmpHost,IDN.ALLOW_UNASSIGNED)
this.hostAscii = IDN.toASCII(tmpHost,IDN.ALLOW_UNASSIGNED)
val prettyHost = IDN.toUnicode(tmpHost,IDN.ALLOW_UNASSIGNED)
this.acct = if( !tmpAcct.contains('@') ) tmpAcct else "$username@$host"
this.acctAscii = if( !tmpAcct.contains('@') ) tmpAcct else "$username@$hostAscii"
this.prettyAcct = if( !tmpAcct.contains('@') ) tmpAcct else "$username@$prettyHost"
this.followers_count = src.long("followers_count")
@ -297,10 +297,10 @@ open class TootAccount(parser : TootParser, src : JsonObject) {
val tmpHost = (findHostFromUrl(sv, null, url)
?: throw RuntimeException("can't get host from acct or url")
).toLowerCase(Locale.JAPAN)
this.host = IDN.toASCII(tmpHost,IDN.ALLOW_UNASSIGNED)
this.hostAscii = IDN.toASCII(tmpHost,IDN.ALLOW_UNASSIGNED)
val prettyHost=IDN.toUnicode(tmpHost,IDN.ALLOW_UNASSIGNED)
this.acct = this.username + "@" + this.host
this.acctAscii = this.username + "@" + this.hostAscii
this.prettyAcct = this.username + "@" + prettyHost
this.followers_count = src.long("followers_count")
@ -323,10 +323,10 @@ open class TootAccount(parser : TootParser, src : JsonObject) {
val tmpHost = (findHostFromUrl(null, null, url)
?: throw RuntimeException("can't get host from url")
).toLowerCase(Locale.JAPAN)
this.host = IDN.toASCII(tmpHost,IDN.ALLOW_UNASSIGNED)
this.hostAscii = IDN.toASCII(tmpHost,IDN.ALLOW_UNASSIGNED)
val prettyHost = IDN.toUnicode(tmpHost,IDN.ALLOW_UNASSIGNED)
this.acct = this.username + "@" + host
this.acctAscii = this.username + "@" + hostAscii
this.prettyAcct = this.username + "@" + prettyHost
this.followers_count = null
@ -655,15 +655,13 @@ open class TootAccount(parser : TootParser, src : JsonObject) {
fun acctAndPrettyAcct(src : String) : Pair<String, String> {
val cols = src.split('@')
val cols = src.split("@")
if(cols.size < 2 ) return Pair(src,src)
val username = cols[0]
return if(cols.size == 1)
Pair(username, username)
else
Pair(
username + "@" + IDN.toASCII(cols[1], IDN.ALLOW_UNASSIGNED),
username + "@" + IDN.toUnicode(cols[1], IDN.ALLOW_UNASSIGNED)
)
return Pair(
"$username@${IDN.toASCII(cols[1], IDN.ALLOW_UNASSIGNED)}",
"$username@${IDN.toUnicode(cols[1], IDN.ALLOW_UNASSIGNED)}"
)
}
}
}

View File

@ -5,14 +5,22 @@ import jp.juggler.util.JsonObject
class TootMention(
val id : EntityId, // Account ID
val url : String, // URL of user's profile (can be remote)
val acct : String, // Equals username for local users, includes @domain for remote ones
acctArg : String, // Equals username for local users, includes @domain for remote ones
val username : String // The username of the account
) {
val acctAscii: String
val acctPretty:String
init{
val(acctAscii,acctPretty)=TootAccount.acctAndPrettyAcct(acctArg)
this.acctAscii = acctAscii
this.acctPretty = acctPretty
}
constructor(src : JsonObject) : this(
id = EntityId.mayDefault(src.string("id")),
url = src.notEmptyOrThrow("url"),
acct = src.notEmptyOrThrow("acct"),
acctArg = src.notEmptyOrThrow("acct"),
username = src.notEmptyOrThrow("username")
)
}

View File

@ -45,7 +45,7 @@ class TootStatus(parser : TootParser, src : JsonObject) : TimelineItem() {
// 投稿元タンスのホスト名
val host_original : String
get() = account.host
get() = account.hostAscii
// 取得タンスのホスト名。トゥート検索サービスでは提供されずnullになる
val host_access : String?
@ -646,7 +646,7 @@ class TootStatus(parser : TootParser, src : JsonObject) : TimelineItem() {
}
mentions?.forEach {
if(fullAcctMe != access_info.getFullAcct(it.acct))
if(fullAcctMe != access_info.getFullAcct(it.acctAscii))
return@hasReceipt TootVisibility.DirectSpecified
}

View File

@ -127,7 +127,7 @@ object AccountPicker {
for(a in account_list) {
val lp = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)
val ac = AcctColor.load(a.acct)
val ac = AcctColor.load(a.acctAscii)
val b = Button(activity)
@ -146,7 +146,7 @@ object AccountPicker {
b.layoutParams = lp
b.minHeight = (0.5f + 32f * density).toInt()
val sb = SpannableStringBuilder(if(AcctColor.hasNickname(ac)) ac.nickname else a.prettyAcct)
val sb = SpannableStringBuilder(if(AcctColor.hasNickname(ac)) ac.nickname else a.acctPretty)
if( a.last_notification_error?.isNotEmpty() == true) {
sb.append("\n")
val start = sb.length

View File

@ -142,9 +142,8 @@ class DlgListMember(
//
} else {
val acct = a.acct
val ac = AcctColor.load(acct)
val nickname = if(AcctColor.hasNickname(ac)) ac.nickname else acct
val ac = AcctColor.load(a.acctAscii)
val nickname = if(AcctColor.hasNickname(ac)) ac.nickname else a.acctPretty
btnListOwner.text = nickname
if(AcctColor.hasColorBackground(ac)) {

View File

@ -33,7 +33,7 @@ object ReportForm {
val cbForward : CheckBox = view.findViewById(R.id.cbForward)
val tvForwardDesc:TextView = view.findViewById(R.id.tvForwardDesc)
val canForward = access_info.host != who.host
val canForward = access_info.hostAscii != who.hostAscii
cbForward.isChecked = false
@ -43,7 +43,7 @@ object ReportForm {
}else{
cbForward.visibility = View.VISIBLE
tvForwardDesc.visibility = View.VISIBLE
cbForward.text = activity.getString(R.string.report_forward_to,who.host)
cbForward.text = activity.getString(R.string.report_forward_to,who.hostAscii)
}
tvUser.text = who.prettyAcct

View File

@ -177,7 +177,7 @@ class AcctColor {
val nickname = ac.nickname
return if(nickname != null && nickname.isNotEmpty()) nickname.sanitizeBDI() else prettyAcct
}
fun getNickname(sa:SavedAccount) : String = getNickname(sa.acct,sa.prettyAcct)
fun getNickname(sa:SavedAccount) : String = getNickname(sa.acctAscii,sa.acctPretty)
fun getNickname(sa:SavedAccount,who:TootAccount) : String = getNickname(sa.getFullAcct(who),sa.getFullPrettyAcct(who))

View File

@ -22,7 +22,7 @@ import java.util.regex.Pattern
class SavedAccount(
val db_id : Long,
val acct : String,
acctArg : String,
hostArg : String? = null,
var token_info : JsonObject? = null,
var loginAccount : TootAccount? = null, // 疑似アカウントではnull
@ -31,8 +31,8 @@ class SavedAccount(
val username : String
override val host : String // punycode
override val prettyHost : String // unicode
override val hostAscii : String // punycode
override val hostPretty : String // unicode
var visibility : TootVisibility = TootVisibility.Public
var confirm_boost : Boolean = false
@ -71,30 +71,32 @@ class SavedAccount(
var last_subscription_error : String? = null
var last_push_endpoint : String? = null
val prettyAcct :String
val acctAscii :String
val acctPretty :String
init {
val pos = acct.indexOf('@')
val pos = acctArg.indexOf('@')
val tmpHost:String?
if(pos == - 1) {
this.username = acct
prettyAcct = acct
this.username = acctArg
acctAscii = acctArg
acctPretty = acctArg
tmpHost = null
} else {
this.username = acct.substring(0, pos)
prettyAcct = username+"@"+IDN.toUnicode(acct.substring(pos+1),IDN.ALLOW_UNASSIGNED)
this.username = acctArg.substring(0, pos)
tmpHost = acctArg.substring(pos+1).toLowerCase(Locale.JAPAN)
acctAscii= username+"@"+IDN.toASCII(tmpHost,IDN.ALLOW_UNASSIGNED)
acctPretty = username+"@"+IDN.toUnicode(tmpHost,IDN.ALLOW_UNASSIGNED)
}
if(username.isEmpty()) throw RuntimeException("missing username in acct")
val host = if(hostArg != null && hostArg.isNotEmpty()) {
hostArg
} else {
val hostInAcct = if(pos == - 1) "" else acct.substring(pos + 1)
if(hostInAcct.isEmpty()) throw RuntimeException("missing host in acct")
hostInAcct
}.toLowerCase(Locale.JAPAN)
val host = hostArg?.notEmpty()?.toLowerCase(Locale.JAPAN)
?: tmpHost
?: error("missing host in acct")
this.host = IDN.toASCII(host,IDN.ALLOW_UNASSIGNED)
this.prettyHost = IDN.toUnicode(host,IDN.ALLOW_UNASSIGNED)
this.hostAscii = IDN.toASCII(host,IDN.ALLOW_UNASSIGNED)
this.hostPretty = IDN.toUnicode(host,IDN.ALLOW_UNASSIGNED)
}
constructor(context : Context, cursor : Cursor) : this(
@ -111,7 +113,7 @@ class SavedAccount(
} else {
TootParser(
context,
LinkHelper.newLinkHelper(this@SavedAccount.host, misskeyVersion = misskeyVersion)
LinkHelper.newLinkHelper(this@SavedAccount.hostAscii, misskeyVersion = misskeyVersion)
).account(jsonAccount)
?: error("missing loginAccount for $strAccount")
}
@ -161,7 +163,7 @@ class SavedAccount(
}
val isNA : Boolean
get() = "?@?" == acct
get() = "?@?" == acctAscii
val isPseudo : Boolean
get() = username == "?"
@ -282,17 +284,17 @@ class SavedAccount(
this.sound_uri = b.sound_uri
}
fun getFullAcct(who : TootAccount?) : String = getFullAcct(who?.acct)
fun getFullAcct(who : TootAccount?) : String = getFullAcct(who?.acctAscii)
fun getFullPrettyAcct(who : TootAccount?) : String = getFullPrettyAcct(who?.prettyAcct)
private fun isLocalUser(acct : String?) : Boolean {
acct ?: return false
val pos = acct.indexOf('@')
return pos == - 1 || host.equals(acct.substring(pos + 1), ignoreCase = true)
return pos == - 1 || matchHost( acct.substring(pos + 1) )
}
fun isLocalUser(who : TootAccount?) : Boolean {
return isLocalUser(who?.acct)
return isLocalUser(who?.acctAscii)
}
fun isRemoteUser(who : TootAccount) : Boolean {
@ -305,24 +307,24 @@ class SavedAccount(
fun getUserUrl(who : TootAccount) : String =
who.url ?: if(who.isRemote) {
"https://${IDN.toUnicode(who.host, IDN.ALLOW_UNASSIGNED)}/@${who.username}"
"https://${IDN.toUnicode(who.hostAscii, IDN.ALLOW_UNASSIGNED)}/@${who.username}"
} else {
"https://$prettyHost/@${who.username}"
"https://$hostPretty/@${who.username}"
}
fun isMe(who : TootAccount?) : Boolean {
if(who == null || who.username != this.username) return false
//
val who_acct = who.acct
val who_acct = who.acctAscii
val pos = who_acct.indexOf('@')
if(pos == - 1) return true // local user have no acct
return who_acct.substring(pos + 1).equals(this.host, ignoreCase = true)
return who_acct.substring(pos + 1).equals(this.hostAscii, ignoreCase = true)
}
fun isMe(who_acct : String) : Boolean {
// 自分のユーザ名部分
var pos = this.acct.indexOf('@')
val me_user = this.acct.substring(0, pos)
var pos = this.acctAscii.indexOf('@')
val me_user = this.acctAscii.substring(0, pos)
//
pos = who_acct.indexOf('@')
@ -331,25 +333,25 @@ class SavedAccount(
// リモートユーザならホスト名部分の比較も必要
val who_user = who_acct.substring(0, pos)
val who_host = who_acct.substring(pos + 1)
return who_user == me_user && ( who_host.equals(this.host, ignoreCase = true) || who_host.equals(this.prettyHost, ignoreCase = true) )
return who_user == me_user && ( who_host.equals(this.hostAscii, ignoreCase = true) || who_host.equals(this.hostPretty, ignoreCase = true) )
}
fun supplyBaseUrl(url : String?) : String? {
return when {
url == null || url.isEmpty() -> return null
url[0] == '/' -> "https://$host$url"
url[0] == '/' -> "https://$hostAscii$url"
else -> url
}
}
fun isNicoru(account : TootAccount?) : Boolean {
var host = this.host
var host = this.hostAscii
var host_start = 0
val acct = account?.acct
val acct = account?.acctAscii
if(acct != null) {
val pos = acct.indexOf('@')
if(pos != - 1) {
host = account.acct
host = account.acctAscii
host_start = pos + 1
}
}
@ -925,8 +927,8 @@ class SavedAccount(
return 0L
}
fun isNicoru(acct : String?) : Boolean {
return acct != null && reAtNicoruHost.matcher(acct).find()
fun isNicoru(acct:String) : Boolean {
return reAtNicoruHost.matcher(acct).find()
}
private fun charAtLower(src : CharSequence, pos : Int) : Char {
@ -1126,4 +1128,13 @@ class SavedAccount(
}
}
override fun equals(other : Any?) : Boolean =
when(other) {
is SavedAccount -> acctAscii == other.acctAscii
else -> false
}
override fun hashCode() : Int = acctAscii.hashCode()
}

View File

@ -8,6 +8,7 @@ import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.Pref
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.api.entity.EntityId
import jp.juggler.subwaytooter.api.entity.TootAccount
import jp.juggler.subwaytooter.api.entity.TootMention
import jp.juggler.subwaytooter.span.EmojiImageSpan
import jp.juggler.subwaytooter.span.HighlightSpan
@ -504,20 +505,31 @@ object HTMLDecoder {
for(item in mentionList) {
if(sb.isNotEmpty()) sb.append(" ")
val rawAcct = item.acct
val fullAcct = getFullAcctOrNull(linkHelper, rawAcct,item.url)
val fullAcct = getFullAcctOrNull(linkHelper, item.acctAscii,item.url)
val linkInfo = if( fullAcct != null){
val(fullAcctAscii,fullAcctPretty) = TootAccount.acctAndPrettyAcct(fullAcct)
LinkInfo(
url = item.url,
caption = "@" + if( Pref.bpMentionFullAcct(App1.pref)) {
fullAcctPretty
} else {
item.acctPretty
},
ac = AcctColor.load(fullAcctAscii),
mention = item,
tag = link_tag
)
}else{
LinkInfo(
url = item.url,
caption = "@" + item.acctPretty,
ac = null,
mention = item,
tag = link_tag
)
}
val linkInfo = LinkInfo(
url = item.url,
caption = "@" + if(fullAcct != null && Pref.bpMentionFullAcct(App1.pref)) {
fullAcct
} else {
rawAcct
},
ac = if(fullAcct != null) AcctColor.load(fullAcct) else null,
mention = item,
tag = link_tag
)
val start = sb.length
sb.append(linkInfo.caption)
val end = sb.length
@ -598,7 +610,7 @@ object HTMLDecoder {
// Account.note does not have mentions metadata.
// fallback to resolve acct by mention URL.
val rawAcct = mention?.acct ?: originalCaption.toString().substring(1)
val rawAcct = mention?.acctPretty ?: originalCaption.toString().substring(1)
val fullAcct = getFullAcctOrNull(options.linkHelper, rawAcct,href)
if(fullAcct != null) {
@ -608,7 +620,7 @@ object HTMLDecoder {
linkInfo.mention = TootMention(
id = EntityId.DEFAULT,
url = href,
acct = fullAcct ,
acctArg = fullAcct ,
username = rawAcct.splitFullAcct().first
)
}

View File

@ -8,26 +8,26 @@ import java.net.IDN
interface LinkHelper {
// SavedAccountのロード時にhostを供給する必要があった
val host : String? // punycode
val prettyHost : String? // unicode
val hostAscii : String? // punycode
val hostPretty : String? // unicode
// fun findAcct(url : String?) : String? = null
// fun colorFromAcct(acct : String?) : AcctColor? = null
// user とか user@host とかを user@host に変換する
// nullや空文字列なら ?@? を返す
fun getFullAcct(acct : String?) : String = when {
acct?.isEmpty() != false -> "?@?"
acct.contains('@') -> acct
else -> "$acct@$host"
fun getFullAcct(acctAscii : String?) : String = when {
acctAscii?.isEmpty() != false -> "?@?"
acctAscii.contains('@') -> acctAscii
else -> "$acctAscii@$hostAscii"
}
// user とか user@host とかを user@host に変換する
// nullや空文字列なら ?@? を返す
fun getFullPrettyAcct(prettyAcct : String?) : String = when {
prettyAcct?.isEmpty() != false -> "?@?"
prettyAcct.contains('@') -> prettyAcct
else -> "$prettyAcct@$prettyHost"
fun getFullPrettyAcct(acctPretty : String?) : String = when {
acctPretty?.isEmpty() != false -> "?@?"
acctPretty.contains('@') -> acctPretty
else -> "$acctPretty@$hostPretty"
}
val misskeyVersion : Int
@ -40,13 +40,19 @@ interface LinkHelper {
val isMastodon : Boolean
get() = misskeyVersion <= 0
fun matchHost(host : String?) : Boolean =
host != null && (
host.equals(hostAscii, ignoreCase = true) ||
host.equals(hostPretty, ignoreCase = true)
)
companion object {
fun newLinkHelper(hostArg : String?, misskeyVersion : Int = 0) = object : LinkHelper {
override val host : String? =
override val hostAscii : String? =
hostArg?.let { IDN.toASCII(hostArg, IDN.ALLOW_UNASSIGNED) }
override val prettyHost : String? =
override val hostPretty : String? =
hostArg?.let { IDN.toUnicode(hostArg, IDN.ALLOW_UNASSIGNED) }
override val misskeyVersion : Int
@ -54,13 +60,14 @@ interface LinkHelper {
}
val nullHost = object : LinkHelper {
override val host : String? = null
override val prettyHost : String? = null
override val hostAscii : String? = null
override val hostPretty : String? = null
}
}
}
// user や user@host から user@host を返す
// ただし host部分がpunycodeかunicodeかは分からない
fun getFullAcctOrNull(
linkHelper : LinkHelper?,
rawAcct : String,
@ -81,8 +88,8 @@ fun getFullAcctOrNull(
// https://fedibird.com/@noellabo/103350050191159092
// に含まれるメンションををリモートから見るとmentions メタデータがない。
// この場合アクセス元のホストを補うのは誤りなのだが、他の方法で解決できないなら仕方ない…
if(linkHelper?.host?.endsWith('?') == false)
return "$rawAcct@${linkHelper.host}"
if(linkHelper?.hostAscii?.endsWith('?') == false)
return "$rawAcct@${linkHelper.hostAscii}"
return null
}

View File

@ -855,11 +855,11 @@ object MisskeyMarkdownDecoder {
} else {
val userHost = when {
host.isEmpty() -> linkHelper.host
host.isEmpty() -> linkHelper.hostAscii
else -> host
} ?: "?"
}?.toLowerCase(Locale.JAPAN) ?: "?"
when(userHost.toLowerCase(Locale.JAPAN)) {
when(userHost) {
// https://github.com/syuilo/misskey/pull/3603
@ -884,16 +884,18 @@ object MisskeyMarkdownDecoder {
val userUrl = "https://$userHost/@$username"
val shortAcct = when {
host.isEmpty()
|| host.equals(linkHelper.host, ignoreCase = true) ->
username
else ->
"$username@$host"
|| host.equals(linkHelper.hostAscii, ignoreCase = true)
|| host.equals(linkHelper.hostPretty, ignoreCase = true) -> username
else -> "$username@$host"
}
val mentions = prepareMentions()
if(mentions.find { m -> m.acct == shortAcct } == null) {
if(mentions.find { m -> m.acctAscii == shortAcct || m.acctPretty == shortAcct } == null) {
mentions.add(
TootMention(
jp.juggler.subwaytooter.api.entity.EntityId.DEFAULT
@ -922,7 +924,7 @@ object MisskeyMarkdownDecoder {
if(tag.isNotEmpty() && linkHelper != null) {
appendLink(
"#$tag",
"https://${linkHelper.host}/tags/" + tag.encodePercent()
"https://${linkHelper.hostAscii}/tags/" + tag.encodePercent()
)
}
}),

View File

@ -27,17 +27,17 @@ object NotificationHelper {
) = when(trackingName) {
"" -> createNotificationChannel(
context,
account.acct, // id
account.acct, // name
context.getString(R.string.notification_channel_description, account.acct),
account.acctAscii, // id
account.acctPretty, // name
context.getString(R.string.notification_channel_description, account.acctPretty),
NotificationManager.IMPORTANCE_DEFAULT // : NotificationManager.IMPORTANCE_LOW;
)
else -> createNotificationChannel(
context,
"${account.acct}/$trackingName", // id
"${account.acct}/$trackingName", // name
context.getString(R.string.notification_channel_description, account.acct),
"${account.acctAscii}/$trackingName", // id
"${account.acctPretty}/$trackingName", // name
context.getString(R.string.notification_channel_description, account.acctPretty),
NotificationManager.IMPORTANCE_DEFAULT // : NotificationManager.IMPORTANCE_LOW;
)
}

View File

@ -580,7 +580,7 @@ class PostHelper(
val request_builder = body_string.toRequestBody(MEDIA_TYPE_JSON).toPost()
if(! Pref.bpDontDuplicationCheck(pref)) {
val digest = (body_string + account.acct).digestSHA256Hex()
val digest = (body_string + account.acctAscii).digestSHA256Hex()
request_builder.header("Idempotency-Key", digest)
}

View File

@ -27,7 +27,7 @@ class PushSubscriptionHelper(
fun clearLastCheck(account : SavedAccount) {
synchronized(lastCheckedMap) {
lastCheckedMap.remove(account.acct)
lastCheckedMap.remove(account.acctAscii)
}
}
@ -38,11 +38,11 @@ class PushSubscriptionHelper(
private fun isRecentlyChecked() : Boolean {
if(verbose) return false
val now = System.currentTimeMillis()
val acct = account.acct
val acctAscii = account.acctAscii
synchronized(lastCheckedMap) {
val lastChecked = lastCheckedMap[acct]
val lastChecked = lastCheckedMap[acctAscii]
val rv = lastChecked != null && now - lastChecked < 3600000L
if(! rv) lastCheckedMap[acct] = now
if(! rv) lastCheckedMap[acctAscii] = now
return rv
}
}
@ -132,7 +132,7 @@ class PushSubscriptionHelper(
return client.http(
jsonObject {
put("acct", account.acct)
put("acct", account.acctAscii)
put("deviceId", deviceId)
put("endpoint", endpoint)
}
@ -190,7 +190,7 @@ class PushSubscriptionHelper(
// 2018/9/1 の上記コミット以降、Misskeyでもサーバ公開鍵を得られるようになった
val endpoint =
"${PollingWorker.APP_SERVER}/webpushcallback/${device_id.encodePercent()}/${account.acct.encodePercent()}/$flags/$clientIdentifier/misskey"
"${PollingWorker.APP_SERVER}/webpushcallback/${device_id.encodePercent()}/${account.acctAscii.encodePercent()}/$flags/$clientIdentifier/misskey"
// アプリサーバが過去のendpoint urlに410を返せるよう、状態を通知する
val r = registerEndpoint(client,device_id,endpoint.toUri().encodedPath!!)
@ -313,7 +313,7 @@ class PushSubscriptionHelper(
val clientIdentifier = "$accessToken$install_id".digestSHA256Base64Url()
val endpoint =
"${PollingWorker.APP_SERVER}/webpushcallback/${device_id.encodePercent()}/${account.acct.encodePercent()}/$flags/$clientIdentifier"
"${PollingWorker.APP_SERVER}/webpushcallback/${device_id.encodePercent()}/${account.acctAscii.encodePercent()}/$flags/$clientIdentifier"
if(oldSubscription?.endpoint == endpoint) {
// 既に登録済みで、endpointも一致している

View File

@ -3,6 +3,7 @@ package jp.juggler.util
import android.content.Context
import android.content.res.Resources
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.text.Spannable
import android.text.SpannableString
@ -335,3 +336,11 @@ fun <T> Array<T>.toHashSet() = HashSet<T>().also { it.addAll(this) }
//fun <T> Collection<T>.toHashSet() = HashSet<T>().also { it.addAll(this) }
//fun <T> Iterable<T>.toHashSet() = HashSet<T>().also { it.addAll(this) }
//fun <T> Sequence<T>.toHashSet() = HashSet<T>().also { it.addAll(this) }
fun defaultLocale(context:Context)=
if( Build.VERSION.SDK_INT >= 24){
context.resources.configuration.locales[0]
}else{
@Suppress("DEPRECATION")
context.resources.configuration.locale
}

View File

@ -16,7 +16,9 @@
package it.sephiroth.android.library.exif2
import android.content.Context
import android.graphics.Bitmap
import android.os.Build
import android.util.Log
import android.util.SparseIntArray
import org.apache.commons.io.IOUtils
@ -2381,13 +2383,15 @@ class ExifInterface {
val seconds = coord[2].toDouble()
ref = ref.substring(0, 1)
String.format(
Locale.ENGLISH,
"%1$.0f° %2$.0f' %3$.0f\" %4\$s",
degrees,
minutes,
seconds,
ref.toUpperCase(Locale.getDefault())
ref.toUpperCase(Locale.ENGLISH)
)
} catch(ex : Throwable) {
ex.printStackTrace()