AsyncTaskからkotlin coroutinesへの移行

This commit is contained in:
tateisu 2020-04-07 13:52:54 +09:00
parent 2c77eaafae
commit 3defbe0cf3
18 changed files with 574 additions and 718 deletions

View File

@ -10,7 +10,6 @@ import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.media.RingtoneManager
import android.net.Uri
import android.os.AsyncTask
import android.os.Build
import android.os.Bundle
import android.os.Handler
@ -21,19 +20,20 @@ import android.text.TextWatcher
import android.view.View
import android.widget.*
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import jp.juggler.subwaytooter.Styler.defaultColorIcon
import jp.juggler.subwaytooter.api.*
import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.dialog.ActionsDialog
import jp.juggler.subwaytooter.dialog.ProgressDialogEx
import jp.juggler.subwaytooter.table.AcctColor
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.*
import jp.juggler.subwaytooter.view.MyNetworkImageView
import jp.juggler.util.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody
@ -44,8 +44,8 @@ import org.jetbrains.anko.textColor
import java.io.*
import kotlin.math.max
class ActAccountSetting
: AppCompatActivity(), View.OnClickListener, CompoundButton.OnCheckedChangeListener {
class ActAccountSetting : AsyncActivity(), View.OnClickListener,
CompoundButton.OnCheckedChangeListener {
companion object {
@ -313,14 +313,14 @@ class ActAccountSetting
R.id.etFieldName2,
R.id.etFieldName3,
R.id.etFieldName4
).map { requireNotNull(findViewById<EditText>(it)) }
).map { findViewById<EditText>(it) } // 型指定を除去してはいけない
listEtFieldValue = arrayOf(
R.id.etFieldValue1,
R.id.etFieldValue2,
R.id.etFieldValue3,
R.id.etFieldValue4
).map { requireNotNull(findViewById<EditText>(it)) }
).map { findViewById<EditText>(it) } // 型指定を除去してはいけない
btnFields = findViewById(R.id.btnFields)
@ -743,63 +743,37 @@ class ActAccountSetting
finish()
val task = @SuppressLint("StaticFieldLeak")
object : AsyncTask<Void, Void, String?>() {
fun unregister() {
try {
val install_id = PrefDevice.prefDevice(this@ActAccountSetting)
.getString(PrefDevice.KEY_INSTALL_ID, null)
if(install_id?.isEmpty() != false) {
log.d("performAccountRemove: missing install_id")
return
}
val tag = account.notification_tag
if(tag?.isEmpty() != false) {
log.d("performAccountRemove: missing notification_tag")
return
}
val call = App1.ok_http_client.newCall(
("instance_url=" + "https://${account.host.ascii}".encodePercent()
+ "&app_id=" + packageName.encodePercent()
+ "&tag=" + tag
)
.toFormRequestBody()
.toPost()
.url(PollingWorker.APP_SERVER + "/unregister")
.build()
)
val response = call.execute()
log.e("performAccountRemove: %s", response)
} catch(ex : Throwable) {
log.trace(ex, "performAccountRemove failed.")
}
GlobalScope.launch(Dispatchers.IO) {
try {
val install_id = PrefDevice.prefDevice(this@ActAccountSetting)
.getString(PrefDevice.KEY_INSTALL_ID, null)
if(install_id?.isEmpty() != false)
error("missing install_id")
}
override fun doInBackground(vararg params : Void) : String? {
unregister()
return null
}
override fun onCancelled(s : String?) {
onPostExecute(s)
}
override fun onPostExecute(s : String?) {
val tag = account.notification_tag
if(tag?.isEmpty() != false)
error("missing notification_tag")
val call = App1.ok_http_client.newCall(
("instance_url=" + "https://${account.host.ascii}".encodePercent()
+ "&app_id=" + packageName.encodePercent()
+ "&tag=" + tag
)
.toFormRequestBody()
.toPost()
.url(PollingWorker.APP_SERVER + "/unregister")
.build()
)
val response = call.execute()
log.e("performAccountRemove: %s", response)
} catch(ex : Throwable) {
log.trace(ex, "performAccountRemove failed.")
}
}
task.executeOnExecutor(App1.task_executor)
}
.show()
}
///////////////////////////////////////////////////
@ -1579,41 +1553,19 @@ class ActAccountSetting
return
}
val progress = ProgressDialogEx(this)
val task = @SuppressLint("StaticFieldLeak")
object : AsyncTask<Void, Void, InputStreamOpener?>() {
override fun doInBackground(vararg params : Void) : InputStreamOpener? {
return try {
createOpener(uri, mime_type)
} catch(ex : Throwable) {
showToast(this@ActAccountSetting, ex, "image converting failed.")
null
}
runWithProgress(
"preparing image",
{ createOpener(uri, mime_type) },
{
updateCredential(
when(request_code) {
REQUEST_CODE_HEADER_ATTACHMENT, REQUEST_CODE_HEADER_CAMERA -> "header"
else -> "avatar"
},
it
)
}
override fun onPostExecute(opener : InputStreamOpener?) {
progress.dismissSafe()
if(opener != null) {
updateCredential(
when(request_code) {
REQUEST_CODE_HEADER_ATTACHMENT, REQUEST_CODE_HEADER_CAMERA -> "header"
else -> "avatar"
},
opener
)
}
}
}
progress.isIndeterminateEx = true
progress.setMessageEx("preparing image…")
progress.setOnCancelListener { task.cancel(true) }
progress.show()
task.executeOnExecutor(App1.task_executor)
)
}
@SuppressLint("StaticFieldLeak")

View File

@ -1,12 +1,10 @@
package jp.juggler.subwaytooter
import android.annotation.SuppressLint
import android.content.Intent
import android.content.SharedPreferences
import android.graphics.Color
import android.graphics.Typeface
import android.net.Uri
import android.os.AsyncTask
import android.os.Bundle
import android.os.Handler
import android.text.Editable
@ -18,7 +16,6 @@ import android.view.Window
import android.widget.*
import androidx.annotation.ColorInt
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import com.jrummyapps.android.colorpicker.ColorPickerDialog
@ -26,7 +23,6 @@ import com.jrummyapps.android.colorpicker.ColorPickerDialogListener
import jp.juggler.subwaytooter.action.CustomShare
import jp.juggler.subwaytooter.action.CustomShareTarget
import jp.juggler.subwaytooter.dialog.DlgAppPicker
import jp.juggler.subwaytooter.dialog.ProgressDialogEx
import jp.juggler.subwaytooter.table.AcctColor
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.util.*
@ -44,7 +40,7 @@ import kotlin.Comparator
import kotlin.collections.ArrayList
import kotlin.math.abs
class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnClickListener {
class ActAppSetting : AsyncActivity(), ColorPickerDialogListener, View.OnClickListener {
companion object {
internal val log = LogCategory("ActAppSetting")
@ -780,93 +776,58 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
fun exportAppData() {
@Suppress("DEPRECATION")
val progress = ProgressDialogEx(this)
val task = @SuppressLint("StaticFieldLeak")
object : AsyncTask<Void, String, File?>() {
override fun doInBackground(vararg params : Void) : File? {
runWithProgress(
"export app data",
{
val cache_dir = cacheDir
cache_dir.mkdir()
try {
val cache_dir = cacheDir
cache_dir.mkdir()
val file = File(
cache_dir,
"SubwayTooter.${android.os.Process.myPid()}.${android.os.Process.myTid()}.zip"
)
// ZipOutputStreamオブジェクトの作成
ZipOutputStream(FileOutputStream(file)).use { zipStream ->
val file = File(
cache_dir,
"SubwayTooter.${android.os.Process.myPid()}.${android.os.Process.myTid()}.zip"
)
// ZipOutputStreamオブジェクトの作成
ZipOutputStream(FileOutputStream(file)).use { zipStream ->
// アプリデータjson
zipStream.putNextEntry(ZipEntry("AppData.json"))
try {
val jw = JsonWriter(OutputStreamWriter(zipStream, "UTF-8"))
AppDataExporter.encodeAppData(this@ActAppSetting, jw)
jw.flush()
} finally {
zipStream.closeEntry()
}
// カラム背景画像
val appState = App1.getAppState(this@ActAppSetting)
for(column in appState.column_list) {
AppDataExporter.saveBackgroundImage(
this@ActAppSetting,
zipStream,
column
)
}
// アプリデータjson
zipStream.putNextEntry(ZipEntry("AppData.json"))
try {
val jw = JsonWriter(OutputStreamWriter(zipStream, "UTF-8"))
AppDataExporter.encodeAppData(this@ActAppSetting, jw)
jw.flush()
} finally {
zipStream.closeEntry()
}
return file
} catch(ex : Throwable) {
log.trace(ex)
showToast(this@ActAppSetting, ex, "exportAppData failed.")
// カラム背景画像
val appState = App1.getAppState(this@ActAppSetting)
for(column in appState.column_list) {
AppDataExporter.saveBackgroundImage(
this@ActAppSetting,
zipStream,
column
)
}
}
return null
}
override fun onCancelled(result : File?) {
onPostExecute(result)
}
override fun onPostExecute(result : File?) {
progress.dismissSafe()
file
},
{
val uri = FileProvider.getUriForFile(
this@ActAppSetting,
App1.FILE_PROVIDER_AUTHORITY,
it
)
val intent = Intent(Intent.ACTION_SEND)
intent.type = contentResolver.getType(uri)
intent.putExtra(Intent.EXTRA_SUBJECT, "SubwayTooter app data")
intent.putExtra(Intent.EXTRA_STREAM, uri)
if(isCancelled || result == null) {
// cancelled.
return
}
try {
val uri = FileProvider.getUriForFile(
this@ActAppSetting,
App1.FILE_PROVIDER_AUTHORITY,
result
)
val intent = Intent(Intent.ACTION_SEND)
intent.type = contentResolver.getType(uri)
intent.putExtra(Intent.EXTRA_SUBJECT, "SubwayTooter app data")
intent.putExtra(Intent.EXTRA_STREAM, uri)
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivityForResult(intent, REQUEST_CODE_OTHER)
} catch(ex : Throwable) {
log.trace(ex)
showToast(this@ActAppSetting, ex, "exportAppData failed.")
}
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivityForResult(intent, REQUEST_CODE_OTHER)
}
}
progress.isIndeterminateEx = true
progress.setCancelable(true)
progress.setOnCancelListener { task.cancel(true) }
progress.show()
task.executeOnExecutor(App1.task_executor)
)
}
// open data picker

View File

@ -3,7 +3,6 @@ package jp.juggler.subwaytooter
import android.annotation.SuppressLint
import android.content.Intent
import android.net.Uri
import android.os.AsyncTask
import android.os.Bundle
import android.os.PersistableBundle
import android.os.Process
@ -13,11 +12,9 @@ import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.FileProvider
import jp.juggler.subwaytooter.api.entity.TootStatus
import jp.juggler.subwaytooter.dialog.ActionsDialog
import jp.juggler.subwaytooter.dialog.ProgressDialogEx
import jp.juggler.util.*
import org.apache.commons.io.IOUtils
import org.jetbrains.anko.textColor
@ -27,8 +24,7 @@ import java.io.FileOutputStream
import java.util.*
import kotlin.collections.ArrayList
class ActLanguageFilter : AppCompatActivity(), View.OnClickListener {
class ActLanguageFilter : AsyncActivity(), View.OnClickListener {
private class MyItem(
val code : String,
@ -397,84 +393,42 @@ class ActLanguageFilter : AppCompatActivity(), View.OnClickListener {
}
}
private fun export() {
val progress = ProgressDialogEx(this)
val data = JsonObject().apply {
for(item in languageList) {
put(item.code, item.allow)
private fun export() = runWithProgress(
"export language filter",
{
val data = JsonObject().apply {
for(item in languageList) {
put(item.code, item.allow)
}
}
.toString()
.encodeUTF8()
val cache_dir = cacheDir
cache_dir.mkdir()
val file = File(
cache_dir,
"SubwayTooter-language-filter.${Process.myPid()}.${Process.myTid()}.json"
)
FileOutputStream(file).use { it.write(data) }
file
},
{
val uri = FileProvider.getUriForFile(
this@ActLanguageFilter,
App1.FILE_PROVIDER_AUTHORITY,
it
)
val intent = Intent(Intent.ACTION_SEND)
intent.type = contentResolver.getType(uri)
intent.putExtra(Intent.EXTRA_SUBJECT, "SubwayTooter language filter data")
intent.putExtra(Intent.EXTRA_STREAM, uri)
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivityForResult(intent, REQUEST_CODE_OTHER)
}
.toString()
.encodeUTF8()
val task = @SuppressLint("StaticFieldLeak")
object : AsyncTask<Void, String, File?>() {
override fun doInBackground(vararg params : Void) : File? {
try {
val cache_dir = cacheDir
cache_dir.mkdir()
val file = File(
cache_dir,
"SubwayTooter-language-filter.${Process.myPid()}.${Process.myTid()}.json"
)
FileOutputStream(file).use { it.write(data) }
return file
} catch(ex : Throwable) {
log.trace(ex)
showToast(
this@ActLanguageFilter,
ex,
"can't save filter data to temporary file."
)
}
return null
}
override fun onCancelled(result : File?) {
onPostExecute(result)
}
override fun onPostExecute(result : File?) {
progress.dismissSafe()
if(isCancelled || result == null) {
// cancelled.
return
}
try {
val uri = FileProvider.getUriForFile(
this@ActLanguageFilter,
App1.FILE_PROVIDER_AUTHORITY,
result
)
val intent = Intent(Intent.ACTION_SEND)
intent.type = contentResolver.getType(uri)
intent.putExtra(Intent.EXTRA_SUBJECT, "SubwayTooter language filter data")
intent.putExtra(Intent.EXTRA_STREAM, uri)
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivityForResult(intent, REQUEST_CODE_OTHER)
} catch(ex : Throwable) {
log.trace(ex)
showToast(this@ActLanguageFilter, ex, "export failed.")
}
}
}
progress.isIndeterminateEx = true
progress.setCancelable(true)
progress.setOnCancelListener { task.cancel(true) }
progress.show()
task.executeOnExecutor(App1.task_executor)
}
)
private fun import() {
try {
@ -495,53 +449,22 @@ class ActLanguageFilter : AppCompatActivity(), View.OnClickListener {
super.onActivityResult(requestCode, resultCode, data)
}
private fun import2(uri : Uri) {
val type = contentResolver.getType(uri)
log.d("import2 type=%s", type)
val progress = ProgressDialogEx(this)
val task = @SuppressLint("StaticFieldLeak")
object : AsyncTask<Void, String, JsonObject?>() {
override fun doInBackground(vararg params : Void) : JsonObject? {
try {
val source = contentResolver.openInputStream(uri)
if(source == null) {
showToast(this@ActLanguageFilter, true, "openInputStream failed.")
return null
}
return source.use { inStream ->
val bao = ByteArrayOutputStream()
IOUtils.copy(inStream, bao)
bao.toByteArray().decodeUTF8().decodeJsonObject()
}
} catch(ex : Throwable) {
log.trace(ex)
showToast(this@ActLanguageFilter, ex, "can't load filter data.")
return null
private fun import2(uri : Uri) = runWithProgress(
"import language filter",
{
log.d("import2 type=${contentResolver.getType(uri)}")
val source = contentResolver.openInputStream(uri)
if(source == null) {
showToast( true, "openInputStream failed.")
null
} else {
source.use { inStream ->
val bao = ByteArrayOutputStream()
IOUtils.copy(inStream, bao)
bao.toByteArray().decodeUTF8().decodeJsonObject()
}
}
override fun onCancelled(result : JsonObject?) {
onPostExecute(result)
}
override fun onPostExecute(result : JsonObject?) {
progress.dismissSafe()
// cancelled.
if(isCancelled || result == null) return
load(result)
}
}
progress.isIndeterminateEx = true
progress.setCancelable(true)
progress.setOnCancelListener { task.cancel(true) }
progress.show()
task.executeOnExecutor(App1.task_executor)
}
},
{ if(it != null) load(it) }
)
}

View File

@ -1,6 +1,5 @@
package jp.juggler.subwaytooter
import android.annotation.SuppressLint
import android.app.Activity
import android.app.Dialog
import android.content.Intent
@ -19,8 +18,6 @@ import android.view.*
import android.view.inputmethod.EditorInfo
import android.widget.*
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.GravityCompat
import androidx.drawerlayout.widget.DrawerLayout
import androidx.recyclerview.widget.LinearLayoutManager
@ -31,13 +28,17 @@ import jp.juggler.subwaytooter.api.*
import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.api.entity.TootStatus.Companion.findStatusIdFromUrl
import jp.juggler.subwaytooter.api.entity.TootTag.Companion.findHashtagFromUrl
import jp.juggler.subwaytooter.dialog.*
import jp.juggler.subwaytooter.dialog.AccountPicker
import jp.juggler.subwaytooter.dialog.ActionsDialog
import jp.juggler.subwaytooter.dialog.DlgQuickTootMenu
import jp.juggler.subwaytooter.dialog.DlgTextInput
import jp.juggler.subwaytooter.span.MyClickableSpan
import jp.juggler.subwaytooter.table.AcctColor
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.*
import jp.juggler.subwaytooter.view.*
import jp.juggler.util.*
import kotlinx.coroutines.delay
import org.apache.commons.io.IOUtils
import org.jetbrains.anko.backgroundDrawable
import java.io.File
@ -51,7 +52,7 @@ import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
class ActMain : AppCompatActivity()
class ActMain : AsyncActivity()
, Column.Callback
, View.OnClickListener
, ViewPager.OnPageChangeListener
@ -1320,7 +1321,7 @@ class ActMain : AppCompatActivity()
drawer = findViewById(R.id.drawer_layout)
drawer.addDrawerListener(this)
drawer.setExclusionSize( stripIconSize)
drawer.setExclusionSize(stripIconSize)
@ -1548,7 +1549,7 @@ class ActMain : AppCompatActivity()
}
)
internal fun updateColumnStrip() {
private fun updateColumnStrip() {
llEmpty.vg(app_state.column_list.isEmpty())
val iconSize = stripIconSize
@ -1599,7 +1600,7 @@ class ActMain : AppCompatActivity()
scrollToColumn(idx)
}
viewRoot.contentDescription = column.getColumnName(true)
viewRoot.backgroundDrawable = getAdaptiveRippleDrawableRound(
this,
column.getHeaderBackgroundColor(),
@ -2523,10 +2524,11 @@ class ActMain : AppCompatActivity()
)
val colorRipple =
footer_button_fg_color.notZero() ?: getAttributeColor(this, R.attr.colorRippleEffect)
btnMenu.backgroundDrawable = getAdaptiveRippleDrawableRound(this,colorBg, colorRipple)
btnToot.backgroundDrawable = getAdaptiveRippleDrawableRound(this,colorBg, colorRipple)
btnQuickToot.backgroundDrawable = getAdaptiveRippleDrawableRound(this,colorBg, colorRipple)
btnQuickTootMenu.backgroundDrawable = getAdaptiveRippleDrawableRound(this,colorBg, colorRipple)
btnMenu.backgroundDrawable = getAdaptiveRippleDrawableRound(this, colorBg, colorRipple)
btnToot.backgroundDrawable = getAdaptiveRippleDrawableRound(this, colorBg, colorRipple)
btnQuickToot.backgroundDrawable = getAdaptiveRippleDrawableRound(this, colorBg, colorRipple)
btnQuickTootMenu.backgroundDrawable =
getAdaptiveRippleDrawableRound(this, colorBg, colorRipple)
val csl = ColorStateList.valueOf(
footer_button_fg_color.notZero()
@ -2738,159 +2740,125 @@ class ActMain : AppCompatActivity()
uri ?: return
// remove all columns
run {
phoneOnly { env -> env.pager.adapter = null }
for(c in app_state.column_list) {
c.dispose()
}
app_state.column_list.clear()
phoneTab(
{ env -> env.pager.adapter = env.pager_adapter },
{ env -> resizeColumnWidth(env) }
)
updateColumnStrip()
phoneOnly { env -> env.pager.adapter = null }
for(c in app_state.column_list) {
c.dispose()
}
app_state.column_list.clear()
@Suppress("DEPRECATION")
val progress = ProgressDialogEx(this)
phoneTab(
{ env -> env.pager.adapter = env.pager_adapter },
{ env -> resizeColumnWidth(env) }
)
val task = @SuppressLint("StaticFieldLeak") object :
AsyncTask<Void, String, ArrayList<Column>?>() {
updateColumnStrip()
runWithProgress(
"importing app data",
fun setProgressMessage(sv : String) {
runOnMainLooper {
progress.setMessageEx(sv)
}
}
override fun doInBackground(vararg params : Void) : ArrayList<Column>? {
doInBackground = { progress ->
fun setProgressMessage(sv : String) =
runOnMainLooper { progress.setMessageEx(sv) }
var newColumnList : ArrayList<Column>? = null
setProgressMessage("import data to local storage...")
// アプリ内領域に一時ファイルを作ってコピーする
val cacheDir = cacheDir
cacheDir.mkdir()
val file = File(
cacheDir,
"SubwayTooter.${Process.myPid()}.${Process.myTid()}.tmp"
)
val source = contentResolver.openInputStream(uri)
if(source == null) {
showToast(true, "openInputStream failed.")
return@runWithProgress null
}
source.use { inStream ->
FileOutputStream(file).use { outStream ->
IOUtils.copy(inStream, outStream)
}
}
// 通知サービスを止める
setProgressMessage("syncing notification poller…")
PollingWorker.queueAppDataImportBefore(this@ActMain)
while(PollingWorker.mBusyAppDataImportBefore.get()) {
delay(1000L)
log.d("syncing polling task...")
}
// データを読み込む
setProgressMessage("reading app data...")
var zipEntryCount = 0
try {
setProgressMessage("import data to local storage...")
// アプリ内領域に一時ファイルを作ってコピーする
val cacheDir = cacheDir
cacheDir.mkdir()
val file = File(
cacheDir,
"SubwayTooter.${Process.myPid()}.${Process.myTid()}.tmp"
)
val source = contentResolver.openInputStream(uri)
if(source == null) {
showToast(this@ActMain, true, "openInputStream failed.")
return null
}
source.use { inStream ->
FileOutputStream(file).use { outStream ->
IOUtils.copy(inStream, outStream)
}
}
// 通知サービスを止める
setProgressMessage("syncing notification poller…")
PollingWorker.queueAppDataImportBefore(this@ActMain)
while(PollingWorker.mBusyAppDataImportBefore.get()) {
Thread.sleep(1000L)
log.d("syncing polling task...")
}
// データを読み込む
setProgressMessage("reading app data...")
var zipEntryCount = 0
try {
ZipInputStream(FileInputStream(file)).use { zipStream ->
while(true) {
val entry = zipStream.nextEntry ?: break
++ zipEntryCount
try {
//
val entryName = entry.name
if(entryName.endsWith(".json")) {
newColumnList = AppDataExporter.decodeAppData(
this@ActMain,
JsonReader(InputStreamReader(zipStream, "UTF-8"))
)
continue
}
if(AppDataExporter.restoreBackgroundImage(
this@ActMain,
newColumnList,
zipStream,
entryName
)
) {
continue
}
} finally {
zipStream.closeEntry()
ZipInputStream(FileInputStream(file)).use { zipStream ->
while(true) {
val entry = zipStream.nextEntry ?: break
++ zipEntryCount
try {
//
val entryName = entry.name
if(entryName.endsWith(".json")) {
newColumnList = AppDataExporter.decodeAppData(
this@ActMain,
JsonReader(InputStreamReader(zipStream, "UTF-8"))
)
continue
}
if(AppDataExporter.restoreBackgroundImage(
this@ActMain,
newColumnList,
zipStream,
entryName
)
) {
continue
}
} finally {
zipStream.closeEntry()
}
}
} catch(ex : Throwable) {
log.trace(ex)
if(zipEntryCount != 0) {
showToast(this@ActMain, ex, "importAppData failed.")
}
}
// zipではなかった場合、zipEntryがない状態になる。例外はPH-1では出なかったが、出ても問題ないようにする。
if(zipEntryCount == 0) {
InputStreamReader(FileInputStream(file), "UTF-8").use { inStream ->
newColumnList = AppDataExporter.decodeAppData(
this@ActMain,
JsonReader(inStream)
)
}
}
} catch(ex : Throwable) {
log.trace(ex)
showToast(this@ActMain, ex, "importAppData failed.")
}
return newColumnList
}
override fun onCancelled(result : ArrayList<Column>?) {
onPostExecute(result)
}
override fun onPostExecute(result : ArrayList<Column>?) {
progress.dismissSafe()
try {
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
} catch(ignored : Throwable) {
}
try {
if(isCancelled || result == null) {
// cancelled.
return
if(zipEntryCount != 0) {
showToast(this@ActMain, ex, "importAppData failed.")
}
run {
phoneOnly { env -> env.pager.adapter = null }
app_state.column_list.clear()
app_state.column_list.addAll(result)
app_state.saveColumnList()
phoneTab(
{ env -> env.pager.adapter = env.pager_adapter },
{ env -> resizeColumnWidth(env) }
}
// zipではなかった場合、zipEntryがない状態になる。例外はPH-1では出なかったが、出ても問題ないようにする。
if(zipEntryCount == 0) {
InputStreamReader(FileInputStream(file), "UTF-8").use { inStream ->
newColumnList = AppDataExporter.decodeAppData(
this@ActMain,
JsonReader(inStream)
)
updateColumnStrip()
}
}
newColumnList
},
afterProc = {
// cancelled.
if(it == null) return@runWithProgress
try {
phoneOnly { env -> env.pager.adapter = null }
app_state.column_list.clear()
app_state.column_list.addAll(it)
app_state.saveColumnList()
phoneTab(
{ env -> env.pager.adapter = env.pager_adapter },
{ env -> resizeColumnWidth(env) }
)
updateColumnStrip()
} finally {
// 通知サービスをリスタート
PollingWorker.queueAppDataImportAfter(this@ActMain)
@ -2898,19 +2866,14 @@ class ActMain : AppCompatActivity()
showToast(this@ActMain, true, R.string.import_completed_please_restart_app)
finish()
},
preProc = {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
},
postProc = {
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
}
try {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
} catch(ignored : Throwable) {
}
progress.isIndeterminateEx = true
progress.setCancelable(false)
progress.setOnCancelListener { task.cancel(true) }
progress.show()
task.executeOnExecutor(App1.task_executor)
)
}
override fun onDrawerSlide(drawerView : View, slideOffset : Float) {

View File

@ -11,21 +11,25 @@ import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.net.Uri
import android.os.*
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.SystemClock
import android.provider.MediaStore
import android.text.*
import androidx.core.view.inputmethod.InputConnectionCompat
import androidx.core.view.inputmethod.InputContentInfoCompat
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import android.text.Editable
import android.text.InputType
import android.text.TextWatcher
import android.text.method.LinkMovementMethod
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.view.inputmethod.EditorInfo
import android.widget.*
import androidx.appcompat.app.AlertDialog
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.view.inputmethod.InputConnectionCompat
import androidx.core.view.inputmethod.InputContentInfoCompat
import jp.juggler.subwaytooter.Styler.defaultColorIcon
import jp.juggler.subwaytooter.api.*
import jp.juggler.subwaytooter.api.entity.*
@ -39,6 +43,7 @@ import jp.juggler.subwaytooter.view.FocusPointView
import jp.juggler.subwaytooter.view.MyEditText
import jp.juggler.subwaytooter.view.MyNetworkImageView
import jp.juggler.util.*
import kotlinx.coroutines.isActive
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody
@ -54,7 +59,7 @@ import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.math.max
class ActPost : AppCompatActivity(),
class ActPost : AsyncActivity(),
View.OnClickListener,
PostAttachment.Callback {
@ -397,17 +402,17 @@ class ActPost : AppCompatActivity(),
private lateinit var btnPost : ImageButton
private lateinit var llAttachment : View
private lateinit var ivMedia : List<MyNetworkImageView>
internal lateinit var cbNSFW : CheckBox
internal lateinit var cbContentWarning : CheckBox
internal lateinit var etContentWarning : MyEditText
internal lateinit var etContent : MyEditText
private lateinit var cbNSFW : CheckBox
private lateinit var cbContentWarning : CheckBox
private lateinit var etContentWarning : MyEditText
private lateinit var etContent : MyEditText
private lateinit var btnFeaturedTag : ImageButton
internal lateinit var cbQuote : CheckBox
private lateinit var cbQuote : CheckBox
internal lateinit var spEnquete : Spinner
private lateinit var spEnquete : Spinner
private lateinit var llEnquete : View
internal lateinit var list_etChoice : List<MyEditText>
private lateinit var list_etChoice : List<MyEditText>
private lateinit var cbMultipleChoice : CheckBox
private lateinit var cbHideTotals : CheckBox
@ -433,7 +438,7 @@ class ActPost : AppCompatActivity(),
internal lateinit var pref : SharedPreferences
internal lateinit var app_state : AppState
private lateinit var post_helper : PostHelper
internal var attachment_list = ArrayList<PostAttachment>()
private var attachment_list = ArrayList<PostAttachment>()
private var isPostComplete : Boolean = false
internal var density : Float = 0f
@ -478,9 +483,9 @@ class ActPost : AppCompatActivity(),
/////////////////////////////////////////////////
internal var in_reply_to_id : EntityId? = null
internal var in_reply_to_text : String? = null
internal var in_reply_to_image : String? = null
internal var in_reply_to_url : String? = null
private var in_reply_to_text : String? = null
private var in_reply_to_image : String? = null
private var in_reply_to_url : String? = null
private var mushroom_input : Int = 0
private var mushroom_start : Int = 0
private var mushroom_end : Int = 0
@ -812,7 +817,7 @@ class ActPost : AppCompatActivity(),
// 比較する前にデフォルトの公開範囲を計算する
visibility = visibility
?: account.visibility
?: TootVisibility.Public
// ?: TootVisibility.Public
// VISIBILITY_WEB_SETTING だと 1.5未満のタンスでトラブルになる
if(TootVisibility.WebSetting == visibility) {
@ -1446,7 +1451,7 @@ class ActPost : AppCompatActivity(),
etContentWarning.visibility = if(cbContentWarning.isChecked) View.VISIBLE else View.GONE
}
internal fun selectAccount(a : SavedAccount?) {
private fun selectAccount(a : SavedAccount?) {
this.account = a
if(a == null) {
post_helper.setInstance(null, false)
@ -2661,7 +2666,7 @@ class ActPost : AppCompatActivity(),
cbQuote.visibility = if(in_reply_to_id != null) View.VISIBLE else View.GONE
}
internal fun showReplyTo() {
private fun showReplyTo() {
if(in_reply_to_id == null) {
llReply.visibility = View.GONE
} else {
@ -2773,16 +2778,13 @@ class ActPost : AppCompatActivity(),
private fun restoreDraft(draft : JsonObject) {
@Suppress("DEPRECATION")
val progress = ProgressDialogEx(this)
val task = @SuppressLint("StaticFieldLeak")
object : AsyncTask<Void, String, String?>() {
val list_warning = ArrayList<String>()
var account : SavedAccount? = null
override fun doInBackground(vararg params : Void) : String? {
val list_warning = ArrayList<String>()
var target_account : SavedAccount? = null
runWithProgress(
"restore from draft",
{progress->
fun isTaskCancelled() = !this.coroutineContext.isActive
var content = draft.string(DRAFT_CONTENT) ?: ""
val account_db_id = draft.long(DRAFT_ACCOUNT_DB_ID) ?: - 1L
@ -2812,15 +2814,16 @@ class ActPost : AppCompatActivity(),
} catch(ignored : JsonException) {
}
return "OK"
return@runWithProgress "OK"
}
this.account = account
target_account = account
// アカウントがあるなら基本的にはすべての情報を復元できるはずだが、いくつか確認が必要だ
val api_client = TootApiClient(this@ActPost, callback = object : TootApiCallback {
override val isApiCancelled : Boolean
get() = isCancelled
get() = isTaskCancelled()
override fun publishApiProgress(s : String) {
runOnMainLooper {
@ -2833,7 +2836,7 @@ class ActPost : AppCompatActivity(),
if(in_reply_to_id != null) {
val result = api_client.request("/api/v1/statuses/$in_reply_to_id")
if(isCancelled) return null
if(isTaskCancelled()) return@runWithProgress null
val jsonObject = result?.jsonObject
if(jsonObject == null) {
list_warning.add(getString(R.string.reply_to_in_draft_is_lost))
@ -2848,7 +2851,7 @@ class ActPost : AppCompatActivity(),
var isSomeAttachmentRemoved = false
val it = tmp_attachment_list.iterator()
while(it.hasNext()) {
if(isCancelled) return null
if(isTaskCancelled()) return@runWithProgress null
val ta = TootAttachment.decodeJson(it.next())
if(check_exist(ta.url)) continue
it.remove()
@ -2869,20 +2872,11 @@ class ActPost : AppCompatActivity(),
log.trace(ex)
}
return "OK"
}
override fun onCancelled(result : String?) {
onPostExecute(result)
}
override fun onPostExecute(result : String?) {
progress.dismissSafe()
if(isCancelled || result == null) {
// cancelled.
return
}
"OK"
},
{result->
// cancelled.
if( result == null) return@runWithProgress
val content = draft.string(DRAFT_CONTENT) ?: ""
val content_warning = draft.string(DRAFT_CONTENT_WARNING) ?: ""
@ -2935,7 +2929,7 @@ class ActPost : AppCompatActivity(),
}
}
if(account != null) selectAccount(account)
if(target_account != null) selectAccount(target_account)
if(tmp_attachment_list?.isNotEmpty() == true) {
attachment_list.clear()
@ -2973,13 +2967,7 @@ class ActPost : AppCompatActivity(),
.show()
}
}
}
progress.isIndeterminateEx = true
progress.setCancelable(true)
progress.setOnCancelListener { task.cancel(true) }
progress.show()
task.executeOnExecutor(App1.task_executor)
)
}
private fun prepareMushroomText(et : EditText) : String {

View File

@ -5,7 +5,6 @@ import android.content.*
import android.media.Ringtone
import android.media.RingtoneManager
import android.net.Uri
import android.os.AsyncTask
import android.os.Handler
import android.os.SystemClock
import android.speech.tts.TextToSpeech
@ -20,6 +19,10 @@ import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.NetworkStateTracker
import jp.juggler.subwaytooter.util.PostAttachment
import jp.juggler.util.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.apache.commons.io.IOUtils
import java.io.ByteArrayOutputStream
import java.io.File
@ -352,19 +355,14 @@ class AppState(internal val context : Context, internal val pref : SharedPrefere
showToast(context, false, R.string.text_to_speech_initializing)
log.d("initializing TextToSpeech…")
object : AsyncTask<Void, Void, TextToSpeech?>() {
GlobalScope.launch(Dispatchers.IO) {
var tmp_tts : TextToSpeech? = null
override fun doInBackground(vararg params : Void) : TextToSpeech {
val tts = TextToSpeech(context, tts_init_listener)
this.tmp_tts = tts
return tts
}
val tts_init_listener : TextToSpeech.OnInitListener =
TextToSpeech.OnInitListener { status ->
val tts = this.tmp_tts
val tts = tmp_tts
if(tts == null || TextToSpeech.SUCCESS != status) {
showToast(
context,
@ -443,8 +441,11 @@ class AppState(internal val context : Context, internal val pref : SharedPrefere
}
}
}.executeOnExecutor(App1.task_executor)
tmp_tts = TextToSpeech(context, tts_init_listener)
}
return
}
if(! willSpeechEnabled && tts != null) {
showToast(context, false, R.string.text_to_speech_shutdown)
log.d("shutdown TextToSpeech…")
@ -516,18 +517,18 @@ class AppState(internal val context : Context, internal val pref : SharedPrefere
if(dedupMode != DedupMode.None) {
synchronized(this) {
val check = duplication_check.find { it.text.equals(sv,ignoreCase = true) }
val check = duplication_check.find { it.text.equals(sv, ignoreCase = true) }
if(check == null) {
duplication_check.addLast(DedupItem(sv))
if(duplication_check.size > 60) duplication_check.removeFirst()
} else{
} else {
val now = SystemClock.elapsedRealtime()
val delta = now - check.time
// 古い項目が残っていることがあるので、check.timeの更新は必須
check.time = now
if(dedupMode == DedupMode.Recent) return
if(dedupMode == DedupMode.RecentExpire && delta < 5000L ) return
if(dedupMode == DedupMode.RecentExpire && delta < 5000L) return
}
}
}
@ -576,7 +577,8 @@ class AppState(internal val context : Context, internal val pref : SharedPrefere
return false
}
if(item.sound_type == HighlightWord.SOUND_TYPE_CUSTOM && item.sound_uri.mayUri().tryRingtone()) return
if(item.sound_type == HighlightWord.SOUND_TYPE_CUSTOM && item.sound_uri.mayUri()
.tryRingtone()) return
RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION).tryRingtone()
}

View File

@ -0,0 +1,95 @@
package jp.juggler.subwaytooter
import android.os.Bundle
import android.os.PersistableBundle
import androidx.appcompat.app.AppCompatActivity
import jp.juggler.subwaytooter.dialog.ProgressDialogEx
import jp.juggler.util.LogCategory
import jp.juggler.util.dismissSafe
import jp.juggler.util.showToast
import kotlinx.coroutines.*
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.coroutines.CoroutineContext
abstract class AsyncActivity : AppCompatActivity(), CoroutineScope {
companion object{
private val log =LogCategory("AsyncActivity")
}
private lateinit var job : Job
override val coroutineContext : CoroutineContext
get() = job + Dispatchers.Main
override fun onCreate(savedInstanceState : Bundle?, persistentState : PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
job = Job()
}
override fun onDestroy() {
super.onDestroy()
(job + Dispatchers.Default).cancel()
}
fun showToast(bLong : Boolean, fmt : String?, vararg args : Any) =
showToast(this, bLong, fmt, *args)
fun showToast(ex : Throwable, fmt : String?, vararg args : Any) =
showToast(this, ex, fmt, *args)
fun showToast(bLong : Boolean, string_id : Int, vararg args : Any) =
showToast(this, bLong, string_id, *args)
fun showToast(ex : Throwable, string_id : Int, vararg args : Any) =
showToast(this, ex, string_id, *args)
fun <T : Any?> runWithProgress(
caption : String,
doInBackground : suspend CoroutineScope.(ProgressDialogEx) -> T,
afterProc : suspend CoroutineScope.(result : T) -> Unit = {},
progressInitializer : suspend CoroutineScope.(ProgressDialogEx) -> Unit = {},
preProc : suspend CoroutineScope.() -> Unit ={},
postProc : suspend CoroutineScope.() -> Unit ={}
) {
val progress = ProgressDialogEx(this)
val task = async(Dispatchers.IO) {
doInBackground(progress)
}
launch {
try{
preProc()
}catch(ex:Throwable){
log.trace(ex)
}
progress.setCancelable(true)
progress.setOnCancelListener {task.cancel()}
progress.isIndeterminateEx = true
progress.setMessageEx("${caption}")
progressInitializer(progress)
progress.show()
try {
val result = try {
task.await()
}catch(ex:CancellationException){
null
}
// TODO キャンセル時に呼ぶ必要がある利用例があるかどうか調べる
if(result!=null) afterProc(result)
} catch(ex : Throwable) {
showToast(this@AsyncActivity, ex, "$caption failed.")
} finally {
progress.dismissSafe()
try{
postProc()
}catch(ex:Throwable){
log.trace(ex)
}
}
}
}
}

View File

@ -1450,7 +1450,7 @@ class Column(
private fun cancelLastTask() {
if(lastTask != null) {
lastTask?.cancel(true)
lastTask?.cancel()
lastTask = null
//
bInitialLoading = false
@ -1929,7 +1929,7 @@ class Column(
@SuppressLint("StaticFieldLeak")
val task = ColumnTask_Loading(this)
this.lastTask = task
task.executeOnExecutor(App1.task_executor)
task.start()
}
private var bMinIdMatched : Boolean = false
@ -2141,7 +2141,7 @@ class Column(
@SuppressLint("StaticFieldLeak")
val task = ColumnTask_Refresh(this, bSilent, bBottom, posted_status_id, refresh_after_toot)
this.lastTask = task
task.executeOnExecutor(App1.task_executor)
task.start()
fireShowColumnStatus()
}
@ -2165,9 +2165,8 @@ class Column(
@SuppressLint("StaticFieldLeak")
val task = ColumnTask_Gap(this, gap)
this.lastTask = task
task.executeOnExecutor(App1.task_executor)
task.start()
fireShowColumnStatus()
}

View File

@ -2,7 +2,6 @@ package jp.juggler.subwaytooter
import android.content.Context
import android.content.SharedPreferences
import android.os.AsyncTask
import android.os.SystemClock
import jp.juggler.subwaytooter.api.TootApiClient
import jp.juggler.subwaytooter.api.TootApiResult
@ -12,6 +11,8 @@ import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.util.JsonObject
import jp.juggler.util.WordTrieTree
import jp.juggler.util.notEmpty
import jp.juggler.util.withCaption
import kotlinx.coroutines.*
import java.util.concurrent.atomic.AtomicBoolean
enum class ColumnTaskType {
@ -24,15 +25,17 @@ enum class ColumnTaskType {
abstract class ColumnTask(
val column : Column,
val ctType : ColumnTaskType
) : AsyncTask<Void, Void, TootApiResult?>() {
override fun onCancelled(result : TootApiResult?) {
onPostExecute(null)
}
) {
val ctStarted = AtomicBoolean(false)
val ctClosed = AtomicBoolean(false)
var job : Job? = null
val isCancelled :Boolean
get()= job?.isCancelled ?: false
var parser = TootParser(context, access_info, highlightTrie = highlight_trie)
var list_tmp : ArrayList<TimelineItem>? = null
@ -170,4 +173,28 @@ abstract class ColumnTask(
}
return TootApiResult()
}
fun cancel() {
job?.cancel()
}
abstract fun doInBackground() : TootApiResult?
abstract fun onPostExecute(result : TootApiResult?)
fun start() {
job = GlobalScope.launch(Dispatchers.Main){
val result = try {
withContext(Dispatchers.IO){
doInBackground()
}
}catch(ex:CancellationException){
null // キャンセルされたらresult==nullとする
}catch(ex:Throwable){
// その他のエラー
TootApiResult(ex.withCaption("error"))
}
onPostExecute(result)
}
}
}

View File

@ -17,7 +17,7 @@ class ColumnTask_Gap(
private var max_id : EntityId? = (gap as? TootGap)?.max_id
private var since_id : EntityId? = (gap as? TootGap)?.since_id
override fun doInBackground(vararg unused : Void) : TootApiResult? {
override fun doInBackground() : TootApiResult? {
ctStarted.set(true)
val client = TootApiClient(context, callback = object : TootApiCallback {

View File

@ -18,7 +18,7 @@ class ColumnTask_Loading(
internal var list_pinned : ArrayList<TimelineItem>? = null
override fun doInBackground(vararg unused : Void) : TootApiResult? {
override fun doInBackground() : TootApiResult? {
ctStarted.set(true)
if(Pref.bpInstanceTicker(pref)) {

View File

@ -26,7 +26,7 @@ class ColumnTask_Refresh(
private var filterUpdated = false
override fun doInBackground(vararg unused : Void) : TootApiResult? {
override fun doInBackground() : TootApiResult? {
ctStarted.set(true)
val client = TootApiClient(context, callback = object : TootApiCallback {

View File

@ -7,7 +7,6 @@ import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Path
import android.os.AsyncTask
import android.os.SystemClock
import android.text.InputType
import android.text.Spannable
@ -38,11 +37,16 @@ import jp.juggler.subwaytooter.dialog.EmojiPicker
import jp.juggler.subwaytooter.span.NetworkEmojiSpan
import jp.juggler.subwaytooter.table.AcctColor
import jp.juggler.subwaytooter.util.*
import jp.juggler.subwaytooter.view.*
import jp.juggler.subwaytooter.view.ListDivider
import jp.juggler.subwaytooter.view.MyLinkMovementMethod
import jp.juggler.subwaytooter.view.MyTextView
import jp.juggler.subwaytooter.view.OutsideDrawerLayout
import jp.juggler.util.*
import kotlinx.coroutines.*
import org.jetbrains.anko.*
import org.jetbrains.anko.custom.customView
import java.io.Closeable
import java.lang.Runnable
import java.lang.reflect.Field
import java.util.regex.Pattern
@ -178,7 +182,7 @@ class ColumnViewHolder(
private var last_image_uri : String? = null
private var last_image_bitmap : Bitmap? = null
private var last_image_task : AsyncTask<Void, Void, Bitmap?>? = null
private var last_image_task : Job? = null
private fun checkRegexFilterError(src : String) : String? {
try {
@ -808,7 +812,7 @@ class ColumnViewHolder(
last_image_bitmap?.recycle()
last_image_bitmap = null
last_image_task?.cancel(true)
last_image_task?.cancel()
last_image_task = null
last_image_uri = null
@ -842,41 +846,35 @@ class ColumnViewHolder(
val screen_h = iv.resources.displayMetrics.heightPixels
// 非同期処理を開始
val task = object : AsyncTask<Void, Void, Bitmap?>() {
override fun doInBackground(vararg params : Void) : Bitmap? {
return try {
createResizedBitmap(
activity, url.toUri(),
if(screen_w > screen_h)
screen_w
else
screen_h
)
} catch(ex : Throwable) {
log.trace(ex)
null
}
}
override fun onCancelled(bitmap : Bitmap?) {
onPostExecute(bitmap)
}
override fun onPostExecute(bitmap : Bitmap?) {
if(bitmap != null) {
if(isCancelled || url != last_image_uri) {
bitmap.recycle()
} else {
last_image_bitmap = bitmap
iv.setImageBitmap(last_image_bitmap)
iv.visibility = View.VISIBLE
last_image_task = GlobalScope.launch(Dispatchers.Main){
val bitmap = try{
withContext(Dispatchers.IO){
try {
createResizedBitmap(
activity, url.toUri(),
if(screen_w > screen_h)
screen_w
else
screen_h
)
} catch(ex : Throwable) {
log.trace(ex)
null
}
}
}catch(ex:Throwable){
null
}
if(bitmap != null) {
if(!coroutineContext.isActive || url != last_image_uri) {
bitmap.recycle()
} else {
last_image_bitmap = bitmap
iv.setImageBitmap(last_image_bitmap)
iv.visibility = View.VISIBLE
}
}
}
last_image_task = task
task.executeOnExecutor(App1.task_executor)
} catch(ex : Throwable) {
log.trace(ex)
}

View File

@ -2,22 +2,21 @@ package jp.juggler.subwaytooter.api
import android.app.Activity
import android.content.Context
import android.os.AsyncTask
import android.os.Handler
import android.os.SystemClock
import java.lang.ref.WeakReference
import java.text.NumberFormat
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.api.entity.Host
import jp.juggler.subwaytooter.dialog.ProgressDialogEx
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.util.dismissSafe
import jp.juggler.util.withCaption
import kotlinx.coroutines.*
import java.lang.Runnable
import java.lang.ref.WeakReference
import java.text.NumberFormat
import java.util.concurrent.atomic.AtomicBoolean
/*
非同期タスク(TootTask)を実行します
- 内部でAsyncTaskを使いますAndroid Lintの警告を抑制します
APIクライアントを必要とする非同期タスク(TootTask)を実行します
- ProgressDialogを表示します抑制することも可能です
- TootApiClientの初期化を行います
- TootApiClientからの進捗イベントをProgressDialogに伝達します
@ -52,43 +51,15 @@ class TootTaskRunner(
internal var max = 1
}
// has AsyncTask
private class MyTask(
private val runner : TootTaskRunner
) : AsyncTask<Void, Void, TootApiResult?>() {
var isActive : Boolean = true
override fun doInBackground(vararg voids : Void) : TootApiResult? {
val callback = runner.callback
return if(callback == null) {
TootApiResult("callback is null")
} else {
callback.background(runner.client)
}
}
override fun onCancelled(result : TootApiResult?) {
onPostExecute(result)
}
override fun onPostExecute(result : TootApiResult?) {
isActive = false
runner.dismissProgress()
runner.callback?.handleResult(result)
}
}
private val handler : Handler
private val client : TootApiClient
private val task : MyTask
private val info = ProgressInfo()
private var progress : ProgressDialogEx? = null
private var progress_prefix : String? = null
private var task : Deferred<TootApiResult?>? = null
private val refContext : WeakReference<Context>
private var callback : TootTask? = null
private var last_message_shown : Long = 0
private val proc_progress_message = object : Runnable {
@ -105,19 +76,29 @@ class TootTaskRunner(
this.refContext = WeakReference(context)
this.handler = Handler(context.mainLooper)
this.client = TootApiClient(context, callback = this)
this.task = MyTask(this)
}
private val _isActive = AtomicBoolean(true)
val isActive : Boolean
get() = task.isActive
get() = _isActive.get()
fun run(callback : TootTask) : TootTaskRunner {
openProgress()
this.callback = callback
task.executeOnExecutor(App1.task_executor)
GlobalScope.launch(Dispatchers.Main) {
openProgress()
val result = try {
withContext(Dispatchers.IO) {
callback.background(client)
}
}catch(ex:CancellationException){
null
} catch(ex : Throwable) {
TootApiResult(ex.withCaption("error"))
}
_isActive.set(false)
dismissProgress()
callback.handleResult(result)
}
return this
}
@ -141,7 +122,7 @@ class TootTaskRunner(
// implements TootApiClient.Callback
override val isApiCancelled : Boolean
get() = task.isCancelled
get() = task?.isActive == false
override fun publishApiProgress(s : String) {
synchronized(this) {
@ -171,7 +152,7 @@ class TootTaskRunner(
val progress = ProgressDialogEx(context)
this.progress = progress
progress.setCancelable(true)
progress.setOnCancelListener { task.cancel(true) }
progress.setOnCancelListener { task?.cancel() }
progress.setProgressStyle(progress_style)
progressSetupCallback(progress)
showProgressMessage()

View File

@ -6,7 +6,7 @@ import jp.juggler.util.JsonObject
class MisskeyAntenna(parser:TootParser,src: JsonObject) :TimelineItem(){
val timeCreatedAt:Long // "2020-02-19T09:08:41.929Z"
private val timeCreatedAt:Long // "2020-02-19T09:08:41.929Z"
val id: EntityId
@ -19,7 +19,7 @@ class MisskeyAntenna(parser:TootParser,src: JsonObject) :TimelineItem(){
// "src":"list",
// "src":"users",
val keywords: Array<Array<String>>
private val keywords: Array<Array<String>>
// "keywords":[[""]],
// "keywords":[[""]],
// "keywords":[[""]],
@ -28,20 +28,20 @@ class MisskeyAntenna(parser:TootParser,src: JsonObject) :TimelineItem(){
// "keywords":[["test"]],
// src=="group" の場合。他はnull
val userGroupId : EntityId?
private val userGroupId : EntityId?
// src=="list" の場合。他はnull
val userListId : EntityId?
private val userListId : EntityId?
val users : Array<String>
// "users":[""],
// "users":["@tateisu","@syuilo"],
val caseSensitive:Boolean
val hasUnreadNote:Boolean
val notify: Boolean
val withFile : Boolean
val withReplies : Boolean
private val caseSensitive:Boolean
private val hasUnreadNote:Boolean
private val notify: Boolean
private val withFile : Boolean
private val withReplies : Boolean
init{
timeCreatedAt = TootStatus.parseTime(src.string("createdAt"))

View File

@ -3,7 +3,6 @@ package jp.juggler.subwaytooter.dialog
import android.annotation.SuppressLint
import android.content.DialogInterface
import android.database.Cursor
import android.os.AsyncTask
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
@ -12,17 +11,22 @@ import android.widget.ListView
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import jp.juggler.subwaytooter.ActPost
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.api.entity.TootStatus
import jp.juggler.subwaytooter.table.PostDraft
import jp.juggler.util.JsonObject
import jp.juggler.util.LogCategory
import jp.juggler.util.dismissSafe
import jp.juggler.util.showToast
import kotlinx.coroutines.*
class DlgDraftPicker : AdapterView.OnItemClickListener, AdapterView.OnItemLongClickListener,
DialogInterface.OnDismissListener {
companion object{
private val log = LogCategory("DlgDraftPicker")
}
private lateinit var activity : ActPost
private lateinit var callback : (draft : JsonObject) -> Unit
private lateinit var lvDraft : ListView
@ -32,7 +36,7 @@ class DlgDraftPicker : AdapterView.OnItemClickListener, AdapterView.OnItemLongCl
private var cursor : Cursor? = null
private var colIdx : PostDraft.ColIdx? = null
private var task : AsyncTask<Void, Void, Cursor?>? = null
private var task : Job? = null
override fun onItemClick(parent : AdapterView<*>, view : View, position : Int, id : Long) {
val json = getPostDraft(position)?.json
@ -61,7 +65,7 @@ class DlgDraftPicker : AdapterView.OnItemClickListener, AdapterView.OnItemLongCl
}
override fun onDismiss(dialog : DialogInterface) {
task?.cancel(true)
task?.cancel()
task = null
lvDraft.adapter = null
@ -98,37 +102,32 @@ class DlgDraftPicker : AdapterView.OnItemClickListener, AdapterView.OnItemLongCl
private fun reload() {
// cancel old task
task?.cancel(true)
task?.cancel()
val new_task = @SuppressLint("StaticFieldLeak")
object : AsyncTask<Void, Void, Cursor?>() {
override fun doInBackground(vararg params : Void) : Cursor? {
return PostDraft.createCursor()
task = GlobalScope.launch(Dispatchers.Main){
val cursor =try {
withContext(Dispatchers.IO) {
PostDraft.createCursor()
} ?: error("cursor is null")
}catch(ex:CancellationException) {
return@launch
}catch(ex:Throwable){
log.trace(ex)
showToast(activity,ex, "failed to loading drafts.")
return@launch
}
override fun onCancelled(cursor : Cursor?) {
onPostExecute(cursor)
}
override fun onPostExecute(cursor : Cursor?) {
if(! dialog.isShowing) {
// dialog is already closed.
cursor?.close()
return
}
if(cursor == null) {
// load failed.
showToast(activity, true, "failed to loading drafts.")
} else {
this@DlgDraftPicker.cursor = cursor
colIdx = PostDraft.ColIdx(cursor)
adapter.notifyDataSetChanged()
}
if(! dialog.isShowing) {
// dialog is already closed.
cursor.close()
} else {
val old = this@DlgDraftPicker.cursor
this@DlgDraftPicker.cursor = cursor
colIdx = PostDraft.ColIdx(cursor)
adapter.notifyDataSetChanged()
old?.close()
}
}
this.task = new_task
new_task.executeOnExecutor(App1.task_executor)
}
private fun getPostDraft(position : Int) : PostDraft? {

View File

@ -3,23 +3,16 @@ package jp.juggler.subwaytooter.dialog
import android.annotation.SuppressLint
import android.app.Dialog
import android.graphics.Bitmap
import android.os.AsyncTask
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import jp.juggler.subwaytooter.ActMain
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.R
import jp.juggler.util.LogCategory
import jp.juggler.util.dismissSafe
import jp.juggler.util.showToast
import net.glxn.qrgen.android.QRCode
@SuppressLint("StaticFieldLeak")
object DlgQRCode {
private val log = LogCategory("DlgQRCode")
internal interface QrCodeCallback {
fun onQrCode(bitmap : Bitmap?)
}
@ -30,39 +23,18 @@ object DlgQRCode {
url : String,
callback : QrCodeCallback
) {
@Suppress("DEPRECATION")
val progress = ProgressDialogEx(activity)
val task = object : AsyncTask<Void, Void, Bitmap?>() {
override fun doInBackground(vararg params : Void) : Bitmap? {
return try {
QRCode.from(url).withSize(size, size).bitmap()
} catch(ex : Throwable) {
log.trace(ex)
showToast(activity, ex, "makeQrCode failed.")
null
}
activity.runWithProgress(
"making QR code",
{
QRCode.from(url).withSize(size, size).bitmap()
},
{
if(it != null) callback.onQrCode(it)
},
progressInitializer = {
it.setMessageEx(activity.getString(R.string.generating_qr_code))
}
override fun onCancelled(result : Bitmap?) {
onPostExecute(result)
}
override fun onPostExecute(result : Bitmap?) {
progress.dismissSafe()
if(result != null) {
callback.onQrCode(result)
}
}
}
progress.isIndeterminateEx = true
progress.setCancelable(true)
progress.setMessageEx(activity.getString(R.string.generating_qr_code))
progress.setOnCancelListener { task.cancel(true) }
progress.show()
task.executeOnExecutor(App1.task_executor)
)
}
fun open(activity : ActMain, message : CharSequence, url : String) {
@ -96,10 +68,7 @@ object DlgQRCode {
viewRoot.findViewById<View>(R.id.btnCancel).setOnClickListener { dialog.cancel() }
dialog.show()
}
})
}
}

View File

@ -24,4 +24,3 @@ org.gradle.caching=true
android.useAndroidX=true
android.enableJetifier=true
android.debug.obsoleteApi=true
android.enableR8=true