1
0
mirror of https://github.com/tateisu/SubwayTooter synced 2025-02-04 04:37:40 +01:00

firebase messaging のtoken id の取得方法が非同期になった。文字列リソース中のパーセント表記のエスケープを修正。

This commit is contained in:
tateisu 2020-12-07 03:32:21 +09:00
parent 9913f89d95
commit 15a845e0e2
19 changed files with 1628 additions and 1604 deletions

View File

@ -68,6 +68,7 @@
<w>ihdr</w>
<w>infos</w>
<w>iptc</w>
<w>jetbrains</w>
<w>jfif</w>
<w>jwcp</w>
<w>kapt</w>

View File

@ -105,9 +105,10 @@ dependencies {
// PreferenceManager
implementation "androidx.preference:preference-ktx:1.1.1"
implementation "androidx.exifinterface:exifinterface:1.3.1"
implementation "androidx.exifinterface:exifinterface:1.3.2"
// CustomTabs
// TODO 1.3.0 調
implementation "androidx.browser:browser:1.2.0"
// Recyclerview
@ -123,7 +124,8 @@ dependencies {
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinx_coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinx_coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:$kotlinx_coroutines_version"
implementation("ru.gildor.coroutines:kotlin-coroutines-okhttp:1.0")
// Anko Layouts
// sdk15, sdk19, sdk21, sdk23 are also available

View File

@ -35,6 +35,7 @@ import jp.juggler.util.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody
@ -1580,7 +1581,9 @@ class ActAccountSetting : AsyncActivity(), View.OnClickListener,
)
override fun background(client : TootApiClient) : TootApiResult? {
return wps.updateSubscription(client, true)
return runBlocking{
wps.updateSubscription(client, true)
}
}
override fun handleResult(result : TootApiResult?) {

View File

@ -538,7 +538,11 @@ class Column(
// プロフカラムでのアカウント情報
@Volatile
internal var who_account : TootAccountRef? = null
// プロフカラムでのfeatured tag 情報(Mastodon3.3.0)
@Volatile
internal var who_featured_tags : List<TootTag>? = null
// リストカラムでのリスト情報
@Volatile
internal var list_info : TootList? = null
@ -1733,8 +1737,14 @@ class Column(
client.request(String.format(Locale.JAPAN, PATH_ACCOUNT, profile_id))?.also { result1 ->
TootAccountRef.mayNull(parser, parser.account(result1.jsonObject))?.also { a ->
this.who_account = a
this.who_featured_tags = null
client.request("/api/v1/accounts/${profile_id}/featured_tags")?.also{ result2->
this.who_featured_tags =TootTag.parseListOrNull(parser,result2.jsonArray)
}
client.publishApiProgress("") // カラムヘッダの再表示
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,7 @@ package jp.juggler.subwaytooter
import android.app.Dialog
import android.graphics.Color
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.style.ForegroundColorSpan
@ -11,19 +12,16 @@ import jp.juggler.emoji.EmojiMap
import jp.juggler.subwaytooter.action.Action_Follow
import jp.juggler.subwaytooter.action.Action_User
import jp.juggler.subwaytooter.api.*
import jp.juggler.subwaytooter.api.entity.TootAccount
import jp.juggler.subwaytooter.api.entity.TootAccountRef
import jp.juggler.subwaytooter.api.entity.TootStatus
import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.dialog.DlgTextInput
import jp.juggler.subwaytooter.span.EmojiImageSpan
import jp.juggler.subwaytooter.span.LinkInfo
import jp.juggler.subwaytooter.span.MyClickableSpan
import jp.juggler.subwaytooter.span.createSpan
import jp.juggler.subwaytooter.table.AcctColor
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.table.UserRelation
import jp.juggler.subwaytooter.util.DecodeOptions
import jp.juggler.subwaytooter.util.NetworkEmojiInvalidator
import jp.juggler.subwaytooter.util.openCustomTab
import jp.juggler.subwaytooter.util.startMargin
import jp.juggler.subwaytooter.util.*
import jp.juggler.subwaytooter.view.MyLinkMovementMethod
import jp.juggler.subwaytooter.view.MyNetworkImageView
import jp.juggler.subwaytooter.view.MyTextView
@ -38,6 +36,8 @@ internal class ViewHolderHeaderProfile(
private val ivBackground : MyNetworkImageView
private val tvCreated : TextView
private val tvLastStatusAt : TextView
private val tvFeaturedTags : TextView
private val ivAvatar : MyNetworkImageView
private val tvDisplayName : TextView
private val tvAcct : TextView
@ -79,6 +79,7 @@ internal class ViewHolderHeaderProfile(
llProfile = viewRoot.findViewById(R.id.llProfile)
tvCreated = viewRoot.findViewById(R.id.tvCreated)
tvLastStatusAt = viewRoot.findViewById(R.id.tvLastStatusAt)
tvFeaturedTags = viewRoot.findViewById(R.id.tvFeaturedTags)
ivAvatar = viewRoot.findViewById(R.id.ivAvatar)
tvDisplayName = viewRoot.findViewById(R.id.tvDisplayName)
tvAcct = viewRoot.findViewById(R.id.tvAcct)
@ -160,6 +161,7 @@ internal class ViewHolderHeaderProfile(
tvMovedName.textSize = f
tvMoved.textSize = f
tvPersonalNotes.textSize = f
tvFeaturedTags.textSize = f
}
f = activity.acct_font_size_sp
@ -187,7 +189,8 @@ internal class ViewHolderHeaderProfile(
btnStatusCount.textColor = contentColor
btnFollowing.textColor = contentColor
btnFollowers.textColor = contentColor
tvFeaturedTags.textColor = contentColor
setIconDrawableId(
activity,
btnMore,
@ -208,6 +211,7 @@ internal class ViewHolderHeaderProfile(
tvCreated.textColor = acctColor
tvMovedAcct.textColor = acctColor
tvLastStatusAt.textColor = acctColor
val whoRef = column.who_account
this.whoRef = whoRef
@ -231,6 +235,7 @@ internal class ViewHolderHeaderProfile(
relation = null
tvCreated.text = ""
tvLastStatusAt.vg(false)
tvFeaturedTags.vg(false)
ivBackground.setImageDrawable(null)
ivAvatar.setImageDrawable(null)
@ -259,6 +264,28 @@ internal class ViewHolderHeaderProfile(
invalidator = null,
fromProfileHeader = true
)
val featuredTagsText = column.who_featured_tags?.notEmpty()?.let{ tagList->
SpannableStringBuilder().apply {
append(activity.getString(R.string.featured_hashtags))
append(":")
tagList.forEach { tag ->
append(" ")
val tagWithSharp = "#" + tag.name
val start = length
append(tagWithSharp)
val end = length
tag.url?.notEmpty()?.let{ url->
val span = MyClickableSpan(LinkInfo(url = url, tag = tag.name, caption = tagWithSharp))
setSpan(span, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}
}
}
tvFeaturedTags.vg( featuredTagsText !=null)?.let{
it.text = featuredTagsText!!
it.movementMethod = MyLinkMovementMethod
}
ivBackground.setImageUrl(
activity.pref,

View File

@ -24,10 +24,10 @@ class TootParser(
val misskeyUserRelationMap = HashMap<EntityId, UserRelation>()
// val misskeyAccountDetailMap = HashMap<EntityId, TootAccount>()
val apiHost : Host?
val apiHost : Host
get() = linkHelper.apiHost
val apDomain : Host?
val apDomain : Host
get() = linkHelper.apDomain
init {

View File

@ -127,9 +127,10 @@ open class TootAccount(parser : TootParser, src : JsonObject) : HostAndDomain {
init {
this.json = src
src["_fromStream"] = parser.fromStream
if(parser.serviceType == ServiceType.MISSKEY) {
this.custom_emojis =
parseMapOrNull(CustomEmoji.decodeMisskey, src.jsonArray("emojis"))
this.profile_emojis = null

View File

@ -7,181 +7,187 @@ import jp.juggler.util.*
import java.util.regex.Pattern
open class TootTag constructor(
// The hashtag, not including the preceding #
val name : String,
val name: String,
// The URL of the hashtag. may null if generated from TootContext
val url : String? = null,
val url: String? = null,
// Mastodon /api/v2/search provides history.
val history : ArrayList<History>? = null
val history: ArrayList<History>? = null
) : TimelineItem() {
val countDaily : Int
val countWeekly : Int
val accountDaily : Int
val accountWeekly : Int
init {
countDaily = history?.first()?.uses ?: 0
countWeekly = history?.sumBy { it.uses } ?: 0
accountDaily = history?.first()?.accounts ?: 0
accountWeekly = history?.map { it.accounts }?.maxOrNull() ?: accountDaily
}
class History(src : JsonObject) {
private val day : Long
val uses : Int
val accounts : Int
init {
day = src.long("day")
?: throw RuntimeException("TootTrendTag.History: missing day")
uses = src.int("uses")
?: throw RuntimeException("TootTrendTag.History: missing uses")
accounts = src.int("accounts")
?: throw RuntimeException("TootTrendTag.History: missing accounts")
}
}
companion object {
val log = LogCategory("TootTag")
fun parse(parser : TootParser, src : JsonObject) =
if(parser.linkHelper.isMisskey) {
val name = src.stringOrThrow("tag")
val url = "https://${parser.apiHost}/tags/${Uri.encode(name)}"
TootTag(
val countDaily: Int
val countWeekly: Int
val accountDaily: Int
val accountWeekly: Int
init {
countDaily = history?.first()?.uses ?: 0
countWeekly = history?.sumBy { it.uses } ?: 0
accountDaily = history?.first()?.accounts ?: 0
accountWeekly = history?.map { it.accounts }?.maxOrNull() ?: accountDaily
}
class History(src: JsonObject) {
private val day: Long
val uses: Int
val accounts: Int
init {
day = src.long("day")
?: throw RuntimeException("TootTrendTag.History: missing day")
uses = src.int("uses")
?: throw RuntimeException("TootTrendTag.History: missing uses")
accounts = src.int("accounts")
?: throw RuntimeException("TootTrendTag.History: missing accounts")
}
}
companion object {
val log = LogCategory("TootTag")
private val reHeadSharp = """\A#""".toRegex()
private val reUserTagUrl = """/@[^/]+/tagged/""".toRegex()
fun parse(parser: TootParser, src: JsonObject) =
if (parser.linkHelper.isMisskey) {
val name = src.stringOrThrow("tag").replaceFirst(reHeadSharp, "")
TootTag(
name = name,
url = url,
history = null
url = "https://${parser.apiHost}/tags/${Uri.encode(name)}"
)
} else {
TootTag(
name = src.stringOrThrow("name"),
url = src.string("url"),
} else {
// /api/v1/accounts/$id/featured_tags の場合、
// name部分は先頭に#がついているかもしれない。必要なら除去するべき。
// url部分はユーザのタグTLになる。 https://nightly.fedibird.com/@noellabo/tagged/fedibird
// STはメニューから選べるので、URLは普通のタグURLの方が望ましい https://nightly.fedibird.com/tags/fedibird
TootTag(
name = src.stringOrThrow("name").replaceFirst(reHeadSharp, ""),
url = src.string("url")?.replaceFirst(reUserTagUrl, "/tags/"),
history = parseHistories(src.jsonArray("history"))
)
}
private fun parseHistories(src : JsonArray?) : ArrayList<History>? {
src ?: return null
val dst = ArrayList<History>()
src.objectList().forEach {
try {
dst.add(History(it))
} catch(ex : Throwable) {
log.e(ex, "parseHistories failed.")
}
}
return dst
}
fun parseListOrNull(parser : TootParser, array : JsonArray?) =
array?.mapNotNull { src->
try {
when(src) {
}
private fun parseHistories(src: JsonArray?): ArrayList<History>? {
src ?: return null
val dst = ArrayList<History>()
src.objectList().forEach {
try {
dst.add(History(it))
} catch (ex: Throwable) {
log.e(ex, "parseHistories failed.")
}
}
return dst
}
fun parseListOrNull(parser: TootParser, array: JsonArray?) =
array?.mapNotNull { src ->
try {
when (src) {
null -> null
"" -> null
is String -> TootTag(name = src)
is JsonObject -> parse(parser, src)
else->null
}
}catch(ex:Throwable){
log.e(ex,"parseListOrNull failed.")
null
}
}?.notEmpty()
fun parseList(parser : TootParser, array : JsonArray?) =
parseListOrNull(parser,array) ?: emptyList()
private const val w = TootAccount.reRubyWord
private const val a = TootAccount.reRubyAlpha
private const val s = "_\\u00B7\\u200c" // separators
private fun generateMastodonTagPattern() : Pattern {
val reMastodonTagName = """([_$w][$s$w]*[$s$a][$s$w]*[_$w])|([_$w]*[$a][_$w]*)"""
return """(?:^|[^\w/)])#($reMastodonTagName)""".asciiPattern()
}
private val reMastodonTag = generateMastodonTagPattern()
// https://medium.com/@alice/some-article#.abcdef123 => タグにならない
// https://en.wikipedia.org/wiki/Ghostbusters_(song)#Lawsuit => タグにならない
// # => #
// #3d => #3d
// #l33ts35k => #l33ts35k
// #world2016 => #world2016
// #_test => #_test
// #test_ => #test_
// #one·two·three· => 末尾の・はタグに含まれない。#one·two·three までがハッシュタグになる。
// #0123456' => 数字だけのハッシュタグはタグとして認識されない
// #000_000 => 認識される。orの前半分が機能してるらしい
//
// タグに使えない文字
// 入力補完用なのでやや緩め
private val reCharsNotTagMastodon = """[^$s$w$a]""".asciiPattern()
private val reCharsNotTagMisskey = """[\s.,!?'${'"'}:/\[\]【】]""".asciiPattern()
// find hashtags in content text(raw)
// returns null if hashtags not found, or ArrayList of String (tag without #)
fun findHashtags(src : String, isMisskey : Boolean) : ArrayList<String>? =
if(isMisskey) {
MisskeyMarkdownDecoder.findHashtags(src)
} else {
var result : ArrayList<String>? = null
val m = reMastodonTag.matcher(src)
while(m.find()) {
if(result == null) result = ArrayList()
result.add(m.groupEx(1) !!)
}
result
}
fun isValid(src : String, isMisskey : Boolean) =
if(isMisskey) {
! reCharsNotTagMisskey.matcher(src).find()
} else {
! reCharsNotTagMastodon.matcher(src).find()
}
// https://mastodon.juggler.jp/tags/%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E3%82%BF%E3%82%B0
// あるサービスは /tags/... でなく /tag/... を使う
private val reUrlHashTag = """\Ahttps://([^/]+)/tags?/([^?#・\s\-+.,:;/]+)(?:\z|[?#])"""
.asciiPattern()
// https://pixelfed.tokyo/discover/tags/SubwayTooter?src=hash
private val reUrlHashTagPixelfed =
"""\Ahttps://([^/]+)/discover/tags/([^?#・\s\-+.,:;/]+)(?:\z|[?#])"""
.asciiPattern()
// returns null or pair of ( decoded tag without sharp, host)
fun String.findHashtagFromUrl() : Pair<String, String>? {
var m = reUrlHashTag.matcher(this)
if(m.find()) {
val host = m.groupEx(1) !!
val tag = m.groupEx(2) !!.decodePercent()
return Pair(tag, host)
}
m = reUrlHashTagPixelfed.matcher(this)
if(m.find()) {
val host = m.groupEx(1) !!
val tag = m.groupEx(2) !!.decodePercent()
return Pair(tag, host)
}
return null
}
}
}
else -> null
}
} catch (ex: Throwable) {
log.e(ex, "parseListOrNull failed.")
null
}
}?.notEmpty()
fun parseList(parser: TootParser, array: JsonArray?) =
parseListOrNull(parser, array) ?: emptyList()
private const val w = TootAccount.reRubyWord
private const val a = TootAccount.reRubyAlpha
private const val s = "_\\u00B7\\u200c" // separators
private fun generateMastodonTagPattern(): Pattern {
val reMastodonTagName = """([_$w][$s$w]*[$s$a][$s$w]*[_$w])|([_$w]*[$a][_$w]*)"""
return """(?:^|[^\w/)])#($reMastodonTagName)""".asciiPattern()
}
private val reMastodonTag = generateMastodonTagPattern()
// https://medium.com/@alice/some-article#.abcdef123 => タグにならない
// https://en.wikipedia.org/wiki/Ghostbusters_(song)#Lawsuit => タグにならない
// # => #
// #3d => #3d
// #l33ts35k => #l33ts35k
// #world2016 => #world2016
// #_test => #_test
// #test_ => #test_
// #one·two·three· => 末尾の・はタグに含まれない。#one·two·three までがハッシュタグになる
// #0123456' => 数字だけのハッシュタグはタグとして認識されない。
// #000_000 => 認識される。orの前半分が機能してるらしい
//
// タグに使えない文字
// 入力補完用なのでやや緩め
private val reCharsNotTagMastodon = """[^$s$w$a]""".asciiPattern()
private val reCharsNotTagMisskey = """[\s.,!?'${'"'}:/\[\]【】]""".asciiPattern()
// find hashtags in content text(raw)
// returns null if hashtags not found, or ArrayList of String (tag without #)
fun findHashtags(src: String, isMisskey: Boolean): ArrayList<String>? =
if (isMisskey) {
MisskeyMarkdownDecoder.findHashtags(src)
} else {
var result: ArrayList<String>? = null
val m = reMastodonTag.matcher(src)
while (m.find()) {
if (result == null) result = ArrayList()
result.add(m.groupEx(1)!!)
}
result
}
fun isValid(src: String, isMisskey: Boolean) =
if (isMisskey) {
!reCharsNotTagMisskey.matcher(src).find()
} else {
!reCharsNotTagMastodon.matcher(src).find()
}
// https://mastodon.juggler.jp/tags/%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E3%82%BF%E3%82%B0
// あるサービスは /tags/... でなく /tag/... を使う
private val reUrlHashTag = """\Ahttps://([^/]+)/tags?/([^?#・\s\-+.,:;/]+)(?:\z|[?#])"""
.asciiPattern()
// https://pixelfed.tokyo/discover/tags/SubwayTooter?src=hash
private val reUrlHashTagPixelfed =
"""\Ahttps://([^/]+)/discover/tags/([^?#・\s\-+.,:;/]+)(?:\z|[?#])"""
.asciiPattern()
// returns null or pair of ( decoded tag without sharp, host)
fun String.findHashtagFromUrl(): Pair<String, String>? {
var m = reUrlHashTag.matcher(this)
if (m.find()) {
val host = m.groupEx(1)!!
val tag = m.groupEx(2)!!.decodePercent()
return Pair(tag, host)
}
m = reUrlHashTagPixelfed.matcher(this)
if (m.find()) {
val host = m.groupEx(1)!!
val tag = m.groupEx(2)!!.decodePercent()
return Pair(tag, host)
}
return null
}
}
}

View File

@ -150,11 +150,11 @@ class PushSubscriptionHelper(
}
}
private fun updateSubscriptionMisskey(client: TootApiClient): TootApiResult? {
private suspend fun updateSubscriptionMisskey(client: TootApiClient): TootApiResult? {
// 現在の購読状態を取得できないので、毎回購読の更新を行う
// FCMのデバイスIDを取得
val device_id = PollingWorker.getDeviceId(context)
val device_id = PollingWorker.getFirebaseMessagingToken(context)
?: return TootApiResult(error = context.getString(R.string.missing_fcm_device_id))
// アクセストークン
@ -221,7 +221,7 @@ class PushSubscriptionHelper(
}
}
private fun updateSubscriptionMastodon(client: TootApiClient, force: Boolean): TootApiResult? {
private suspend fun updateSubscriptionMastodon(client: TootApiClient, force: Boolean): TootApiResult? {
// 現在の購読状態を取得
// https://github.com/tootsuite/mastodon/pull/7471
@ -293,7 +293,7 @@ class PushSubscriptionHelper(
}
// FCMのデバイスIDを取得
val device_id = PollingWorker.getDeviceId(context)
val device_id = PollingWorker.getFirebaseMessagingToken(context)
?: return TootApiResult(error = context.getString(R.string.missing_fcm_device_id))
// アクセストークン
@ -508,7 +508,7 @@ class PushSubscriptionHelper(
}
}
fun updateSubscription(client: TootApiClient, force: Boolean = false): TootApiResult? =
suspend fun updateSubscription(client: TootApiClient, force: Boolean = false): TootApiResult? =
try {
when {
isRecentlyChecked() ->

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -15,25 +14,23 @@
android:id="@+id/tvMoved"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="3dp"
android:layout_marginEnd="12dp"
android:layout_marginStart="12dp"
android:layout_marginTop="6dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="3dp"
android:drawablePadding="4dp"
android:gravity="center"
tools:text="aaa\@bbb さんは引っ越しました"
/>
tools:text="aaa\@bbb さんは引っ越しました" />
<LinearLayout
android:id="@+id/llMoved"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:background="@drawable/btn_bg_transparent_round6dp"
android:gravity="center_vertical"
android:orientation="horizontal"
>
android:orientation="horizontal">
<jp.juggler.subwaytooter.view.MyNetworkImageView
android:id="@+id/ivMoved"
@ -49,33 +46,29 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
>
android:orientation="vertical">
<TextView
android:id="@+id/tvMovedName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="Follower Name"
/>
tools:text="Follower Name" />
<TextView
android:id="@+id/tvMovedAcct"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingEnd="4dp"
android:paddingStart="4dp"
android:paddingEnd="4dp"
android:textSize="12sp"
tools:text="aaaaaaaaaaaaaaaa"
/>
tools:text="aaaaaaaaaaaaaaaa" />
</LinearLayout>
<FrameLayout
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="4dp"
>
android:layout_marginStart="4dp">
<ImageButton
android:id="@+id/btnMoved"
@ -84,8 +77,7 @@
android:background="@drawable/btn_bg_transparent_round6dp"
android:contentDescription="@string/follow"
android:scaleType="center"
tools:src="@drawable/ic_follow_plus"
/>
tools:src="@drawable/ic_follow_plus" />
<ImageView
android:id="@+id/ivMovedBy"
@ -93,8 +85,7 @@
android:layout_height="match_parent"
android:scaleType="center"
android:src="@drawable/ic_followed_by"
tools:ignore="ContentDescription"
/>
tools:ignore="ContentDescription" />
</FrameLayout>
</LinearLayout>
@ -104,20 +95,17 @@
android:layout_height="wrap_content"
android:gravity="end"
android:textSize="12sp"
tools:text="xxxx-xx-xx xx:xx:xx"
/>
tools:text="xxxx-xx-xx xx:xx:xx" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
android:layout_height="wrap_content">
<jp.juggler.subwaytooter.view.MyNetworkImageView
android:id="@+id/ivBackground"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
/>
android:scaleType="centerCrop" />
<LinearLayout
android:id="@+id/llProfile"
@ -125,16 +113,14 @@
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="12dp"
>
android:padding="12dp">
<jp.juggler.subwaytooter.view.MyNetworkImageView
android:id="@+id/ivAvatar"
android:layout_width="128dp"
android:layout_height="128dp"
android:layout_marginTop="20dp"
android:background="@drawable/btn_bg_transparent_round6dp"
/>
android:background="@drawable/btn_bg_transparent_round6dp" />
<TextView
android:id="@+id/tvDisplayName"
@ -143,8 +129,7 @@
android:layout_marginTop="8dp"
android:gravity="center"
android:textSize="20sp"
tools:text="ディスプレイネームディスプレイネームディスプレイネーム"
/>
tools:text="ディスプレイネームディスプレイネームディスプレイネーム" />
<TextView
android:id="@+id/tvAcct"
@ -154,32 +139,28 @@
android:gravity="center"
android:textColor="?attr/colorLink"
tools:text="\@fugahogehogera\@jugemujyugemugokounosurikire.jp"
/>
tools:text="\@fugahogehogera\@jugemujyugemugokounosurikire.jp" />
<jp.juggler.subwaytooter.view.MyTextView
android:id="@+id/tvNote"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
tools:text="説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 "
/>
tools:text="説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 " />
<jp.juggler.subwaytooter.view.MyTextView
android:id="@+id/tvMisskeyExtra"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
tools:text="説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 "
/>
tools:text="説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 説明文 " />
</LinearLayout>
<FrameLayout
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
>
android:layout_marginTop="12dp">
<ImageButton
android:id="@+id/btnFollow"
@ -188,8 +169,7 @@
android:background="@drawable/btn_bg_transparent_round6dp"
android:contentDescription="@string/follow"
android:scaleType="center"
tools:src="@drawable/ic_follow_plus"
/>
tools:src="@drawable/ic_follow_plus" />
<ImageView
android:id="@+id/ivFollowedBy"
@ -197,61 +177,64 @@
android:layout_height="match_parent"
android:scaleType="center"
android:src="@drawable/ic_followed_by"
tools:ignore="ContentDescription"
/>
tools:ignore="ContentDescription" />
</FrameLayout>
</FrameLayout>
<LinearLayout
android:id="@+id/llFields"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:id="@+id/llFields"
android:orientation="vertical"
/>
android:orientation="vertical" />
<TextView
android:id="@+id/tvFeaturedTags"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="3dp" />
<TextView
android:id="@+id/tvLastStatusAt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:gravity="center"
android:textSize="12sp"
tools:text="xxxx-xx-xx xx:xx:xx"
/>
tools:text="xxxx-xx-xx xx:xx:xx" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="start"
android:text="@string/personal_notes"
/>
android:text="@string/personal_notes" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start|top"
android:orientation="horizontal"
>
android:orientation="horizontal">
<TextView
android:id="@+id/tvPersonalNotes"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start"
android:padding="12dp"
android:text="@string/personal_notes"
/>
android:text="@string/personal_notes" />
<ImageButton
android:id="@+id/btnPersonalNotesEdit"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@drawable/btn_bg_transparent_round6dp"
android:layout_marginStart="4dp"
android:id="@+id/btnPersonalNotesEdit"
android:src="@drawable/ic_edit"
android:background="@drawable/btn_bg_transparent_round6dp"
android:contentDescription="@string/edit"
/>
android:src="@drawable/ic_edit" />
</LinearLayout>
<TextView
@ -262,40 +245,35 @@
android:background="@drawable/btn_bg_transparent_round6dp"
android:gravity="center"
android:padding="12dp"
android:text="@string/remote_profile_warning"
/>
android:text="@string/remote_profile_warning" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:gravity="center"
android:orientation="horizontal"
>
android:orientation="horizontal">
<Button
android:id="@+id/btnStatusCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/btn_bg_transparent_round6dp"
tools:text="statuses\n124"
/>
tools:text="statuses\n124" />
<Button
android:id="@+id/btnFollowing"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/btn_bg_transparent_round6dp"
tools:text="following\n9999"
/>
tools:text="following\n9999" />
<Button
android:id="@+id/btnFollowers"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/btn_bg_transparent_round6dp"
tools:text="followers\n9999"
/>
tools:text="followers\n9999" />
<ImageButton
android:id="@+id/btnMore"
@ -304,10 +282,9 @@
android:background="@drawable/btn_bg_transparent_round6dp"
android:contentDescription="@string/more"
android:minWidth="48dp"
android:paddingEnd="4dp"
android:paddingStart="4dp"
android:src="@drawable/ic_more"
/>
android:paddingEnd="4dp"
android:src="@drawable/ic_more" />
</LinearLayout>

View File

@ -634,7 +634,7 @@
<string name="always_show_application">Mostra (mitjançant) el nom de l\'aplicació, si és possible</string>
<string name="force_gap_when_refresh">Forçar que s\'afegeixi una separació en refrescar la part superior</string>
<string name="avatar_icon_size">Mida d\'icona d\'avatar (Unitat:dp. per defecte:48. cal reiniciar aplicació)</string>
<string name="avatar_icon_round_ratio">Quocient d\'arrodoniment de la icona d\'avatar (Unitat:%. per defrecte:33. cal reiniciar aplicació)</string>
<string name="avatar_icon_round_ratio">Quocient d\'arrodoniment de la icona d\'avatar (Unitat:%%. per defrecte:33. cal reiniciar aplicació)</string>
<string name="avatar_icon_dont_round">No arrodonir els angles de la icona d\'avatar (cal reiniciar l\'aplicació)</string>
<string name="share_view_pool">Comparteix la visualització entre pissarres</string>
<string name="rename">Rebateja…</string>
@ -821,7 +821,7 @@
<string name="conversation_to">A:</string>
<string name="use_quote_toot">Fer ús de \"Nota citada\"</string>
<string name="mute_application_confirm">Vols silenciar l\'aplicació \"%1$s\"\?</string>
<string name="boost_button_alpha">Augmenta l\'opacitat del botó de difusió (Unitat:%. cal reiniciar aplicació. també afecta al text de contingut alfa.)</string>
<string name="boost_button_alpha">Augmenta l\'opacitat del botó de difusió (Unitat:%%. cal reiniciar aplicació. també afecta al text de contingut alfa.)</string>
<string name="toot_background_color">Color de fons dels brams</string>
<string name="column_color_default">Color per defecte de les pissarres</string>
<string name="header_background_color">Color de fons de la capçalera</string>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="app_name">Subway Tooter</string>
<string name="action_settings">Einstellungen</string>
<string name="account_add">Konto hinzufügen</string>
@ -321,7 +321,7 @@
<string name="read_gap_head">Lese Kopfspalt</string>
<string name="remote_only">Nur entfernt</string>
<string name="featured">Promotet</string>
<string name="announcements_period2">Ereignis-Zeitraum: %1$s...%2$s</string>
<string name="announcements_period2">Ereignis-Zeitraum: %1$s%2$s</string>
<string name="announcements_period1">Ereignis-Zeitraum: %1$s</string>
<string name="boost_with_visibility">Sichtbar boosten</string>
<string name="hide_totals">Verberge die Summe bis zum Ablauf</string>
@ -424,7 +424,7 @@
<string name="notification_type_mention">Nennung</string>
<string name="share_view_pool">Teile Anzeigenpool zwischen den Spalten</string>
<string name="allow_column_duplication">Erlaube Verdoppelung von Spalten</string>
<string name="avatar_icon_round_ratio">Abrundungsfaktor des Avatarbildes (Einheit: %. Standard: 33. App-Neustart erforderlich)</string>
<string name="avatar_icon_round_ratio">Abrundungsfaktor des Avatarbildes (Einheit: %%. Standard: 33. App-Neustart erforderlich)</string>
<string name="always_show_application">Zeige per App-Name, falls möglich</string>
<string name="domain_block_from_local">Kann Domänen auf deinem Server nicht blockieren.</string>
<string name="domain_block_from_pseudo">Kann Domäne nicht vom Pseudokonto aus blockieren.</string>
@ -569,11 +569,11 @@
<string name="endorse_set">In deinem Profil promoten</string>
<string name="already_voted">Bereits abgestimmt.</string>
<string name="toot_background_color">Hintergrundfarbe des Toots</string>
<string name="boost_button_alpha">Deckkraft des Boost-Buttons (Einheit: %. App-Neustart erforderlich; wird auch von der Transparenz des Inhaltstextes beeinflusst)</string>
<string name="boost_button_alpha">Deckkraft des Boost-Buttons (Einheit: %%. App-Neustart erforderlich; wird auch von der Transparenz des Inhaltstextes beeinflusst)</string>
<string name="mute_application_confirm">App \"%1$s\" stummschalten\?</string>
<string name="use_quote_toot">Benutze “Toot zitieren”. (Misskey oder individiualisierter Mastodonserver)</string>
<string name="conversation_to">An:</string>
<string name="participants_and_more">...und weitere</string>
<string name="participants_and_more">und weitere</string>
<string name="participants">Teilnehmer:</string>
<string name="show_conversation">Unterhaltung anzeigen</string>
<string name="around_toot_limitation_warning">Das Feature \"Timeline um die angegebene Zeit herum\" ist auf Mastodonserver ab Version 2.6.0 beschränkt. Eine etwas neuere Timeline kann nicht abgerufen werden.</string>
@ -982,7 +982,7 @@
<string name="show_quick_toot_bar">Zeige „Schnell-Toot“-Leiste (App-Neustart erforderlich)</string>
<string name="timeline_font_size">Schriftgröße der Timeline
\n(Einheit: Skalierbare Pixel / SP. Leer lassen, um Standard zu benutzen. App-Neustart erforderlich)
\n...
\n
\n…</string>
<string name="notification_sound">Benachrichtungston</string>
<string name="open_local_timeline_for">Timeline von \"%1$s\" öffnen</string>
@ -1007,7 +1007,7 @@
<string name="blocked_domains">Blockierte Domains</string>
<string name="url_omitted">(URL ausgelassen)</string>
<string name="enable_speech">Aktiviert Sprachausgabe</string>
<string name="account_change_failed_old_draft_has_no_in_reply_to_url">Konnte Konto nicht wechseln. Alte Entwurfsdaten besitzen kein in_reply_to_url, kann für gewählten Server nicht in in_reply_to_url umgewandelt werden.</string>
<string name="account_change_failed_old_draft_has_no_in_reply_to_url" tools:ignore="Typos">Konnte Konto nicht wechseln. Alte Entwurfsdaten besitzen kein in_reply_to_url, kann für gewählten Server nicht in in_reply_to_url umgewandelt werden.</string>
<string name="toot_button_default_account">Standardkonto, wenn der „Toot“-Button gedrückt wird</string>
<string name="notification_on_off_desc">Es gibt auch eine Konteneinstellung, um Benachrichtigungen ein- und auszuschalten.</string>
<string name="behavior">Verhalten</string>

View File

@ -602,7 +602,7 @@
<string name="always_show_application">Afficher (via) le nom de l\'application si possible</string>
<string name="force_gap_when_refresh">Forcer lajout d\'un espace lors du rafraîchissement du dessus</string>
<string name="avatar_icon_size">Taille de licône de lavatar (Unité:dp. par défaut:48. Redémarrage de lapp requis)</string>
<string name="avatar_icon_round_ratio">Ratio de larrondi de licône de lavatar (Unité:%. défaut:33. redémarrage de l\'application requis)</string>
<string name="avatar_icon_round_ratio">Ratio de larrondi de licône de lavatar (Unité:%%. défaut:33. redémarrage de l\'application requis)</string>
<string name="avatar_icon_dont_round">Ne pas arrondir les coins de lavatar (redémarrage requis)</string>
<string name="share_view_pool">Partager le pool de vues entre les colonnes</string>
<string name="rename">Renommer …</string>
@ -849,7 +849,7 @@
<string name="strip_icon_size">Taille de licône de la barre de colonne (Unité : dp. Défaut : 30. Redémarrage de lapplication requis)</string>
<string name="show_acct_in_system_notification">Afficher le compte (au lieu du nom dutilisateur) dans les notifications système</string>
<string name="account_tl_around">Fil public autour …</string>
<string name="boost_button_alpha">Transparence du bouton Boost (Unité:%. redémarrage de lapp nécessaire. Affecte également la transparence du contenu texte.)</string>
<string name="boost_button_alpha">Transparence du bouton Boost (Unité:%%. redémarrage de lapp nécessaire. Affecte également la transparence du contenu texte.)</string>
<string name="move_notifications_quick_filter_to_column_setting">Afficher le filtre rapide des notifications dans la configuration de la colonne (redémarrage de lapp requis)</string>
<string name="official_account">Compte officiel</string>
<string name="releases">Sorties</string>

View File

@ -72,7 +72,7 @@
<string name="auto_cw_prefix">(自動CW)</string>
<string name="available_mastodon_2_4_later">(マストドン2.4以降で利用可能)</string>
<string name="avatar_icon_dont_round">ユーザ画像を角丸にしない(アプリ再起動が必要)</string>
<string name="avatar_icon_round_ratio">ユーザ画像の角丸率(単位:%。デフォルト:33。アプリ再起動が必要)</string>
<string name="avatar_icon_round_ratio">ユーザ画像の角丸率(単位:%%。デフォルト:33。アプリ再起動が必要)</string>
<string name="avatar_icon_size">ユーザ画像の大きさ(単位:dp。デフォルト:48。アプリ再起動が必要)</string>
<string name="back_button_action">戻るボタンの動作</string>
<string name="back_to_column_list">戻るボタンでカラム一覧を開く</string>
@ -809,7 +809,7 @@
<string name="conversation_to">送り先:</string>
<string name="use_quote_toot">引用トゥートにする(MisskeyまたはカスタマイズされたMastodonサーバ)</string>
<string name="mute_application_confirm">アプリ \"%1$s\" はミュートされます。よろしいですか?</string>
<string name="boost_button_alpha">ブーストボタンのアルファ不透明度(単位:%。アプリの再起動が必要。本文テキスト色のアルファ値の影響も受けます)</string>
<string name="boost_button_alpha">ブーストボタンのアルファ不透明度(単位:%%。アプリの再起動が必要。本文テキスト色のアルファ値の影響も受けます)</string>
<string name="toot_background_color">トゥート背景色</string>
<string name="unlisted_visibility">\'未収載\' 公開範囲</string>
<string name="followers_visibility">\'フォロワーのみ\' 公開範囲</string>

View File

@ -611,7 +611,7 @@
<string name="always_show_application">가능하면 (경유) 앱 이름 보이기</string>
<string name="force_gap_when_refresh">위로 새로고침할 때 강제로 띄움 넣기</string>
<string name="avatar_icon_size">아바타 아이콘 크기 (단위:dp. 기본값:48. 앱 재시작 필요)</string>
<string name="avatar_icon_round_ratio">아바타 아이콘 곡률 (Unit:%. 기본값:33. 앱 재시작 필요)</string>
<string name="avatar_icon_round_ratio">아바타 아이콘 곡률 (Unit:%%. 기본값:33. 앱 재시작 필요)</string>
<string name="avatar_icon_dont_round">아바타 아이콘의 모서리를 둥글게 하지 않기 (앱 재시작 필요)</string>
<string name="share_view_pool">칼럼 사이에 뷰 풀을 공유</string>
<string name="rename">이름 변경…</string>
@ -796,7 +796,7 @@
<string name="conversation_to">받을 이:</string>
<string name="use_quote_toot">\"인용 리노트\" 사용</string>
<string name="mute_application_confirm">앱 \"%1$s\"를 음소거할까요\?</string>
<string name="boost_button_alpha">부스트 버튼 알파 불투명도 (단위:%. 앱 재시작 필요. 본문의 알파에 영향을 미침.)</string>
<string name="boost_button_alpha">부스트 버튼 알파 불투명도 (단위:%%. 앱 재시작 필요. 본문의 알파에 영향을 미침.)</string>
<string name="toot_background_color">툿 배경색</string>
<string name="column_color_default">칼럼 색 기본값</string>
<string name="header_background_color">헤더 배경색</string>

View File

@ -678,7 +678,7 @@
<string name="media_attachment_still_uploading">Media har ikke blitt lastet opp enda.</string>
<string name="highlight_word">Framhev nøkkelord</string>
<string name="avatar_icon_size">Avatarikonstørrelse (enhet:dp. Forvalg:48. Programomstart kreves)</string>
<string name="avatar_icon_round_ratio">Avatarikonstørrelseforhold (enhet:%. Forvalg:33. Programomstart kreves)</string>
<string name="avatar_icon_round_ratio">Avatarikonstørrelseforhold (enhet:%%. Forvalg:33. Programomstart kreves)</string>
<string name="avatar_icon_dont_round">Ikke avrund avatarikonets hjørner (programomstart kreves)</string>
<string name="allow_column_duplication">Tillat duplisering av kolonner</string>
<string name="system_notification_not_related">Ikke valgt når systemmerknad trykkes</string>

View File

@ -621,7 +621,7 @@
<string name="always_show_application">Show (via) application name if possible</string>
<string name="force_gap_when_refresh">Force adding gap when refreshing top</string>
<string name="avatar_icon_size">Avatar icon size (Unit:dp. default:48. app restart required)</string>
<string name="avatar_icon_round_ratio">Avatar icon round ratio (Unit:%. default:33. app restart required)</string>
<string name="avatar_icon_round_ratio">Avatar icon round ratio (Unit:%%. default:33. app restart required)</string>
<string name="avatar_icon_dont_round">Don\'t round corner of avatar icon (app restart required)</string>
<string name="share_view_pool">Share view pool between columns</string>
<string name="rename">Rename…</string>
@ -815,7 +815,7 @@
<string name="conversation_to">To:</string>
<string name="use_quote_toot">Use \"Quote Toot\". (Misskey or some customized Mastodon server)</string>
<string name="mute_application_confirm">Mute the app \"%1$s\"?</string>
<string name="boost_button_alpha">Boost button alpha opacity (Unit:%. app restart required. also affected of content text alpha.)</string>
<string name="boost_button_alpha">Boost button alpha opacity (Unit:%%. app restart required. also affected of content text alpha.)</string>
<string name="toot_background_color">Toot background color</string>
<string name="column_color_default">Column color default</string>
<string name="header_background_color">Header background color</string>

View File

@ -122,8 +122,8 @@ for my $lang ( sort keys %langs ){
# 残りの部分に%が登場したらエラー
my $sv = $value;
$sv =~ s/(%\d+\$[\d\.]*[sdxf])//g;
if( $sv =~ /%/ && not $sv=~/:%/ ){
$sv =~ s/(%\d+\$[\d\.]*[sdxf])|%%//g;
if( $sv =~ /%/ ){
$hasError =1;
print "!! ($lang)$name : broken param: $sv // $value\n";
}