add push policy in account settings

This commit is contained in:
tateisu 2021-05-11 15:12:43 +09:00
parent 970264bd3a
commit 3589e2a054
15 changed files with 372 additions and 304 deletions

View File

@ -22,6 +22,7 @@ import okio.ByteString
import org.junit.Assert.* import org.junit.Assert.*
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import java.util.concurrent.atomic.AtomicReference
@Suppress("MemberVisibilityCanPrivate") @Suppress("MemberVisibilityCanPrivate")
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@ -354,7 +355,7 @@ class TestTootApiClient {
// json error // json error
response = createResponseErrorCode() response = createResponseErrorCode()
message = TootApiClient.simplifyErrorHtml(response, response.body?.string() ?: "") message = TootApiClient.simplifyErrorHtml(response)
assertEquals("Error!", message) assertEquals("Error!", message)
// HTML error // HTML error
@ -367,7 +368,7 @@ class TestTootApiClient {
.body("""<html><body>Error!</body></html>""".toResponseBody(mediaTypeHtml)) .body("""<html><body>Error!</body></html>""".toResponseBody(mediaTypeHtml))
.build() .build()
message = TootApiClient.simplifyErrorHtml(response, response.body?.string() ?: "") message = TootApiClient.simplifyErrorHtml(response)
assertEquals("Error!", message) assertEquals("Error!", message)
// other error // other error
@ -383,7 +384,7 @@ class TestTootApiClient {
.body("Error!".toResponseBody("text/plain".toMediaType())) .body("Error!".toResponseBody("text/plain".toMediaType()))
.build() .build()
message = TootApiClient.simplifyErrorHtml(response, response.body?.string() ?: "") message =TootApiClient.simplifyErrorHtml(response)
assertEquals("Error!", message) assertEquals("Error!", message)
// empty body // empty body
@ -399,7 +400,7 @@ class TestTootApiClient {
.body("".toResponseBody("text/plain".toMediaType())) .body("".toResponseBody("text/plain".toMediaType()))
.build() .build()
message = TootApiClient.simplifyErrorHtml(response, response.body?.string() ?: "") message = TootApiClient.simplifyErrorHtml(response=response,caption="caption" )
assertEquals("", message) assertEquals("", message)
} }
@ -423,7 +424,7 @@ class TestTootApiClient {
.message("This is test") .message("This is test")
.build() .build()
message = TootApiClient.formatResponse(response, "caption", null) message = TootApiClient.formatResponse(response,"caption")
assertEquals("(HTTP 500 This is test) caption", message) assertEquals("(HTTP 500 This is test) caption", message)
@ -440,7 +441,7 @@ class TestTootApiClient {
.body("""{"error":"Error!"}""".toResponseBody(MEDIA_TYPE_JSON)) .body("""{"error":"Error!"}""".toResponseBody(MEDIA_TYPE_JSON))
.build() .build()
message = TootApiClient.formatResponse(response, "caption", null) message = TootApiClient.formatResponse(response,"caption")
assertEquals("Error! (HTTP 500 status-message) caption", message) assertEquals("Error! (HTTP 500 status-message) caption", message)
// json error (after reading body) // json error (after reading body)
@ -459,7 +460,7 @@ class TestTootApiClient {
bodyString = response.body?.string() bodyString = response.body?.string()
message = TootApiClient.formatResponse(response, "caption", bodyString) message = TootApiClient.formatResponse(response,"caption",bodyString)
assertEquals("Error! (HTTP 500 status-message) caption", message) assertEquals("Error! (HTTP 500 status-message) caption", message)
// without status message // without status message
@ -477,9 +478,8 @@ class TestTootApiClient {
bodyString = response.body?.string() bodyString = response.body?.string()
message = TootApiClient.formatResponse(response, "caption", bodyString) message = TootApiClient.formatResponse(response = response,caption = "caption",bodyString = bodyString)
assertEquals("Error! (HTTP 500) caption", message) assertEquals("Error! (HTTP 500) caption", message)
} }
@Test @Test
@ -1051,7 +1051,8 @@ class TestTootApiClient {
println(url) println(url)
// ブラウザからコールバックで受け取ったcodeを処理する // ブラウザからコールバックで受け取ったcodeを処理する
result = client.authentication2Mastodon(clientName, "DUMMY_CODE") val refToken = AtomicReference<String>(null)
result = client.authentication2Mastodon(clientName, "DUMMY_CODE",refToken)
jsonObject = result?.jsonObject jsonObject = result?.jsonObject
assertNotNull(jsonObject) assertNotNull(jsonObject)
if (jsonObject == null) return@runBlocking if (jsonObject == null) return@runBlocking

View File

@ -167,9 +167,15 @@ class ActAccountSetting : AsyncActivity(), View.OnClickListener,
private lateinit var spResizeImage: Spinner private lateinit var spResizeImage: Spinner
private class ResizeItems(val config: ResizeConfig, val caption: String) private lateinit var spPushPolicy: Spinner
private lateinit var imageResizeItems: List<ResizeItems> private class ResizeItem(val config: ResizeConfig, val caption: String)
private lateinit var imageResizeItems: List<ResizeItem>
private class PushPolicyItem(val id: String?, val caption: String)
private lateinit var pushPolicyItems: List<PushPolicyItem>
/////////////////////////////////////////////////// ///////////////////////////////////////////////////
@ -336,6 +342,7 @@ class ActAccountSetting : AsyncActivity(), View.OnClickListener,
etMediaSizeMax = findViewById(R.id.etMediaSizeMax) etMediaSizeMax = findViewById(R.id.etMediaSizeMax)
etMovieSizeMax = findViewById(R.id.etMovieSizeMax) etMovieSizeMax = findViewById(R.id.etMovieSizeMax)
spResizeImage = findViewById(R.id.spResizeImage) spResizeImage = findViewById(R.id.spResizeImage)
spPushPolicy = findViewById(R.id.spPushPolicy)
imageResizeItems = SavedAccount.resizeConfigList.map { imageResizeItems = SavedAccount.resizeConfigList.map {
val caption = when (it.type) { val caption = when (it.type) {
@ -355,7 +362,7 @@ class ActAccountSetting : AsyncActivity(), View.OnClickListener,
) )
} }
} }
ResizeItems(it, caption) ResizeItem(it, caption)
} }
spResizeImage.adapter = ArrayAdapter( spResizeImage.adapter = ArrayAdapter(
this, this,
@ -365,6 +372,22 @@ class ActAccountSetting : AsyncActivity(), View.OnClickListener,
setDropDownViewResource(R.layout.lv_spinner_dropdown) setDropDownViewResource(R.layout.lv_spinner_dropdown)
} }
pushPolicyItems = listOf(
PushPolicyItem(null, getString(R.string.unspecified)),
PushPolicyItem("all", getString(R.string.all)),
PushPolicyItem("followed", getString(R.string.following)),
PushPolicyItem("follower", getString(R.string.followers)),
PushPolicyItem("none", getString(R.string.no_one)),
)
spPushPolicy.adapter = ArrayAdapter(
this,
android.R.layout.simple_spinner_item,
pushPolicyItems.map { it.caption }.toTypedArray()
).apply {
setDropDownViewResource(R.layout.lv_spinner_dropdown)
}
listEtFieldName = arrayOf( listEtFieldName = arrayOf(
R.id.etFieldName1, R.id.etFieldName1,
R.id.etFieldName2, R.id.etFieldName2,
@ -438,6 +461,7 @@ class ActAccountSetting : AsyncActivity(), View.OnClickListener,
spResizeImage.onItemSelectedListener = this spResizeImage.onItemSelectedListener = this
spPushPolicy.onItemSelectedListener = this
btnNotificationStyleEditReply.vg(Pref.bpSeparateReplyNotificationGroup(pref)) btnNotificationStyleEditReply.vg(Pref.bpSeparateReplyNotificationGroup(pref))
@ -587,18 +611,28 @@ class ActAccountSetting : AsyncActivity(), View.OnClickListener,
etMediaSizeMax.setText(a.image_max_megabytes ?: "") etMediaSizeMax.setText(a.image_max_megabytes ?: "")
etMovieSizeMax.setText(a.movie_max_megabytes ?: "") etMovieSizeMax.setText(a.movie_max_megabytes ?: "")
} else { } else {
etMediaSizeMax.setText(a.image_max_megabytes etMediaSizeMax.setText(
?: a.getImageMaxBytes(ti).div(1000000).toString()) a.image_max_megabytes
etMovieSizeMax.setText(a.movie_max_megabytes ?: a.getImageMaxBytes(ti).div(1000000).toString()
?: a.getMovieMaxBytes(ti).div(1000000).toString()) )
etMovieSizeMax.setText(
a.movie_max_megabytes
?: a.getMovieMaxBytes(ti).div(1000000).toString()
)
} }
val currentResizeConfig = a.getResizeConfig() val currentResizeConfig = a.getResizeConfig()
var index = imageResizeItems.indexOfFirst { it.config.spec == currentResizeConfig.spec } var index = imageResizeItems.indexOfFirst { it.config.spec == currentResizeConfig.spec }
log.d("ResizeItem current ${currentResizeConfig.spec} index=$index ") log.d("ResizeItem current ${currentResizeConfig.spec} index=$index ")
if (index == -1) index = imageResizeItems.indexOfFirst { it.config.spec == SavedAccount.defaultResizeConfig.spec } if (index == -1) index =
imageResizeItems.indexOfFirst { it.config.spec == SavedAccount.defaultResizeConfig.spec }
spResizeImage.setSelection(index, false) spResizeImage.setSelection(index, false)
val currentPushPolicy = a.push_policy
index = pushPolicyItems.indexOfFirst { it.id == currentPushPolicy }
if (index == -1) index = 0
spPushPolicy.setSelection(index, false)
showVisibility() showVisibility()
showAcctColor() showAcctColor()
} }
@ -657,6 +691,9 @@ class ActAccountSetting : AsyncActivity(), View.OnClickListener,
?: SavedAccount.defaultResizeConfig ?: SavedAccount.defaultResizeConfig
).spec ).spec
account.push_policy =
pushPolicyItems.elementAtOrNull(spPushPolicy.selectedItemPosition)?.id
account.saveSetting() account.saveSetting()
} }

View File

@ -577,9 +577,9 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
if (client.isApiCancelled) return Pair(null, null) if (client.isApiCancelled) return Pair(null, null)
val response = requireNotNull(result.response) val response = result.response!!
if (!response.isSuccessful) { if (!response.isSuccessful) {
result.setError(TootApiClient.formatResponse(response, result.caption)) result.parseErrorResponse()
return Pair(result, null) return Pair(result, null)
} }
@ -594,7 +594,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
if (client.isApiCancelled) return Pair(null, null) if (client.isApiCancelled) return Pair(null, null)
return Pair(result, ba) return Pair(result, ba)
} catch (ex: Throwable) { } catch (ex: Throwable) {
result.setError(TootApiClient.formatResponse(response, result.caption, "?")) result.parseErrorResponse( "?")
return Pair(result, null) return Pair(result, null)
} }
} }

View File

@ -369,17 +369,14 @@ class ActPost : AsyncActivity(),
val request = Request.Builder().url(url).build() val request = Request.Builder().url(url).build()
val call = App1.ok_http_client.newCall(request) val call = App1.ok_http_client.newCall(request)
val response = call.await() val response = call.await()
if (response.isSuccessful) { if (response.isSuccessful) return true
return true
} log.e(TootApiClient.formatResponse(response,"check_exist failed."))
log.e(TootApiClient.formatResponse(response, "check_exist failed."))
} catch (ex: Throwable) { } catch (ex: Throwable) {
log.trace(ex) log.trace(ex)
} }
return false return false
} }
} }
private lateinit var btnAccount: Button private lateinit var btnAccount: Button

View File

@ -124,8 +124,9 @@ class App1 : Application() {
// 2020/9/20 56=>57 SavedAccountテーブルに項目追加 // 2020/9/20 56=>57 SavedAccountテーブルに項目追加
// 2020/9/20 57=>58 UserRelationテーブルに項目追加 // 2020/9/20 57=>58 UserRelationテーブルに項目追加
// 2021/2/10 58=>59 SavedAccountテーブルに項目追加 // 2021/2/10 58=>59 SavedAccountテーブルに項目追加
// 2021/5/11 59=>60 SavedAccountテーブルに項目追加
internal const val DB_VERSION = 59 internal const val DB_VERSION = 60
private val tableList = arrayOf( private val tableList = arrayOf(
LogData, LogData,

View File

@ -42,82 +42,10 @@ class TootApiClient(
private val reStartJsonArray = """\A\s*\[""".asciiPattern() private val reStartJsonArray = """\A\s*\[""".asciiPattern()
private val reStartJsonObject = """\A\s*\{""".asciiPattern() private val reStartJsonObject = """\A\s*\{""".asciiPattern()
private val reWhiteSpace = """\s+""".asciiPattern()
val DEFAULT_JSON_ERROR_PARSER = val DEFAULT_JSON_ERROR_PARSER =
{ json: JsonObject -> json["error"]?.toString() } { json: JsonObject -> json["error"]?.toString() }
internal fun simplifyErrorHtml(
response: Response,
sv: String,
jsonErrorParser: (json: JsonObject) -> String? = DEFAULT_JSON_ERROR_PARSER
): String {
// JsonObjectとして解釈できるならエラーメッセージを検出する
try {
val json = sv.decodeJsonObject()
val error_message =
jsonErrorParser(json)?.notEmpty()
if (error_message != null) {
return error_message
}
} catch (_: Throwable) {
}
// HTMLならタグの除去を試みる
val ct = response.body?.contentType()
if (ct?.subtype == "html") {
val decoded = DecodeOptions().decodeHTML(sv).toString()
return reWhiteSpace.matcher(decoded).replaceAll(" ").trim()
}
// XXX: Amazon S3 が403を返した場合にcontent-typeが?/xmlでserverがAmazonならXMLをパースしてエラーを整形することもできるが、多分必要ない
return reWhiteSpace.matcher(sv).replaceAll(" ").trim()
}
fun formatResponse(
response: Response,
caption: String,
bodyString: String? = null,
jsonErrorParser: (json: JsonObject) -> String? = DEFAULT_JSON_ERROR_PARSER
): String {
val sb = StringBuilder()
try {
// body は既に読み終わっているか、そうでなければこれから読む
if (bodyString != null) {
sb.append(simplifyErrorHtml(response, bodyString, jsonErrorParser))
} else {
try {
val string = response.body?.string()
if (string != null) {
sb.append(simplifyErrorHtml(response, string, jsonErrorParser))
}
} catch (ex: Throwable) {
log.e(ex, "missing response body.")
sb.append("(missing response body)")
}
}
if (sb.isNotEmpty()) sb.append(' ')
sb.append("(HTTP ").append(response.code.toString())
val message = response.message
if (message.isNotEmpty()) sb.append(' ').append(message)
sb.append(")")
if (caption.isNotEmpty()) {
sb.append(' ').append(caption)
}
} catch (ex: Throwable) {
log.trace(ex)
}
return sb.toString().replace("\n+".toRegex(), "\n")
}
fun getScopeString(ti: TootInstance?) = when { fun getScopeString(ti: TootInstance?) = when {
// 古いサーバ // 古いサーバ
ti?.versionGE(TootInstance.VERSION_2_4_0_rc1) == false -> "read+write+follow" ti?.versionGE(TootInstance.VERSION_2_4_0_rc1) == false -> "read+write+follow"
@ -198,6 +126,25 @@ class TootApiClient(
return encodeScopeArray(a) == encodeScopeArray(b) return encodeScopeArray(a) == encodeScopeArray(b)
} }
fun formatResponse(
response: Response,
caption: String = "?",
bodyString: String? = null
) = TootApiResult(
response = response,
caption = caption,
bodyString = bodyString
).apply { parseErrorResponse() }.error ?: "(null)"
fun simplifyErrorHtml(
response: Response,
caption:String = "?",
bodyString:String =response.body?.string() ?: "",
jsonErrorParser: (json: JsonObject) -> String? = DEFAULT_JSON_ERROR_PARSER
) = TootApiResult(
response = response,
caption = caption,
).simplifyErrorHtml( bodyString,jsonErrorParser)
} }
// 認証に関する設定を保存する // 認証に関する設定を保存する
@ -345,11 +292,8 @@ class TootApiClient(
} }
if (!response.isSuccessful || bodyString?.isEmpty() != false) { if (!response.isSuccessful || bodyString?.isEmpty() != false) {
result.parseErrorResponse(
result.error = formatResponse( bodyString?.notEmpty() ?: NO_INFORMATION,
response,
result.caption,
if (bodyString?.isNotEmpty() == true) bodyString else NO_INFORMATION,
jsonErrorParser jsonErrorParser
) )
} }
@ -388,11 +332,8 @@ class TootApiClient(
if (isApiCancelled) return null if (isApiCancelled) return null
if (!response.isSuccessful || bodyBytes?.isEmpty() != false) { if (!response.isSuccessful || bodyBytes?.isEmpty() != false) {
result.parseErrorResponse(
result.error = formatResponse( bodyBytes?.notEmpty()?.decodeUTF8() ?: NO_INFORMATION,
response,
result.caption,
if (bodyBytes?.isNotEmpty() == true) bodyBytes.decodeUTF8() else NO_INFORMATION,
jsonErrorParser jsonErrorParser
) )
} }
@ -411,16 +352,12 @@ class TootApiClient(
progressPath: String? = null, progressPath: String? = null,
jsonErrorParser: (json: JsonObject) -> String? = DEFAULT_JSON_ERROR_PARSER jsonErrorParser: (json: JsonObject) -> String? = DEFAULT_JSON_ERROR_PARSER
): TootApiResult? { ): TootApiResult? {
val response = result.response!! // nullにならないはず
try { try {
readBodyBytes(result, progressPath, jsonErrorParser) readBodyBytes(result, progressPath, jsonErrorParser)
?: return if (isApiCancelled) null else result ?: return if (isApiCancelled) null else result
} catch (ex: Throwable) { } catch (ex: Throwable) {
log.trace(ex) log.trace(ex)
result.error = result.parseErrorResponse(result.bodyString ?: NO_INFORMATION)
formatResponse(response, result.caption, result.bodyString ?: NO_INFORMATION)
} }
return result return result
} }
@ -430,19 +367,14 @@ class TootApiClient(
progressPath: String? = null, progressPath: String? = null,
jsonErrorParser: (json: JsonObject) -> String? = DEFAULT_JSON_ERROR_PARSER jsonErrorParser: (json: JsonObject) -> String? = DEFAULT_JSON_ERROR_PARSER
): TootApiResult? { ): TootApiResult? {
val response = result.response!! // nullにならないはず
try { try {
val bodyString = readBodyString(result, progressPath, jsonErrorParser) val bodyString = readBodyString(result, progressPath, jsonErrorParser)
?: return if (isApiCancelled) null else result ?: return if (isApiCancelled) null else result
result.data = bodyString result.data = bodyString
} catch (ex: Throwable) { } catch (ex: Throwable) {
log.trace(ex) log.trace(ex)
result.error = result.parseErrorResponse(result.bodyString ?: NO_INFORMATION)
formatResponse(response, result.caption, result.bodyString ?: NO_INFORMATION)
} }
return result return result
} }
@ -506,8 +438,7 @@ class TootApiClient(
} catch (ex: Throwable) { } catch (ex: Throwable) {
log.trace(ex) log.trace(ex)
result.error = result.parseErrorResponse(result.bodyString ?: NO_INFORMATION)
formatResponse(response, result.caption, result.bodyString ?: NO_INFORMATION)
} }
return result return result

View File

@ -1,5 +1,6 @@
package jp.juggler.subwaytooter.api package jp.juggler.subwaytooter.api
import jp.juggler.subwaytooter.util.DecodeOptions
import jp.juggler.util.* import jp.juggler.util.*
import okhttp3.Response import okhttp3.Response
import okhttp3.WebSocket import okhttp3.WebSocket
@ -8,13 +9,16 @@ open class TootApiResult(
@Suppress("unused") val dummy : Int = 0, @Suppress("unused") val dummy : Int = 0,
var error : String? = null, var error : String? = null,
var response : Response? = null, var response : Response? = null,
var caption : String = "?",
var bodyString : String? = null var bodyString : String? = null
) { ) {
companion object { companion object {
private val log = LogCategory("TootApiResult") private val log = LogCategory("TootApiResult")
private val reWhiteSpace = """\s+""".asciiPattern()
private val reLinkURL = """<([^>]+)>;\s*rel="([^"]+)"""".asciiPattern() private val reLinkURL = """<([^>]+)>;\s*rel="([^"]+)"""".asciiPattern()
fun makeWithCaption(caption : String?) : TootApiResult { fun makeWithCaption(caption : String?) : TootApiResult {
@ -52,7 +56,7 @@ open class TootApiResult(
var link_older : String? = null // より古いデータへのリンク var link_older : String? = null // より古いデータへのリンク
var link_newer : String? = null // より新しいデータへの var link_newer : String? = null // より新しいデータへの
var caption : String = "?"
constructor() : this(0) constructor() : this(0)
@ -97,4 +101,75 @@ open class TootApiResult(
} }
} }
} }
// アカウント作成APIのdetailsを読むため、エラー応答のjsonオブジェクトを保持する
var errorJson : JsonObject? = null
internal fun simplifyErrorHtml(
sv: String,
jsonErrorParser: (json: JsonObject) -> String? = TootApiClient.DEFAULT_JSON_ERROR_PARSER
): String {
val response = this.response!!
// JsonObjectとして解釈できるならエラーメッセージを検出する
try {
val json = sv.decodeJsonObject()
this.errorJson = json
jsonErrorParser(json)?.notEmpty()?.let{ return it }
} catch (_: Throwable) {
}
// HTMLならタグの除去を試みる
val ct = response.body?.contentType()
if (ct?.subtype == "html") {
val decoded = DecodeOptions().decodeHTML(sv).toString()
return reWhiteSpace.matcher(decoded).replaceAll(" ").trim()
}
// XXX: Amazon S3 が403を返した場合にcontent-typeが?/xmlでserverがAmazonならXMLをパースしてエラーを整形することもできるが、多分必要ない
return reWhiteSpace.matcher(sv).replaceAll(" ").trim()
}
fun parseErrorResponse(
bodyString: String? = null,
jsonErrorParser: (json: JsonObject) -> String? = TootApiClient.DEFAULT_JSON_ERROR_PARSER
){
val response = this.response!!
val sb = StringBuilder()
try {
// body は既に読み終わっているか、そうでなければこれから読む
if (bodyString != null) {
sb.append(simplifyErrorHtml( bodyString, jsonErrorParser))
} else {
try {
val string = response.body?.string()
if (string != null) {
sb.append(simplifyErrorHtml( string, jsonErrorParser))
}
} catch (ex: Throwable) {
log.e(ex, "missing response body.")
sb.append("(missing response body)")
}
}
if (sb.isNotEmpty()) sb.append(' ')
sb.append("(HTTP ").append(response.code.toString())
val message = response.message
if (message.isNotEmpty()) sb.append(' ').append(message)
sb.append(")")
if (caption.isNotEmpty()) {
sb.append(' ').append(caption)
}
} catch (ex: Throwable) {
log.trace(ex)
}
this.error = sb.toString().replace("\n+".toRegex(), "\n")
}
} }

View File

@ -397,7 +397,7 @@ class PollingWorker private constructor(contextArg: Context) {
private val workerNotifier = Channel<Unit>(capacity = Channel.CONFLATED) private val workerNotifier = Channel<Unit>(capacity = Channel.CONFLATED)
fun notifyWorker(){ fun notifyWorker() {
GlobalScope.launch { workerNotifier.send(Unit) } GlobalScope.launch { workerNotifier.send(Unit) }
} }
@ -607,7 +607,6 @@ class PollingWorker private constructor(contextArg: Context) {
} }
// JobService#onStopJob から呼ばれる // JobService#onStopJob から呼ばれる
// return True to indicate to the JobManager whether you'd like to reschedule this job based on the retry criteria provided at job creation-time. // return True to indicate to the JobManager whether you'd like to reschedule this job based on the retry criteria provided at job creation-time.
// return False to drop the job. Regardless of the value returned, your job must stop executing. // return False to drop the job. Regardless of the value returned, your job must stop executing.
@ -616,7 +615,7 @@ class PollingWorker private constructor(contextArg: Context) {
// 同じジョブ番号がジョブリストにあるか? // 同じジョブ番号がジョブリストにあるか?
synchronized(startedJobList) { synchronized(startedJobList) {
startedJobList.removeFirst { it.jobId == jobId }?.let{ item-> startedJobList.removeFirst { it.jobId == jobId }?.let { item ->
log.w("onStopJob: jobId=${jobId}, set cancel flag.") log.w("onStopJob: jobId=${jobId}, set cancel flag.")
// リソースがなくてStopされるのだからrescheduleはtrue // リソースがなくてStopされるのだからrescheduleはtrue
item.cancel(true) item.cancel(true)
@ -644,12 +643,11 @@ class PollingWorker private constructor(contextArg: Context) {
} }
// ポーリングが完了した // ポーリングが完了した
fun onPollingComplete(requiredNextPolling:Boolean) { fun onPollingComplete(requiredNextPolling: Boolean) {
when(requiredNextPolling) { when (requiredNextPolling) {
// まだスケジュールされてないなら登録する // まだスケジュールされてないなら登録する
true-> if (! scheduler.allPendingJobs.any { it.id == JobId.Polling.int } ) { true -> if (!scheduler.allPendingJobs.any { it.id == JobId.Polling.int }) {
log.d("registering next polling…") log.d("registering next polling…")
scheduleJob(context, JobId.Polling) scheduleJob(context, JobId.Polling)
} }
@ -664,7 +662,7 @@ class PollingWorker private constructor(contextArg: Context) {
} }
// ジョブ完了後にメインスレッドで呼ばれる // ジョブ完了後にメインスレッドで呼ばれる
fun onJobComplete(item:JobItem) { fun onJobComplete(item: JobItem) {
synchronized(startedJobList) { synchronized(startedJobList) {
startedJobList.remove(item) startedJobList.remove(item)
@ -683,7 +681,7 @@ class PollingWorker private constructor(contextArg: Context) {
} }
// return false if app data import started. // return false if app data import started.
fun onStartTask(taskId: TaskId):Boolean { fun onStartTask(taskId: TaskId): Boolean {
@Suppress("NON_EXHAUSTIVE_WHEN") @Suppress("NON_EXHAUSTIVE_WHEN")
when (taskId) { when (taskId) {
TaskId.AppDataImportBefore -> { TaskId.AppDataImportBefore -> {

View File

@ -16,10 +16,7 @@ import jp.juggler.subwaytooter.api.TootApiClient
import jp.juggler.subwaytooter.api.TootApiResult import jp.juggler.subwaytooter.api.TootApiResult
import jp.juggler.subwaytooter.api.TootParser import jp.juggler.subwaytooter.api.TootParser
import jp.juggler.subwaytooter.api.entity.* import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.table.AcctColor import jp.juggler.subwaytooter.table.*
import jp.juggler.subwaytooter.table.NotificationCache
import jp.juggler.subwaytooter.table.NotificationTracking
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.PushSubscriptionHelper import jp.juggler.subwaytooter.util.PushSubscriptionHelper
import jp.juggler.util.* import jp.juggler.util.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
@ -30,12 +27,12 @@ import kotlin.math.min
class TaskRunner( class TaskRunner(
private val pollingWorker: PollingWorker, private val pollingWorker: PollingWorker,
val job: JobItem, val job: JobItem,
private val taskId: TaskId, private val taskId: TaskId,
private val taskData: JsonObject private val taskData: JsonObject
) { ) {
companion object{ companion object {
private val log = LogCategory("TaskRunner") private val log = LogCategory("TaskRunner")
private var workerStatus = "" private var workerStatus = ""
@ -46,7 +43,7 @@ class TaskRunner(
} }
val context = pollingWorker.context val context = pollingWorker.context
val notification_manager = pollingWorker.notification_manager val notification_manager = pollingWorker.notification_manager
val pref = pollingWorker.pref val pref = pollingWorker.pref
val error_instance = ArrayList<String>() val error_instance = ArrayList<String>()
@ -70,7 +67,7 @@ class TaskRunner(
pollingWorker.processInjectedData(job.injectedAccounts) pollingWorker.processInjectedData(job.injectedAccounts)
TaskId.ResetTrackingState -> TaskId.ResetTrackingState ->
NotificationTracking.resetTrackingState( taskData.long(PollingWorker.EXTRA_DB_ID)) NotificationTracking.resetTrackingState(taskData.long(PollingWorker.EXTRA_DB_ID))
// プッシュ通知が届いた // プッシュ通知が届いた
TaskId.FcmMessage -> { TaskId.FcmMessage -> {
@ -106,7 +103,8 @@ class TaskRunner(
TaskId.NotificationDelete -> { TaskId.NotificationDelete -> {
val db_id = taskData.long(PollingWorker.EXTRA_DB_ID) val db_id = taskData.long(PollingWorker.EXTRA_DB_ID)
val type = TrackingType.parseStr(taskData.string(PollingWorker.EXTRA_NOTIFICATION_TYPE)) val type =
TrackingType.parseStr(taskData.string(PollingWorker.EXTRA_NOTIFICATION_TYPE))
val typeName = type.typeName val typeName = type.typeName
val id = taskData.string(PollingWorker.EXTRA_NOTIFICATION_ID) val id = taskData.string(PollingWorker.EXTRA_NOTIFICATION_ID)
log.d("Notification deleted! db_id=$db_id,type=$type,id=$id") log.d("Notification deleted! db_id=$db_id,type=$type,id=$id")
@ -118,7 +116,8 @@ class TaskRunner(
TaskId.NotificationClick -> { TaskId.NotificationClick -> {
val db_id = taskData.long(PollingWorker.EXTRA_DB_ID) val db_id = taskData.long(PollingWorker.EXTRA_DB_ID)
val type = TrackingType.parseStr(taskData.string(PollingWorker.EXTRA_NOTIFICATION_TYPE)) val type =
TrackingType.parseStr(taskData.string(PollingWorker.EXTRA_NOTIFICATION_TYPE))
val typeName = type.typeName val typeName = type.typeName
val id = taskData.string(PollingWorker.EXTRA_NOTIFICATION_ID).notEmpty() val id = taskData.string(PollingWorker.EXTRA_NOTIFICATION_ID).notEmpty()
log.d("Notification clicked! db_id=$db_id,type=$type,id=$id") log.d("Notification clicked! db_id=$db_id,type=$type,id=$id")
@ -132,7 +131,10 @@ class TaskRunner(
val itemTag = "$notification_tag/$id" val itemTag = "$notification_tag/$id"
notification_manager.cancel(itemTag, PollingWorker.NOTIFICATION_ID) notification_manager.cancel(itemTag, PollingWorker.NOTIFICATION_ID)
} else { } else {
notification_manager.cancel(notification_tag, PollingWorker.NOTIFICATION_ID) notification_manager.cancel(
notification_tag,
PollingWorker.NOTIFICATION_ID
)
} }
// DB更新処理 // DB更新処理
NotificationTracking.updateRead(db_id, typeName) NotificationTracking.updateRead(db_id, typeName)
@ -179,7 +181,8 @@ class TaskRunner(
liveSet.add(t.account.apiHost) liveSet.add(t.account.apiHost)
} }
if (liveSet.isEmpty()) break if (liveSet.isEmpty()) break
PollingWorker.workerStatus = "waiting ${liveSet.joinToString(", ") { it.pretty }}" PollingWorker.workerStatus =
"waiting ${liveSet.joinToString(", ") { it.pretty }}"
delay(if (job.isJobCancelled) 100L else 1000L) delay(if (job.isJobCancelled) 100L else 1000L)
} }
@ -208,6 +211,36 @@ class TaskRunner(
private var currentCall: WeakReference<Call>? = null private var currentCall: WeakReference<Call>? = null
private var policyFilter: (TootNotification) -> Boolean = when (account.push_policy) {
"followed" -> { it ->
val who = it.account
when {
who == null -> true
account.isMe(who) -> true
else -> UserRelation.load(account.db_id, who.id).following
}
}
"follower" -> { it ->
val who = it.account
when {
it.type == TootNotification.TYPE_FOLLOW ||
it.type == TootNotification.TYPE_FOLLOW_REQUEST -> true
who == null -> true
account.isMe(who) -> true
else -> UserRelation.load(account.db_id, who.id).followed_by
}
}
"none" -> { _ -> false }
else -> { _ -> true }
}
/////////////////// ///////////////////
val isActive: Boolean val isActive: Boolean
@ -353,14 +386,17 @@ class TaskRunner(
private val duplicate_check = HashSet<EntityId>() private val duplicate_check = HashSet<EntityId>()
private val dstListData = LinkedList<NotificationData>() private val dstListData = LinkedList<NotificationData>()
fun checkAccount() { fun checkAccount() {
this.nr = NotificationTracking.load(account.acct.pretty, account.db_id, trackingName) this.nr =
NotificationTracking.load(account.acct.pretty, account.db_id, trackingName)
fun JsonObject.isMention() = when (NotificationCache.parseNotificationType(account, this)) { fun JsonObject.isMention() =
TootNotification.TYPE_REPLY, TootNotification.TYPE_MENTION -> true when (NotificationCache.parseNotificationType(account, this)) {
else -> false TootNotification.TYPE_REPLY, TootNotification.TYPE_MENTION -> true
} else -> false
}
val jsonList = when (trackingType) { val jsonList = when (trackingType) {
@ -425,6 +461,9 @@ class TaskRunner(
} }
} }
// Mastodon 3.4.0rc1 push policy
if (!policyFilter(notification)) return
// 後から処理したものが先頭に来る // 後から処理したものが先頭に来る
dstListData.add(0, NotificationData(account, notification)) dstListData.add(0, NotificationData(account, notification))
} }
@ -692,7 +731,7 @@ class TaskRunner(
// FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY を付与してはいけない // FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY を付与してはいけない
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}, },
PendingIntent.FLAG_UPDATE_CURRENT or (if(Build.VERSION.SDK_INT>=23) PendingIntent.FLAG_IMMUTABLE else 0) PendingIntent.FLAG_UPDATE_CURRENT or (if (Build.VERSION.SDK_INT >= 23) PendingIntent.FLAG_IMMUTABLE else 0)
) )
) )
@ -705,7 +744,7 @@ class TaskRunner(
data = data =
"subwaytooter://notification_delete/?$params".toUri() "subwaytooter://notification_delete/?$params".toUri()
}, },
PendingIntent.FLAG_UPDATE_CURRENT or (if(Build.VERSION.SDK_INT>=23) PendingIntent.FLAG_IMMUTABLE else 0) PendingIntent.FLAG_UPDATE_CURRENT or (if (Build.VERSION.SDK_INT >= 23) PendingIntent.FLAG_IMMUTABLE else 0)
) )
) )
@ -730,7 +769,11 @@ class TaskRunner(
log.d("showNotification[${account.acct.pretty}] set notification...") log.d("showNotification[${account.acct.pretty}] set notification...")
notification_manager.notify(notification_tag, PollingWorker.NOTIFICATION_ID, builder.build()) notification_manager.notify(
notification_tag,
PollingWorker.NOTIFICATION_ID,
builder.build()
)
} }
} }
} }
@ -748,7 +791,7 @@ class TaskRunner(
context, context,
3, 3,
intent_click, intent_click,
PendingIntent.FLAG_UPDATE_CURRENT or (if(Build.VERSION.SDK_INT>=23) PendingIntent.FLAG_IMMUTABLE else 0) PendingIntent.FLAG_UPDATE_CURRENT or (if (Build.VERSION.SDK_INT >= 23) PendingIntent.FLAG_IMMUTABLE else 0)
) )
val builder = if (Build.VERSION.SDK_INT >= 26) { val builder = if (Build.VERSION.SDK_INT >= 26) {
@ -799,7 +842,6 @@ class TaskRunner(
} }
private fun NotificationData.getNotificationLine(): String { private fun NotificationData.getNotificationLine(): String {
val name = when (Pref.bpShowAcctInSystemNotification(pref)) { val name = when (Pref.bpShowAcctInSystemNotification(pref)) {
@ -861,7 +903,7 @@ class TaskRunner(
} }
private fun deleteCacheData(db_id: Long?) { private fun deleteCacheData(db_id: Long?) {
if(db_id != null) { if (db_id != null) {
log.d("Notification clear! db_id=$db_id") log.d("Notification clear! db_id=$db_id")
SavedAccount.loadAccount(context, db_id) ?: return SavedAccount.loadAccount(context, db_id) ?: return
NotificationCache.deleteCache(db_id) NotificationCache.deleteCache(db_id)

View File

@ -79,7 +79,9 @@ class SavedAccount(
var image_resize: String? = null var image_resize: String? = null
var image_max_megabytes : String? = null var image_max_megabytes : String? = null
var movie_max_megabytes : String? = null var movie_max_megabytes : String? = null
var push_policy : String? = null
init { init {
val tmpAcct = Acct.parse(acctArg) val tmpAcct = Acct.parse(acctArg)
this.username = tmpAcct.username this.username = tmpAcct.username
@ -165,6 +167,7 @@ class SavedAccount(
image_resize = cursor.getStringOrNull(COL_IMAGE_RESIZE) image_resize = cursor.getStringOrNull(COL_IMAGE_RESIZE)
image_max_megabytes = cursor.getStringOrNull(COL_IMAGE_MAX_MEGABYTES) image_max_megabytes = cursor.getStringOrNull(COL_IMAGE_MAX_MEGABYTES)
movie_max_megabytes = cursor.getStringOrNull(COL_MOVIE_MAX_MEGABYTES) movie_max_megabytes = cursor.getStringOrNull(COL_MOVIE_MAX_MEGABYTES)
push_policy = cursor.getStringOrNull(COL_PUSH_POLICY)
} }
val isNA : Boolean val isNA : Boolean
@ -232,6 +235,7 @@ class SavedAccount(
cv.putOrNull(COL_IMAGE_RESIZE,image_resize) cv.putOrNull(COL_IMAGE_RESIZE,image_resize)
cv.putOrNull(COL_IMAGE_MAX_MEGABYTES, image_max_megabytes) cv.putOrNull(COL_IMAGE_MAX_MEGABYTES, image_max_megabytes)
cv.putOrNull(COL_MOVIE_MAX_MEGABYTES, movie_max_megabytes) cv.putOrNull(COL_MOVIE_MAX_MEGABYTES, movie_max_megabytes)
cv.putOrNull(COL_PUSH_POLICY,push_policy)
// UIからは更新しない // UIからは更新しない
// notification_tag // notification_tag
@ -298,6 +302,7 @@ class SavedAccount(
this.image_resize = b.image_resize this.image_resize = b.image_resize
this.image_max_megabytes = b.image_max_megabytes this.image_max_megabytes = b.image_max_megabytes
this.movie_max_megabytes = b.movie_max_megabytes this.movie_max_megabytes = b.movie_max_megabytes
this.push_policy = b.push_policy
} }
fun getFullAcct(who : TootAccount?) = getFullAcct(who?.acct) fun getFullAcct(who : TootAccount?) = getFullAcct(who?.acct)
@ -400,6 +405,8 @@ class SavedAccount(
private const val COL_IMAGE_MAX_MEGABYTES = "image_max_megabytes" // スキーマ59 private const val COL_IMAGE_MAX_MEGABYTES = "image_max_megabytes" // スキーマ59
private const val COL_MOVIE_MAX_MEGABYTES = "movie_max_megabytes" // スキーマ59 private const val COL_MOVIE_MAX_MEGABYTES = "movie_max_megabytes" // スキーマ59
private const val COL_PUSH_POLICY = "push_policy" // スキーマ60
///////////////////////////////// /////////////////////////////////
// login information // login information
const val INVALID_DB_ID = - 1L const val INVALID_DB_ID = - 1L
@ -497,6 +504,9 @@ class SavedAccount(
+ ",$COL_IMAGE_MAX_MEGABYTES text default null" + ",$COL_IMAGE_MAX_MEGABYTES text default null"
+ ",$COL_MOVIE_MAX_MEGABYTES text default null" + ",$COL_MOVIE_MAX_MEGABYTES text default null"
// スキーマ60から
+ ",$COL_PUSH_POLICY text default null"
+ ")" + ")"
) )
db.execSQL("create index if not exists ${table}_user on ${table}(u)") db.execSQL("create index if not exists ${table}_user on ${table}(u)")
@ -504,7 +514,11 @@ class SavedAccount(
} }
override fun onDBUpgrade(db : SQLiteDatabase, oldVersion : Int, newVersion : Int) { override fun onDBUpgrade(db : SQLiteDatabase, oldVersion : Int, newVersion : Int) {
if(oldVersion < 2 && newVersion >= 2) { fun isUpgraded(n:Int,block:()->Unit){
if( oldVersion < n && newVersion >= n ) block()
}
isUpgraded(2){
try { try {
db.execSQL("alter table $table add column notification_mention integer default 1") db.execSQL("alter table $table add column notification_mention integer default 1")
} catch(ex : Throwable) { } catch(ex : Throwable) {
@ -530,7 +544,7 @@ class SavedAccount(
} }
} }
if(oldVersion < 10 && newVersion >= 10) { isUpgraded( 10) {
try { try {
db.execSQL("alter table $table add column $COL_CONFIRM_FOLLOW integer default 1") db.execSQL("alter table $table add column $COL_CONFIRM_FOLLOW integer default 1")
} catch(ex : Throwable) { } catch(ex : Throwable) {
@ -556,7 +570,7 @@ class SavedAccount(
} }
} }
if(oldVersion < 13 && newVersion >= 13) { isUpgraded( 13) {
try { try {
db.execSQL("alter table $table add column $COL_NOTIFICATION_TAG text default ''") db.execSQL("alter table $table add column $COL_NOTIFICATION_TAG text default ''")
} catch(ex : Throwable) { } catch(ex : Throwable) {
@ -564,7 +578,7 @@ class SavedAccount(
} }
} }
if(oldVersion < 14 && newVersion >= 14) { isUpgraded( 14) {
try { try {
db.execSQL("alter table $table add column $COL_REGISTER_KEY text default ''") db.execSQL("alter table $table add column $COL_REGISTER_KEY text default ''")
} catch(ex : Throwable) { } catch(ex : Throwable) {
@ -578,7 +592,7 @@ class SavedAccount(
} }
} }
if(oldVersion < 16 && newVersion >= 16) { isUpgraded( 16) {
try { try {
db.execSQL("alter table $table add column $COL_SOUND_URI text default ''") db.execSQL("alter table $table add column $COL_SOUND_URI text default ''")
} catch(ex : Throwable) { } catch(ex : Throwable) {
@ -586,7 +600,7 @@ class SavedAccount(
} }
} }
if(oldVersion < 18 && newVersion >= 18) { isUpgraded( 18) {
try { try {
db.execSQL("alter table $table add column $COL_DONT_SHOW_TIMEOUT integer default 0") db.execSQL("alter table $table add column $COL_DONT_SHOW_TIMEOUT integer default 0")
} catch(ex : Throwable) { } catch(ex : Throwable) {
@ -594,7 +608,7 @@ class SavedAccount(
} }
} }
if(oldVersion < 23 && newVersion >= 23) { isUpgraded( 23) {
try { try {
db.execSQL("alter table $table add column $COL_CONFIRM_FAVOURITE integer default 1") db.execSQL("alter table $table add column $COL_CONFIRM_FAVOURITE integer default 1")
} catch(ex : Throwable) { } catch(ex : Throwable) {
@ -602,7 +616,7 @@ class SavedAccount(
} }
} }
if(oldVersion < 24 && newVersion >= 24) { isUpgraded( 24) {
try { try {
db.execSQL("alter table $table add column $COL_CONFIRM_UNFAVOURITE integer default 1") db.execSQL("alter table $table add column $COL_CONFIRM_UNFAVOURITE integer default 1")
} catch(ex : Throwable) { } catch(ex : Throwable) {
@ -615,7 +629,7 @@ class SavedAccount(
} }
} }
if(oldVersion < 27 && newVersion >= 27) { isUpgraded( 27) {
try { try {
db.execSQL("alter table $table add column $COL_DEFAULT_TEXT text default ''") db.execSQL("alter table $table add column $COL_DEFAULT_TEXT text default ''")
} catch(ex : Throwable) { } catch(ex : Throwable) {
@ -623,15 +637,15 @@ class SavedAccount(
} }
} }
if(oldVersion < 28 && newVersion >= 28) { isUpgraded( 28) {
try { try {
db.execSQL("alter table $table add column $COL_MISSKEY_VERSION integer default 0") db.execSQL("alter table $table add column $COL_MISSKEY_VERSION integer default 0")
} catch(ex : Throwable) { } catch(ex : Throwable) {
log.trace(ex) log.trace(ex)
} }
} }
if(oldVersion < 33 && newVersion >= 33) { isUpgraded( 33) {
try { try {
db.execSQL("alter table $table add column $COL_NOTIFICATION_REACTION integer default 1") db.execSQL("alter table $table add column $COL_NOTIFICATION_REACTION integer default 1")
} catch(ex : Throwable) { } catch(ex : Throwable) {
@ -643,8 +657,8 @@ class SavedAccount(
log.trace(ex) log.trace(ex)
} }
} }
if(oldVersion < 38 && newVersion >= 38) { isUpgraded( 38) {
try { try {
db.execSQL("alter table $table add column $COL_DEFAULT_SENSITIVE integer default 0") db.execSQL("alter table $table add column $COL_DEFAULT_SENSITIVE integer default 0")
} catch(ex : Throwable) { } catch(ex : Throwable) {
@ -657,53 +671,53 @@ class SavedAccount(
} }
} }
if(oldVersion < 39 && newVersion >= 39) { isUpgraded( 39) {
try { try {
db.execSQL("alter table $table add column $COL_MAX_TOOT_CHARS integer default 0") db.execSQL("alter table $table add column $COL_MAX_TOOT_CHARS integer default 0")
} catch(ex : Throwable) { } catch(ex : Throwable) {
log.trace(ex) log.trace(ex)
} }
} }
if(oldVersion < 42 && newVersion >= 42) { isUpgraded( 42) {
try { try {
db.execSQL("alter table $table add column $COL_LAST_NOTIFICATION_ERROR text") db.execSQL("alter table $table add column $COL_LAST_NOTIFICATION_ERROR text")
} catch(ex : Throwable) { } catch(ex : Throwable) {
log.trace(ex) log.trace(ex)
} }
} }
if(oldVersion < 44 && newVersion >= 44) { isUpgraded( 44) {
try { try {
db.execSQL("alter table $table add column $COL_NOTIFICATION_FOLLOW_REQUEST integer default 1") db.execSQL("alter table $table add column $COL_NOTIFICATION_FOLLOW_REQUEST integer default 1")
} catch(ex : Throwable) { } catch(ex : Throwable) {
log.trace(ex) log.trace(ex)
} }
} }
if(oldVersion < 45 && newVersion >= 45) { isUpgraded( 45) {
try { try {
db.execSQL("alter table $table add column $COL_LAST_SUBSCRIPTION_ERROR text") db.execSQL("alter table $table add column $COL_LAST_SUBSCRIPTION_ERROR text")
} catch(ex : Throwable) { } catch(ex : Throwable) {
log.trace(ex) log.trace(ex)
} }
} }
if(oldVersion < 46 && newVersion >= 46) { isUpgraded(46) {
try { try {
db.execSQL("alter table $table add column $COL_LAST_PUSH_ENDPOINT text") db.execSQL("alter table $table add column $COL_LAST_PUSH_ENDPOINT text")
} catch(ex : Throwable) { } catch(ex : Throwable) {
log.trace(ex) log.trace(ex)
} }
} }
if(oldVersion < 56 && newVersion >= 56) { isUpgraded( 56) {
try { try {
db.execSQL("alter table $table add column $COL_DOMAIN text") db.execSQL("alter table $table add column $COL_DOMAIN text")
} catch(ex : Throwable) { } catch(ex : Throwable) {
log.trace(ex) log.trace(ex)
} }
} }
if(oldVersion < 57 && newVersion >= 57) { isUpgraded( 57) {
try { try {
db.execSQL("alter table $table add column $COL_NOTIFICATION_POST integer default 1") db.execSQL("alter table $table add column $COL_NOTIFICATION_POST integer default 1")
} catch(ex : Throwable) { } catch(ex : Throwable) {
@ -711,7 +725,7 @@ class SavedAccount(
} }
} }
if(oldVersion < 59 && newVersion >= 59) { isUpgraded( 59) {
try { try {
db.execSQL("alter table $table add column $COL_IMAGE_RESIZE text default null") db.execSQL("alter table $table add column $COL_IMAGE_RESIZE text default null")
} catch(ex : Throwable) { } catch(ex : Throwable) {
@ -728,6 +742,13 @@ class SavedAccount(
log.trace(ex) log.trace(ex)
} }
} }
isUpgraded( 60) {
try {
db.execSQL("alter table $table add column $COL_PUSH_POLICY text default null")
} catch(ex : Throwable) {
log.trace(ex)
}
}
} }
val defaultResizeConfig = ResizeConfig(ResizeType.LongSide, 1280) val defaultResizeConfig = ResizeConfig(ResizeType.LongSide, 1280)

View File

@ -480,6 +480,7 @@ class PushSubscriptionHelper(
}) })
put("data", JsonObject().apply { put("data", JsonObject().apply {
put("alerts", newAlerts) put("alerts", newAlerts)
account.push_policy?.let{ put("policy",it )}
}) })
} }

View File

@ -8,3 +8,6 @@ fun <E : List<*>> E?.notEmpty(): E? =
fun <E : Map<*, *>> E?.notEmpty(): E? = fun <E : Map<*, *>> E?.notEmpty(): E? =
if (this?.isNotEmpty() == true) this else null if (this?.isNotEmpty() == true) this else null
fun ByteArray?.notEmpty(): ByteArray? =
if (this?.isNotEmpty() == true) this else null

View File

@ -458,77 +458,45 @@
style="@style/setting_row_label" style="@style/setting_row_label"
android:text="@string/confirmation" /> android:text="@string/confirmation" />
<LinearLayout style="@style/setting_row_form"> <CheckBox
android:id="@+id/cbConfirmFollow"
style="@style/setting_row_form"
android:text="@string/follow" />
<CheckBox <CheckBox
android:id="@+id/cbConfirmFollow" android:id="@+id/cbConfirmFollowLockedUser"
style="@style/setting_horizontal_stretch" style="@style/setting_row_form"
android:text="@string/follow" /> android:text="@string/follow_locked_user" />
</LinearLayout> <CheckBox
android:id="@+id/cbConfirmUnfollow"
style="@style/setting_row_form"
android:text="@string/unfollow" />
<LinearLayout style="@style/setting_row_form"> <CheckBox
android:id="@+id/cbConfirmBoost"
style="@style/setting_row_form"
android:text="@string/boost" />
<CheckBox <CheckBox
android:id="@+id/cbConfirmFollowLockedUser" android:id="@+id/cbConfirmUnboost"
style="@style/setting_horizontal_stretch" style="@style/setting_row_form"
android:text="@string/follow_locked_user" /> android:text="@string/unboost" />
</LinearLayout> <CheckBox
android:id="@+id/cbConfirmFavourite"
style="@style/setting_row_form"
android:text="@string/favourite" />
<LinearLayout style="@style/setting_row_form"> <CheckBox
android:id="@+id/cbConfirmUnfavourite"
style="@style/setting_row_form"
android:text="@string/unfavourite" />
<CheckBox <CheckBox
android:id="@+id/cbConfirmUnfollow" android:id="@+id/cbConfirmToot"
style="@style/setting_horizontal_stretch" style="@style/setting_row_form"
android:text="@string/unfollow" /> android:text="@string/act_post" />
</LinearLayout>
<LinearLayout style="@style/setting_row_form">
<CheckBox
android:id="@+id/cbConfirmBoost"
style="@style/setting_wrap"
android:text="@string/boost" />
</LinearLayout>
<LinearLayout style="@style/setting_row_form">
<CheckBox
android:id="@+id/cbConfirmUnboost"
style="@style/setting_wrap"
android:text="@string/unboost" />
</LinearLayout>
<LinearLayout style="@style/setting_row_form">
<CheckBox
android:id="@+id/cbConfirmFavourite"
style="@style/setting_wrap"
android:text="@string/favourite" />
</LinearLayout>
<LinearLayout style="@style/setting_row_form">
<CheckBox
android:id="@+id/cbConfirmUnfavourite"
style="@style/setting_wrap"
android:text="@string/unfavourite" />
</LinearLayout>
<LinearLayout style="@style/setting_row_form">
<CheckBox
android:id="@+id/cbConfirmToot"
style="@style/setting_wrap"
android:text="@string/act_post" />
</LinearLayout>
<View style="@style/setting_divider" /> <View style="@style/setting_divider" />
@ -536,74 +504,63 @@
style="@style/setting_row_label" style="@style/setting_row_label"
android:text="@string/notifications" /> android:text="@string/notifications" />
<LinearLayout style="@style/setting_row_form"> <CheckBox
android:id="@+id/cbNotificationMention"
style="@style/setting_row_form"
android:text="@string/mention2" />
<CheckBox <CheckBox
android:id="@+id/cbNotificationMention" android:id="@+id/cbNotificationBoost"
style="@style/setting_horizontal_stretch" style="@style/setting_row_form"
android:text="@string/mention2" /> android:text="@string/boost" />
</LinearLayout>
<LinearLayout style="@style/setting_row_form"> <CheckBox
android:id="@+id/cbNotificationFavourite"
style="@style/setting_row_form"
android:text="@string/favourite" />
<CheckBox <CheckBox
android:id="@+id/cbNotificationBoost" android:id="@+id/cbNotificationFollow"
style="@style/setting_horizontal_stretch" style="@style/setting_row_form"
android:text="@string/boost" /> android:text="@string/follow" />
</LinearLayout>
<LinearLayout style="@style/setting_row_form"> <CheckBox
android:id="@+id/cbNotificationFollowRequest"
style="@style/setting_row_form"
android:text="@string/follow_request" />
<CheckBox <CheckBox
android:id="@+id/cbNotificationFavourite" android:id="@+id/cbNotificationReaction"
style="@style/setting_horizontal_stretch" style="@style/setting_row_form"
android:text="@string/favourite" /> android:text="@string/reaction" />
</LinearLayout>
<LinearLayout style="@style/setting_row_form"> <CheckBox
android:id="@+id/cbNotificationVote"
style="@style/setting_row_form"
android:text="@string/vote_polls" />
<CheckBox <CheckBox
android:id="@+id/cbNotificationFollow" android:id="@+id/cbNotificationPost"
style="@style/setting_horizontal_stretch" style="@style/setting_row_form"
android:text="@string/follow" /> android:text="@string/notification_type_post" />
</LinearLayout>
<LinearLayout style="@style/setting_row_form"> <TextView
style="@style/setting_row_form"
<CheckBox android:layout_marginTop="12dp"
android:id="@+id/cbNotificationFollowRequest" android:text="@string/push_notification_filter" />
style="@style/setting_horizontal_stretch"
android:text="@string/follow_request" />
</LinearLayout>
<LinearLayout style="@style/setting_row_form">
<CheckBox
android:id="@+id/cbNotificationReaction"
style="@style/setting_horizontal_stretch"
android:text="@string/reaction" />
</LinearLayout>
<LinearLayout style="@style/setting_row_form">
<CheckBox
android:id="@+id/cbNotificationVote"
style="@style/setting_horizontal_stretch"
android:text="@string/vote_polls" />
</LinearLayout>
<LinearLayout style="@style/setting_row_form">
<CheckBox
android:id="@+id/cbNotificationPost"
style="@style/setting_horizontal_stretch"
android:text="@string/notification_type_post" />
</LinearLayout>
<Spinner
android:id="@+id/spPushPolicy"
style="@style/setting_row_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="40dp"
/>
<Button <Button
android:id="@+id/btnPushSubscription" android:id="@+id/btnPushSubscription"
style="@style/setting_row_button" style="@style/setting_row_button"
android:layout_marginTop="12dp"
android:ellipsize="start" android:ellipsize="start"
android:text="@string/update_push_subscription" android:text="@string/update_push_subscription"
android:textAllCaps="false" /> android:textAllCaps="false" />

View File

@ -865,7 +865,7 @@
<string name="header_icon_size">ヘッダ部アイコンの大きさ(単位:dp。デフォルト:24。アプリ再起動が必要)</string> <string name="header_icon_size">ヘッダ部アイコンの大きさ(単位:dp。デフォルト:24。アプリ再起動が必要)</string>
<string name="header_text_size">ヘッダ部テキストの大きさ(単位:sp。デフォルト:14。アプリ再起動が必要)</string> <string name="header_text_size">ヘッダ部テキストの大きさ(単位:sp。デフォルト:14。アプリ再起動が必要)</string>
<string name="strip_icon_size">カラムストリップのアイコンの大きさ(単位:dp。デフォルト:30。アプリ再起動が必要)</string> <string name="strip_icon_size">カラムストリップのアイコンの大きさ(単位:dp。デフォルト:30。アプリ再起動が必要)</string>
<string name="all">All</string> <string name="all">全て</string>
<string name="show_links_in_context_menu">本文中のリンクをコンテキストメニューに表示する</string> <string name="show_links_in_context_menu">本文中のリンクをコンテキストメニューに表示する</string>
<string name="scheduled_status_requires_mastodon_2_7_0">予約投稿はマストドン2.7.0以降で使えます</string> <string name="scheduled_status_requires_mastodon_2_7_0">予約投稿はマストドン2.7.0以降で使えます</string>
<string name="move_notifications_quick_filter_to_column_setting">通知カラムのクイックフィルタをカラム設定内部に表示する(アプリ再起動が必要)</string> <string name="move_notifications_quick_filter_to_column_setting">通知カラムのクイックフィルタをカラム設定内部に表示する(アプリ再起動が必要)</string>
@ -1077,5 +1077,7 @@
<string name="update_mail_address">登録メールアドレスを変更する</string> <string name="update_mail_address">登録メールアドレスを変更する</string>
<string name="resend_confirm_mail_requested">確認メールの再送を要求しました。</string> <string name="resend_confirm_mail_requested">確認メールの再送を要求しました。</string>
<string name="confirm_mail_description">確認メールの再送を要求した後の手順:\n- あなたのメーラーで新着メールが届くのを確認する。\n- メール中の確認リンクを開く。\n- このダイアログを閉じてカラムをリロードする。</string> <string name="confirm_mail_description">確認メールの再送を要求した後の手順:\n- あなたのメーラーで新着メールが届くのを確認する。\n- メール中の確認リンクを開く。\n- このダイアログを閉じてカラムをリロードする。</string>
<string name="push_notification_filter">プッシュ通知フィルタ(Mastodon 3.4.0以降。プッシュ通知の更新が必要)</string>
<string name="no_one">誰もいない</string>
</resources> </resources>

View File

@ -1091,4 +1091,6 @@
<string name="update_mail_address">Update e-mail address</string> <string name="update_mail_address">Update e-mail address</string>
<string name="resend_confirm_mail_requested">Resending confirm E-mail was requested.</string> <string name="resend_confirm_mail_requested">Resending confirm E-mail was requested.</string>
<string name="confirm_mail_description">After requesting resending confirm E-mail,\n- please check the mail on your mailer.\n- open confirm link in the mail.\n- close this dialog and reload column.</string> <string name="confirm_mail_description">After requesting resending confirm E-mail,\n- please check the mail on your mailer.\n- open confirm link in the mail.\n- close this dialog and reload column.</string>
<string name="push_notification_filter">Push notification filter (Mastodon 3.4.0+, requires update push subscription)</string>
<string name="no_one">No one</string>
</resources> </resources>