remove pre-2.4.0 style unsafe notification listener
This commit is contained in:
parent
67bb8e899c
commit
365485cf91
|
@ -220,11 +220,6 @@
|
|||
android:label="@string/nickname_and_color_and_notification_sound"
|
||||
android:windowSoftInputMode="adjustResize|stateAlwaysHidden"
|
||||
/>
|
||||
<activity
|
||||
android:name=".ActCustomStreamListener"
|
||||
android:label="@string/custom_stream_listener"
|
||||
android:windowSoftInputMode="adjustResize|stateAlwaysHidden"
|
||||
/>
|
||||
|
||||
<activity
|
||||
android:name=".ActText"
|
||||
|
|
|
@ -311,8 +311,6 @@ class ActAppSettingChild : AppCompatActivity()
|
|||
, R.id.btnTimelineFontReset
|
||||
, R.id.btnTimelineFontBoldEdit
|
||||
, R.id.btnTimelineFontBoldReset
|
||||
, R.id.btnCustomStreamListenerEdit
|
||||
, R.id.btnCustomStreamListenerReset
|
||||
, R.id.btnCcdHeaderBackgroundEdit
|
||||
, R.id.btnCcdHeaderBackgroundReset
|
||||
, R.id.btnCcdHeaderForegroundEdit
|
||||
|
@ -909,19 +907,6 @@ class ActAppSettingChild : AppCompatActivity()
|
|||
} catch(ex : Throwable) {
|
||||
showToast(this, ex, "could not open picker for font")
|
||||
}
|
||||
|
||||
R.id.btnCustomStreamListenerEdit -> ActCustomStreamListener.open(this)
|
||||
|
||||
R.id.btnCustomStreamListenerReset -> {
|
||||
pref.edit()
|
||||
.remove(Pref.spStreamListenerConfigUrl)
|
||||
.remove(Pref.spStreamListenerSecret)
|
||||
.remove(Pref.spStreamListenerConfigData)
|
||||
.apply()
|
||||
SavedAccount.clearRegistrationCache()
|
||||
PollingWorker.queueUpdateListener(this)
|
||||
showToast(this, false, getString(R.string.custom_stream_listener_was_reset))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,364 +0,0 @@
|
|||
package jp.juggler.subwaytooter
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.AsyncTask
|
||||
import android.os.Bundle
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
import jp.juggler.subwaytooter.api.TootApiClient
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.util.*
|
||||
import okhttp3.Request
|
||||
import org.hjson.JsonValue
|
||||
import java.util.regex.Pattern
|
||||
|
||||
class ActCustomStreamListener : AppCompatActivity(), View.OnClickListener, TextWatcher {
|
||||
|
||||
companion object {
|
||||
|
||||
internal val log = LogCategory("ActCustomStreamListener")
|
||||
|
||||
// internal val EXTRA_ACCT = "acct"
|
||||
|
||||
fun open(activity : Activity) {
|
||||
val intent = Intent(activity, ActCustomStreamListener::class.java)
|
||||
activity.startActivity(intent)
|
||||
}
|
||||
|
||||
internal const val STATE_STREAM_CONFIG_JSON = "stream_config_json"
|
||||
internal val reInstanceURL = Pattern.compile("\\Ahttps://[a-z0-9.-_:]+\\z")
|
||||
internal val reUpperCase = Pattern.compile("[A-Z]")
|
||||
internal val reUrl = Pattern.compile("\\Ahttps?://[\\w\\-?&#%~!$'()*+,/:;=@._\\[\\]]+\\z")
|
||||
}
|
||||
|
||||
private lateinit var etStreamListenerConfigurationUrl : EditText
|
||||
private lateinit var etStreamListenerSecret : EditText
|
||||
private lateinit var tvLog : TextView
|
||||
private lateinit var btnDiscard : View
|
||||
private lateinit var btnTest : View
|
||||
private lateinit var btnSave : View
|
||||
|
||||
internal var stream_config_json : String? = null
|
||||
|
||||
private var bLoading = false
|
||||
|
||||
private val isTestRunning : Boolean
|
||||
get() = last_task?.isCancelled ?: false
|
||||
|
||||
internal var last_task : AsyncTask<Void, Void, String?>? = null
|
||||
|
||||
override fun onCreate(savedInstanceState : Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
App1.setActivityTheme(this, false)
|
||||
|
||||
initUI()
|
||||
|
||||
if(savedInstanceState != null) {
|
||||
stream_config_json = savedInstanceState.getString(STATE_STREAM_CONFIG_JSON)
|
||||
} else {
|
||||
load()
|
||||
}
|
||||
|
||||
showButtonState()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState : Bundle?) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState ?: return
|
||||
|
||||
outState.putString(STATE_STREAM_CONFIG_JSON, stream_config_json)
|
||||
|
||||
}
|
||||
|
||||
private fun initUI() {
|
||||
setContentView(R.layout.act_custom_stream_listener)
|
||||
|
||||
Styler.fixHorizontalPadding(findViewById(R.id.llContent))
|
||||
|
||||
etStreamListenerConfigurationUrl = findViewById(R.id.etStreamListenerConfigurationUrl)
|
||||
etStreamListenerSecret = findViewById(R.id.etStreamListenerSecret)
|
||||
etStreamListenerConfigurationUrl.addTextChangedListener(this)
|
||||
etStreamListenerSecret.addTextChangedListener(this)
|
||||
|
||||
tvLog = findViewById(R.id.tvLog)
|
||||
|
||||
btnDiscard = findViewById(R.id.btnDiscard)
|
||||
btnTest = findViewById(R.id.btnTest)
|
||||
btnSave = findViewById(R.id.btnSave)
|
||||
|
||||
btnDiscard.setOnClickListener(this)
|
||||
btnTest.setOnClickListener(this)
|
||||
btnSave.setOnClickListener(this)
|
||||
}
|
||||
|
||||
private fun load() {
|
||||
bLoading = true
|
||||
|
||||
val pref = Pref.pref(this)
|
||||
|
||||
etStreamListenerConfigurationUrl.setText(Pref.spStreamListenerConfigUrl(pref))
|
||||
etStreamListenerSecret.setText(Pref.spStreamListenerSecret(pref))
|
||||
stream_config_json = null
|
||||
tvLog.text = getString(R.string.input_url_and_secret_then_test)
|
||||
|
||||
bLoading = false
|
||||
}
|
||||
|
||||
override fun beforeTextChanged(s : CharSequence, start : Int, count : Int, after : Int) {
|
||||
|
||||
}
|
||||
|
||||
override fun onTextChanged(s : CharSequence, start : Int, before : Int, count : Int) {
|
||||
|
||||
}
|
||||
|
||||
override fun afterTextChanged(s : Editable) {
|
||||
tvLog.text = getString(R.string.input_url_and_secret_then_test)
|
||||
stream_config_json = null
|
||||
showButtonState()
|
||||
}
|
||||
|
||||
private fun showButtonState() {
|
||||
btnSave.isEnabled = stream_config_json != null
|
||||
btnTest.isEnabled = ! isTestRunning
|
||||
}
|
||||
|
||||
override fun onClick(v : View) {
|
||||
when(v.id) {
|
||||
R.id.btnDiscard -> {
|
||||
etStreamListenerConfigurationUrl.hideKeyboard()
|
||||
finish()
|
||||
}
|
||||
|
||||
R.id.btnTest -> {
|
||||
etStreamListenerConfigurationUrl.hideKeyboard()
|
||||
startTest()
|
||||
}
|
||||
|
||||
R.id.btnSave -> {
|
||||
etStreamListenerConfigurationUrl.hideKeyboard()
|
||||
if(save()) {
|
||||
SavedAccount.clearRegistrationCache()
|
||||
PollingWorker.queueUpdateListener(this)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun save() : Boolean {
|
||||
if(stream_config_json == null) {
|
||||
showToast(this, false, "please test before save.")
|
||||
return false
|
||||
}
|
||||
|
||||
Pref.pref(this).edit()
|
||||
.put(
|
||||
Pref.spStreamListenerConfigUrl,
|
||||
etStreamListenerConfigurationUrl.text.toString().trim { it <= ' ' })
|
||||
.put(
|
||||
Pref.spStreamListenerSecret,
|
||||
etStreamListenerSecret.text.toString().trim { it <= ' ' })
|
||||
.put(Pref.spStreamListenerConfigData, stream_config_json ?: "")
|
||||
.apply()
|
||||
return true
|
||||
}
|
||||
|
||||
internal fun addLog(line : String) {
|
||||
runOnMainLooper {
|
||||
val old = tvLog.text.toString()
|
||||
tvLog.text = if(old.isEmpty()) line else old + "\n" + line
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private fun startTest() {
|
||||
val strSecret = etStreamListenerSecret.text.toString().trim { it <= ' ' }
|
||||
val strUrl = etStreamListenerConfigurationUrl.text.toString().trim { it <= ' ' }
|
||||
stream_config_json = null
|
||||
showButtonState()
|
||||
|
||||
val task = object : AsyncTask<Void, Void, String?>() {
|
||||
override fun doInBackground(vararg params : Void) : String? {
|
||||
try {
|
||||
|
||||
while(true) {
|
||||
|
||||
if(! Pref.bpSendAccessTokenToAppServer(Pref.pref(this@ActCustomStreamListener))) {
|
||||
addLog("we won't use push notification until 'SendAccessTokenToAppServer' is not set.")
|
||||
break
|
||||
}
|
||||
|
||||
if(strSecret.isEmpty()) {
|
||||
addLog("Secret is empty. Custom Listener is not used.")
|
||||
break
|
||||
} else if(strUrl.isEmpty()) {
|
||||
addLog("Configuration URL is empty. Custom Listener is not used.")
|
||||
break
|
||||
}
|
||||
|
||||
addLog("try to loading Configuration data from URL…")
|
||||
var builder : Request.Builder = Request.Builder()
|
||||
.url(strUrl)
|
||||
|
||||
var call = App1.ok_http_client.newCall(builder.build())
|
||||
|
||||
val response = call.execute()
|
||||
|
||||
val bodyString : String? = try {
|
||||
response.body()?.string()
|
||||
} catch(ex : Throwable) {
|
||||
log.trace(ex)
|
||||
null
|
||||
}
|
||||
|
||||
if(! response.isSuccessful || bodyString?.isEmpty() != false) {
|
||||
addLog(
|
||||
TootApiClient.formatResponse(
|
||||
response,
|
||||
"Can't get configuration from URL.",
|
||||
bodyString
|
||||
)
|
||||
)
|
||||
break
|
||||
}
|
||||
|
||||
val jv : JsonValue = try {
|
||||
JsonValue.readHjson(bodyString)
|
||||
} catch(ex : Throwable) {
|
||||
log.trace(ex)
|
||||
addLog(ex.withCaption("Can't parse configuration data."))
|
||||
break
|
||||
}
|
||||
|
||||
if(! jv.isObject) {
|
||||
addLog("configuration data is not JSON Object.")
|
||||
break
|
||||
}
|
||||
val root = jv.asObject()
|
||||
|
||||
var has_wildcard = false
|
||||
var has_error = false
|
||||
for(member in root) {
|
||||
val strInstance = member.name
|
||||
if("*" == strInstance) {
|
||||
has_wildcard = true
|
||||
} else if(reUpperCase.matcher(strInstance).find()) {
|
||||
addLog("$strInstance : instance URL must be lower case.")
|
||||
has_error = true
|
||||
continue
|
||||
} else if(strInstance[strInstance.length - 1] == '/') {
|
||||
addLog("$strInstance : instance URL must not be trailed with '/'.")
|
||||
has_error = true
|
||||
continue
|
||||
} else if(! reInstanceURL.matcher(strInstance).find()) {
|
||||
addLog("$strInstance : instance URL is not like https://.....")
|
||||
has_error = true
|
||||
continue
|
||||
}
|
||||
val entry_value = member.value
|
||||
if(! entry_value.isObject) {
|
||||
addLog("$strInstance : value for this instance is not JSON Object.")
|
||||
has_error = true
|
||||
continue
|
||||
}
|
||||
val entry = entry_value.asObject()
|
||||
|
||||
val keys = arrayOf(
|
||||
"urlStreamingListenerRegister",
|
||||
"urlStreamingListenerUnregister",
|
||||
"appId"
|
||||
)
|
||||
for(key in keys) {
|
||||
val v = entry.get(key)
|
||||
if(! v.isString) {
|
||||
addLog("$strInstance.$key : missing parameter, or data type is not string.")
|
||||
has_error = true
|
||||
continue
|
||||
}
|
||||
val sv = v.asString()
|
||||
if(sv.isEmpty()) {
|
||||
addLog("$strInstance.$key : empty parameter.")
|
||||
has_error = true
|
||||
} else if(sv.contains(" ")) {
|
||||
addLog("$strInstance.$key : contains whitespace.")
|
||||
has_error = true
|
||||
}
|
||||
|
||||
if("appId" != key) {
|
||||
if(! reUrl.matcher(sv).find()) {
|
||||
addLog("$strInstance.$key : not like Url.")
|
||||
has_error = true
|
||||
} else if(sv.startsWith("https://")) {
|
||||
try {
|
||||
addLog("check access to $sv …")
|
||||
builder = Request.Builder().url(sv)
|
||||
call = App1.ok_http_client.newCall(builder.build())
|
||||
call.execute()
|
||||
} catch(ex : Throwable) {
|
||||
log.trace(ex)
|
||||
addLog(ex.withCaption("$strInstance.$key : connect failed."))
|
||||
has_error = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(! has_wildcard) {
|
||||
addLog("Warning: This configuration has no wildcard entry.")
|
||||
if(! has_error) {
|
||||
for(sa in SavedAccount.loadAccountList(this@ActCustomStreamListener)) {
|
||||
if(sa.isPseudo) continue
|
||||
val instanceUrl = ("https://" + sa.host).toLowerCase()
|
||||
val v = root.get(instanceUrl)
|
||||
if(v == null || ! v.isObject) {
|
||||
addLog("Warning: $instanceUrl : is found in account, but not found in configuration data.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(has_error) {
|
||||
addLog("This configuration has error. ")
|
||||
break
|
||||
}
|
||||
|
||||
return bodyString
|
||||
}
|
||||
|
||||
} catch(ex : Throwable) {
|
||||
log.trace(ex)
|
||||
addLog(ex.withCaption("Can't read configuration from URL."))
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onCancelled(s : String?) {
|
||||
onPostExecute(s)
|
||||
}
|
||||
|
||||
override fun onPostExecute(s : String?) {
|
||||
last_task = null
|
||||
if(s != null) {
|
||||
stream_config_json = s
|
||||
addLog("seems configuration is ok.")
|
||||
} else {
|
||||
addLog("error detected.")
|
||||
}
|
||||
showButtonState()
|
||||
}
|
||||
|
||||
}
|
||||
last_task = task
|
||||
task.executeOnExecutor(App1.task_executor)
|
||||
}
|
||||
|
||||
}
|
|
@ -20,38 +20,27 @@ import android.os.PowerManager
|
|||
import android.os.SystemClock
|
||||
import android.support.v4.app.NotificationCompat
|
||||
import android.support.v4.content.ContextCompat
|
||||
|
||||
import com.google.firebase.iid.FirebaseInstanceId
|
||||
import jp.juggler.subwaytooter.api.*
|
||||
|
||||
import org.hjson.JsonObject
|
||||
import org.hjson.JsonValue
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.ArrayList
|
||||
import java.util.Collections
|
||||
import java.util.Comparator
|
||||
import java.util.HashSet
|
||||
import java.util.LinkedList
|
||||
import java.util.TreeSet
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
import jp.juggler.subwaytooter.api.entity.TootNotification
|
||||
import jp.juggler.subwaytooter.api.TootApiCallback
|
||||
import jp.juggler.subwaytooter.api.TootApiClient
|
||||
import jp.juggler.subwaytooter.api.TootParser
|
||||
import jp.juggler.subwaytooter.api.entity.EntityId
|
||||
import jp.juggler.subwaytooter.api.entity.EntityIdLong
|
||||
import jp.juggler.subwaytooter.api.entity.TootNotification
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatus
|
||||
import jp.juggler.subwaytooter.table.*
|
||||
import jp.juggler.subwaytooter.util.*
|
||||
import jp.juggler.util.*
|
||||
import okhttp3.Call
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
class PollingWorker private constructor(contextArg : Context) {
|
||||
|
||||
|
@ -102,7 +91,6 @@ class PollingWorker private constructor(contextArg : Context) {
|
|||
const val TASK_NOTIFICATION_DELETE = 10
|
||||
const val TASK_NOTIFICATION_CLICK = 11
|
||||
private const val TASK_UPDATE_NOTIFICATION = 12
|
||||
private const val TASK_UPDATE_LISTENER = 13
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private var sInstance : PollingWorker? = null
|
||||
|
@ -266,10 +254,6 @@ class PollingWorker private constructor(contextArg : Context) {
|
|||
|
||||
}
|
||||
|
||||
fun queueUpdateListener(context : Context) {
|
||||
addTask(context, true, TASK_UPDATE_LISTENER, null)
|
||||
}
|
||||
|
||||
fun queueUpdateNotification(context : Context) {
|
||||
addTask(context, true, TASK_UPDATE_NOTIFICATION, null)
|
||||
}
|
||||
|
@ -420,7 +404,7 @@ class PollingWorker private constructor(contextArg : Context) {
|
|||
private val appState : AppState
|
||||
internal val handler : Handler
|
||||
internal val pref : SharedPreferences
|
||||
internal val connectivityManager : ConnectivityManager
|
||||
private val connectivityManager : ConnectivityManager
|
||||
internal val notification_manager : NotificationManager
|
||||
internal val scheduler : JobScheduler
|
||||
private val power_manager : PowerManager?
|
||||
|
@ -819,10 +803,6 @@ class PollingWorker private constructor(contextArg : Context) {
|
|||
|
||||
internal inner class TaskRunner {
|
||||
|
||||
var mCustomStreamListenerSecret : String? = null
|
||||
var mCustomStreamListenerSettingString : String? = null
|
||||
private var mCustomStreamListenerSetting : JsonObject? = null
|
||||
|
||||
lateinit var job : JobItem
|
||||
private var taskId : Int = 0
|
||||
|
||||
|
@ -938,8 +918,6 @@ class PollingWorker private constructor(contextArg : Context) {
|
|||
|
||||
}
|
||||
|
||||
loadCustomStreamListenerSetting()
|
||||
|
||||
job_status.set("make install id")
|
||||
|
||||
// インストールIDを生成する
|
||||
|
@ -1051,22 +1029,6 @@ class PollingWorker private constructor(contextArg : Context) {
|
|||
notification_manager.notify(NOTIFICATION_ID_ERROR, builder.build())
|
||||
}
|
||||
|
||||
private fun loadCustomStreamListenerSetting() {
|
||||
mCustomStreamListenerSetting = null
|
||||
mCustomStreamListenerSecret = null
|
||||
val jsonString = Pref.spStreamListenerConfigData(pref)
|
||||
mCustomStreamListenerSettingString = jsonString
|
||||
if(jsonString.isNotEmpty()) {
|
||||
try {
|
||||
mCustomStreamListenerSetting = JsonValue.readHjson(jsonString).asObject()
|
||||
mCustomStreamListenerSecret = Pref.spStreamListenerSecret(pref)
|
||||
} catch(ex : Throwable) {
|
||||
log.trace(ex)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
internal inner class AccountThread(
|
||||
val account : SavedAccount
|
||||
) : Thread(), CurrentCallCallback {
|
||||
|
@ -1123,18 +1085,8 @@ class PollingWorker private constructor(contextArg : Context) {
|
|||
}
|
||||
|
||||
if(job.isJobCancelled) return
|
||||
if(wps.flags == 0) {
|
||||
if(! account.isMisskey) unregisterDeviceToken()
|
||||
return
|
||||
}
|
||||
|
||||
if(wps.subscribed) {
|
||||
if(! account.isMisskey) unregisterDeviceToken()
|
||||
} else {
|
||||
if(! account.isMisskey) registerDeviceToken()
|
||||
}
|
||||
|
||||
if(job.isJobCancelled) return
|
||||
if(wps.flags == 0) return
|
||||
|
||||
checkAccount()
|
||||
|
||||
|
@ -1149,155 +1101,6 @@ class PollingWorker private constructor(contextArg : Context) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun unregisterDeviceToken() {
|
||||
try {
|
||||
if(SavedAccount.REGISTER_KEY_UNREGISTERED == account.register_key) {
|
||||
log.d("unregisterDeviceToken: already unregistered.")
|
||||
return
|
||||
}
|
||||
|
||||
// ネットワーク的な事情でインストールIDを取得できなかったのなら、何もしない
|
||||
val install_id = job.install_id
|
||||
if(install_id?.isEmpty() != false) {
|
||||
log.d("unregisterDeviceToken: missing install_id")
|
||||
return
|
||||
}
|
||||
|
||||
val tag = account.notification_tag
|
||||
if(tag?.isEmpty() != false) {
|
||||
log.d("unregisterDeviceToken: missing notification_tag")
|
||||
return
|
||||
}
|
||||
|
||||
val post_data = ("instance_url=" + ("https://" + account.host).encodePercent()
|
||||
+ "&app_id=" + context.packageName.encodePercent()
|
||||
+ "&tag=" + tag)
|
||||
|
||||
val request = post_data.toRequestBody().toPost()
|
||||
.url("$APP_SERVER/unregister")
|
||||
.build()
|
||||
|
||||
val call = App1.ok_http_client.newCall(request)
|
||||
current_call = call
|
||||
|
||||
val response = call.execute()
|
||||
|
||||
log.d("unregisterDeviceToken: %s", response)
|
||||
|
||||
if(response.isSuccessful) {
|
||||
account.register_key = SavedAccount.REGISTER_KEY_UNREGISTERED
|
||||
account.register_time = 0L
|
||||
account.saveRegisterKey()
|
||||
}
|
||||
|
||||
} catch(ex : Throwable) {
|
||||
log.trace(ex)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun registerDeviceToken() {
|
||||
try {
|
||||
// 設定によってはデバイストークンやアクセストークンを送信しない
|
||||
if(! Pref.bpSendAccessTokenToAppServer(Pref.pref(context))) {
|
||||
log.d("registerDeviceToken: SendAccessTokenToAppServer is not set.")
|
||||
return
|
||||
}
|
||||
|
||||
// ネットワーク的な事情でインストールIDを取得できなかったのなら、何もしない
|
||||
val install_id = job.install_id
|
||||
if(install_id?.isEmpty() != false) {
|
||||
log.d("registerDeviceToken: missing install id")
|
||||
return
|
||||
}
|
||||
|
||||
val prefDevice = PrefDevice.prefDevice(context)
|
||||
|
||||
val device_token = prefDevice.getString(PrefDevice.KEY_DEVICE_TOKEN, null)
|
||||
if(device_token?.isEmpty() != false) {
|
||||
log.d("registerDeviceToken: missing device_token")
|
||||
return
|
||||
}
|
||||
|
||||
val access_token = account.getAccessToken()
|
||||
if(access_token?.isEmpty() != false) {
|
||||
log.d("registerDeviceToken: missing access_token")
|
||||
return
|
||||
}
|
||||
|
||||
var tag : String? = account.notification_tag
|
||||
|
||||
if(SavedAccount.REGISTER_KEY_UNREGISTERED == account.register_key) {
|
||||
tag = null
|
||||
}
|
||||
|
||||
if(tag?.isEmpty() != false) {
|
||||
account.notification_tag =
|
||||
(job.install_id + account.db_id + account.acct).digestSHA256Hex()
|
||||
tag = account.notification_tag
|
||||
account.saveNotificationTag()
|
||||
}
|
||||
|
||||
val reg_key = (tag
|
||||
+ access_token
|
||||
+ device_token
|
||||
+ (if(mCustomStreamListenerSecret == null) "" else mCustomStreamListenerSecret)
|
||||
+ if(mCustomStreamListenerSettingString == null) "" else mCustomStreamListenerSettingString
|
||||
).digestSHA256Hex()
|
||||
|
||||
val now = System.currentTimeMillis()
|
||||
if(reg_key == account.register_key && now - account.register_time < 3600000 * 3) {
|
||||
// タグやトークンが同一なら、前回登録に成功してから一定時間は再登録しない
|
||||
log.d("registerDeviceToken: already registered.")
|
||||
return
|
||||
}
|
||||
|
||||
val post_data = StringBuilder()
|
||||
.append("instance_url=").append(("https://" + account.host).encodePercent())
|
||||
.append("&app_id=").append(context.packageName.encodePercent())
|
||||
.append("&tag=").append(tag)
|
||||
.append("&access_token=").append(access_token)
|
||||
.append("&device_token=").append(device_token)
|
||||
|
||||
val jsonString = mCustomStreamListenerSettingString
|
||||
val appSecret = mCustomStreamListenerSecret
|
||||
|
||||
if(jsonString != null && appSecret != null) {
|
||||
post_data.append("&user_config=").append(jsonString.encodePercent())
|
||||
post_data.append("&app_secret=").append(appSecret.encodePercent())
|
||||
}
|
||||
|
||||
val request = post_data.toString().toRequestBody().toPost()
|
||||
.url("$APP_SERVER/register")
|
||||
.build()
|
||||
|
||||
val call = App1.ok_http_client.newCall(request)
|
||||
current_call = call
|
||||
|
||||
val response = call.execute()
|
||||
|
||||
var body : String? = null
|
||||
try {
|
||||
body = response.body()?.string()
|
||||
} catch(ignored : Throwable) {
|
||||
}
|
||||
|
||||
log.d("registerDeviceToken: %s (%s)", response, body ?: "")
|
||||
|
||||
val code = response.code()
|
||||
|
||||
if(response.isSuccessful || code >= 400 && code < 500) {
|
||||
// 登録できた時も4xxエラーだった時もDBに記録する
|
||||
account.register_key = reg_key
|
||||
account.register_time = now
|
||||
account.saveRegisterKey()
|
||||
}
|
||||
|
||||
} catch(ex : Throwable) {
|
||||
log.trace(ex)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkAccount() {
|
||||
this.nr = NotificationTracking.load(account.db_id)
|
||||
this.parser = TootParser(context, account)
|
||||
|
|
|
@ -328,13 +328,6 @@ object Pref {
|
|||
R.id.swVerticalArrangeThumbnails
|
||||
)
|
||||
|
||||
|
||||
val bpSendAccessTokenToAppServer = BooleanPref(
|
||||
"SendAccessTokenToAppServer",
|
||||
false,
|
||||
R.id.swSendAccessTokenToAppServer
|
||||
)
|
||||
|
||||
val bpDontShowPreviewCard = BooleanPref(
|
||||
"DontShowPreviewCard",
|
||||
false,
|
||||
|
@ -418,9 +411,6 @@ object Pref {
|
|||
val spMovieSizeMax = StringPref("max_movie_size", "40")
|
||||
val spTimelineFont = StringPref("timeline_font", "", skipImport = true)
|
||||
val spTimelineFontBold = StringPref("timeline_font_bold", "", skipImport = true)
|
||||
val spStreamListenerSecret = StringPref("stream_listener_secret", "")
|
||||
val spStreamListenerConfigUrl = StringPref("stream_listener_config_url", "")
|
||||
val spStreamListenerConfigData = StringPref("stream_listener_config_data", "")
|
||||
val spMspUserToken = StringPref("mastodon_search_portal_user_token", "")
|
||||
val spEmojiPickerRecent = StringPref("emoji_picker_recent", "")
|
||||
val spRoundRatio = StringPref("round_ratio", "33")
|
||||
|
|
|
@ -21,7 +21,7 @@ class DuplicateMap {
|
|||
val uri = o.uri
|
||||
val url = o.url
|
||||
when {
|
||||
uri?.isNotEmpty() == true -> {
|
||||
uri.isNotEmpty() -> {
|
||||
if(set_uri.contains(uri)) return true
|
||||
set_uri.add(uri)
|
||||
}
|
||||
|
|
|
@ -1496,7 +1496,7 @@ fun TootApiClient.syncStatus(accessInfo : SavedAccount, urlArg : String) : TootA
|
|||
return result
|
||||
}
|
||||
val uri = obj.uri
|
||||
if(uri?.isNotEmpty() == true) {
|
||||
if(uri.isNotEmpty() ) {
|
||||
url = uri
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,69 +87,6 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/send_access_token_to_app_server"
|
||||
/>
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<Switch
|
||||
android:id="@+id/swSendAccessTokenToAppServer"
|
||||
style="@style/setting_horizontal_stretch"
|
||||
android:gravity="center"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<TextView
|
||||
style="@style/setting_horizontal_stretch"
|
||||
android:text="@string/send_access_token_to_app_server_desc"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/custom_stream_listener"
|
||||
/>
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnCustomStreamListenerEdit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/edit"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnCustomStreamListenerReset"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/reset"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<TextView
|
||||
style="@style/setting_horizontal_stretch"
|
||||
android:text="@string/custom_stream_listener_desc"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
|
|
@ -1,112 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/llContent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
||||
android:orientation="vertical"
|
||||
>
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:labelFor="@+id/etStreamListenerConfigurationUrl"
|
||||
android:text="@string/configuration_url"
|
||||
/>
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etStreamListenerConfigurationUrl"
|
||||
style="@style/setting_horizontal_stretch"
|
||||
android:inputType="textUri"
|
||||
android:maxLines="1"
|
||||
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:labelFor="@+id/etStreamListenerSecret"
|
||||
android:text="@string/secret"
|
||||
/>
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etStreamListenerSecret"
|
||||
style="@style/setting_horizontal_stretch"
|
||||
android:inputType="textPassword"
|
||||
android:maxLines="1"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/custom_stream_listener_desc"
|
||||
android:textSize="12sp"
|
||||
/>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:fadeScrollbars="false"
|
||||
android:fillViewport="true"
|
||||
>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvLog"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="12dp"
|
||||
/>
|
||||
</ScrollView>
|
||||
|
||||
<LinearLayout
|
||||
style="?android:attr/buttonBarStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:measureWithLargestChild="true"
|
||||
>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnDiscard"
|
||||
style="?android:attr/buttonBarButtonStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/discard"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnTest"
|
||||
style="?android:attr/buttonBarButtonStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/test"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSave"
|
||||
style="?android:attr/buttonBarButtonStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/save"
|
||||
android:textAllCaps="false"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
|
@ -271,9 +271,6 @@
|
|||
<string name="app_data_export">Allforio data\'r ap</string>
|
||||
<string name="app_data_import">Mewnforio data\'r ap</string>
|
||||
<string name="draft_deleted">Drafft wedi\'i ddileu.</string>
|
||||
<string name="secret">Cyfrinachol</string>
|
||||
<string name="test">Profi</string>
|
||||
<string name="custom_stream_listener_desc">Mae\'r gosodiad hwn ar gyfer defnyddwyr profiadol. Os nad ydych chi\'n siŵr, peidiwch a golygu. </string>
|
||||
<string name="language_code">en</string>
|
||||
<string name="performance">Ymddangosiad</string>
|
||||
<string name="behavior">Ymddygiad</string>
|
||||
|
@ -403,7 +400,6 @@
|
|||
<string name="locked_account">Cyfrif wedi\'i gloi</string>
|
||||
<string name="languages">ieithoedd</string>
|
||||
<string name="changed">newidwyd.</string>
|
||||
<string name="send_access_token_to_app_server">Anfon tocyn mynediad i weinydd yr ap</string>
|
||||
<string name="follow_accept">derbyn</string>
|
||||
<string name="follow_deny">gwrthod</string>
|
||||
<string name="follow_accept_confirm">Derbyn y cais dilyn o %1$s\?</string>
|
||||
|
|
|
@ -357,13 +357,6 @@
|
|||
<string name="draft_deleted">Brouillon effacé.</string>
|
||||
<string name="draft_picker_desc">Appui long pour effacer.</string>
|
||||
<string name="dont_crop_media_thumbnail">Ne pas recadrer les aperçus de pièces jointes\n(redémarrage nécessaire)</string>
|
||||
<string name="custom_stream_listener">Écouteur de notification personnalisé</string>
|
||||
<string name="configuration_url">Configuration URL</string>
|
||||
<string name="secret">Secret</string>
|
||||
<string name="test">Tester</string>
|
||||
<string name="custom_stream_listener_desc">Réglages réservés aux utilisateurs avertis. Si vous ne savez pas de quoi il s\'agit, laissez tel quel.</string>
|
||||
<string name="custom_stream_listener_was_reset">Custom notification listener réinitialisé.</string>
|
||||
<string name="input_url_and_secret_then_test">Entrez les informations d\'URL et de Secret, appuyez ensuite sur le bouton [Tester].</string>
|
||||
<string name="tab_indicator_color">Couleur de la ligne d\'indication des colonnes affichées</string>
|
||||
<string name="plugin">Plugin</string>
|
||||
<string name="select_plugin">Sélection du plugin</string>
|
||||
|
@ -652,10 +645,6 @@
|
|||
<string name="changed">a changé.</string>
|
||||
<string name="thumbnails_arrange_vertically">Disposition verticale des vignettes (redémarrage de l\'application requis)</string>
|
||||
<string name="instance_local">Instance locale</string>
|
||||
<string name="send_access_token_to_app_server">Envoyer le jeton d\'accès au serveur de l’application</string>
|
||||
<string name="send_access_token_to_app_server_desc">Normally you can use \"pull\" notification (with delay) without app server, because it works on your device.
|
||||
\nBut if you want to use \"custom notification listener\", or if you are the member of very limited instances that is supported by ST\'s app server,
|
||||
\nyou can use \"push\" notifications, but you have to allow sending access tokens to the app server. (this is mastodon\'s defect)</string>
|
||||
<string name="pull_notification_check_interval">Intervalle de vérification des notifications (unité : minutes, par défaut : 15, min:15)</string>
|
||||
<string name="follow_accept">accepter</string>
|
||||
<string name="follow_deny">refuser</string>
|
||||
|
|
|
@ -142,7 +142,6 @@
|
|||
<string name="column_list">カラム一覧</string>
|
||||
<string name="column_list_desc">スワイプで削除します。並べ替えと削除は戻るまたは選択した時に反映されます</string>
|
||||
<string name="comment_empty">通報する理由を書いてください</string>
|
||||
<string name="configuration_url">設定情報URL</string>
|
||||
<string name="confirm">確認</string>
|
||||
<string name="confirm_account_remove">アカウントをこのアプリから除去しますか?関連するカラムはすべて除去されます</string>
|
||||
<string name="confirm_before_boost">ブースト前に確認</string>
|
||||
|
@ -181,9 +180,6 @@
|
|||
<string name="copy">コピー</string>
|
||||
<string name="copy_complete">クリップボードにコピーしました</string>
|
||||
<string name="copy_url">URLをクリップボードにコピー</string>
|
||||
<string name="custom_stream_listener">カスタム通知リスナ</string>
|
||||
<string name="custom_stream_listener_desc">この設定は上級者向けです。よく分からない場合は編集しないでください。失敗するとリアルタイム通知が動作しなくなります。</string>
|
||||
<string name="custom_stream_listener_was_reset">カスタム通知リスナを使わないようにしました</string>
|
||||
<string name="default_status_visibility">投稿の公開範囲の既定値</string>
|
||||
<string name="delete">削除</string>
|
||||
<string name="delete_base_status_before_toot">元トゥートを削除して投稿します。お気に入りとブーストは失われます。返信は切断されます。マストドン2.4.1未満の場合は添付メディアは維持されません。よろしいですか?</string>
|
||||
|
@ -355,7 +351,6 @@
|
|||
<string name="image_capture">カメラで撮影</string>
|
||||
<string name="in_reply_to_id_conversion_failed">アカウント切り替えできません。in_reply_toのID変換に失敗しました。</string>
|
||||
<string name="input_access_token">アクセストークンを指定する(上級者向け)</string>
|
||||
<string name="input_url_and_secret_then_test">URLとシークレットを入力してテストボタンを押してください</string>
|
||||
<string name="instance">インスタンス</string>
|
||||
<string name="instance_does_not_support_push_api">タンスのバージョン %1$s は古くてプッシュ購読を利用できません</string>
|
||||
<string name="instance_hint">例: mastodon.social</string>
|
||||
|
@ -608,13 +603,10 @@
|
|||
<string name="search_is_not_available_on_pseudo_account">疑似アカウントでは検索APIを利用できなくなりました</string>
|
||||
<string name="search_of">検索:%1$s</string>
|
||||
<string name="search_web">Web検索</string>
|
||||
<string name="secret">シークレット</string>
|
||||
<string name="select_and_copy">選択してコピー…</string>
|
||||
<string name="select_draft">どの下書きから復元しますか?</string>
|
||||
<string name="select_plugin">プラグインの選択</string>
|
||||
<string name="send">共有</string>
|
||||
<string name="send_access_token_to_app_server">アクセストークンをアプリサーバに送信する</string>
|
||||
<string name="send_access_token_to_app_server_desc">通常は\"pull\"通知(すこし遅れる)を利用できます。それは端末上で動作するのでアプリサーバは必要ありません。しかしカスタム通知リスナを使う場合やSTがサポートする限られたインスタンスのユーザである場合は\"push\"通知を利用することができます。ただしアクセストークンをアプリサーバに送信する必要があります(これはマストドンの欠陥です)。</string>
|
||||
<string name="send_header_account_acct">Account-Acct</string>
|
||||
<string name="send_header_account_created_at">Account-Created-At</string>
|
||||
<string name="send_header_account_followers_count">Account-Followers-Count</string>
|
||||
|
@ -672,7 +664,6 @@
|
|||
<string name="tablet_mode">タブレットモード</string>
|
||||
<string name="tap_to_show">タップで表示</string>
|
||||
<string name="target_user">対象ユーザ</string>
|
||||
<string name="test">テスト</string>
|
||||
<string name="text_color">文字色</string>
|
||||
<string name="text_to_speech_initialize_failed">TextToSpeechの初期化に失敗。status=%1$s</string>
|
||||
<string name="text_to_speech_initializing">TextToSpeechの初期化中…</string>
|
||||
|
|
|
@ -293,10 +293,6 @@
|
|||
<string name="app_data_import">Importer programdata</string>
|
||||
<string name="draft_picker_desc">Trykk lenge for å slette.</string>
|
||||
<string name="draft_deleted">Kladd slettet.</string>
|
||||
<string name="configuration_url">Oppsettsnettadresse</string>
|
||||
<string name="secret">Hemmeldighet</string>
|
||||
<string name="test">Test</string>
|
||||
<string name="custom_stream_listener_desc">Denne innstillinger er myntet på avanserte brukere. Hvis du er usikker, la være å endre.</string>
|
||||
<string name="plugin">Programtillegg</string>
|
||||
<string name="select_plugin">Velg programtillegg</string>
|
||||
<string name="plugin_not_installed">Programtillegg ikke installert.</string>
|
||||
|
@ -500,7 +496,6 @@
|
|||
<string name="contact">kontaktkonto</string>
|
||||
<string name="languages">språk</string>
|
||||
<string name="changed">endret.</string>
|
||||
<string name="send_access_token_to_app_server">Send tilgangssymbol til programtjener</string>
|
||||
<string name="follow_accept">godta</string>
|
||||
<string name="follow_deny">forkast</string>
|
||||
<string name="follow_accept_confirm">Godta følgingsforespørselen fra %1$s?</string>
|
||||
|
@ -648,9 +643,6 @@
|
|||
<string name="app_data_import_confirm">eksisterende data (før import) vil bli tømt. Er du sikker\?</string>
|
||||
<string name="user_id_conversion_failed">Kunne ikke konvertere bruker-ID</string>
|
||||
<string name="dont_crop_media_thumbnail">Ikke beskjær mediaminiatyrbilde (programstart kreves)</string>
|
||||
<string name="custom_stream_listener">Egendefinert merknadslytter</string>
|
||||
<string name="custom_stream_listener_was_reset">Egendefinert merknadslytter tilbakestilt.</string>
|
||||
<string name="input_url_and_secret_then_test">Skriv inn nettadresse og hemmelighet, trykk så på \"Test\".</string>
|
||||
<string name="notification_on_off_desc">Sjekk også kontoinnstillingen for å skru på/av merknad.</string>
|
||||
<string name="in_reply_to_id_conversion_failed">Kunne ikke konvertere \"in_reply_to\"-ID</string>
|
||||
<string name="prior_chrome_custom_tabs">Tidligere Google Chrome (hvis egendefinerte faner er påslått)</string>
|
||||
|
@ -711,9 +703,6 @@
|
|||
<string name="system_notification_not_related">Ikke valgt når systemmerknad trykkes</string>
|
||||
<string name="append_attachment_url_to_content">Legg til vedleggsnettadresse i bindeleddstekst</string>
|
||||
<string name="instance_local">Lokal instans</string>
|
||||
<string name="send_access_token_to_app_server_desc">Normalt kan du bruke \"pull\"-merknader (med forsinkelse) uten programtjener, fordi det virker på din enhet.
|
||||
\nHvis du ønsker å bruke \"egendefinert merknadslytter\", eller hvis du er medlem av veldig begrensede instanser som støttes av STs programtjener,
|
||||
\nkan du bruke \"push\"-merknader, men du må tillate innsending av tilgangssymboler til programtjeneren. (Dette er en begrensning i Mastodon).</string>
|
||||
<string name="pull_notification_check_interval">Sjekkintervall for pull-merknad (enhet: minutter, forvalg:15, min:15)</string>
|
||||
<string name="token_exported">Som følge av eksport av programdata eller gjenoppretting fra sikkerhetskopi, brukes dette tilgangssymbolet også av andre enheter. Fordi tilgangssymboler som ikke brukes av andre enheter kreves for å bruke push-merknader, anbefales oppdatering av tilgangssymbolet.</string>
|
||||
<string name="missing_push_api">Instansen har inget API for push-merknader.</string>
|
||||
|
|
|
@ -358,13 +358,6 @@
|
|||
<string name="draft_picker_desc">Long tap to delete.</string>
|
||||
<string name="draft_deleted">Draft deleted.</string>
|
||||
<string name="dont_crop_media_thumbnail">Don\'t crop media thumbnail (app restart required)</string>
|
||||
<string name="custom_stream_listener">Custom notification listener</string>
|
||||
<string name="configuration_url">Configuration URL</string>
|
||||
<string name="secret">Secret</string>
|
||||
<string name="test">Test</string>
|
||||
<string name="custom_stream_listener_desc">This setting is for advanced users. If you are not sure, please don\'t edit.</string>
|
||||
<string name="custom_stream_listener_was_reset">Custom notification listener was reset.</string>
|
||||
<string name="input_url_and_secret_then_test">Input URL and Secret, then tap Test button.</string>
|
||||
<string name="tab_indicator_color">Tab\'s indicator color</string>
|
||||
<string name="plugin">Plugin</string>
|
||||
<string name="select_plugin">Select plugin</string>
|
||||
|
@ -645,10 +638,6 @@
|
|||
<string name="changed">changed.</string>
|
||||
<string name="thumbnails_arrange_vertically">Vertical arrange thumbnails (app restart required)</string>
|
||||
<string name="instance_local">Instance local</string>
|
||||
<string name="send_access_token_to_app_server">Send access token to app server</string>
|
||||
<string name="send_access_token_to_app_server_desc">Normally you can use \"pull\" notification (with delay) without app server, because it works on your device.
|
||||
\nBut if you want to use \"custom notification listener\", or if you are the member of very limited instances that is supported by ST\'s app server,
|
||||
\nyou can use \"push\" notifications, but you have to allow sending access tokens to the app server. (this is mastodon\'s defect)</string>
|
||||
<string name="pull_notification_check_interval">Pull notification check interval (unit: minutes, default:15, min:15)</string>
|
||||
<string name="follow_accept">accept</string>
|
||||
<string name="follow_deny">deny</string>
|
||||
|
|
Loading…
Reference in New Issue