クラッシュレポート対応。リファクタ。

This commit is contained in:
tateisu 2018-01-21 21:46:36 +09:00
parent ecb7b1bc75
commit cb34c6c458
99 changed files with 3185 additions and 2625 deletions

View File

@ -18,6 +18,7 @@
<inspection_tool class="PrivatePropertyName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PropertyName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="TryFinallyCanBeTryWithResources" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UnnecessaryModuleDependencyInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UseWithIndex" enabled="false" level="INFO" enabled_by_default="false" />
</profile>
</component>

View File

@ -12,8 +12,8 @@ android {
minSdkVersion 21
targetSdkVersion 27
versionCode 210
versionName "2.1.0"
versionCode 211
versionName "2.1.1"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
@ -119,6 +119,8 @@ dependencies {
// compile 'com.simplecityapps:recyclerview-fastscroll:1.0.16'
implementation 'me.drakeet.support:toastcompat:1.0.2'
}
repositories {

View File

@ -24,7 +24,7 @@ class TestDuplicateMap {
)
)
private val generatedItems = ArrayList<Any>()
private val generatedItems = ArrayList<TimelineItem>()
private fun genStatus(
parser : TootParser,

View File

@ -1,3 +1,5 @@
@file:Suppress("MemberVisibilityCanBePrivate")
package jp.juggler.subwaytooter.api
import android.support.test.InstrumentationRegistry

View File

@ -4,7 +4,8 @@ import android.support.test.runner.AndroidJUnit4
import android.test.mock.MockContext
import jp.juggler.subwaytooter.api.TootParser
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.parseLong
import jp.juggler.subwaytooter.util.toJsonObject
import org.json.JSONArray
import org.json.JSONObject
import org.junit.Assert.*
@ -18,13 +19,13 @@ class TestEntityUtils {
class TestEntity(val s : String, val l : Long) : Mappable<String> {
constructor(src : JSONObject) : this(
s = src.notEmptyOrThrow("s"),
l = Utils.optLongX(src, "l")
l = src.parseLong("l") ?: -1L
)
@Suppress("UNUSED_PARAMETER")
constructor(parser : TootParser, src : JSONObject) : this(
s = src.notEmptyOrThrow("s"),
l = Utils.optLongX(src, "l")
l = src.parseLong("l") ?: -1L
)
override val mapKey : String
@ -36,38 +37,38 @@ class TestEntityUtils {
assertEquals(null, parseItem(::TestEntity, null))
run {
val src = JSONObject("""{"s":null,"l":"100"}""")
val src = """{"s":null,"l":"100"}""".toJsonObject()
val item = parseItem(::TestEntity, src)
assertNull(item)
}
run {
val src = JSONObject("""{"s":"","l":"100"}""")
val src = """{"s":"","l":"100"}""".toJsonObject()
val item = parseItem(::TestEntity, src)
assertNull(item)
}
run {
val src = JSONObject("""{"s":"A","l":null}""")
val src = """{"s":"A","l":null}""".toJsonObject()
val item = parseItem(::TestEntity, src)
assertNotNull(item)
assertEquals(src.optString("s"), item?.s)
assertEquals(src.optLong("l"), item?.l)
}
run {
val src = JSONObject("""{"s":"A","l":""}""")
val src = """{"s":"A","l":""}""".toJsonObject()
val item = parseItem(::TestEntity, src)
assertNotNull(item)
assertEquals(src.optString("s"), item?.s)
assertEquals(src.optLong("l"), item?.l)
}
run {
val src = JSONObject("""{"s":"A","l":100}""")
val src = """{"s":"A","l":100}""".toJsonObject()
val item = parseItem(::TestEntity, src)
assertNotNull(item)
assertEquals(src.optString("s"), item?.s)
assertEquals(src.optLong("l"), item?.l)
}
run {
val src = JSONObject("""{"s":"A","l":"100"}""")
val src ="""{"s":"A","l":"100"}""".toJsonObject()
val item = parseItem(::TestEntity, src)
assertNotNull(item)
assertEquals(src.optString("s"), item?.s)
@ -76,77 +77,77 @@ class TestEntityUtils {
}
@Test
fun TestParseList() {
fun testParseList() {
assertEquals(0, parseList(::TestEntity, null).size)
val src = JSONArray()
assertEquals(0, parseList(::TestEntity, src).size)
src.put(JSONObject("""{"s":"A","l":"100"}"""))
src.put("""{"s":"A","l":"100"}""".toJsonObject())
assertEquals(1, parseList(::TestEntity, src).size)
src.put(JSONObject("""{"s":"A","l":"100"}"""))
src.put("""{"s":"A","l":"100"}""".toJsonObject())
assertEquals(2, parseList(::TestEntity, src).size)
// error
src.put(JSONObject("""{"s":"","l":"100"}"""))
src.put("""{"s":"","l":"100"}""".toJsonObject())
assertEquals(2, parseList(::TestEntity, src).size)
}
@Test
fun TestParseListOrNull() {
fun testParseListOrNull() {
assertEquals(null, parseListOrNull(::TestEntity, null))
val src = JSONArray()
assertEquals(null, parseListOrNull(::TestEntity, src))
src.put(JSONObject("""{"s":"A","l":"100"}"""))
src.put("""{"s":"A","l":"100"}""".toJsonObject())
assertEquals(1, parseListOrNull(::TestEntity, src)?.size)
src.put(JSONObject("""{"s":"A","l":"100"}"""))
src.put("""{"s":"A","l":"100"}""".toJsonObject())
assertEquals(2, parseListOrNull(::TestEntity, src)?.size)
// error
src.put(JSONObject("""{"s":"","l":"100"}"""))
src.put("""{"s":"","l":"100"}""".toJsonObject())
assertEquals(2, parseListOrNull(::TestEntity, src)?.size)
}
@Test
fun TestParseMap() {
fun testParseMap() {
assertEquals(0, parseMap(::TestEntity, null).size)
val src = JSONArray()
assertEquals(0, parseMap(::TestEntity, src).size)
src.put(JSONObject("""{"s":"A","l":"100"}"""))
src.put("""{"s":"A","l":"100"}""".toJsonObject())
assertEquals(1, parseMap(::TestEntity, src).size)
src.put(JSONObject("""{"s":"B","l":"100"}"""))
src.put("""{"s":"B","l":"100"}""".toJsonObject())
assertEquals(2, parseMap(::TestEntity, src).size)
// error
src.put(JSONObject("""{"s":"","l":"100"}"""))
src.put("""{"s":"","l":"100"}""".toJsonObject())
assertEquals(2, parseMap(::TestEntity, src).size)
}
@Test
fun TestParseMapOrNull() {
fun testParseMapOrNull() {
assertEquals(null, parseMapOrNull(::TestEntity, null))
val src = JSONArray()
assertEquals(null, parseMapOrNull(::TestEntity, src))
src.put(JSONObject("""{"s":"A","l":"100"}"""))
src.put("""{"s":"A","l":"100"}""".toJsonObject())
assertEquals(1, parseMapOrNull(::TestEntity, src)?.size)
src.put(JSONObject("""{"s":"B","l":"100"}"""))
src.put("""{"s":"B","l":"100"}""".toJsonObject())
assertEquals(2, parseMapOrNull(::TestEntity, src)?.size)
// error
src.put(JSONObject("""{"s":"","l":"100"}"""))
src.put("""{"s":"","l":"100"}""".toJsonObject())
assertEquals(2, parseMapOrNull(::TestEntity, src)?.size)
}
@ -159,38 +160,38 @@ class TestEntityUtils {
assertEquals(null, parseItem(::TestEntity, parser, null))
run {
val src = JSONObject("""{"s":null,"l":"100"}""")
val src ="""{"s":null,"l":"100"}""".toJsonObject()
val item = parseItem(::TestEntity, parser, src)
assertNull(item)
}
run {
val src = JSONObject("""{"s":"","l":"100"}""")
val src = """{"s":"","l":"100"}""".toJsonObject()
val item = parseItem(::TestEntity, parser, src)
assertNull(item)
}
run {
val src = JSONObject("""{"s":"A","l":null}""")
val src = """{"s":"A","l":null}""".toJsonObject()
val item = parseItem(::TestEntity, parser, src)
assertNotNull(item)
assertEquals(src.optString("s"), item?.s)
assertEquals(src.optLong("l"), item?.l)
}
run {
val src = JSONObject("""{"s":"A","l":""}""")
val src = """{"s":"A","l":""}""".toJsonObject()
val item = parseItem(::TestEntity, parser, src)
assertNotNull(item)
assertEquals(src.optString("s"), item?.s)
assertEquals(src.optLong("l"), item?.l)
}
run {
val src = JSONObject("""{"s":"A","l":100}""")
val src = """{"s":"A","l":100}""".toJsonObject()
val item = parseItem(::TestEntity, parser, src)
assertNotNull(item)
assertEquals(src.optString("s"), item?.s)
assertEquals(src.optLong("l"), item?.l)
}
run {
val src = JSONObject("""{"s":"A","l":"100"}""")
val src = """{"s":"A","l":"100"}""".toJsonObject()
val item = parseItem(::TestEntity, parser, src)
assertNotNull(item)
assertEquals(src.optString("s"), item?.s)
@ -199,70 +200,70 @@ class TestEntityUtils {
}
@Test
fun TestParseListWithParser() {
fun testParseListWithParser() {
assertEquals(0, parseList(::TestEntity, parser, null).size)
val src = JSONArray()
assertEquals(0, parseList(::TestEntity, parser, src).size)
src.put(JSONObject("""{"s":"A","l":"100"}"""))
src.put("""{"s":"A","l":"100"}""".toJsonObject())
assertEquals(1, parseList(::TestEntity, parser, src).size)
src.put(JSONObject("""{"s":"A","l":"100"}"""))
src.put("""{"s":"A","l":"100"}""".toJsonObject())
assertEquals(2, parseList(::TestEntity, parser, src).size)
// error
src.put(JSONObject("""{"s":"","l":"100"}"""))
src.put("""{"s":"","l":"100"}""".toJsonObject())
assertEquals(2, parseList(::TestEntity, parser, src).size)
}
@Test
fun TestParseListOrNullWithParser() {
fun testParseListOrNullWithParser() {
assertEquals(null, parseListOrNull(::TestEntity, parser, null))
val src = JSONArray()
assertEquals(null, parseListOrNull(::TestEntity, parser, src))
src.put(JSONObject("""{"s":"A","l":"100"}"""))
src.put("""{"s":"A","l":"100"}""".toJsonObject())
assertEquals(1, parseListOrNull(::TestEntity, parser, src)?.size)
src.put(JSONObject("""{"s":"A","l":"100"}"""))
src.put("""{"s":"A","l":"100"}""".toJsonObject())
assertEquals(2, parseListOrNull(::TestEntity, parser, src)?.size)
// error
src.put(JSONObject("""{"s":"","l":"100"}"""))
src.put("""{"s":"","l":"100"}""".toJsonObject())
assertEquals(2, parseListOrNull(::TestEntity, parser, src)?.size)
}
@Test(expected = RuntimeException::class)
fun TestNotEmptyOrThrow1() {
fun testNotEmptyOrThrow1() {
println(notEmptyOrThrow("param1", null))
}
@Test(expected = RuntimeException::class)
fun TestNotEmptyOrThrow2() {
fun testNotEmptyOrThrow2() {
println(notEmptyOrThrow("param1", ""))
}
@Test
fun TestNotEmptyOrThrow3() {
fun testNotEmptyOrThrow3() {
assertEquals("A", notEmptyOrThrow("param1", "A"))
}
@Test(expected = RuntimeException::class)
fun TestNotEmptyOrThrow4() {
println(JSONObject("""{"param1":null}""").notEmptyOrThrow("param1"))
fun testNotEmptyOrThrow4() {
println("""{"param1":null}""".toJsonObject().notEmptyOrThrow("param1"))
}
@Test(expected = RuntimeException::class)
fun TestNotEmptyOrThrow5() {
println(JSONObject("""{"param1":""}""").notEmptyOrThrow("param1"))
fun testNotEmptyOrThrow5() {
println("""{"param1":""}""".toJsonObject().notEmptyOrThrow("param1"))
}
@Test
fun TestNotEmptyOrThrow6() {
assertEquals("A", JSONObject("""{"param1":"A"}""").notEmptyOrThrow("param1"))
fun testNotEmptyOrThrow6() {
assertEquals("A", """{"param1":"A"}""".toJsonObject().notEmptyOrThrow("param1"))
}
}

View File

@ -37,9 +37,10 @@ class MyGifDrawable internal constructor(
/**
* True if the drawable's resources have been recycled.
*/
// For testing.
internal var isRecycled : Boolean = false
private set
private var isRecycled : Boolean = false
/**
* True if the drawable is currently visible. Default to true because on certain platforms (at
* least 4.1.1), setVisible is not called on [Drawables][android.graphics.drawable.Drawable]
@ -74,7 +75,7 @@ class MyGifDrawable internal constructor(
val buffer : ByteBuffer
get() = state.frameLoader.buffer
val frameCount : Int
private val frameCount : Int
get() = state.frameLoader.frameCount
/**
@ -82,7 +83,7 @@ class MyGifDrawable internal constructor(
* is displayed.
*/
// Public API.
val frameIndex : Int
private val frameIndex : Int
get() = state.frameLoader.currentIndex
/**

View File

@ -6,7 +6,6 @@ import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.View
import android.widget.Button
import android.widget.LinearLayout
import android.widget.TextView
@ -18,13 +17,13 @@ class ActAbout : AppCompatActivity() {
companion object {
val log = LogCategory("ActAbout")
val EXTRA_SEARCH = "search"
const val EXTRA_SEARCH = "search"
val url_store = "https://play.google.com/store/apps/details?id=jp.juggler.subwaytooter"
const val url_store = "https://play.google.com/store/apps/details?id=jp.juggler.subwaytooter"
val developer_acct = "tateisu@mastodon.juggler.jp"
const val developer_acct = "tateisu@mastodon.juggler.jp"
val url_futaba = "https://www.instagram.com/hinomoto_hutaba/"
const val url_futaba = "https://www.instagram.com/hinomoto_hutaba/"
val contributors = arrayOf("@Balor@freeradical.zone", "update english language", "@Luattic@oc.todon.fr", "update french language")
}
@ -39,7 +38,8 @@ class ActAbout : AppCompatActivity() {
try {
val pInfo = packageManager.getPackageInfo(packageName, 0)
findViewById<TextView>(R.id.tvVersion) .text = getString(R.string.version_is, pInfo.versionName)
val tv = findViewById<TextView>(R.id.tvVersion)
tv.text = getString(R.string.version_is, pInfo.versionName)
} catch(ex : PackageManager.NameNotFoundException) {
log.trace(ex)
}

View File

@ -49,20 +49,50 @@ import jp.juggler.subwaytooter.api.entity.TootStatus
import jp.juggler.subwaytooter.dialog.ActionsDialog
import jp.juggler.subwaytooter.table.AcctColor
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.DecodeOptions
import jp.juggler.subwaytooter.util.EmojiDecoder
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.NetworkEmojiInvalidator
import jp.juggler.subwaytooter.util.NotificationHelper
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.*
import jp.juggler.subwaytooter.view.MyNetworkImageView
import okhttp3.Request
import okhttp3.RequestBody
class ActAccountSetting : AppCompatActivity(), View.OnClickListener, CompoundButton.OnCheckedChangeListener {
class ActAccountSetting
: AppCompatActivity(), View.OnClickListener, CompoundButton.OnCheckedChangeListener {
lateinit internal var account : SavedAccount
lateinit internal var pref : SharedPreferences
companion object {
internal val log = LogCategory("ActAccountSetting")
internal const val KEY_ACCOUNT_DB_ID = "account_db_id"
internal const val REQUEST_CODE_ACCT_CUSTOMIZE = 1
internal const val REQUEST_CODE_NOTIFICATION_SOUND = 2
private const val REQUEST_CODE_AVATAR_ATTACHMENT = 3
private const val REQUEST_CODE_HEADER_ATTACHMENT = 4
private const val REQUEST_CODE_AVATAR_CAMERA = 5
private const val REQUEST_CODE_HEADER_CAMERA = 6
internal const val RESULT_INPUT_ACCESS_TOKEN = Activity.RESULT_FIRST_USER + 10
internal const val EXTRA_DB_ID = "db_id"
internal const val max_length_display_name = 30
internal const val max_length_note = 160
private const val PERMISSION_REQUEST_AVATAR = 1
private const val PERMISSION_REQUEST_HEADER = 2
internal const val MIME_TYPE_JPEG = "image/jpeg"
internal const val MIME_TYPE_PNG = "image/png"
fun open(activity : Activity, ai : SavedAccount, requestCode : Int) {
val intent = Intent(activity, ActAccountSetting::class.java)
intent.putExtra(KEY_ACCOUNT_DB_ID, ai.db_id)
activity.startActivityForResult(intent, requestCode)
}
}
internal lateinit var account : SavedAccount
internal lateinit var pref : SharedPreferences
private lateinit var tvInstance : TextView
private lateinit var tvUser : TextView
@ -104,7 +134,7 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener, CompoundBut
private lateinit var btnNote : View
private lateinit var name_invalidator : NetworkEmojiInvalidator
private lateinit var note_invalidator : NetworkEmojiInvalidator
lateinit internal var handler : Handler
internal lateinit var handler : Handler
internal var loading = false
@ -123,7 +153,7 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener, CompoundBut
initUI()
val a = SavedAccount.loadAccount(this, intent.getLongExtra(KEY_ACCOUNT_DB_ID, - 1L))
if(a == null){
if(a == null) {
finish()
return
}
@ -152,7 +182,7 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener, CompoundBut
REQUEST_CODE_NOTIFICATION_SOUND -> {
if(resultCode == Activity.RESULT_OK) {
// RINGTONE_PICKERからの選択されたデータを取得する
val uri = Utils.getExtraObject(data, RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
val uri = data?.extras?.get(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
if(uri is Uri) {
notification_sound_uri = uri.toString()
saveUIToData()
@ -174,7 +204,11 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener, CompoundBut
if(uri1 != null) {
// 単一選択
val type = data.type
addAttachment(requestCode, uri1, if(type?.isNotEmpty() == true) type else contentResolver.getType(uri1))
addAttachment(
requestCode,
uri1,
if(type?.isNotEmpty() == true) type else contentResolver.getType(uri1)
)
} else {
// 複数選択
data.clipData?.let { clipData ->
@ -195,7 +229,7 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener, CompoundBut
// 失敗したら DBからデータを削除
val uriCameraImage = this@ActAccountSetting.uriCameraImage
if(uriCameraImage != null) {
contentResolver.delete(uriCameraImage , null, null)
contentResolver.delete(uriCameraImage, null, null)
this@ActAccountSetting.uriCameraImage = null
}
} else {
@ -212,7 +246,7 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener, CompoundBut
}
}
var density: Float = 1f
var density : Float = 1f
private fun initUI() {
this.density = resources.displayMetrics.density
@ -348,7 +382,12 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener, CompoundBut
val ac = AcctColor.load(full_acct)
val nickname = ac.nickname
tvUserCustom.text = if(nickname?.isNotEmpty() == true) nickname else full_acct
tvUserCustom.setTextColor(if(ac.color_fg != 0) ac.color_fg else Styler.getAttributeColor(this, R.attr.colorTimeSmall))
tvUserCustom.setTextColor(
if(ac.color_fg != 0) ac.color_fg else Styler.getAttributeColor(
this,
R.attr.colorTimeSmall
)
)
tvUserCustom.setBackgroundColor(ac.color_bg)
}
@ -390,7 +429,12 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener, CompoundBut
R.id.btnVisibility -> performVisibility()
R.id.btnOpenBrowser -> open_browser("https://" + account.host + "/")
R.id.btnUserCustom -> ActNickname.open(this, full_acct, false, REQUEST_CODE_ACCT_CUSTOMIZE)
R.id.btnUserCustom -> ActNickname.open(
this,
full_acct,
false,
REQUEST_CODE_ACCT_CUSTOMIZE
)
R.id.btnNotificationSoundEdit -> openNotificationSoundPicker()
@ -432,7 +476,13 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener, CompoundBut
}
private fun performVisibility() {
val caption_list = arrayOf(Styler.getVisibilityCaption(this, TootStatus.VISIBILITY_WEB_SETTING), Styler.getVisibilityCaption(this, TootStatus.VISIBILITY_PUBLIC), Styler.getVisibilityCaption(this, TootStatus.VISIBILITY_UNLISTED), Styler.getVisibilityCaption(this, TootStatus.VISIBILITY_PRIVATE), Styler.getVisibilityCaption(this, TootStatus.VISIBILITY_DIRECT))
val caption_list = arrayOf(
Styler.getVisibilityCaption(this, TootStatus.VISIBILITY_WEB_SETTING),
Styler.getVisibilityCaption(this, TootStatus.VISIBILITY_PUBLIC),
Styler.getVisibilityCaption(this, TootStatus.VISIBILITY_UNLISTED),
Styler.getVisibilityCaption(this, TootStatus.VISIBILITY_PRIVATE),
Styler.getVisibilityCaption(this, TootStatus.VISIBILITY_DIRECT)
)
AlertDialog.Builder(this)
.setTitle(R.string.choose_visibility)
@ -462,7 +512,7 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener, CompoundBut
account.delete()
val pref = Pref.pref(this@ActAccountSetting)
if(account.db_id == Pref.lpTabletTootDefaultAccount(pref) ) {
if(account.db_id == Pref.lpTabletTootDefaultAccount(pref)) {
pref.edit().put(Pref.lpTabletTootDefaultAccount, - 1L).apply()
}
@ -474,7 +524,8 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener, CompoundBut
internal fun unregister() {
try {
val install_id = PrefDevice.prefDevice(this@ActAccountSetting).getString(PrefDevice.KEY_INSTALL_ID, null)
val install_id = PrefDevice.prefDevice(this@ActAccountSetting)
.getString(PrefDevice.KEY_INSTALL_ID, null)
if(install_id?.isEmpty() != false) {
log.d("performAccountRemove: missing install_id")
return
@ -486,13 +537,18 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener, CompoundBut
return
}
val post_data = ("instance_url=" + Uri.encode("https://" + account.host)
+ "&app_id=" + Uri.encode(packageName)
val post_data = ("instance_url=" + ("https://" + account.host).encodePercent()
+ "&app_id=" + packageName.encodePercent()
+ "&tag=" + tag)
val request = Request.Builder()
.url(PollingWorker.APP_SERVER + "/unregister")
.post(RequestBody.create(TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED, post_data))
.post(
RequestBody.create(
TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED,
post_data
)
)
.build()
val call = App1.ok_http_client.newCall(request)
@ -549,7 +605,7 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener, CompoundBut
}
// エラーを表示
error != null -> {
Utils.showToast(this@ActAccountSetting, true, error)
showToast(this@ActAccountSetting, true, error)
log.e("can't get oauth browser URL. $error")
}
}
@ -574,8 +630,11 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener, CompoundBut
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, false)
try {
val notification_sound_uri = this.notification_sound_uri
if(notification_sound_uri?.isNotEmpty() == true){
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, Uri.parse(notification_sound_uri))
if(notification_sound_uri?.isNotEmpty() == true) {
intent.putExtra(
RingtoneManager.EXTRA_RINGTONE_EXISTING_URI,
Uri.parse(notification_sound_uri)
)
}
} catch(ignored : Throwable) {
}
@ -589,7 +648,12 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener, CompoundBut
private fun initializeProfile() {
// 初期状態
ivProfileAvatar.setErrorImageResId(Styler.getAttributeResourceId(this, R.attr.ic_question))
ivProfileAvatar.setDefaultImageResId(Styler.getAttributeResourceId(this, R.attr.ic_question))
ivProfileAvatar.setDefaultImageResId(
Styler.getAttributeResourceId(
this,
R.attr.ic_question
)
)
etDisplayName.setText("(loading...)")
etNote.setText("(loading...)")
// 初期状態では編集不可能
@ -628,7 +692,7 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener, CompoundBut
if(data != null) {
showProfile(data)
} else {
Utils.showToast(this@ActAccountSetting, true, result.error)
showToast(this@ActAccountSetting, true, result.error)
}
}
@ -676,9 +740,11 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener, CompoundBut
internal var data : TootAccount? = null
override fun background(client : TootApiClient) : TootApiResult? {
val request_builder = Request.Builder()
.patch(RequestBody.create(
TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED, form_data
))
.patch(
RequestBody.create(
TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED, form_data
)
)
val result = client.request("/api/v1/accounts/update_credentials", request_builder)
val jsonObject = result?.jsonObject
@ -696,7 +762,7 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener, CompoundBut
if(data != null) {
showProfile(data)
} else {
Utils.showToast(this@ActAccountSetting, true, result.error)
showToast(this@ActAccountSetting, true, result.error)
}
}
@ -710,8 +776,14 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener, CompoundBut
val length = sv.codePointCount(0, sv.length)
if(length > max_length_display_name) {
AlertDialog.Builder(this)
.setMessage(getString(R.string.length_warning, getString(R.string.display_name), length, max_length_display_name
))
.setMessage(
getString(
R.string.length_warning,
getString(R.string.display_name),
length,
max_length_display_name
)
)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.ok) { _, _ -> sendDisplayName(true) }
.setCancelable(true)
@ -719,7 +791,7 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener, CompoundBut
return
}
}
updateCredential("display_name=" + Uri.encode(EmojiDecoder.decodeShortCode(sv)))
updateCredential("display_name=" + EmojiDecoder.decodeShortCode(sv).encodePercent() )
}
private fun sendNote(bConfirmed : Boolean) {
@ -728,8 +800,14 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener, CompoundBut
val length = sv.codePointCount(0, sv.length)
if(length > max_length_note) {
AlertDialog.Builder(this)
.setMessage(getString(R.string.length_warning, getString(R.string.note), length, max_length_note
))
.setMessage(
getString(
R.string.length_warning,
getString(R.string.note),
length,
max_length_note
)
)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.ok) { _, _ -> sendNote(true) }
.setCancelable(true)
@ -737,7 +815,7 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener, CompoundBut
return
}
}
updateCredential("note=" + Uri.encode(EmojiDecoder.decodeShortCode(sv)))
updateCredential("note=" + EmojiDecoder.decodeShortCode(sv).encodePercent())
}
private fun pickAvatarImage() {
@ -749,7 +827,10 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener, CompoundBut
}
private fun openPicker(permission_request_code : Int) {
val permissionCheck = ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
val permissionCheck = ContextCompat.checkSelfPermission(
this,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE
)
if(permissionCheck != PackageManager.PERMISSION_GRANTED) {
preparePermission(permission_request_code)
return
@ -779,11 +860,12 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener, CompoundBut
if(Build.VERSION.SDK_INT >= 23) {
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), request_code
ActivityCompat.requestPermissions(
this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), request_code
)
return
}
Utils.showToast(this, true, R.string.missing_permission_to_access_media)
showToast(this, true, R.string.missing_permission_to_access_media)
}
override fun onRequestPermissionsResult(
@ -795,7 +877,7 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener, CompoundBut
if(grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
openPicker(requestCode)
} else {
Utils.showToast(this, true, R.string.missing_permission_to_access_media)
showToast(this, true, R.string.missing_permission_to_access_media)
}
}
}
@ -811,7 +893,7 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener, CompoundBut
startActivityForResult(intent, request_code)
} catch(ex : Throwable) {
log.trace(ex)
Utils.showToast(this, ex, "ACTION_OPEN_DOCUMENT failed.")
showToast(this, ex, "ACTION_OPEN_DOCUMENT failed.")
}
}
@ -824,7 +906,8 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener, CompoundBut
val values = ContentValues()
values.put(MediaStore.Images.Media.TITLE, filename)
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
uriCameraImage = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
uriCameraImage =
contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
intent.putExtra(MediaStore.EXTRA_OUTPUT, uriCameraImage)
@ -832,7 +915,7 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener, CompoundBut
startActivityForResult(intent, request_code)
} catch(ex : Throwable) {
log.trace(ex)
Utils.showToast(this, ex, "opening camera app failed.")
showToast(this, ex, "opening camera app failed.")
}
}
@ -862,12 +945,12 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener, CompoundBut
// 設定からリサイズ指定を読む
val resize_to = 1280
val bitmap = Utils.createResizedBitmap(log, this, uri, false, resize_to)
val bitmap = createResizedBitmap( this, uri, resize_to )
if(bitmap != null) {
try {
val cache_dir = externalCacheDir
if(cache_dir == null) {
Utils.showToast(this, false, "getExternalCacheDir returns null.")
showToast(this, false, "getExternalCacheDir returns null.")
break
}
@ -905,7 +988,7 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener, CompoundBut
} catch(ex : Throwable) {
log.trace(ex)
Utils.showToast(this, ex, "Resizing image failed.")
showToast(this, ex, "Resizing image failed.")
}
break
@ -929,12 +1012,12 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener, CompoundBut
private fun addAttachment(request_code : Int, uri : Uri, mime_type : String?) {
if(mime_type == null) {
Utils.showToast(this, false, "mime type is not provided.")
showToast(this, false, "mime type is not provided.")
return
}
if(! mime_type.startsWith("image/")) {
Utils.showToast(this, false, "mime type is not image.")
showToast(this, false, "mime type is not image.")
return
}
@ -948,22 +1031,27 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener, CompoundBut
opener.open().use { inData ->
val bao = ByteArrayOutputStream()
//
bao.write(Utils.encodeUTF8("data:" + opener.mimeType + ";base64,"))
bao.write("data:${opener.mimeType};base64,".encodeUTF8())
//
Base64OutputStream(bao, Base64.NO_WRAP).use { base64 -> IOUtils.copy(inData, base64) }
val value = Utils.decodeUTF8(bao.toByteArray())
Base64OutputStream(bao, Base64.NO_WRAP).use { base64 ->
IOUtils.copy(
inData,
base64
)
}
val value = bao.toByteArray().decodeUTF8()
return when(request_code) {
REQUEST_CODE_HEADER_ATTACHMENT, REQUEST_CODE_HEADER_CAMERA -> "header="
else -> "avatar="
} + Uri.encode(value)
} + value.encodePercent()
}
} finally {
opener.deleteTempFile()
}
} catch(ex : Throwable) {
Utils.showToast(this@ActAccountSetting, ex, "image converting failed.")
showToast(this@ActAccountSetting, ex, "image converting failed.")
}
return null
@ -979,37 +1067,5 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener, CompoundBut
task.executeOnExecutor(App1.task_executor)
}
companion object {
internal val log = LogCategory("ActAccountSetting")
internal val KEY_ACCOUNT_DB_ID = "account_db_id"
fun open(activity : Activity, ai : SavedAccount, requestCode : Int) {
val intent = Intent(activity, ActAccountSetting::class.java)
intent.putExtra(KEY_ACCOUNT_DB_ID, ai.db_id)
activity.startActivityForResult(intent, requestCode)
}
internal val REQUEST_CODE_ACCT_CUSTOMIZE = 1
internal val REQUEST_CODE_NOTIFICATION_SOUND = 2
private val REQUEST_CODE_AVATAR_ATTACHMENT = 3
private val REQUEST_CODE_HEADER_ATTACHMENT = 4
private val REQUEST_CODE_AVATAR_CAMERA = 5
private val REQUEST_CODE_HEADER_CAMERA = 6
internal val RESULT_INPUT_ACCESS_TOKEN = Activity.RESULT_FIRST_USER + 10
internal val EXTRA_DB_ID = "db_id"
internal val max_length_display_name = 30
internal val max_length_note = 160
private val PERMISSION_REQUEST_AVATAR = 1
private val PERMISSION_REQUEST_HEADER = 2
internal val MIME_TYPE_JPEG = "image/jpeg"
internal val MIME_TYPE_PNG = "image/png"
}
}

View File

@ -43,7 +43,7 @@ import java.util.Locale
import jp.juggler.subwaytooter.table.AcctColor
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.showToast
class ActAppSetting : AppCompatActivity()
, CompoundButton.OnCheckedChangeListener
@ -440,7 +440,7 @@ class ActAppSetting : AppCompatActivity()
intent.type = "*/*"
startActivityForResult(intent, REQUEST_CODE_TIMELINE_FONT)
} catch(ex : Throwable) {
Utils.showToast(this, ex, "could not open picker for font")
showToast(this, ex, "could not open picker for font")
}
R.id.btnTimelineFontBoldReset -> {
@ -455,7 +455,7 @@ class ActAppSetting : AppCompatActivity()
intent.type = "*/*"
startActivityForResult(intent, REQUEST_CODE_TIMELINE_FONT_BOLD)
} catch(ex : Throwable) {
Utils.showToast(this, ex, "could not open picker for font")
showToast(this, ex, "could not open picker for font")
}
R.id.btnSettingExport -> exportAppData()
@ -472,7 +472,7 @@ class ActAppSetting : AppCompatActivity()
.apply()
SavedAccount.clearRegistrationCache()
PollingWorker.queueUpdateListener(this)
Utils.showToast(this, false, getString(R.string.custom_stream_listener_was_reset))
showToast(this, false, getString(R.string.custom_stream_listener_was_reset))
}
}
}
@ -687,7 +687,7 @@ class ActAppSetting : AppCompatActivity()
private fun saveTimelineFont(uri : Uri?, file_name : String) : File? {
try {
if(uri == null) {
Utils.showToast(this, false, "missing uri.")
showToast(this, false, "missing uri.")
return null
}
@ -701,7 +701,7 @@ class ActAppSetting : AppCompatActivity()
val source = contentResolver.openInputStream(uri) // nullable
if(source == null) {
Utils.showToast(this, false, "openInputStream returns null. uri=%s", uri)
showToast(this, false, "openInputStream returns null. uri=%s", uri)
return null
} else {
source.use { inStream ->
@ -713,20 +713,20 @@ class ActAppSetting : AppCompatActivity()
val face = Typeface.createFromFile(tmp_file)
if(face == null) {
Utils.showToast(this, false, "Typeface.createFromFile() failed.")
showToast(this, false, "Typeface.createFromFile() failed.")
return null
}
val file = File(dir, file_name)
if(! tmp_file.renameTo(file)) {
Utils.showToast(this, false, "File operation failed.")
showToast(this, false, "File operation failed.")
return null
}
return file
} catch(ex : Throwable) {
log.trace(ex)
Utils.showToast(this, ex, "saveTimelineFont failed.")
showToast(this, ex, "saveTimelineFont failed.")
return null
}
@ -755,7 +755,7 @@ class ActAppSetting : AppCompatActivity()
return file
} catch(ex : Throwable) {
log.trace(ex)
Utils.showToast(this@ActAppSetting, ex, "exportAppData failed.")
showToast(this@ActAppSetting, ex, "exportAppData failed.")
}
return null
@ -784,7 +784,7 @@ class ActAppSetting : AppCompatActivity()
startActivityForResult(intent, REQUEST_CODE_APP_DATA_EXPORT)
} catch(ex : Throwable) {
log.trace(ex)
Utils.showToast(this@ActAppSetting, ex, "exportAppData failed.")
showToast(this@ActAppSetting, ex, "exportAppData failed.")
}
}
@ -804,7 +804,7 @@ class ActAppSetting : AppCompatActivity()
intent.type = "*/*"
startActivityForResult(intent, REQUEST_CODE_APP_DATA_IMPORT)
} catch(ex : Throwable) {
Utils.showToast(this, ex, "importAppData(1) failed.")
showToast(this, ex, "importAppData(1) failed.")
}
}

View File

@ -14,10 +14,19 @@ import java.util.ArrayList
import java.util.concurrent.atomic.AtomicReference
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.digestSHA256
class ActCallback : AppCompatActivity() {
companion object {
private val log = LogCategory("ActCallback")
const val ACTION_NOTIFICATION_CLICK = "notification_click"
internal val last_uri = AtomicReference<Uri>(null)
internal val sent_intent = AtomicReference<Intent>(null)
}
override fun onCreate(savedInstanceState : Bundle?) {
super.onCreate(savedInstanceState)
var intent : Intent? = intent
@ -145,7 +154,7 @@ class ActCallback : AppCompatActivity() {
cache_dir.mkdirs()
val name = "img." + System.currentTimeMillis().toString() + "." + Utils.digestSHA256(uri.toString())
val name = "img." + System.currentTimeMillis().toString() + "." + uri.toString().digestSHA256()
val dst = File(cache_dir, name)
@ -187,12 +196,4 @@ class ActCallback : AppCompatActivity() {
}
companion object {
private val log = LogCategory("ActCallback")
val ACTION_NOTIFICATION_CLICK = "notification_click"
internal val last_uri = AtomicReference<Uri>(null)
internal val sent_intent = AtomicReference<Intent>(null)
}
}

View File

@ -22,7 +22,7 @@ import java.text.NumberFormat
import java.util.Locale
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.createResizedBitmap
class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPickerDialogListener {
@ -56,12 +56,12 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
internal var density : Float = 0f
private lateinit var flColumnBackground : View
lateinit internal var ivColumnBackground : ImageView
lateinit internal var sbColumnBackgroundAlpha : SeekBar
internal lateinit var ivColumnBackground : ImageView
internal lateinit var sbColumnBackgroundAlpha : SeekBar
private lateinit var llColumnHeader : View
private lateinit var ivColumnHeader : ImageView
private lateinit var tvColumnName : TextView
lateinit internal var etAlpha : EditText
internal lateinit var etAlpha : EditText
private lateinit var tvSampleAcct : TextView
private lateinit var tvSampleContent : TextView
@ -195,18 +195,23 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
}
// 0xFF000000 と書きたいがkotlinではこれはlong型定数になってしまう
private val colorFF000000 :Int = (0xff shl 24 )
private val colorFF000000 : Int = (0xff shl 24)
override fun onColorSelected(dialogId : Int, @ColorInt colorSelected : Int) {
when(dialogId) {
COLOR_DIALOG_ID_HEADER_BACKGROUND -> column.header_bg_color = colorFF000000 or colorSelected
COLOR_DIALOG_ID_HEADER_FOREGROUND -> column.header_fg_color = colorFF000000 or colorSelected
COLOR_DIALOG_ID_COLUMN_BACKGROUND -> column.column_bg_color = colorFF000000 or colorSelected
COLOR_DIALOG_ID_HEADER_BACKGROUND -> column.header_bg_color = colorFF000000 or
colorSelected
COLOR_DIALOG_ID_HEADER_FOREGROUND -> column.header_fg_color = colorFF000000 or
colorSelected
COLOR_DIALOG_ID_COLUMN_BACKGROUND -> column.column_bg_color = colorFF000000 or
colorSelected
COLOR_DIALOG_ID_ACCT_TEXT -> {
column.acct_color = if(colorSelected == 0) 1 else colorSelected
}
COLOR_DIALOG_ID_CONTENT_TEXT -> {
column.content_color = if(colorSelected == 0) 1 else colorSelected
column.content_color = if(colorSelected == 0) 1 else colorSelected
}
}
show()
@ -219,7 +224,10 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
if(data != null) {
val uri = data.data
if(uri != null) {
contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
contentResolver.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION
)
column.column_bg_image = uri.toString()
show()
}
@ -260,7 +268,8 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
sbColumnBackgroundAlpha = findViewById(R.id.sbColumnBackgroundAlpha)
sbColumnBackgroundAlpha.max = PROGRESS_MAX
sbColumnBackgroundAlpha.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
sbColumnBackgroundAlpha.setOnSeekBarChangeListener(object :
SeekBar.OnSeekBarChangeListener {
override fun onStartTrackingTouch(seekBar : SeekBar) {}
override fun onStopTrackingTouch(seekBar : SeekBar) {
@ -272,14 +281,25 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
if(! fromUser) return
column.column_bg_image_alpha = progress / PROGRESS_MAX.toFloat()
ivColumnBackground.alpha = column.column_bg_image_alpha
etAlpha.setText(String.format(Locale.getDefault(), "%.4f", column.column_bg_image_alpha))
etAlpha.setText(
String.format(
Locale.getDefault(),
"%.4f",
column.column_bg_image_alpha
)
)
}
})
etAlpha = findViewById(R.id.etAlpha)
etAlpha.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s : CharSequence, start : Int, count : Int, after : Int) {
override fun beforeTextChanged(
s : CharSequence,
start : Int,
count : Int,
after : Int
) {
}
@ -290,7 +310,9 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
override fun afterTextChanged(s : Editable) {
if(loading_busy) return
try {
var f = NumberFormat.getInstance(Locale.getDefault()).parse(etAlpha.text.toString()).toFloat()
var f =
NumberFormat.getInstance(Locale.getDefault()).parse(etAlpha.text.toString())
.toFloat()
if(! f.isNaN()) {
if(f < 0f) f = 0f
if(f > 1f) f = 1f
@ -313,22 +335,38 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
if(c == 0) {
llColumnHeader.setBackgroundResource(R.drawable.btn_bg_ddd)
} else {
ViewCompat.setBackground(llColumnHeader, Styler.getAdaptiveRippleDrawable(
c,
if(column.header_fg_color != 0)
column.header_fg_color
else
Styler.getAttributeColor(this, R.attr.colorRippleEffect)
))
ViewCompat.setBackground(
llColumnHeader, Styler.getAdaptiveRippleDrawable(
c,
if(column.header_fg_color != 0)
column.header_fg_color
else
Styler.getAttributeColor(this, R.attr.colorRippleEffect)
)
)
}
c = column.header_fg_color
if(c == 0) {
tvColumnName.setTextColor(Styler.getAttributeColor(this, android.R.attr.textColorPrimary))
Styler.setIconDefaultColor(this, ivColumnHeader, column.getIconAttrId(column.column_type))
tvColumnName.setTextColor(
Styler.getAttributeColor(
this,
android.R.attr.textColorPrimary
)
)
Styler.setIconDefaultColor(
this,
ivColumnHeader,
column.getIconAttrId(column.column_type)
)
} else {
tvColumnName.setTextColor(c)
Styler.setIconCustomColor(this, ivColumnHeader, c, column.getIconAttrId(column.column_type))
Styler.setIconCustomColor(
this,
ivColumnHeader,
c,
column.getIconAttrId(column.column_type)
)
}
tvColumnName.text = column.getColumnName(false)
@ -340,18 +378,27 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
}
var alpha = column.column_bg_image_alpha
if( alpha.isNaN() ) {
if(alpha.isNaN()) {
alpha = 1f
column.column_bg_image_alpha = alpha
}
ivColumnBackground.alpha = alpha
sbColumnBackgroundAlpha.progress = (0.5f + alpha * PROGRESS_MAX).toInt()
etAlpha.setText(String.format(Locale.getDefault(), "%.4f", column.column_bg_image_alpha))
etAlpha.setText(
String.format(
Locale.getDefault(),
"%.4f",
column.column_bg_image_alpha
)
)
loadImage(ivColumnBackground, column.column_bg_image)
c = if(column.acct_color != 0) column.acct_color else Styler.getAttributeColor(this, R.attr.colorTimeSmall)
c = if(column.acct_color != 0) column.acct_color else Styler.getAttributeColor(
this,
R.attr.colorTimeSmall
)
tvSampleAcct.setTextColor(c)
c = if(column.content_color != 0) column.content_color else content_color_default
@ -375,9 +422,9 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
}
private fun loadImage(ivColumnBackground : ImageView, url : String ) {
private fun loadImage(ivColumnBackground : ImageView, url : String) {
try {
if( url.isEmpty() ) {
if(url.isEmpty()) {
closeBitmaps()
return
@ -392,7 +439,7 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
// 画像をロードして、成功したら表示してURLを覚える
val resize_max = (0.5f + 64f * density).toInt()
val uri = Uri.parse(url)
last_image_bitmap = Utils.createResizedBitmap(log, this, uri, false, resize_max)
last_image_bitmap = createResizedBitmap( this, uri, resize_max )
if(last_image_bitmap != null) {
ivColumnBackground.setImageBitmap(last_image_bitmap)
last_image_uri = url
@ -404,5 +451,4 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
}
}

View File

@ -23,10 +23,27 @@ import org.json.JSONObject
import java.util.ArrayList
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.activity
import jp.juggler.subwaytooter.util.showToast
class ActColumnList : AppCompatActivity() {
companion object {
private val log = LogCategory("ActColumnList")
internal const val TMP_FILE_COLUMN_LIST = "tmp_column_list"
const val EXTRA_ORDER = "order"
const val EXTRA_SELECTION = "selection"
fun open(activity : ActMain, currentItem : Int, requestCode : Int) {
val array = activity.app_state.encodeColumnList()
AppState.saveColumnList(activity, ActColumnList.TMP_FILE_COLUMN_LIST, array)
val intent = Intent(activity, ActColumnList::class.java)
intent.putExtra(ActColumnList.EXTRA_SELECTION, currentItem)
activity.startActivityForResult(intent, requestCode)
}
}
private lateinit var listView : DragListView
private lateinit var listAdapter : MyListAdapter
private var old_selection : Int = 0
@ -46,9 +63,9 @@ class ActColumnList : AppCompatActivity() {
override fun onSaveInstanceState(outState : Bundle?) {
super.onSaveInstanceState(outState)
outState?: return
outState ?: return
outState.putInt(EXTRA_SELECTION, old_selection)
//
@ -108,7 +125,10 @@ class ActColumnList : AppCompatActivity() {
// mRefreshLayout.setEnabled( false );
}
override fun onItemSwipeEnded(item : ListSwipeItem, swipedDirection : ListSwipeItem.SwipeDirection?) {
override fun onItemSwipeEnded(
item : ListSwipeItem,
swipedDirection : ListSwipeItem.SwipeDirection?
) {
// 操作完了でリフレッシュ許可
// mRefreshLayout.setEnabled( USE_SWIPE_REFRESH );
@ -116,7 +136,11 @@ class ActColumnList : AppCompatActivity() {
if(swipedDirection == ListSwipeItem.SwipeDirection.LEFT) {
val adapterItem = item.tag as MyItem
if(adapterItem.json.optBoolean(Column.KEY_DONT_CLOSE, false)) {
Utils.showToast(this@ActColumnList, false, R.string.column_has_dont_close_option)
showToast(
this@ActColumnList,
false,
R.string.column_has_dont_close_option
)
listView.resetSwipedViews(null)
return
}
@ -196,6 +220,7 @@ class ActColumnList : AppCompatActivity() {
// リスト要素のデータ
internal class MyItem(val json : JSONObject, val id : Long, context : Context) {
val name : String = json.optString(Column.KEY_COLUMN_NAME)
val acct : String = json.optString(Column.KEY_COLUMN_ACCESS)
val old_index = json.optInt(Column.KEY_OLD_INDEX)
@ -206,7 +231,8 @@ class ActColumnList : AppCompatActivity() {
init {
var c = json.optInt(Column.KEY_COLUMN_ACCESS_COLOR, 0)
this.acct_color_fg = if(c != 0) c else Styler.getAttributeColor(context, R.attr.colorColumnListItemText)
this.acct_color_fg =
if(c != 0) c else Styler.getAttributeColor(context, R.attr.colorColumnListItemText)
c = json.optInt(Column.KEY_COLUMN_ACCESS_COLOR_BG, 0)
this.acct_color_bg = c
@ -223,10 +249,10 @@ class ActColumnList : AppCompatActivity() {
, true // 長押しでドラッグ開始するなら真
) {
private val ivBookmark:View = viewRoot.findViewById(R.id.ivBookmark)
private val tvAccess:TextView = viewRoot.findViewById(R.id.tvAccess)
private val tvName :TextView= viewRoot.findViewById(R.id.tvName)
private val ivColumnIcon:ImageView = viewRoot.findViewById(R.id.ivColumnIcon)
private val ivBookmark : View = viewRoot.findViewById(R.id.ivBookmark)
private val tvAccess : TextView = viewRoot.findViewById(R.id.tvAccess)
private val tvName : TextView = viewRoot.findViewById(R.id.tvName)
private val ivColumnIcon : ImageView = viewRoot.findViewById(R.id.ivColumnIcon)
private val acct_pad_lr = (0.5f + 4f * viewRoot.resources.displayMetrics.density).toInt()
init {
@ -245,9 +271,11 @@ class ActColumnList : AppCompatActivity() {
tvAccess.setBackgroundColor(item.acct_color_bg)
tvAccess.setPaddingRelative(acct_pad_lr, 0, acct_pad_lr, 0)
tvName.text = item.name
ivColumnIcon.setImageResource(Styler.getAttributeResourceId(
this@ActColumnList, Column.getIconAttrId(item.acct, item.type)
))
ivColumnIcon.setImageResource(
Styler.getAttributeResourceId(
this@ActColumnList, Column.getIconAttrId(item.acct, item.type)
)
)
}
// @Override
@ -257,13 +285,13 @@ class ActColumnList : AppCompatActivity() {
override fun onItemClicked(view : View?) {
val item = itemView.tag as MyItem // itemView は親クラスのメンバ変数
val activity = Utils.getActivityFromView(view)
(activity as? ActColumnList)?.performItemSelected(item)
(view.activity as? ActColumnList)?.performItemSelected(item)
}
}
// ドラッグ操作中のデータ
private inner class MyDragItem internal constructor(context : Context, layoutId : Int) : DragItem(context, layoutId) {
private inner class MyDragItem internal constructor(context : Context, layoutId : Int) :
DragItem(context, layoutId) {
override fun onBindDragView(clickedView : View, dragView : View) {
val item = clickedView.tag as MyItem
@ -277,10 +305,14 @@ class ActColumnList : AppCompatActivity() {
tv.text = item.name
val ivColumnIcon = dragView.findViewById<ImageView>(R.id.ivColumnIcon)
ivColumnIcon.setImageResource(Styler.getAttributeResourceId(
this@ActColumnList, Column.getIconAttrId(item.acct, item.type)))
ivColumnIcon.setImageResource(
Styler.getAttributeResourceId(
this@ActColumnList, Column.getIconAttrId(item.acct, item.type)
)
)
dragView.findViewById<View>(R.id.ivBookmark).visibility = clickedView.findViewById<View>(R.id.ivBookmark).visibility
dragView.findViewById<View>(R.id.ivBookmark).visibility =
clickedView.findViewById<View>(R.id.ivBookmark).visibility
dragView.findViewById<View>(R.id.item_layout).setBackgroundColor(
Styler.getAttributeColor(this@ActColumnList, R.attr.list_item_bg_pressed_dragged)
@ -288,7 +320,8 @@ class ActColumnList : AppCompatActivity() {
}
}
private inner class MyListAdapter internal constructor() : DragItemAdapter<MyItem, MyViewHolder>() {
private inner class MyListAdapter internal constructor() :
DragItemAdapter<MyItem, MyViewHolder>() {
init {
@ -313,19 +346,4 @@ class ActColumnList : AppCompatActivity() {
}
companion object {
private val log = LogCategory("ActColumnList")
internal val TMP_FILE_COLUMN_LIST = "tmp_column_list"
val EXTRA_ORDER = "order"
val EXTRA_SELECTION = "selection"
fun open(activity : ActMain, currentItem : Int, requestCode : Int) {
val array = activity.app_state.encodeColumnList()
AppState.saveColumnList(activity, ActColumnList.TMP_FILE_COLUMN_LIST, array)
val intent = Intent(activity, ActColumnList::class.java)
intent.putExtra(ActColumnList.EXTRA_SELECTION, currentItem)
activity.startActivityForResult(intent, requestCode)
}
}
}

View File

@ -19,12 +19,28 @@ import org.hjson.JsonValue
import java.util.regex.Pattern
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.*
import okhttp3.Request
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
@ -120,17 +136,17 @@ class ActCustomStreamListener : AppCompatActivity(), View.OnClickListener, TextW
override fun onClick(v : View) {
when(v.id) {
R.id.btnDiscard -> {
Utils.hideKeyboard(this, etStreamListenerConfigurationUrl)
etStreamListenerConfigurationUrl.hideKeyboard()
finish()
}
R.id.btnTest -> {
Utils.hideKeyboard(this, etStreamListenerConfigurationUrl)
etStreamListenerConfigurationUrl.hideKeyboard()
startTest()
}
R.id.btnSave -> {
Utils.hideKeyboard(this, etStreamListenerConfigurationUrl)
etStreamListenerConfigurationUrl.hideKeyboard()
if(save()) {
SavedAccount.clearRegistrationCache()
PollingWorker.queueUpdateListener(this)
@ -142,20 +158,24 @@ class ActCustomStreamListener : AppCompatActivity(), View.OnClickListener, TextW
private fun save() : Boolean {
if(stream_config_json == null) {
Utils.showToast(this, false, "please test before save.")
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.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) {
Utils.runOnMainThread {
runOnMainLooper {
val old = tvLog.text.toString()
tvLog.text = if(old.isEmpty()) line else old + "\n" + line
}
@ -197,7 +217,13 @@ class ActCustomStreamListener : AppCompatActivity(), View.OnClickListener, TextW
}
if(! response.isSuccessful || bodyString?.isEmpty() != false) {
addLog(TootApiClient.formatResponse(response, "Can't get configuration from URL.", bodyString))
addLog(
TootApiClient.formatResponse(
response,
"Can't get configuration from URL.",
bodyString
)
)
break
}
@ -205,7 +231,7 @@ class ActCustomStreamListener : AppCompatActivity(), View.OnClickListener, TextW
JsonValue.readHjson(bodyString)
} catch(ex : Throwable) {
log.trace(ex)
addLog(Utils.formatError(ex, "Can't parse configuration data."))
addLog(ex.withCaption("Can't parse configuration data."))
break
}
@ -242,7 +268,11 @@ class ActCustomStreamListener : AppCompatActivity(), View.OnClickListener, TextW
}
val entry = entry_value.asObject()
val keys = arrayOf("urlStreamingListenerRegister", "urlStreamingListenerUnregister", "appId")
val keys = arrayOf(
"urlStreamingListenerRegister",
"urlStreamingListenerUnregister",
"appId"
)
for(key in keys) {
val v = entry.get(key)
if(! v.isString) {
@ -271,7 +301,7 @@ class ActCustomStreamListener : AppCompatActivity(), View.OnClickListener, TextW
call.execute()
} catch(ex : Throwable) {
log.trace(ex)
addLog(strInstance + "." + key + " : " + Utils.formatError(ex, "connect failed."))
addLog(ex.withCaption("$strInstance.$key : connect failed."))
has_error = true
}
@ -304,7 +334,7 @@ class ActCustomStreamListener : AppCompatActivity(), View.OnClickListener, TextW
} catch(ex : Throwable) {
log.trace(ex)
addLog(Utils.formatError(ex, "Can't read configuration from URL."))
addLog(ex.withCaption("Can't read configuration from URL."))
}
return null
@ -330,20 +360,4 @@ class ActCustomStreamListener : AppCompatActivity(), View.OnClickListener, TextW
task.executeOnExecutor(App1.task_executor)
}
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 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")
}
}

View File

@ -15,13 +15,16 @@ import com.jrummyapps.android.colorpicker.ColorPickerDialog
import com.jrummyapps.android.colorpicker.ColorPickerDialogListener
import org.json.JSONException
import org.json.JSONObject
import jp.juggler.subwaytooter.table.HighlightWord
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.toJsonObject
class ActHighlightWordEdit : AppCompatActivity(), View.OnClickListener, ColorPickerDialogListener, CompoundButton.OnCheckedChangeListener {
class ActHighlightWordEdit
: AppCompatActivity(),
View.OnClickListener,
ColorPickerDialogListener,
CompoundButton.OnCheckedChangeListener {
companion object {
internal val log = LogCategory("ActHighlightWordEdit")
@ -44,10 +47,10 @@ class ActHighlightWordEdit : AppCompatActivity(), View.OnClickListener, ColorPic
}
}
lateinit internal var item : HighlightWord
internal lateinit var item : HighlightWord
lateinit private var tvName : TextView
lateinit private var swSound : Switch
private lateinit var tvName : TextView
private lateinit var swSound : Switch
private var bBusy = false
@ -72,22 +75,23 @@ class ActHighlightWordEdit : AppCompatActivity(), View.OnClickListener, ColorPic
App1.setActivityTheme(this, false)
initUI()
item = HighlightWord(JSONObject(
if(savedInstanceState != null) savedInstanceState.getString(EXTRA_ITEM)
else intent.getStringExtra(EXTRA_ITEM)
))
item = HighlightWord(
(savedInstanceState?.getString(EXTRA_ITEM)
?: intent.getStringExtra(EXTRA_ITEM)
).toJsonObject()
)
showSampleText()
}
override fun onActivityResult(requestCode : Int, resultCode : Int, data : Intent) {
override fun onActivityResult(requestCode : Int, resultCode : Int, data : Intent?) {
when(requestCode) {
REQUEST_CODE_NOTIFICATION_SOUND -> {
if(resultCode == Activity.RESULT_OK) {
// RINGTONE_PICKERからの選択されたデータを取得する
val uri = Utils.getExtraObject(data, RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
val uri = data?.extras?.get(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
if(uri is Uri) {
item.sound_uri = uri.toString()
item.sound_type = HighlightWord.SOUND_TYPE_CUSTOM
@ -95,6 +99,7 @@ class ActHighlightWordEdit : AppCompatActivity(), View.OnClickListener, ColorPic
}
}
}
else -> super.onActivityResult(requestCode, resultCode, data)
}
}
@ -151,7 +156,10 @@ class ActHighlightWordEdit : AppCompatActivity(), View.OnClickListener, ColorPic
showSampleText()
}
R.id.btnBackgroundColorEdit -> openColorPicker(COLOR_DIALOG_ID_BACKGROUND, item.color_bg)
R.id.btnBackgroundColorEdit -> openColorPicker(
COLOR_DIALOG_ID_BACKGROUND,
item.color_bg
)
R.id.btnBackgroundColorReset -> {
item.color_bg = 0
@ -162,7 +170,8 @@ class ActHighlightWordEdit : AppCompatActivity(), View.OnClickListener, ColorPic
R.id.btnNotificationSoundReset -> {
item.sound_uri = null
item.sound_type = if(swSound.isChecked) HighlightWord.SOUND_TYPE_DEFAULT else HighlightWord.SOUND_TYPE_NONE
item.sound_type =
if(swSound.isChecked) HighlightWord.SOUND_TYPE_DEFAULT else HighlightWord.SOUND_TYPE_NONE
}
}
@ -175,7 +184,8 @@ class ActHighlightWordEdit : AppCompatActivity(), View.OnClickListener, ColorPic
item.sound_type = HighlightWord.SOUND_TYPE_NONE
} else {
item.sound_type = if(item.sound_uri?.isEmpty() != false ) HighlightWord.SOUND_TYPE_DEFAULT else HighlightWord.SOUND_TYPE_CUSTOM
item.sound_type =
if(item.sound_uri?.isEmpty() != false) HighlightWord.SOUND_TYPE_DEFAULT else HighlightWord.SOUND_TYPE_CUSTOM
}
}
@ -212,7 +222,7 @@ class ActHighlightWordEdit : AppCompatActivity(), View.OnClickListener, ColorPic
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, false)
try {
val sound_uri = item.sound_uri
val uri = if(sound_uri?.isEmpty()!= false ) null else Uri.parse(sound_uri)
val uri = if(sound_uri?.isEmpty() != false) null else Uri.parse(sound_uri)
if(uri != null) {
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, uri)
}
@ -223,5 +233,4 @@ class ActHighlightWordEdit : AppCompatActivity(), View.OnClickListener, ColorPic
startActivityForResult(chooser, REQUEST_CODE_NOTIFICATION_SOUND)
}
}

View File

@ -19,15 +19,14 @@ import com.woxthebox.draglistview.DragListView
import com.woxthebox.draglistview.swipe.ListSwipeHelper
import com.woxthebox.draglistview.swipe.ListSwipeItem
import org.json.JSONObject
import java.lang.ref.WeakReference
import java.util.ArrayList
import jp.juggler.subwaytooter.dialog.DlgTextInput
import jp.juggler.subwaytooter.table.HighlightWord
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.showToast
import jp.juggler.subwaytooter.util.toJsonObject
class ActHighlightWordList : AppCompatActivity(), View.OnClickListener {
@ -236,7 +235,7 @@ class ActHighlightWordList : AppCompatActivity(), View.OnClickListener {
private fun create() {
DlgTextInput.show(this, getString(R.string.new_item), "", object : DlgTextInput.Callback {
override fun onEmptyError() {
Utils.showToast(this@ActHighlightWordList, true, R.string.word_empty)
showToast(this@ActHighlightWordList, true, R.string.word_empty)
}
override fun onOK(dialog : Dialog, text : String) {
@ -264,7 +263,7 @@ class ActHighlightWordList : AppCompatActivity(), View.OnClickListener {
override fun onActivityResult(requestCode : Int, resultCode : Int, data : Intent?) {
if(requestCode == REQUEST_CODE_EDIT && resultCode == RESULT_OK && data != null) {
try {
val item = HighlightWord(JSONObject(data.getStringExtra(ActHighlightWordEdit.EXTRA_ITEM)))
val item = HighlightWord(data.getStringExtra(ActHighlightWordEdit.EXTRA_ITEM).toJsonObject())
item.save()
loadData()
return

View File

@ -27,7 +27,6 @@ import android.support.design.widget.NavigationView
import android.support.v4.view.GravityCompat
import android.support.v4.widget.DrawerLayout
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.DefaultItemAnimator
import android.view.Menu
import android.view.MenuItem
import android.view.Window
@ -214,7 +213,7 @@ class ActMain : AppCompatActivity()
if(tag is ItemViewHolder) {
column = tag.column
break
}else if(tag is ViewHolderItem ) {
} else if(tag is ViewHolderItem) {
column = tag.ivh.column
break
} else if(tag is ViewHolderHeaderBase) {
@ -267,27 +266,27 @@ class ActMain : AppCompatActivity()
////////////////////////////////////////////////////////////////////////////
val follow_complete_callback : EmptyCallback = {
Utils.showToast(this@ActMain, false, R.string.follow_succeeded)
showToast(this@ActMain, false, R.string.follow_succeeded)
}
val unfollow_complete_callback : EmptyCallback = {
Utils.showToast(this@ActMain, false, R.string.unfollow_succeeded)
showToast(this@ActMain, false, R.string.unfollow_succeeded)
}
val favourite_complete_callback : EmptyCallback = {
Utils.showToast(this@ActMain, false, R.string.favourite_succeeded)
showToast(this@ActMain, false, R.string.favourite_succeeded)
}
val unfavourite_complete_callback : EmptyCallback = {
Utils.showToast(this@ActMain, false, R.string.unfavourite_succeeded)
showToast(this@ActMain, false, R.string.unfavourite_succeeded)
}
val boost_complete_callback : EmptyCallback = {
Utils.showToast(this@ActMain, false, R.string.boost_succeeded)
showToast(this@ActMain, false, R.string.boost_succeeded)
}
val unboost_complete_callback : EmptyCallback = {
Utils.showToast(this@ActMain, false, R.string.unboost_succeeded)
showToast(this@ActMain, false, R.string.unboost_succeeded)
}
private var nScreenColumn : Int = 0
@ -454,12 +453,13 @@ class ActMain : AppCompatActivity()
// アカウント設定から戻ってきたら、カラムを消す必要があるかもしれない
run {
val new_order = ArrayList<Int>()
for( i in 0 until app_state.column_list.size){
for(i in 0 until app_state.column_list.size) {
val column = app_state.column_list[i]
if(! column.access_info.isNA) {
val sa = SavedAccount.loadAccount(this@ActMain, column.access_info.db_id)
if(sa == null) continue
// 存在確認
SavedAccount.loadAccount(this@ActMain, column.access_info.db_id)
?: continue
}
new_order.add(i)
}
@ -484,7 +484,7 @@ class ActMain : AppCompatActivity()
updateColumnStripSelection(- 1, - 1f)
for(c in app_state.column_list) {
c.fireShowContent(reason="ActMain onStart",reset=true)
c.fireShowContent(reason = "ActMain onStart", reset = true)
}
// 相対時刻表示
@ -634,7 +634,8 @@ class ActMain : AppCompatActivity()
post_helper.in_reply_to_id = - 1L
post_helper.attachment_list = null
Utils.hideKeyboard(this, etQuickToot)
etQuickToot.hideKeyboard()
post_helper.post(
account
, false
@ -738,7 +739,10 @@ class ActMain : AppCompatActivity()
val idx = data.getIntExtra(ActColumnCustomize.EXTRA_COLUMN_INDEX, 0)
if(idx >= 0 && idx < app_state.column_list.size) {
app_state.column_list[idx].fireColumnColor()
app_state.column_list[idx].fireShowContent(reason="ActMain column color changed",reset=true)
app_state.column_list[idx].fireShowContent(
reason = "ActMain column color changed",
reset = true
)
}
updateColumnStrip()
}
@ -858,7 +862,7 @@ class ActMain : AppCompatActivity()
if(vs == ve && vs != RecyclerView.NO_POSITION) {
app_state.column_list[vs].let(closer)
} else {
Utils.showToast(
showToast(
this,
false,
getString(R.string.cant_close_column_by_back_button_when_multiple_column_shown)
@ -1246,10 +1250,10 @@ class ActMain : AppCompatActivity()
})
env.tablet_pager.itemAnimator = null
// val animator = env.tablet_pager.itemAnimator
// if( animator is DefaultItemAnimator){
// animator.supportsChangeAnimations = false
// }
// val animator = env.tablet_pager.itemAnimator
// if( animator is DefaultItemAnimator){
// animator.supportsChangeAnimations = false
// }
env.tablet_snap_helper = GravitySnapHelper(Gravity.START)
env.tablet_snap_helper.attachToRecyclerView(env.tablet_pager)
@ -1423,7 +1427,7 @@ class ActMain : AppCompatActivity()
status_id
)
} catch(ex : Throwable) {
Utils.showToast(this, ex, "can't parse status id.")
showToast(this, ex, "can't parse status id.")
}
return
@ -1575,7 +1579,7 @@ class ActMain : AppCompatActivity()
client.account = sa
} catch(ex : Throwable) {
log.trace(ex)
return TootApiResult(Utils.formatError(ex, "invalid state"))
return TootApiResult(ex.withCaption("invalid state"))
}
} else if(sv.startsWith("host:")) {
@ -1622,27 +1626,27 @@ class ActMain : AppCompatActivity()
// cancelled.
} else if(error != null) {
Utils.showToast(this@ActMain, true, result.error)
showToast(this@ActMain, true, result.error)
} else if(token_info == null) {
Utils.showToast(this@ActMain, true, "can't get access token.")
showToast(this@ActMain, true, "can't get access token.")
} else if(jsonObject == null) {
Utils.showToast(this@ActMain, true, "can't parse json response.")
showToast(this@ActMain, true, "can't parse json response.")
} else if(ta == null) {
// 自分のユーザネームを取れなかった
// …普通はエラーメッセージが設定されてるはずだが
Utils.showToast(this@ActMain, true, "can't verify user credential.")
showToast(this@ActMain, true, "can't verify user credential.")
} else if(sa != null) {
// アクセストークン更新時
// インスタンスは同じだと思うが、ユーザ名が異なる可能性がある
if(sa.username != ta.username) {
Utils.showToast(this@ActMain, true, R.string.user_name_not_match)
showToast(this@ActMain, true, R.string.user_name_not_match)
} else {
Utils.showToast(this@ActMain, false, R.string.access_token_updated_for, sa.acct)
showToast(this@ActMain, false, R.string.access_token_updated_for, sa.acct)
// DBの情報を更新する
sa.updateTokenInfo(token_info)
@ -1690,7 +1694,7 @@ class ActMain : AppCompatActivity()
if(bModified) {
account.saveSetting()
}
Utils.showToast(this@ActMain, false, R.string.account_confirmed)
showToast(this@ActMain, false, R.string.account_confirmed)
// 通知の更新が必要かもしれない
PollingWorker.queueUpdateNotification(this@ActMain)
@ -1771,7 +1775,7 @@ class ActMain : AppCompatActivity()
}
override fun onEmptyError() {
Utils.showToast(this@ActMain, true, R.string.token_not_specified)
showToast(this@ActMain, true, R.string.token_not_specified)
}
})
}
@ -1802,7 +1806,7 @@ class ActMain : AppCompatActivity()
fun closeColumn(bConfirm : Boolean, column : Column) {
if(column.dont_close) {
Utils.showToast(this, false, R.string.column_has_dont_close_option)
showToast(this, false, R.string.column_has_dont_close_option)
return
}
@ -1920,7 +1924,7 @@ class ActMain : AppCompatActivity()
)
}
} catch(ex : Throwable) {
Utils.showToast(this, ex, "can't parse status id.")
showToast(this, ex, "can't parse status id.")
}
return
@ -2063,8 +2067,6 @@ class ActMain : AppCompatActivity()
return false
}
private fun addColumn(column : Column, indexArg : Int) : Int {
var index = indexArg
val size = app_state.column_list.size
@ -2197,13 +2199,13 @@ class ActMain : AppCompatActivity()
env.tablet_pager_adapter.notifyDataSetChanged()
}
private fun scrollToColumn(index : Int, smoothScroll:Boolean = true ) {
private fun scrollToColumn(index : Int, smoothScroll : Boolean = true) {
scrollColumnStrip(index)
phoneTab(
// スマホはスムーススクロール基本ありだがたまにしない
{ env -> env.pager.setCurrentItem(index, smoothScroll) },
// タブレットでスムーススクロールさせると頻繁にオーバーランするので絶対しない
{ env -> env.tablet_pager.scrollToPosition(index) }
)
@ -2238,7 +2240,7 @@ class ActMain : AppCompatActivity()
AsyncTask<Void, String, ArrayList<Column>?>() {
internal fun setProgressMessage(sv : String) {
Utils.runOnMainThread { progress.setMessage(sv) }
runOnMainLooper { progress.setMessage(sv) }
}
override fun doInBackground(vararg params : Void) : ArrayList<Column>? {
@ -2256,7 +2258,7 @@ class ActMain : AppCompatActivity()
// ローカルファイルにコピーする
val source = contentResolver.openInputStream(uri)
if(source == null) {
Utils.showToast(this@ActMain, true, "openInputStream failed.")
showToast(this@ActMain, true, "openInputStream failed.")
return null
} else {
source.use { inStream ->
@ -2281,7 +2283,7 @@ class ActMain : AppCompatActivity()
}
} catch(ex : Throwable) {
log.trace(ex)
Utils.showToast(this@ActMain, ex, "importAppData failed.")
showToast(this@ActMain, ex, "importAppData failed.")
}
return null
@ -2360,7 +2362,7 @@ class ActMain : AppCompatActivity()
private fun resizeAutoCW(column_w : Int) {
val sv = Pref.spAutoCWLines(pref)
nAutoCwLines = Utils.parse_int(sv, - 1)
nAutoCwLines = sv.optInt() ?: - 1
if(nAutoCwLines > 0) {
val lv_pad = (0.5f + 12 * density).toInt()
val icon_width = avatarIconSize

View File

@ -38,8 +38,6 @@ import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
import com.google.android.exoplayer2.util.Util
import org.json.JSONArray
import java.util.LinkedList
import jp.juggler.subwaytooter.api.TootApiClient
@ -48,9 +46,7 @@ import jp.juggler.subwaytooter.api.TootTask
import jp.juggler.subwaytooter.api.TootTaskRunner
import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.dialog.ActionsDialog
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.ProgressResponseBody
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.*
import jp.juggler.subwaytooter.view.PinchBitmapView
class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
@ -68,11 +64,10 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
internal const val EXTRA_IDX = "idx"
internal const val EXTRA_DATA = "data"
internal fun <T: TootAttachmentLike> encodeMediaList(list : ArrayList<T>?)
= list?.encodeJson()?.toString() ?: "[]"
internal fun <T : TootAttachmentLike> encodeMediaList(list : ArrayList<T>?) =
list?.encodeJson()?.toString() ?: "[]"
internal fun decodeMediaList(src : String?)
= parseList(::TootAttachment,JSONArray(src))
internal fun decodeMediaList(src : String?) = parseList(::TootAttachment, src?.toJsonArray() )
fun open(activity : ActMain, list : ArrayList<TootAttachmentLike>, idx : Int) {
val intent = Intent(activity, ActMediaViewer::class.java)
@ -102,7 +97,10 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
log.d("exoPlayer onTimelineChanged")
}
override fun onTracksChanged(trackGroups : TrackGroupArray?, trackSelections : TrackSelectionArray?) {
override fun onTracksChanged(
trackGroups : TrackGroupArray?,
trackSelections : TrackSelectionArray?
) {
log.d("exoPlayer onTracksChanged")
}
@ -119,7 +117,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
val now = SystemClock.elapsedRealtime()
if(now - buffering_last_shown >= short_limit && exoPlayer.duration >= short_limit) {
buffering_last_shown = now
Utils.showToast(this@ActMediaViewer, false, R.string.video_buffering)
showToast(this@ActMediaViewer, false, R.string.video_buffering)
}
/*
exoPlayer.getDuration() may returns negative value (TIME_UNSET ,same as Long.MIN_VALUE + 1).
@ -133,7 +131,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
override fun onPlayerError(error : ExoPlaybackException) {
log.d("exoPlayer onPlayerError")
Utils.showToast(this@ActMediaViewer, error, "player error.")
showToast(this@ActMediaViewer, error, "player error.")
}
override fun onPositionDiscontinuity() {
@ -148,9 +146,9 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
override fun onSaveInstanceState(outState : Bundle?) {
super.onSaveInstanceState(outState)
outState ?: return
outState.putInt(EXTRA_IDX, idx)
outState.putString(EXTRA_DATA, encodeMediaList(media_list))
}
@ -210,11 +208,22 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
loadDelta(delta)
}
override fun onMove(bitmap_w : Float, bitmap_h : Float, tx : Float, ty : Float, scale : Float) {
override fun onMove(
bitmap_w : Float,
bitmap_h : Float,
tx : Float,
ty : Float,
scale : Float
) {
App1.getAppState(this@ActMediaViewer).handler.post(Runnable {
if(isDestroyed) return@Runnable
if(tvStatus.visibility == View.VISIBLE) {
tvStatus.text = getString(R.string.zooming_of, bitmap_w.toInt(), bitmap_h.toInt(), scale)
tvStatus.text = getString(
R.string.zooming_of,
bitmap_w.toInt(),
bitmap_h.toInt(),
scale
)
}
})
}
@ -248,7 +257,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
}
val ta = media_list[idx]
val description = ta.description
if( description?.isNotEmpty() == true ){
if(description?.isNotEmpty() == true) {
svDescription.visibility = View.VISIBLE
tvDescription.text = description
}
@ -271,7 +280,8 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
}
@SuppressLint("StaticFieldLeak") private fun loadVideo(ta : TootAttachment) {
@SuppressLint("StaticFieldLeak")
private fun loadVideo(ta : TootAttachment) {
val url = ta.getLargeUrl(App1.pref)
if(url == null) {
@ -288,7 +298,12 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
this, Util.getUserAgent(this, getString(R.string.app_name)), defaultBandwidthMeter
)
val mediaSource = ExtractorMediaSource(Uri.parse(url), dataSourceFactory, extractorsFactory, App1.getAppState(this).handler, ExtractorMediaSource.EventListener { error -> showError(Utils.formatError(error, "load error.")) }
val mediaSource = ExtractorMediaSource(
Uri.parse(url),
dataSourceFactory,
extractorsFactory,
App1.getAppState(this).handler,
ExtractorMediaSource.EventListener { showError(it.withCaption("load error.") ) }
)
exoPlayer.prepare(mediaSource)
exoPlayer.playWhenReady = true
@ -300,9 +315,10 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
}
}
@SuppressLint("StaticFieldLeak") private fun loadBitmap(ta : TootAttachment) {
@SuppressLint("StaticFieldLeak")
private fun loadBitmap(ta : TootAttachment) {
val urlList = ta.getLargeUrlList(App1.pref)
if(urlList.isEmpty() ) {
if(urlList.isEmpty()) {
showError("missing media attachment url.")
return
}
@ -346,17 +362,17 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
internal fun getHttpCached(client : TootApiClient, url : String) : TootApiResult? {
val result = TootApiResult.makeWithCaption(url)
if(!client.sendRequest(result){
okhttp3.Request.Builder()
.url(url)
.cacheControl(App1.CACHE_5MIN)
.build()
}) return result
if(! client.sendRequest(result) {
okhttp3.Request.Builder()
.url(url)
.cacheControl(App1.CACHE_5MIN)
.build()
}) return result
if( client.isApiCancelled ) return null
if(client.isApiCancelled) return null
val response = requireNotNull(result.response)
if(! response.isSuccessful) {
return result.setError( TootApiClient.formatResponse(response,result.caption))
return result.setError(TootApiClient.formatResponse(response, result.caption))
}
try {
@ -367,15 +383,15 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
}
client.publishApiProgressRatio(bytesRead.toInt(), bytesTotal.toInt())
}
if( client.isApiCancelled ) return null
if(client.isApiCancelled) return null
} catch(ex : Throwable) {
result.setError( TootApiClient.formatResponse(response,result.caption,"?"))
result.setError(TootApiClient.formatResponse(response, result.caption, "?"))
}
return result
}
override fun background(client : TootApiClient) : TootApiResult? {
if( urlList.isEmpty()) return TootApiResult("missing url")
if(urlList.isEmpty()) return TootApiResult("missing url")
var result : TootApiResult? = null
for(url in urlList) {
result = getHttpCached(client, url)
@ -383,7 +399,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
if(data != null) {
client.publishApiProgress("decoding image…")
val bitmap = decodeBitmap(data, 2048)
if( bitmap != null ) {
if(bitmap != null) {
this.bitmap = bitmap
break
}
@ -394,10 +410,10 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
override fun handleResult(result : TootApiResult?) {
val bitmap = this.bitmap
if(bitmap != null){
if(bitmap != null) {
pbvImage.setBitmap(bitmap)
}else if(result != null){
Utils.showToast(this@ActMediaViewer, true, result.error)
} else if(result != null) {
showToast(this@ActMediaViewer, true, result.error)
}
}
})
@ -425,7 +441,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
R.id.btnMore -> more(media_list[idx])
}
} catch(ex : Throwable) {
Utils.showToast(this, ex, "action failed.")
showToast(this, ex, "action failed.")
}
}
@ -434,7 +450,10 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
private fun download(ta : TootAttachmentLike) {
val permissionCheck = ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
val permissionCheck = ContextCompat.checkSelfPermission(
this,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE
)
if(permissionCheck != PackageManager.PERMISSION_GRANTED) {
preparePermission()
return
@ -462,7 +481,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
it.remove()
} else if(url == dh.url) {
// 履歴に同じURLがあればエラーとする
Utils.showToast(this, false, R.string.dont_repeat_download_to_same_url)
showToast(this, false, R.string.dont_repeat_download_to_same_url)
return
}
}
@ -478,7 +497,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
val size = pathSegments.size
for(i in size - 1 downTo 0) {
val s = pathSegments[i]
if( s?.isNotEmpty() == true) {
if(s?.isNotEmpty() == true) {
fileName = s
break
}
@ -506,7 +525,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
downLoadManager.enqueue(request)
Utils.showToast(this, false, R.string.downloading)
showToast(this, false, R.string.downloading)
}
private fun share(action : String, url : String) {
@ -523,7 +542,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
startActivity(intent)
} catch(ex : Throwable) {
Utils.showToast(this, ex, "can't open app.")
showToast(this, ex, "can't open app.")
}
}
@ -546,10 +565,10 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
//クリップボードにデータを格納
cm.primaryClip = cd
Utils.showToast(this, false, R.string.url_is_copied)
showToast(this, false, R.string.url_is_copied)
} catch(ex : Throwable) {
Utils.showToast(this, ex, "clipboard access failed.")
showToast(this, ex, "clipboard access failed.")
}
}
@ -579,8 +598,13 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
ad.show(this, null)
}
private fun addMoreMenu(ad : ActionsDialog, caption_prefix : String, url : String?, action : String) {
if( url ?.isEmpty() != false ) return
private fun addMoreMenu(
ad : ActionsDialog,
caption_prefix : String,
url : String?,
action : String
) {
if(url?.isEmpty() != false) return
val caption = getString(R.string.open_browser_of, caption_prefix)
@ -590,17 +614,18 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
} catch(ex : Throwable) {
Utils.showToast(this@ActMediaViewer, ex, "can't open app.")
showToast(this@ActMediaViewer, ex, "can't open app.")
}
}
}
private fun preparePermission() {
if(Build.VERSION.SDK_INT >= 23) {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), PERMISSION_REQUEST_CODE
ActivityCompat.requestPermissions(
this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), PERMISSION_REQUEST_CODE
)
} else {
Utils.showToast(this, true, R.string.missing_permission_to_access_media)
showToast(this, true, R.string.missing_permission_to_access_media)
}
}
@ -619,7 +644,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
++ i
}
if(bNotGranted) {
Utils.showToast(this, true, R.string.missing_permission_to_access_media)
showToast(this, true, R.string.missing_permission_to_access_media)
} else {
download(media_list[idx])
}

View File

@ -21,13 +21,11 @@ import jp.juggler.subwaytooter.util.LogCategory
class ActMutedApp : AppCompatActivity() {
companion object {
private val log = LogCategory("ActMutedApp")
}
lateinit internal var listView : DragListView
internal lateinit var listView : DragListView
private lateinit var listAdapter : MyListAdapter
override fun onCreate(savedInstanceState : Bundle?) {
@ -86,13 +84,16 @@ class ActMutedApp : AppCompatActivity() {
// mRefreshLayout.setEnabled( false );
}
override fun onItemSwipeEnded(item : ListSwipeItem?, swipedDirection : ListSwipeItem.SwipeDirection?) {
override fun onItemSwipeEnded(
item : ListSwipeItem?,
swipedDirection : ListSwipeItem.SwipeDirection?
) {
// 操作完了でリフレッシュ許可
// mRefreshLayout.setEnabled( USE_SWIPE_REFRESH );
// 左にスワイプした(右端に青が見えた) なら要素を削除する
if(swipedDirection == ListSwipeItem.SwipeDirection.LEFT) {
val o = item ?.tag
val o = item?.tag
if(o is MyItem) {
MutedApp.delete(o.name)
listAdapter.removeItem(listAdapter.getPositionForItem(o))
@ -128,7 +129,8 @@ class ActMutedApp : AppCompatActivity() {
internal class MyItem(val id : Long, val name : String)
// リスト要素のViewHolder
internal class MyViewHolder(viewRoot : View) : DragItemAdapter.ViewHolder(viewRoot, R.id.ivDragHandle, false) {
internal class MyViewHolder(viewRoot : View) :
DragItemAdapter.ViewHolder(viewRoot, R.id.ivDragHandle, false) {
val tvName : TextView
@ -142,7 +144,7 @@ class ActMutedApp : AppCompatActivity() {
viewRoot.supportedSwipeDirection = ListSwipeItem.SwipeDirection.LEFT
}
}// View ID。 ここを押すとドラッグ操作をすぐに開始する
} // View ID。 ここを押すとドラッグ操作をすぐに開始する
// 長押しでドラッグ開始するなら真
fun bind(item : MyItem) {
@ -161,10 +163,12 @@ class ActMutedApp : AppCompatActivity() {
}
// ドラッグ操作中のデータ
private inner class MyDragItem internal constructor(context : Context, layoutId : Int) : DragItem(context, layoutId) {
private inner class MyDragItem internal constructor(context : Context, layoutId : Int) :
DragItem(context, layoutId) {
override fun onBindDragView(clickedView : View, dragView : View) {
dragView.findViewById<TextView>(R.id.tvName).text = clickedView.findViewById<TextView>(R.id.tvName).text
dragView.findViewById<TextView>(R.id.tvName).text =
clickedView.findViewById<TextView>(R.id.tvName).text
dragView.findViewById<View>(R.id.item_layout).setBackgroundColor(
Styler.getAttributeColor(this@ActMutedApp, R.attr.list_item_bg_pressed_dragged)
@ -172,7 +176,8 @@ class ActMutedApp : AppCompatActivity() {
}
}
private inner class MyListAdapter internal constructor() : DragItemAdapter<MyItem, MyViewHolder>() {
private inner class MyListAdapter internal constructor() :
DragItemAdapter<MyItem, MyViewHolder>() {
init {
setHasStableIds(true)

View File

@ -19,7 +19,7 @@ import com.jrummyapps.android.colorpicker.ColorPickerDialog
import com.jrummyapps.android.colorpicker.ColorPickerDialogListener
import jp.juggler.subwaytooter.table.AcctColor
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.hideKeyboard
class ActNickname : AppCompatActivity(), View.OnClickListener, ColorPickerDialogListener {
@ -29,7 +29,12 @@ class ActNickname : AppCompatActivity(), View.OnClickListener, ColorPickerDialog
internal const val EXTRA_SHOW_NOTIFICATION_SOUND = "show_notification_sound"
internal const val REQUEST_CODE_NOTIFICATION_SOUND = 2
fun open(activity : Activity, full_acct : String, bShowNotificationSound : Boolean, requestCode : Int) {
fun open(
activity : Activity,
full_acct : String,
bShowNotificationSound : Boolean,
requestCode : Int
) {
val intent = Intent(activity, ActNickname::class.java)
intent.putExtra(EXTRA_ACCT, full_acct)
intent.putExtra(EXTRA_SHOW_NOTIFICATION_SOUND, bShowNotificationSound)
@ -78,7 +83,12 @@ class ActNickname : AppCompatActivity(), View.OnClickListener, ColorPickerDialog
private fun initUI() {
title = getString(if(show_notification_sound) R.string.nickname_and_color_and_notification_sound else R.string.nickname_and_color)
title = getString(
if(show_notification_sound)
R.string.nickname_and_color_and_notification_sound
else
R.string.nickname_and_color
)
setContentView(R.layout.act_nickname)
Styler.fixHorizontalPadding(findViewById(R.id.llContent))
@ -112,7 +122,12 @@ class ActNickname : AppCompatActivity(), View.OnClickListener, ColorPickerDialog
btnNotificationSoundReset.isEnabled = bBefore8
etNickname.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s : CharSequence, start : Int, count : Int, after : Int) {
override fun beforeTextChanged(
s : CharSequence,
start : Int,
count : Int,
after : Int
) {
}
@ -129,7 +144,8 @@ class ActNickname : AppCompatActivity(), View.OnClickListener, ColorPickerDialog
private fun load() {
bLoading = true
findViewById<View>(R.id.llNotificationSound).visibility = if(show_notification_sound) View.VISIBLE else View.GONE
findViewById<View>(R.id.llNotificationSound).visibility =
if(show_notification_sound) View.VISIBLE else View.GONE
tvAcct.text = acct
@ -146,13 +162,17 @@ class ActNickname : AppCompatActivity(), View.OnClickListener, ColorPickerDialog
private fun save() {
if(bLoading) return
AcctColor(
acct, etNickname.text.toString().trim { it <= ' ' }, color_fg, color_bg, notification_sound_uri
acct,
etNickname.text.toString().trim { it <= ' ' },
color_fg,
color_bg,
notification_sound_uri
).save(System.currentTimeMillis())
}
private fun show() {
val s = etNickname.text.toString().trim { it <= ' ' }
tvPreview.text = if( s.isNotEmpty() ) s else acct
tvPreview.text = if(s.isNotEmpty()) s else acct
var c : Int
c = color_fg
@ -167,7 +187,7 @@ class ActNickname : AppCompatActivity(), View.OnClickListener, ColorPickerDialog
val builder : ColorPickerDialog.Builder
when(v.id) {
R.id.btnTextColorEdit -> {
Utils.hideKeyboard(this, etNickname)
etNickname.hideKeyboard()
builder = ColorPickerDialog.newBuilder()
.setDialogType(ColorPickerDialog.TYPE_CUSTOM)
.setAllowPresets(true)
@ -176,12 +196,14 @@ class ActNickname : AppCompatActivity(), View.OnClickListener, ColorPickerDialog
if(color_fg != 0) builder.setColor(color_fg)
builder.show(this)
}
R.id.btnTextColorReset -> {
color_fg = 0
show()
}
R.id.btnBackgroundColorEdit -> {
Utils.hideKeyboard(this, etNickname)
etNickname.hideKeyboard()
builder = ColorPickerDialog.newBuilder()
.setDialogType(ColorPickerDialog.TYPE_CUSTOM)
.setAllowPresets(true)
@ -190,15 +212,18 @@ class ActNickname : AppCompatActivity(), View.OnClickListener, ColorPickerDialog
if(color_bg != 0) builder.setColor(color_bg)
builder.show(this)
}
R.id.btnBackgroundColorReset -> {
color_bg = 0
show()
}
R.id.btnSave -> {
save()
setResult(Activity.RESULT_OK)
finish()
}
R.id.btnDiscard -> {
setResult(Activity.RESULT_CANCELED)
finish()
@ -228,7 +253,9 @@ class ActNickname : AppCompatActivity(), View.OnClickListener, ColorPickerDialog
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, false)
try {
val notification_sound_uri = this.notification_sound_uri
val uri = if(notification_sound_uri?.isEmpty() != false ) null else Uri.parse(notification_sound_uri)
val uri = if(notification_sound_uri?.isEmpty() != false) null else Uri.parse(
notification_sound_uri
)
if(uri != null) {
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, uri)
}
@ -239,16 +266,15 @@ class ActNickname : AppCompatActivity(), View.OnClickListener, ColorPickerDialog
startActivityForResult(chooser, REQUEST_CODE_NOTIFICATION_SOUND)
}
override fun onActivityResult(requestCode : Int, resultCode : Int, data : Intent) {
override fun onActivityResult(requestCode : Int, resultCode : Int, data : Intent?) {
if(resultCode == Activity.RESULT_OK && requestCode == REQUEST_CODE_NOTIFICATION_SOUND) {
// RINGTONE_PICKERからの選択されたデータを取得する
val uri = Utils.getExtraObject(data, RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
if(uri is Uri ) {
val uri = data?.extras?.get(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
if(uri is Uri) {
notification_sound_uri = uri.toString()
}
}
super.onActivityResult(requestCode, resultCode, data)
}
}

View File

@ -9,26 +9,25 @@ import org.apache.commons.io.IOUtils
import java.io.ByteArrayOutputStream
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.decodeUTF8
class ActOSSLicense : AppCompatActivity() {
companion object {
private val log = LogCategory("ActOSSLicense")
}
override fun onCreate(savedInstanceState : Bundle?) {
super.onCreate(savedInstanceState)
App1.setActivityTheme(this, true)
setContentView(R.layout.act_oss_license)
try {
resources.openRawResource(R.raw.oss_license)?.use{ inData ->
resources.openRawResource(R.raw.oss_license)?.use { inData ->
ByteArrayOutputStream().use { bao ->
IOUtils.copy(inData, bao)
val text = Utils.decodeUTF8(bao.toByteArray())
val tv = findViewById<TextView>(R.id.tvText)
tv.text = text
tv.text = bao.toByteArray().decodeUTF8()
}
}
} catch(ex : Throwable) {

View File

@ -147,7 +147,12 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
private const val STATE_MUSHROOM_START = "mushroom_start"
private const val STATE_MUSHROOM_END = "mushroom_end"
fun open(activity : Activity, request_code : Int, account_db_id : Long, reply_status : TootStatus?) {
fun open(
activity : Activity,
request_code : Int,
account_db_id : Long,
reply_status : TootStatus?
) {
val intent = Intent(activity, ActPost::class.java)
intent.putExtra(KEY_ACCOUNT_DB_ID, account_db_id)
if(reply_status != null) {
@ -156,7 +161,12 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
activity.startActivityForResult(intent, request_code)
}
fun open(activity : Activity, request_code : Int, account_db_id : Long, initial_text : String?) {
fun open(
activity : Activity,
request_code : Int,
account_db_id : Long,
initial_text : String?
) {
val intent = Intent(activity, ActPost::class.java)
intent.putExtra(KEY_ACCOUNT_DB_ID, account_db_id)
if(initial_text != null) {
@ -165,7 +175,12 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
activity.startActivityForResult(intent, request_code)
}
fun open(activity : Activity, request_code : Int, account_db_id : Long, sent_intent : Intent?) {
fun open(
activity : Activity,
request_code : Int,
account_db_id : Long,
sent_intent : Intent?
) {
val intent = Intent(activity, ActPost::class.java)
intent.putExtra(KEY_ACCOUNT_DB_ID, account_db_id)
if(sent_intent != null) {
@ -175,9 +190,9 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
}
internal fun check_exist(url : String?) : Boolean {
if( url?.isEmpty() != false ) return false
if(url?.isEmpty() != false) return false
try {
val request = Request.Builder().url(url ).build()
val request = Request.Builder().url(url).build()
val call = App1.ok_http_client.newCall(request)
val response = call.execute()
if(response.isSuccessful) {
@ -242,7 +257,8 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
}
}
private val scroll_listener : ViewTreeObserver.OnScrollChangedListener = ViewTreeObserver.OnScrollChangedListener { post_helper.onScrollChanged() }
private val scroll_listener : ViewTreeObserver.OnScrollChangedListener =
ViewTreeObserver.OnScrollChangedListener { post_helper.onScrollChanged() }
//////////////////////////////////////////////////////////
// Account
@ -302,7 +318,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
if(uri != null) {
// 単一選択
var type = data.type
if(type?.isEmpty() != false ) {
if(type?.isEmpty() != false) {
type = contentResolver.getType(uri)
}
addAttachment(uri, type)
@ -324,15 +340,15 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
if(resultCode != Activity.RESULT_OK) {
// 失敗したら DBからデータを削除
val uriCameraImage = this.uriCameraImage
if(uriCameraImage != null){
contentResolver.delete(uriCameraImage , null, null)
if(uriCameraImage != null) {
contentResolver.delete(uriCameraImage, null, null)
this@ActPost.uriCameraImage = null
}
} else {
// 画像のURL
val uri : Uri? = data?.data ?: uriCameraImage
if(uri != null) {
val type :String? = contentResolver.getType(uri)
val type : String? = contentResolver.getType(uri)
addAttachment(uri, type)
}
}
@ -345,7 +361,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
} else if(requestCode == REQUEST_CODE_MUSHROOM && resultCode == Activity.RESULT_OK) {
val text = data?.getStringExtra("replace_key")
if( text != null ){
if(text != null) {
applyMushroomResult(text)
}
}
@ -384,7 +400,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
initUI()
if(account_list.isEmpty()) {
Utils.showToast(this, true, R.string.please_add_account)
showToast(this, true, R.string.please_add_account)
finish()
return
}
@ -395,7 +411,8 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
mushroom_start = savedInstanceState.getInt(STATE_MUSHROOM_START, 0)
mushroom_end = savedInstanceState.getInt(STATE_MUSHROOM_END, 0)
val account_db_id = savedInstanceState.getLong(KEY_ACCOUNT_DB_ID, SavedAccount.INVALID_DB_ID)
val account_db_id =
savedInstanceState.getLong(KEY_ACCOUNT_DB_ID, SavedAccount.INVALID_DB_ID)
if(account_db_id != SavedAccount.INVALID_DB_ID) {
var i = 0
val ie = account_list.size
@ -433,20 +450,14 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
this.attachment_list.clear()
try {
val array = JSONArray(sv)
var i = 0
val ie = array.length()
while(i < ie) {
val array = sv.toJsonArray()
for( i in 0 until array.length()){
try {
val a = parseItem(::TootAttachment, array.optJSONObject(i))
if(a != null) {
attachment_list.add(PostAttachment(a))
}
if(a != null) attachment_list.add(PostAttachment(a))
} catch(ex : Throwable) {
log.trace(ex)
}
++ i
}
} catch(ex : Throwable) {
log.trace(ex)
@ -497,7 +508,8 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
addAttachment(uri, type)
}
} else if(Intent.ACTION_SEND_MULTIPLE == action) {
val list_uri = sent_intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)
val list_uri =
sent_intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)
if(list_uri != null) {
for(uri in list_uri) {
if(uri != null) {
@ -510,7 +522,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
if(Intent.ACTION_SEND == action) {
val sv = sent_intent.getStringExtra(Intent.EXTRA_TEXT)
if(sv != null) {
val svEmoji = DecodeOptions(decodeEmoji = true).decodeEmoji(this,sv)
val svEmoji = DecodeOptions(decodeEmoji = true).decodeEmoji(this, sv)
etContent.setText(svEmoji)
etContent.setSelection(svEmoji.length)
}
@ -521,7 +533,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
var sv : String? = intent.getStringExtra(KEY_INITIAL_TEXT)
if(sv != null) {
val svEmoji = DecodeOptions(decodeEmoji = true).decodeEmoji(this,sv)
val svEmoji = DecodeOptions(decodeEmoji = true).decodeEmoji(this, sv)
etContent.setText(svEmoji)
etContent.setSelection(svEmoji.length)
}
@ -531,7 +543,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
sv = intent.getStringExtra(KEY_REPLY_STATUS)
if(sv != null && account != null) {
try {
val reply_status = TootParser(this@ActPost, account).status(JSONObject(sv))
val reply_status = TootParser(this@ActPost, account).status(sv.toJsonObject())
if(reply_status != null) {
// CW をリプライ元に合わせる
@ -560,7 +572,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
// 今回メンションを追加する?
val who_acct = account.getFullAcct(reply_status.account)
if(mention_list.contains("@" + who_acct )) {
if(mention_list.contains("@" + who_acct)) {
// 既に含まれている
} else if(! account.isMe(reply_status.account) || mention_list.isEmpty()) {
// 自分ではない、もしくは、メンションが空
@ -574,7 +586,8 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
}
if(sb.isNotEmpty()) {
sb.append(' ')
val svEmoji = DecodeOptions(decodeEmoji = true).decodeEmoji(this,sb.toString())
val svEmoji =
DecodeOptions(decodeEmoji = true).decodeEmoji(this, sb.toString())
etContent.setText(svEmoji)
etContent.setSelection(svEmoji.length)
}
@ -600,7 +613,10 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
this.visibility = reply_status.visibility
} else {
// デフォルトの方が公開範囲が大きい場合、リプライ元に合わせて公開範囲を狭める
if(TootStatus.isVisibilitySpoilRequired(this.visibility, reply_status.visibility)) {
if(TootStatus.isVisibilitySpoilRequired(
this.visibility,
reply_status.visibility
)) {
this.visibility = reply_status.visibility
}
}
@ -649,7 +665,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
override fun onSaveInstanceState(outState : Bundle?) {
super.onSaveInstanceState(outState)
outState ?: return
outState.putInt(STATE_MUSHROOM_INPUT, mushroom_input)
@ -757,7 +773,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
findViewById<View>(R.id.btnPlugin).setOnClickListener(this)
findViewById<View>(R.id.btnEmojiPicker).setOnClickListener(this)
for(iv in ivMedia) {
iv.setOnClickListener(this)
iv.setDefaultImageResId(Styler.getAttributeResourceId(this, R.attr.ic_loading))
@ -801,7 +817,8 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
var s = EmojiDecoder.decodeShortCode(etContent.text.toString())
length += s.codePointCount(0, s.length)
s = if(cbContentWarning.isChecked) EmojiDecoder.decodeShortCode(etContentWarning.text.toString()) else ""
s =
if(cbContentWarning.isChecked) EmojiDecoder.decodeShortCode(etContentWarning.text.toString()) else ""
length += s.codePointCount(0, s.length)
val max : Int
@ -817,7 +834,10 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
val remain = max - length
tvCharCount.text = Integer.toString(remain)
val color = Styler.getAttributeColor(this, if(remain < 0) R.attr.colorRegexFilterError else android.R.attr.textColorPrimary)
val color = Styler.getAttributeColor(
this,
if(remain < 0) R.attr.colorRegexFilterError else android.R.attr.textColorPrimary
)
tvCharCount.setTextColor(color)
}
@ -847,7 +867,12 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
if(AcctColor.hasColorForeground(ac)) {
btnAccount.setTextColor(ac.color_fg)
} else {
btnAccount.setTextColor(Styler.getAttributeColor(this, android.R.attr.textColorPrimary))
btnAccount.setTextColor(
Styler.getAttributeColor(
this,
android.R.attr.textColorPrimary
)
)
}
}
}
@ -856,7 +881,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
if(! attachment_list.isEmpty()) {
// 添付ファイルがあったら確認の上添付ファイルを捨てないと切り替えられない
Utils.showToast(this, false, R.string.cant_change_account_when_attachment_specified)
showToast(this, false, R.string.cant_change_account_when_attachment_specified)
return
}
@ -917,7 +942,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
selectAccount(a)
try {
if(TootStatus.isVisibilitySpoilRequired(this.visibility, a.visibility)) {
Utils.showToast(this@ActPost, true, R.string.spoil_visibility_for_account)
showToast(this@ActPost, true, R.string.spoil_visibility_for_account)
this.visibility = a.visibility
showVisibility()
}
@ -930,6 +955,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
@SuppressLint("StaticFieldLeak")
private fun startReplyConversion(access_info : SavedAccount) {
val in_reply_to_url = this.in_reply_to_url
if(in_reply_to_url == null) {
// 下書きが古い形式の場合、URLがないので別タンスへの移動ができない
AlertDialog.Builder(this@ActPost)
@ -946,7 +972,11 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
internal var target_status : TootStatus? = null
override fun background(client : TootApiClient) : TootApiResult? {
// 検索APIに他タンスのステータスのURLを投げると、自タンスのステータスを得られる
val path = String.format(Locale.JAPAN, Column.PATH_SEARCH, Uri.encode(in_reply_to_url)) + "&resolve=1"
val path = String.format(
Locale.JAPAN,
Column.PATH_SEARCH,
in_reply_to_url.encodePercent()
) + "&resolve=1"
val result = client.request(path)
val jsonObject = result?.jsonObject
@ -970,7 +1000,11 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
in_reply_to_id = target_status.id
setAccountWithVisibilityConversion(access_info)
} else {
Utils.showToast(this@ActPost, true, getString(R.string.in_reply_to_id_conversion_failed) + "\n" + result.error)
showToast(
this@ActPost,
true,
getString(R.string.in_reply_to_id_conversion_failed) + "\n" + result.error
)
}
}
})
@ -1018,10 +1052,12 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
AlertDialog.Builder(this)
.setTitle(R.string.media_attachment)
.setItems(arrayOf<CharSequence>(
getString(R.string.set_description),
getString(R.string.delete)
)) { _, i ->
.setItems(
arrayOf<CharSequence>(
getString(R.string.set_description),
getString(R.string.delete)
)
) { _, i ->
when(i) {
0 -> editAttachmentDescription(pa)
1 -> deleteAttachment(pa)
@ -1050,19 +1086,23 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
private fun editAttachmentDescription(pa : PostAttachment) {
val a = pa.attachment
if(a == null) {
Utils.showToast(this, true, R.string.attachment_description_cant_edit_while_uploading)
showToast(this, true, R.string.attachment_description_cant_edit_while_uploading)
return
}
DlgTextInput.show(this, getString(R.string.attachment_description), a.description, object : DlgTextInput.Callback {
override fun onOK(dialog : Dialog, text : String) {
setAttachmentDescription(pa, dialog, text)
}
override fun onEmptyError() {
Utils.showToast(this@ActPost, true, R.string.description_empty)
}
})
DlgTextInput.show(
this,
getString(R.string.attachment_description),
a.description,
object : DlgTextInput.Callback {
override fun onOK(dialog : Dialog, text : String) {
setAttachmentDescription(pa, dialog, text)
}
override fun onEmptyError() {
showToast(this@ActPost, true, R.string.description_empty)
}
})
}
@SuppressLint("StaticFieldLeak")
@ -1107,14 +1147,15 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
}
} else {
Utils.showToast(this@ActPost, true, result.error)
showToast(this@ActPost, true, result.error)
}
}
})
}
private fun openAttachment() {
val permissionCheck = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
val permissionCheck =
ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
if(permissionCheck != PackageManager.PERMISSION_GRANTED) {
preparePermission()
return
@ -1142,12 +1183,12 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
private fun performAttachment() {
if(attachment_list.size >= 4) {
Utils.showToast(this, false, R.string.attachment_too_many)
showToast(this, false, R.string.attachment_too_many)
return
}
if(account == null) {
Utils.showToast(this, false, R.string.account_select_please)
showToast(this, false, R.string.account_select_please)
return
}
@ -1161,7 +1202,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
startActivityForResult(intent, REQUEST_CODE_ATTACHMENT)
} catch(ex : Throwable) {
log.trace(ex)
Utils.showToast(this, ex, "ACTION_OPEN_DOCUMENT failed.")
showToast(this, ex, "ACTION_OPEN_DOCUMENT failed.")
}
}
@ -1191,12 +1232,17 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
// 設定からリサイズ指定を読む
val resize_to = list_resize_max[Pref.ipResizeImage(pref)]
val bitmap = Utils.createResizedBitmap(log, this, uri, true, resize_to)
val bitmap = createResizedBitmap(
this,
uri,
resize_to,
skipIfNoNeedToResizeAndRotate = true
)
if(bitmap != null) {
try {
val cache_dir = externalCacheDir
if(cache_dir == null) {
Utils.showToast(this, false, "getExternalCacheDir returns null.")
showToast(this, false, "getExternalCacheDir returns null.")
break
}
@ -1233,7 +1279,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
} catch(ex : Throwable) {
log.trace(ex)
Utils.showToast(this, ex, "Resizing image failed.")
showToast(this, ex, "Resizing image failed.")
}
break
@ -1254,25 +1300,26 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
}
}
@SuppressLint("StaticFieldLeak") private fun addAttachment(uri : Uri, mime_type : String?) {
@SuppressLint("StaticFieldLeak")
private fun addAttachment(uri : Uri, mime_type : String?) {
if(attachment_list.size >= 4) {
Utils.showToast(this, false, R.string.attachment_too_many)
showToast(this, false, R.string.attachment_too_many)
return
}
val account = this@ActPost.account
if(account == null) {
Utils.showToast(this, false, R.string.account_select_please)
showToast(this, false, R.string.account_select_please)
return
}
if(mime_type?.isEmpty() != false) {
Utils.showToast(this, false, R.string.mime_type_missing)
showToast(this, false, R.string.mime_type_missing)
return
} else if(! acceptable_mime_types.contains(mime_type)) {
Utils.showToast(this, true, R.string.mime_type_not_acceptable, mime_type)
showToast(this, true, R.string.mime_type_not_acceptable, mime_type)
return
}
@ -1281,9 +1328,9 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
val pa = PostAttachment(this)
attachment_list.add(pa)
showMediaAttachment()
Utils.showToast(this, false, R.string.attachment_uploading)
showToast(this, false, R.string.attachment_uploading)
TootTaskRunner(this,TootTaskRunner.PROGRESS_NONE).run(account , object : TootTask {
TootTaskRunner(this, TootTaskRunner.PROGRESS_NONE).run(account, object : TootTask {
override fun background(client : TootApiClient) : TootApiResult? {
if(mime_type.isEmpty()) {
return TootApiResult("mime_type is empty.")
@ -1292,39 +1339,43 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
try {
val opener = createOpener(uri, mime_type)
val sv = Pref.spMediaSizeMax(pref)
var media_size_max = 1000000 * Utils.parse_int(sv, 8)
if(media_size_max < 1000000) media_size_max = 1000000
val media_size_max =
1000000 * Math.max(1, Pref.spMediaSizeMax.optInt(pref) ?: 8)
val content_length = getStreamSize(true, opener.open())
if(content_length > media_size_max) {
return TootApiResult(getString(R.string.file_size_too_big, media_size_max / 1000000))
return TootApiResult(
getString(
R.string.file_size_too_big,
media_size_max / 1000000
)
)
}
val multipart_body = MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart(
"file", getDocumentName(uri), object : RequestBody() {
override fun contentType() : MediaType? {
return MediaType.parse(opener.mimeType)
}
@Throws(IOException::class)
override fun contentLength() : Long {
return content_length
}
@Throws(IOException::class)
override fun writeTo(sink : BufferedSink) {
opener.open().use { inData ->
val tmp = ByteArray(4096)
while(true) {
val r = inData.read(tmp, 0, tmp.size)
if(r <= 0) break
sink.write(tmp, 0, r)
override fun contentType() : MediaType? {
return MediaType.parse(opener.mimeType)
}
@Throws(IOException::class)
override fun contentLength() : Long {
return content_length
}
@Throws(IOException::class)
override fun writeTo(sink : BufferedSink) {
opener.open().use { inData ->
val tmp = ByteArray(4096)
while(true) {
val r = inData.read(tmp, 0, tmp.size)
if(r <= 0) break
sink.write(tmp, 0, r)
}
}
}
}
}
)
.build()
@ -1347,7 +1398,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
return result
} catch(ex : Throwable) {
return TootApiResult(Utils.formatError(ex, "read failed."))
return TootApiResult(ex.withCaption("read failed."))
}
}
@ -1356,7 +1407,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
if(pa.attachment == null) {
pa.status = PostAttachment.STATUS_UPLOAD_FAILED
if(result != null) {
Utils.showToast(this@ActPost, true, result.error)
showToast(this@ActPost, true, result.error)
}
} else {
pa.status = PostAttachment.STATUS_UPLOADED
@ -1385,7 +1436,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
val a = pa.attachment
if(a != null) {
// アップロード完了
Utils.showToast(this@ActPost, false, R.string.attachment_uploaded)
showToast(this@ActPost, false, R.string.attachment_uploaded)
// 投稿欄の末尾に追記する
val selStart = etContent.selectionStart
@ -1419,7 +1470,8 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
val values = ContentValues()
values.put(MediaStore.Images.Media.TITLE, filename)
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
uriCameraImage = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
uriCameraImage =
contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
intent.putExtra(MediaStore.EXTRA_OUTPUT, uriCameraImage)
@ -1427,7 +1479,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
startActivityForResult(intent, REQUEST_CODE_CAMERA)
} catch(ex : Throwable) {
log.trace(ex)
Utils.showToast(this, ex, "opening camera app failed.")
showToast(this, ex, "opening camera app failed.")
}
}
@ -1436,11 +1488,14 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
if(Build.VERSION.SDK_INT >= 23) {
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE) // Manifest.permission.CAMERA,
, PERMISSION_REQUEST_CODE
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE) // Manifest.permission.CAMERA,
,
PERMISSION_REQUEST_CODE
)
} else {
Utils.showToast(this, true, R.string.missing_permission_to_access_media)
showToast(this, true, R.string.missing_permission_to_access_media)
}
}
@ -1459,7 +1514,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
++ i
}
if(bNotGranted) {
Utils.showToast(this, true, R.string.missing_permission_to_access_media)
showToast(this, true, R.string.missing_permission_to_access_media)
} else {
openAttachment()
}
@ -1502,7 +1557,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
}
private fun showVisibility() {
btnVisibility.setImageResource(Styler.getVisibilityIcon(this, visibility ))
btnVisibility.setImageResource(Styler.getVisibilityIcon(this, visibility))
}
private fun performVisibility() {
@ -1541,7 +1596,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
) {
post_helper.openEmojiPickerFromMore()
}
dialog.addAction(
getString(R.string.clear_text)
@ -1574,7 +1629,6 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
dialog.show(this, null)
}
///////////////////////////////////////////////////////////////////////////////////////
// post
@ -1584,7 +1638,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
// アップロード中は投稿できない
for(pa in attachment_list) {
if(pa.status == PostAttachment.STATUS_UPLOADING) {
Utils.showToast(this, false, R.string.media_attachment_still_uploading)
showToast(this, false, R.string.media_attachment_still_uploading)
return
}
}
@ -1633,7 +1687,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
tvReplyTo.text = DecodeOptions(
short = true,
decodeEmoji = true
).decodeHTML(this@ActPost, account, in_reply_to_text)
ivReply.setImageUrl(pref, Styler.calcIconRound(ivReply.layoutParams), in_reply_to_image)
}
@ -1649,7 +1703,8 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
private fun saveDraft() {
val content = etContent.text.toString()
val content_warning = if(cbContentWarning.isChecked) etContentWarning.text.toString() else ""
val content_warning =
if(cbContentWarning.isChecked) etContentWarning.text.toString() else ""
val isEnquete = cbEnquete.isChecked
val str_choice = arrayOf(
@ -1683,7 +1738,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
json.put(DRAFT_CONTENT_WARNING_CHECK, cbContentWarning.isChecked)
json.put(DRAFT_NSFW_CHECK, cbNSFW.isChecked)
json.put(DRAFT_VISIBILITY, visibility)
json.put(DRAFT_ACCOUNT_DB_ID, account?.db_id ?: -1L )
json.put(DRAFT_ACCOUNT_DB_ID, account?.db_id ?: - 1L)
json.put(DRAFT_ATTACHMENT_LIST, tmp_attachment_list)
json.put(DRAFT_REPLY_ID, in_reply_to_id)
json.put(DRAFT_REPLY_TEXT, in_reply_to_text)
@ -1724,8 +1779,8 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
override fun doInBackground(vararg params : Void) : String? {
var content = draft.optString(DRAFT_CONTENT)
val account_db_id = Utils.optLongX(draft, DRAFT_ACCOUNT_DB_ID, - 1L)
var content = draft.parseString(DRAFT_CONTENT) ?: ""
val account_db_id = draft.parseLong(DRAFT_ACCOUNT_DB_ID) ?: - 1L
var tmp_attachment_list = draft.optJSONArray(DRAFT_ATTACHMENT_LIST)
val account = SavedAccount.loadAccount(this@ActPost, account_db_id)
@ -1735,7 +1790,8 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
var i = 0
val ie = tmp_attachment_list.length()
while(i < ie) {
val ta = parseItem(::TootAttachment, tmp_attachment_list.optJSONObject(i))
val ta =
parseItem(::TootAttachment, tmp_attachment_list.optJSONObject(i))
val text_url = ta?.text_url
if(text_url?.isNotEmpty() == true) {
content = content.replace(text_url, "")
@ -1757,13 +1813,13 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
this.account = account
// アカウントがあるなら基本的にはすべての情報を復元できるはずだが、いくつか確認が必要だ
val api_client = TootApiClient(this@ActPost, callback=object : TootApiCallback {
val api_client = TootApiClient(this@ActPost, callback = object : TootApiCallback {
override val isApiCancelled : Boolean
get() = isCancelled
override fun publishApiProgress(s : String) {
Utils.runOnMainThread { progress.setMessage(s) }
runOnMainLooper { progress.setMessage(s) }
}
})
@ -1830,19 +1886,20 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
val content_warning_checked = draft.optBoolean(DRAFT_CONTENT_WARNING_CHECK)
val nsfw_checked = draft.optBoolean(DRAFT_NSFW_CHECK)
val tmp_attachment_list = draft.optJSONArray(DRAFT_ATTACHMENT_LIST)
val reply_id = Utils.optLongX(draft, DRAFT_REPLY_ID, - 1L)
val reply_id = draft.parseLong(DRAFT_REPLY_ID) ?: - 1L
val reply_text = draft.optString(DRAFT_REPLY_TEXT, null)
val reply_image = draft.optString(DRAFT_REPLY_IMAGE, null)
val reply_url = draft.optString(DRAFT_REPLY_URL, null)
val draft_visibility = draft.parseString(DRAFT_VISIBILITY)
val evEmoji = DecodeOptions(decodeEmoji = true).decodeEmoji(this@ActPost,content)
val evEmoji = DecodeOptions(decodeEmoji = true).decodeEmoji(this@ActPost, content)
etContent.setText(evEmoji)
etContent.setSelection(evEmoji.length)
etContentWarning.setText(content_warning)
etContentWarning.setSelection(content_warning.length)
cbContentWarning.isChecked = content_warning_checked
cbNSFW.isChecked = nsfw_checked
this@ActPost.visibility = visibility
if(draft_visibility != null) this@ActPost.visibility = draft_visibility
cbEnquete.isChecked = draft.optBoolean(DRAFT_IS_ENQUETE, false)
val array = draft.optJSONArray(DRAFT_ENQUETE_ITEMS)
@ -2000,8 +2057,8 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
else -> R.raw.recommended_plugin_en
}
Utils.loadRawResource(this, res_id)?.let { data ->
val text = Utils.decodeUTF8(data)
this.loadRawResource(res_id)?.let { data ->
val text = data.decodeUTF8()
val viewRoot = layoutInflater.inflate(R.layout.dlg_plugin_missing, null, false)
val tvText = viewRoot.findViewById<TextView>(R.id.tvText)

View File

@ -17,7 +17,7 @@ import jp.juggler.subwaytooter.table.MutedWord
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.DecodeOptions
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.showToast
class ActText : AppCompatActivity(), View.OnClickListener {
@ -225,10 +225,10 @@ class ActText : AppCompatActivity(), View.OnClickListener {
clipboard.primaryClip = clip
Utils.showToast(this, false, R.string.copy_complete)
showToast(this, false, R.string.copy_complete)
} catch(ex : Throwable) {
log.trace(ex)
Utils.showToast(this, ex, "copy failed.")
showToast(this, ex, "copy failed.")
}
}
@ -244,7 +244,7 @@ class ActText : AppCompatActivity(), View.OnClickListener {
} catch(ex : Throwable) {
log.trace(ex)
Utils.showToast(this, ex, "send failed.")
showToast(this, ex, "send failed.")
}
}
@ -252,7 +252,7 @@ class ActText : AppCompatActivity(), View.OnClickListener {
private fun search() {
val sv = selection
if( sv.isEmpty() ) {
Utils.showToast(this, false, "please select search keyword")
showToast(this, false, "please select search keyword")
return
}
try {
@ -263,7 +263,7 @@ class ActText : AppCompatActivity(), View.OnClickListener {
}
} catch(ex : Throwable) {
log.trace(ex)
Utils.showToast(this, ex, "search failed.")
showToast(this, ex, "search failed.")
}
}
@ -271,7 +271,7 @@ class ActText : AppCompatActivity(), View.OnClickListener {
private fun searchToot(resultCode : Int) {
val sv = selection
if(sv.isEmpty() ) {
Utils.showToast(this, false, "please select search keyword")
showToast(this, false, "please select search keyword")
return
}
try {
@ -291,10 +291,10 @@ class ActText : AppCompatActivity(), View.OnClickListener {
for(column in App1.getAppState(this).column_list) {
column.onMuteAppUpdated()
}
Utils.showToast(this, false, R.string.word_was_muted)
showToast(this, false, R.string.word_was_muted)
} catch(ex : Throwable) {
log.trace(ex)
Utils.showToast(this, ex, "muteWord failed.")
showToast(this, ex, "muteWord failed.")
}
}

View File

@ -47,11 +47,7 @@ import jp.juggler.subwaytooter.table.PostDraft
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.table.TagSet
import jp.juggler.subwaytooter.table.UserRelation
import jp.juggler.subwaytooter.util.CustomEmojiCache
import jp.juggler.subwaytooter.util.CustomEmojiLister
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.ProgressResponseBody
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.*
import okhttp3.Cache
import okhttp3.CacheControl
import okhttp3.CipherSuite
@ -222,7 +218,7 @@ class App1 : Application() {
lateinit var ok_http_client : OkHttpClient
lateinit var ok_http_client2 : OkHttpClient
private lateinit var ok_http_client2 : OkHttpClient
lateinit var pref : SharedPreferences
@ -531,7 +527,7 @@ class App1 : Application() {
customTabsIntent.launchUrl(activity, Uri.parse(url))
} catch(ex : Throwable) {
log.trace(ex)
Utils.showToast(activity, false, "can't open browser app")
showToast(activity, false, "can't open browser app")
}
}

View File

@ -21,7 +21,7 @@ import java.util.HashMap
import java.util.Locale
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.parseLong
object AppDataExporter {
@ -69,7 +69,13 @@ object AppDataExporter {
}
else -> throw RuntimeException(String.format(Locale.JAPAN, "bad data type: JSONObject key =%s", k))
else -> throw RuntimeException(
String.format(
Locale.JAPAN,
"bad data type: JSONObject key =%s",
k
)
)
}
}
}
@ -94,7 +100,14 @@ object AppDataExporter {
JsonToken.NUMBER -> dst.put(name, reader.nextDouble())
else -> throw RuntimeException(String.format(Locale.JAPAN, "bad data type: %s key =%s", token, name))
else -> throw RuntimeException(
String.format(
Locale.JAPAN,
"bad data type: %s key =%s",
token,
name
)
)
}
}
reader.endObject()
@ -216,7 +229,8 @@ object AppDataExporter {
} // 無視する
}
reader.endObject()
val new_id = db.insertWithOnConflict(table, null, cv, SQLiteDatabase.CONFLICT_REPLACE)
val new_id =
db.insertWithOnConflict(table, null, cv, SQLiteDatabase.CONFLICT_REPLACE)
if(new_id == - 1L) {
throw RuntimeException("importTable: invalid row_id")
}
@ -251,7 +265,13 @@ object AppDataExporter {
(v is Float && v.isNaN()) -> writer.value(MAGIC_NAN)
else -> writer.value(v)
}
else -> throw RuntimeException(String.format(Locale.JAPAN, "writePref. bad data type: Preference key =%s", k))
else -> throw RuntimeException(
String.format(
Locale.JAPAN,
"writePref. bad data type: Preference key =%s",
k
)
)
}
}
writer.endObject()
@ -271,7 +291,7 @@ object AppDataExporter {
continue
}
val prefItem = Pref.map.get(k)
val prefItem = Pref.map[k]
when(prefItem) {
is Pref.BooleanPref -> e.putBoolean(k, reader.nextBoolean())
is Pref.IntPref -> e.putInt(k, reader.nextInt())
@ -312,16 +332,21 @@ object AppDataExporter {
}
@Throws(IOException::class, JSONException::class)
private fun readColumn(app_state : AppState, reader : JsonReader, id_map : HashMap<Long, Long>) : ArrayList<Column> {
private fun readColumn(
app_state : AppState,
reader : JsonReader,
id_map : HashMap<Long, Long>
) : ArrayList<Column> {
val result = ArrayList<Column>()
reader.beginArray()
while(reader.hasNext()) {
val item = readJsonObject(reader)
val old_id = Utils.optLongX(item, Column.KEY_ACCOUNT_ROW_ID, - 1L)
val old_id = item.parseLong(Column.KEY_ACCOUNT_ROW_ID) ?: - 1L
if(old_id == - 1L) {
// 検索カラムは NAアカウントと紐ついている。変換の必要はない
} else {
val new_id = id_map[old_id] ?: throw RuntimeException("readColumn: can't convert account id")
val new_id =
id_map[old_id] ?: throw RuntimeException("readColumn: can't convert account id")
item.put(Column.KEY_ACCOUNT_ROW_ID, new_id)
}
try {

View File

@ -35,10 +35,8 @@ import java.util.regex.Pattern
import jp.juggler.subwaytooter.api.entity.TootStatus
import jp.juggler.subwaytooter.table.HighlightWord
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.span.MyClickableSpan
import jp.juggler.subwaytooter.util.PostAttachment
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.*
class AppState(internal val context : Context, internal val pref : SharedPreferences) {
@ -63,11 +61,11 @@ class AppState(internal val context : Context, internal val pref : SharedPrefere
try {
context.openFileOutput(fileName, Context.MODE_PRIVATE).use { os ->
os.write(Utils.encodeUTF8(array.toString()))
os.write(array.toString().encodeUTF8())
}
} catch(ex : Throwable) {
log.trace(ex)
Utils.showToast(context, ex, "saveColumnList failed.")
showToast(context, ex, "saveColumnList failed.")
}
}
@ -78,12 +76,12 @@ class AppState(internal val context : Context, internal val pref : SharedPrefere
context.openFileInput(fileName).use { inData ->
val bao = ByteArrayOutputStream(inData.available())
IOUtils.copy(inData, bao)
return JSONArray(Utils.decodeUTF8(bao.toByteArray()))
return bao.toByteArray().decodeUTF8().toJsonArray()
}
} catch(ignored : FileNotFoundException) {
} catch(ex : Throwable) {
log.trace(ex)
Utils.showToast(context, ex, "loadColumnList failed.")
showToast(context, ex, "loadColumnList failed.")
}
return null
@ -128,7 +126,7 @@ class AppState(internal val context : Context, internal val pref : SharedPrefere
// initからプロパティにアクセスする場合、そのプロパティはinitより上で定義されていないとダメっぽい
// そしてその他のメソッドからval プロパティにアクセスする場合、そのプロパティはメソッドより上で初期化されていないとダメっぽい
init {
this.handler = Handler()
this.density = context.resources.displayMetrics.density
@ -190,7 +188,10 @@ class AppState(internal val context : Context, internal val pref : SharedPrefere
log.d("proc_flushSpeechQueue: tts_speak wait expired.")
restartTTS()
} else {
log.d("proc_flushSpeechQueue: tts is speaking. queue_count=%d, expire_remain=%.3f", queue_count, expire_remain / 1000f
log.d(
"proc_flushSpeechQueue: tts is speaking. queue_count=%d, expire_remain=%.3f",
queue_count,
expire_remain / 1000f
)
handler.postDelayed(this, expire_remain)
return
@ -313,7 +314,7 @@ class AppState(internal val context : Context, internal val pref : SharedPrefere
if(willSpeechEnabled && tts == null && tts_status == TTS_STATUS_NONE) {
tts_status = TTS_STATUS_INITIALIZING
Utils.showToast(context, false, R.string.text_to_speech_initializing)
showToast(context, false, R.string.text_to_speech_initializing)
log.d("initializing TextToSpeech…")
object : AsyncTask<Void, Void, TextToSpeech?>() {
@ -325,79 +326,92 @@ class AppState(internal val context : Context, internal val pref : SharedPrefere
return tts
}
internal val tts_init_listener : TextToSpeech.OnInitListener = TextToSpeech.OnInitListener { status ->
val tts = this.tmp_tts
if(tts == null || TextToSpeech.SUCCESS != status) {
Utils.showToast(context, false, R.string.text_to_speech_initialize_failed, status)
log.d("speech initialize failed. status=%s", status)
return@OnInitListener
}
Utils.runOnMainThread {
if(! willSpeechEnabled) {
Utils.showToast(context, false, R.string.text_to_speech_shutdown)
log.d("shutdown TextToSpeech…")
tts.shutdown()
} else {
this@AppState.tts = tts
tts_status = TTS_STATUS_INITIALIZED
tts_speak_start = 0L
tts_speak_end = 0L
voice_list.clear()
try {
val voice_set = try{
tts.voices
// may raise NullPointerException is tts has no collection
}catch(ignored:Throwable){
null
}
if(voice_set == null || voice_set.isEmpty()) {
log.d("TextToSpeech.getVoices returns null or empty set.")
} else {
val lang = Locale.getDefault().toLanguageTag()
for(v in voice_set) {
log.d("Voice %s %s %s", v.name, v.locale.toLanguageTag(), lang
)
if(lang != v.locale.toLanguageTag()) {
continue
}
voice_list.add(v)
internal val tts_init_listener : TextToSpeech.OnInitListener =
TextToSpeech.OnInitListener { status ->
val tts = this.tmp_tts
if(tts == null || TextToSpeech.SUCCESS != status) {
showToast(
context,
false,
R.string.text_to_speech_initialize_failed,
status
)
log.d("speech initialize failed. status=%s", status)
return@OnInitListener
}
runOnMainLooper {
if(! willSpeechEnabled) {
showToast(context, false, R.string.text_to_speech_shutdown)
log.d("shutdown TextToSpeech…")
tts.shutdown()
} else {
this@AppState.tts = tts
tts_status = TTS_STATUS_INITIALIZED
tts_speak_start = 0L
tts_speak_end = 0L
voice_list.clear()
try {
val voice_set = try {
tts.voices
// may raise NullPointerException is tts has no collection
} catch(ignored : Throwable) {
null
}
if(voice_set == null || voice_set.isEmpty()) {
log.d("TextToSpeech.getVoices returns null or empty set.")
} else {
val lang = Locale.getDefault().toLanguageTag()
for(v in voice_set) {
log.d(
"Voice %s %s %s",
v.name,
v.locale.toLanguageTag(),
lang
)
if(lang != v.locale.toLanguageTag()) {
continue
}
voice_list.add(v)
}
}
} catch(ex : Throwable) {
log.trace(ex)
log.e(ex, "TextToSpeech.getVoices raises exception.")
}
} catch(ex : Throwable) {
log.trace(ex)
log.e(ex, "TextToSpeech.getVoices raises exception.")
handler.post(proc_flushSpeechQueue)
context.registerReceiver(
tts_receiver,
IntentFilter(TextToSpeech.ACTION_TTS_QUEUE_PROCESSING_COMPLETED)
)
// tts.setOnUtteranceProgressListener( new UtteranceProgressListener() {
// @Override public void onStart( String utteranceId ){
// log.d( "UtteranceProgressListener.onStart id=%s", utteranceId );
// }
//
// @Override public void onDone( String utteranceId ){
// log.d( "UtteranceProgressListener.onDone id=%s", utteranceId );
// handler.post( proc_flushSpeechQueue );
// }
//
// @Override public void onError( String utteranceId ){
// log.d( "UtteranceProgressListener.onError id=%s", utteranceId );
// handler.post( proc_flushSpeechQueue );
// }
// } );
}
handler.post(proc_flushSpeechQueue)
context.registerReceiver(tts_receiver, IntentFilter(TextToSpeech.ACTION_TTS_QUEUE_PROCESSING_COMPLETED))
// tts.setOnUtteranceProgressListener( new UtteranceProgressListener() {
// @Override public void onStart( String utteranceId ){
// log.d( "UtteranceProgressListener.onStart id=%s", utteranceId );
// }
//
// @Override public void onDone( String utteranceId ){
// log.d( "UtteranceProgressListener.onDone id=%s", utteranceId );
// handler.post( proc_flushSpeechQueue );
// }
//
// @Override public void onError( String utteranceId ){
// log.d( "UtteranceProgressListener.onError id=%s", utteranceId );
// handler.post( proc_flushSpeechQueue );
// }
// } );
}
}
}
}.executeOnExecutor(App1.task_executor)
}
if(! willSpeechEnabled && tts != null) {
Utils.showToast(context, false, R.string.text_to_speech_shutdown)
showToast(context, false, R.string.text_to_speech_shutdown)
log.d("shutdown TextToSpeech…")
tts?.shutdown()
tts = null

View File

@ -2,7 +2,6 @@ package jp.juggler.subwaytooter
import android.annotation.SuppressLint
import android.content.Context
import android.net.Uri
import android.os.AsyncTask
import android.os.SystemClock
import jp.juggler.subwaytooter.api.*
@ -26,13 +25,8 @@ import jp.juggler.subwaytooter.table.MutedWord
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.table.TagSet
import jp.juggler.subwaytooter.table.UserRelation
import jp.juggler.subwaytooter.util.BucketList
import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.VersionString
import jp.juggler.subwaytooter.util.WordTrieTree
import jp.juggler.subwaytooter.util.ScrollPosition
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.*
class Column(
val app_state : AppState,
@ -86,8 +80,8 @@ class Column(
private const val PATH_ACCOUNT = "/api/v1/accounts/%d" // 1:account_id
private const val PATH_STATUSES = "/api/v1/statuses/%d" // 1:status_id
private const val PATH_STATUSES_CONTEXT = "/api/v1/statuses/%d/context" // 1:status_id
const val PATH_SEARCH =
"/api/v1/search?q=%s" // 1: query(urlencoded) , also, append "&resolve=1" if resolve non-local accounts
const val PATH_SEARCH = "/api/v1/search?q=%s"
// search args 1: query(urlencoded) , also, append "&resolve=1" if resolve non-local accounts
private const val PATH_INSTANCE = "/api/v1/instance"
private const val PATH_LIST_INFO = "/api/v1/lists/%s"
@ -161,7 +155,7 @@ class Column(
}
fun loadAccount(context : Context, src : JSONObject) : SavedAccount {
val account_db_id = Utils.optLongX(src, KEY_ACCOUNT_ROW_ID)
val account_db_id = src.parseLong(KEY_ACCOUNT_ROW_ID) ?: - 1L
return if(account_db_id >= 0) {
SavedAccount.loadAccount(context, account_db_id)
?: throw RuntimeException("missing account")
@ -250,7 +244,7 @@ class Column(
TYPE_HOME, TYPE_NOTIFICATIONS -> "/api/v1/streaming/?stream=user"
TYPE_LOCAL -> "/api/v1/streaming/?stream=public:local"
TYPE_FEDERATE -> "/api/v1/streaming/?stream=public"
TYPE_HASHTAG -> "/api/v1/streaming/?stream=hashtag&tag=" + Uri.encode(hashtag) // タグ先頭の#を含まない
TYPE_HASHTAG -> "/api/v1/streaming/?stream=hashtag&tag=" + hashtag.encodePercent() // タグ先頭の#を含まない
TYPE_LIST_TL -> "/api/v1/streaming/?stream=list&list=" + profile_id.toString()
else -> null
}
@ -451,27 +445,27 @@ class Column(
hide_media_default = src.optBoolean(KEY_HIDE_MEDIA_DEFAULT)
enable_speech = src.optBoolean(KEY_ENABLE_SPEECH)
regex_text = Utils.optStringX(src, KEY_REGEX_TEXT) ?: ""
regex_text = src.parseString(KEY_REGEX_TEXT) ?: ""
header_bg_color = src.optInt(KEY_HEADER_BACKGROUND_COLOR)
header_fg_color = src.optInt(KEY_HEADER_TEXT_COLOR)
column_bg_color = src.optInt(KEY_COLUMN_BACKGROUND_COLOR)
acct_color = src.optInt(KEY_COLUMN_ACCT_TEXT_COLOR)
content_color = src.optInt(KEY_COLUMN_CONTENT_TEXT_COLOR)
column_bg_image = Utils.optStringX(src, KEY_COLUMN_BACKGROUND_IMAGE) ?: ""
column_bg_image = src.parseString(KEY_COLUMN_BACKGROUND_IMAGE) ?: ""
column_bg_image_alpha = src.optDouble(KEY_COLUMN_BACKGROUND_IMAGE_ALPHA, 1.0).toFloat()
when(column_type) {
TYPE_CONVERSATION, TYPE_BOOSTED_BY, TYPE_FAVOURITED_BY -> status_id =
Utils.optLongX(src, KEY_STATUS_ID)
src.parseLong(KEY_STATUS_ID) ?: - 1L
TYPE_PROFILE -> {
profile_id = Utils.optLongX(src, KEY_PROFILE_ID)
profile_id = src.parseLong(KEY_PROFILE_ID) ?: - 1L
profile_tab = src.optInt(KEY_PROFILE_TAB)
}
TYPE_LIST_MEMBER, TYPE_LIST_TL -> profile_id = Utils.optLongX(src, KEY_PROFILE_ID)
TYPE_LIST_MEMBER, TYPE_LIST_TL -> profile_id = src.parseLong(KEY_PROFILE_ID) ?: - 1L
TYPE_HASHTAG -> hashtag = src.optString(KEY_HASHTAG)
@ -939,35 +933,35 @@ class Column(
changeList : List<AdapterChange>? = null,
reset : Boolean = false
) {
if(! Utils.isMainThread) {
if(! isMainThread) {
throw RuntimeException("fireShowContent: not on main thread.")
}
viewHolder?.showContent(reason, changeList, reset)
}
internal fun fireShowColumnHeader() {
if(! Utils.isMainThread) {
if(! isMainThread) {
throw RuntimeException("fireShowColumnHeader: not on main thread.")
}
viewHolder?.showColumnHeader()
}
internal fun fireColumnColor() {
if(! Utils.isMainThread) {
if(! isMainThread) {
throw RuntimeException("fireColumnColor: not on main thread.")
}
viewHolder?.showColumnColor()
}
fun fireRelativeTime() {
if(! Utils.isMainThread) {
if(! isMainThread) {
throw RuntimeException("fireRelativeTime: not on main thread.")
}
viewHolder?.updateRelativeTime()
}
fun fireRebindAdapterItems() {
if(! Utils.isMainThread) {
if(! isMainThread) {
throw RuntimeException("fireRelativeTime: not on main thread.")
}
viewHolder?.rebindAdapterItems()
@ -1504,8 +1498,8 @@ class Column(
get() = isCancelled || is_dispose.get()
override fun publishApiProgress(s : String) {
Utils.runOnMainThread {
if(isCancelled) return@runOnMainThread
runOnMainLooper {
if(isCancelled) return@runOnMainLooper
task_progress = s
fireShowContent(reason = "loading progress", changeList = ArrayList())
}
@ -1606,7 +1600,7 @@ class Column(
TYPE_HASHTAG -> return getStatuses(
client,
String.format(Locale.JAPAN, PATH_HASHTAG, Uri.encode(hashtag))
String.format(Locale.JAPAN, PATH_HASHTAG, hashtag.encodePercent())
)
TYPE_REPORTS -> return parseReports(client, PATH_REPORTS)
@ -1665,7 +1659,7 @@ class Column(
)
//
} else {
Utils.showToast(context, true, "TootContext parse failed.")
showToast(context, true, "TootContext parse failed.")
this.list_tmp = addOne(this.list_tmp, target_status)
}
@ -1688,7 +1682,11 @@ class Column(
return TootApiResult(context.getString(R.string.search_is_not_available_on_pseudo_account))
}
var path =
String.format(Locale.JAPAN, PATH_SEARCH, Uri.encode(search_query))
String.format(
Locale.JAPAN,
PATH_SEARCH,
search_query.encodePercent()
)
if(search_resolve) path += "&resolve=1"
result = client.request(path)
@ -1883,14 +1881,14 @@ class Column(
if(last_task != null) {
if(! bSilent) {
Utils.showToast(context, true, R.string.column_is_busy)
showToast(context, true, R.string.column_is_busy)
val holder = viewHolder
if(holder != null) holder.refreshLayout.isRefreshing = false
}
return
} else if(bBottom && max_id.isEmpty()) {
if(! bSilent) {
Utils.showToast(context, true, R.string.end_of_list)
showToast(context, true, R.string.end_of_list)
val holder = viewHolder
if(holder != null) holder.refreshLayout.isRefreshing = false
}
@ -2422,8 +2420,8 @@ class Column(
get() = isCancelled || is_dispose.get()
override fun publishApiProgress(s : String) {
Utils.runOnMainThread {
if(isCancelled) return@runOnMainThread
runOnMainLooper {
if(isCancelled) return@runOnMainLooper
task_progress = s
fireShowContent(reason = "refresh progress", changeList = ArrayList())
}
@ -2517,7 +2515,7 @@ class Column(
TYPE_HASHTAG -> getStatusList(
client,
String.format(Locale.JAPAN, PATH_HASHTAG, Uri.encode(hashtag))
String.format(Locale.JAPAN, PATH_HASHTAG, hashtag.encodePercent())
)
TYPE_SEARCH_MSP ->
@ -2705,11 +2703,11 @@ class Column(
internal fun startGap(gap : TootGap?) {
if(gap == null) {
Utils.showToast(context, true, "gap is null")
showToast(context, true, "gap is null")
return
}
if(last_task != null) {
Utils.showToast(context, true, R.string.column_is_busy)
showToast(context, true, R.string.column_is_busy)
return
}
@ -2945,8 +2943,8 @@ class Column(
get() = isCancelled || is_dispose.get()
override fun publishApiProgress(s : String) {
Utils.runOnMainThread {
if(isCancelled) return@runOnMainThread
runOnMainLooper {
if(isCancelled) return@runOnMainLooper
task_progress = s
fireShowContent(reason = "gap progress", changeList = ArrayList())
}
@ -2974,7 +2972,7 @@ class Column(
TYPE_HASHTAG -> getStatusList(
client,
String.format(Locale.JAPAN, PATH_HASHTAG, Uri.encode(hashtag))
String.format(Locale.JAPAN, PATH_HASHTAG, hashtag.encodePercent())
)
TYPE_BOOSTED_BY -> getAccountList(
@ -3138,8 +3136,7 @@ class Column(
private fun loadSearchDesc(raw_en : Int, raw_ja : Int) : String {
val res_id = if("ja" == context.getString(R.string.language_code)) raw_ja else raw_en
val data = Utils.loadRawResource(context, res_id)
return if(data == null) "?" else Utils.decodeUTF8(data)
return context.loadRawResource(res_id)?.decodeUTF8() ?: "?"
}
private var cacheHeaderDesc : String? = null

View File

@ -25,19 +25,17 @@ import java.util.regex.Pattern
import jp.juggler.subwaytooter.action.Action_List
import jp.juggler.subwaytooter.action.Action_Notification
import jp.juggler.subwaytooter.table.AcctColor
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.ScrollPosition
import jp.juggler.subwaytooter.util.Utils
import android.support.v7.widget.ListRecyclerView
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import jp.juggler.subwaytooter.util.*
import jp.juggler.subwaytooter.view.ListDivider
import java.io.Closeable
import java.lang.reflect.Field
class ColumnViewHolder(
val activity : ActMain,
root : View
viewRoot : View
) : View.OnClickListener,
SwipyRefreshLayout.OnRefreshListener,
CompoundButton.OnCheckedChangeListener {
@ -140,7 +138,7 @@ class ColumnViewHolder(
tvRegexFilterError.text = if(message != null && message.isNotEmpty()) {
message
} else {
Utils.formatError(ex, activity.resources, R.string.regex_error)
ex.withCaption(activity.resources, R.string.regex_error)
}
return false
}
@ -162,7 +160,7 @@ class ColumnViewHolder(
init {
if(activity.timeline_font != null) {
Utils.scanView(root) { v ->
viewRoot.scan { v ->
try {
if(v is Button) {
// ボタンは触らない
@ -175,42 +173,42 @@ class ColumnViewHolder(
}
}
flColumnBackground = root.findViewById(R.id.flColumnBackground)
ivColumnBackgroundImage = root.findViewById(R.id.ivColumnBackgroundImage)
llColumnHeader = root.findViewById(R.id.llColumnHeader)
flColumnBackground = viewRoot.findViewById(R.id.flColumnBackground)
ivColumnBackgroundImage = viewRoot.findViewById(R.id.ivColumnBackgroundImage)
llColumnHeader = viewRoot.findViewById(R.id.llColumnHeader)
tvColumnIndex = root.findViewById(R.id.tvColumnIndex)
tvColumnIndex = viewRoot.findViewById(R.id.tvColumnIndex)
tvColumnName = root.findViewById(R.id.tvColumnName)
tvColumnContext = root.findViewById(R.id.tvColumnContext)
ivColumnIcon = root.findViewById(R.id.ivColumnIcon)
tvColumnName = viewRoot.findViewById(R.id.tvColumnName)
tvColumnContext = viewRoot.findViewById(R.id.tvColumnContext)
ivColumnIcon = viewRoot.findViewById(R.id.ivColumnIcon)
btnColumnSetting = root.findViewById(R.id.btnColumnSetting)
btnColumnReload = root.findViewById(R.id.btnColumnReload)
btnColumnClose = root.findViewById(R.id.btnColumnClose)
btnColumnSetting = viewRoot.findViewById(R.id.btnColumnSetting)
btnColumnReload = viewRoot.findViewById(R.id.btnColumnReload)
btnColumnClose = viewRoot.findViewById(R.id.btnColumnClose)
tvLoading = root.findViewById(R.id.tvLoading)
listView = root.findViewById(R.id.listView)
tvLoading = viewRoot.findViewById(R.id.tvLoading)
listView = viewRoot.findViewById(R.id.listView)
if(Pref.bpShareViewPool(activity.pref)) {
listView.recycledViewPool = activity.viewPool
}
listView.itemAnimator = null
//
// val animator = listView.itemAnimator
// if( animator is DefaultItemAnimator){
// animator.supportsChangeAnimations = false
// }
//
// val animator = listView.itemAnimator
// if( animator is DefaultItemAnimator){
// animator.supportsChangeAnimations = false
// }
btnSearch = root.findViewById(R.id.btnSearch)
etSearch = root.findViewById(R.id.etSearch)
cbResolve = root.findViewById(R.id.cbResolve)
btnSearch = viewRoot.findViewById(R.id.btnSearch)
etSearch = viewRoot.findViewById(R.id.etSearch)
cbResolve = viewRoot.findViewById(R.id.cbResolve)
llSearch = root.findViewById(R.id.llSearch)
llListList = root.findViewById(R.id.llListList)
llSearch = viewRoot.findViewById(R.id.llSearch)
llListList = viewRoot.findViewById(R.id.llListList)
btnListAdd = root.findViewById(R.id.btnListAdd)
etListName = root.findViewById(R.id.etListName)
btnListAdd = viewRoot.findViewById(R.id.btnListAdd)
etListName = viewRoot.findViewById(R.id.etListName)
btnListAdd.setOnClickListener(this)
etListName.setOnEditorActionListener { _, actionId, _ ->
@ -222,24 +220,24 @@ class ColumnViewHolder(
handled
}
llColumnSetting = root.findViewById(R.id.llColumnSetting)
llColumnSetting = viewRoot.findViewById(R.id.llColumnSetting)
cbDontCloseColumn = root.findViewById(R.id.cbDontCloseColumn)
cbWithAttachment = root.findViewById(R.id.cbWithAttachment)
cbWithHighlight = root.findViewById(R.id.cbWithHighlight)
cbDontShowBoost = root.findViewById(R.id.cbDontShowBoost)
cbDontShowFollow = root.findViewById(R.id.cbDontShowFollow)
cbDontShowFavourite = root.findViewById(R.id.cbDontShowFavourite)
cbDontShowReply = root.findViewById(R.id.cbDontShowReply)
cbDontStreaming = root.findViewById(R.id.cbDontStreaming)
cbDontAutoRefresh = root.findViewById(R.id.cbDontAutoRefresh)
cbHideMediaDefault = root.findViewById(R.id.cbHideMediaDefault)
cbEnableSpeech = root.findViewById(R.id.cbEnableSpeech)
etRegexFilter = root.findViewById(R.id.etRegexFilter)
llRegexFilter = root.findViewById(R.id.llRegexFilter)
tvRegexFilterError = root.findViewById(R.id.tvRegexFilterError)
cbDontCloseColumn = viewRoot.findViewById(R.id.cbDontCloseColumn)
cbWithAttachment = viewRoot.findViewById(R.id.cbWithAttachment)
cbWithHighlight = viewRoot.findViewById(R.id.cbWithHighlight)
cbDontShowBoost = viewRoot.findViewById(R.id.cbDontShowBoost)
cbDontShowFollow = viewRoot.findViewById(R.id.cbDontShowFollow)
cbDontShowFavourite = viewRoot.findViewById(R.id.cbDontShowFavourite)
cbDontShowReply = viewRoot.findViewById(R.id.cbDontShowReply)
cbDontStreaming = viewRoot.findViewById(R.id.cbDontStreaming)
cbDontAutoRefresh = viewRoot.findViewById(R.id.cbDontAutoRefresh)
cbHideMediaDefault = viewRoot.findViewById(R.id.cbHideMediaDefault)
cbEnableSpeech = viewRoot.findViewById(R.id.cbEnableSpeech)
etRegexFilter = viewRoot.findViewById(R.id.etRegexFilter)
llRegexFilter = viewRoot.findViewById(R.id.llRegexFilter)
tvRegexFilterError = viewRoot.findViewById(R.id.tvRegexFilterError)
btnDeleteNotification = root.findViewById(R.id.btnDeleteNotification)
btnDeleteNotification = viewRoot.findViewById(R.id.btnDeleteNotification)
llColumnHeader.setOnClickListener(this)
btnColumnSetting.setOnClickListener(this)
@ -247,9 +245,9 @@ class ColumnViewHolder(
btnColumnClose.setOnClickListener(this)
btnDeleteNotification.setOnClickListener(this)
root.findViewById<View>(R.id.btnColor).setOnClickListener(this)
viewRoot.findViewById<View>(R.id.btnColor).setOnClickListener(this)
this.refreshLayout = root.findViewById(R.id.swipyRefreshLayout)
this.refreshLayout = viewRoot.findViewById(R.id.swipyRefreshLayout)
refreshLayout.setOnRefreshListener(this)
refreshLayout.setDistanceToTriggerSync((0.5f + 20f * activity.density).toInt())
@ -489,7 +487,7 @@ class ColumnViewHolder(
showColumnColor()
showContent(reason="onPageCreate",reset=true)
showContent(reason = "onPageCreate", reset = true)
} finally {
loading_busy = false
}
@ -610,16 +608,16 @@ class ColumnViewHolder(
// 非同期処理を開始
val task = object : AsyncTask<Void, Void, Bitmap?>() {
override fun doInBackground(vararg params : Void) : Bitmap? {
try {
val resize_max = if(screen_w > screen_h) screen_w else screen_h
val uri = Uri.parse(url)
return Utils.createResizedBitmap(log, activity, uri, false, resize_max)
return try {
createResizedBitmap(
activity,
Uri.parse(url),
if(screen_w > screen_h) screen_w else screen_h
)
} catch(ex : Throwable) {
log.trace(ex)
null
}
return null
}
override fun onCancelled(bitmap : Bitmap?) {
@ -744,7 +742,7 @@ class ColumnViewHolder(
R.id.cbHideMediaDefault -> {
column.hide_media_default = isChecked
activity.app_state.saveColumnList()
column.fireShowContent(reason="HideMediaDefault in ColumnSetting",reset=true)
column.fireShowContent(reason = "HideMediaDefault in ColumnSetting", reset = true)
}
R.id.cbEnableSpeech -> {
@ -770,7 +768,7 @@ class ColumnViewHolder(
App1.custom_emoji_cache.clearErrorCache()
if(column.isSearchColumn) {
Utils.hideKeyboard(activity, etSearch)
etSearch.hideKeyboard()
etSearch.setText(column.search_query)
cbResolve.isChecked = column.search_resolve
}
@ -779,7 +777,7 @@ class ColumnViewHolder(
}
R.id.btnSearch -> {
Utils.hideKeyboard(activity, etSearch)
etSearch.hideKeyboard()
column.search_query = etSearch.text.toString().trim { it <= ' ' }
column.search_resolve = cbResolve.isChecked
activity.app_state.saveColumnList()
@ -809,7 +807,7 @@ class ColumnViewHolder(
R.id.btnListAdd -> {
val tv = etListName.text.toString().trim { it <= ' ' }
if(tv.isEmpty()) {
Utils.showToast(activity, true, R.string.list_name_empty)
showToast(activity, true, R.string.list_name_empty)
return
}
Action_List.create(activity, column.access_info, tv, null)
@ -828,7 +826,7 @@ class ColumnViewHolder(
}
private fun showColumnCloseButton() {
val dont_close = column ?.dont_close ?: return
val dont_close = column?.dont_close ?: return
btnColumnClose.isEnabled = ! dont_close
btnColumnClose.alpha = if(dont_close) 0.3f else 1f
}
@ -837,10 +835,10 @@ class ColumnViewHolder(
fun updateRelativeTime() = rebindAdapterItems()
fun rebindAdapterItems() {
for( childIndex in 0 until listView.childCount){
for(childIndex in 0 until listView.childCount) {
val adapterIndex = listView.getChildAdapterPosition(listView.getChildAt(childIndex))
if( adapterIndex == RecyclerView.NO_POSITION) continue
status_adapter?.notifyItemChanged( adapterIndex )
if(adapterIndex == RecyclerView.NO_POSITION) continue
status_adapter?.notifyItemChanged(adapterIndex)
}
}
@ -879,14 +877,14 @@ class ColumnViewHolder(
}
internal fun showContent(
reason:String,
changeList : List<AdapterChange>? = null ,
reason : String,
changeList : List<AdapterChange>? = null,
reset : Boolean = false
) {
) {
// クラッシュレポートにadapterとリストデータの状態不整合が多かったので、
// とりあえずリストデータ変更の通知だけは最優先で行っておく
try {
status_adapter?.notifyChange(reason,changeList,reset)
status_adapter?.notifyChange(reason, changeList, reset)
} catch(ex : Throwable) {
log.trace(ex)
}
@ -934,7 +932,7 @@ class ColumnViewHolder(
refreshLayout.isRefreshing = false
val refreshError = column.mRefreshLoadingError
if(refreshError.isNotEmpty()) {
Utils.showToast(activity, true, refreshError)
showToast(activity, true, refreshError)
column.mRefreshLoadingError = ""
}
}
@ -949,7 +947,7 @@ class ColumnViewHolder(
val column = this.column
when {
column == null -> log.d("saveScrollPosition [%d] , column==null", page_idx)
column.is_dispose.get() -> log.d(
"saveScrollPosition [%d] , column is disposed",
page_idx
@ -981,7 +979,7 @@ class ColumnViewHolder(
}
}
fun setScrollPosition(sp : ScrollPosition, deltaDp : Float =0f) {
fun setScrollPosition(sp : ScrollPosition, deltaDp : Float = 0f) {
val last_adapter = listView.adapter
if(column == null || last_adapter == null) return
@ -1002,12 +1000,14 @@ class ColumnViewHolder(
}, 20L)
}
inner class AdapterItemHeightWorkarea internal constructor(val adapter:ItemListAdapter) : Closeable {
inner class AdapterItemHeightWorkarea internal constructor(val adapter : ItemListAdapter) :
Closeable {
private val item_width : Int
private val widthSpec : Int
private var lastViewType : Int = - 1
private var lastViewHolder : RecyclerView.ViewHolder? = null
init {
this.item_width = listView.width - listView.paddingLeft - listView.paddingRight
this.widthSpec = View.MeasureSpec.makeMeasureSpec(item_width, View.MeasureSpec.EXACTLY)
@ -1029,7 +1029,7 @@ class ColumnViewHolder(
return childViewHolder.itemView.measuredHeight
}
log.d("getAdapterItemHeight idx=$adapterIndex createView")
val viewType = adapter.getItemViewType(adapterIndex)
@ -1054,7 +1054,7 @@ class ColumnViewHolder(
var adapterIndex = column?.toAdapterIndex(listIndex) ?: return
val adapter = status_adapter
if( adapter == null) {
if(adapter == null) {
log.e("setListItemTop: missing status adapter")
return
}
@ -1073,7 +1073,7 @@ class ColumnViewHolder(
}
fun getListItemTop(listIndex : Int) : Int {
val adapterIndex = column?.toAdapterIndex(listIndex)
?: return 0
@ -1084,12 +1084,12 @@ class ColumnViewHolder(
}
fun findFirstVisibleListItem() : Int {
val adapterIndex = listLayoutManager.findFirstVisibleItemPosition()
if(adapterIndex == RecyclerView.NO_POSITION)
throw IndexOutOfBoundsException()
return column?.toListIndex(adapterIndex)
?: throw IndexOutOfBoundsException()

View File

@ -28,7 +28,7 @@ import jp.juggler.subwaytooter.dialog.DlgQRCode
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.table.UserRelation
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.showToast
@SuppressLint("InflateParams")
internal class DlgContextMenu(
@ -484,14 +484,14 @@ internal class DlgContextMenu(
R.id.btnDomainBlock -> who?.let { who ->
// 疑似アカウントではドメインブロックできない
if(access_info.isPseudo) {
Utils.showToast(activity, false, R.string.domain_block_from_pseudo)
showToast(activity, false, R.string.domain_block_from_pseudo)
return@let
}
val who_host = who.host
// 自分のドメインではブロックできない
if( access_info.host.equals(who_host,ignoreCase = true)) {
Utils.showToast(activity, false, R.string.domain_block_from_local)
showToast(activity, false, R.string.domain_block_from_local)
return@let
}
AlertDialog.Builder(activity)

View File

@ -5,7 +5,7 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.showToast
class DownloadReceiver : BroadcastReceiver() {
override fun onReceive(context : Context, intent : Intent?) {
@ -42,9 +42,9 @@ class DownloadReceiver : BroadcastReceiver() {
重複を回避する方法はなさそうだ
*/
Utils.showToast(context, false, context.getString(R.string.download_complete, title))
showToast(context, false, context.getString(R.string.download_complete, title))
} else {
Utils.showToast(context, false, context.getString(R.string.download_failed, title))
showToast(context, false, context.getString(R.string.download_failed, title))
}
}
}

View File

@ -12,23 +12,24 @@ class EventReceiver : BroadcastReceiver() {
internal val log = LogCategory("EventReceiver")
const val ACTION_NOTIFICATION_DELETE = "notification_delete"
}
override fun onReceive(context : Context, intent : Intent?) {
if(intent != null) {
val action = intent.action
if(Intent.ACTION_BOOT_COMPLETED == action) {
PollingWorker.queueBootCompleted(context)
when {
Intent.ACTION_BOOT_COMPLETED == action -> PollingWorker.queueBootCompleted(context)
Intent.ACTION_MY_PACKAGE_REPLACED == action -> PollingWorker.queuePackageReplaced(
context
)
} else if(Intent.ACTION_MY_PACKAGE_REPLACED == action) {
PollingWorker.queuePackageReplaced(context)
ACTION_NOTIFICATION_DELETE == action -> {
val db_id = intent.getLongExtra(PollingWorker.EXTRA_DB_ID, - 1L)
PollingWorker.queueNotificationDeleted(context, db_id)
}
} else if(ACTION_NOTIFICATION_DELETE == action) {
val db_id = intent.getLongExtra(PollingWorker.EXTRA_DB_ID, - 1L)
PollingWorker.queueNotificationDeleted(context, db_id)
} else {
log.e("onReceive: unsupported action %s", action)
else -> log.e("onReceive: unsupported action %s", action)
}
}
}

View File

@ -2,7 +2,6 @@ package jp.juggler.subwaytooter
import android.content.Context
import android.graphics.Typeface
import android.net.Uri
import android.os.SystemClock
import android.support.v4.content.ContextCompat
import android.support.v4.view.ViewCompat
@ -55,9 +54,9 @@ internal class ItemViewHolder(
val viewRoot : View
private var bSimpleList : Boolean = false
lateinit var column : Column
private lateinit var list_adapter : ItemListAdapter
private lateinit var llBoosted : View
private lateinit var ivBoosted : ImageView
@ -87,7 +86,7 @@ internal class ItemViewHolder(
private lateinit var tvContent : MyTextView
private lateinit var flMedia : View
private lateinit var llMedia:View
private lateinit var llMedia : View
private lateinit var btnShowMedia : TextView
private lateinit var ivMedia1 : MyNetworkImageView
private lateinit var ivMedia2 : MyNetworkImageView
@ -127,7 +126,7 @@ internal class ItemViewHolder(
private var boost_account : TootAccount? = null
private var follow_account : TootAccount? = null
private var boost_time :Long = 0L
private var boost_time : Long = 0L
private val content_color_default : Int
private var acct_color : Int = 0
@ -215,7 +214,12 @@ internal class ItemViewHolder(
}
fun bind(list_adapter : ItemListAdapter, column : Column, bSimpleList : Boolean, item : TimelineItem) {
fun bind(
list_adapter : ItemListAdapter,
column : Column,
bSimpleList : Boolean,
item : TimelineItem
) {
this.list_adapter = list_adapter
this.column = column
this.bSimpleList = bSimpleList
@ -223,7 +227,7 @@ internal class ItemViewHolder(
this.access_info = column.access_info
if(activity.timeline_font != null || activity.timeline_font_bold != null) {
Utils.scanView(this.viewRoot) { v ->
viewRoot.scan { v ->
try {
if(v is Button) {
// ボタンは太字なので触らない
@ -307,8 +311,7 @@ internal class ItemViewHolder(
llSearchTag.visibility = View.GONE
llList.visibility = View.GONE
llExtra.removeAllViews()
var c : Int
c = if(column.content_color != 0) column.content_color else content_color_default
tvBoosted.setTextColor(c)
@ -330,7 +333,6 @@ internal class ItemViewHolder(
// tvBoostedAcct.setTextColor( c );
// tvFollowerAcct.setTextColor( c );
// tvAcct.setTextColor( c );
this.item = item
when(item) {
@ -345,16 +347,12 @@ internal class ItemViewHolder(
val reblog = item.reblog
if(reblog != null) {
showBoost(
item.account
,
item.time_created_at
,
R.attr.btn_boost
,
Utils.formatSpannable1(
item.account,
item.time_created_at,
R.attr.btn_boost,
item.account.decoded_display_name.intoStringResource(
activity,
R.string.display_name_boosted_by,
item.account.decoded_display_name
R.string.display_name_boosted_by
)
)
showStatus(activity, reblog)
@ -374,16 +372,12 @@ internal class ItemViewHolder(
when(n.type) {
TootNotification.TYPE_FAVOURITE -> {
if(n_account != null) showBoost(
n_account
,
n.time_created_at
,
if(access_info.isNicoru(n_account)) R.attr.ic_nicoru else R.attr.btn_favourite
,
Utils.formatSpannable1(
n_account,
n.time_created_at,
if(access_info.isNicoru(n_account)) R.attr.ic_nicoru else R.attr.btn_favourite,
n_account.decoded_display_name.intoStringResource(
activity,
R.string.display_name_favourited_by,
n_account.decoded_display_name
R.string.display_name_favourited_by
)
)
if(n_status != null) showStatus(activity, n_status)
@ -391,16 +385,12 @@ internal class ItemViewHolder(
TootNotification.TYPE_REBLOG -> {
if(n_account != null) showBoost(
n_account
,
n.time_created_at
,
R.attr.btn_boost
,
Utils.formatSpannable1(
n_account,
n.time_created_at,
R.attr.btn_boost,
n_account.decoded_display_name.intoStringResource(
activity,
R.string.display_name_boosted_by,
n_account.decoded_display_name
R.string.display_name_boosted_by
)
)
if(n_status != null) showStatus(activity, n_status)
@ -410,16 +400,12 @@ internal class ItemViewHolder(
TootNotification.TYPE_FOLLOW -> {
if(n_account != null) {
showBoost(
n_account
,
n.time_created_at
,
R.attr.ic_follow_plus
,
Utils.formatSpannable1(
n_account,
n.time_created_at,
R.attr.ic_follow_plus,
n_account.decoded_display_name.intoStringResource(
activity,
R.string.display_name_followed_by,
n_account.decoded_display_name
R.string.display_name_followed_by
)
)
showAccount(n_account)
@ -429,16 +415,12 @@ internal class ItemViewHolder(
TootNotification.TYPE_MENTION -> {
if(! bSimpleList) {
if(n_account != null) showBoost(
n_account
,
n.time_created_at
,
R.attr.btn_reply
,
Utils.formatSpannable1(
n_account,
n.time_created_at,
R.attr.btn_reply,
n_account.decoded_display_name.intoStringResource(
activity,
R.string.display_name_replied_by,
n_account.decoded_display_name
R.string.display_name_replied_by
)
)
}
@ -485,7 +467,11 @@ internal class ItemViewHolder(
private fun showAccount(who : TootAccount) {
follow_account = who
llFollow.visibility = View.VISIBLE
ivFollow.setImageUrl(activity.pref, Styler.calcIconRound(ivFollow.layoutParams), access_info.supplyBaseUrl(who.avatar_static))
ivFollow.setImageUrl(
activity.pref,
Styler.calcIconRound(ivFollow.layoutParams),
access_info.supplyBaseUrl(who.avatar_static)
)
tvFollowerName.text = who.decoded_display_name
follow_invalidator.register(who.decoded_display_name)
@ -615,7 +601,7 @@ internal class ItemViewHolder(
else -> ! status.sensitive
}
val is_shown = MediaShown.isShown(status, default_shown)
btnShowMedia.visibility = if(! is_shown) View.VISIBLE else View.GONE
llMedia.visibility = if(! is_shown) View.GONE else View.VISIBLE
setMedia(ivMedia1, status, media_attachments, 0)
@ -696,17 +682,16 @@ internal class ItemViewHolder(
tvTime.text = sb
}
fun updateRelativeTime() {
val boost_time = this.boost_time
if( boost_time != 0L ){
tvBoostedTime.text = TootStatus.formatTime(tvBoostedTime.context, boost_time, true)
}
val status_showing = this.status_showing
if( status_showing != null ){
showStatusTime(activity, status_showing)
}
}
// fun updateRelativeTime() {
// val boost_time = this.boost_time
// if(boost_time != 0L) {
// tvBoostedTime.text = TootStatus.formatTime(tvBoostedTime.context, boost_time, true)
// }
// val status_showing = this.status_showing
// if(status_showing != null) {
// showStatusTime(activity, status_showing)
// }
// }
private fun setAcct(tv : TextView, acctLong : String, acctShort : String?) {
@ -834,7 +819,7 @@ internal class ItemViewHolder(
ContentWarning.save(status, new_shown)
// 1個だけ開閉するのではなく、例えば通知TLにある複数の要素をまとめて開閉するなどある
list_adapter.notifyChange(reason="ContentWarning onClick", reset=true)
list_adapter.notifyChange(reason = "ContentWarning onClick", reset = true)
}
@ -999,7 +984,7 @@ internal class ItemViewHolder(
is TootTag -> {
// search_tag は#を含まない
val tagEncoded = Uri.encode(item.name)
val tagEncoded = item.name.encodePercent()
val host = access_info.host
val url = "https://$host/tags/$tagEncoded"
Action_HashTag.timelineOtherInstance(
@ -1203,7 +1188,7 @@ internal class ItemViewHolder(
val now = System.currentTimeMillis()
val remain = enquete.time_start + NicoEnquete.ENQUETE_EXPIRE - now
if(remain <= 0) {
Utils.showToast(context, false, R.string.enquete_was_end)
showToast(context, false, R.string.enquete_was_end)
return
}
@ -1228,15 +1213,15 @@ internal class ItemViewHolder(
val data = result.jsonObject
if(data != null) {
val message = Utils.optStringX(data, "message") ?: "?"
val message = data.parseString("message") ?: "?"
val valid = data.optBoolean("valid")
if(valid) {
Utils.showToast(context, false, R.string.enquete_voted)
showToast(context, false, R.string.enquete_voted)
} else {
Utils.showToast(context, true, R.string.enquete_vote_failed, message)
showToast(context, true, R.string.enquete_vote_failed, message)
}
} else {
Utils.showToast(context, true, result.error)
showToast(context, true, result.error)
}
}
@ -1697,7 +1682,6 @@ internal class ItemViewHolder(
}
}
}

View File

@ -1,51 +0,0 @@
package jp.juggler.subwaytooter;
import android.content.Context;
import android.support.annotation.NonNull;
import android.util.Log;
import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.Registry;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.load.DecodeFormat;
import com.bumptech.glide.load.engine.bitmap_recycle.LruBitmapPool;
import com.bumptech.glide.load.engine.cache.LruResourceCache;
import com.bumptech.glide.load.engine.cache.MemorySizeCalculator;
import com.bumptech.glide.load.engine.executor.GlideExecutor;
import com.bumptech.glide.module.AppGlideModule;
import com.bumptech.glide.request.RequestOptions;
import jp.juggler.subwaytooter.util.LogCategory;
import static com.bumptech.glide.load.engine.executor.GlideExecutor.newDiskCacheExecutor;
import static com.bumptech.glide.load.engine.executor.GlideExecutor.newSourceExecutor;
@GlideModule
public class MyAppGlideModule extends AppGlideModule {
static final LogCategory log = new LogCategory( "MyAppGlideModule" );
// v3との互換性のためにAndroidManifestを読むかどうか(デフォルトtrue)
@Override public boolean isManifestParsingEnabled() {
return false;
}
@Override public void registerComponents( @NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
// デフォルト実装は何もしないらしい
super.registerComponents( context,glide,registry );
// App1を初期化してからOkHttp3Factoryと連動させる
App1.Companion.prepare( context.getApplicationContext() );
App1.Companion.registerGlideComponents(context,glide,registry);
}
@Override
public void applyOptions(Context context, GlideBuilder builder) {
// デフォルト実装は何もしないらしい
super.applyOptions( context,builder );
// App1を初期化してから色々する
App1.Companion.prepare( context.getApplicationContext() );
App1.Companion.applyGlideOptions(context,builder);
}
}

View File

@ -0,0 +1,37 @@
package jp.juggler.subwaytooter
import android.content.Context
import com.bumptech.glide.Glide
import com.bumptech.glide.GlideBuilder
import com.bumptech.glide.Registry
import com.bumptech.glide.annotation.GlideModule
import com.bumptech.glide.module.AppGlideModule
@GlideModule
class MyAppGlideModule : AppGlideModule() {
// v3との互換性のためにAndroidManifestを読むかどうか(デフォルトtrue)
override fun isManifestParsingEnabled() : Boolean {
return false
}
override fun registerComponents(context : Context, glide : Glide, registry : Registry) {
// デフォルト実装は何もしないらしい
super.registerComponents(context, glide, registry)
// App1を初期化してからOkHttp3Factoryと連動させる
App1.prepare(context.applicationContext)
App1.registerGlideComponents(context, glide, registry)
}
override fun applyOptions(context : Context, builder : GlideBuilder) {
// デフォルト実装は何もしないらしい
super.applyOptions(context, builder)
// App1を初期化してから色々する
App1.prepare(context.applicationContext)
App1.applyGlideOptions(context, builder)
}
}

View File

@ -148,7 +148,12 @@ class PollingWorker private constructor(c : Context) {
}
// タスクの追加
private fun addTask(context : Context, removeOld : Boolean, task_id : Int, taskDataArg : JSONObject?) {
private fun addTask(
context : Context,
removeOld : Boolean,
task_id : Int,
taskDataArg : JSONObject?
) {
try {
val taskData = taskDataArg ?: JSONObject()
taskData.put(EXTRA_TASK_ID, task_id)
@ -264,7 +269,10 @@ class PollingWorker private constructor(c : Context) {
// ジョブが完了した?
val now = SystemClock.elapsedRealtime()
if(! pw.hasJob(JOB_FCM)) {
log.d("handleFCMMessage: JOB_FCM completed. time=%.2f", (now - time_start) / 1000f)
log.d(
"handleFCMMessage: JOB_FCM completed. time=%.2f",
(now - time_start) / 1000f
)
break
}
// ジョブの状況を通知する
@ -328,7 +336,10 @@ class PollingWorker private constructor(c : Context) {
this.wifi_manager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as? WifiManager
?: throw NotImplementedError("missing WifiManager system service")
power_lock = power_manager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, PollingWorker::class.java.name)
power_lock = power_manager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK,
PollingWorker::class.java.name
)
power_lock.setReferenceCounted(false)
wifi_lock = wifi_manager.createWifiLock(PollingWorker::class.java.name)
@ -719,7 +730,7 @@ class PollingWorker private constructor(c : Context) {
this.taskId = taskId
var process_db_id = - 1L //
when(taskId) {
TASK_APP_DATA_IMPORT_BEFORE -> {
scheduler.cancelAll()
@ -735,13 +746,14 @@ class PollingWorker private constructor(c : Context) {
mBusyAppDataImportBefore.set(false)
return
}
TASK_APP_DATA_IMPORT_AFTER -> {
mBusyAppDataImportAfter.set(false)
mBusyAppDataImportBefore.set(false)
NotificationTracking.resetPostAll()
// fall
}
}
// アプリデータのインポート処理がビジーな間、他のジョブは実行されない
@ -755,7 +767,6 @@ class PollingWorker private constructor(c : Context) {
TASK_PACKAGE_REPLACED -> NotificationTracking.resetPostAll()
// デバイストークンが更新された
TASK_FCM_DEVICE_TOKEN -> {
}
@ -763,7 +774,7 @@ class PollingWorker private constructor(c : Context) {
// プッシュ通知が届いた
TASK_FCM_MESSAGE -> {
var bDone = false
val tag = Utils.optStringX(taskData, EXTRA_TAG)
val tag = taskData.parseString(EXTRA_TAG)
if(tag != null) {
for(sa in SavedAccount.loadByTag(context, tag)) {
NotificationTracking.resetLastLoad(sa.db_id)
@ -776,31 +787,37 @@ class PollingWorker private constructor(c : Context) {
NotificationTracking.resetLastLoad()
}
}
TASK_NOTIFICATION_CLEAR -> {
val db_id = Utils.optLongX(taskData, EXTRA_DB_ID, - 1L)
deleteCacheData(db_id)
val db_id = taskData.parseLong(EXTRA_DB_ID)
log.d("Notification clear! db_id=%s", db_id)
if(db_id != null) {
deleteCacheData(db_id)
}
}
TASK_NOTIFICATION_DELETE -> {
val db_id = Utils.optLongX(taskData, EXTRA_DB_ID, - 1L)
val db_id = taskData.parseLong(EXTRA_DB_ID)
log.d("Notification deleted! db_id=%s", db_id)
NotificationTracking.updateRead(db_id)
if(db_id != null) {
NotificationTracking.updateRead(db_id)
}
return
}
TASK_NOTIFICATION_CLICK -> {
val db_id = Utils.optLongX(taskData, EXTRA_DB_ID, - 1L)
val db_id = taskData.parseLong(EXTRA_DB_ID)
log.d("Notification clicked! db_id=%s", db_id)
// 通知をキャンセル
notification_manager.cancel(db_id.toString(), NOTIFICATION_ID)
// DB更新処理
NotificationTracking.updateRead(db_id)
if(db_id != null) {
// 通知をキャンセル
notification_manager.cancel(db_id.toString(), NOTIFICATION_ID)
// DB更新処理
NotificationTracking.updateRead(db_id)
}
return
}
}
loadCustomStreamListenerSetting()
@ -868,11 +885,11 @@ class PollingWorker private constructor(c : Context) {
// インストールIDを生成する前に、各データの通知登録キャッシュをクリアする
// トークンがまだ生成されていない場合、このメソッドは null を返します。
private fun prepareInstallId():String? {
private fun prepareInstallId() : String? {
val prefDevice = PrefDevice.prefDevice(context)
var sv = prefDevice.getString(PrefDevice.KEY_INSTALL_ID, null)
if(sv?.isNotEmpty() == true ) return sv
if(sv?.isNotEmpty() == true) return sv
SavedAccount.clearRegistrationCache()
try {
@ -884,7 +901,8 @@ class PollingWorker private constructor(c : Context) {
log.e("getInstallId: missing device token.")
return null
} else {
prefDevice.edit().putString(PrefDevice.KEY_DEVICE_TOKEN, device_token).apply()
prefDevice.edit().putString(PrefDevice.KEY_DEVICE_TOKEN, device_token)
.apply()
}
} catch(ex : Throwable) {
log.e("getInstallId: could not get device token.")
@ -905,11 +923,16 @@ class PollingWorker private constructor(c : Context) {
val body = response.body()?.string()
if(! response.isSuccessful || body?.isEmpty() != false) {
log.e(TootApiClient.formatResponse(response, "getInstallId: get /counter failed."))
log.e(
TootApiClient.formatResponse(
response,
"getInstallId: get /counter failed."
)
)
return null
}
sv = Utils.digestSHA256(device_token + UUID.randomUUID() + body)
sv = (device_token + UUID.randomUUID() + body).digestSHA256()
prefDevice.edit().putString(PrefDevice.KEY_INSTALL_ID, sv).apply()
return sv
@ -928,13 +951,22 @@ class PollingWorker private constructor(c : Context) {
// 通知タップ時のPendingIntent
val intent_click = Intent(context, ActCallback::class.java)
intent_click.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
val pi_click = PendingIntent.getActivity(context, 3, intent_click, PendingIntent.FLAG_UPDATE_CURRENT)
val pi_click = PendingIntent.getActivity(
context,
3,
intent_click,
PendingIntent.FLAG_UPDATE_CURRENT
)
val builder = if(Build.VERSION.SDK_INT >= 26) {
// Android 8 から、通知のスタイルはユーザが管理することになった
// NotificationChannel を端末に登録しておけば、チャネルごとに管理画面が作られる
val channel = NotificationHelper.createNotificationChannel(
context, "ErrorNotification", "Error", null, 2 /* NotificationManager.IMPORTANCE_LOW */
context,
"ErrorNotification",
"Error",
null,
2 /* NotificationManager.IMPORTANCE_LOW */
)
NotificationCompat.Builder(context, channel.id)
} else {
@ -945,7 +977,12 @@ class PollingWorker private constructor(c : Context) {
.setContentIntent(pi_click)
.setAutoCancel(true)
.setSmallIcon(R.drawable.ic_notification) // ここは常に白テーマのアイコンを使う
.setColor(ContextCompat.getColor(context, R.color.Light_colorAccent)) // ここは常に白テーマの色を使う
.setColor(
ContextCompat.getColor(
context,
R.color.Light_colorAccent
)
) // ここは常に白テーマの色を使う
.setWhen(System.currentTimeMillis())
.setGroup(context.packageName + ":" + "Error")
@ -974,7 +1011,7 @@ class PollingWorker private constructor(c : Context) {
mCustomStreamListenerSecret = null
val jsonString = Pref.spStreamListenerConfigData(pref)
mCustomStreamListenerSettingString = jsonString
if(jsonString.isNotEmpty() ){
if(jsonString.isNotEmpty()) {
try {
mCustomStreamListenerSetting = JsonValue.readHjson(jsonString).asObject()
mCustomStreamListenerSecret = Pref.spStreamListenerSecret(pref)
@ -985,7 +1022,8 @@ class PollingWorker private constructor(c : Context) {
}
}
internal inner class AccountThread(val account : SavedAccount) : Thread(), CurrentCallCallback {
internal inner class AccountThread(val account : SavedAccount) : Thread(),
CurrentCallCallback {
private var current_call : Call? = null
@ -1031,7 +1069,7 @@ class PollingWorker private constructor(c : Context) {
&& ! account.notification_boost
&& ! account.notification_favourite
&& ! account.notification_follow
) {
) {
unregisterDeviceToken()
return
}
@ -1079,13 +1117,18 @@ class PollingWorker private constructor(c : Context) {
return
}
val post_data = ("instance_url=" + Uri.encode("https://" + account.host)
+ "&app_id=" + Uri.encode(context.packageName)
val post_data = ("instance_url=" + ("https://" + account.host).encodePercent()
+ "&app_id=" + context.packageName.encodePercent()
+ "&tag=" + tag)
val request = Request.Builder()
.url(APP_SERVER + "/unregister")
.post(RequestBody.create(TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED, post_data))
.post(
RequestBody.create(
TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED,
post_data
)
)
.build()
val call = App1.ok_http_client.newCall(request)
@ -1138,18 +1181,18 @@ class PollingWorker private constructor(c : Context) {
}
if(tag?.isEmpty() != false) {
account.notification_tag = Utils.digestSHA256(job.install_id + account.db_id + account.acct)
account.notification_tag =
(job.install_id + account.db_id + account.acct).digestSHA256()
tag = account.notification_tag
account.saveNotificationTag()
}
val reg_key = Utils.digestSHA256(
tag
+ access_token
+ device_token
+ (if(mCustomStreamListenerSecret == null) "" else mCustomStreamListenerSecret)
+ if(mCustomStreamListenerSettingString == null) "" else mCustomStreamListenerSettingString
)
val reg_key = (tag
+ access_token
+ device_token
+ (if(mCustomStreamListenerSecret == null) "" else mCustomStreamListenerSecret)
+ if(mCustomStreamListenerSettingString == null) "" else mCustomStreamListenerSettingString
).digestSHA256()
val now = System.currentTimeMillis()
if(reg_key == account.register_key && now - account.register_time < 3600000 * 3) {
@ -1159,8 +1202,8 @@ class PollingWorker private constructor(c : Context) {
}
val post_data = StringBuilder()
.append("instance_url=").append(Uri.encode("https://" + account.host))
.append("&app_id=").append(Uri.encode(context.packageName))
.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)
@ -1169,13 +1212,18 @@ class PollingWorker private constructor(c : Context) {
val appSecret = mCustomStreamListenerSecret
if(jsonString != null && appSecret != null) {
post_data.append("&user_config=").append(Uri.encode(jsonString))
post_data.append("&app_secret=").append(Uri.encode(appSecret))
post_data.append("&user_config=").append(jsonString.encodePercent())
post_data.append("&app_secret=").append(appSecret.encodePercent())
}
val request = Request.Builder()
.url(APP_SERVER + "/register")
.post(RequestBody.create(TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED, post_data.toString()))
.post(
RequestBody.create(
TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED,
post_data.toString()
)
)
.build()
val call = App1.ok_http_client.newCall(request)
@ -1210,20 +1258,21 @@ class PollingWorker private constructor(c : Context) {
this.parser = TootParser(context, account)
// まずキャッシュされたデータを処理する
if(nr.last_data != null) {
try {
val array = JSONArray(nr.last_data)
try {
val last_data = nr.last_data
if(last_data != null) {
val array = last_data.toJsonArray()
for(i in array.length() - 1 downTo 0) {
if(job.isJobCancelled) return
val src = array.optJSONObject(i)
update_sub(src)
}
} catch(ex : JSONException) {
log.trace(ex)
}
} catch(ex : JSONException) {
log.trace(ex)
}
if(job.isJobCancelled) return
// 前回の更新から一定時刻が経過したら新しいデータを読んでリストに追加する
@ -1283,8 +1332,8 @@ class PollingWorker private constructor(c : Context) {
if(job.isJobCancelled) return
dstListJson.sortWith(Comparator { a, b ->
val la = Utils.optLongX(a, KEY_TIME, 0)
val lb = Utils.optLongX(b, KEY_TIME, 0)
val la = a.parseLong(KEY_TIME) ?: 0
val lb = b.parseLong(KEY_TIME) ?: 0
// 新しい順
return@Comparator if(la < lb) 1 else if(la > lb) - 1 else 0
})
@ -1304,19 +1353,24 @@ class PollingWorker private constructor(c : Context) {
private fun update_sub(src : JSONObject) {
if(nr.nid_read == 0L || nr.nid_show == 0L) {
log.d("update_sub account_db_id=%s, nid_read=%s, nid_show=%s", account.db_id, nr.nid_read, nr.nid_show)
log.d(
"update_sub account_db_id=%s, nid_read=%s, nid_show=%s",
account.db_id,
nr.nid_read,
nr.nid_show
)
}
val id = Utils.optLongX(src, "id")
val id = src.parseLong("id")
if(duplicate_check.contains(id)) return
if(id == null || duplicate_check.contains(id)) return
duplicate_check.add(id)
if(id > nid_last_show) {
nid_last_show = id
}
val type = Utils.optStringX(src, "type")
val type = src.parseString("type")
if(id <= nr.nid_read) {
// log.d("update_sub: ignore data that id=%s, <= read id %s ",id,nr.nid_read);
@ -1326,7 +1380,11 @@ class PollingWorker private constructor(c : Context) {
}
if(id > nr.nid_show) {
log.d("update_sub: found new data that id=%s, greater than shown id %s ", id, nr.nid_show)
log.d(
"update_sub: found new data that id=%s, greater than shown id %s ",
id,
nr.nid_show
)
// 種別チェックより先に「表示済み」idの更新を行う
nr.nid_show = id
}
@ -1359,7 +1417,10 @@ class PollingWorker private constructor(c : Context) {
private fun getNotificationLine(type : String, display_name : CharSequence) : String {
if(TootNotification.TYPE_FAVOURITE == type) {
return "- " + context.getString(R.string.display_name_favourited_by, display_name)
return "- " + context.getString(
R.string.display_name_favourited_by,
display_name
)
}
if(TootNotification.TYPE_REBLOG == type) {
return "- " + context.getString(R.string.display_name_boosted_by, display_name)
@ -1396,7 +1457,11 @@ class PollingWorker private constructor(c : Context) {
// 先頭にあるデータが同じなら、通知を更新しない
// このマーカーは端末再起動時にリセットされるので、再起動後は通知が出るはず
log.d("showNotification[%s] id=%s is already shown.", account.acct, item.notification.id)
log.d(
"showNotification[%s] id=%s is already shown.",
account.acct,
item.notification.id
)
return
}
@ -1408,15 +1473,26 @@ class PollingWorker private constructor(c : Context) {
// 通知タップ時のPendingIntent
val intent_click = Intent(context, ActCallback::class.java)
intent_click.action = ActCallback.ACTION_NOTIFICATION_CLICK
intent_click.data = Uri.parse("subwaytooter://notification_click/?db_id=" + account.db_id)
intent_click.data =
Uri.parse("subwaytooter://notification_click/?db_id=" + account.db_id)
intent_click.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
val pi_click = PendingIntent.getActivity(context, 256 + account.db_id.toInt(), intent_click, PendingIntent.FLAG_UPDATE_CURRENT)
val pi_click = PendingIntent.getActivity(
context,
256 + account.db_id.toInt(),
intent_click,
PendingIntent.FLAG_UPDATE_CURRENT
)
// 通知を消去した時のPendingIntent
val intent_delete = Intent(context, EventReceiver::class.java)
intent_delete.action = EventReceiver.ACTION_NOTIFICATION_DELETE
intent_delete.putExtra(EXTRA_DB_ID, account.db_id)
val pi_delete = PendingIntent.getBroadcast(context, Integer.MAX_VALUE - account.db_id.toInt(), intent_delete, PendingIntent.FLAG_UPDATE_CURRENT)
val pi_delete = PendingIntent.getBroadcast(
context,
Integer.MAX_VALUE - account.db_id.toInt(),
intent_delete,
PendingIntent.FLAG_UPDATE_CURRENT
)
log.d("showNotification[%s] creating notification(2)", account.acct)
@ -1434,7 +1510,12 @@ class PollingWorker private constructor(c : Context) {
.setDeleteIntent(pi_delete)
.setAutoCancel(true)
.setSmallIcon(R.drawable.ic_notification) // ここは常に白テーマのアイコンを使う
.setColor(ContextCompat.getColor(context, R.color.Light_colorAccent)) // ここは常に白テーマの色を使う
.setColor(
ContextCompat.getColor(
context,
R.color.Light_colorAccent
)
) // ここは常に白テーマの色を使う
.setWhen(item.notification.time_created_at)
// Android 7.0 ではグループを指定しないと勝手に通知が束ねられてしまう。
@ -1448,7 +1529,7 @@ class PollingWorker private constructor(c : Context) {
var iv = 0
if( Pref.bpNotificationSound(pref) ) {
if(Pref.bpNotificationSound(pref)) {
var sound_uri : Uri? = null
@ -1504,7 +1585,10 @@ class PollingWorker private constructor(c : Context) {
log.d("showNotification[%s] creating notification(7)", account.acct)
var a = getNotificationLine(item.notification.type, item.notification.account?.decoded_display_name ?: "?")
var a = getNotificationLine(
item.notification.type,
item.notification.account?.decoded_display_name ?: "?"
)
val acct = item.access_info.acct
if(data_list.size == 1) {
builder.setContentTitle(a)
@ -1520,7 +1604,10 @@ class PollingWorker private constructor(c : Context) {
for(i in 0 .. 4) {
if(i >= data_list.size) break
item = data_list[i]
a = getNotificationLine(item.notification.type, item.notification.account?.decoded_display_name ?: "?")
a = getNotificationLine(
item.notification.type,
item.notification.account?.decoded_display_name ?: "?"
)
style.addLine(a)
}
builder.setStyle(style)
@ -1544,22 +1631,25 @@ class PollingWorker private constructor(c : Context) {
val duplicate_check = HashSet<Long>()
val dst_array = ArrayList<JSONObject>()
if(nr.last_data != null) {
try {
// まずキャッシュされたデータを処理する
try {
val array = JSONArray(nr.last_data)
val last_data = nr.last_data
if(last_data != null) {
val array = last_data.toJsonArray()
for(i in array.length() - 1 downTo 0) {
val src = array.optJSONObject(i)
val id = Utils.optLongX(src, "id")
dst_array.add(src)
duplicate_check.add(id)
log.d("add old. id=%s", id)
val id = src.parseLong("id")
if(id != null) {
dst_array.add(src)
duplicate_check.add(id)
log.d("add old. id=%s", id)
}
}
} catch(ex : JSONException) {
log.trace(ex)
}
} catch(ex : JSONException) {
log.trace(ex)
}
for(item in data.list) {
try {
if(duplicate_check.contains(item.id)) {
@ -1590,8 +1680,8 @@ class PollingWorker private constructor(c : Context) {
// 新しい順にソート
Collections.sort(dst_array, Comparator { a, b ->
val la = Utils.optLongX(a, KEY_TIME, 0)
val lb = Utils.optLongX(b, KEY_TIME, 0)
val la = a.parseLong(KEY_TIME) ?: 0
val lb = b.parseLong(KEY_TIME) ?: 0
// 新しい順
if(la < lb) return@Comparator + 1
if(la > lb) - 1 else 0

View File

@ -3,6 +3,7 @@ package jp.juggler.subwaytooter
import android.content.Context
import android.content.SharedPreferences
import android.preference.PreferenceManager
import jp.juggler.subwaytooter.util.optInt
object Pref {
@ -99,6 +100,8 @@ object Pref {
override fun put(editor : SharedPreferences.Editor, v : String) {
editor.putString(key, v)
}
fun optInt(pref : SharedPreferences) = invoke(pref).optInt() ?: defVal.optInt()
}
// boolean

View File

@ -4,8 +4,6 @@ import android.content.Context
import android.content.SharedPreferences
import android.os.Handler
import org.json.JSONObject
import java.net.ProtocolException
import java.util.LinkedList
import java.util.concurrent.atomic.AtomicBoolean
@ -20,8 +18,9 @@ import jp.juggler.subwaytooter.api.TootParser
import jp.juggler.subwaytooter.api.entity.TootPayload
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.WordTrieTree
import jp.juggler.subwaytooter.util.runOnMainLooper
import jp.juggler.subwaytooter.util.toJsonObject
import okhttp3.Response
import okhttp3.WebSocket
import okhttp3.WebSocketListener
@ -103,7 +102,7 @@ internal class StreamReader(
override fun onMessage(webSocket : WebSocket, text : String) {
// log.d( "WebSocket onMessage. url=%s, message=%s", webSocket.request().url(), text );
try {
val obj = JSONObject(text)
val obj = text.toJsonObject()
val event = obj.optString("event")
if(event == null || event.isEmpty()) {
@ -117,9 +116,9 @@ internal class StreamReader(
return
}
Utils.runOnMainThread {
runOnMainLooper {
synchronized(this) {
if(bDisposed.get()) return@runOnMainThread
if(bDisposed.get()) return@runOnMainLooper
for(callback in callback_list) {
try {
callback(event, payload)
@ -250,7 +249,7 @@ internal class StreamReader(
streamCallback : (event_type : String, item : Any?) -> Unit
) {
val reader = prepareReader(accessInfo, endPoint, highlightTrie)
reader.addCallback(streamCallback)
if(! reader.bListening.get()) {

View File

@ -6,7 +6,7 @@ import android.widget.Button
import android.widget.TextView
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.scan
internal abstract class ViewHolderHeaderBase(val activity : ActMain, val viewRoot : View) :
RecyclerView.ViewHolder(viewRoot) {
@ -19,7 +19,7 @@ internal abstract class ViewHolderHeaderBase(val activity : ActMain, val viewRoo
internal lateinit var access_info : SavedAccount
init {
Utils.scanView(viewRoot) { v ->
viewRoot.scan { v ->
try {
if(v is Button) {
// ボタンは太字なので触らない

View File

@ -8,7 +8,7 @@ import android.widget.TextView
import jp.juggler.subwaytooter.api.entity.TootInstance
import jp.juggler.subwaytooter.util.DecodeOptions
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.showToast
import jp.juggler.subwaytooter.view.MyLinkMovementMethod
import jp.juggler.subwaytooter.view.MyNetworkImageView
@ -149,7 +149,7 @@ internal class ViewHolderHeaderInstance(
} catch(ex : Throwable) {
log.e(ex, "startActivity failed. mail=$email")
Utils.showToast(activity, true, R.string.missing_mail_app)
showToast(activity, true, R.string.missing_mail_app)
}
}
@ -164,7 +164,7 @@ internal class ViewHolderHeaderInstance(
} catch(ex : Throwable) {
log.e(ex, "startActivity failed. thumbnail=$thumbnail")
Utils.showToast(activity, true, "missing web browser")
showToast(activity, true, "missing web browser")
}
}

View File

@ -18,7 +18,7 @@ import jp.juggler.subwaytooter.table.AcctColor
import jp.juggler.subwaytooter.table.UserRelation
import jp.juggler.subwaytooter.span.EmojiImageSpan
import jp.juggler.subwaytooter.util.NetworkEmojiInvalidator
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.intoStringResource
import jp.juggler.subwaytooter.view.MyLinkMovementMethod
import jp.juggler.subwaytooter.view.MyNetworkImageView
@ -219,11 +219,11 @@ internal class ViewHolderHeaderProfile(
llMoved.visibility = View.VISIBLE
tvMoved.visibility = View.VISIBLE
val caption = Utils.formatSpannable1(
val caption = who.decodeDisplayName(activity).intoStringResource(
activity,
R.string.account_moved_to,
who.decodeDisplayName(activity)
R.string.account_moved_to
)
tvMoved.text = caption
moved_caption_invalidator.register(caption)

View File

@ -1,7 +1,6 @@
package jp.juggler.subwaytooter.action
import android.content.Context
import android.net.Uri
import org.json.JSONObject
@ -17,9 +16,7 @@ import jp.juggler.subwaytooter.api.entity.TootRelationShip
import jp.juggler.subwaytooter.api.entity.parseList
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.table.UserRelation
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.TootAccountOrNullCallback
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.*
// ユーザ名からアカウントIDを取得する
internal fun findAccountByName(
@ -34,7 +31,8 @@ internal fun findAccountByName(
internal var who : TootAccount? = null
override fun background(client : TootApiClient) : TootApiResult? {
val path = "/api/v1/accounts/search" + "?q=" + Uri.encode(user)
val path = "/api/v1/accounts/search" + "?q=" + user.encodePercent()
val result = client.request(path)
val array = result?.jsonArray
@ -42,7 +40,10 @@ internal fun findAccountByName(
for(i in 0 until array.length()) {
val a = TootAccount.parse(activity, access_info, array.optJSONObject(i))
if(a != null) {
if(a.username == user && access_info.getFullAcct(a).equals(user + "@" + host, ignoreCase = true)) {
if(a.username == user && access_info.getFullAcct(a).equals(
user + "@" + host,
ignoreCase = true
)) {
who = a
break
}
@ -93,7 +94,7 @@ internal fun addPseudoAccount(
val log = LogCategory("addPseudoAccount")
log.trace(ex)
log.e(ex, "failed.")
Utils.showToast(context, ex, "addPseudoAccount failed.")
showToast(context, ex, "addPseudoAccount failed.")
}
return null
@ -108,7 +109,10 @@ fun makeAccountListNonPseudo(
val list_other_host = ArrayList<SavedAccount>()
for(a in SavedAccount.loadAccountList(context)) {
if(a.isPseudo) continue
(if(pickup_host == null || pickup_host.equals(a.host, ignoreCase = true)) list_same_host else list_other_host).add(a)
(if(pickup_host == null || pickup_host.equals(
a.host,
ignoreCase = true
)) list_same_host else list_other_host).add(a)
}
SavedAccount.sort(list_same_host)
SavedAccount.sort(list_other_host)
@ -134,7 +138,7 @@ internal fun loadRelation1(
val r2 = rr.result
val jsonArray = r2?.jsonArray
if(jsonArray != null) {
val list = parseList(::TootRelationShip,jsonArray)
val list = parseList(::TootRelationShip, jsonArray)
if(list.isNotEmpty()) {
rr.relation = saveUserRelation(access_info, list[0])
}
@ -147,7 +151,10 @@ const val NOT_CROSS_ACCOUNT = 1
const val CROSS_ACCOUNT_SAME_INSTANCE = 2
const val CROSS_ACCOUNT_REMOTE_INSTANCE = 3
internal fun calcCrossAccountMode(timeline_account : SavedAccount, action_account : SavedAccount) : Int {
internal fun calcCrossAccountMode(
timeline_account : SavedAccount,
action_account : SavedAccount
) : Int {
return if(! timeline_account.host.equals(action_account.host, ignoreCase = true)) {
CROSS_ACCOUNT_REMOTE_INSTANCE
} else if(! timeline_account.acct.equals(action_account.acct, ignoreCase = true)) {

View File

@ -21,7 +21,7 @@ import jp.juggler.subwaytooter.dialog.AccountPicker
import jp.juggler.subwaytooter.dialog.DlgTextInput
import jp.juggler.subwaytooter.dialog.LoginForm
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.showToast
import org.json.JSONObject
object Action_Account {
@ -81,7 +81,7 @@ object Action_Account {
}
override fun onEmptyError() {
Utils.showToast(activity, true, R.string.token_not_specified)
showToast(activity, true, R.string.token_not_specified)
}
}
)
@ -90,7 +90,7 @@ object Action_Account {
// 疑似アカウントを追加
val a = addPseudoAccount(activity, instance)
if(a != null) {
Utils.showToast(activity, false, R.string.server_confirmed)
showToast(activity, false, R.string.server_confirmed)
val pos = App1.getAppState(activity).column_list.size
activity.addColumn(pos, a, Column.TYPE_LOCAL)
@ -114,7 +114,7 @@ object Action_Account {
.setNeutralButton(R.string.close, null)
.show()
} else {
Utils.showToast(activity, true, error)
showToast(activity, true, error)
}
}
}

View File

@ -6,7 +6,7 @@ import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.api.entity.TootApplication
import jp.juggler.subwaytooter.table.MutedApp
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.showToast
object Action_App {
@ -23,7 +23,7 @@ object Action_App {
for(column in App1.getAppState(activity).column_list) {
column.onMuteAppUpdated()
}
Utils.showToast(activity, false, R.string.app_was_muted)
showToast(activity, false, R.string.app_was_muted)
}
}

View File

@ -1,6 +1,5 @@
package jp.juggler.subwaytooter.action
import android.net.Uri
import android.support.v7.app.AlertDialog
import jp.juggler.subwaytooter.ActMain
@ -19,7 +18,8 @@ import jp.juggler.subwaytooter.table.AcctColor
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.table.UserRelation
import jp.juggler.subwaytooter.util.EmptyCallback
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.encodePercent
import jp.juggler.subwaytooter.util.showToast
import okhttp3.Request
import okhttp3.RequestBody
@ -36,7 +36,7 @@ object Action_Follow {
callback : EmptyCallback? = null
) {
if(access_info.isMe(who)) {
Utils.showToast(activity, false, R.string.it_is_you)
showToast(activity, false, R.string.it_is_you)
return
}
@ -161,7 +161,7 @@ object Action_Follow {
// リモートフォローする
val request_builder = Request.Builder().post(
RequestBody.create(
TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED, "uri=" + Uri.encode(who.acct)
TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED, "uri=" + who.acct.encodePercent()
))
result = client.request("/api/v1/follows", request_builder)
@ -205,18 +205,18 @@ object Action_Follow {
if(bFollow && relation .getRequested(who)) {
// 鍵付きアカウントにフォローリクエストを申請した状態
Utils.showToast(activity, false, R.string.follow_requested)
showToast(activity, false, R.string.follow_requested)
} else if(! bFollow && relation .getRequested(who)) {
Utils.showToast(activity, false, R.string.follow_request_cant_remove_by_sender)
showToast(activity, false, R.string.follow_request_cant_remove_by_sender)
} else {
// ローカル操作成功、もしくはリモートフォロー成功
if(callback != null) callback()
}
} else if(bFollow && who.locked && ( result.response?.code() ?: -1) == 422) {
Utils.showToast(activity, false, R.string.cant_follow_locked_user)
showToast(activity, false, R.string.cant_follow_locked_user)
} else {
Utils.showToast(activity, false, result.error)
showToast(activity, false, result.error)
}
}
@ -233,7 +233,7 @@ object Action_Follow {
callback : EmptyCallback? =null
) {
if(access_info.isMe(acct)) {
Utils.showToast(activity, false, R.string.it_is_you)
showToast(activity, false, R.string.it_is_you)
return
}
@ -294,7 +294,7 @@ object Action_Follow {
val request_builder = Request.Builder().post(
RequestBody.create(
TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED, "uri=" + Uri.encode(acct)
TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED, "uri=" + acct.encodePercent()
))
var result = client.request("/api/v1/follows", request_builder)
@ -323,9 +323,9 @@ object Action_Follow {
if(callback != null) callback()
} else if(locked && (result.response?.code() ?: -1 )== 422) {
Utils.showToast(activity, false, R.string.cant_follow_locked_user)
showToast(activity, false, R.string.cant_follow_locked_user)
} else {
Utils.showToast(activity, false, result.error)
showToast(activity, false, result.error)
}
}
@ -383,7 +383,7 @@ object Action_Follow {
activity : ActMain, access_info : SavedAccount, who : TootAccount, bAllow : Boolean
) {
if(access_info.isMe(who)) {
Utils.showToast(activity, false, R.string.it_is_you)
showToast(activity, false, R.string.it_is_you)
return
}
@ -407,9 +407,9 @@ object Action_Follow {
column.removeFollowRequest(access_info, who.id)
}
Utils.showToast(activity, false, if(bAllow) R.string.follow_request_authorized else R.string.follow_request_rejected, who.decoded_display_name)
showToast(activity, false, if(bAllow) R.string.follow_request_authorized else R.string.follow_request_rejected, who.decoded_display_name)
} else {
Utils.showToast(activity, false, result.error)
showToast(activity, false, result.error)
}
}
})

View File

@ -1,7 +1,5 @@
package jp.juggler.subwaytooter.action
import android.net.Uri
import java.util.ArrayList
import jp.juggler.subwaytooter.ActMain
@ -14,7 +12,8 @@ import jp.juggler.subwaytooter.api.TootTask
import jp.juggler.subwaytooter.api.TootTaskRunner
import jp.juggler.subwaytooter.dialog.AccountPicker
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.encodePercent
import jp.juggler.subwaytooter.util.showToast
import okhttp3.Request
import okhttp3.RequestBody
@ -61,7 +60,7 @@ object Action_Instance {
) {
if(access_info.host.equals(domain, ignoreCase = true)) {
Utils.showToast(activity, false, R.string.it_is_you)
showToast(activity, false, R.string.it_is_you)
return
}
@ -69,7 +68,7 @@ object Action_Instance {
override fun background(client : TootApiClient) : TootApiResult? {
val body = RequestBody.create(
TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED, "domain=" + Uri.encode(domain)
TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED, "domain=" + domain.encodePercent()
)
var request_builder = Request.Builder()
@ -87,10 +86,10 @@ object Action_Instance {
column.onDomainBlockChanged(access_info, domain, bBlock)
}
Utils.showToast(activity, false, if(bBlock) R.string.block_succeeded else R.string.unblock_succeeded)
showToast(activity, false, if(bBlock) R.string.block_succeeded else R.string.unblock_succeeded)
} else {
Utils.showToast(activity, false, result.error)
showToast(activity, false, result.error)
}
}
})

View File

@ -11,7 +11,8 @@ import jp.juggler.subwaytooter.api.TootTaskRunner
import jp.juggler.subwaytooter.api.entity.TootList
import jp.juggler.subwaytooter.api.entity.parseItem
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.showToast
import jp.juggler.subwaytooter.util.withCaption
import okhttp3.Request
import okhttp3.RequestBody
@ -34,7 +35,7 @@ object Action_List {
try {
content.put("title", title)
} catch(ex : Throwable) {
return TootApiResult(Utils.formatError(ex, "can't encoding json parameter."))
return TootApiResult(ex.withCaption("can't encoding json parameter."))
}
val request_builder = Request.Builder().post(
@ -60,11 +61,11 @@ object Action_List {
column.onListListUpdated(access_info)
}
Utils.showToast(activity, false, R.string.list_created)
showToast(activity, false, R.string.list_created)
callback?.onCreated(list)
} else {
Utils.showToast(activity, false, result.error)
showToast(activity, false, result.error)
}
}
})
@ -88,10 +89,10 @@ object Action_List {
column.onListListUpdated(access_info)
}
Utils.showToast(activity, false, R.string.delete_succeeded)
showToast(activity, false, R.string.delete_succeeded)
} else {
Utils.showToast(activity, false, result.error)
showToast(activity, false, result.error)
}
}
})

View File

@ -1,7 +1,5 @@
package jp.juggler.subwaytooter.action
import android.net.Uri
import org.json.JSONArray
import org.json.JSONObject
@ -19,7 +17,9 @@ import jp.juggler.subwaytooter.api.entity.TootRelationShip
import jp.juggler.subwaytooter.api.entity.parseList
import jp.juggler.subwaytooter.dialog.DlgConfirm
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.encodePercent
import jp.juggler.subwaytooter.util.showToast
import jp.juggler.subwaytooter.util.withCaption
import okhttp3.Request
import okhttp3.RequestBody
@ -32,7 +32,12 @@ object Action_ListMember {
}
fun add(
activity : ActMain, access_info : SavedAccount, list_id : Long, local_who : TootAccount, bFollow : Boolean, callback : Callback?
activity : ActMain,
access_info : SavedAccount,
list_id : Long,
local_who : TootAccount,
bFollow : Boolean,
callback : Callback?
) {
TootTaskRunner(activity).run(access_info, object : TootTask {
override fun background(client : TootApiClient) : TootApiResult? {
@ -49,27 +54,34 @@ object Action_ListMember {
val request_builder = Request.Builder().post(
RequestBody.create(
TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED, "" // 空データ
))
)
)
result = client.request("/api/v1/accounts/" + local_who.id + "/follow", request_builder)
result = client.request(
"/api/v1/accounts/" + local_who.id + "/follow",
request_builder
)
} else {
// リモートフォローする
val request_builder = Request.Builder().post(
RequestBody.create(
TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED, "uri=" + Uri.encode(local_who.acct)
))
TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED,
"uri=" + local_who.acct.encodePercent()
)
)
result = client.request("/api/v1/follows", request_builder)
val jsonObject = result?.jsonObject ?:return result
val jsonObject = result?.jsonObject ?: return result
val a = TootAccount.parse(activity, access_info,jsonObject) ?: return TootApiResult("parse error.")
val a = TootAccount.parse(activity, access_info, jsonObject)
?: return TootApiResult("parse error.")
// リモートフォローの後にリレーションシップを取得しなおす
result = client.request("/api/v1/accounts/relationships?id[]=" + a.id)
}
val jsonArray = result?.jsonArray ?: return result
val relation_list = parseList(::TootRelationShip,jsonArray)
val relation_list = parseList(::TootRelationShip, jsonArray)
relation = if(relation_list.isEmpty()) null else relation_list[0]
if(relation == null) {
@ -95,13 +107,14 @@ object Action_ListMember {
account_ids.put(local_who.id.toString())
content.put("account_ids", account_ids)
} catch(ex : Throwable) {
return TootApiResult(Utils.formatError(ex, "can't encoding json parameter."))
return TootApiResult(ex.withCaption("can't encoding json parameter."))
}
val request_builder = Request.Builder().post(
RequestBody.create(
TootApiClient.MEDIA_TYPE_JSON, content.toString()
))
)
)
return client.request("/api/v1/lists/$list_id/accounts", request_builder)
@ -122,7 +135,7 @@ object Action_ListMember {
// フォロー状態の更新を表示に反映させる
if(bFollow) activity.showColumnMatchAccount(access_info)
Utils.showToast(activity, false, R.string.list_member_added)
showToast(activity, false, R.string.list_member_added)
bSuccess = true
@ -130,13 +143,26 @@ object Action_ListMember {
val response = result.response
val error = result.error
if(response != null
&& response .code() == 422
&& error != null && reFollowError.matcher(error ).find()) {
&& response.code() == 422
&& error != null && reFollowError.matcher(error).find()) {
if(! bFollow) {
DlgConfirm.openSimple(
activity, activity.getString(R.string.list_retry_with_follow, access_info.getFullAcct(local_who))
) { Action_ListMember.add(activity, access_info, list_id, local_who, true, callback) }
activity,
activity.getString(
R.string.list_retry_with_follow,
access_info.getFullAcct(local_who)
)
) {
Action_ListMember.add(
activity,
access_info,
list_id,
local_who,
true,
callback
)
}
} else {
android.app.AlertDialog.Builder(activity)
.setCancelable(true)
@ -147,7 +173,7 @@ object Action_ListMember {
return
}
Utils.showToast(activity, true, error)
showToast(activity, true, error)
}
} finally {
@ -159,12 +185,17 @@ object Action_ListMember {
}
fun delete(
activity : ActMain, access_info : SavedAccount, list_id : Long, local_who : TootAccount, callback : Callback?
activity : ActMain,
access_info : SavedAccount,
list_id : Long,
local_who : TootAccount,
callback : Callback?
) {
TootTaskRunner(activity).run(access_info, object : TootTask {
override fun background(client : TootApiClient) : TootApiResult? {
return client.request(
"/api/v1/lists/" + list_id + "/accounts?account_ids[]=" + local_who.id, Request.Builder().delete()
"/api/v1/lists/" + list_id + "/accounts?account_ids[]=" + local_who.id,
Request.Builder().delete()
)
}
@ -181,12 +212,12 @@ object Action_ListMember {
column.onListMemberUpdated(access_info, list_id, local_who, false)
}
Utils.showToast(activity, false, R.string.delete_succeeded)
showToast(activity, false, R.string.delete_succeeded)
bSuccess = true
} else {
Utils.showToast(activity, false, result.error)
showToast(activity, false, result.error)
}
} finally {
callback?.onListMemberUpdated(false, bSuccess)

View File

@ -12,7 +12,7 @@ import jp.juggler.subwaytooter.api.TootTask
import jp.juggler.subwaytooter.api.TootTaskRunner
import jp.juggler.subwaytooter.api.entity.TootNotification
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.showToast
import okhttp3.Request
import okhttp3.RequestBody
@ -49,9 +49,9 @@ object Action_Notification {
column.removeNotifications()
}
}
Utils.showToast(activity, false, R.string.delete_succeeded)
showToast(activity, false, R.string.delete_succeeded)
} else {
Utils.showToast(activity, false, result.error)
showToast(activity, false, result.error)
}
}
@ -81,9 +81,9 @@ object Action_Notification {
for(column in App1.getAppState(activity).column_list) {
column.removeNotificationOne(access_info, notification)
}
Utils.showToast(activity, true, R.string.delete_succeeded)
showToast(activity, true, R.string.delete_succeeded)
} else {
Utils.showToast(activity, true, result.error)
showToast(activity, true, result.error)
}
}

View File

@ -24,7 +24,8 @@ import jp.juggler.subwaytooter.table.AcctColor
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.EmptyCallback
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.showToast
import jp.juggler.subwaytooter.util.encodePercent
import okhttp3.Request
import okhttp3.RequestBody
@ -71,7 +72,7 @@ object Action_Toot {
callback : EmptyCallback?
) {
if(App1.getAppState(activity).isBusyFav(access_info, arg_status)) {
Utils.showToast(activity, false, R.string.wait_previous_operation)
showToast(activity, false, R.string.wait_previous_operation)
return
}
//
@ -87,7 +88,9 @@ object Action_Toot {
var target_status : TootStatus?
if(nCrossAccountMode == CROSS_ACCOUNT_REMOTE_INSTANCE) {
// 検索APIに他タンスのステータスのURLを投げると、自タンスのステータスを得られる
val path = String.format(Locale.JAPAN, Column.PATH_SEARCH, Uri.encode(arg_status.url)) + "&resolve=1"
val status_url = arg_status.url
if( status_url?.isEmpty()!=false) return TootApiResult("missing status URL")
val path = String.format(Locale.JAPAN, Column.PATH_SEARCH, status_url.encodePercent()) + "&resolve=1"
result = client.request(path)
val jsonObject = result?.jsonObject ?: return result
@ -169,7 +172,7 @@ object Action_Toot {
}
else -> Utils.showToast(activity, true, result.error)
else -> showToast(activity, true, result.error)
}
// 結果に関わらず、更新中状態から復帰させる
activity.showColumnMatchAccount(access_info)
@ -214,7 +217,7 @@ object Action_Toot {
// アカウントからステータスにブースト操作を行っているなら、何もしない
if(App1.getAppState(activity).isBusyBoost(access_info, arg_status)) {
Utils.showToast(activity, false, R.string.wait_previous_operation)
showToast(activity, false, R.string.wait_previous_operation)
return
}
@ -223,7 +226,7 @@ object Action_Toot {
// if(arg_status.reblogged) {
// if(App1.getAppState(activity).isBusyFav(access_info, arg_status) || arg_status.favourited) {
// // FAVがついているか、FAV操作中はBoostを外せない
// Utils.showToast(activity, false, R.string.cant_remove_boost_while_favourited)
// showToast(activity, false, R.string.cant_remove_boost_while_favourited)
// return
// }
// }
@ -264,8 +267,10 @@ object Action_Toot {
var target_status : TootStatus?
if(nCrossAccountMode == CROSS_ACCOUNT_REMOTE_INSTANCE) {
val status_url = arg_status.url
if( status_url?.isEmpty()!=false) return TootApiResult("missing status URL")
// 検索APIに他タンスのステータスのURLを投げると、自タンスのステータスを得られる
val path = String.format(Locale.JAPAN, Column.PATH_SEARCH, Uri.encode(arg_status.url)) + "&resolve=1"
val path = String.format(Locale.JAPAN, Column.PATH_SEARCH, status_url.encodePercent() ) + "&resolve=1"
result = client.request(path)
val jsonObject = result?.jsonObject ?: return result
@ -346,7 +351,7 @@ object Action_Toot {
if(callback != null) callback()
}
else -> Utils.showToast(activity, true, result.error)
else -> showToast(activity, true, result.error)
}
// 結果に関わらず、更新中状態から復帰させる
@ -374,12 +379,12 @@ object Action_Toot {
if(result == null) return // cancelled.
if(result.jsonObject != null) {
Utils.showToast(activity, false, R.string.delete_succeeded)
showToast(activity, false, R.string.delete_succeeded)
for(column in App1.getAppState(activity).column_list) {
column.removeStatus(access_info, status_id)
}
} else {
Utils.showToast(activity, false, result.error)
showToast(activity, false, result.error)
}
}
@ -554,7 +559,7 @@ object Action_Toot {
}
} else {
// 検索APIに他タンスのステータスのURLを投げると、自タンスのステータスを得られる
val path = String.format(Locale.JAPAN, Column.PATH_SEARCH, Uri.encode(remote_status_url)) + "&resolve=1"
val path = String.format(Locale.JAPAN, Column.PATH_SEARCH, remote_status_url.encodePercent()) + "&resolve=1"
result = client.request(path)
val jsonObject = result?.jsonObject
if(jsonObject != null) {
@ -578,7 +583,7 @@ object Action_Toot {
if(local_status_id != - 1L) {
conversationLocal(activity, pos, access_info, local_status_id)
} else {
Utils.showToast(activity, true, result.error)
showToast(activity, true, result.error)
}
}
})
@ -636,7 +641,7 @@ object Action_Toot {
}
}
}
else -> Utils.showToast(activity, true, result.error)
else -> showToast(activity, true, result.error)
}
// 結果に関わらず、更新中状態から復帰させる
@ -691,7 +696,7 @@ object Action_Toot {
internal var local_status : TootStatus? = null
override fun background(client : TootApiClient) : TootApiResult? {
// 検索APIに他タンスのステータスのURLを投げると、自タンスのステータスを得られる
val path = String.format(Locale.JAPAN, Column.PATH_SEARCH, Uri.encode(remote_status_url)) + "&resolve=1"
val path = String.format(Locale.JAPAN, Column.PATH_SEARCH,remote_status_url.encodePercent()) + "&resolve=1"
val result = client.request(path)
val jsonObject = result?.jsonObject
@ -715,7 +720,7 @@ object Action_Toot {
if(ls != null) {
reply(activity, access_info, ls)
} else {
Utils.showToast(activity, true, result.error)
showToast(activity, true, result.error)
}
}
})
@ -759,9 +764,9 @@ object Action_Toot {
}
}
}
Utils.showToast(activity, true, if(bMute) R.string.mute_succeeded else R.string.unmute_succeeded)
showToast(activity, true, if(bMute) R.string.mute_succeeded else R.string.unmute_succeeded)
} else {
Utils.showToast(activity, true, result.error)
showToast(activity, true, result.error)
}
}
})

View File

@ -1,7 +1,5 @@
package jp.juggler.subwaytooter.action
import android.net.Uri
import org.json.JSONObject
import java.util.Locale
@ -25,8 +23,7 @@ import jp.juggler.subwaytooter.dialog.ReportForm
import jp.juggler.subwaytooter.table.AcctColor
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.table.UserRelation
import jp.juggler.subwaytooter.util.TootApiResultCallback
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.*
import okhttp3.Request
import okhttp3.RequestBody
@ -42,7 +39,7 @@ object Action_User {
) {
if(access_info.isMe(who)) {
Utils.showToast(activity, false, R.string.it_is_you)
showToast(activity, false, R.string.it_is_you)
return
}
@ -75,7 +72,7 @@ object Action_User {
if(relation != null) {
// 未確認だが、自分をミュートしようとするとリクエストは成功するがレスポンス中のmutingはfalseになるはず
if(bMute && ! relation.muting) {
Utils.showToast(activity, false, R.string.not_muted)
showToast(activity, false, R.string.not_muted)
return
}
@ -93,10 +90,10 @@ object Action_User {
}
}
Utils.showToast(activity, false, if(relation.muting) R.string.mute_succeeded else R.string.unmute_succeeded)
showToast(activity, false, if(relation.muting) R.string.mute_succeeded else R.string.unmute_succeeded)
} else {
Utils.showToast(activity, false, result.error)
showToast(activity, false, result.error)
}
}
})
@ -108,7 +105,7 @@ object Action_User {
activity : ActMain, access_info : SavedAccount, who : TootAccount, bBlock : Boolean
) {
if(access_info.isMe(who)) {
Utils.showToast(activity, false, R.string.it_is_you)
showToast(activity, false, R.string.it_is_you)
return
}
@ -142,7 +139,7 @@ object Action_User {
// 自分をブロックしようとすると、blocking==falseで帰ってくる
if(bBlock && ! relation.blocking) {
Utils.showToast(activity, false, R.string.not_blocked)
showToast(activity, false, R.string.not_blocked)
return
}
@ -160,10 +157,10 @@ object Action_User {
}
}
Utils.showToast(activity, false, if(relation.blocking) R.string.block_succeeded else R.string.unblock_succeeded)
showToast(activity, false, if(relation.blocking) R.string.block_succeeded else R.string.unblock_succeeded)
} else {
Utils.showToast(activity, false, result.error)
showToast(activity, false, result.error)
}
}
})
@ -196,7 +193,8 @@ object Action_User {
internal var who_local : TootAccount? = null
override fun background(client : TootApiClient) : TootApiResult? {
val path = String.format(Locale.JAPAN, Column.PATH_SEARCH, Uri.encode(who_url)) + "&resolve=1"
val path = String.format(Locale.JAPAN, Column.PATH_SEARCH, who_url.encodePercent()) + "&resolve=1"
val result = client.request(path)
val jsonObject = result?.jsonObject
@ -219,7 +217,7 @@ object Action_User {
if(wl != null) {
activity.addColumn(pos, access_info, Column.TYPE_PROFILE, wl.id)
} else {
Utils.showToast(activity, true, result.error)
showToast(activity, true, result.error)
// 仕方ないのでchrome tab で開く
App1.openCustomTab(activity, who_url)
@ -336,14 +334,14 @@ object Action_User {
onReportComplete : TootApiResultCallback
) {
if(access_info.isMe(who)) {
Utils.showToast(activity, false, R.string.it_is_you)
showToast(activity, false, R.string.it_is_you)
return
}
TootTaskRunner(activity).run(access_info, object : TootTask {
override fun background(client : TootApiClient) : TootApiResult? {
val sb = ("account_id=" + who.id.toString()
+ "&comment=" + Uri.encode(comment)
+ "&comment=" + comment.encodePercent()
+ "&status_ids[]=" + status.id.toString())
val request_builder = Request.Builder().post(
@ -360,9 +358,9 @@ object Action_User {
if(result.jsonObject != null) {
onReportComplete(result)
Utils.showToast(activity, false, R.string.report_completed)
showToast(activity, false, R.string.report_completed)
} else {
Utils.showToast(activity, true, result.error)
showToast(activity, true, result.error)
}
}
})
@ -373,7 +371,7 @@ object Action_User {
activity : ActMain, access_info : SavedAccount, who : TootAccount, bShow : Boolean
) {
if(access_info.isMe(who)) {
Utils.showToast(activity, false, R.string.it_is_you)
showToast(activity, false, R.string.it_is_you)
return
}
@ -386,7 +384,7 @@ object Action_User {
try {
content.put("reblogs", bShow)
} catch(ex : Throwable) {
return TootApiResult(Utils.formatError(ex, "json encoding error"))
return TootApiResult(ex.withCaption("json encoding error"))
}
val request_builder = Request.Builder().post(
@ -408,9 +406,9 @@ object Action_User {
if(relation != null) {
saveUserRelation(access_info, relation)
Utils.showToast(activity, true, R.string.operation_succeeded)
showToast(activity, true, R.string.operation_succeeded)
} else {
Utils.showToast(activity, true, result.error)
showToast(activity, true, result.error)
}
}
})

View File

@ -2,7 +2,6 @@ package jp.juggler.subwaytooter.api
import android.content.Context
import android.content.SharedPreferences
import android.net.Uri
import org.json.JSONException
import org.json.JSONObject
@ -66,10 +65,9 @@ class TootApiClient(
private val reStartJsonObject = Pattern.compile("\\A\\s*\\{")
private val reWhiteSpace = Pattern.compile("\\s+")
private val mspTokenUrl = "http://mastodonsearch.jp/api/v1.0.1/utoken"
private val mspSearchUrl = "http://mastodonsearch.jp/api/v1.0.1/cross"
private val mspApiKey = "e53de7f66130208f62d1808672bf6320523dcd0873dc69bc"
private const val mspTokenUrl = "http://mastodonsearch.jp/api/v1.0.1/utoken"
private const val mspSearchUrl = "http://mastodonsearch.jp/api/v1.0.1/cross"
private const val mspApiKey = "e53de7f66130208f62d1808672bf6320523dcd0873dc69bc"
fun getMspMaxId(array : JSONArray, max_id : String) : String {
// max_id の更新
@ -92,7 +90,7 @@ class TootApiClient(
// returns the number for "from" parameter of next page.
// returns "" if no more next page.
fun getTootsearchMaxId(root : JSONObject, old : String) : String {
val old_from = Utils.parse_int(old, 0)
val old_from = old.optInt() ?: 0
val hits2 = getTootsearchHits(root)
if(hits2 != null) {
val size = hits2.length()
@ -101,9 +99,7 @@ class TootApiClient(
return ""
}
val DEFAULT_JSON_ERROR_PARSER = { json : JSONObject ->
Utils.optStringX(json, "error")
}
val DEFAULT_JSON_ERROR_PARSER = { json : JSONObject -> json.parseString("error") }
internal fun simplifyErrorHtml(
response : Response,
@ -113,8 +109,7 @@ class TootApiClient(
// JSONObjectとして解釈できるならエラーメッセージを検出する
try {
val data = JSONObject(sv)
val error_message = jsonErrorParser(data)
val error_message = jsonErrorParser(sv.toJsonObject())
if(error_message?.isNotEmpty() == true) {
return error_message
}
@ -221,7 +216,12 @@ class TootApiClient(
null == result.error
} catch(ex : Throwable) {
result.setError(result.caption + ": " + Utils.formatError(ex, context.resources, R.string.network_error))
result.setError(
"${result.caption}: ${ex.withCaption(
context.resources,
R.string.network_error
)}"
)
false
}
}
@ -240,7 +240,13 @@ class TootApiClient(
val request = response.request()
if(request != null) {
publishApiProgress(context.getString(R.string.reading_api, request.method(), progressPath ?: result.caption))
publishApiProgress(
context.getString(
R.string.reading_api,
request.method(),
progressPath ?: result.caption
)
)
}
val bodyString = response.body()?.string()
@ -281,7 +287,8 @@ class TootApiClient(
} catch(ex : Throwable) {
log.trace(ex)
result.error = formatResponse(response, result.caption, result.bodyString ?: NO_INFORMATION)
result.error =
formatResponse(response, result.caption, result.bodyString ?: NO_INFORMATION)
}
return result
}
@ -300,10 +307,10 @@ class TootApiClient(
?: return if(isApiCancelled) null else result
if(reStartJsonArray.matcher(bodyString).find()) {
result.data = JSONArray(bodyString)
result.data = bodyString.toJsonArray()
} else if(reStartJsonObject.matcher(bodyString).find()) {
val json = JSONObject(bodyString)
val json = bodyString.toJsonObject()
val error_message = jsonErrorParser(json)
if(error_message != null) {
result.error = error_message
@ -316,7 +323,8 @@ class TootApiClient(
} catch(ex : Throwable) {
log.trace(ex)
result.error = formatResponse(response, result.caption, result.bodyString ?: NO_INFORMATION)
result.error =
formatResponse(response, result.caption, result.bodyString ?: NO_INFORMATION)
}
return result
@ -324,7 +332,10 @@ class TootApiClient(
//////////////////////////////////////////////////////////////////////
fun request(path : String, request_builder : Request.Builder = Request.Builder()) : TootApiResult? {
fun request(
path : String,
request_builder : Request.Builder = Request.Builder()
) : TootApiResult? {
val result = TootApiResult.makeWithCaption(instance)
if(result.error != null) return result
@ -332,19 +343,19 @@ class TootApiClient(
try {
if(! sendRequest(result) {
log.d("request: $path")
request_builder.url("https://" + instance + path)
val access_token = account.getAccessToken()
if(access_token?.isNotEmpty() == true) {
request_builder.header("Authorization", "Bearer " + access_token)
}
request_builder.build()
}) return result
log.d("request: $path")
request_builder.url("https://" + instance + path)
val access_token = account.getAccessToken()
if(access_token?.isNotEmpty() == true) {
request_builder.header("Authorization", "Bearer " + access_token)
}
request_builder.build()
}) return result
return parseJson(result)
} finally {
@ -358,8 +369,8 @@ class TootApiClient(
val result = TootApiResult.makeWithCaption(instance)
if(result.error != null) return result
if(! sendRequest(result) {
Request.Builder().url("https://$instance/api/v1/instance").build()
}) return result
Request.Builder().url("https://$instance/api/v1/instance").build()
}) return result
return parseJson(result)
}
@ -371,14 +382,18 @@ class TootApiClient(
// OAuth2 クライアント登録
if(! sendRequest(result) {
Request.Builder()
.url("https://$instance/api/v1/apps")
.post(RequestBody.create(MEDIA_TYPE_FORM_URL_ENCODED, "client_name=" + Uri.encode(clientName)
+ "&redirect_uris=" + Uri.encode(REDIRECT_URL)
+ "&scopes=read write follow"
))
.build()
}) return result
Request.Builder()
.url("https://$instance/api/v1/apps")
.post(
RequestBody.create(
MEDIA_TYPE_FORM_URL_ENCODED,
"client_name=" + clientName.encodePercent()
+ "&redirect_uris=" + REDIRECT_URL.encodePercent()
+ "&scopes=read write follow"
)
)
.build()
}) return result
return parseJson(result)
}
@ -392,19 +407,30 @@ class TootApiClient(
if(result.error != null) return result
if(! sendRequest(result) {
Request.Builder()
.url("https://$instance/oauth/token")
.post(RequestBody.create(MEDIA_TYPE_FORM_URL_ENCODED, "grant_type=client_credentials"
+ "&client_id=" + Uri.encode(client_info.optString("client_id"))
+ "&client_secret=" + Uri.encode(client_info.optString("client_secret"))
))
.build()
}) return result
val client_id = client_info.parseString("client_id")
?: return result.setError("missing client_id")
val client_secret = client_info.parseString("client_secret")
?: return result.setError("missing client_secret")
Request.Builder()
.url("https://$instance/oauth/token")
.post(
RequestBody.create(
MEDIA_TYPE_FORM_URL_ENCODED,
"grant_type=client_credentials"
+ "&client_id=" + client_id.encodePercent()
+ "&client_secret=" + client_secret.encodePercent()
)
)
.build()
}) return result
val r2 = parseJson(result)
val jsonObject = r2?.jsonObject ?: return r2
val sv = Utils.optStringX(jsonObject, "access_token")
val sv = jsonObject.parseString("access_token")
if(sv?.isNotEmpty() == true) {
result.data = sv
} else {
@ -420,24 +446,24 @@ class TootApiClient(
if(result.error != null) return result
if(! sendRequest(result) {
Request.Builder()
.url("https://$instance/api/v1/apps/verify_credentials")
.header("Authorization", "Bearer $client_credential")
.build()
}) return result
Request.Builder()
.url("https://$instance/api/v1/apps/verify_credentials")
.header("Authorization", "Bearer $client_credential")
.build()
}) return result
return parseJson(result)
}
internal fun prepareBrowserUrl(client_info : JSONObject) : String {
// 認証ページURLを作る
internal fun prepareBrowserUrl(client_info : JSONObject) : String? {
val account = this.account
// 認証ページURLを作る
val client_id = client_info.parseString("client_id") ?: return null
return ("https://" + instance + "/oauth/authorize"
+ "?client_id=" + Uri.encode(Utils.optStringX(client_info, "client_id"))
+ "?client_id=" + client_id.encodePercent()
+ "&response_type=code"
+ "&redirect_uri=" + Uri.encode(REDIRECT_URL)
+ "&redirect_uri=" + REDIRECT_URL.encodePercent()
+ "&scope=read+write+follow"
+ "&scopes=read+write+follow"
+ "&state=" + (if(account != null) "db:" + account.db_id else "host:" + instance)
@ -458,7 +484,7 @@ class TootApiClient(
val client_info = ClientInfo.load(instance, client_name)
if(client_info != null) {
var client_credential = Utils.optStringX(client_info, KEY_CLIENT_CREDENTIAL)
var client_credential = client_info.parseString(KEY_CLIENT_CREDENTIAL)
// client_credential をまだ取得していないなら取得する
if(client_credential?.isEmpty() != false) {
@ -501,30 +527,36 @@ class TootApiClient(
val instance = result.caption // same to instance
val client_name = if(clientNameArg.isNotEmpty()) clientNameArg else DEFAULT_CLIENT_NAME
val client_info = ClientInfo.load(instance, client_name) ?: return result.setError("missing client id")
val client_info =
ClientInfo.load(instance, client_name) ?: return result.setError("missing client id")
if(! sendRequest(result) {
val post_content = ("grant_type=authorization_code"
+ "&code=" + Uri.encode(code)
+ "&client_id=" + Uri.encode(Utils.optStringX(client_info, "client_id"))
+ "&redirect_uri=" + Uri.encode(REDIRECT_URL)
+ "&client_secret=" + Uri.encode(Utils.optStringX(client_info, "client_secret"))
+ "&scope=read+write+follow"
+ "&scopes=read+write+follow")
Request.Builder()
.url("https://$instance/oauth/token")
.post(RequestBody.create(MEDIA_TYPE_FORM_URL_ENCODED, post_content))
.build()
}) return result
val client_id = client_info.parseString("client_id")
val client_secret = client_info.parseString("client_secret")
if(client_id == null) return result.setError("missing client_id ")
if(client_secret == null) return result.setError("missing client_secret")
val post_content = ("grant_type=authorization_code"
+ "&code=" + code.encodePercent()
+ "&client_id=" + client_id.encodePercent()
+ "&redirect_uri=" + REDIRECT_URL.encodePercent()
+ "&client_secret=" + client_secret.encodePercent()
+ "&scope=read+write+follow"
+ "&scopes=read+write+follow")
Request.Builder()
.url("https://$instance/oauth/token")
.post(RequestBody.create(MEDIA_TYPE_FORM_URL_ENCODED, post_content))
.build()
}) return result
val r2 = parseJson(result)
val token_info = r2?.jsonObject ?: return r2
// {"access_token":"******","token_type":"bearer","scope":"read","created_at":1492334641}
val access_token = Utils.optStringX(token_info, "access_token")
val access_token = token_info.parseString("access_token")
if(access_token?.isEmpty() != false) {
return result.setError("missing access_token in the response.")
}
@ -543,11 +575,11 @@ class TootApiClient(
// 認証されたアカウントのユーザ情報を取得する
if(! sendRequest(result) {
Request.Builder()
.url("https://$instance/api/v1/accounts/verify_credentials")
.header("Authorization", "Bearer $access_token")
.build()
}) return result
Request.Builder()
.url("https://$instance/api/v1/accounts/verify_credentials")
.header("Authorization", "Bearer $access_token")
.build()
}) return result
val r2 = parseJson(result)
if(r2?.jsonObject != null) {
@ -563,13 +595,13 @@ class TootApiClient(
fun searchMsp(query : String, max_id : String) : TootApiResult? {
// ユーザトークンを読む
var user_token :String? = Pref.spMspUserToken(pref)
var user_token : String? = Pref.spMspUserToken(pref)
for(nTry in 0 until 3) {
if(callback.isApiCancelled) return null
// ユーザトークンがなければ取得する
if( user_token == null || user_token.isEmpty() ){
if(user_token == null || user_token.isEmpty()) {
callback.publishApiProgress("get MSP user token...")
@ -577,17 +609,17 @@ class TootApiClient(
if(result.error != null) return result
if(! sendRequest(result) {
Request.Builder()
.url(mspTokenUrl + "?apikey=" + Uri.encode(mspApiKey))
.build()
}) return result
Request.Builder()
.url(mspTokenUrl + "?apikey=" + mspApiKey.encodePercent())
.build()
}) return result
val r2 = parseJson(result) { json ->
val error = Utils.optStringX(json, "error")
val error = json.parseString("error")
if(error == null) {
null
} else {
val type = Utils.optStringX(json, "type")
val type = json.parseString("type")
"error: $type $error"
}
}
@ -595,8 +627,8 @@ class TootApiClient(
user_token = jsonObject.optJSONObject("result")?.optString("token")
if(user_token?.isEmpty() != false) {
return result.setError("Can't get MSP user token. response=${result.bodyString}")
}else{
pref.edit().put( Pref.spMspUserToken,user_token).apply()
} else {
pref.edit().put(Pref.spMspUserToken, user_token).apply()
}
}
@ -606,18 +638,18 @@ class TootApiClient(
if(result.error != null) return result
if(! sendRequest(result) {
val url = (mspSearchUrl
+ "?apikey=" + Uri.encode(mspApiKey)
+ "&utoken=" + Uri.encode(user_token)
+ "&q=" + Uri.encode(query)
+ "&max=" + Uri.encode(max_id))
Request.Builder().url(url).build()
}) return result
val url = (mspSearchUrl
+ "?apikey=" + mspApiKey.encodePercent()
+ "&utoken=" + user_token.encodePercent()
+ "&q=" + query.encodePercent()
+ "&max=" + max_id.encodePercent())
Request.Builder().url(url).build()
}) return result
var isUserTokenError = false
val r2 = parseJson(result) { json ->
val error = Utils.optStringX(json, "error")
val error = json.parseString("error")
if(error == null) {
null
} else {
@ -627,7 +659,7 @@ class TootApiClient(
isUserTokenError = true
}
val type = Utils.optStringX(json, "type")
val type = json.parseString("type")
"API returns error: $type $error"
}
}
@ -645,16 +677,16 @@ class TootApiClient(
if(result.error != null) return result
if(! sendRequest(result) {
val url = ("https://tootsearch.chotto.moe/api/v1/search"
+ "?sort=" + Uri.encode("created_at:desc")
+ "&from=" + max_id
+ "&q=" + Uri.encode(query))
Request.Builder()
.url(url)
.build()
}) return result
val url = ("https://tootsearch.chotto.moe/api/v1/search"
+ "?sort=" + "created_at:desc".encodePercent()
+ "&from=" + max_id.encodePercent()
+ "&q=" + query.encodePercent())
Request.Builder()
.url(url)
.build()
}) return result
return parseJson(result)
}
@ -668,13 +700,13 @@ class TootApiClient(
if(result.error != null) return result
if(! sendRequest(result, progressPath = url) {
Request.Builder().url(url).build()
}) return result
Request.Builder().url(url).build()
}) return result
return parseString(result)
}
fun webSocket(path : String, ws_listener : WebSocketListener) : TootApiResult? {
fun webSocket(path : String, ws_listener : WebSocketListener) : TootApiResult? {
val result = TootApiResult.makeWithCaption(instance)
if(result.error != null) return result
val account = this.account ?: return TootApiResult("account is null")
@ -682,11 +714,11 @@ class TootApiClient(
var url = "wss://$instance$path"
val request_builder = Request.Builder()
val access_token = account.getAccessToken()
if(access_token?.isNotEmpty() == true) {
val delm = if(- 1 != url.indexOf('?')) '&' else '?'
url = url + delm + "access_token=" + Uri.encode(access_token)
url = url + delm + "access_token=" + access_token.encodePercent()
}
val request = request_builder.url(url).build()
@ -699,7 +731,8 @@ class TootApiClient(
result.data = ws
} catch(ex : Throwable) {
log.trace(ex)
result.error = result.caption + ": " + Utils.formatError(ex, context.resources, R.string.network_error)
result.error =
"${result.caption}: ${ex.withCaption(context.resources, R.string.network_error)}"
}
return result

View File

@ -1,7 +1,7 @@
package jp.juggler.subwaytooter.api.entity
import org.json.JSONObject
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.parseString
class CustomEmoji(
val shortcode : String, // shortcode (コロンを含まない)
@ -12,7 +12,7 @@ class CustomEmoji(
constructor(src : JSONObject) : this(
shortcode = src.notEmptyOrThrow("shortcode"),
url = src.notEmptyOrThrow("url"),
static_url = Utils.optStringX(src, "static_url")
static_url = src.parseString("static_url")
)
override val mapKey : String

View File

@ -2,7 +2,7 @@ package jp.juggler.subwaytooter.api.entity
import jp.juggler.subwaytooter.api.TootParser
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.parseString
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
@ -15,7 +15,11 @@ object EntityUtil {
////////////////////////////////////////
// JSONObjectを渡してEntityを生成するコードのnullチェックと例外補足
inline fun <reified T> parseItem(factory : (src : JSONObject) -> T, src : JSONObject?, log : LogCategory = EntityUtil.log) : T? {
inline fun <reified T> parseItem(
factory : (src : JSONObject) -> T,
src : JSONObject?,
log : LogCategory = EntityUtil.log
) : T? {
if(src == null) return null
return try {
factory(src)
@ -26,7 +30,11 @@ inline fun <reified T> parseItem(factory : (src : JSONObject) -> T, src : JSONOb
}
}
inline fun <reified T> parseList(factory : (src : JSONObject) -> T, src : JSONArray?, log : LogCategory = EntityUtil.log) : ArrayList<T> {
inline fun <reified T> parseList(
factory : (src : JSONObject) -> T,
src : JSONArray?,
log : LogCategory = EntityUtil.log
) : ArrayList<T> {
val dst = ArrayList<T>()
if(src != null) {
val src_length = src.length()
@ -41,7 +49,11 @@ inline fun <reified T> parseList(factory : (src : JSONObject) -> T, src : JSONAr
return dst
}
inline fun <reified T> parseListOrNull(factory : (src : JSONObject) -> T, src : JSONArray?, log : LogCategory = EntityUtil.log) : ArrayList<T>? {
inline fun <reified T> parseListOrNull(
factory : (src : JSONObject) -> T,
src : JSONArray?,
log : LogCategory = EntityUtil.log
) : ArrayList<T>? {
if(src != null) {
val src_length = src.length()
if(src_length > 0) {
@ -68,7 +80,7 @@ inline fun <reified K, reified V> parseMap(
val size = src.length()
for(i in 0 until size) {
val item = parseItem(factory, src.optJSONObject(i), log)
if(item != null) dst.put(item.mapKey, item)
if(item != null) dst[item.mapKey] = item
}
}
return dst
@ -85,7 +97,7 @@ inline fun <reified K, reified V> parseMapOrNull(
val dst = HashMap<K, V>()
for(i in 0 until size) {
val item = parseItem(factory, src.optJSONObject(i), log)
if(item != null) dst.put(item.mapKey, item)
if(item != null) dst[item.mapKey] = item
}
if(dst.isNotEmpty()) return dst
}
@ -95,7 +107,12 @@ inline fun <reified K, reified V> parseMapOrNull(
////////////////////////////////////////
inline fun <reified T> parseItem(factory : (parser : TootParser, src : JSONObject) -> T, parser : TootParser, src : JSONObject?, log : LogCategory = EntityUtil.log) : T? {
inline fun <reified T> parseItem(
factory : (parser : TootParser, src : JSONObject) -> T,
parser : TootParser,
src : JSONObject?,
log : LogCategory = EntityUtil.log
) : T? {
if(src == null) return null
return try {
factory(parser, src)
@ -106,7 +123,12 @@ inline fun <reified T> parseItem(factory : (parser : TootParser, src : JSONObjec
}
}
inline fun <reified T> parseList(factory : (parser : TootParser, src : JSONObject) -> T, parser : TootParser, src : JSONArray?, log : LogCategory = EntityUtil.log) : ArrayList<T> {
inline fun <reified T> parseList(
factory : (parser : TootParser, src : JSONObject) -> T,
parser : TootParser,
src : JSONArray?,
log : LogCategory = EntityUtil.log
) : ArrayList<T> {
val dst = ArrayList<T>()
if(src != null) {
val src_length = src.length()
@ -122,7 +144,12 @@ inline fun <reified T> parseList(factory : (parser : TootParser, src : JSONObjec
}
@Suppress("unused")
inline fun <reified T> parseListOrNull(factory : (parser : TootParser, src : JSONObject) -> T, parser : TootParser, src : JSONArray?, log : LogCategory = EntityUtil.log) : ArrayList<T>? {
inline fun <reified T> parseListOrNull(
factory : (parser : TootParser, src : JSONObject) -> T,
parser : TootParser,
src : JSONArray?,
log : LogCategory = EntityUtil.log
) : ArrayList<T>? {
if(src != null) {
val src_length = src.length()
if(src_length > 0) {
@ -140,10 +167,10 @@ inline fun <reified T> parseListOrNull(factory : (parser : TootParser, src : JSO
////////////////////////////////////////
fun <T: TootAttachmentLike> ArrayList<T>.encodeJson() : JSONArray {
fun <T : TootAttachmentLike> ArrayList<T>.encodeJson() : JSONArray {
val a = JSONArray()
for(ta in this) {
if( ta is TootAttachment) {
if(ta is TootAttachment) {
try {
a.put(ta.json)
} catch(ex : JSONException) {
@ -156,8 +183,7 @@ fun <T: TootAttachmentLike> ArrayList<T>.encodeJson() : JSONArray {
////////////////////////////////////////
fun notEmptyOrThrow(name : String, value : String?)
= if(value?.isNotEmpty() == true) value else throw RuntimeException("$name is empty")
fun notEmptyOrThrow(name : String, value : String?) =
if(value?.isNotEmpty() == true) value else throw RuntimeException("$name is empty")
fun JSONObject.notEmptyOrThrow(name : String)
= notEmptyOrThrow(name, Utils.optStringX(this, name))
fun JSONObject.notEmptyOrThrow(name : String) = notEmptyOrThrow(name, this.parseString(name))

View File

@ -8,9 +8,7 @@ import java.util.ArrayList
import java.util.regex.Pattern
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.DecodeOptions
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.*
@Suppress("MemberVisibilityCanPrivate")
class NicoEnquete(
@ -31,17 +29,17 @@ class NicoEnquete(
val items : ArrayList<Spannable>?
// 結果の数値 // null or array of number
val ratios : ArrayList<Float>?
private val ratios : ArrayList<Float>?
// 結果の数値のテキスト // null or array of string
val ratios_text : ArrayList<String>?
private val ratios_text : ArrayList<String>?
// 以下はJSONには存在しないが内部で使う
val time_start : Long
val status_id : Long
init {
this.type = Utils.optStringX(src, "type")
this.type = src.parseString( "type")
this.question = DecodeOptions(
short = true,
@ -50,7 +48,7 @@ class NicoEnquete(
linkTag = status,
emojiMapCustom = status.custom_emojis,
emojiMapProfile = status.profile_emojis
).decodeHTML(context, access_info, Utils.optStringX(src, "question") ?: "?")
).decodeHTML(context, access_info, src.parseString( "question") ?: "?")
this.items = parseChoiceList(context, status, parseStringArray(src, "items"))
@ -73,7 +71,7 @@ class NicoEnquete(
const val TYPE_ENQUETE_RESULT = "enquete_result"
@Suppress("HasPlatformType")
val reWhitespace = Pattern.compile("[\\s\\t\\x0d\\x0a]+")
private val reWhitespace = Pattern.compile("[\\s\\t\\x0d\\x0a]+")
fun parse(
context : Context,
@ -89,7 +87,7 @@ class NicoEnquete(
access_info,
status,
list_attachment,
JSONObject(jsonString)
jsonString.toJsonObject()
)
} catch(ex : Throwable) {
log.trace(ex)
@ -97,15 +95,10 @@ class NicoEnquete(
}
}
private fun parseStringArray(src : JSONObject, name : String) : ArrayList<String>? {
private fun parseStringArray(src : JSONObject, name : String) :ArrayList<String>?{
val array = src.optJSONArray(name)
if(array != null) {
val size = array.length()
val dst = ArrayList<String>(size)
for(i in 0 until size) {
val sv = Utils.optStringX(array, i)
if(sv != null) dst.add(sv)
}
val dst = array.toStringArrayList()
if(dst.isNotEmpty()) return dst
}
return null
@ -140,7 +133,7 @@ class NicoEnquete(
emojiMapProfile = status.profile_emojis
).decodeEmoji(context,
reWhitespace
.matcher(Utils.sanitizeBDI(stringArray[i]))
.matcher(stringArray[i].sanitizeBDI())
.replaceAll(" ")
)
)

View File

@ -2,7 +2,8 @@ package jp.juggler.subwaytooter.api.entity
import org.json.JSONObject
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.parseLong
import jp.juggler.subwaytooter.util.parseString
class NicoProfileEmoji(
val url : String,
@ -14,8 +15,8 @@ class NicoProfileEmoji(
constructor(src : JSONObject) : this(
url = src.notEmptyOrThrow("url"),
shortcode = src.notEmptyOrThrow("shortcode"),
account_url = Utils.optStringX(src, "account_url"),
account_id = Utils.optLongX(src, "account_id", TootAccount.INVALID_ID)
account_url = src.parseString("account_url"),
account_id = src.parseLong("account_id") ?: TootAccount.INVALID_ID
)
override val mapKey : String

View File

@ -3,8 +3,7 @@ package jp.juggler.subwaytooter.api.entity
import android.content.Context
import android.net.Uri
import android.text.Spannable
import jp.juggler.subwaytooter.util.DecodeOptions
import jp.juggler.subwaytooter.util.*
import org.json.JSONArray
import org.json.JSONObject
@ -12,16 +11,12 @@ import org.json.JSONObject
import java.util.ArrayList
import java.util.regex.Pattern
import jp.juggler.subwaytooter.util.LinkClickContext
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.Utils
open class TootAccount(
context : Context,
accessInfo : LinkClickContext,
src : JSONObject,
serviceType : ServiceType
) :TimelineItem(){
) : TimelineItem() {
//URL of the user's profile page (can be remote)
// https://mastodon.juggler.jp/@tateisu
@ -33,7 +28,7 @@ open class TootAccount(
// Equals username for local users, includes @domain for remote ones
val acct : String
// The username of the account /[A-Za-z0-9_]{1,30}/
val username : String
@ -82,27 +77,27 @@ open class TootAccount(
val source : Source?
val profile_emojis : HashMap<String,NicoProfileEmoji>?
val profile_emojis : HashMap<String, NicoProfileEmoji>?
val moved : TootAccount?
init {
var sv : String?
// 絵文字データは先に読んでおく
this.profile_emojis = parseMapOrNull(::NicoProfileEmoji,src.optJSONArray("profile_emojis"))
this.profile_emojis = parseMapOrNull(::NicoProfileEmoji, src.optJSONArray("profile_emojis"))
// 疑似アカウントにacctとusernameだけ
this.url = Utils.optStringX(src,"url")
this.username = src.notEmptyOrThrow( "username")
this.url = src.parseString("url")
this.username = src.notEmptyOrThrow("username")
//
sv = Utils.optStringX(src, "display_name")
this.display_name = if(sv?.isNotEmpty() == true) Utils.sanitizeBDI(sv) else username
sv = src.parseString("display_name")
this.display_name = if(sv?.isNotEmpty() == true) sv.sanitizeBDI() else username
this.decoded_display_name = decodeDisplayName(context)
//
this.note = Utils.optStringX(src, "note")
this.note = src.parseString("note")
this.decoded_note = DecodeOptions(
short = true,
decodeEmoji = true,
@ -110,7 +105,8 @@ open class TootAccount(
).decodeHTML(context, accessInfo, this.note)
this.source = parseSource(src.optJSONObject("source"))
this.moved = src.optJSONObject("moved")?.let { TootAccount(context, accessInfo, it, serviceType) }
this.moved =
src.optJSONObject("moved")?.let { TootAccount(context, accessInfo, it, serviceType) }
this.locked = src.optBoolean("locked")
when(serviceType) {
@ -118,53 +114,53 @@ open class TootAccount(
val hostAccess = accessInfo.host
this.id = Utils.optLongX(src, "id", INVALID_ID)
this.id = src.parseLong("id") ?: INVALID_ID
this.acct = src.notEmptyOrThrow("acct")
this.host = findHostFromUrl(acct,hostAccess,url)
this.host = findHostFromUrl(acct, hostAccess, url)
?: throw RuntimeException("can't find host from acct or url")
this.followers_count = Utils.optLongX(src, "followers_count")
this.following_count = Utils.optLongX(src, "following_count")
this.statuses_count = Utils.optLongX(src, "statuses_count")
this.followers_count = src.parseLong("followers_count")
this.following_count = src.parseLong("following_count")
this.statuses_count = src.parseLong("statuses_count")
this.created_at = Utils.optStringX(src, "created_at")
this.created_at = src.parseString("created_at")
this.time_created_at = TootStatus.parseTime(this.created_at)
this.avatar = Utils.optStringX(src, "avatar")
this.avatar_static = Utils.optStringX(src, "avatar_static")
this.header = Utils.optStringX(src, "header")
this.header_static = Utils.optStringX(src, "header_static")
this.avatar = src.parseString("avatar")
this.avatar_static = src.parseString("avatar_static")
this.header = src.parseString("header")
this.header_static = src.parseString("header_static")
}
ServiceType.TOOTSEARCH -> {
// tootsearch のアカウントのIDはどのタンス上のものか分からないので役に立たない
this.id = INVALID_ID // Utils.optLongX(src, "id", INVALID_ID)
this.id = INVALID_ID // src.parseLong( "id", INVALID_ID)
sv = src.notEmptyOrThrow("acct")
this.host = findHostFromUrl(sv,null,url)
this.host = findHostFromUrl(sv, null, url)
?: throw RuntimeException("can't find host from acct or url")
this.acct = this.username + "@" + this.host
this.followers_count = Utils.optLongX(src, "followers_count")
this.following_count = Utils.optLongX(src, "following_count")
this.statuses_count = Utils.optLongX(src, "statuses_count")
this.followers_count = src.parseLong("followers_count")
this.following_count = src.parseLong("following_count")
this.statuses_count = src.parseLong("statuses_count")
this.created_at = Utils.optStringX(src, "created_at")
this.created_at = src.parseString("created_at")
this.time_created_at = TootStatus.parseTime(this.created_at)
this.avatar = Utils.optStringX(src, "avatar")
this.avatar_static = Utils.optStringX(src, "avatar_static")
this.header = Utils.optStringX(src, "header")
this.header_static = Utils.optStringX(src, "header_static")
this.avatar = src.parseString("avatar")
this.avatar_static = src.parseString("avatar_static")
this.header = src.parseString("header")
this.header_static = src.parseString("header_static")
}
ServiceType.MSP -> {
this.id = Utils.optLongX(src, "id", INVALID_ID)
this.id = src.parseLong("id") ?: INVALID_ID
// MSPはLTLの情報しか持ってないのでacctは常にホスト名部分を持たない
this.host = findHostFromUrl(null,null,url)
?:throw RuntimeException("can't find host from url")
this.host = findHostFromUrl(null, null, url)
?: throw RuntimeException("can't find host from url")
this.acct = this.username + "@" + host
this.followers_count = null
@ -174,7 +170,7 @@ open class TootAccount(
this.created_at = null
this.time_created_at = 0L
val avatar = Utils.optStringX(src, "avatar")
val avatar = src.parseString("avatar")
this.avatar = avatar
this.avatar_static = avatar
this.header = null
@ -196,8 +192,8 @@ open class TootAccount(
val note : String?
init {
this.privacy = Utils.optStringX(src, "privacy")
this.note = Utils.optStringX(src, "note")
this.privacy = src.parseString("privacy")
this.note = src.parseString("note")
// nullになることがあるが、falseと同じ扱いでよい
this.sensitive = src.optBoolean("sensitive", false)
}
@ -225,7 +221,8 @@ open class TootAccount(
private val reWhitespace = Pattern.compile("[\\s\\t\\x0d\\x0a]+")
@Suppress("HasPlatformType")
val reAccountUrl = Pattern.compile("\\Ahttps://([A-Za-z0-9.-]+)/@([A-Za-z0-9_]+)(?:\\z|[?#])")
val reAccountUrl =
Pattern.compile("\\Ahttps://([A-Za-z0-9.-]+)/@([A-Za-z0-9_]+)(?:\\z|[?#])")
fun parse(
context : Context,
@ -254,7 +251,11 @@ open class TootAccount(
}
}
fun parseList(context : Context, account : LinkClickContext, array : JSONArray?) : ArrayList<TootAccount> {
fun parseList(
context : Context,
account : LinkClickContext,
array : JSONArray?
) : ArrayList<TootAccount> {
val result = ArrayList<TootAccount>()
if(array != null) {
val array_size = array.length()
@ -269,10 +270,10 @@ open class TootAccount(
}
// Tootsearch用。URLやUriを使ってアカウントのインスタンス名を調べる
fun findHostFromUrl(acct:String? ,accessHost:String?, url : String?) : String? {
fun findHostFromUrl(acct : String?, accessHost : String?, url : String?) : String? {
// acctから調べる
if( acct != null ) {
if(acct != null) {
val pos = acct.indexOf('@')
if(pos != - 1) {
val host = acct.substring(pos + 1)
@ -281,12 +282,12 @@ open class TootAccount(
}
// accessHostから調べる
if( accessHost != null ){
if(accessHost != null) {
return accessHost
}
// URLから調べる
if( url != null ) {
if(url != null) {
try {
// たぶんどんなURLでもauthorityの部分にホスト名が来るだろう(慢心)
val uri = Uri.parse(url)
@ -295,7 +296,7 @@ open class TootAccount(
log.e(ex, "findHostFromUrl: can't parse host from URL $url")
}
}
return null
}
}

View File

@ -2,14 +2,15 @@ package jp.juggler.subwaytooter.api.entity
import org.json.JSONObject
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.parseString
class TootApplication (
class TootApplication(
val name : String?,
@Suppress("unused") private val website : String?
){
constructor(src:JSONObject):this(
name = Utils.optStringX(src, "name"),
website = Utils.optStringX(src, "website")
) {
constructor(src : JSONObject) : this(
name = src.parseString("name"),
website = src.parseString("website")
)
}

View File

@ -5,12 +5,13 @@ import android.content.SharedPreferences
import org.json.JSONObject
import jp.juggler.subwaytooter.Pref
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.parseLong
import jp.juggler.subwaytooter.util.parseString
class TootAttachment(src : JSONObject) : TootAttachmentLike {
val json : JSONObject
// ID of the attachment
val id : Long
@ -39,13 +40,13 @@ class TootAttachment(src : JSONObject) : TootAttachmentLike {
init {
json = src
id = Utils.optLongX(src, "id")
type = Utils.optStringX(src, "type")
url = Utils.optStringX(src, "url")
remote_url = Utils.optStringX(src, "remote_url")
preview_url = Utils.optStringX(src, "preview_url")
text_url = Utils.optStringX(src, "text_url")
description = Utils.optStringX(src, "description")
id = src.parseLong("id") ?: - 1L
type = src.parseString("type")
url = src.parseString("url")
remote_url = src.parseString("remote_url")
preview_url = src.parseString("preview_url")
text_url = src.parseString("text_url")
description = src.parseString("description")
}
override val urlForThumbnail : String?
@ -57,20 +58,21 @@ class TootAttachment(src : JSONObject) : TootAttachmentLike {
}
fun getLargeUrl(pref : SharedPreferences) : String? {
return if( Pref.bpPriorLocalURL(pref) ){
if( url?.isNotEmpty() ==true) url else remote_url
return if(Pref.bpPriorLocalURL(pref)) {
if(url?.isNotEmpty() == true) url else remote_url
} else {
if( remote_url?.isNotEmpty() == true ) remote_url else url
if(remote_url?.isNotEmpty() == true) remote_url else url
}
}
fun getLargeUrlList(pref : SharedPreferences) : ArrayList<String> {
val result = ArrayList<String>()
if( Pref.bpPriorLocalURL(pref) ){
if( url?.isNotEmpty() ==true) result.add(url)
if( remote_url?.isNotEmpty()==true) result.add( remote_url)
if(Pref.bpPriorLocalURL(pref)) {
if(url?.isNotEmpty() == true) result.add(url)
if(remote_url?.isNotEmpty() == true) result.add(remote_url)
} else {
if( remote_url?.isNotEmpty()==true) result.add( remote_url)
if( url?.isNotEmpty() ==true) result.add(url)
if(remote_url?.isNotEmpty() == true) result.add(remote_url)
if(url?.isNotEmpty() == true) result.add(url)
}
return result
}

View File

@ -1,6 +1,6 @@
package jp.juggler.subwaytooter.api.entity
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.parseString
import org.json.JSONArray
class TootAttachmentMSP(
@ -27,7 +27,7 @@ class TootAttachmentMSP(
val result = ArrayList<TootAttachmentLike>()
result.ensureCapacity(array_size)
for(i in 0 until array_size) {
val sv = Utils.optStringX(array, i)
val sv = array.parseString( i)
if(sv != null && sv.isNotBlank()) {
result.add(TootAttachmentMSP(sv))
}

View File

@ -2,7 +2,7 @@ package jp.juggler.subwaytooter.api.entity
import org.json.JSONObject
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.parseString
class TootCard(
@ -26,16 +26,16 @@ class TootCard(
) {
constructor(src : JSONObject) : this(
url = Utils.optStringX(src, "url"),
title = Utils.optStringX(src, "title"),
description = Utils.optStringX(src, "description"),
image = Utils.optStringX(src, "image"),
url = src.parseString("url"),
title = src.parseString("title"),
description = src.parseString("description"),
image = src.parseString("image"),
type = Utils.optStringX(src, "type"),
author_name = Utils.optStringX(src, "author_name"),
author_url = Utils.optStringX(src, "author_url"),
provider_name = Utils.optStringX(src, "provider_name"),
provider_url = Utils.optStringX(src, "provider_url")
type = src.parseString("type"),
author_name = src.parseString("author_name"),
author_url = src.parseString("author_url"),
provider_name = src.parseString("provider_name"),
provider_url = src.parseString("provider_url")
)
}

View File

@ -2,8 +2,9 @@ package jp.juggler.subwaytooter.api.entity
import org.json.JSONObject
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.VersionString
import jp.juggler.subwaytooter.util.parseLong
import jp.juggler.subwaytooter.util.parseString
class TootInstance(src : JSONObject) {
@ -35,16 +36,15 @@ class TootInstance(src : JSONObject) {
// XXX: urls をパースしてない。使ってないから…
init {
this.uri = Utils.optStringX(src, "uri")
this.title = Utils.optStringX(src, "title")
this.description = Utils.optStringX(src, "description")
this.email = Utils.optStringX(src, "email")
this.version = Utils.optStringX(src, "version")
this.uri = src.parseString("uri")
this.title = src.parseString("title")
this.description = src.parseString("description")
this.email = src.parseString("email")
this.version = src.parseString("version")
this.decoded_version = VersionString(version)
this.stats = parseItem(::Stats,src.optJSONObject("stats"))
this.thumbnail = Utils.optStringX(src, "thumbnail")
this.stats = parseItem(::Stats, src.optJSONObject("stats"))
this.thumbnail = src.parseString("thumbnail")
}
class Stats(src : JSONObject) {
@ -53,9 +53,9 @@ class TootInstance(src : JSONObject) {
val domain_count : Long
init {
this.user_count = Utils.optLongX(src, "user_count", - 1L)
this.status_count = Utils.optLongX(src, "status_count", - 1L)
this.domain_count = Utils.optLongX(src, "domain_count", - 1L)
this.user_count = src.parseLong("user_count") ?: - 1L
this.status_count = src.parseLong("status_count") ?: - 1L
this.domain_count = src.parseLong("domain_count") ?: - 1L
}
}

View File

@ -7,7 +7,8 @@ import org.json.JSONObject
import java.util.ArrayList
import java.util.regex.Pattern
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.parseLong
import jp.juggler.subwaytooter.util.parseString
class TootList(
val id : Long,
@ -25,13 +26,13 @@ class TootList(
}
constructor(src : JSONObject) : this(
id = Utils.optLongX(src, "id"),
title = Utils.optStringX(src, "title")
id = src.parseLong("id") ?: - 1L,
title = src.parseString("title")
)
companion object {
private var log = LogCategory("TootList")
private val reNumber = Pattern.compile("(\\d+)")
private fun makeTitleForSort(title : String?) : ArrayList<Any> {
@ -62,12 +63,12 @@ class TootList(
}
return list
}
private fun compareLong(a:Long ,b:Long):Int{
private fun compareLong(a : Long, b : Long) : Int {
return a.compareTo(b)
}
private fun compareString(a:String ,b:String):Int{
private fun compareString(a : String, b : String) : Int {
return a.compareTo(b)
}
}
@ -94,7 +95,7 @@ class TootList(
if(ob == null) 0 else - 1
} else if(ob == null) {
1
}else {
} else {
when {
oa is Long && ob is Long -> compareLong(oa, ob)
@ -102,10 +103,11 @@ class TootList(
else -> (ob is Long).b2i() - (oa is Long).b2i()
}
}
log.d("%s %s %s"
,oa
,if(delta<0) "<" else if(delta>0)">" else "="
,ob
log.d(
"%s %s %s"
, oa
, if(delta < 0) "<" else if(delta > 0) ">" else "="
, ob
)
if(delta != 0) return delta
++ i

View File

@ -2,7 +2,7 @@ package jp.juggler.subwaytooter.api.entity
import org.json.JSONObject
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.parseLong
class TootMention(
val id : Long, // Account ID
@ -12,7 +12,7 @@ class TootMention(
) {
constructor(src : JSONObject) : this(
id = Utils.optLongX(src, "id"),
id = src.parseLong("id") ?: -1L,
url = src.notEmptyOrThrow("url"),
acct = src.notEmptyOrThrow("acct"),
username = src.notEmptyOrThrow("username")

View File

@ -3,7 +3,8 @@ package jp.juggler.subwaytooter.api.entity
import org.json.JSONObject
import jp.juggler.subwaytooter.api.TootParser
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.parseLong
import jp.juggler.subwaytooter.util.parseString
class TootNotification(
val json : JSONObject,
@ -12,7 +13,7 @@ class TootNotification(
private val created_at : String?, // The time the notification was created
val account : TootAccount?, // The Account sending the notification to the user
val status : TootStatus? // The Status associated with the notification, if applicable
) :TimelineItem(){
) : TimelineItem() {
val time_created_at : Long
@ -22,10 +23,15 @@ class TootNotification(
constructor(parser : TootParser, src : JSONObject) : this(
json = src,
id = Utils.optLongX(src, "id"),
id = src.parseLong("id") ?: - 1L,
type = src.notEmptyOrThrow("type"),
created_at = Utils.optStringX(src,"created_at"),
account = TootAccount.parse(parser.context, parser.accessInfo, src.optJSONObject("account"), ServiceType.MASTODON),
created_at = src.parseString("created_at"),
account = TootAccount.parse(
parser.context,
parser.accessInfo,
src.optJSONObject("account"),
ServiceType.MASTODON
),
status = TootStatus.parse(parser, src.optJSONObject("status"), ServiceType.MASTODON)
)

View File

@ -2,6 +2,7 @@ package jp.juggler.subwaytooter.api.entity
import jp.juggler.subwaytooter.api.TootParser
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.toJsonObject
import org.json.JSONObject
import java.util.regex.Pattern
@ -50,7 +51,7 @@ object TootPayload {
if(payload is String) {
if(payload[0] == '{') {
val src = JSONObject(payload)
val src = payload.toJsonObject()
return when(event) {
"update" ->
// 2017/8/24 18:37 mastodon.juggler.jpでここを通った

View File

@ -3,7 +3,7 @@ package jp.juggler.subwaytooter.api.entity
import org.json.JSONObject
import jp.juggler.subwaytooter.table.UserRelation
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.parseLong
class TootRelationShip(src : JSONObject) {
@ -32,7 +32,7 @@ class TootRelationShip(src : JSONObject) {
val showing_reblogs : Int
init {
this.id = Utils.optLongX(src, "id")
this.id = src.parseLong("id") ?: - 1L
var ov = src.opt("following")
if(ov is JSONObject) {

View File

@ -2,15 +2,16 @@ package jp.juggler.subwaytooter.api.entity
import org.json.JSONObject
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.parseLong
import jp.juggler.subwaytooter.util.parseString
class TootReport(
val id : Long,
private val action_taken : String? // The action taken in response to the report
) :TimelineItem() {
@Suppress("unused") private val action_taken : String? // The action taken in response to the report
) : TimelineItem() {
constructor(src : JSONObject) : this(
id = Utils.optLongX(src, "id"),
action_taken = Utils.optStringX(src, "action_taken")
id = src.parseLong("id") ?: - 1L,
action_taken = src.parseString("action_taken")
)
}

View File

@ -22,7 +22,8 @@ import java.text.SimpleDateFormat
import java.util.*
@Suppress("MemberVisibilityCanPrivate")
class TootStatus(parser : TootParser, src : JSONObject, serviceType : ServiceType) :TimelineItem(){
class TootStatus(parser : TootParser, src : JSONObject, serviceType : ServiceType) :
TimelineItem() {
val json : JSONObject
@ -71,7 +72,7 @@ class TootStatus(parser : TootParser, src : JSONObject, serviceType : ServiceTyp
val sensitive : Boolean
// The detected language for the status, if detected
val language : String?
private val language : String?
//If not empty, warning text that should be displayed before the actual content
val spoiler_text : String?
@ -89,13 +90,13 @@ class TootStatus(parser : TootParser, src : JSONObject, serviceType : ServiceTyp
val profile_emojis : HashMap<String, NicoProfileEmoji>?
// The time the status was created
val created_at : String?
private val created_at : String?
// null or the ID of the status it replies to
val in_reply_to_id : String?
// null or the ID of the account it replies to
val in_reply_to_account_id : String?
private val in_reply_to_account_id : String?
// null or the reblogged Status
val reblog : TootStatus?
@ -148,13 +149,14 @@ class TootStatus(parser : TootParser, src : JSONObject, serviceType : ServiceTyp
init {
this.json = src
this.uri = Utils.optStringX(src, "uri") // MSPだとuriは提供されない
this.url = Utils.optStringX(src, "url") // 頻繁にnullになる
this.created_at = Utils.optStringX(src, "created_at")
this.uri = src.parseString("uri") // MSPだとuriは提供されない
this.url = src.parseString("url") // 頻繁にnullになる
this.created_at = src.parseString("created_at")
// 絵文字マップはすぐ後で使うので、最初の方で読んでおく
this.custom_emojis = parseMapOrNull(::CustomEmoji, src.optJSONArray("emojis"), log)
this.profile_emojis = parseMapOrNull(::NicoProfileEmoji, src.optJSONArray("profile_emojis"), log)
this.profile_emojis =
parseMapOrNull(::NicoProfileEmoji, src.optJSONArray("profile_emojis"), log)
this.account = TootAccount.parse(
parser.context,
@ -167,16 +169,17 @@ class TootStatus(parser : TootParser, src : JSONObject, serviceType : ServiceTyp
ServiceType.MASTODON -> {
this.host_access = parser.accessInfo.host
this.id = Utils.optLongX(src, "id", INVALID_ID)
this.id = src.parseLong("id") ?: INVALID_ID
this.reblogs_count = Utils.optLongX(src, "reblogs_count")
this.favourites_count = Utils.optLongX(src, "favourites_count")
this.reblogs_count = src.parseLong("reblogs_count")
this.favourites_count = src.parseLong("favourites_count")
this.reblogged = src.optBoolean("reblogged")
this.favourited = src.optBoolean("favourited")
this.time_created_at = parseTime(this.created_at)
this.media_attachments = parseListOrNull(::TootAttachment, src.optJSONArray("media_attachments"), log)
this.visibility = Utils.optStringX(src, "visibility")
this.media_attachments =
parseListOrNull(::TootAttachment, src.optJSONArray("media_attachments"), log)
this.visibility = src.parseString("visibility")
this.sensitive = src.optBoolean("sensitive")
}
@ -188,11 +191,12 @@ class TootStatus(parser : TootParser, src : JSONObject, serviceType : ServiceTyp
this.id = findStatusIdFromUri(uri, url)
this.reblogs_count = Utils.optLongX(src, "reblogs_count")
this.favourites_count = Utils.optLongX(src, "favourites_count")
this.reblogs_count = src.parseLong("reblogs_count")
this.favourites_count = src.parseLong("favourites_count")
this.time_created_at = TootStatus.parseTime(this.created_at)
this.media_attachments = parseListOrNull(::TootAttachment, src.optJSONArray("media_attachments"), log)
this.media_attachments =
parseListOrNull(::TootAttachment, src.optJSONArray("media_attachments"), log)
this.visibility = VISIBILITY_PUBLIC
this.sensitive = src.optBoolean("sensitive")
@ -202,28 +206,33 @@ class TootStatus(parser : TootParser, src : JSONObject, serviceType : ServiceTyp
this.host_access = null
// MSPのデータはLTLから呼んだものなので、常に投稿元タンスでのidが得られる
this.id = Utils.optLongX(src, "id", INVALID_ID)
this.id = src.parseLong("id") ?: INVALID_ID
this.time_created_at = parseTimeMSP(created_at)
this.media_attachments = TootAttachmentMSP.parseList(src.optJSONArray("media_attachments"))
this.media_attachments =
TootAttachmentMSP.parseList(src.optJSONArray("media_attachments"))
this.visibility = VISIBILITY_PUBLIC
this.sensitive = src.optInt("sensitive", 0) != 0
}
}
this.in_reply_to_id = Utils.optStringX(src, "in_reply_to_id")
this.in_reply_to_account_id = Utils.optStringX(src, "in_reply_to_account_id")
this.in_reply_to_id = src.parseString("in_reply_to_id")
this.in_reply_to_account_id = src.parseString("in_reply_to_account_id")
this.mentions = parseListOrNull(::TootMention, src.optJSONArray("mentions"), log)
this.tags = parseListOrNull(::TootTag, src.optJSONArray("tags"))
this.application = parseItem(::TootApplication, src.optJSONObject("application"), log)
this.pinned = parser.pinned || src.optBoolean("pinned")
this.muted = src.optBoolean("muted")
this.language = Utils.optStringX(src, "language")
this.decoded_mentions = HTMLDecoder.decodeMentions(parser.accessInfo, this.mentions, this) ?: EMPTY_SPANNABLE
this.language = src.parseString("language")
this.decoded_mentions = HTMLDecoder.decodeMentions(
parser.accessInfo,
this.mentions,
this
) ?: EMPTY_SPANNABLE
// this.decoded_tags = HTMLDecoder.decodeTags( account,status.tags );
// content
this.content = Utils.optStringX(src, "content")
this.content = src.parseString("content")
var options = DecodeOptions(
short = true,
decodeEmoji = true,
@ -241,11 +250,10 @@ class TootStatus(parser : TootParser, src : JSONObject, serviceType : ServiceTyp
}
// spoiler_text
this.spoiler_text = Utils.sanitizeBDI(
reWhitespace
.matcher(Utils.optStringX(src, "spoiler_text") ?: "")
.replaceAll(" ")
)
this.spoiler_text = reWhitespace
.matcher(src.parseString("spoiler_text") ?: "")
.replaceAll(" ")
.sanitizeBDI()
options = DecodeOptions(
emojiMapCustom = custom_emojis,
@ -265,7 +273,7 @@ class TootStatus(parser : TootParser, src : JSONObject, serviceType : ServiceTyp
parser.accessInfo,
this,
media_attachments,
Utils.optStringX(src, "enquete")
src.parseString("enquete")
)
// Pinned TL を取得した時にreblogが登場することはないので、reblogについてpinned 状態を気にする必要はない
@ -330,15 +338,19 @@ class TootStatus(parser : TootParser, src : JSONObject, serviceType : ServiceTyp
// OStatus
@Suppress("HasPlatformType")
val reTootUriOS = Pattern.compile("tag:([^,]*),[^:]*:objectId=(\\d+):objectType=Status", Pattern.CASE_INSENSITIVE)
private val reTootUriOS = Pattern.compile(
"tag:([^,]*),[^:]*:objectId=(\\d+):objectType=Status",
Pattern.CASE_INSENSITIVE
)
// ActivityPub 1
@Suppress("HasPlatformType")
val reTootUriAP1 = Pattern.compile("https?://([^/]+)/users/[A-Za-z0-9_]+/statuses/(\\d+)")
private val reTootUriAP1 =
Pattern.compile("https?://([^/]+)/users/[A-Za-z0-9_]+/statuses/(\\d+)")
// ActivityPub 2
@Suppress("HasPlatformType")
val reTootUriAP2 = Pattern.compile("https?://([^/]+)/@[A-Za-z0-9_]+/(\\d+)")
private val reTootUriAP2 = Pattern.compile("https?://([^/]+)/@[A-Za-z0-9_]+/(\\d+)")
const val INVALID_ID = - 1L
@ -411,9 +423,11 @@ class TootStatus(parser : TootParser, src : JSONObject, serviceType : ServiceTyp
private val tz_utc = TimeZone.getTimeZone("UTC")
private val reTime = Pattern.compile("\\A(\\d+)\\D+(\\d+)\\D+(\\d+)\\D+(\\d+)\\D+(\\d+)\\D+(\\d+)\\D+(\\d+)")
private val reTime =
Pattern.compile("\\A(\\d+)\\D+(\\d+)\\D+(\\d+)\\D+(\\d+)\\D+(\\d+)\\D+(\\d+)\\D+(\\d+)")
private val reMSPTime = Pattern.compile("\\A(\\d+)\\D+(\\d+)\\D+(\\d+)\\D+(\\d+)\\D+(\\d+)\\D+(\\d+)")
private val reMSPTime =
Pattern.compile("\\A(\\d+)\\D+(\\d+)\\D+(\\d+)\\D+(\\d+)\\D+(\\d+)\\D+(\\d+)")
fun parseTime(strTime : String?) : Long {
if(strTime != null && strTime.isNotEmpty()) {
@ -424,14 +438,14 @@ class TootStatus(parser : TootParser, src : JSONObject, serviceType : ServiceTyp
} else {
val g = GregorianCalendar(tz_utc)
g.set(
Utils.parse_int(m.group(1), 1),
Utils.parse_int(m.group(2), 1) - 1,
Utils.parse_int(m.group(3), 1),
Utils.parse_int(m.group(4), 0),
Utils.parse_int(m.group(5), 0),
Utils.parse_int(m.group(6), 0)
m.group(1).optInt() ?: 1,
(m.group(2).optInt() ?: 1) - 1,
m.group(3).optInt() ?: 1,
m.group(4).optInt() ?: 0,
m.group(5).optInt() ?: 0,
m.group(6).optInt() ?: 0
)
g.set(Calendar.MILLISECOND, Utils.parse_int(m.group(7), 0))
g.set(Calendar.MILLISECOND, m.group(7).optInt() ?: 0)
return g.timeInMillis
}
} catch(ex : Throwable) { // ParseException, ArrayIndexOutOfBoundsException
@ -452,12 +466,12 @@ class TootStatus(parser : TootParser, src : JSONObject, serviceType : ServiceTyp
} else {
val g = GregorianCalendar(tz_utc)
g.set(
Utils.parse_int(m.group(1), 1),
Utils.parse_int(m.group(2), 1) - 1,
Utils.parse_int(m.group(3), 1),
Utils.parse_int(m.group(4), 0),
Utils.parse_int(m.group(5), 0),
Utils.parse_int(m.group(6), 0)
m.group(1).optInt() ?: 1,
(m.group(2).optInt() ?: 1) - 1,
m.group(3).optInt() ?: 1,
m.group(4).optInt() ?: 0,
m.group(5).optInt() ?: 0,
m.group(6).optInt() ?: 0
)
g.set(Calendar.MILLISECOND, 500)
return g.timeInMillis
@ -484,22 +498,38 @@ class TootStatus(parser : TootParser, src : JSONObject, serviceType : ServiceTyp
delta < 60000L -> {
val v = (delta / 1000L).toInt()
return context.getString(if(v > 1) R.string.relative_time_second_2 else R.string.relative_time_second_1, v, sign)
return context.getString(
if(v > 1) R.string.relative_time_second_2 else R.string.relative_time_second_1,
v,
sign
)
}
delta < 3600000L -> {
val v = (delta / 60000L).toInt()
return context.getString(if(v > 1) R.string.relative_time_minute_2 else R.string.relative_time_minute_1, v, sign)
return context.getString(
if(v > 1) R.string.relative_time_minute_2 else R.string.relative_time_minute_1,
v,
sign
)
}
delta < 86400000L -> {
val v = (delta / 3600000L).toInt()
return context.getString(if(v > 1) R.string.relative_time_hour_2 else R.string.relative_time_hour_1, v, sign)
return context.getString(
if(v > 1) R.string.relative_time_hour_2 else R.string.relative_time_hour_1,
v,
sign
)
}
delta < 40 * 86400000L -> {
val v = (delta / 86400000L).toInt()
return context.getString(if(v > 1) R.string.relative_time_day_2 else R.string.relative_time_day_1, v, sign)
return context.getString(
if(v > 1) R.string.relative_time_day_2 else R.string.relative_time_day_1,
v,
sign
)
}
else -> {
@ -533,7 +563,10 @@ class TootStatus(parser : TootParser, src : JSONObject, serviceType : ServiceTyp
throw IndexOutOfBoundsException("visibility not in range")
}
fun isVisibilitySpoilRequired(current_visibility : String?, max_visibility : String?) : Boolean {
fun isVisibilitySpoilRequired(
current_visibility : String?,
max_visibility : String?
) : Boolean {
return try {
val cvi = parseVisibility(current_visibility)
val mvi = parseVisibility(max_visibility)

View File

@ -1,6 +1,6 @@
package jp.juggler.subwaytooter.api.entity
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.parseString
import org.json.JSONArray
import org.json.JSONObject
@ -14,7 +14,7 @@ class TootTag(
constructor(src : JSONObject):this(
name = src.notEmptyOrThrow("name"),
url = Utils.optStringX(src,"url")
url = src.parseString("url")
)
companion object {
@ -23,7 +23,7 @@ class TootTag(
val result = ArrayList<TootTag>()
if(array != null) {
for( i in 0 until array.length() ){
val sv = Utils.optStringX(array, i)
val sv = array.parseString( i)
if( sv?.isNotEmpty() == true ) {
result.add(TootTag(name = sv))
}

View File

@ -6,6 +6,8 @@ import android.support.v7.app.AppCompatActivity
import android.view.Gravity
import android.view.View
import android.widget.Button
import android.widget.LinearLayout
import android.widget.TextView
import java.util.ArrayList
import java.util.concurrent.atomic.AtomicBoolean
@ -13,12 +15,10 @@ import java.util.concurrent.atomic.AtomicBoolean
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.table.AcctColor
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.Utils
import android.widget.LinearLayout
import android.widget.TextView
import jp.juggler.subwaytooter.util.DialogInterfaceCallback
import jp.juggler.subwaytooter.util.SavedAccountCallback
import jp.juggler.subwaytooter.util.showToast
object AccountPicker {
@ -39,7 +39,7 @@ object AccountPicker {
}()
if(account_list.isEmpty()) {
Utils.showToast(activity, false, R.string.account_empty)
showToast(activity, false, R.string.account_empty)
return
} else if(! bAllowPseudo) {
val tmp_list = ArrayList<SavedAccount>()
@ -50,7 +50,7 @@ object AccountPicker {
account_list.clear()
account_list.addAll(tmp_list)
if(account_list.isEmpty()) {
Utils.showToast(activity, false, R.string.not_available_for_pseudo_account)
showToast(activity, false, R.string.not_available_for_pseudo_account)
return
}
}

View File

@ -17,14 +17,13 @@ import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.api.entity.TootStatus
import jp.juggler.subwaytooter.table.PostDraft
import jp.juggler.subwaytooter.util.JSONObjectCallback
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.showToast
import org.json.JSONObject
class DlgDraftPicker : AdapterView.OnItemClickListener, AdapterView.OnItemLongClickListener, DialogInterface.OnDismissListener {
private lateinit var activity : ActPost
private lateinit var callback : JSONObjectCallback
private lateinit var callback : (draft : JSONObject) -> Unit
private lateinit var lvDraft : ListView
private lateinit var adapter : MyAdapter
private lateinit var dialog : AlertDialog
@ -46,7 +45,7 @@ class DlgDraftPicker : AdapterView.OnItemClickListener, AdapterView.OnItemLongCl
val draft = getPostDraft(position)
if(draft != null) {
Utils.showToast(activity, false, R.string.draft_deleted)
showToast(activity, false, R.string.draft_deleted)
draft.delete()
reload()
return true
@ -65,7 +64,7 @@ class DlgDraftPicker : AdapterView.OnItemClickListener, AdapterView.OnItemLongCl
}
@SuppressLint("InflateParams")
fun open(_activity : ActPost, _callback : JSONObjectCallback) {
fun open(_activity : ActPost, _callback : (draft : JSONObject) -> Unit) {
this.activity = _activity
this.callback = _callback
@ -114,7 +113,7 @@ class DlgDraftPicker : AdapterView.OnItemClickListener, AdapterView.OnItemLongCl
if(cursor == null) {
// load failed.
Utils.showToast(activity, true, "failed to loading drafts.")
showToast(activity, true, "failed to loading drafts.")
} else {
this@DlgDraftPicker.cursor = cursor
colIdx = PostDraft.ColIdx(cursor)

View File

@ -2,7 +2,6 @@ package jp.juggler.subwaytooter.dialog
import android.annotation.SuppressLint
import android.app.Dialog
import android.net.Uri
import android.view.View
import android.view.ViewGroup
import android.view.Window
@ -34,7 +33,8 @@ import jp.juggler.subwaytooter.api.entity.parseList
import jp.juggler.subwaytooter.table.AcctColor
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.NetworkEmojiInvalidator
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.encodePercent
import jp.juggler.subwaytooter.util.showToast
import jp.juggler.subwaytooter.view.MyListView
import jp.juggler.subwaytooter.view.MyNetworkImageView
@ -186,7 +186,7 @@ class DlgListMember(
override fun background(client : TootApiClient) : TootApiResult? {
// リストに追加したいアカウントの自タンスでのアカウントIDを取得する
local_who = null
var result = client.request("/api/v1/search?resolve=true&q=" + Uri.encode(target_user_full_acct))
var result = client.request("/api/v1/search?resolve=true&q=" + target_user_full_acct.encodePercent())
val jsonObject = result?.jsonObject ?: return result
@ -237,7 +237,7 @@ class DlgListMember(
val error = result.error
if( error?.isNotEmpty() == true && result.response?.code() == 404 ) {
Utils.showToast(activity, true, result.error)
showToast(activity, true, result.error)
}
}
@ -260,14 +260,14 @@ class DlgListMember(
DlgTextInput.show(activity, activity.getString(R.string.list_create), null, object : DlgTextInput.Callback {
override fun onEmptyError() {
Utils.showToast(activity, false, R.string.list_name_empty)
showToast(activity, false, R.string.list_name_empty)
}
override fun onOK(dialog : Dialog, text : String) {
val list_owner = this@DlgListMember.list_owner
if(list_owner == null) {
Utils.showToast(activity, false, "list owner is not selected.")
showToast(activity, false, "list owner is not selected.")
return
}
@ -375,13 +375,13 @@ class DlgListMember(
val list_owner = this@DlgListMember.list_owner
if(list_owner == null) {
Utils.showToast(activity, false, "list owner is not selected")
showToast(activity, false, "list owner is not selected")
return
}
val local_who = this@DlgListMember.local_who
if(local_who == null) {
Utils.showToast(activity, false, "target user is not synchronized")
showToast(activity, false, "target user is not synchronized")
return
}

View File

@ -12,7 +12,7 @@ import jp.juggler.subwaytooter.ActMain
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.showToast
import net.glxn.qrgen.android.QRCode
@ -34,7 +34,7 @@ object DlgQRCode {
QRCode.from(url).withSize(size, size).bitmap()
} catch(ex : Throwable) {
log.trace(ex)
Utils.showToast(activity, ex, "makeQrCode failed.")
showToast(activity, ex, "makeQrCode failed.")
null
}
}

View File

@ -33,7 +33,7 @@ import jp.juggler.subwaytooter.view.NetworkEmojiView
class EmojiPicker(
private val activity : Activity,
private val instance : String?,
private val onEmojiPicked: (name : String,instance:String?,bInstanceHasCustomEmoji:Boolean)->Unit
private val onEmojiPicked : (name : String, instance : String?, bInstanceHasCustomEmoji : Boolean) -> Unit
// onEmojiPickedのinstance引数は通常の絵文字ならnull、カスタム絵文字なら非null、
) : View.OnClickListener {
@ -43,7 +43,7 @@ class EmojiPicker(
const val CATEGORY_RECENT = - 2
const val CATEGORY_CUSTOM = - 1
internal val tone_list = arrayOf(
SkinTone.create("_light_skin_tone", "_tone1"),
SkinTone.create("_medium_light_skin_tone", "_tone2"),
@ -54,15 +54,15 @@ class EmojiPicker(
}
private val viewRoot : View
private val pager_adapter : EmojiPickerPagerAdapter
private val page_list = ArrayList<EmojiPickerPage>()
private val pager : ViewPager
private val dialog : Dialog
private val pager_strip : PagerSlidingTabStrip
private val ibSkinTone : Array<ImageButton>
@ -74,15 +74,15 @@ class EmojiPicker(
private val custom_list = ArrayList<EmojiItem>()
private val emoji_url_map = HashMap<String, String>()
private val recent_page_idx : Int
private val custom_page_idx : Int
class SkinTone(val suffix_list : Array<out String>){
class SkinTone(val suffix_list : Array<out String>) {
companion object {
fun create( vararg suffix_list : String):SkinTone{
return SkinTone( suffix_list )
fun create(vararg suffix_list : String) : SkinTone {
return SkinTone(suffix_list)
}
}
}
@ -94,15 +94,15 @@ class EmojiPicker(
// recentをロードする
val pref = App1.pref
val sv = Pref.spEmojiPickerRecent(pref)
if( sv.isNotEmpty() ) {
if(sv.isNotEmpty()) {
try {
val array = JSONArray(sv)
for( i in 0 until array.length() ){
val array = sv.toJsonArray()
for(i in 0 until array.length()) {
val item = array.optJSONObject(i)
val c1 = Utils.optStringX(item, "name")
val c2 = Utils.optStringX(item, "instance")
if(c1 != null && c1.isNotEmpty() ) {
recent_list.add(EmojiItem(c1, c2))
val name = item.parseString("name")
if(name?.isNotEmpty() == true) {
val instance = item.parseString("instance")
recent_list.add(EmojiItem(name, instance))
}
}
} catch(ex : Throwable) {
@ -116,13 +116,43 @@ class EmojiPicker(
page_list.add(EmojiPickerPage(CATEGORY_RECENT, R.string.emoji_category_recent))
this.custom_page_idx = page_list.size
page_list.add(EmojiPickerPage(CATEGORY_CUSTOM, R.string.emoji_category_custom))
page_list.add(EmojiPickerPage(EmojiMap201709.CATEGORY_PEOPLE, R.string.emoji_category_people))
page_list.add(EmojiPickerPage(EmojiMap201709.CATEGORY_NATURE, R.string.emoji_category_nature))
page_list.add(
EmojiPickerPage(
EmojiMap201709.CATEGORY_PEOPLE,
R.string.emoji_category_people
)
)
page_list.add(
EmojiPickerPage(
EmojiMap201709.CATEGORY_NATURE,
R.string.emoji_category_nature
)
)
page_list.add(EmojiPickerPage(EmojiMap201709.CATEGORY_FOODS, R.string.emoji_category_foods))
page_list.add(EmojiPickerPage(EmojiMap201709.CATEGORY_ACTIVITY, R.string.emoji_category_activity))
page_list.add(EmojiPickerPage(EmojiMap201709.CATEGORY_PLACES, R.string.emoji_category_places))
page_list.add(EmojiPickerPage(EmojiMap201709.CATEGORY_OBJECTS, R.string.emoji_category_objects))
page_list.add(EmojiPickerPage(EmojiMap201709.CATEGORY_SYMBOLS, R.string.emoji_category_symbols))
page_list.add(
EmojiPickerPage(
EmojiMap201709.CATEGORY_ACTIVITY,
R.string.emoji_category_activity
)
)
page_list.add(
EmojiPickerPage(
EmojiMap201709.CATEGORY_PLACES,
R.string.emoji_category_places
)
)
page_list.add(
EmojiPickerPage(
EmojiMap201709.CATEGORY_OBJECTS,
R.string.emoji_category_objects
)
)
page_list.add(
EmojiPickerPage(
EmojiMap201709.CATEGORY_SYMBOLS,
R.string.emoji_category_symbols
)
)
page_list.add(EmojiPickerPage(EmojiMap201709.CATEGORY_FLAGS, R.string.emoji_category_flags))
this.viewRoot = activity.layoutInflater.inflate(R.layout.dlg_picker_emoji, null, false)
@ -143,9 +173,9 @@ class EmojiPicker(
pager_strip.setViewPager(pager)
// カスタム絵文字をロードする
if( instance!= null && instance.isNotEmpty() ) {
if(instance != null && instance.isNotEmpty()) {
setCustomEmojiList(
App1.custom_emoji_lister.getList(instance){
App1.custom_emoji_lister.getList(instance) {
setCustomEmojiList(it) // ロード完了時に呼ばれる
}
)
@ -159,8 +189,7 @@ class EmojiPicker(
w?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
}
var bInstanceHasCustomEmoji = false
private var bInstanceHasCustomEmoji = false
private fun setCustomEmojiList(list : ArrayList<CustomEmoji>?) {
if(list == null) return
@ -168,7 +197,7 @@ class EmojiPicker(
custom_list.clear()
for(emoji in list) {
custom_list.add(EmojiItem(emoji.shortcode, instance))
emoji_url_map.put(emoji.shortcode, emoji.url)
emoji_url_map[emoji.shortcode] = emoji.url
}
pager_adapter.getPageViewHolder(custom_page_idx)?.notifyDataSetChanged()
pager_adapter.getPageViewHolder(recent_page_idx)?.notifyDataSetChanged()
@ -224,7 +253,6 @@ class EmojiPicker(
showSkinTone()
}
internal inner class EmojiPickerPage(category_id : Int, title_id : Int) {
val title : String
val emoji_list : ArrayList<EmojiItem>
@ -252,7 +280,9 @@ class EmojiPicker(
}
inner class EmojiPickerPageViewHolder(activity : Activity, root : View) : BaseAdapter(), AdapterView.OnItemClickListener {
inner class EmojiPickerPageViewHolder(activity : Activity, root : View) : BaseAdapter(),
AdapterView.OnItemClickListener {
private val gridView : GridView
private val wh : Int
@ -331,18 +361,18 @@ class EmojiPicker(
}
override fun onItemClick(adapterView : AdapterView<*>, view : View, idx : Int, l : Long) {
val page = this.page ?: return
val item = page.emoji_list[idx]
val name = item.name
if( item.instance != null && item.instance.isNotEmpty() ) {
if(item.instance != null && item.instance.isNotEmpty()) {
// カスタム絵文字
selected(name, item.instance)
}else{
} else {
EmojiMap201709.sShortNameToImageId[name] ?: return
// 普通の絵文字
selected( if(selected_tone != 0) applySkinTone(name) else name, null)
selected(if(selected_tone != 0) applySkinTone(name) else name, null)
}
}
}
@ -355,9 +385,9 @@ class EmojiPicker(
val pref = App1.pref
val list = ArrayList<JSONObject>()
val sv = Pref.spEmojiPickerRecent(pref)
if( sv.isNotEmpty() ) {
if(sv.isNotEmpty()) {
try {
val array = JSONArray(sv)
val array = sv.toJsonArray()
var i = 0
val ie = array.length()
while(i < ie) {
@ -372,16 +402,16 @@ class EmojiPicker(
// 選択された絵文字と同じ項目を除去
// 項目が増えすぎたら減らす
run{
run {
val it = list.iterator()
var nCount = 0
while( it.hasNext()) {
while(it.hasNext()) {
val item = it.next()
if(name == Utils.optStringX(item, "name")
&& instance == Utils.optStringX(item, "instance")
) {
if(name == item.parseString( "name")
&& instance == item.parseString( "instance")
) {
it.remove()
}else if( ++nCount >= 256){
} else if(++ nCount >= 256) {
it.remove()
}
}
@ -407,7 +437,7 @@ class EmojiPicker(
}
onEmojiPicked(name,instance,bInstanceHasCustomEmoji)
onEmojiPicked(name, instance, bInstanceHasCustomEmoji)
}
internal inner class EmojiPickerPagerAdapter : PagerAdapter() {
@ -454,7 +484,7 @@ class EmojiPicker(
}
override fun destroyItem(container : ViewGroup, page_idx : Int, obj : Any) {
if( obj is View ){
if(obj is View) {
container.removeView(obj)
//
val holder = holder_list.get(page_idx)

View File

@ -19,7 +19,7 @@ import java.util.ArrayList
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.showToast
object LoginForm {
private val log = LogCategory("LoginForm")
@ -65,10 +65,10 @@ object LoginForm {
val instance = etInstance.text.toString().trim { it <= ' ' }
if( instance.isEmpty() ) {
Utils.showToast(activity, true, R.string.instance_not_specified)
showToast(activity, true, R.string.instance_not_specified)
return@OnClickListener
} else if(instance.contains("/") || instance.contains("@")) {
Utils.showToast(activity, true, R.string.instance_not_need_slash)
showToast(activity, true, R.string.instance_not_need_slash)
return@OnClickListener
}
onClickOk(dialog, instance, cbPseudoAccount.isChecked, cbInputAccessToken.isChecked)

View File

@ -11,8 +11,7 @@ import android.widget.TextView
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.api.entity.TootAccount
import jp.juggler.subwaytooter.api.entity.TootStatus
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.showToast
object ReportForm {
@ -37,7 +36,7 @@ object ReportForm {
view.findViewById<View>(R.id.btnOk).setOnClickListener(View.OnClickListener {
val comment = etComment.text.toString().trim { it <= ' ' }
if(comment.isEmpty()) {
Utils.showToast(activity, true, R.string.comment_empty)
showToast(activity, true, R.string.comment_empty)
return@OnClickListener
}

View File

@ -12,7 +12,7 @@ import jp.juggler.subwaytooter.util.LinkClickContext
typealias MyClickableSpanClickCallback = (widget : View, span : MyClickableSpan)->Unit
class MyClickableSpan(
val lcc : LinkClickContext,
// val lcc : LinkClickContext,
val text : String,
val url : String,
ac : AcctColor?,

View File

@ -6,12 +6,12 @@ import android.database.sqlite.SQLiteDatabase
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.Utils
import android.support.v4.util.LruCache
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.style.BackgroundColorSpan
import android.text.style.ForegroundColorSpan
import jp.juggler.subwaytooter.util.sanitizeBDI
import java.util.Locale
@ -23,7 +23,13 @@ class AcctColor {
var nickname : String? = null
var notification_sound : String? = null
constructor(acct : String, nickname : String, color_fg : Int, color_bg : Int, notification_sound : String? ) {
constructor(
acct : String,
nickname : String,
color_fg : Int,
color_bg : Int,
notification_sound : String?
) {
this.acct = acct
this.nickname = nickname
this.color_fg = color_fg
@ -46,7 +52,10 @@ class AcctColor {
cv.put(COL_COLOR_FG, color_fg)
cv.put(COL_COLOR_BG, color_bg)
cv.put(COL_NICKNAME, if(nickname == null) "" else nickname)
cv.put(COL_NOTIFICATION_SOUND, if(notification_sound == null) "" else notification_sound)
cv.put(
COL_NOTIFICATION_SOUND,
if(notification_sound == null) "" else notification_sound
)
App1.database.replace(table, null, cv)
mMemoryCache.remove(acct)
} catch(ex : Throwable) {
@ -80,7 +89,6 @@ class AcctColor {
private val mMemoryCache = LruCache<String, AcctColor>(2048)
fun onDBCreate(db : SQLiteDatabase) {
log.d("onDBCreate!")
db.execSQL(
@ -118,8 +126,6 @@ class AcctColor {
}
}
fun load(acctArg : String) : AcctColor {
val acct = acctArg.toLowerCase(Locale.ENGLISH)
val cached : AcctColor? = mMemoryCache.get(acct)
@ -129,10 +135,10 @@ class AcctColor {
val where_arg = load_where_arg.get()
where_arg[0] = acct
App1.database.query(table, null, load_where, where_arg, null, null, null)
.use{cursor->
.use { cursor ->
if(cursor.moveToNext()) {
var idx : Int
val ac = AcctColor(acct)
idx = cursor.getColumnIndex(COL_COLOR_FG)
@ -145,7 +151,8 @@ class AcctColor {
ac.nickname = if(cursor.isNull(idx)) null else cursor.getString(idx)
idx = cursor.getColumnIndex(COL_NOTIFICATION_SOUND)
ac.notification_sound = if(cursor.isNull(idx)) null else cursor.getString(idx)
ac.notification_sound =
if(cursor.isNull(idx)) null else cursor.getString(idx)
mMemoryCache.put(acct, ac)
return ac
@ -157,7 +164,12 @@ class AcctColor {
log.e(ex, "load failed.")
}
log.d("lruCache size=%s,hit=%s,miss=%s", mMemoryCache.size(), mMemoryCache.hitCount(), mMemoryCache.missCount())
log.d(
"lruCache size=%s,hit=%s,miss=%s",
mMemoryCache.size(),
mMemoryCache.hitCount(),
mMemoryCache.missCount()
)
val ac = AcctColor(acct)
mMemoryCache.put(acct, ac)
return ac
@ -166,13 +178,13 @@ class AcctColor {
fun getNickname(acct : String) : String {
val ac = load(acct)
val nickname = ac.nickname
return if( nickname != null && nickname.isNotEmpty() ) Utils.sanitizeBDI( nickname) else acct
return if(nickname != null && nickname.isNotEmpty()) nickname.sanitizeBDI() else acct
}
fun getNotificationSound(acct : String) : String? {
val ac = load(acct)
val notification_sound = ac.notification_sound
return if( notification_sound != null && notification_sound.isNotEmpty() ) notification_sound else null
return if(notification_sound != null && notification_sound.isNotEmpty()) notification_sound else null
}
fun hasNickname(ac : AcctColor?) : Boolean {
@ -192,20 +204,39 @@ class AcctColor {
mMemoryCache.evictAll()
}
fun getStringWithNickname(context : Context, string_id : Int, acct : String) : CharSequence {
fun getStringWithNickname(
context : Context,
string_id : Int,
acct : String
) : CharSequence {
val ac = load(acct)
val nickname = ac.nickname
val name = if(nickname ==null || nickname.isEmpty()) acct else Utils.sanitizeBDI(nickname)
val sb = SpannableStringBuilder(context.getString(string_id, String(charArrayOf(CHAR_REPLACE))))
val name = if(nickname == null || nickname.isEmpty()) acct else nickname.sanitizeBDI()
val sb = SpannableStringBuilder(
context.getString(
string_id,
String(charArrayOf(CHAR_REPLACE))
)
)
for(i in sb.length - 1 downTo 0) {
val c = sb[i]
if(c != CHAR_REPLACE) continue
sb.replace(i, i + 1, name)
if(ac.color_fg != 0) {
sb.setSpan(ForegroundColorSpan(ac.color_fg), i, i + name.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
sb.setSpan(
ForegroundColorSpan(ac.color_fg),
i,
i + name.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
if(ac.color_bg != 0) {
sb.setSpan(BackgroundColorSpan(ac.color_bg), i, i + name.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
sb.setSpan(
BackgroundColorSpan(ac.color_bg),
i,
i + name.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
}
return sb

View File

@ -7,6 +7,7 @@ import org.json.JSONObject
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.toJsonObject
object ClientInfo {
private val log = LogCategory("ClientInfo")
@ -41,9 +42,8 @@ object ClientInfo {
App1.database.query(table, null, "h=? and cn=?", arrayOf(instance, client_name), null, null, null)
.use { cursor ->
if(cursor.moveToFirst()) {
return JSONObject(cursor.getString(cursor.getColumnIndex(COL_RESULT)))
return cursor.getString(cursor.getColumnIndex(COL_RESULT)).toJsonObject()
}
}
} catch(ex : Throwable) {
log.e(ex, "load failed.")

View File

@ -4,14 +4,11 @@ import android.content.ContentValues
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import org.json.JSONException
import org.json.JSONObject
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.api.entity.notEmptyOrThrow
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.WordTrieTree
import jp.juggler.subwaytooter.util.*
class HighlightWord {
@ -120,12 +117,12 @@ class HighlightWord {
}
constructor(src : JSONObject) {
this.id = Utils.optLongX(src, COL_ID)
this.id = src.parseLong( COL_ID) ?: -1L
this.name = src.notEmptyOrThrow(COL_NAME)
this.color_bg = src.optInt(COL_COLOR_BG)
this.color_fg = src.optInt(COL_COLOR_FG)
this.sound_type = src.optInt(COL_SOUND_TYPE)
this.sound_uri = Utils.optStringX(src, COL_SOUND_URI)
this.sound_uri = src.parseString( COL_SOUND_URI)
}
constructor(name : String) {

View File

@ -16,8 +16,7 @@ object MutedApp {
const val table = "app_mute"
const val COL_ID = "_id"
const val COL_NAME = "name"
private val COL_TIME_SAVE = "time_save"
private const val COL_TIME_SAVE = "time_save"
fun onDBCreate(db : SQLiteDatabase) {
log.d("onDBCreate!")

View File

@ -12,7 +12,8 @@ import java.util.Collections
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.digestSHA256
import jp.juggler.subwaytooter.util.toJsonObject
class PostDraft {
@ -49,11 +50,11 @@ class PostDraft {
private val log = LogCategory("PostDraft")
private val table = "post_draft"
private val COL_ID = BaseColumns._ID
private val COL_TIME_SAVE = "time_save"
private val COL_JSON = "json"
private val COL_HASH = "hash"
private const val table = "post_draft"
private const val COL_ID = BaseColumns._ID
private const val COL_TIME_SAVE = "time_save"
private const val COL_JSON = "json"
private const val COL_HASH = "hash"
fun onDBCreate(db : SQLiteDatabase) {
log.d("onDBCreate!")
@ -110,7 +111,7 @@ class PostDraft {
sb.append("=")
sb.append(v)
}
val hash = Utils.digestSHA256(sb.toString())
val hash = sb.toString().digestSHA256()
// save to db
val cv = ContentValues()
@ -126,17 +127,13 @@ class PostDraft {
fun hasDraft() : Boolean {
try {
val cursor = App1.database.query(table, arrayOf("count(*)"), null, null, null, null, null)
if(cursor != null) {
try {
App1.database.query(table, arrayOf("count(*)"), null, null, null, null, null)
.use { cursor ->
if(cursor.moveToNext()) {
val count = cursor.getInt(0)
return count > 0
}
} finally {
cursor.close()
}
}
} catch(ex : Throwable) {
log.trace(ex)
log.e(ex, "hasDraft failed.")
@ -147,7 +144,15 @@ class PostDraft {
fun createCursor() : Cursor? {
try {
return App1.database.query(table, null, null, null, null, null, COL_TIME_SAVE + " desc")
return App1.database.query(
table,
null,
null,
null,
null,
null,
COL_TIME_SAVE + " desc"
)
} catch(ex : Throwable) {
log.trace(ex)
log.e(ex, "createCursor failed.")
@ -168,7 +173,7 @@ class PostDraft {
dst.id = cursor.getLong(colIdx.idx_id)
dst.time_save = cursor.getLong(colIdx.idx_time_save)
try {
dst.json = JSONObject(cursor.getString(colIdx.idx_json))
dst.json = cursor.getString(colIdx.idx_json).toJsonObject()
} catch(ex : Throwable) {
log.trace(ex)
dst.json = JSONObject()

View File

@ -19,7 +19,8 @@ import jp.juggler.subwaytooter.api.entity.TootAccount
import jp.juggler.subwaytooter.api.entity.TootInstance
import jp.juggler.subwaytooter.util.LinkClickContext
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.parseString
import jp.juggler.subwaytooter.util.toJsonObject
class SavedAccount(
val db_id : Long,
@ -92,7 +93,7 @@ class SavedAccount(
cursor.getString(cursor.getColumnIndex(COL_HOST)) // host
) {
val jsonAccount = JSONObject(cursor.getString(cursor.getColumnIndex(COL_ACCOUNT)))
val jsonAccount = cursor.getString(cursor.getColumnIndex(COL_ACCOUNT)).toJsonObject()
val loginAccount = TootAccount.parse(
context,
@ -133,7 +134,7 @@ class SavedAccount(
this.register_time = cursor.getLong(cursor.getColumnIndex(COL_REGISTER_TIME))
this.token_info = JSONObject(cursor.getString(cursor.getColumnIndex(COL_TOKEN)))
this.token_info = cursor.getString(cursor.getColumnIndex(COL_TOKEN)).toJsonObject()
this.sound_uri = cursor.getString(cursor.getColumnIndex(COL_SOUND_URI))
}
@ -561,7 +562,7 @@ class SavedAccount(
}
val REGISTER_KEY_UNREGISTERED = "unregistered"
const val REGISTER_KEY_UNREGISTERED = "unregistered"
fun clearRegistrationCache() {
val cv = ContentValues()
@ -735,7 +736,7 @@ class SavedAccount(
}
fun getAccessToken() : String? {
return token_info?.let { Utils.optStringX(it, "access_token") }
return token_info?.parseString("access_token")
}
}

View File

@ -0,0 +1,188 @@
package jp.juggler.subwaytooter.util
import android.content.Context
import android.graphics.*
import android.net.Uri
import it.sephiroth.android.library.exif2.ExifInterface
object BitmapUtils {
internal val log = LogCategory("BitmapUtils")
}
fun createResizedBitmap(
context : Context,
uri : Uri,
resizeToArg : Int,
skipIfNoNeedToResizeAndRotate : Boolean =false
) : Bitmap? {
var resize_to = resizeToArg
try {
// EXIF回転情報の取得
val orientation : Int? = context.contentResolver.openInputStream(uri)?.use { inStream ->
val exif = ExifInterface()
exif.readExif(
inStream,
ExifInterface.Options.OPTION_IFD_0 or ExifInterface.Options.OPTION_IFD_1 or ExifInterface.Options.OPTION_IFD_EXIF
)
exif.getTagIntValue(ExifInterface.TAG_ORIENTATION)
}
// 画像のサイズを調べる
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
options.inScaled = false
options.outWidth = 0
options.outHeight = 0
context.contentResolver.openInputStream(uri)?.use { inStream ->
BitmapFactory.decodeStream(inStream, null, options)
}
var src_width = options.outWidth
var src_height = options.outHeight
if(src_width <= 0 || src_height <= 0) {
showToast(context, false, "could not get image bounds.")
return null
}
// 長辺
val size = if(src_width > src_height) src_width else src_height
// リサイズも回転も必要がない場合
if(skipIfNoNeedToResizeAndRotate
&& (orientation == null || orientation == 1)
&& (resize_to <= 0 || size <= resize_to)) {
BitmapUtils.log.d("createOpener: no need to resize & rotate")
return null
}
if(size > resize_to) {
// 縮小が必要
} else {
// 縮小は不要
resize_to = size
}
// inSampleSizeを計算
var bits = 0
var x = size
while(x > resize_to * 2) {
++ bits
x = x shr 1
}
options.inJustDecodeBounds = false
options.inSampleSize = 1 shl bits
val sourceBitmap : Bitmap? =
context.contentResolver.openInputStream(uri)?.use { inStream ->
BitmapFactory.decodeStream(inStream, null, options)
}
if(sourceBitmap == null) {
showToast(context, false, "could not decode image.")
return null
}
try {
src_width = options.outWidth
src_height = options.outHeight
val scale : Float
var dst_width : Int
var dst_height : Int
if(src_width >= src_height) {
scale = resize_to / src_width.toFloat()
dst_width = resize_to
dst_height = (0.5f + src_height / src_width.toFloat() * resize_to).toInt()
if(dst_height < 1) dst_height = 1
} else {
scale = resize_to / src_height.toFloat()
dst_height = resize_to
dst_width = (0.5f + src_width / src_height.toFloat() * resize_to).toInt()
if(dst_width < 1) dst_width = 1
}
val matrix = Matrix()
matrix.reset()
// 画像の中心が原点に来るようにして
matrix.postTranslate(src_width * - 0.5f, src_height * - 0.5f)
// スケーリング
matrix.postScale(scale, scale)
// 回転情報があれば回転
if(orientation != null) {
val tmp : Int
when(orientation) {
2 -> matrix.postScale(1f, - 1f) // 上下反転
3 -> matrix.postRotate(180f) // 180度回転
4 -> matrix.postScale(- 1f, 1f) // 左右反転
5 -> {
tmp = dst_width
dst_width = dst_height
dst_height = tmp
matrix.postScale(1f, - 1f)
matrix.postRotate(- 90f)
}
6 -> {
tmp = dst_width
dst_width = dst_height
dst_height = tmp
matrix.postRotate(90f)
}
7 -> {
tmp = dst_width
dst_width = dst_height
dst_height = tmp
matrix.postScale(1f, - 1f)
matrix.postRotate(90f)
}
8 -> {
tmp = dst_width
dst_width = dst_height
dst_height = tmp
matrix.postRotate(- 90f)
}
else -> {
}
}
}
// 表示領域に埋まるように平行移動
matrix.postTranslate(dst_width * 0.5f, dst_height * 0.5f)
// 出力用Bitmap作成
var dst : Bitmap? =
Bitmap.createBitmap(dst_width, dst_height, Bitmap.Config.ARGB_8888)
try {
return if(dst == null) {
showToast(context, false, "bitmap creation failed.")
null
} else {
val canvas = Canvas(dst)
val paint = Paint()
paint.isFilterBitmap = true
canvas.drawBitmap(sourceBitmap, matrix, paint)
BitmapUtils.log.d("createResizedBitmap: resized to %sx%s", dst_width, dst_height)
val tmp = dst
dst = null
tmp
}
} finally {
dst?.recycle()
}
} finally {
sourceBitmap.recycle()
}
} catch(ex : SecurityException) {
BitmapUtils.log.e(ex, "maybe we need pick up image again.")
} catch(ex : Throwable) {
BitmapUtils.log.trace(ex)
}
return null
}

View File

@ -15,21 +15,6 @@ class ChromeTabOpener(
var allowIntercept :Boolean = true
) {
fun setAccessInfo(access_info : SavedAccount?) : ChromeTabOpener {
this.accessInfo = access_info
return this
}
fun allowIntercept( v:Boolean ): ChromeTabOpener{
this.allowIntercept = v;
return this
}
fun setTagList(tag_list : ArrayList<String>) : ChromeTabOpener {
this.tagList = tag_list
return this
}
fun open() {
activity.openChromeTab(this)
}

View File

@ -189,7 +189,7 @@ class CustomEmojiCache(internal val context : Context) {
var item : CacheItem? = cache[request.url]
if(item == null) {
item = CacheItem(request.url, frames)
cache.put(request.url, item)
cache[request.url] = item
} else {
item.frames?.dispose()
item.frames = frames

View File

@ -4,8 +4,6 @@ import android.content.Context
import android.os.Handler
import android.os.SystemClock
import org.json.JSONArray
import java.util.ArrayList
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentLinkedQueue
@ -159,7 +157,7 @@ class CustomEmojiLister(internal val context : Context) {
var item : CacheItem? = cache[request.instance]
if(item == null) {
item = CacheItem(request.instance, list)
cache.put(request.instance, item)
cache[request.instance] = item
} else {
item.list = list
item.time_update = now
@ -204,7 +202,7 @@ class CustomEmojiLister(internal val context : Context) {
private fun decodeEmojiList(data : String, instance : String) : ArrayList<CustomEmoji>? {
return try {
val list = parseList(::CustomEmoji, JSONArray(data))
val list = parseList(::CustomEmoji, data.toJsonArray() )
list.sortWith(compareBy(String.CASE_INSENSITIVE_ORDER, { it.shortcode }))
list
} catch(ex : Throwable) {

View File

@ -0,0 +1,30 @@
@file:Suppress("unused")
package jp.juggler.subwaytooter.util
import org.w3c.dom.Element
import org.w3c.dom.NamedNodeMap
import java.io.ByteArrayInputStream
import javax.xml.parsers.DocumentBuilder
import javax.xml.parsers.DocumentBuilderFactory
object DomXmlUtils {
val log = LogCategory("DomXmlUtils")
val xml_builder : DocumentBuilder by lazy {
DocumentBuilderFactory.newInstance().newDocumentBuilder()
}
}
fun ByteArray.parseXml() : Element? {
return try {
DomXmlUtils.xml_builder.parse(ByteArrayInputStream(this)).documentElement
} catch(ex : Throwable) {
DomXmlUtils.log.trace(ex)
null
}
}
fun NamedNodeMap.getAttribute(name : String, defVal : String?) : String? {
return this.getNamedItem(name)?.nodeValue ?: defVal
}

View File

@ -67,7 +67,7 @@ object HTMLDecoder {
private val reEntity = Pattern.compile("&(#?)(\\w+);")
private val entity_map = HashMap<String, Char>()
private fun _addEntity(s : String, c : Char) {
entity_map.put(s, c)
entity_map[s] = c
}
private fun chr(num : Int) : Char {

View File

@ -1,7 +1,6 @@
package jp.juggler.subwaytooter.util
import android.content.SharedPreferences
import android.net.Uri
import android.os.Handler
import android.support.v7.app.AlertDialog
import android.support.v7.app.AppCompatActivity
@ -83,14 +82,14 @@ class PostHelper(
var visibility = this.visibility ?: ""
if(content.isEmpty()) {
Utils.showToast(activity, true, R.string.post_error_contents_empty)
showToast(activity, true, R.string.post_error_contents_empty)
return
}
// nullはCWチェックなしを示す
// nullじゃなくてカラならエラー
if(spoiler_text != null && spoiler_text.isEmpty()) {
Utils.showToast(activity, true, R.string.post_error_contents_warning_empty)
showToast(activity, true, R.string.post_error_contents_warning_empty)
return
}
@ -105,19 +104,19 @@ class PostHelper(
val item = enquete_items[n]
if(item.isEmpty()) {
if(n < 2) {
Utils.showToast(activity, true, R.string.enquete_item_is_empty, n + 1)
showToast(activity, true, R.string.enquete_item_is_empty, n + 1)
return
}
} else {
val code_count = item.codePointCount(0, item.length)
if(code_count > 15) {
val over = code_count - 15
Utils.showToast(activity, true, R.string.enquete_item_too_long, n + 1, over)
showToast(activity, true, R.string.enquete_item_too_long, n + 1, over)
return
} else if(n > 0) {
for(i in 0 until n) {
if(item == enquete_items[i]) {
Utils.showToast(activity, true, R.string.enquete_item_duplicate, n + 1)
showToast(activity, true, R.string.enquete_item_duplicate, n + 1)
return
}
}
@ -239,11 +238,11 @@ class PostHelper(
val sb = StringBuilder()
sb.append("status=")
sb.append(Uri.encode(EmojiDecoder.decodeShortCode(content)))
sb.append(EmojiDecoder.decodeShortCode(content).encodePercent())
if(visibility_checked != null) {
sb.append("&visibility=")
sb.append(Uri.encode(visibility_checked))
sb.append(visibility_checked.encodePercent())
}
if(bNSFW) {
@ -252,7 +251,7 @@ class PostHelper(
if(spoiler_text?.isNotEmpty() == true) {
sb.append("&spoiler_text=")
sb.append(Uri.encode(EmojiDecoder.decodeShortCode(spoiler_text)))
sb.append(EmojiDecoder.decodeShortCode(spoiler_text).encodePercent())
}
if(in_reply_to_id != - 1L) {
@ -273,11 +272,10 @@ class PostHelper(
)
}
val request_builder = Request.Builder()
.post(request_body)
val digest = Utils.digestSHA256(body_string + account.acct)
val request_builder = Request.Builder().post(request_body)
if(digest != null && ! Pref.bpDontDuplicationCheck(pref) ) {
if( ! Pref.bpDontDuplicationCheck(pref) ) {
val digest = (body_string + account.acct).digestSHA256()
request_builder.header("Idempotency-Key", digest)
}
@ -316,7 +314,7 @@ class PostHelper(
// 連投してIdempotency が同じだった場合もエラーにはならず、ここを通る
callback(account, status)
} else {
Utils.showToast(activity, true, result.error)
showToast(activity, true, result.error)
}
}
@ -624,7 +622,7 @@ class PostHelper(
proc_text_changed.run()
// キーボードを再度表示する
Handler(activity.mainLooper).post { Utils.showKeyboard(activity, et) }
Handler(activity.mainLooper).post { et.showKeyboard() }
}.show()
}

View File

@ -47,7 +47,7 @@ class ProgressResponseBody private constructor(private val originalBody : Respon
@Suppress("MemberVisibilityCanPrivate")
@Throws(IOException::class)
fun bytes(body : ResponseBody, callback : ProgressResponseBodyCallback) : ByteArray {
private fun bytes(body : ResponseBody, callback : ProgressResponseBodyCallback) : ByteArray {
if(body is ProgressResponseBody) {
body.callback = callback
}

View File

@ -9,13 +9,13 @@ interface CurrentCallCallback {
fun onCallCreated(call : Call)
}
interface SimpleHttpClient{
interface SimpleHttpClient {
var currentCallCallback : CurrentCallCallback?
fun getResponse(request: Request) : Response
fun getWebSocket(request: Request, webSocketListener : WebSocketListener): WebSocket
fun getResponse(request : Request) : Response
fun getWebSocket(request : Request, webSocketListener : WebSocketListener) : WebSocket
}
class SimpleHttpClientImpl(val okHttpClient:OkHttpClient): SimpleHttpClient{
class SimpleHttpClientImpl(private val okHttpClient : OkHttpClient) : SimpleHttpClient {
override var currentCallCallback : CurrentCallCallback? = null
override fun getResponse(request : Request) : Response {
@ -24,8 +24,11 @@ class SimpleHttpClientImpl(val okHttpClient:OkHttpClient): SimpleHttpClient{
return call.execute()
}
override fun getWebSocket(request : Request, webSocketListener : WebSocketListener) : WebSocket {
return okHttpClient.newWebSocket(request,webSocketListener)
override fun getWebSocket(
request : Request,
webSocketListener : WebSocketListener
) : WebSocket {
return okHttpClient.newWebSocket(request, webSocketListener)
}
}

View File

@ -0,0 +1,159 @@
@file:Suppress("unused")
package jp.juggler.subwaytooter.util
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.os.Environment
import android.os.storage.StorageManager
import android.webkit.MimeTypeMap
import java.io.File
import java.util.ArrayList
import java.util.HashMap
object StorageUtils{
private val log = LogCategory("StorageUtils")
private const val PATH_TREE = "tree"
private const val PATH_DOCUMENT = "document"
internal class FileInfo(any_uri : String?) {
var uri : Uri? = null
private var mime_type : String? = null
init {
if(any_uri != null) {
uri = if(any_uri.startsWith("/")) {
Uri.fromFile(File(any_uri))
} else {
Uri.parse(any_uri)
}
val ext = MimeTypeMap.getFileExtensionFromUrl(any_uri)
if(ext != null) {
mime_type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext.toLowerCase())
}
}
}
}
private fun getSecondaryStorageVolumesMap(context : Context) : Map<String, String> {
val result = HashMap<String, String>()
try {
val sm = context.applicationContext.getSystemService(Context.STORAGE_SERVICE) as? StorageManager
if(sm == null) {
log.e("can't get StorageManager")
} else {
// SDカードスロットのある7.0端末が手元にないから検証できない
// if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ){
// for(StorageVolume volume : sm.getStorageVolumes() ){
// // String path = volume.getPath();
// String state = volume.getState();
//
// }
// }
val getVolumeList = sm.javaClass.getMethod("getVolumeList")
val volumes = getVolumeList.invoke(sm)
log.d("volumes type=%s", volumes.javaClass)
if(volumes is ArrayList<*>) {
//
for(volume in volumes) {
val volume_clazz = volume.javaClass
val path = volume_clazz.getMethod("getPath").invoke(volume) as? String
val state = volume_clazz.getMethod("getState").invoke(volume) as? String
if(path != null && state == "mounted") {
//
val isPrimary = volume_clazz.getMethod("isPrimary").invoke(volume) as? Boolean
if(isPrimary == true) result["primary"] = path
//
val uuid = volume_clazz.getMethod("getUuid").invoke(volume) as? String
if(uuid != null) result[uuid] = path
}
}
}
}
} catch(ex : Throwable) {
log.trace(ex)
}
return result
}
private fun isExternalStorageDocument(uri : Uri) : Boolean {
return "com.android.externalstorage.documents" == uri.authority
}
private fun getDocumentId(documentUri : Uri) : String {
val paths = documentUri.pathSegments
if(paths.size >= 2 && PATH_DOCUMENT == paths[0]) {
// document
return paths[1]
}
if(paths.size >= 4 && PATH_TREE == paths[0]
&& PATH_DOCUMENT == paths[2]) {
// document in tree
return paths[3]
}
if(paths.size >= 2 && PATH_TREE == paths[0]) {
// tree
return paths[1]
}
throw IllegalArgumentException("Invalid URI: " + documentUri)
}
fun getFile(context : Context, path : String) : File? {
try {
if(path.startsWith("/")) return File(path)
val uri = Uri.parse(path)
if("file" == uri.scheme) return File(uri.path)
// if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT )
run {
if(isExternalStorageDocument(uri)) {
try {
val docId = getDocumentId(uri)
val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
if(split.size >= 2) {
val uuid = split[0]
if("primary".equals(uuid, ignoreCase = true)) {
return File(Environment.getExternalStorageDirectory().toString() + "/" + split[1])
} else {
val volume_map = getSecondaryStorageVolumesMap(context)
val volume_path = volume_map[uuid]
if(volume_path != null) {
return File(volume_path + "/" + split[1])
}
}
}
} catch(ex : Throwable) {
log.trace(ex)
}
}
}
// MediaStore Uri
context.contentResolver.query(uri, null, null, null, null).use { cursor ->
if(cursor.moveToFirst()) {
val col_count = cursor.columnCount
for(i in 0 until col_count) {
val type = cursor.getType(i)
if(type != Cursor.FIELD_TYPE_STRING) continue
val name = cursor.getColumnName(i)
val value = if(cursor.isNull(i)) null else cursor.getString(i)
if(value != null && value.isNotEmpty() && "filePath" == name) return File(value)
}
}
}
} catch(ex : Throwable) {
log.trace(ex)
}
return null
}
}

View File

@ -19,7 +19,8 @@ class TaskList {
private lateinit var _list : LinkedList<JSONObject>
@Synchronized private fun prepareList(context : Context) : LinkedList<JSONObject> {
@Synchronized
private fun prepareList(context : Context) : LinkedList<JSONObject> {
if(! ::_list.isInitialized) {
_list = LinkedList()
@ -27,7 +28,7 @@ class TaskList {
context.openFileInput(FILE_TASK_LIST).use { inputStream ->
val bao = ByteArrayOutputStream()
IOUtils.copy(inputStream, bao)
val array = JSONArray(Utils.decodeUTF8(bao.toByteArray()))
val array = bao.toByteArray().decodeUTF8().toJsonArray()
var i = 0
val ie = array.length()
while(i < ie) {
@ -47,7 +48,8 @@ class TaskList {
return _list
}
@Synchronized private fun saveArray(context : Context) {
@Synchronized
private fun saveArray(context : Context) {
val list = prepareList(context)
try {
log.d("saveArray size=%s", list.size)
@ -55,10 +57,9 @@ class TaskList {
for(item in list) {
array.put(item)
}
val data = Utils.encodeUTF8(array.toString())
context.openFileOutput(FILE_TASK_LIST, Context.MODE_PRIVATE).use { outStream ->
IOUtils.write(data, outStream)
}
val data = array.toString().encodeUTF8()
context.openFileOutput(FILE_TASK_LIST, Context.MODE_PRIVATE)
.use { IOUtils.write(data, it) }
} catch(ex : Throwable) {
log.trace(ex)
log.e(ex, "TaskList: saveArray failed.size=%s", list.size)

View File

@ -5,7 +5,6 @@ import jp.juggler.subwaytooter.api.TootApiResult
import jp.juggler.subwaytooter.api.entity.TootAccount
import jp.juggler.subwaytooter.api.entity.TootStatus
import jp.juggler.subwaytooter.table.SavedAccount
import org.json.JSONObject
/////////////////////////////////////////////////////////////////
// callback (that returns Unit)
@ -20,13 +19,6 @@ typealias SavedAccountCallback = (ai : SavedAccount) -> Unit
typealias DialogInterfaceCallback = (dialog: DialogInterface) -> Unit
typealias JSONObjectCallback = (draft : JSONObject) -> Unit
typealias PostCompleteCallback = (target_account : SavedAccount, status : TootStatus) -> Unit
typealias ProgressResponseBodyCallback = (bytesRead : Long, bytesTotal : Long)->Unit
/////////////////////////////////////////////////////////////////
// checker (that returns not Unit)
typealias BooleanChecker = ()->Boolean

File diff suppressed because it is too large Load Diff

View File

@ -6,26 +6,27 @@ import android.support.annotation.NonNull;
// Kotlin wait/notify をサポートしてない
// しかしConcurrent ライブラリには notify() を直接表現できるクラスがない
// 仕方がないのでJavaコード経由でwait/notifyを呼び出す
@SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
public class WaitNotifyHelper {
public static void waitEx( @NonNull Object obj, long ms) {
try {
public static void waitEx( @NonNull Object obj, long ms ){
try{
synchronized( obj ){
obj.wait(ms);
obj.wait( ms );
}
} catch(InterruptedException ignored){
}catch( InterruptedException ignored ){
}
}
public static void notifyEx(@NonNull Object obj) {
try {
public static void notifyEx( @NonNull Object obj ){
try{
synchronized( obj ){
obj.notify();
}
} catch(Throwable ignored){
}catch( Throwable ignored ){
}
}
}

View File

@ -13,9 +13,10 @@ import android.view.VelocityTracker
import android.view.View
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.Utils
import jp.juggler.subwaytooter.util.runOnMainLooper
class PinchBitmapView(context : Context, attrs : AttributeSet?, defStyle : Int) : View(context, attrs, defStyle) {
class PinchBitmapView(context : Context, attrs : AttributeSet?, defStyle : Int) :
View(context, attrs, defStyle) {
companion object {
@ -125,6 +126,7 @@ class PinchBitmapView(context : Context, attrs : AttributeSet?, defStyle : Int)
// ページめくり操作のコールバック
interface Callback {
fun onSwipe(delta : Int)
fun onMove(bitmap_w : Float, bitmap_h : Float, tx : Float, ty : Float, scale : Float)
@ -319,15 +321,17 @@ class PinchBitmapView(context : Context, attrs : AttributeSet?, defStyle : Int)
} else if(image_move_x >= drag_width || image_move_y >= drag_width * 5f) {
// 「画像を動かした」かどうかの最終チェック
log.d("image was moved. not paging action. %f %f ", image_move_x / drag_width, image_move_y / drag_width
log.d(
"image was moved. not paging action. %f %f ",
image_move_x / drag_width,
image_move_y / drag_width
)
} else {
log.d("paging! %f %f %f", image_move_x / drag_width, image_move_y / drag_width, xv
log.d(
"paging! %f %f %f", image_move_x / drag_width, image_move_y / drag_width, xv
)
Utils.runOnMainThread {
callback?.onSwipe(if(xv >= 0f) - 1 else 1)
}
runOnMainLooper { callback?.onSwipe(if(xv >= 0f) - 1 else 1) }
}
}
}
@ -422,7 +426,11 @@ class PinchBitmapView(context : Context, attrs : AttributeSet?, defStyle : Int)
getCoordinateOnImage(avg_on_image1, pos.avg)
// ズーム率を変更する
current_scale = clip(scale_min, scale_max, start_image_scale * pos.max_radius / start_pos.max_radius)
current_scale = clip(
scale_min,
scale_max,
start_image_scale * pos.max_radius / start_pos.max_radius
)
// 再び調べる
getCoordinateOnImage(avg_on_image2, pos.avg)
@ -445,13 +453,14 @@ class PinchBitmapView(context : Context, attrs : AttributeSet?, defStyle : Int)
}
// 画像の表示位置を更新
current_trans_x = clipTranslate(view_w, bitmap_w, current_scale, start_image_trans_x + move_x)
current_trans_y = clipTranslate(view_h, bitmap_h, current_scale, start_image_trans_y + move_y)
current_trans_x =
clipTranslate(view_w, bitmap_w, current_scale, start_image_trans_x + move_x)
current_trans_y =
clipTranslate(view_h, bitmap_h, current_scale, start_image_trans_y + move_y)
}
callback?.onMove(bitmap_w, bitmap_h, current_trans_x, current_trans_y, current_scale)
invalidate()
}
}

View File

@ -115,7 +115,6 @@
android:id="@+id/ivBackground"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:scaleType="centerCrop"
/>

View File

@ -1,3 +1,9 @@
@file:Suppress(
"USELESS_CAST", "unused", "DEPRECATED_IDENTITY_EQUALS", "UNUSED_VARIABLE",
"UNUSED_VALUE", "ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE", "VARIABLE_WITH_REDUNDANT_INITIALIZER",
"ReplaceCallWithComparison"
)
package jp.juggler.subwaytooter
import android.view.View
@ -5,9 +11,10 @@ import org.junit.Test
import org.junit.Assert.*
import kotlin.concurrent.thread
//import kotlin.test.*
typealias TestLambdaCallback = (x:Int)->Int
typealias TestLambdaCallback = (x : Int) -> Int
class TestKotlinFeature {
@ -15,7 +22,7 @@ class TestKotlinFeature {
private val CODE_A2 = 2
@Test
fun WhenExpression() {
fun testWhenExpression() {
// ifを式として扱えるように、whenも式として扱える
run {
@ -113,8 +120,8 @@ class TestKotlinFeature {
assertEquals(true, 10 !in range2)
// ==,=== 演算子とプリミティブ型
val long10 : Long = 10L
val int10 : Int = 10
val long10 = 10L
val int10 = 10
val int10b : Int = generate10A()
val int10c : Int = generate10B()
@ -154,8 +161,8 @@ class TestKotlinFeature {
assertEquals(true, int10 as Any === generate10A() as Any)
// valueOfは-128..127は内部でキャッシュを行うから、値によっては同じアドレスだったり異なったりする
var intA : Int = 0
var intB : Int = 0
var intA = 0
var intB = 0
for(i in 126 .. 127) {
println("i=$i")
intA = i
@ -215,8 +222,8 @@ class TestKotlinFeature {
assertEquals(0, if(nullableNull != null) 1 else 0)
}
interface MyKotlinInterface{
fun method(x:Int) :Int
interface MyKotlinInterface {
fun method(x : Int) : Int
}
@Test
@ -224,74 +231,76 @@ class TestKotlinFeature {
// 定義例(文脈あり)
Thread({ println("SAM 1") }).start()
Thread{ println("SAM 2") }.start()
Thread { println("SAM 2") }.start()
// 定義例(文脈不明)
val a = Runnable({ println("SAM a") })
// 参照型の定義
val ref: Runnable = a
val ref : Runnable = a
// 参照型の呼び出し
ref.run()
// Nullableな参照型の定義
val refNullable : Runnable? = a
if( refNullable != null) {
if(refNullable != null) {
// 呼び出し
Thread(refNullable).start()
}
View.OnClickListener{ _ ->
View.OnClickListener { _ ->
println("clicked")
}.onClick( null)
}.onClick(null)
// kotlinで定義したインタフェースに対してSAMコンストラクタを使えるか
// ダメでした
// val ki = MyKotlinInterface{
// it * it
// }
// val ki = MyKotlinInterface{
// it * it
// }
}
@Test
fun testLambda() {
// 定義例(文脈あり)
thread(start=true){println("testLambda")}
println( 10.let{ x-> x*x})
10.let{ println(it)}
thread(start = true) { println("testLambda") }
println(10.let { x -> x * x })
10.let { println(it) }
// 定義例(文脈不明)
val a = { println("testLambda") }
// 参照型の定義
val ref: (x:Int)->Int = { it* it }
val ref : (x : Int) -> Int = { it * it }
// 参照型の呼び出し
println( ref(10))
println(ref(10))
// 参照型の定義(Nullable)
val refNullable : TestLambdaCallback? = { it * it }
if( refNullable != null ){
if(refNullable != null) {
refNullable(10)
}
}
@Test fun testAnonymousFunction(){
@Test
fun testAnonymousFunction() {
// 定義例(文脈あり)
thread( start=true,block=fun(){ println("testAnonymousFunction")})
println( 10.let(fun(x:Int)=x*x))
10.let{ println(it)}
thread(start = true, block = fun() { println("testAnonymousFunction") })
println(10.let(fun(x : Int) = x * x))
10.let { println(it) }
// 定義例(文脈不明)
val a = fun(x:Int)=x*x
val a = fun(x : Int) = x * x
// 参照型の定義
val ref: (x:Int)->Int = a
val ref : (x : Int) -> Int = a
// 参照型の呼び出し
println( ref(10))
println(ref(10))
// 参照型の定義(Nullable)
val refNullable : TestLambdaCallback? = fun(i:Int)=i*i
if( refNullable != null ){
val refNullable : TestLambdaCallback? = fun(i : Int) = i * i
if(refNullable != null) {
refNullable(10)
}
}
@ -300,102 +309,106 @@ class TestKotlinFeature {
fun testObjectExpression() {
// 定義例(文脈あり)
abstract class Base {
abstract fun method(x:Int) :Int
abstract fun method(x : Int) : Int
}
val a = object: Base() {
override fun method(x:Int) :Int{
return x*x
val a = object : Base() {
override fun method(x : Int) : Int {
return x * x
}
}
// 定義例(文脈の有無で変化しない)
// 参照型の定義
val ref: Base = a
val ref : Base = a
// 参照型の定義(Nullable)
val refNullable : Base? = a
if( refNullable != null ){
if(refNullable != null) {
val v = refNullable.method(10)
println("OE v=$v")
}
fun caller( b : Base){
fun caller(b : Base) {
val v = b.method(10)
println("OE b $v")
}
caller(object:Base(){
caller(object : Base() {
override fun method(x : Int) : Int {
return x* x* x
return x * x * x
}
})
}
private fun member(x :Int ) = x * x
private fun member(x : Int) = x * x
@Test
fun testMemberReference() {
fun caller( a :( receiver:TestKotlinFeature, x:Int)->Int){
val v = a(this,10)
fun caller(a : (receiver : TestKotlinFeature, x : Int) -> Int) {
val v = a(this, 10)
println("testMemberReference caller $v")
}
caller( TestKotlinFeature::member)
caller(TestKotlinFeature::member)
val b = TestKotlinFeature::member
val a : ( receiver:TestKotlinFeature, x:Int)->Int = TestKotlinFeature::member
val a : (receiver : TestKotlinFeature, x : Int) -> Int = TestKotlinFeature::member
}
fun methodNotInline( callback: (x:Int) ->Int):Int{
fun methodNotInline(callback : (x : Int) -> Int) : Int {
return callback(3)
}
inline fun methodInline( callback: (x:Int) ->Int):Int{
inline fun methodInline(callback : (x : Int) -> Int) : Int {
return callback(5)
}
@Test fun testReturn() {
@Test
fun testReturn() {
// loop@ for( i in 1..2) {
// // 関数の引数以外の場所で定義したラムダ式
// var x = { x : Int ->
// break // コンパイルエラー
// break@loop // コンパイルエラー
// // return // コンパイルエラー
// x * x
// }(10)
// println("testReturn A:$x")
//
// // 非インライン関数の引数として定義したラムダ式
// x = methodNotInline { x : Int ->
// break // コンパイルエラー
// break@loop // コンパイルエラー
//
// // return // コンパイルエラー
//
// return@methodNotInline x * x
// }
// println("testReturn B:$x")
//
// // インライン関数の引数として定義したラムダ式
// methodInline { x : Int ->
// break // コンパイルエラー
// break@loop // コンパイルエラー
//
// return 10 // できる
//
// return@methodInline 10 // できる
// }
// }
// loop@ for( i in 1..2) {
// // 関数の引数以外の場所で定義したラムダ式
// var x = { x : Int ->
// break // コンパイルエラー
// break@loop // コンパイルエラー
// // return // コンパイルエラー
// x * x
// }(10)
// println("testReturn A:$x")
//
// // 非インライン関数の引数として定義したラムダ式
// x = methodNotInline { x : Int ->
// break // コンパイルエラー
// break@loop // コンパイルエラー
//
// // return // コンパイルエラー
//
// return@methodNotInline x * x
// }
// println("testReturn B:$x")
//
// // インライン関数の引数として定義したラムダ式
// methodInline { x : Int ->
// break // コンパイルエラー
// break@loop // コンパイルエラー
//
// return 10 // できる
//
// return@methodInline 10 // できる
// }
// }
}
private fun <A,B> A.letNotInline( code: (A)->B ) :B {
private fun <A, B> A.letNotInline(code : (A) -> B) : B {
return code(this)
}
@Test fun testInline0(){
var result :Int
@Test
fun testInline0() {
var result : Int
val n = 11
for( i in 1..10) {
for(i in 1 .. 10) {
println(n.letNotInline { v ->
val rv = v * i
result = rv
@ -403,11 +416,12 @@ class TestKotlinFeature {
})
}
}
@Test fun testInline1(){
var result :Int
@Test
fun testInline1() {
var result : Int
val n = 12
for( i in 1..10) {
for(i in 1 .. 10) {
println(n.let { v ->
val rv = v * i
result = rv
@ -416,61 +430,65 @@ class TestKotlinFeature {
}
}
@Test fun testInline2(){
var result :Int
@Test
fun testInline2() {
var result : Int
val n = 13
for( i in 1..10) {
for(i in 1 .. 10) {
val rv = n * i
result = rv
println(rv)
}
}
@Test fun testRawArray(){
@Test
fun testRawArray() {
// サイズを指定して生成
val a = IntArray(4)
for(i in 0 until a.size){
a[i]=i*2
for(i in 0 until a.size) {
a[i] = i * 2
}
println( a.joinToString(","))
println(a.joinToString(","))
// サイズと初期化ラムダを指定して生成
val b = IntArray(4){ index -> index *3 }
println( b.joinToString(","))
val b = IntArray(4) { index -> index * 3 }
println(b.joinToString(","))
// 可変長引数で初期化するライブラリ関数
var b2 = intArrayOf( 0,1,2,3)
var b2 = intArrayOf(0, 1, 2, 3)
// 参照型の配列だと初期化ラムダが必須
val c = Array<CharSequence>(4){ (it*4).toString() }
println( c.joinToString(","))
val d = Array<CharSequence?>(4){ if( it%2 == 0 ) null else (it*5).toString() }
println( d.joinToString(","))
val c = Array<CharSequence>(4) { (it * 4).toString() }
println(c.joinToString(","))
val d = Array<CharSequence?>(4) { if(it % 2 == 0) null else (it * 5).toString() }
println(d.joinToString(","))
// ラムダ式の戻り値の型から配列の型パラメータが推測される
val e = Array(4){ if( it%2 == 0 ) null else (it*6).toString() }
println( e.joinToString(","))
val e = Array(4) { if(it % 2 == 0) null else (it * 6).toString() }
println(e.joinToString(","))
// 可変長引数で初期化するライブラリ関数
var e2 = arrayOf( null,1,null,2)
var e2 = arrayOf(null, 1, null, 2)
}
@Test fun testOutProjectedType(){
fun foo( args: Array<out Number>){
@Test
fun testOutProjectedType() {
fun foo(args : Array<out Number>) {
val sb = StringBuilder()
for(s in args){
for(s in args) {
if(sb.isNotEmpty()) sb.append(',')
sb
.append(s.toString())
.append('#')
.append( s.javaClass.simpleName)
.append(s.javaClass.simpleName)
}
println(sb)
println(args.contains(6)) // 禁止されていない。inポジションって何だ…
// arggs[0]=6 //禁止されている
}
foo( arrayOf(1,2,3))
foo( arrayOf(1f,2f,3f))
foo(arrayOf(1, 2, 3))
foo(arrayOf(1f, 2f, 3f))
}
}