add privacy policy, update dependencies
This commit is contained in:
parent
bc24aa7e15
commit
d109e5c324
|
@ -3,23 +3,24 @@ package jp.juggler.apng.sample
|
|||
import android.Manifest
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.os.Bundle
|
||||
import android.os.SystemClock
|
||||
import android.support.v4.app.ActivityCompat
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.*
|
||||
import android.widget.AdapterView
|
||||
import android.widget.BaseAdapter
|
||||
import android.widget.ListView
|
||||
import android.widget.TextView
|
||||
import jp.juggler.apng.ApngFrames
|
||||
import kotlinx.coroutines.experimental.*
|
||||
import kotlinx.coroutines.experimental.android.UI
|
||||
import java.lang.ref.WeakReference
|
||||
import android.support.v4.content.ContextCompat
|
||||
import kotlinx.coroutines.experimental.android.Main
|
||||
import kotlin.coroutines.experimental.CoroutineContext
|
||||
|
||||
|
||||
|
||||
class ActList : AppCompatActivity() {
|
||||
class ActList : AppCompatActivity(), CoroutineScope {
|
||||
|
||||
companion object {
|
||||
const val TAG = "ActList"
|
||||
|
@ -33,7 +34,15 @@ class ActList : AppCompatActivity() {
|
|||
private lateinit var listAdapter : MyAdapter
|
||||
private var timeAnimationStart : Long = 0L
|
||||
|
||||
private lateinit var activityJob: Job
|
||||
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = Dispatchers.Main + activityJob
|
||||
|
||||
override fun onCreate(savedInstanceState : Bundle?) {
|
||||
|
||||
activityJob = Job()
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.act_list)
|
||||
this.listView = findViewById(R.id.listView)
|
||||
|
@ -56,35 +65,13 @@ class ActList : AppCompatActivity() {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
launch(UI) {
|
||||
|
||||
if(isDestroyed) return@launch
|
||||
|
||||
val list = async(CommonPool) {
|
||||
// RawリソースのIDと名前の一覧
|
||||
R.raw::class.java.fields
|
||||
.mapNotNull { it.get(null) as? Int }
|
||||
.map { id ->
|
||||
ListItem(
|
||||
id,
|
||||
resources.getResourceName(id)
|
||||
.replaceFirst(""".+/""".toRegex(), "")
|
||||
)
|
||||
}
|
||||
.toMutableList()
|
||||
.apply { sortBy { it.caption } }
|
||||
|
||||
}.await()
|
||||
|
||||
if(isDestroyed) return@launch
|
||||
|
||||
listAdapter.list.addAll(list)
|
||||
listAdapter.notifyDataSetChanged()
|
||||
}
|
||||
load()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
activityJob.cancel()
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode : Int,
|
||||
|
@ -108,6 +95,27 @@ class ActList : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun load() = launch{
|
||||
val list = async(Dispatchers.IO) {
|
||||
// RawリソースのIDと名前の一覧
|
||||
R.raw::class.java.fields
|
||||
.mapNotNull { it.get(null) as? Int }
|
||||
.map { id ->
|
||||
ListItem(
|
||||
id,
|
||||
resources.getResourceName(id)
|
||||
.replaceFirst(""".+/""".toRegex(), "")
|
||||
)
|
||||
}
|
||||
.toMutableList()
|
||||
.apply { sortBy { it.caption } }
|
||||
|
||||
}.await()
|
||||
|
||||
listAdapter.list.addAll(list)
|
||||
listAdapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
inner class MyAdapter : BaseAdapter(), AdapterView.OnItemClickListener {
|
||||
|
||||
val list = ArrayList<ListItem>()
|
||||
|
@ -155,12 +163,11 @@ class ActList : AppCompatActivity() {
|
|||
|
||||
}
|
||||
|
||||
class MyViewHolder(
|
||||
inner class MyViewHolder(
|
||||
viewRoot : View,
|
||||
_activity : ActList
|
||||
) {
|
||||
|
||||
private val activity = ref(_activity)
|
||||
private val tvCaption : TextView = viewRoot.findViewById(R.id.tvCaption)
|
||||
private val apngView : ApngView = viewRoot.findViewById(R.id.apngView)
|
||||
|
||||
|
@ -179,41 +186,33 @@ class ActList : AppCompatActivity() {
|
|||
lastId = resId
|
||||
apngView.apngFrames?.dispose()
|
||||
apngView.apngFrames = null
|
||||
launch(UI) {
|
||||
launch{
|
||||
var apngFrames :ApngFrames? = null
|
||||
try {
|
||||
if(activity()?.isDestroyed != false) return@launch
|
||||
|
||||
lastJob?.cancelAndJoin()
|
||||
|
||||
val job = async(CommonPool) {
|
||||
activity()?.resources?.openRawResource(resId)?.use { inStream ->
|
||||
|
||||
val job = async(Dispatchers.IO) {
|
||||
resources?.openRawResource(resId)?.use { inStream ->
|
||||
ApngFrames.parseApng(inStream, 128)
|
||||
}
|
||||
}
|
||||
|
||||
lastJob = job
|
||||
val apngFrames = job.await()
|
||||
|
||||
if(activity()?.isDestroyed == false
|
||||
&& lastId == resId
|
||||
&& apngFrames != null
|
||||
) {
|
||||
apngFrames = job.await()
|
||||
|
||||
if(apngFrames != null && lastId == resId ){
|
||||
apngView.apngFrames = apngFrames
|
||||
} else {
|
||||
apngFrames?.dispose()
|
||||
apngFrames = null
|
||||
}
|
||||
|
||||
} catch(ex : Throwable) {
|
||||
ex.printStackTrace()
|
||||
Log.e(TAG, "load error: ${ex.javaClass.simpleName} ${ex.message}")
|
||||
}finally{
|
||||
apngFrames?.dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class WeakRef<T : Any>(t : T) : WeakReference<T>(t) {
|
||||
operator fun invoke() : T? = get()
|
||||
}
|
||||
|
||||
fun <T : Any> ref(t : T) = WeakRef(t)
|
||||
|
|
|
@ -10,14 +10,13 @@ import android.util.Log
|
|||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import jp.juggler.apng.ApngFrames
|
||||
import kotlinx.coroutines.experimental.CommonPool
|
||||
import kotlinx.coroutines.experimental.android.UI
|
||||
import kotlinx.coroutines.experimental.async
|
||||
import kotlinx.coroutines.experimental.launch
|
||||
import kotlinx.coroutines.experimental.*
|
||||
import kotlinx.coroutines.experimental.android.Main
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import kotlin.coroutines.experimental.CoroutineContext
|
||||
|
||||
class ActViewer : AppCompatActivity() {
|
||||
class ActViewer : AppCompatActivity() , CoroutineScope {
|
||||
|
||||
companion object {
|
||||
const val TAG="ActViewer"
|
||||
|
@ -35,7 +34,13 @@ class ActViewer : AppCompatActivity() {
|
|||
private lateinit var apngView : ApngView
|
||||
private lateinit var tvError : TextView
|
||||
|
||||
private lateinit var activityJob: Job
|
||||
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = Dispatchers.Main + activityJob
|
||||
|
||||
override fun onCreate(savedInstanceState : Bundle?) {
|
||||
activityJob = Job()
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val intent = this.intent
|
||||
|
@ -56,11 +61,11 @@ class ActViewer : AppCompatActivity() {
|
|||
return@setOnLongClickListener true
|
||||
}
|
||||
|
||||
launch(UI) {
|
||||
launch{
|
||||
var apngFrames : ApngFrames? = null
|
||||
try {
|
||||
if(isDestroyed) return@launch
|
||||
|
||||
val apngFrames = async(CommonPool) {
|
||||
apngFrames = async(Dispatchers.IO) {
|
||||
resources.openRawResource(resId).use {
|
||||
ApngFrames.parseApng(
|
||||
it,
|
||||
|
@ -70,16 +75,11 @@ class ActViewer : AppCompatActivity() {
|
|||
}
|
||||
}.await()
|
||||
|
||||
apngView.visibility = View.VISIBLE
|
||||
tvError.visibility = View.GONE
|
||||
apngView.apngFrames = apngFrames
|
||||
apngFrames = null
|
||||
|
||||
|
||||
|
||||
if(isDestroyed) {
|
||||
apngFrames.dispose()
|
||||
} else {
|
||||
apngView.visibility = View.VISIBLE
|
||||
tvError.visibility = View.GONE
|
||||
apngView.apngFrames = apngFrames
|
||||
}
|
||||
} catch(ex : Throwable) {
|
||||
ex.printStackTrace()
|
||||
Log.e(ActList.TAG, "load error: ${ex.javaClass.simpleName} ${ex.message}")
|
||||
|
@ -90,6 +90,8 @@ class ActViewer : AppCompatActivity() {
|
|||
tvError.visibility = View.VISIBLE
|
||||
tvError.text = message
|
||||
}
|
||||
}finally{
|
||||
apngFrames?.dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -97,11 +99,13 @@ class ActViewer : AppCompatActivity() {
|
|||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
apngView.apngFrames?.dispose()
|
||||
activityJob.cancel()
|
||||
}
|
||||
|
||||
private fun save(apngFrames:ApngFrames){
|
||||
val title = this.title
|
||||
launch(CommonPool){
|
||||
|
||||
launch(Dispatchers.IO){
|
||||
val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
|
||||
dir.mkdirs()
|
||||
if(! dir.exists() ) {
|
||||
|
|
|
@ -11,8 +11,8 @@ android {
|
|||
targetSdkVersion target_sdk_version
|
||||
minSdkVersion min_sdk_version
|
||||
|
||||
versionCode 288
|
||||
versionName "2.8.8"
|
||||
versionCode 289
|
||||
versionName "2.8.9"
|
||||
applicationId "jp.juggler.subwaytooter"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
|
||||
|
@ -80,7 +80,7 @@ dependencies {
|
|||
|
||||
// https://firebase.google.com/support/release-notes/android
|
||||
implementation "com.google.firebase:firebase-core:16.0.3"
|
||||
implementation "com.google.firebase:firebase-messaging:17.3.1"
|
||||
implementation "com.google.firebase:firebase-messaging:17.3.2"
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
|
@ -89,9 +89,8 @@ dependencies {
|
|||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinx_coroutines_version"
|
||||
|
||||
// Anko Layouts
|
||||
implementation "org.jetbrains.anko:anko:$anko_version"
|
||||
implementation "org.jetbrains.anko:anko-sdk25:$anko_version"
|
||||
// sdk15, sdk19, sdk21, sdk23 are also available
|
||||
implementation "org.jetbrains.anko:anko-sdk25:$anko_version"
|
||||
implementation "org.jetbrains.anko:anko-appcompat-v7:$anko_version"
|
||||
|
||||
// Coroutine listeners for Anko Layouts
|
||||
|
|
|
@ -1424,7 +1424,7 @@ class ActAccountSetting
|
|||
|
||||
@Throws(IOException::class)
|
||||
override fun open() : InputStream {
|
||||
return contentResolver.openInputStream(uri)
|
||||
return contentResolver.openInputStream(uri) ?: error("openInputStream returns null")
|
||||
}
|
||||
|
||||
override fun deleteTempFile() {
|
||||
|
|
|
@ -3,6 +3,7 @@ package jp.juggler.subwaytooter
|
|||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.PackageManager
|
||||
|
@ -42,10 +43,6 @@ import jp.juggler.subwaytooter.api.entity.*
|
|||
|
||||
import org.apache.commons.io.IOUtils
|
||||
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.io.InputStreamReader
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.ArrayList
|
||||
import java.util.HashSet
|
||||
|
@ -65,6 +62,7 @@ import jp.juggler.subwaytooter.span.MyClickableSpan
|
|||
import jp.juggler.subwaytooter.span.MyClickableSpanClickCallback
|
||||
import jp.juggler.subwaytooter.util.*
|
||||
import jp.juggler.subwaytooter.view.ListDivider
|
||||
import java.io.*
|
||||
import java.util.zip.ZipInputStream
|
||||
import kotlin.math.min
|
||||
|
||||
|
@ -433,6 +431,8 @@ class ActMain : AppCompatActivity()
|
|||
if(savedInstanceState != null) {
|
||||
sent_intent2?.let { handleSentIntent(it) }
|
||||
}
|
||||
|
||||
checkPrivacyPolicy()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
@ -850,9 +850,7 @@ class ActMain : AppCompatActivity()
|
|||
showFooterColor()
|
||||
|
||||
if(resultCode == RESULT_APP_DATA_IMPORT) {
|
||||
if(data != null) {
|
||||
importAppData(data.data)
|
||||
}
|
||||
importAppData(data?.data)
|
||||
}
|
||||
|
||||
} else if(requestCode == REQUEST_CODE_TEXT) {
|
||||
|
@ -1330,7 +1328,9 @@ class ActMain : AppCompatActivity()
|
|||
env.tablet_pager.adapter = env.tablet_pager_adapter
|
||||
env.tablet_pager.layoutManager = env.tablet_layout_manager
|
||||
env.tablet_pager.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrollStateChanged(recyclerView : RecyclerView?, newState : Int) {
|
||||
|
||||
|
||||
override fun onScrollStateChanged(recyclerView : RecyclerView, newState : Int) {
|
||||
super.onScrollStateChanged(recyclerView, newState)
|
||||
|
||||
val vs = env.tablet_layout_manager.findFirstVisibleItemPosition()
|
||||
|
@ -1345,7 +1345,7 @@ class ActMain : AppCompatActivity()
|
|||
}
|
||||
}
|
||||
|
||||
override fun onScrolled(recyclerView : RecyclerView?, dx : Int, dy : Int) {
|
||||
override fun onScrolled(recyclerView : RecyclerView, dx : Int, dy : Int) {
|
||||
super.onScrolled(recyclerView, dx, dy)
|
||||
updateColumnStripSelection(- 1, - 1f)
|
||||
}
|
||||
|
@ -1465,7 +1465,7 @@ class ActMain : AppCompatActivity()
|
|||
var slide_ratio = 0f
|
||||
if(vr.first <= vr.last) {
|
||||
val child = env.tablet_layout_manager.findViewByPosition(vr.first)
|
||||
slide_ratio = Math.abs(child.left / nColumnWidth.toFloat())
|
||||
slide_ratio = Math.abs( (child?.left ?: 0) / nColumnWidth.toFloat())
|
||||
}
|
||||
|
||||
llColumnStrip.setVisibleRange(vr.first, vr.last, slide_ratio)
|
||||
|
@ -2339,8 +2339,12 @@ class ActMain : AppCompatActivity()
|
|||
}, { env ->
|
||||
for(i in 0 until env.tablet_layout_manager.childCount) {
|
||||
val v = env.tablet_layout_manager.getChildAt(i)
|
||||
val columnViewHolder =
|
||||
(env.tablet_pager.getChildViewHolder(v) as? TabletColumnViewHolder)?.columnViewHolder
|
||||
|
||||
val columnViewHolder =when(v){
|
||||
null-> null
|
||||
else->(env.tablet_pager.getChildViewHolder(v) as? TabletColumnViewHolder)?.columnViewHolder
|
||||
}
|
||||
|
||||
if(columnViewHolder?.isColumnSettingShown == true) {
|
||||
columnViewHolder.closeColumnSetting()
|
||||
return@closeColumnSetting true
|
||||
|
@ -2496,7 +2500,8 @@ class ActMain : AppCompatActivity()
|
|||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private fun importAppData(uri : Uri) {
|
||||
private fun importAppData(uri : Uri?) {
|
||||
uri ?: return
|
||||
|
||||
// remove all columns
|
||||
run {
|
||||
|
@ -2764,4 +2769,45 @@ class ActMain : AppCompatActivity()
|
|||
}
|
||||
}
|
||||
|
||||
private var dlgPrivacyPolicy : WeakReference<Dialog>?=null
|
||||
|
||||
private fun checkPrivacyPolicy() {
|
||||
|
||||
// 既に表示中かもしれない
|
||||
if( dlgPrivacyPolicy?.get()?.isShowing == true) return
|
||||
|
||||
|
||||
val res_id = when(getString(R.string.language_code)) {
|
||||
"ja" -> R.raw.privacy_policy_ja
|
||||
"fr" -> R.raw.privacy_policy_fr
|
||||
else -> R.raw.privacy_policy_en
|
||||
}
|
||||
|
||||
// プライバシーポリシーデータの読み込み
|
||||
val bytes = loadRawResource(res_id)
|
||||
if( bytes.isEmpty() ) return
|
||||
|
||||
// 同意ずみなら表示しない
|
||||
val digest = bytes.digestSHA256().encodeBase64Url()
|
||||
if( digest == Pref.spAgreedPrivacyPolicyDigest(pref) ) return
|
||||
|
||||
val dialog = AlertDialog.Builder(this)
|
||||
.setTitle(R.string.privacy_policy)
|
||||
.setMessage( bytes.decodeUTF8())
|
||||
.setNegativeButton(R.string.cancel){_,_ ->
|
||||
finish()
|
||||
}
|
||||
.setOnCancelListener{_->
|
||||
finish()
|
||||
}
|
||||
.setPositiveButton(R.string.agree){_,_ ->
|
||||
pref.edit().put(Pref.spAgreedPrivacyPolicyDigest,digest).apply()
|
||||
}
|
||||
.create()
|
||||
dlgPrivacyPolicy = WeakReference(dialog)
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -3,13 +3,9 @@ package jp.juggler.subwaytooter
|
|||
import android.os.Bundle
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.widget.TextView
|
||||
|
||||
import org.apache.commons.io.IOUtils
|
||||
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
import jp.juggler.subwaytooter.util.LogCategory
|
||||
import jp.juggler.subwaytooter.util.decodeUTF8
|
||||
import jp.juggler.subwaytooter.util.loadRawResource
|
||||
|
||||
class ActOSSLicense : AppCompatActivity() {
|
||||
|
||||
|
@ -23,13 +19,8 @@ class ActOSSLicense : AppCompatActivity() {
|
|||
setContentView(R.layout.act_oss_license)
|
||||
|
||||
try {
|
||||
resources.openRawResource(R.raw.oss_license)?.use { inData ->
|
||||
ByteArrayOutputStream().use { bao ->
|
||||
IOUtils.copy(inData, bao)
|
||||
val tv = findViewById<TextView>(R.id.tvText)
|
||||
tv.text = bao.toByteArray().decodeUTF8()
|
||||
}
|
||||
}
|
||||
val tv = findViewById<TextView>(R.id.tvText)
|
||||
tv.text = loadRawResource(R.raw.oss_license).decodeUTF8()
|
||||
} catch(ex : Throwable) {
|
||||
log.trace(ex)
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import android.support.v4.content.ContextCompat
|
|||
import android.support.v7.app.AlertDialog
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.text.Editable
|
||||
import android.text.Spannable
|
||||
import android.text.TextWatcher
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.view.View
|
||||
|
@ -641,19 +642,26 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
|||
// 再編集の場合はdefault_textは反映されない
|
||||
|
||||
val decodeOptions = DecodeOptions(this)
|
||||
etContent.text = decodeOptions.decodeHTML(base_status.content)
|
||||
etContent.setSelection(etContent.text.length)
|
||||
etContentWarning.setText(decodeOptions.decodeEmoji(base_status.spoiler_text))
|
||||
etContentWarning.setSelection(etContentWarning.text.length)
|
||||
cbContentWarning.isChecked = etContentWarning.text.isNotEmpty()
|
||||
|
||||
var text :Spannable
|
||||
|
||||
text = decodeOptions.decodeHTML(base_status.content)
|
||||
etContent.text = text
|
||||
etContent.setSelection(text.length )
|
||||
|
||||
text =decodeOptions.decodeEmoji(base_status.spoiler_text)
|
||||
etContentWarning.setText(text)
|
||||
etContentWarning.setSelection(text.length)
|
||||
cbContentWarning.isChecked = text.isNotEmpty()
|
||||
cbNSFW.isChecked = base_status.sensitive == true
|
||||
|
||||
val src_enquete = base_status.enquete
|
||||
val src_items = src_enquete?.items
|
||||
if(src_items != null && src_enquete.type == NicoEnquete.TYPE_ENQUETE) {
|
||||
cbEnquete.isChecked = true
|
||||
etContent.text = decodeOptions.decodeHTML(src_enquete.question)
|
||||
etContent.setSelection(etContent.text.length)
|
||||
text = decodeOptions.decodeHTML(src_enquete.question)
|
||||
etContent.text = text
|
||||
etContent.setSelection(text.length)
|
||||
|
||||
var src_index = 0
|
||||
for(et in list_etChoice) {
|
||||
|
@ -763,22 +771,37 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
|||
if(svEmoji.isEmpty()) return
|
||||
|
||||
val editable = etContent.text
|
||||
if(editable.isNotEmpty()
|
||||
&& ! CharacterGroup.isWhitespace(editable[editable.length - 1].toInt())
|
||||
) {
|
||||
editable.append(' ')
|
||||
}
|
||||
|
||||
if(selectBefore) {
|
||||
val start = editable.length
|
||||
editable.append(' ')
|
||||
editable.append(svEmoji)
|
||||
etContent.text = editable
|
||||
etContent.setSelection(start)
|
||||
} else {
|
||||
editable.append(svEmoji)
|
||||
etContent.text = editable
|
||||
etContent.setSelection(editable.length)
|
||||
if( editable == null ) {
|
||||
val sb = StringBuilder ()
|
||||
if(selectBefore) {
|
||||
val start = 0
|
||||
sb.append(' ')
|
||||
sb.append(svEmoji)
|
||||
etContent.setText(sb)
|
||||
etContent.setSelection(start)
|
||||
} else {
|
||||
sb.append(svEmoji)
|
||||
etContent.setText(sb)
|
||||
etContent.setSelection(sb.length)
|
||||
}
|
||||
}else{
|
||||
if(editable.isNotEmpty()
|
||||
&& ! CharacterGroup.isWhitespace(editable[editable.length - 1].toInt())
|
||||
) {
|
||||
editable.append(' ')
|
||||
}
|
||||
|
||||
if(selectBefore) {
|
||||
val start = editable.length
|
||||
editable.append(' ')
|
||||
editable.append(svEmoji)
|
||||
etContent.text = editable
|
||||
etContent.setSelection(start)
|
||||
} else {
|
||||
editable.append(svEmoji)
|
||||
etContent.text = editable
|
||||
etContent.setSelection(editable.length)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1494,7 +1517,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
|||
|
||||
@Throws(IOException::class)
|
||||
override fun open() : InputStream {
|
||||
return contentResolver.openInputStream(uri)
|
||||
return contentResolver.openInputStream(uri) ?: error("openInputStream returns null")
|
||||
}
|
||||
|
||||
override fun deleteTempFile() {
|
||||
|
@ -2348,7 +2371,7 @@ class ActPost : AppCompatActivity(), View.OnClickListener, PostAttachment.Callba
|
|||
else -> R.raw.recommended_plugin_en
|
||||
}
|
||||
|
||||
this.loadRawResource(res_id)?.let { data ->
|
||||
this.loadRawResource(res_id).let { data ->
|
||||
val text = data.decodeUTF8()
|
||||
val viewRoot = layoutInflater.inflate(R.layout.dlg_plugin_missing, null, false)
|
||||
|
||||
|
|
|
@ -4900,7 +4900,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
|
||||
return context.loadRawResource(res_id)?.decodeUTF8() ?: "?"
|
||||
return context.loadRawResource(res_id).decodeUTF8()
|
||||
}
|
||||
|
||||
private var cacheHeaderDesc : String? = null
|
||||
|
|
|
@ -194,7 +194,7 @@ class ColumnViewHolder(
|
|||
listView = viewRoot.findViewById(R.id.listView)
|
||||
|
||||
if(Pref.bpShareViewPool(activity.pref)) {
|
||||
listView.recycledViewPool = activity.viewPool
|
||||
listView.setRecycledViewPool( activity.viewPool)
|
||||
}
|
||||
listView.itemAnimator = null
|
||||
//
|
||||
|
|
|
@ -89,7 +89,7 @@ class StringPref(
|
|||
) : BasePref<String>(key) {
|
||||
|
||||
override operator fun invoke(pref : SharedPreferences) : String {
|
||||
return pref.getString(key, defVal)
|
||||
return pref.getString(key,defVal) ?: defVal
|
||||
}
|
||||
|
||||
override fun put(editor : SharedPreferences.Editor, v : String) {
|
||||
|
@ -414,6 +414,7 @@ object Pref {
|
|||
val spUserAgent = StringPref("UserAgent", "")
|
||||
|
||||
val spMediaReadTimeout = StringPref("spMediaReadTimeout", "60")
|
||||
val spAgreedPrivacyPolicyDigest= StringPref("spAgreedPrivacyPolicyDigest", "")
|
||||
|
||||
// long
|
||||
val lpTabletTootDefaultAccount = LongPref("tablet_toot_default_account", - 1L)
|
||||
|
|
|
@ -619,7 +619,7 @@ object Action_Toot {
|
|||
|
||||
val dialog = ActionsDialog()
|
||||
|
||||
val host_original = Uri.parse(url).authority
|
||||
val host_original = Uri.parse(url).authority ?: ""
|
||||
|
||||
// 選択肢:ブラウザで表示する
|
||||
dialog.addAction(
|
||||
|
|
|
@ -358,8 +358,11 @@ open class TootAccount(parser : TootParser, src : JSONObject) {
|
|||
if(url != null) {
|
||||
try {
|
||||
// たぶんどんなURLでもauthorityの部分にホスト名が来るだろう(慢心)
|
||||
val uri = Uri.parse(url)
|
||||
return uri.authority.toLowerCase()
|
||||
val host = Uri.parse(url).authority
|
||||
if( host?.isNotEmpty() == true){
|
||||
return host.toLowerCase()
|
||||
}
|
||||
log.e("findHostFromUrl: can't parse host from URL $url")
|
||||
} catch(ex : Throwable) {
|
||||
log.e(ex, "findHostFromUrl: can't parse host from URL $url")
|
||||
}
|
||||
|
|
|
@ -38,12 +38,14 @@ object LoginForm {
|
|||
) -> Unit
|
||||
) {
|
||||
val view = activity.layoutInflater.inflate(R.layout.dlg_account_add, null, false)
|
||||
val etInstance :AutoCompleteTextView = view.findViewById(R.id.etInstance)
|
||||
val btnOk :View = view.findViewById(R.id.btnOk)
|
||||
val cbPseudoAccount :CheckBox = view.findViewById(R.id.cbPseudoAccount)
|
||||
val cbInputAccessToken :CheckBox = view.findViewById(R.id.cbInputAccessToken)
|
||||
val etInstance : AutoCompleteTextView = view.findViewById(R.id.etInstance)
|
||||
val btnOk : View = view.findViewById(R.id.btnOk)
|
||||
val cbPseudoAccount : CheckBox = view.findViewById(R.id.cbPseudoAccount)
|
||||
val cbInputAccessToken : CheckBox = view.findViewById(R.id.cbInputAccessToken)
|
||||
|
||||
cbPseudoAccount.setOnCheckedChangeListener { _, _ -> cbInputAccessToken.isEnabled = ! cbPseudoAccount.isChecked }
|
||||
cbPseudoAccount.setOnCheckedChangeListener { _, _ ->
|
||||
cbInputAccessToken.isEnabled = ! cbPseudoAccount.isChecked
|
||||
}
|
||||
|
||||
if(instanceArg != null && instanceArg.isNotEmpty()) {
|
||||
etInstance.setText(instanceArg)
|
||||
|
@ -61,36 +63,34 @@ object LoginForm {
|
|||
}
|
||||
val dialog = Dialog(activity)
|
||||
dialog.setContentView(view)
|
||||
btnOk.setOnClickListener(View.OnClickListener {
|
||||
btnOk.setOnClickListener { _ ->
|
||||
val instance = etInstance.text.toString().trim { it <= ' ' }
|
||||
|
||||
if( instance.isEmpty() ) {
|
||||
showToast(activity, true, R.string.instance_not_specified)
|
||||
return@OnClickListener
|
||||
} else if(instance.contains("/") || instance.contains("@")) {
|
||||
showToast(activity, true, R.string.instance_not_need_slash)
|
||||
return@OnClickListener
|
||||
when {
|
||||
instance.isEmpty() -> showToast(activity, true, R.string.instance_not_specified)
|
||||
instance.contains("/") || instance.contains("@") -> showToast(
|
||||
activity,
|
||||
true,
|
||||
R.string.instance_not_need_slash
|
||||
)
|
||||
else -> onClickOk(
|
||||
dialog,
|
||||
instance,
|
||||
cbPseudoAccount.isChecked,
|
||||
cbInputAccessToken.isChecked
|
||||
)
|
||||
}
|
||||
onClickOk(dialog, instance, cbPseudoAccount.isChecked, cbInputAccessToken.isChecked)
|
||||
})
|
||||
}
|
||||
view.findViewById<View>(R.id.btnCancel).setOnClickListener { dialog.cancel() }
|
||||
|
||||
val instance_list = ArrayList<String>()
|
||||
try {
|
||||
val `is` = activity.resources.openRawResource(R.raw.server_list)
|
||||
try {
|
||||
val br = BufferedReader(InputStreamReader(`is`, "UTF-8"))
|
||||
activity.resources.openRawResource(R.raw.server_list).use { inStream ->
|
||||
val br = BufferedReader(InputStreamReader(inStream, "UTF-8"))
|
||||
while(true) {
|
||||
val s : String = br.readLine()?.trim { it <= ' ' }?.toLowerCase() ?: break
|
||||
if(s.isNotEmpty()) instance_list.add(s)
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
`is`.close()
|
||||
} catch(ignored : Throwable) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
} catch(ex : Throwable) {
|
||||
log.trace(ex)
|
||||
|
@ -107,7 +107,7 @@ object LoginForm {
|
|||
|
||||
override fun performFiltering(constraint : CharSequence?) : Filter.FilterResults {
|
||||
val result = Filter.FilterResults()
|
||||
if( constraint?.isNotEmpty() ==true ) {
|
||||
if(constraint?.isNotEmpty() == true) {
|
||||
val key = constraint.toString().toLowerCase()
|
||||
// suggestions リストは毎回生成する必要がある。publishResultsと同時にアクセスされる場合がある
|
||||
val suggestions = StringArray()
|
||||
|
@ -123,7 +123,10 @@ object LoginForm {
|
|||
return result
|
||||
}
|
||||
|
||||
override fun publishResults(constraint : CharSequence?, results : Filter.FilterResults?) {
|
||||
override fun publishResults(
|
||||
constraint : CharSequence?,
|
||||
results : Filter.FilterResults?
|
||||
) {
|
||||
clear()
|
||||
val values = results?.values
|
||||
if(values is StringArray) {
|
||||
|
@ -145,7 +148,8 @@ object LoginForm {
|
|||
|
||||
dialog.window?.setLayout(
|
||||
WindowManager.LayoutParams.MATCH_PARENT,
|
||||
WindowManager.LayoutParams.WRAP_CONTENT)
|
||||
WindowManager.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
|
|
|
@ -133,7 +133,7 @@ class AcctColor {
|
|||
if(cached != null) return cached
|
||||
|
||||
try {
|
||||
val where_arg = load_where_arg.get()
|
||||
val where_arg = load_where_arg.get() ?: arrayOfNulls<String?>(1)
|
||||
where_arg[0] = acct
|
||||
App1.database.query(table, null, load_where, where_arg, null, null, null)
|
||||
.use { cursor ->
|
||||
|
|
|
@ -111,7 +111,7 @@ object AcctSet :TableCompanion{
|
|||
|
||||
fun searchPrefix(prefix : String, limit : Int) : ArrayList<CharSequence> {
|
||||
try {
|
||||
val where_arg = prefix_search_where_arg.get()
|
||||
val where_arg = prefix_search_where_arg.get() ?: arrayOfNulls<String?>(1)
|
||||
where_arg[0] = makePattern(prefix)
|
||||
App1.database.query(table, null, prefix_search_where, where_arg, null, null, COL_ACCT + " asc limit " + limit)
|
||||
.use { cursor ->
|
||||
|
|
|
@ -46,7 +46,7 @@ object SubscriptionServerKey : TableCompanion {
|
|||
|
||||
fun find(clientIdentifier : String) : String? {
|
||||
try {
|
||||
val whereArgs = findWhereArgs.get()
|
||||
val whereArgs = findWhereArgs.get() ?: arrayOfNulls<String?>(1)
|
||||
whereArgs[0] = clientIdentifier
|
||||
App1.database.query(
|
||||
table,
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package jp.juggler.subwaytooter.table
|
||||
|
||||
import android.database.Cursor
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
|
||||
// SQLite にBooleanをそのまま保存することはできないのでInt型との変換が必要になる
|
||||
|
|
|
@ -124,7 +124,7 @@ object TagSet :TableCompanion{
|
|||
val dst = ArrayList<CharSequence>()
|
||||
|
||||
try {
|
||||
val where_arg = prefix_search_where_arg.get()
|
||||
val where_arg = prefix_search_where_arg.get()?: arrayOfNulls<String?>(1)
|
||||
where_arg[0] = makePattern(prefix)
|
||||
App1.database.query(
|
||||
table,
|
||||
|
@ -133,7 +133,7 @@ object TagSet :TableCompanion{
|
|||
where_arg,
|
||||
null,
|
||||
null,
|
||||
COL_TAG + " asc limit " + limit
|
||||
"$COL_TAG asc limit $limit"
|
||||
).use { cursor ->
|
||||
dst.ensureCapacity(cursor.count)
|
||||
val idx_acct = cursor.getColumnIndex(COL_TAG)
|
||||
|
|
|
@ -214,22 +214,21 @@ class UserRelation {
|
|||
|
||||
private fun load(db_id : Long, who_id : Long) : UserRelation? {
|
||||
try {
|
||||
val where_arg = load_where_arg.get()
|
||||
val where_arg = load_where_arg.get() ?: arrayOfNulls<String?>(2)
|
||||
where_arg[0] = db_id.toString()
|
||||
where_arg[1] = who_id.toString()
|
||||
App1.database.query(table, null, load_where, where_arg, null, null, null)
|
||||
.use { cursor ->
|
||||
if(cursor.moveToNext()) {
|
||||
val dst = UserRelation()
|
||||
dst.following = 0 != cursor.getInt(cursor.getColumnIndex(COL_FOLLOWING))
|
||||
dst.followed_by = 0 !=
|
||||
cursor.getInt(cursor.getColumnIndex(COL_FOLLOWED_BY))
|
||||
dst.blocking = 0 != cursor.getInt(cursor.getColumnIndex(COL_BLOCKING))
|
||||
dst.muting = 0 != cursor.getInt(cursor.getColumnIndex(COL_MUTING))
|
||||
dst.requested = 0 != cursor.getInt(cursor.getColumnIndex(COL_REQUESTED))
|
||||
dst.following = cursor.getInt(cursor.getColumnIndex(COL_FOLLOWING)).i2b()
|
||||
dst.followed_by =cursor.getInt(cursor.getColumnIndex(COL_FOLLOWED_BY)).i2b()
|
||||
dst.blocking = cursor.getInt(cursor.getColumnIndex(COL_BLOCKING)).i2b()
|
||||
dst.muting = cursor.getInt(cursor.getColumnIndex(COL_MUTING)).i2b()
|
||||
dst.requested = cursor.getInt(cursor.getColumnIndex(COL_REQUESTED)).i2b()
|
||||
dst.following_reblogs =
|
||||
cursor.getInt(cursor.getColumnIndex(COL_FOLLOWING_REBLOGS))
|
||||
dst.endorsed = 0 != cursor.getInt(cursor.getColumnIndex(COL_ENDORSED))
|
||||
dst.endorsed = cursor.getInt(cursor.getColumnIndex(COL_ENDORSED)).i2b()
|
||||
return dst
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,7 +74,7 @@ object UserRelationMisskey : TableCompanion {
|
|||
cv.put(COL_MUTING, src.muting.b2i())
|
||||
cv.put(COL_REQUESTED, src.requested.b2i())
|
||||
cv.put(COL_FOLLOWING_REBLOGS, src.following_reblogs)
|
||||
cv.put(COL_ENDORSED,src.endorsed.b2i())
|
||||
cv.put(COL_ENDORSED, src.endorsed.b2i())
|
||||
App1.database.replace(table, null, cv)
|
||||
|
||||
val key = String.format("%s:%s", db_id, whoId)
|
||||
|
@ -112,7 +112,7 @@ object UserRelationMisskey : TableCompanion {
|
|||
cv.put(COL_MUTING, src.muting.b2i())
|
||||
cv.put(COL_REQUESTED, src.requested.b2i())
|
||||
cv.put(COL_FOLLOWING_REBLOGS, src.following_reblogs)
|
||||
cv.put(COL_ENDORSED,src.endorsed.b2i())
|
||||
cv.put(COL_ENDORSED, src.endorsed.b2i())
|
||||
db.replace(table, null, cv)
|
||||
}
|
||||
bOK = true
|
||||
|
@ -144,22 +144,22 @@ object UserRelationMisskey : TableCompanion {
|
|||
fun load(db_id : Long, who_id : String) : UserRelation? {
|
||||
|
||||
try {
|
||||
val where_arg = load_where_arg.get()
|
||||
val where_arg = load_where_arg.get() ?: arrayOfNulls<String?>(2)
|
||||
where_arg[0] = db_id.toString()
|
||||
where_arg[1] = who_id
|
||||
App1.database.query(table, null, load_where, where_arg, null, null, null)
|
||||
.use { cursor ->
|
||||
if(cursor.moveToNext()) {
|
||||
val dst = UserRelation()
|
||||
dst.following = 0 != cursor.getInt(cursor.getColumnIndex(COL_FOLLOWING))
|
||||
dst.followed_by = 0 !=
|
||||
cursor.getInt(cursor.getColumnIndex(COL_FOLLOWED_BY))
|
||||
dst.blocking = 0 != cursor.getInt(cursor.getColumnIndex(COL_BLOCKING))
|
||||
dst.muting = 0 != cursor.getInt(cursor.getColumnIndex(COL_MUTING))
|
||||
dst.requested = 0 != cursor.getInt(cursor.getColumnIndex(COL_REQUESTED))
|
||||
dst.following = cursor.getInt(cursor.getColumnIndex(COL_FOLLOWING)).i2b()
|
||||
dst.followed_by =
|
||||
cursor.getInt(cursor.getColumnIndex(COL_FOLLOWED_BY)).i2b()
|
||||
dst.blocking = cursor.getInt(cursor.getColumnIndex(COL_BLOCKING)).i2b()
|
||||
dst.muting = cursor.getInt(cursor.getColumnIndex(COL_MUTING)).i2b()
|
||||
dst.requested = cursor.getInt(cursor.getColumnIndex(COL_REQUESTED)).i2b()
|
||||
dst.following_reblogs =
|
||||
cursor.getInt(cursor.getColumnIndex(COL_FOLLOWING_REBLOGS))
|
||||
dst.endorsed = 0 != cursor.getInt(cursor.getColumnIndex(COL_ENDORSED))
|
||||
dst.endorsed = cursor.getInt(cursor.getColumnIndex(COL_ENDORSED)).i2b()
|
||||
return dst
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ class BucketList<E> constructor(
|
|||
// allocalted を指定しない場合は BucketPosを生成します
|
||||
private fun findPos(
|
||||
total_index : Int,
|
||||
result : BucketPos = pos_internal.get()
|
||||
result : BucketPos = pos_internal.get()!!
|
||||
) : BucketPos {
|
||||
|
||||
if(total_index < 0 || total_index >= size) {
|
||||
|
|
|
@ -262,7 +262,7 @@ object HTMLDecoder {
|
|||
log.d("parseChild: %s|%s %s [%s]", indent, child.tag, open_type, child.text)
|
||||
|
||||
if(OPEN_TYPE_OPEN == open_type) {
|
||||
child.addChild(t, indent + "--")
|
||||
child.addChild(t, "$indent--")
|
||||
}
|
||||
}
|
||||
if(DEBUG_HTML_PARSER) log.d("parseChild: %s)%s", indent, tag)
|
||||
|
@ -435,10 +435,10 @@ object HTMLDecoder {
|
|||
sb.append("://")
|
||||
}
|
||||
sb.append(uri.authority)
|
||||
val a = uri.encodedPath
|
||||
val a = uri.encodedPath ?: ""
|
||||
val q = uri.encodedQuery
|
||||
val f = uri.encodedFragment
|
||||
val remain = a + (if(q == null) "" else "?" + q) + if(f == null) "" else "#" + f
|
||||
val remain = a + (if(q==null) "" else "?$q") + if(f==null) "" else "#$f"
|
||||
if(remain.length > 10) {
|
||||
sb.append(remain.substring(0, 10))
|
||||
sb.append("…")
|
||||
|
|
|
@ -122,17 +122,19 @@ internal class PopupAutoCompleteAcct(
|
|||
}
|
||||
v.setOnClickListener {
|
||||
|
||||
val src = et.text
|
||||
val src_length = src.length
|
||||
val start = Math.min(src_length, sel_start)
|
||||
val end = Math.min(src_length, sel_end)
|
||||
|
||||
val start : Int
|
||||
val editable = et.text ?: ""
|
||||
val sb = SpannableStringBuilder()
|
||||
.append(src.subSequence(0, start))
|
||||
|
||||
val src_length = editable.length
|
||||
start = Math.min(src_length, sel_start)
|
||||
val end = Math.min(src_length, sel_end)
|
||||
sb.append(editable.subSequence(0, start))
|
||||
val remain = editable.subSequence(end, src_length)
|
||||
|
||||
if(acct[0] == ' ') {
|
||||
// 絵文字ショートコード
|
||||
if(! EmojiDecoder.canStartShortCode(src, start)) sb.append(' ')
|
||||
if(! EmojiDecoder.canStartShortCode(sb, start)) sb.append(' ')
|
||||
sb.append(acct.subSequence(2, acct.length))
|
||||
} else {
|
||||
// @user@host, #hashtag
|
||||
|
@ -141,7 +143,7 @@ internal class PopupAutoCompleteAcct(
|
|||
}
|
||||
|
||||
val newSelection = sb.length
|
||||
if(end < src_length) sb.append(src.subSequence(end, src_length))
|
||||
sb.append(remain)
|
||||
|
||||
et.text = sb
|
||||
et.setSelection(newSelection)
|
||||
|
|
|
@ -310,7 +310,7 @@ class PostHelper(
|
|||
|
||||
if( visibility_checked == TootVisibility.DirectSpecified ){
|
||||
val userIds = JSONArray()
|
||||
val reMention = Pattern.compile("(?:\\A|\\s)@([a-zA-Z0-9_]{1,20})(?:@([\\w\\.\\:-]+))?(?:\\z|\\s)")
|
||||
val reMention = Pattern.compile("(?:\\A|\\s)@([a-zA-Z0-9_]{1,20})(?:@([\\w.:-]+))?(?:\\z|\\s)")
|
||||
val m = reMention.matcher(content)
|
||||
while(m.find()){
|
||||
val username = m.group(1)
|
||||
|
@ -830,7 +830,7 @@ class PostHelper(
|
|||
EmojiPicker(activity, instance, isMisskey) { name, instance, bInstanceHasCustomEmoji ->
|
||||
val et = this.et ?: return@EmojiPicker
|
||||
|
||||
val src = et.text
|
||||
val src = et.text ?: ""
|
||||
val src_length = src.length
|
||||
val end = Math.min(src_length, et.selectionEnd)
|
||||
val start = src.lastIndexOf(':', end - 1)
|
||||
|
@ -841,7 +841,7 @@ class PostHelper(
|
|||
.appendEmoji(name, instance, bInstanceHasCustomEmoji)
|
||||
|
||||
val newSelection = sb.length
|
||||
if(end < src_length) sb.append(src.subSequence(end, src_length))
|
||||
if(end < src_length) sb.append(src.subSequence(end, src_length) )
|
||||
|
||||
et.text = sb
|
||||
et.setSelection(newSelection)
|
||||
|
@ -858,17 +858,17 @@ class PostHelper(
|
|||
EmojiPicker(activity, instance, isMisskey) { name, instance, bInstanceHasCustomEmoji ->
|
||||
val et = this.et ?: return@EmojiPicker
|
||||
|
||||
val src = et.text
|
||||
val src = et.text ?: ""
|
||||
val src_length = src.length
|
||||
val start = Math.min(src_length, et.selectionStart)
|
||||
val end = Math.min(src_length, et.selectionEnd)
|
||||
|
||||
val sb = SpannableStringBuilder()
|
||||
.append(src.subSequence(0, start))
|
||||
.append(src.subSequence(0, start) )
|
||||
.appendEmoji(name, instance, bInstanceHasCustomEmoji)
|
||||
|
||||
val newSelection = sb.length
|
||||
if(end < src_length) sb.append(src.subSequence(end, src_length))
|
||||
if(end < src_length) sb.append(src.subSequence(end, src_length) )
|
||||
|
||||
et.text = sb
|
||||
et.setSelection(newSelection)
|
||||
|
|
|
@ -27,7 +27,8 @@ class ScrollPosition {
|
|||
}
|
||||
|
||||
fun restore(holder : ColumnViewHolder) {
|
||||
if(adapterIndex in 0 until holder.listView.adapter.itemCount) {
|
||||
val adapter = holder.listView.adapter ?: return
|
||||
if(adapterIndex in 0 until adapter.itemCount) {
|
||||
holder.listLayoutManager.scrollToPositionWithOffset(adapterIndex, offset)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -104,7 +104,7 @@ object StorageUtils{
|
|||
// tree
|
||||
return paths[1]
|
||||
}
|
||||
throw IllegalArgumentException("Invalid URI: " + documentUri)
|
||||
throw IllegalArgumentException("Invalid URI: $documentUri")
|
||||
}
|
||||
|
||||
fun getFile(context : Context, path : String) : File? {
|
||||
|
@ -138,7 +138,7 @@ object StorageUtils{
|
|||
}
|
||||
}
|
||||
// MediaStore Uri
|
||||
context.contentResolver.query(uri, null, null, null, null).use { cursor ->
|
||||
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) {
|
||||
|
|
|
@ -856,19 +856,15 @@ fun View.showKeyboard() {
|
|||
////////////////////////////////////////////////////////////////////
|
||||
// context
|
||||
|
||||
fun Context.loadRawResource(res_id : Int) : ByteArray? {
|
||||
try {
|
||||
this.resources.openRawResource(res_id).use { inStream ->
|
||||
val bao = ByteArrayOutputStream()
|
||||
IOUtils.copy(inStream, bao)
|
||||
return bao.toByteArray()
|
||||
}
|
||||
} catch(ex : Throwable) {
|
||||
Utils.log.trace(ex)
|
||||
fun Context.loadRawResource( resId:Int):ByteArray{
|
||||
resources.openRawResource(resId).use{ inStream->
|
||||
val bao = ByteArrayOutputStream( inStream.available() )
|
||||
IOUtils.copy(inStream,bao)
|
||||
return bao.toByteArray()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// file
|
||||
|
||||
|
|
|
@ -411,7 +411,7 @@ class MyNetworkImageView : AppCompatImageView {
|
|||
invalidate()
|
||||
}
|
||||
|
||||
override fun onVisibilityChanged(changedView : View?, visibility : Int) {
|
||||
override fun onVisibilityChanged(changedView : View, visibility : Int) {
|
||||
super.onVisibilityChanged(changedView, visibility)
|
||||
loadImageIfNecessary()
|
||||
}
|
||||
|
|
|
@ -51,6 +51,8 @@ class TabletModeRecyclerView : RecyclerView {
|
|||
if(index >= 0) {
|
||||
if(mForbidStartDragging) return false
|
||||
|
||||
val layoutManager = this.layoutManager ?: return false
|
||||
|
||||
val x = (e.getX(index) + 0.5f).toInt()
|
||||
val y = (e.getY(index) + 0.5f).toInt()
|
||||
val canScrollHorizontally = layoutManager.canScrollHorizontally()
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
|
||||
## Subway Tooter Privacy Policy
|
||||
|
||||
### User data handled by Subway Tooter
|
||||
|
||||
#### Access token of SNS services
|
||||
|
||||
In accordance with access delegation of SNS services (Mastodon, Misskey, etc.), Subway Tooter saves the access token to application data.
|
||||
|
||||
Also due to old Mastodon restrictions, In accordance with user's explicit permission, Subway Tooter send an access token to the application server or notification listener server for push notification.
|
||||
|
||||
Also due to current Mastodon restrictions, Subway Tooter send an unrecoverable digest of access token to the application server for push subscription.
|
||||
|
||||
#### User information registered in the SNS service
|
||||
|
||||
In accordance with access delegation of SNS services (Mastodon, Misskey, etc.), Subway Tooter saves the information and settings of login account to application data. This includes not only the public profile, also may include information that the login account only can read or update.
|
||||
|
||||
Except in the case of sending to the relevant SNS service for editing account information with accordance with user's explicit intent, Subway Tooter does not send this information to the outside of app.
|
||||
|
||||
When deleting account information from Subway Tooter, also the user information will be deleted from Subway Tooter's application data.
|
||||
|
||||
#### Activities on the SNS service
|
||||
|
||||
Subway Tooter has the function to read / update the activities from login account on SNS service such as posts, favorites, boosts, and lists.
|
||||
|
||||
Subway Tooter will not send these information to outside the application EXCEPT in the following cases.
|
||||
- With user's explicit intent, sending activity update/delete request to relevant SNS service.
|
||||
- With user's explicit intent, sharing URLs or datas to external web browser or other applications.
|
||||
- With user's explicit intent, downloading data to the folder that is accessible by other applications.
|
||||
|
||||
#### Information coming from the input plugin
|
||||
|
||||
Subway Tooter has a function to get information from input plugin (external application).
|
||||
With user's explicit intent, Those information may send to relevant SNS service.
|
||||
|
||||
#### User tracking
|
||||
|
||||
Subway Tooter uses Firebase Cloud Messaging for push notifications.
|
||||
Following information will be send to external server.
|
||||
- Firebase Cloud Messaging sends message recipient information to Google server.
|
||||
- Subway Tooter sends push subscription management information to the app server.
|
||||
|
||||
There is no other kind of user tracking.
|
||||
|
||||
#### User location
|
||||
|
||||
Subway Tooter has no function to get user location.
|
||||
The user location may be entered from input plugin, but Subway Tooter handles it as like as normal text.
|
||||
|
||||
#### Advertisement
|
||||
Subway Tooter has no function to show Advertisement.
|
||||
|
||||
There are cases SNS services may show ad in its API response, but Subway Tooter does not participate in those.
|
|
@ -0,0 +1,54 @@
|
|||
|
||||
## Subway Tooter Privacy Policy
|
||||
|
||||
### User data handled by Subway Tooter
|
||||
|
||||
#### Access token of SNS services
|
||||
|
||||
In accordance with access delegation of SNS services (Mastodon, Misskey, etc.), Subway Tooter saves the access token to application data.
|
||||
|
||||
Also due to old Mastodon restrictions, In accordance with user's explicit permission, Subway Tooter send an access token to the application server or notification listener server for push notification.
|
||||
|
||||
Also due to current Mastodon restrictions, Subway Tooter send an unrecoverable digest of access token to the application server for push subscription.
|
||||
|
||||
#### User information registered in the SNS service
|
||||
|
||||
In accordance with access delegation of SNS services (Mastodon, Misskey, etc.), Subway Tooter saves the information and settings of login account to application data. This includes not only the public profile, also may include information that the login account only can read or update.
|
||||
|
||||
Except in the case of sending to the relevant SNS service for editing account information with accordance with user's explicit intent, Subway Tooter does not send this information to the outside of app.
|
||||
|
||||
When deleting account information from Subway Tooter, also the user information will be deleted from Subway Tooter's application data.
|
||||
|
||||
#### Activities on the SNS service
|
||||
|
||||
Subway Tooter has the function to read / update the activities from login account on SNS service such as posts, favorites, boosts, and lists.
|
||||
|
||||
Subway Tooter will not send these information to outside the application EXCEPT in the following cases.
|
||||
- With user's explicit intent, sending activity update/delete request to relevant SNS service.
|
||||
- With user's explicit intent, sharing URLs or datas to external web browser or other applications.
|
||||
- With user's explicit intent, downloading data to the folder that is accessible by other applications.
|
||||
|
||||
#### Information coming from the input plugin
|
||||
|
||||
Subway Tooter has a function to get information from input plugin (external application).
|
||||
With user's explicit intent, Those information may send to relevant SNS service.
|
||||
|
||||
#### User tracking
|
||||
|
||||
Subway Tooter uses Firebase Cloud Messaging for push notifications.
|
||||
Following information will be send to external server.
|
||||
- Firebase Cloud Messaging sends message recipient information to Google server.
|
||||
- Subway Tooter sends push subscription management information to the app server.
|
||||
|
||||
There is no other kind of user tracking.
|
||||
|
||||
#### User location
|
||||
|
||||
Subway Tooter has no function to get user location.
|
||||
The user location may be entered from input plugin, but Subway Tooter handles it as like as normal text.
|
||||
|
||||
#### Advertisement
|
||||
Subway Tooter has no function to show Advertisement.
|
||||
|
||||
There are cases SNS services may show ad in its API response, but Subway Tooter does not participate in those.
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
|
||||
## Subway Tooter プライバシー ポリシー
|
||||
|
||||
### Subway Tooter が取り扱うユーザデータ
|
||||
|
||||
#### SNSサービスのアクセストークン
|
||||
|
||||
Subway Tooter はMastodonやMisskey等のSNSサービスのアカウントの権限の認可を受けてそのアクセストークンをアプリ内部に保存します。
|
||||
|
||||
また古いMastodonの制限により、プッシュ通知の実現のためにユーザが許可した場合のみアクセストークンをアプリサーバや通知サーバに送ることがあります。
|
||||
|
||||
また現行のMastodonの制限により、プッシュ通知の実現のためにアクセストークンを復元不可能なダイジェスト化したものをアプリサーバに保存します。このダイジェストからアクセストークンを復元することはまず不可能です。
|
||||
|
||||
#### SNSサービスに登録したユーザ情報
|
||||
|
||||
Subway Tooter は登録されたログインアカウントの情報をアプリ内部に保存します。これは公開プロフィールの範囲だけではなく、ログインアカウントと紐付けられたアクセストークンを持つ本人だけが取得/変更可能な情報を含みます。
|
||||
|
||||
アカウント情報の編集のために当該SNSサービス自体に送る場合を除き、Subway Tooter はこの情報を外部に送信することはありません。
|
||||
|
||||
Subway Tooterからアカウント情報を削除した際に、この情報はSubway Tooterのアプリ内部からも削除されます。
|
||||
|
||||
#### SNSサービスで行われた活動
|
||||
|
||||
Subway Tooter はログインアカウントから行われた投稿、お気に入り、ブースト、リストなどの活動情報を取得/更新する機能を持ちます。
|
||||
|
||||
以下の場合を除き、Subway Tooter がこれらの情報をアプリ外部に送信することはありません。
|
||||
- 活動情報を更新するために当該SNSサービスに更新/削除リクエストを送る場合
|
||||
- ユーザの明示的な操作により、端末内のWebブラウザや他アプリにURLやデータを共有する場合
|
||||
- ユーザの明示的な操作により、端末内の他アプリがアクセス可能な場所にデータを保存する場合
|
||||
|
||||
#### 入力プラグイン経由で得られた情報
|
||||
|
||||
Subway Tooter は投稿フォームから入力プラグイン(外部アプリ)経由で情報を得る機能があります。ユーザの明示的な操作により得られたそれらの情報は、投稿内容の一部としてSNSサービスに送信されます。
|
||||
|
||||
#### ユーザトラッキング
|
||||
|
||||
Subway Tooter はPush通知の受信にFirebase Cloud Messagingを使うため、以下の情報をサーバに送信します。
|
||||
- (Firebase Cloud Messaging による) メッセージ送信先情報のGoogleサーバへの送信
|
||||
- (Subway Tooterによる) push購読の管理に必要な情報のSubway Tooter アプリサーバへの送信
|
||||
|
||||
|
||||
#### 位置情報
|
||||
Subway Tooter には位置情報を取り扱う機能はありません。
|
||||
入力プラグイン経由で間接的に位置情報が入力される場合がありえますが、Subway Tooterはその情報を通常のテキストと同程度に取り扱います。
|
||||
|
||||
#### 広告
|
||||
Subway Tooter には広告を表示する機能はありません。
|
||||
SNSサービスから取得した情報に何らかの広告が含まれる場合はありえますが、Subway Tooterはそれらの広告には関与しません。
|
||||
|
|
@ -760,5 +760,7 @@
|
|||
<string name="link_color">リンクの色 (アプリ再起動が必要)</string>
|
||||
<string name="missing_closeable_column">閉じれるカラムが表示範囲内にありません</string>
|
||||
<string name="dont_use_custom_tabs">リンクを開く際に (ChromeやFirefoxの) Custom Tabsを使わない</string>
|
||||
<string name="privacy_policy">プライバシーポリシー</string>
|
||||
<string name="agree">同意</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -778,5 +778,7 @@
|
|||
<string name="link_color">Link color (app restart required)</string>
|
||||
<string name="missing_closeable_column">missing closeable column in visible range.</string>
|
||||
<string name="dont_use_custom_tabs">Don\'t use (Chrome/Firefox) Custom Tabs when open links</string>
|
||||
<string name="privacy_policy">Privacy Policy</string>
|
||||
<string name="agree">Agree</string>
|
||||
|
||||
</resources>
|
||||
|
|
17
build.gradle
17
build.gradle
|
@ -1,20 +1,21 @@
|
|||
buildscript {
|
||||
|
||||
ext.kotlin_version = '1.2.70'
|
||||
ext.kotlinx_coroutines_version = '0.25.3'
|
||||
ext.anko_version='0.10.5'
|
||||
ext.asl_version='27.1.1'
|
||||
ext.target_sdk_version = 27
|
||||
ext.min_sdk_version = 21
|
||||
ext.target_sdk_version = 28
|
||||
ext.asl_version='28.0.0'
|
||||
|
||||
ext.kotlin_version = '1.2.71'
|
||||
ext.kotlinx_coroutines_version = '0.27.0'
|
||||
ext.anko_version='0.10.5'
|
||||
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
google()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.1.4'
|
||||
classpath 'com.android.tools.build:gradle:3.2.0'
|
||||
classpath 'com.google.gms:google-services:4.1.0'
|
||||
// https://android-developers.googleblog.com/2018/05/announcing-new-sdk-versioning.html では
|
||||
// com.google.gms:google-services:3.3.0 以降を使うように書いてあるんだけど、3.3.0にするとGradle sync に失敗する。
|
||||
|
@ -30,10 +31,10 @@ buildscript {
|
|||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
maven { url 'https://maven.google.com' }
|
||||
maven { url 'https://jitpack.io' }
|
||||
google()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
|
||||
|
|
Loading…
Reference in New Issue