From 23fb9cec4344b72b72663bf3c52402a6795a5e3f Mon Sep 17 00:00:00 2001 From: tateisu Date: Sun, 2 Feb 2020 03:28:16 +0900 Subject: [PATCH] improve IDN domain support --- .../juggler/subwaytooter/ActAccountSetting.kt | 26 +++--- .../jp/juggler/subwaytooter/ActAppSetting.kt | 4 +- .../subwaytooter/ActColumnCustomize.kt | 6 +- .../juggler/subwaytooter/ActKeywordFilter.kt | 8 +- .../java/jp/juggler/subwaytooter/ActMain.kt | 32 +++---- .../jp/juggler/subwaytooter/ActNickname.kt | 23 +++-- .../java/jp/juggler/subwaytooter/ActPost.kt | 38 +++++---- .../java/jp/juggler/subwaytooter/AppState.kt | 20 ++--- .../java/jp/juggler/subwaytooter/Column.kt | 38 ++++----- .../jp/juggler/subwaytooter/ColumnType.kt | 4 +- .../juggler/subwaytooter/ColumnViewHolder.kt | 8 +- .../jp/juggler/subwaytooter/DlgContextMenu.kt | 25 +++--- .../jp/juggler/subwaytooter/ItemViewHolder.kt | 10 +-- .../jp/juggler/subwaytooter/PollingWorker.kt | 32 ++++--- .../jp/juggler/subwaytooter/StreamReader.kt | 2 +- .../subwaytooter/ViewHolderHeaderInstance.kt | 4 +- .../subwaytooter/action/ActionUtils.kt | 21 ++--- .../subwaytooter/action/Action_Account.kt | 2 +- .../subwaytooter/action/Action_Filter.kt | 2 +- .../subwaytooter/action/Action_Follow.kt | 11 ++- .../subwaytooter/action/Action_HashTag.kt | 48 +++++++---- .../subwaytooter/action/Action_Instance.kt | 12 +-- .../subwaytooter/action/Action_ListMember.kt | 2 +- .../action/Action_Notification.kt | 2 +- .../subwaytooter/action/Action_Toot.kt | 72 ++++++++-------- .../subwaytooter/action/Action_User.kt | 30 +++---- .../subwaytooter/api/TootAccountMap.kt | 2 +- .../juggler/subwaytooter/api/TootApiClient.kt | 4 +- .../jp/juggler/subwaytooter/api/TootParser.kt | 2 +- .../subwaytooter/api/entity/TootAccount.kt | 42 +++++----- .../subwaytooter/api/entity/TootMention.kt | 12 ++- .../subwaytooter/api/entity/TootStatus.kt | 4 +- .../subwaytooter/dialog/AccountPicker.kt | 4 +- .../subwaytooter/dialog/DlgListMember.kt | 5 +- .../juggler/subwaytooter/dialog/ReportForm.kt | 4 +- .../juggler/subwaytooter/table/AcctColor.kt | 2 +- .../subwaytooter/table/SavedAccount.kt | 83 +++++++++++-------- .../juggler/subwaytooter/util/HTMLDecoder.kt | 42 ++++++---- .../juggler/subwaytooter/util/LinkHelper.kt | 39 +++++---- .../util/MisskeyMarkdownDecoder.kt | 20 +++-- .../subwaytooter/util/NotificationHelper.kt | 12 +-- .../juggler/subwaytooter/util/PostHelper.kt | 2 +- .../util/PushSubscriptionHelper.kt | 14 ++-- .../main/java/jp/juggler/util/StringUtils.kt | 9 ++ .../android/library/exif2/ExifInterface.kt | 6 +- 45 files changed, 427 insertions(+), 363 deletions(-) diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.kt b/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.kt index bed9004e..f0cf7f7f 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.kt @@ -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 ) diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActAppSetting.kt b/app/src/main/java/jp/juggler/subwaytooter/ActAppSetting.kt index 68ec9a9f..048f98ca 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActAppSetting.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActAppSetting.kt @@ -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 diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActColumnCustomize.kt b/app/src/main/java/jp/juggler/subwaytooter/ActColumnCustomize.kt index 584c08d8..1e0c9eb6 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActColumnCustomize.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActColumnCustomize.kt @@ -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 ) diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActKeywordFilter.kt b/app/src/main/java/jp/juggler/subwaytooter/ActKeywordFilter.kt index 324ae4aa..7393f5c1 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActKeywordFilter.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActKeywordFilter.kt @@ -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 } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActMain.kt b/app/src/main/java/jp/juggler/subwaytooter/ActMain.kt index 3cba0975..58289afa 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActMain.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActMain.kt @@ -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() 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() } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActNickname.kt b/app/src/main/java/jp/juggler/subwaytooter/ActNickname.kt index 2e144454..55a09b32 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActNickname.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActNickname.kt @@ -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(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 } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt b/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt index ad5a299c..d166549c 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt @@ -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() 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() diff --git a/app/src/main/java/jp/juggler/subwaytooter/AppState.kt b/app/src/main/java/jp/juggler/subwaytooter/AppState.kt index 10d2e9f6..66111a2c 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/AppState.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/AppState.kt @@ -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", diff --git a/app/src/main/java/jp/juggler/subwaytooter/Column.kt b/app/src/main/java/jp/juggler/subwaytooter/Column.kt index 278403b5..8e36e328 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/Column.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/Column.kt @@ -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 ) : 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(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(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(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(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) } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ColumnType.kt b/app/src/main/java/jp/juggler/subwaytooter/ColumnType.kt index 73113ece..1aabfbeb 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ColumnType.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ColumnType.kt @@ -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?, diff --git a/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.kt b/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.kt index 794b77b2..06c7ce64 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.kt @@ -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 -> diff --git a/app/src/main/java/jp/juggler/subwaytooter/DlgContextMenu.kt b/app/src/main/java/jp/juggler/subwaytooter/DlgContextMenu.kt index 97298af2..9787ad30 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/DlgContextMenu.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/DlgContextMenu.kt @@ -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 -> { diff --git a/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.kt b/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.kt index ca6a9d40..2d505f5c 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.kt @@ -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 { diff --git a/app/src/main/java/jp/juggler/subwaytooter/PollingWorker.kt b/app/src/main/java/jp/juggler/subwaytooter/PollingWorker.kt index aeee1439..2d6d561d 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/PollingWorker.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/PollingWorker.kt @@ -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()) } diff --git a/app/src/main/java/jp/juggler/subwaytooter/StreamReader.kt b/app/src/main/java/jp/juggler/subwaytooter/StreamReader.kt index 62a33325..dd829713 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/StreamReader.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/StreamReader.kt @@ -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 diff --git a/app/src/main/java/jp/juggler/subwaytooter/ViewHolderHeaderInstance.kt b/app/src/main/java/jp/juggler/subwaytooter/ViewHolderHeaderInstance.kt index bd92df94..f267b8e7 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ViewHolderHeaderInstance.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ViewHolderHeaderInstance.kt @@ -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) ) } diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/ActionUtils.kt b/app/src/main/java/jp/juggler/subwaytooter/action/ActionUtils.kt index 0558c857..dda821c3 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/ActionUtils.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/ActionUtils.kt @@ -99,10 +99,11 @@ fun makeAccountListNonPseudo( val list_other_host = ArrayList() 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 } diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Account.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Account.kt index a3965d5f..d0e7f904 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Account.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Account.kt @@ -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) diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Filter.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Filter.kt index 7317bca2..9441c046 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Filter.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Filter.kt @@ -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) } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Follow.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Follow.kt index 243d392a..97b828d2 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Follow.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Follow.kt @@ -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() diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_HashTag.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_HashTag.kt index 19a17fb4..305de762 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/Action_HashTag.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_HashTag.kt @@ -122,22 +122,34 @@ object Action_HashTag { val list_other = ArrayList() 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) } diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Instance.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Instance.kt index 12c7491f..97293007 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Instance.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Instance.kt @@ -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() 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) diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_ListMember.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_ListMember.kt index 2629e312..00949480 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/Action_ListMember.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_ListMember.kt @@ -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 } diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Notification.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Notification.kt index 9a16de42..d2ddb691 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Notification.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Notification.kt @@ -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() } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Toot.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Toot.kt index a174b02c..c0f1420a 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Toot.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Toot.kt @@ -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() 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() val other_account_list = ArrayList() @@ -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 } diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_User.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_User.kt index 244c3c89..35006de5 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/Action_User.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_User.kt @@ -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( diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/TootAccountMap.kt b/app/src/main/java/jp/juggler/subwaytooter/api/TootAccountMap.kt index 8155e8eb..d660001f 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/TootAccountMap.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/TootAccountMap.kt @@ -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) diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/TootApiClient.kt b/app/src/main/java/jp/juggler/subwaytooter/api/TootApiClient.kt index 3e74a2c5..bd83989d 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/TootApiClient.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/TootApiClient.kt @@ -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 } diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/TootParser.kt b/app/src/main/java/jp/juggler/subwaytooter/api/TootParser.kt index 612a97f2..1fb75c1d 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/TootParser.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/TootParser.kt @@ -23,7 +23,7 @@ class TootParser( val misskeyAccountDetailMap = HashMap() val accessHost : String? - get() = linkHelper.host + get() = linkHelper.hostAscii init { if(linkHelper.isMisskey) serviceType = ServiceType.MISSKEY diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAccount.kt b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAccount.kt index 29f2776c..2a8a6d7c 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAccount.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAccount.kt @@ -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 { - 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)}" + ) } } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootMention.kt b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootMention.kt index 6c8f30dc..c8e240f5 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootMention.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootMention.kt @@ -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") ) } diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.kt b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.kt index 56c46ddd..ac144648 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.kt @@ -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 } diff --git a/app/src/main/java/jp/juggler/subwaytooter/dialog/AccountPicker.kt b/app/src/main/java/jp/juggler/subwaytooter/dialog/AccountPicker.kt index 4938ed39..e3d890d6 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/dialog/AccountPicker.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/dialog/AccountPicker.kt @@ -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 diff --git a/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgListMember.kt b/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgListMember.kt index 250a6d86..ef051818 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgListMember.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgListMember.kt @@ -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)) { diff --git a/app/src/main/java/jp/juggler/subwaytooter/dialog/ReportForm.kt b/app/src/main/java/jp/juggler/subwaytooter/dialog/ReportForm.kt index 7a15dbff..e0e0768a 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/dialog/ReportForm.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/dialog/ReportForm.kt @@ -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 diff --git a/app/src/main/java/jp/juggler/subwaytooter/table/AcctColor.kt b/app/src/main/java/jp/juggler/subwaytooter/table/AcctColor.kt index 5c864135..40d27b58 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/table/AcctColor.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/table/AcctColor.kt @@ -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)) diff --git a/app/src/main/java/jp/juggler/subwaytooter/table/SavedAccount.kt b/app/src/main/java/jp/juggler/subwaytooter/table/SavedAccount.kt index a9ff8580..e5e54b39 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/table/SavedAccount.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/table/SavedAccount.kt @@ -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() + + } diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/HTMLDecoder.kt b/app/src/main/java/jp/juggler/subwaytooter/util/HTMLDecoder.kt index 871f8b58..e1816a27 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/HTMLDecoder.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/HTMLDecoder.kt @@ -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 ) } diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/LinkHelper.kt b/app/src/main/java/jp/juggler/subwaytooter/util/LinkHelper.kt index 6b184ad8..f24e3541 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/LinkHelper.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/LinkHelper.kt @@ -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 } diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/MisskeyMarkdownDecoder.kt b/app/src/main/java/jp/juggler/subwaytooter/util/MisskeyMarkdownDecoder.kt index ce90fdc6..ce367e81 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/MisskeyMarkdownDecoder.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/MisskeyMarkdownDecoder.kt @@ -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() ) } }), diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/NotificationHelper.kt b/app/src/main/java/jp/juggler/subwaytooter/util/NotificationHelper.kt index f6933981..411cd7ea 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/NotificationHelper.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/NotificationHelper.kt @@ -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; ) } diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/PostHelper.kt b/app/src/main/java/jp/juggler/subwaytooter/util/PostHelper.kt index c0be51bb..f4495d6c 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/PostHelper.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/PostHelper.kt @@ -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) } diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/PushSubscriptionHelper.kt b/app/src/main/java/jp/juggler/subwaytooter/util/PushSubscriptionHelper.kt index 1400c582..14e7fa16 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/PushSubscriptionHelper.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/PushSubscriptionHelper.kt @@ -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も一致している diff --git a/app/src/main/java/jp/juggler/util/StringUtils.kt b/app/src/main/java/jp/juggler/util/StringUtils.kt index 7da9d76e..553e72c8 100644 --- a/app/src/main/java/jp/juggler/util/StringUtils.kt +++ b/app/src/main/java/jp/juggler/util/StringUtils.kt @@ -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 Array.toHashSet() = HashSet().also { it.addAll(this) } //fun Collection.toHashSet() = HashSet().also { it.addAll(this) } //fun Iterable.toHashSet() = HashSet().also { it.addAll(this) } //fun Sequence.toHashSet() = HashSet().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 + } diff --git a/exif/src/main/java/it/sephiroth/android/library/exif2/ExifInterface.kt b/exif/src/main/java/it/sephiroth/android/library/exif2/ExifInterface.kt index b31ed9d9..74321cb9 100644 --- a/exif/src/main/java/it/sephiroth/android/library/exif2/ExifInterface.kt +++ b/exif/src/main/java/it/sephiroth/android/library/exif2/ExifInterface.kt @@ -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()