diff --git a/apng_sample/src/main/java/jp/juggler/apng/sample/ActList.kt b/apng_sample/src/main/java/jp/juggler/apng/sample/ActList.kt index 0b782797..b71b08ff 100644 --- a/apng_sample/src/main/java/jp/juggler/apng/sample/ActList.kt +++ b/apng_sample/src/main/java/jp/juggler/apng/sample/ActList.kt @@ -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() @@ -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 : T) : WeakReference(t) { - operator fun invoke() : T? = get() -} - -fun ref(t : T) = WeakRef(t) diff --git a/apng_sample/src/main/java/jp/juggler/apng/sample/ActViewer.kt b/apng_sample/src/main/java/jp/juggler/apng/sample/ActViewer.kt index b88edca4..0e4cb424 100644 --- a/apng_sample/src/main/java/jp/juggler/apng/sample/ActViewer.kt +++ b/apng_sample/src/main/java/jp/juggler/apng/sample/ActViewer.kt @@ -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() ) { diff --git a/app/build.gradle b/app/build.gradle index d5ae6761..0ce1c810 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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 diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.kt b/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.kt index a42d3ebb..b15d76b3 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.kt @@ -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() { diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActMain.kt b/app/src/main/java/jp/juggler/subwaytooter/ActMain.kt index 16f5d0ad..4dfadcfa 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActMain.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActMain.kt @@ -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?=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() + } + + + } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActOSSLicense.kt b/app/src/main/java/jp/juggler/subwaytooter/ActOSSLicense.kt index c8453066..831c21a8 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActOSSLicense.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActOSSLicense.kt @@ -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(R.id.tvText) - tv.text = bao.toByteArray().decodeUTF8() - } - } + val tv = findViewById(R.id.tvText) + tv.text = loadRawResource(R.raw.oss_license).decodeUTF8() } catch(ex : Throwable) { log.trace(ex) } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt b/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt index eab6464b..27f480c6 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt @@ -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) diff --git a/app/src/main/java/jp/juggler/subwaytooter/Column.kt b/app/src/main/java/jp/juggler/subwaytooter/Column.kt index 728a138c..215ad68a 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/Column.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/Column.kt @@ -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 diff --git a/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.kt b/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.kt index b196a2c5..bde7a275 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.kt @@ -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 // diff --git a/app/src/main/java/jp/juggler/subwaytooter/Pref.kt b/app/src/main/java/jp/juggler/subwaytooter/Pref.kt index 37e3c3d0..1a932f2b 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/Pref.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/Pref.kt @@ -89,7 +89,7 @@ class StringPref( ) : BasePref(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) diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Toot.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Toot.kt index 4b5cb60b..55c07347 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Toot.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Toot.kt @@ -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( diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAccount.kt b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAccount.kt index 2280a7c9..03eb393e 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAccount.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAccount.kt @@ -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") } diff --git a/app/src/main/java/jp/juggler/subwaytooter/dialog/LoginForm.kt b/app/src/main/java/jp/juggler/subwaytooter/dialog/LoginForm.kt index 5e0a63c7..71d66f32 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/dialog/LoginForm.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/dialog/LoginForm.kt @@ -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(R.id.btnCancel).setOnClickListener { dialog.cancel() } val instance_list = ArrayList() 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() } diff --git a/app/src/main/java/jp/juggler/subwaytooter/table/AcctColor.kt b/app/src/main/java/jp/juggler/subwaytooter/table/AcctColor.kt index f28ff820..0af0759d 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/table/AcctColor.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/table/AcctColor.kt @@ -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(1) where_arg[0] = acct App1.database.query(table, null, load_where, where_arg, null, null, null) .use { cursor -> diff --git a/app/src/main/java/jp/juggler/subwaytooter/table/AcctSet.kt b/app/src/main/java/jp/juggler/subwaytooter/table/AcctSet.kt index a7e3c632..3cbf1f69 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/table/AcctSet.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/table/AcctSet.kt @@ -111,7 +111,7 @@ object AcctSet :TableCompanion{ fun searchPrefix(prefix : String, limit : Int) : ArrayList { try { - val where_arg = prefix_search_where_arg.get() + val where_arg = prefix_search_where_arg.get() ?: arrayOfNulls(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 -> diff --git a/app/src/main/java/jp/juggler/subwaytooter/table/SubscriptionServerKey.kt b/app/src/main/java/jp/juggler/subwaytooter/table/SubscriptionServerKey.kt index 9c8f90e4..b1d5cd17 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/table/SubscriptionServerKey.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/table/SubscriptionServerKey.kt @@ -46,7 +46,7 @@ object SubscriptionServerKey : TableCompanion { fun find(clientIdentifier : String) : String? { try { - val whereArgs = findWhereArgs.get() + val whereArgs = findWhereArgs.get() ?: arrayOfNulls(1) whereArgs[0] = clientIdentifier App1.database.query( table, diff --git a/app/src/main/java/jp/juggler/subwaytooter/table/TableUtils.kt b/app/src/main/java/jp/juggler/subwaytooter/table/TableUtils.kt index d84c2917..8b802378 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/table/TableUtils.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/table/TableUtils.kt @@ -1,6 +1,5 @@ package jp.juggler.subwaytooter.table -import android.database.Cursor import android.database.sqlite.SQLiteDatabase // SQLite にBooleanをそのまま保存することはできないのでInt型との変換が必要になる diff --git a/app/src/main/java/jp/juggler/subwaytooter/table/TagSet.kt b/app/src/main/java/jp/juggler/subwaytooter/table/TagSet.kt index 92325c39..065d62f6 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/table/TagSet.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/table/TagSet.kt @@ -124,7 +124,7 @@ object TagSet :TableCompanion{ val dst = ArrayList() try { - val where_arg = prefix_search_where_arg.get() + val where_arg = prefix_search_where_arg.get()?: arrayOfNulls(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) diff --git a/app/src/main/java/jp/juggler/subwaytooter/table/UserRelation.kt b/app/src/main/java/jp/juggler/subwaytooter/table/UserRelation.kt index 215d75c6..6ee8bbda 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/table/UserRelation.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/table/UserRelation.kt @@ -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(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 } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/table/UserRelationMisskey.kt b/app/src/main/java/jp/juggler/subwaytooter/table/UserRelationMisskey.kt index f7dd766b..cbe0313a 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/table/UserRelationMisskey.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/table/UserRelationMisskey.kt @@ -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(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 } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/BucketList.kt b/app/src/main/java/jp/juggler/subwaytooter/util/BucketList.kt index 8e600d59..4ac87746 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/BucketList.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/BucketList.kt @@ -60,7 +60,7 @@ class BucketList 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) { diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/HTMLDecoder.kt b/app/src/main/java/jp/juggler/subwaytooter/util/HTMLDecoder.kt index be836c31..71fdd5d4 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/HTMLDecoder.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/HTMLDecoder.kt @@ -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("…") diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/PopupAutoCompleteAcct.kt b/app/src/main/java/jp/juggler/subwaytooter/util/PopupAutoCompleteAcct.kt index 0570b39b..d87ea016 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/PopupAutoCompleteAcct.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/PopupAutoCompleteAcct.kt @@ -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) diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/PostHelper.kt b/app/src/main/java/jp/juggler/subwaytooter/util/PostHelper.kt index 3afbb6fc..930ee570 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/PostHelper.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/PostHelper.kt @@ -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) diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/ScrollPosition.kt b/app/src/main/java/jp/juggler/subwaytooter/util/ScrollPosition.kt index fb9ae609..582fb583 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/ScrollPosition.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/ScrollPosition.kt @@ -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) } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/StorageUtils.kt b/app/src/main/java/jp/juggler/subwaytooter/util/StorageUtils.kt index 87e9608c..67c6a3f4 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/StorageUtils.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/StorageUtils.kt @@ -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) { diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/Utils.kt b/app/src/main/java/jp/juggler/subwaytooter/util/Utils.kt index 6d0b39c0..c1c5faaf 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/Utils.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/Utils.kt @@ -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 diff --git a/app/src/main/java/jp/juggler/subwaytooter/view/MyNetworkImageView.kt b/app/src/main/java/jp/juggler/subwaytooter/view/MyNetworkImageView.kt index 2f00171d..cae1ccaf 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/view/MyNetworkImageView.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/view/MyNetworkImageView.kt @@ -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() } diff --git a/app/src/main/java/jp/juggler/subwaytooter/view/TabletModeRecyclerView.kt b/app/src/main/java/jp/juggler/subwaytooter/view/TabletModeRecyclerView.kt index 872ed7d6..c507706f 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/view/TabletModeRecyclerView.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/view/TabletModeRecyclerView.kt @@ -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() diff --git a/app/src/main/res/raw/privacy_policy_en.txt b/app/src/main/res/raw/privacy_policy_en.txt new file mode 100644 index 00000000..c3afbd15 --- /dev/null +++ b/app/src/main/res/raw/privacy_policy_en.txt @@ -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. diff --git a/app/src/main/res/raw/privacy_policy_fr.txt b/app/src/main/res/raw/privacy_policy_fr.txt new file mode 100644 index 00000000..3e5015e8 --- /dev/null +++ b/app/src/main/res/raw/privacy_policy_fr.txt @@ -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. + diff --git a/app/src/main/res/raw/privacy_policy_ja.txt b/app/src/main/res/raw/privacy_policy_ja.txt new file mode 100644 index 00000000..e68abf3a --- /dev/null +++ b/app/src/main/res/raw/privacy_policy_ja.txt @@ -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はそれらの広告には関与しません。 + diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index abd48dd7..e9410750 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -760,5 +760,7 @@ リンクの色 (アプリ再起動が必要) 閉じれるカラムが表示範囲内にありません リンクを開く際に (ChromeやFirefoxの) Custom Tabsを使わない + プライバシーポリシー + 同意 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b2aa6934..25a220df 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -778,5 +778,7 @@ Link color (app restart required) missing closeable column in visible range. Don\'t use (Chrome/Firefox) Custom Tabs when open links + Privacy Policy + Agree diff --git a/build.gradle b/build.gradle index ae88d269..f55f30ec 100644 --- a/build.gradle +++ b/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() } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 782f572c..3d09a315 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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