diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.kt b/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.kt index b2101b3e..680ac092 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.kt @@ -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(it)) } + ).map { findViewById(it) } // 型指定を除去してはいけない listEtFieldValue = arrayOf( R.id.etFieldValue1, R.id.etFieldValue2, R.id.etFieldValue3, R.id.etFieldValue4 - ).map { requireNotNull(findViewById(it)) } + ).map { findViewById(it) } // 型指定を除去してはいけない btnFields = findViewById(R.id.btnFields) @@ -743,63 +743,37 @@ class ActAccountSetting finish() - val task = @SuppressLint("StaticFieldLeak") - object : AsyncTask() { - - 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() { - - 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") diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActAppSetting.kt b/app/src/main/java/jp/juggler/subwaytooter/ActAppSetting.kt index 08d920e1..1b87eb82 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActAppSetting.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActAppSetting.kt @@ -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() { - - 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 diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActLanguageFilter.kt b/app/src/main/java/jp/juggler/subwaytooter/ActLanguageFilter.kt index b48b012b..e5757e1d 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActLanguageFilter.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActLanguageFilter.kt @@ -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() { - - 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() { - - 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) } + ) } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActMain.kt b/app/src/main/java/jp/juggler/subwaytooter/ActMain.kt index 9194d80b..e5bb613c 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActMain.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActMain.kt @@ -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?>() { + updateColumnStrip() + + + runWithProgress( + "importing app data", - fun setProgressMessage(sv : String) { - runOnMainLooper { - progress.setMessageEx(sv) - } - } - - override fun doInBackground(vararg params : Void) : ArrayList? { + doInBackground = { progress -> + fun setProgressMessage(sv : String) = + runOnMainLooper { progress.setMessageEx(sv) } var newColumnList : ArrayList? = 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?) { - onPostExecute(result) - } - - override fun onPostExecute(result : ArrayList?) { - - 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) { diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt b/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt index 0a9a38bf..efab1d01 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt @@ -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 - 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 + private lateinit var list_etChoice : List 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() + private var attachment_list = ArrayList() 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() { - - val list_warning = ArrayList() - var account : SavedAccount? = null - - override fun doInBackground(vararg params : Void) : String? { + val list_warning = ArrayList() + 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 { diff --git a/app/src/main/java/jp/juggler/subwaytooter/AppState.kt b/app/src/main/java/jp/juggler/subwaytooter/AppState.kt index cb8adf26..95312e93 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/AppState.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/AppState.kt @@ -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() { + 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() } diff --git a/app/src/main/java/jp/juggler/subwaytooter/AsyncActivity.kt b/app/src/main/java/jp/juggler/subwaytooter/AsyncActivity.kt new file mode 100644 index 00000000..05842ebd --- /dev/null +++ b/app/src/main/java/jp/juggler/subwaytooter/AsyncActivity.kt @@ -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 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) + } + } + } + } +} diff --git a/app/src/main/java/jp/juggler/subwaytooter/Column.kt b/app/src/main/java/jp/juggler/subwaytooter/Column.kt index 6da21228..15b81a2d 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/Column.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/Column.kt @@ -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() } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ColumnTask.kt b/app/src/main/java/jp/juggler/subwaytooter/ColumnTask.kt index c692a6f5..f6701038 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ColumnTask.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ColumnTask.kt @@ -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() { - - 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? = 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) + } + } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ColumnTask_Gap.kt b/app/src/main/java/jp/juggler/subwaytooter/ColumnTask_Gap.kt index 9be54808..6f06b034 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ColumnTask_Gap.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ColumnTask_Gap.kt @@ -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 { diff --git a/app/src/main/java/jp/juggler/subwaytooter/ColumnTask_Loading.kt b/app/src/main/java/jp/juggler/subwaytooter/ColumnTask_Loading.kt index 1b4a38a8..8859d194 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ColumnTask_Loading.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ColumnTask_Loading.kt @@ -18,7 +18,7 @@ class ColumnTask_Loading( internal var list_pinned : ArrayList? = null - override fun doInBackground(vararg unused : Void) : TootApiResult? { + override fun doInBackground() : TootApiResult? { ctStarted.set(true) if(Pref.bpInstanceTicker(pref)) { diff --git a/app/src/main/java/jp/juggler/subwaytooter/ColumnTask_Refresh.kt b/app/src/main/java/jp/juggler/subwaytooter/ColumnTask_Refresh.kt index 26f13be8..d8bd70d9 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ColumnTask_Refresh.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ColumnTask_Refresh.kt @@ -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 { diff --git a/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.kt b/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.kt index 7b93f36f..7705bc42 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.kt @@ -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? = 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() { - 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) } diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/TootTaskRunner.kt b/app/src/main/java/jp/juggler/subwaytooter/api/TootTaskRunner.kt index b6fe31da..b6711f59 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/TootTaskRunner.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/TootTaskRunner.kt @@ -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() { - - 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? = null private val refContext : WeakReference - 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() diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/MisskeyAntenna.kt b/app/src/main/java/jp/juggler/subwaytooter/api/entity/MisskeyAntenna.kt index 9c6c6cde..b1cec2ab 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/MisskeyAntenna.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/MisskeyAntenna.kt @@ -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> + private val keywords: Array> // "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 // "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")) diff --git a/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgDraftPicker.kt b/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgDraftPicker.kt index e456913b..db98633b 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgDraftPicker.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgDraftPicker.kt @@ -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? = 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() { - 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? { diff --git a/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgQRCode.kt b/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgQRCode.kt index d2123c6c..43923407 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgQRCode.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgQRCode.kt @@ -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() { - - 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(R.id.btnCancel).setOnClickListener { dialog.cancel() } dialog.show() - } }) - } - } diff --git a/gradle.properties b/gradle.properties index 8a3fe071..a9ee9ead 100644 --- a/gradle.properties +++ b/gradle.properties @@ -24,4 +24,3 @@ org.gradle.caching=true android.useAndroidX=true android.enableJetifier=true android.debug.obsoleteApi=true -android.enableR8=true