とりあえずアプリサーバV2に対応する
This commit is contained in:
parent
ffdd474c73
commit
6c804a2a5d
|
@ -34,14 +34,14 @@ inline val AnkoContext<*>.resources: Resources
|
|||
inline val AnkoContext<*>.assets: AssetManager
|
||||
get() = ctx.assets
|
||||
|
||||
inline val AnkoContext<*>.defaultSharedPreferences: SharedPreferences
|
||||
get() = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||
|
||||
inline val Context.defaultSharedPreferences: SharedPreferences
|
||||
get() = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
|
||||
inline val Fragment.defaultSharedPreferences: SharedPreferences
|
||||
get() = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
//inline val AnkoContext<*>.defaultSharedPreferences: SharedPreferences
|
||||
// get() = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||
//
|
||||
//inline val Context.defaultSharedPreferences: SharedPreferences
|
||||
// get() = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
//
|
||||
//inline val Fragment.defaultSharedPreferences: SharedPreferences
|
||||
// get() = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||
|
||||
inline val Fragment.act: Activity?
|
||||
get() = activity
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
import io.gitlab.arturbosch.detekt.Detekt
|
||||
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.regex.Matcher
|
||||
import java.util.regex.Pattern
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'org.jetbrains.kotlin.plugin.serialization'
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
apply plugin: "io.gitlab.arturbosch.detekt"
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("org.jetbrains.kotlin.kapt")
|
||||
id("org.jetbrains.kotlin.plugin.serialization")
|
||||
id("com.google.devtools.ksp").version("1.8.0-1.0.9")
|
||||
id("io.gitlab.arturbosch.detekt")
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion stCompileSdkVersion
|
||||
|
@ -58,25 +61,30 @@ android {
|
|||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
minifyEnabled false
|
||||
shrinkResources false
|
||||
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
||||
|
||||
lintOptions {
|
||||
disable 'MissingTranslation'
|
||||
disable "MissingTranslation"
|
||||
}
|
||||
}
|
||||
debug{
|
||||
debug {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Specifies comma-separated list of flavor dimensions.
|
||||
flavorDimensions "rcOrDev"
|
||||
flavorDimensions "fcmType"
|
||||
|
||||
productFlavors {
|
||||
rc {
|
||||
dimension "rcOrDev"
|
||||
nofcm {
|
||||
dimension "fcmType"
|
||||
versionNameSuffix "-noFcm"
|
||||
}
|
||||
fcm {
|
||||
dimension "fcmType"
|
||||
versionNameSuffix "-play"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,18 +105,18 @@ android {
|
|||
|
||||
packagingOptions {
|
||||
resources {
|
||||
excludes += ['/META-INF/{AL2.0,LGPL2.1}', 'META-INF/DEPENDENCIES']
|
||||
excludes += ["/META-INF/{AL2.0,LGPL2.1}", "META-INF/DEPENDENCIES"]
|
||||
// https://github.com/Kotlin/kotlinx.coroutines/issues/1064
|
||||
pickFirsts += ['META-INF/atomicfu.kotlin_module']
|
||||
pickFirsts += ["META-INF/atomicfu.kotlin_module"]
|
||||
}
|
||||
}
|
||||
|
||||
useLibrary 'android.test.base'
|
||||
useLibrary 'android.test.mock'
|
||||
useLibrary "android.test.base"
|
||||
useLibrary "android.test.mock"
|
||||
lint {
|
||||
warning 'DuplicatePlatformClasses'
|
||||
warning "DuplicatePlatformClasses"
|
||||
}
|
||||
namespace 'jp.juggler.subwaytooter'
|
||||
namespace "jp.juggler.subwaytooter"
|
||||
|
||||
}
|
||||
|
||||
|
@ -134,18 +142,20 @@ dependencies {
|
|||
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:$desugarLibVersion"
|
||||
|
||||
implementation(project(":base"))
|
||||
implementation project(':colorpicker')
|
||||
implementation project(':emoji')
|
||||
implementation project(':apng_android')
|
||||
implementation project(':anko')
|
||||
implementation fileTree(include: ['*.aar'], dir: 'src/main/libs')
|
||||
implementation project(":colorpicker")
|
||||
implementation project(":emoji")
|
||||
implementation project(":apng_android")
|
||||
implementation project(":anko")
|
||||
implementation fileTree(include: ["*.aar"], dir: "src/main/libs")
|
||||
|
||||
// implementation "org.conscrypt:conscrypt-android:$conscryptVersion"
|
||||
api "org.conscrypt:conscrypt-android:$conscryptVersion"
|
||||
implementation "com.github.UnifiedPush:android-connector:2.1.1"
|
||||
|
||||
kapt "androidx.annotation:annotation:$androidxAnnotationVersion"
|
||||
kapt "androidx.room:room-compiler:$roomVersion"
|
||||
kapt "com.github.bumptech.glide:compiler:$glideVersion"
|
||||
|
||||
//kapt "com.github.bumptech.glide:compiler:$glideVersion"
|
||||
ksp "com.github.bumptech.glide:ksp:$glideVersion"
|
||||
|
||||
detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:$detektVersion")
|
||||
|
||||
|
@ -189,6 +199,29 @@ repositories {
|
|||
mavenCentral()
|
||||
}
|
||||
|
||||
def willApplyGoogleService() {
|
||||
Gradle gradle = getGradle()
|
||||
String tskReqStr = gradle.getStartParameter().getTaskRequests().toString()
|
||||
Matcher matcher
|
||||
|
||||
matcher = Pattern.compile("assemble|generate", Pattern.CASE_INSENSITIVE).matcher(tskReqStr)
|
||||
if (!matcher.find()) {
|
||||
// not assemble or generate task.
|
||||
return false
|
||||
}
|
||||
|
||||
matcher = Pattern.compile("(?:assemble|generate)fcm\\w+", Pattern.CASE_INSENSITIVE).matcher(tskReqStr)
|
||||
if (!matcher.find()) {
|
||||
println "willApplyGoogleService=false. $tskReqStr"
|
||||
return false
|
||||
} else {
|
||||
println "willApplyGoogleService=true. $tskReqStr"
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if (willApplyGoogleService()) apply plugin: "com.google.gms.google-services"
|
||||
|
||||
|
||||
tasks.register("detektAll", Detekt) {
|
||||
description = "Custom DETEKT build for all modules"
|
||||
|
@ -247,3 +280,4 @@ tasks.register("detektAll", Detekt) {
|
|||
sarif.outputLocation = file("$buildDir/reports/detekt/st-${name}.sarif")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<application tools:ignore="MissingApplicationIcon">
|
||||
<service
|
||||
android:name="jp.juggler.subwaytooter.push.MyFcmService"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
</manifest>
|
|
@ -0,0 +1,16 @@
|
|||
package jp.juggler.subwaytooter.push
|
||||
|
||||
import com.google.firebase.messaging.FirebaseMessaging
|
||||
import kotlinx.coroutines.tasks.await
|
||||
|
||||
@Suppress("unused")
|
||||
class FcmTokenLoader {
|
||||
// com.google.firebase:firebase-messaging.20.3.0 以降
|
||||
// implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:$kotlinx_coroutines_version"
|
||||
suspend fun getToken(): String? =
|
||||
FirebaseMessaging.getInstance().token.await()
|
||||
|
||||
suspend fun deleteToken(){
|
||||
FirebaseMessaging.getInstance().deleteToken().await()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package jp.juggler.subwaytooter.push
|
||||
|
||||
import com.google.firebase.messaging.FirebaseMessagingService
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import jp.juggler.util.log.LogCategory
|
||||
import jp.juggler.util.os.checkAppForeground
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
/**
|
||||
* FCMのイベントを受け取るサービス。
|
||||
* - IntentServiceの一種なのでワーカースレッドから呼ばれる。runBlockingして良し。
|
||||
*/
|
||||
class MyFcmService : FirebaseMessagingService() {
|
||||
companion object{
|
||||
private val log = LogCategory("MyFcmService")
|
||||
}
|
||||
|
||||
/**
|
||||
* FCMデバイストークンが更新された
|
||||
*/
|
||||
override fun onNewToken(token: String) {
|
||||
try {
|
||||
checkAppForeground("MyFcmService.onNewToken")
|
||||
fcmHandler.onTokenChanged(token)
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "onNewToken failed.")
|
||||
} finally {
|
||||
checkAppForeground("MyFcmService.onNewToken")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* メッセージを受信した
|
||||
* - ワーカースレッドから呼ばれる。runBlockingして良し。
|
||||
* - IntentServiceの一種なので、呼び出しの間はネットワークを使えるなどある
|
||||
*/
|
||||
override fun onMessageReceived(remoteMessage: RemoteMessage) {
|
||||
try {
|
||||
checkAppForeground("MyFcmService.onMessageReceived")
|
||||
runBlocking {
|
||||
fcmHandler.onMessageReceived( remoteMessage.data)
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "onMessageReceived failed.")
|
||||
} finally {
|
||||
checkAppForeground("MyFcmService.onMessageReceived")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -84,16 +84,17 @@
|
|||
<application
|
||||
android:name=".App1"
|
||||
android:allowBackup="true"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:fullBackupContent="@xml/backup_spec"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:largeHeap="true"
|
||||
android:localeConfig="@xml/locales_config"
|
||||
android:maxAspectRatio="100"
|
||||
android:resizeableActivity="true"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme.Light"
|
||||
tools:ignore="DataExtractionRules,UnusedAttribute">
|
||||
<!-- android:localeConfig="@xml/locales_config" -->
|
||||
|
||||
<activity
|
||||
android:name=".ActMain"
|
||||
|
@ -362,23 +363,38 @@
|
|||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:name=".MyFirebaseMessagingService"
|
||||
android:exported="true"
|
||||
tools:ignore="ExportedService">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<provider
|
||||
android:name="androidx.startup.InitializationProvider"
|
||||
android:authorities="${applicationId}.androidx-startup"
|
||||
android:exported="false"
|
||||
tools:node="merge">
|
||||
<meta-data
|
||||
android:name="jp.juggler.subwaytooter.global.GlobalInitializer"
|
||||
android:name="jp.juggler.subwaytooter.pref.LazyContextInitializer"
|
||||
android:value="androidx.startup" />
|
||||
<meta-data
|
||||
android:name="jp.juggler.subwaytooter.push.FcmHandlerInitializer"
|
||||
android:value="androidx.startup" />
|
||||
<meta-data
|
||||
android:name="jp.juggler.subwaytooter.pref.PrefDeviceInitializer"
|
||||
android:value="androidx.startup" />
|
||||
<meta-data
|
||||
android:name="jp.juggler.subwaytooter.notification.NotificationChannelsInitializer"
|
||||
android:value="androidx.startup" />
|
||||
<meta-data
|
||||
android:name="jp.juggler.subwaytooter.table.AppDatabaseHolderIniitalizer"
|
||||
android:value="androidx.startup" />
|
||||
</provider>
|
||||
|
||||
<receiver
|
||||
android:name=".push.UpMessageReceiver"
|
||||
android:exported="true"
|
||||
tools:ignore="ExportedReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="org.unifiedpush.android.connector.MESSAGE" />
|
||||
<action android:name="org.unifiedpush.android.connector.UNREGISTERED" />
|
||||
<action android:name="org.unifiedpush.android.connector.NEW_ENDPOINT" />
|
||||
<action android:name="org.unifiedpush.android.connector.REGISTRATION_REFUSED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
</application>
|
||||
</manifest>
|
||||
|
|
|
@ -2,6 +2,7 @@ package jp.juggler.subwaytooter
|
|||
|
||||
import android.app.Activity
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
|
@ -15,24 +16,29 @@ import android.view.View
|
|||
import android.widget.*
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import jp.juggler.subwaytooter.action.accountRemove
|
||||
import jp.juggler.subwaytooter.api.*
|
||||
import jp.juggler.subwaytooter.api.auth.AuthBase
|
||||
import jp.juggler.subwaytooter.api.entity.*
|
||||
import jp.juggler.subwaytooter.auth.AuthRepo
|
||||
import jp.juggler.subwaytooter.databinding.ActAccountSettingBinding
|
||||
import jp.juggler.subwaytooter.dialog.ActionsDialog
|
||||
import jp.juggler.subwaytooter.dialog.actionsDialog
|
||||
import jp.juggler.subwaytooter.notification.*
|
||||
import jp.juggler.subwaytooter.pref.PrefB
|
||||
import jp.juggler.subwaytooter.table.AcctColor
|
||||
import jp.juggler.subwaytooter.push.PushBase
|
||||
import jp.juggler.subwaytooter.push.pushRepo
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.daoAcctColor
|
||||
import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||
import jp.juggler.subwaytooter.util.*
|
||||
import jp.juggler.util.*
|
||||
import jp.juggler.util.coroutine.AppDispatchers
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.coroutine.launchMain
|
||||
import jp.juggler.util.coroutine.launchProgress
|
||||
import jp.juggler.util.data.*
|
||||
import jp.juggler.util.log.LogCategory
|
||||
import jp.juggler.util.log.showToast
|
||||
import jp.juggler.util.log.withCaption
|
||||
import jp.juggler.util.media.ResizeConfig
|
||||
import jp.juggler.util.media.ResizeType
|
||||
import jp.juggler.util.media.createResizedBitmap
|
||||
|
@ -124,6 +130,10 @@ class ActAccountSetting : AppCompatActivity(),
|
|||
ActAccountSettingBinding.inflate(layoutInflater, null, false)
|
||||
}
|
||||
|
||||
private val authRepo by lazy {
|
||||
AuthRepo(this)
|
||||
}
|
||||
|
||||
private lateinit var nameInvalidator: NetworkEmojiInvalidator
|
||||
private lateinit var noteInvalidator: NetworkEmojiInvalidator
|
||||
private lateinit var defaultTextInvalidator: NetworkEmojiInvalidator
|
||||
|
@ -218,20 +228,22 @@ class ActAccountSetting : AppCompatActivity(),
|
|||
|
||||
initUI()
|
||||
|
||||
val a = intent.long(KEY_ACCOUNT_DB_ID)
|
||||
?.let { SavedAccount.loadAccount(this, it) }
|
||||
if (a == null) {
|
||||
finish()
|
||||
return
|
||||
launchAndShowError {
|
||||
val a = intent.long(KEY_ACCOUNT_DB_ID)
|
||||
?.let { daoSavedAccount.loadAccount(it) }
|
||||
if (a == null) {
|
||||
finish()
|
||||
return@launchAndShowError
|
||||
}
|
||||
supportActionBar?.subtitle = a.acct.pretty
|
||||
|
||||
loadUIFromData(a)
|
||||
|
||||
initializeProfile()
|
||||
|
||||
views.btnOpenBrowser.text =
|
||||
getString(R.string.open_instance_website, account.apiHost.pretty)
|
||||
}
|
||||
supportActionBar?.subtitle = a.acct.pretty
|
||||
|
||||
loadUIFromData(a)
|
||||
|
||||
initializeProfile()
|
||||
|
||||
views.btnOpenBrowser.text =
|
||||
getString(R.string.open_instance_website, account.apiHost.pretty)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
|
@ -336,7 +348,7 @@ class ActAccountSetting : AppCompatActivity(),
|
|||
R.id.etFieldValue4
|
||||
).map { findViewById(it) }
|
||||
|
||||
btnNotificationStyleEditReply.vg(PrefB.bpSeparateReplyNotificationGroup.invoke())
|
||||
btnNotificationStyleEditReply.vg(PrefB.bpSeparateReplyNotificationGroup.value)
|
||||
|
||||
nameInvalidator = NetworkEmojiInvalidator(handler, etDisplayName)
|
||||
noteInvalidator = NetworkEmojiInvalidator(handler, etNote)
|
||||
|
@ -504,72 +516,76 @@ class ActAccountSetting : AppCompatActivity(),
|
|||
}
|
||||
|
||||
private fun showAcctColor() {
|
||||
|
||||
val sa = this.account
|
||||
val ac = AcctColor.load(sa)
|
||||
val ac = daoAcctColor.load(sa)
|
||||
views.tvUserCustom.apply {
|
||||
backgroundColor = ac.color_bg
|
||||
backgroundColor = ac.colorBg
|
||||
text = ac.nickname
|
||||
textColor = ac.color_fg.notZero() ?: attrColor(R.attr.colorTimeSmall)
|
||||
textColor = ac.colorFg.notZero()
|
||||
?: attrColor(R.attr.colorTimeSmall)
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveUIToData() {
|
||||
if (!::account.isInitialized) return
|
||||
if (loadingBusy) return
|
||||
account.visibility = visibility
|
||||
launchAndShowError {
|
||||
|
||||
views.apply {
|
||||
account.visibility = visibility
|
||||
|
||||
account.dont_hide_nsfw = swNSFWOpen.isChecked
|
||||
account.dont_show_timeout = swDontShowTimeout.isChecked
|
||||
account.expand_cw = swExpandCW.isChecked
|
||||
account.default_sensitive = swMarkSensitive.isChecked
|
||||
account.notification_mention = cbNotificationMention.isChecked
|
||||
account.notification_boost = cbNotificationBoost.isChecked
|
||||
account.notification_favourite = cbNotificationFavourite.isChecked
|
||||
account.notification_follow = cbNotificationFollow.isChecked
|
||||
account.notification_follow_request = cbNotificationFollowRequest.isChecked
|
||||
account.notification_reaction = cbNotificationReaction.isChecked
|
||||
account.notification_vote = cbNotificationVote.isChecked
|
||||
account.notification_post = cbNotificationPost.isChecked
|
||||
account.notification_update = cbNotificationUpdate.isChecked
|
||||
account.notification_status_reference = cbNotificationStatusReference.isChecked
|
||||
views.apply {
|
||||
account.dont_hide_nsfw = swNSFWOpen.isChecked
|
||||
account.dont_show_timeout = swDontShowTimeout.isChecked
|
||||
account.expand_cw = swExpandCW.isChecked
|
||||
account.default_sensitive = swMarkSensitive.isChecked
|
||||
account.notification_mention = cbNotificationMention.isChecked
|
||||
account.notification_boost = cbNotificationBoost.isChecked
|
||||
account.notification_favourite = cbNotificationFavourite.isChecked
|
||||
account.notification_follow = cbNotificationFollow.isChecked
|
||||
account.notification_follow_request = cbNotificationFollowRequest.isChecked
|
||||
account.notification_reaction = cbNotificationReaction.isChecked
|
||||
account.notification_vote = cbNotificationVote.isChecked
|
||||
account.notification_post = cbNotificationPost.isChecked
|
||||
account.notification_update = cbNotificationUpdate.isChecked
|
||||
account.notification_status_reference = cbNotificationStatusReference.isChecked
|
||||
|
||||
account.confirm_follow = cbConfirmFollow.isChecked
|
||||
account.confirm_follow_locked = cbConfirmFollowLockedUser.isChecked
|
||||
account.confirm_unfollow = cbConfirmUnfollow.isChecked
|
||||
account.confirm_boost = cbConfirmBoost.isChecked
|
||||
account.confirm_favourite = cbConfirmFavourite.isChecked
|
||||
account.confirm_unboost = cbConfirmUnboost.isChecked
|
||||
account.confirm_unfavourite = cbConfirmUnfavourite.isChecked
|
||||
account.confirm_post = cbConfirmToot.isChecked
|
||||
account.confirm_reaction = cbConfirmReaction.isChecked
|
||||
account.confirm_unbookmark = cbConfirmUnbookmark.isChecked
|
||||
account.confirm_follow = cbConfirmFollow.isChecked
|
||||
account.confirm_follow_locked = cbConfirmFollowLockedUser.isChecked
|
||||
account.confirm_unfollow = cbConfirmUnfollow.isChecked
|
||||
account.confirm_boost = cbConfirmBoost.isChecked
|
||||
account.confirm_favourite = cbConfirmFavourite.isChecked
|
||||
account.confirm_unboost = cbConfirmUnboost.isChecked
|
||||
account.confirm_unfavourite = cbConfirmUnfavourite.isChecked
|
||||
account.confirm_post = cbConfirmToot.isChecked
|
||||
account.confirm_reaction = cbConfirmReaction.isChecked
|
||||
account.confirm_unbookmark = cbConfirmUnbookmark.isChecked
|
||||
|
||||
account.sound_uri = ""
|
||||
account.default_text = etDefaultText.text.toString()
|
||||
account.sound_uri = ""
|
||||
account.default_text = etDefaultText.text.toString()
|
||||
|
||||
account.max_toot_chars = etMaxTootChars.parseInt()?.takeIf { it > 0 } ?: 0
|
||||
account.max_toot_chars = etMaxTootChars.parseInt()?.takeIf { it > 0 } ?: 0
|
||||
|
||||
account.movie_max_megabytes = etMovieSizeMax.text.toString().trim()
|
||||
account.image_max_megabytes = etMediaSizeMax.text.toString().trim()
|
||||
account.image_resize = (
|
||||
imageResizeItems.elementAtOrNull(spResizeImage.selectedItemPosition)?.config
|
||||
?: SavedAccount.defaultResizeConfig
|
||||
).spec
|
||||
account.movie_max_megabytes = etMovieSizeMax.text.toString().trim()
|
||||
account.image_max_megabytes = etMediaSizeMax.text.toString().trim()
|
||||
account.image_resize = (
|
||||
imageResizeItems.elementAtOrNull(spResizeImage.selectedItemPosition)?.config
|
||||
?: SavedAccount.defaultResizeConfig
|
||||
).spec
|
||||
|
||||
account.push_policy =
|
||||
pushPolicyItems.elementAtOrNull(spPushPolicy.selectedItemPosition)?.id
|
||||
account.push_policy =
|
||||
pushPolicyItems.elementAtOrNull(spPushPolicy.selectedItemPosition)?.id
|
||||
|
||||
account.movieTranscodeMode = spMovieTranscodeMode.selectedItemPosition
|
||||
account.movieTranscodeBitrate = etMovieBitrate.text.toString()
|
||||
account.movieTranscodeFramerate = etMovieFrameRate.text.toString()
|
||||
account.movieTranscodeSquarePixels = etMovieSquarePixels.text.toString()
|
||||
account.lang = languages.elementAtOrNull(spLanguageCode.selectedItemPosition)?.first
|
||||
?: SavedAccount.LANG_WEB
|
||||
account.movieTranscodeMode = spMovieTranscodeMode.selectedItemPosition
|
||||
account.movieTranscodeBitrate = etMovieBitrate.text.toString()
|
||||
account.movieTranscodeFramerate = etMovieFrameRate.text.toString()
|
||||
account.movieTranscodeSquarePixels = etMovieSquarePixels.text.toString()
|
||||
account.lang = languages.elementAtOrNull(spLanguageCode.selectedItemPosition)?.first
|
||||
?: SavedAccount.LANG_WEB
|
||||
}
|
||||
|
||||
daoSavedAccount.saveSetting(account)
|
||||
}
|
||||
|
||||
account.saveSetting()
|
||||
}
|
||||
|
||||
override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {
|
||||
|
@ -617,24 +633,20 @@ class ActAccountSetting : AppCompatActivity(),
|
|||
R.id.btnFields -> sendFields()
|
||||
|
||||
R.id.btnNotificationStyleEdit ->
|
||||
MessageNotification.openNotificationChannelSetting(
|
||||
this,
|
||||
account,
|
||||
MessageNotification.TRACKING_NAME_DEFAULT
|
||||
PullNotification.openNotificationChannelSetting(
|
||||
this
|
||||
)
|
||||
|
||||
R.id.btnNotificationStyleEditReply ->
|
||||
MessageNotification.openNotificationChannelSetting(
|
||||
this,
|
||||
account,
|
||||
MessageNotification.TRACKING_NAME_REPLY
|
||||
PullNotification.openNotificationChannelSetting(
|
||||
this
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showVisibility() {
|
||||
views.btnVisibility.text =
|
||||
getVisibilityString(this, account.isMisskey, visibility)
|
||||
visibility.getVisibilityString(account.isMisskey)
|
||||
}
|
||||
|
||||
private fun performVisibility() {
|
||||
|
@ -734,10 +746,11 @@ class ActAccountSetting : AppCompatActivity(),
|
|||
.setMessage(R.string.confirm_account_remove)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
accountRemove(account)
|
||||
finish()
|
||||
}
|
||||
.show()
|
||||
launchAndShowError {
|
||||
authRepo.accountRemove(account)
|
||||
finish()
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
|
@ -823,7 +836,7 @@ class ActAccountSetting : AppCompatActivity(),
|
|||
result.jsonObject
|
||||
} else {
|
||||
// 承認待ち状態のチェック
|
||||
account.checkConfirmed(this, client)
|
||||
authRepo.checkConfirmed(account, client)
|
||||
|
||||
val result = client.request(
|
||||
"/api/v1/accounts/verify_credentials"
|
||||
|
@ -1259,21 +1272,21 @@ class ActAccountSetting : AppCompatActivity(),
|
|||
}
|
||||
|
||||
private fun openPicker(permissionRequester: PermissionRequester) {
|
||||
if (!permissionRequester.checkOrLaunch()) return
|
||||
|
||||
val propName = when (permissionRequester) {
|
||||
prPickHeader -> "header"
|
||||
else -> "avatar"
|
||||
launchAndShowError {
|
||||
if (!permissionRequester.checkOrLaunch()) return@launchAndShowError
|
||||
val propName = when (permissionRequester) {
|
||||
prPickHeader -> "header"
|
||||
else -> "avatar"
|
||||
}
|
||||
actionsDialog {
|
||||
action(getString(R.string.pick_image)) {
|
||||
performAttachment(propName)
|
||||
}
|
||||
action(getString(R.string.image_capture)) {
|
||||
performCamera(propName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val a = ActionsDialog()
|
||||
a.addAction(getString(R.string.pick_image)) {
|
||||
performAttachment(propName)
|
||||
}
|
||||
a.addAction(getString(R.string.image_capture)) {
|
||||
performCamera(propName)
|
||||
}
|
||||
a.show(this, null)
|
||||
}
|
||||
|
||||
private fun performAttachment(propName: String) {
|
||||
|
@ -1416,19 +1429,57 @@ class ActAccountSetting : AppCompatActivity(),
|
|||
}
|
||||
|
||||
private fun updatePushSubscription(force: Boolean) {
|
||||
val wps = PushSubscriptionHelper(applicationContext, account, verbose = true)
|
||||
launchMain {
|
||||
runApiTask(account) { client ->
|
||||
wps.updateSubscription(client, force = force)
|
||||
}?.let {
|
||||
val log = wps.logString
|
||||
if (log.isNotEmpty()) {
|
||||
AlertDialog.Builder(this@ActAccountSetting)
|
||||
.setMessage(log)
|
||||
.setPositiveButton(R.string.close, null)
|
||||
.show()
|
||||
val activity = this
|
||||
launchAndShowError {
|
||||
val anyNotificationWanted = account.notification_boost ||
|
||||
account.notification_favourite ||
|
||||
account.notification_follow ||
|
||||
account.notification_mention ||
|
||||
account.notification_reaction ||
|
||||
account.notification_vote ||
|
||||
account.notification_follow_request ||
|
||||
account.notification_post ||
|
||||
account.notification_update
|
||||
|
||||
val lines = ArrayList<String>()
|
||||
val subLogger = object : PushBase.SubscriptionLogger {
|
||||
override val context: Context
|
||||
get() = activity
|
||||
|
||||
override fun i(msg: String) {
|
||||
log.w(msg)
|
||||
synchronized(lines) {
|
||||
lines.add(msg)
|
||||
}
|
||||
}
|
||||
|
||||
override fun e(msg: String) {
|
||||
log.e(msg)
|
||||
synchronized(lines) {
|
||||
lines.add(msg)
|
||||
}
|
||||
}
|
||||
|
||||
override fun e(ex: Throwable, msg: String) {
|
||||
log.e(ex, msg)
|
||||
synchronized(lines) {
|
||||
lines.add(ex.withCaption(msg))
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
pushRepo.updateSubscription(
|
||||
subLogger,
|
||||
account,
|
||||
willRemoveSubscription = !anyNotificationWanted,
|
||||
)
|
||||
} catch (ex: Throwable) {
|
||||
subLogger.e(ex, "updateSubscription failed.")
|
||||
}
|
||||
AlertDialog.Builder(activity)
|
||||
.setMessage("${account.acct}:\n${lines.joinToString("\n")}")
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
package jp.juggler.subwaytooter
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.net.toUri
|
||||
import jp.juggler.subwaytooter.databinding.ActAlertBinding
|
||||
import jp.juggler.util.data.encodePercent
|
||||
import jp.juggler.util.data.notEmpty
|
||||
import jp.juggler.util.ui.setNavigationBack
|
||||
|
||||
class ActAlert : AppCompatActivity() {
|
||||
companion object {
|
||||
private const val EXTRA_MESSAGE = "message"
|
||||
private const val EXTRA_TITLE = "title"
|
||||
|
||||
fun Context.intentActAlert(
|
||||
tag: String,
|
||||
message: String,
|
||||
title: String,
|
||||
) = Intent(this, ActAlert::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
data = "app://error/${tag.encodePercent()}".toUri()
|
||||
putExtra(EXTRA_MESSAGE, message)
|
||||
putExtra(EXTRA_TITLE, title)
|
||||
}
|
||||
}
|
||||
|
||||
private val views by lazy {
|
||||
ActAlertBinding.inflate(layoutInflater)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(views.root)
|
||||
setSupportActionBar(views.toolbar)
|
||||
setNavigationBack(views.toolbar)
|
||||
|
||||
intent?.getStringExtra(EXTRA_TITLE).notEmpty()
|
||||
?.let { title = it }
|
||||
|
||||
intent?.getStringExtra(EXTRA_MESSAGE).notEmpty()
|
||||
?.let { views.etMessage.setText(it) }
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
package jp.juggler.subwaytooter
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.ResolveInfo
|
||||
import android.graphics.Color
|
||||
import android.graphics.Typeface
|
||||
|
@ -33,6 +32,7 @@ import jp.juggler.subwaytooter.appsetting.AppDataExporter
|
|||
import jp.juggler.subwaytooter.appsetting.AppSettingItem
|
||||
import jp.juggler.subwaytooter.appsetting.SettingType
|
||||
import jp.juggler.subwaytooter.appsetting.appSettingRoot
|
||||
import jp.juggler.subwaytooter.auth.AuthRepo
|
||||
import jp.juggler.subwaytooter.databinding.ActAppSettingBinding
|
||||
import jp.juggler.subwaytooter.databinding.LvSettingItemBinding
|
||||
import jp.juggler.subwaytooter.dialog.DlgAppPicker
|
||||
|
@ -41,11 +41,9 @@ import jp.juggler.subwaytooter.pref.impl.BooleanPref
|
|||
import jp.juggler.subwaytooter.pref.impl.FloatPref
|
||||
import jp.juggler.subwaytooter.pref.impl.IntPref
|
||||
import jp.juggler.subwaytooter.pref.impl.StringPref
|
||||
import jp.juggler.subwaytooter.pref.pref
|
||||
import jp.juggler.subwaytooter.pref.put
|
||||
import jp.juggler.subwaytooter.pref.remove
|
||||
import jp.juggler.subwaytooter.table.AcctColor
|
||||
import jp.juggler.subwaytooter.pref.lazyPref
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.daoAcctColor
|
||||
import jp.juggler.subwaytooter.util.CustomShare
|
||||
import jp.juggler.subwaytooter.util.CustomShareTarget
|
||||
import jp.juggler.subwaytooter.util.cn
|
||||
|
@ -92,7 +90,6 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
|||
|
||||
private var customShareTarget: CustomShareTarget? = null
|
||||
|
||||
lateinit var pref: SharedPreferences
|
||||
lateinit var handler: Handler
|
||||
|
||||
val views by lazy {
|
||||
|
@ -103,6 +100,10 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
|||
MyAdapter()
|
||||
}
|
||||
|
||||
val authRepo by lazy {
|
||||
AuthRepo(this)
|
||||
}
|
||||
|
||||
private val arNoop = ActivityResultHandler(log) { }
|
||||
|
||||
private val arImportAppData = ActivityResultHandler(log) { r ->
|
||||
|
@ -159,7 +160,6 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
|||
App1.setActivityTheme(this)
|
||||
|
||||
this.handler = App1.getAppState(this).handler
|
||||
this.pref = pref()
|
||||
|
||||
// val intent = this.intent
|
||||
// val layoutId = intent.getIntExtra(EXTRA_LAYOUT_ID, 0)
|
||||
|
@ -218,12 +218,12 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
|||
}
|
||||
|
||||
private fun removeDefaultPref() {
|
||||
val e = pref.edit()
|
||||
val e = lazyPref.edit()
|
||||
var changed = false
|
||||
appSettingRoot.scan {
|
||||
when {
|
||||
(it.pref as? IntPref)?.noRemove == true -> Unit
|
||||
it.pref?.removeDefault(pref, e) == true -> changed = true
|
||||
it.pref?.removeDefault(lazyPref, e) == true -> changed = true
|
||||
}
|
||||
}
|
||||
if (changed) e.apply()
|
||||
|
@ -371,7 +371,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
|||
SettingType.ColorAlpha -> newColor.notZero() ?: 1
|
||||
else -> newColor or Color.BLACK
|
||||
}
|
||||
pref.edit().put(ip, c).apply()
|
||||
ip.value = c
|
||||
findItemViewHolder(colorTarget)?.showColor()
|
||||
colorTarget.changed(this)
|
||||
}
|
||||
|
@ -512,8 +512,6 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
|||
private val tvDesc = views.tvDesc
|
||||
private val tvError = views.tvError
|
||||
|
||||
private val pref = actAppSetting.pref
|
||||
|
||||
var item: AppSettingItem? = null
|
||||
|
||||
private var bindingBusy = false
|
||||
|
@ -575,7 +573,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
|||
vg(false) // skip animation
|
||||
text = name
|
||||
isEnabledAlpha = item.enabled
|
||||
isChecked = bp(pref)
|
||||
isChecked = bp.value
|
||||
vg(true)
|
||||
}
|
||||
|
||||
|
@ -586,7 +584,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
|||
vg(false) // skip animation
|
||||
actAppSetting.setSwitchColor(views.swSwitch)
|
||||
isEnabledAlpha = item.enabled
|
||||
isChecked = bp(pref)
|
||||
isChecked = bp.value
|
||||
vg(true)
|
||||
}
|
||||
|
||||
|
@ -608,12 +606,12 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
|||
showCaption(name)
|
||||
views.llButtonBar.vg(true)
|
||||
views.vColor.vg(true)
|
||||
views.vColor.setBackgroundColor(ip(pref))
|
||||
views.vColor.setBackgroundColor(ip.value)
|
||||
views.btnEdit.isEnabledAlpha = item.enabled
|
||||
views.btnReset.isEnabledAlpha = item.enabled
|
||||
views.btnEdit.setOnClickListener {
|
||||
actAppSetting.colorTarget = item
|
||||
val color = ip(pref)
|
||||
val color = ip.value
|
||||
val builder = ColorPickerDialog.newBuilder()
|
||||
.setDialogType(ColorPickerDialog.TYPE_CUSTOM)
|
||||
.setAllowPresets(true)
|
||||
|
@ -623,7 +621,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
|||
builder.show(actAppSetting)
|
||||
}
|
||||
views.btnReset.setOnClickListener {
|
||||
pref.edit().remove(ip).apply()
|
||||
ip.removeValue()
|
||||
showColor()
|
||||
item.changed.invoke(actAppSetting)
|
||||
}
|
||||
|
@ -644,7 +642,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
|||
argsInt?.map { actAppSetting.getString(it) }
|
||||
?: item.spinnerArgsProc(actAppSetting)
|
||||
)
|
||||
views.spSpinner.setSelection(pi.invoke(pref))
|
||||
views.spSpinner.setSelection(pi.value)
|
||||
} else {
|
||||
item.spinnerInitializer.invoke(actAppSetting, views.spSpinner)
|
||||
}
|
||||
|
@ -655,9 +653,9 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
|||
views.etEditText.vg(true)?.let { etEditText ->
|
||||
val text = when (val pi = item.pref) {
|
||||
is FloatPref ->
|
||||
item.fromFloat.invoke(actAppSetting, pi(pref))
|
||||
item.fromFloat.invoke(actAppSetting, pi.value)
|
||||
is StringPref ->
|
||||
pi(pref)
|
||||
pi.value
|
||||
else -> error("EditText has incorrect pref $pi")
|
||||
}
|
||||
|
||||
|
@ -736,7 +734,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
|||
fun showColor() {
|
||||
val item = item ?: return
|
||||
val ip = item.pref.cast<IntPref>() ?: return
|
||||
val c = ip(pref)
|
||||
val c = ip.value
|
||||
views.vColor.setBackgroundColor(c)
|
||||
}
|
||||
|
||||
|
@ -753,15 +751,14 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
|||
val sv = item.filter.invoke(p0?.toString() ?: "")
|
||||
|
||||
when (val pi = item.pref) {
|
||||
is StringPref ->
|
||||
pref.edit().put(pi, sv).apply()
|
||||
is StringPref -> pi.value = sv
|
||||
|
||||
is FloatPref -> {
|
||||
val fv = item.toFloat.invoke(actAppSetting, sv)
|
||||
if (fv.isFinite()) {
|
||||
pref.edit().put(pi, fv).apply()
|
||||
pi.value = fv
|
||||
} else {
|
||||
pref.edit().remove(pi.key).apply()
|
||||
pi.removeValue()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -785,7 +782,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
|||
if (bindingBusy) return
|
||||
val item = item ?: return
|
||||
when (val pi = item.pref) {
|
||||
is IntPref -> pref.edit().put(pi, views.spSpinner.selectedItemPosition).apply()
|
||||
is IntPref -> pi.value = views.spSpinner.selectedItemPosition
|
||||
else -> item.spinnerOnSelected.invoke(actAppSetting, views.spSpinner, position)
|
||||
}
|
||||
item.changed.invoke(actAppSetting)
|
||||
|
@ -795,7 +792,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
|||
if (bindingBusy) return
|
||||
val item = item ?: return
|
||||
when (val pi = item.pref) {
|
||||
is BooleanPref -> pref.edit().put(pi, isChecked).apply()
|
||||
is BooleanPref -> pi.value = isChecked
|
||||
else -> error("CompoundButton has no booleanPref $pi")
|
||||
}
|
||||
item.changed.invoke(actAppSetting)
|
||||
|
@ -946,7 +943,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
|||
data.handleGetContentResult(contentResolver).firstOrNull()?.uri?.let {
|
||||
val file = saveTimelineFont(it, fileName)
|
||||
if (file != null) {
|
||||
pref.edit().put(item.pref.cast()!!, file.absolutePath).apply()
|
||||
(item.pref as? StringPref)?.value = file.absolutePath
|
||||
showTimelineFont(item)
|
||||
}
|
||||
}
|
||||
|
@ -959,19 +956,17 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
|||
}
|
||||
|
||||
fun showTimelineFont(item: AppSettingItem, tv: TextView) {
|
||||
val fontUrl = item.pref.cast<StringPref>()!!.invoke(this)
|
||||
try {
|
||||
if (fontUrl.isNotEmpty()) {
|
||||
item.pref.cast<StringPref>()?.value.notEmpty()?.let { url ->
|
||||
tv.typeface = Typeface.DEFAULT
|
||||
val face = Typeface.createFromFile(fontUrl)
|
||||
val face = Typeface.createFromFile(url)
|
||||
tv.typeface = face
|
||||
tv.text = fontUrl
|
||||
tv.text = url
|
||||
return
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "showTimelineFont failed.")
|
||||
}
|
||||
|
||||
// fallback
|
||||
tv.text = getString(R.string.not_selected)
|
||||
tv.typeface = Typeface.DEFAULT
|
||||
|
@ -1026,17 +1021,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
|||
|
||||
//////////////////////////////////////////////////////
|
||||
|
||||
inner class AccountAdapter internal constructor() : BaseAdapter() {
|
||||
|
||||
internal val list = ArrayList<SavedAccount>()
|
||||
|
||||
init {
|
||||
for (a in SavedAccount.loadAccountList(this@ActAppSetting)) {
|
||||
if (a.isPseudo) continue
|
||||
list.add(a)
|
||||
}
|
||||
SavedAccount.sort(list)
|
||||
}
|
||||
inner class AccountAdapter(val list: List<SavedAccount>) : BaseAdapter() {
|
||||
|
||||
override fun getCount(): Int {
|
||||
return 1 + list.size
|
||||
|
@ -1058,7 +1043,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
|||
)
|
||||
view.findViewById<TextView>(android.R.id.text1).text = when (position) {
|
||||
0 -> getString(R.string.ask_always)
|
||||
else -> AcctColor.getNickname(list[position - 1])
|
||||
else -> daoAcctColor.getNickname(list[position - 1])
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
@ -1068,7 +1053,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
|||
viewOld ?: layoutInflater.inflate(R.layout.lv_spinner_dropdown, parent, false)
|
||||
view.findViewById<TextView>(android.R.id.text1).text = when (position) {
|
||||
0 -> getString(R.string.ask_always)
|
||||
else -> AcctColor.getNickname(list[position - 1])
|
||||
else -> daoAcctColor.getNickname(list[position - 1])
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
@ -1201,7 +1186,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
|||
|
||||
fun setCustomShare(appSettingItem: AppSettingItem, target: CustomShareTarget, value: String) {
|
||||
val sp: StringPref = appSettingItem.pref.cast() ?: error("$target: not StringPref")
|
||||
pref.edit().put(sp, value).apply()
|
||||
sp.value = value
|
||||
|
||||
showCustomShareIcon(findItemViewHolder(appSettingItem)?.views?.textView1, target)
|
||||
}
|
||||
|
@ -1238,7 +1223,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
|||
private fun setWebBrowser(appSettingItem: AppSettingItem, value: String) {
|
||||
val sp: StringPref = appSettingItem.pref.cast()
|
||||
?: error("${getString(appSettingItem.caption)}: not StringPref")
|
||||
pref.edit().put(sp, value).apply()
|
||||
sp.value = value
|
||||
|
||||
showWebBrowser(findItemViewHolder(appSettingItem)?.views?.textView1, value)
|
||||
}
|
||||
|
|
|
@ -9,12 +9,14 @@ import jp.juggler.subwaytooter.api.entity.Acct
|
|||
import jp.juggler.subwaytooter.databinding.ActWordListBinding
|
||||
import jp.juggler.subwaytooter.databinding.LvMuteAppBinding
|
||||
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
||||
import jp.juggler.subwaytooter.table.FavMute
|
||||
import jp.juggler.subwaytooter.table.daoFavMute
|
||||
import jp.juggler.util.backPressed
|
||||
import jp.juggler.util.coroutine.AppDispatchers
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.data.cast
|
||||
import jp.juggler.util.log.LogCategory
|
||||
import jp.juggler.util.ui.setNavigationBack
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class ActFavMute : AppCompatActivity() {
|
||||
|
||||
|
@ -52,27 +54,20 @@ class ActFavMute : AppCompatActivity() {
|
|||
item ?: return
|
||||
launchAndShowError {
|
||||
confirm(R.string.delete_confirm, item.acct.pretty)
|
||||
FavMute.delete(item.acct)
|
||||
daoFavMute.delete(item.acct)
|
||||
listAdapter.remove(item)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadData() {
|
||||
listAdapter.items = buildList {
|
||||
try {
|
||||
FavMute.createCursor().use { cursor ->
|
||||
val idxId = cursor.getColumnIndex(FavMute.COL_ID)
|
||||
val idxName = cursor.getColumnIndex(FavMute.COL_ACCT)
|
||||
while (cursor.moveToNext()) {
|
||||
val item = MyItem(
|
||||
id = cursor.getLong(idxId),
|
||||
acct = Acct.parse(cursor.getString(idxName)),
|
||||
)
|
||||
add(item)
|
||||
}
|
||||
launchAndShowError {
|
||||
listAdapter.items = withContext(AppDispatchers.IO) {
|
||||
daoFavMute.listAll().map {
|
||||
MyItem(
|
||||
id = it.id,
|
||||
acct = Acct.parse(it.acct),
|
||||
)
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "loadData failed.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,9 @@ import com.jrummyapps.android.colorpicker.ColorPickerDialog
|
|||
import com.jrummyapps.android.colorpicker.ColorPickerDialogListener
|
||||
import jp.juggler.subwaytooter.databinding.ActHighlightEditBinding
|
||||
import jp.juggler.subwaytooter.table.HighlightWord
|
||||
import jp.juggler.subwaytooter.table.daoHighlightWord
|
||||
import jp.juggler.util.backPressed
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.data.decodeJsonObject
|
||||
import jp.juggler.util.data.mayUri
|
||||
import jp.juggler.util.data.notEmpty
|
||||
|
@ -83,32 +85,34 @@ class ActHighlightWordEdit
|
|||
|
||||
setResult(RESULT_CANCELED)
|
||||
|
||||
fun loadData(): HighlightWord? {
|
||||
savedInstanceState?.getString(STATE_ITEM)
|
||||
?.decodeJsonObject()
|
||||
?.let { return HighlightWord(it) }
|
||||
launchAndShowError {
|
||||
fun loadData(): HighlightWord? {
|
||||
savedInstanceState?.getString(STATE_ITEM)
|
||||
?.decodeJsonObject()
|
||||
?.let { return HighlightWord(it) }
|
||||
|
||||
intent?.string(EXTRA_INITIAL_TEXT)
|
||||
?.let { return HighlightWord(it) }
|
||||
intent?.string(EXTRA_INITIAL_TEXT)
|
||||
?.let { return HighlightWord(it) }
|
||||
|
||||
intent?.long(EXTRA_ITEM_ID)
|
||||
?.let { return HighlightWord.load(it) }
|
||||
intent?.long(EXTRA_ITEM_ID)
|
||||
?.let { return daoHighlightWord.load(it) }
|
||||
|
||||
return null
|
||||
return null
|
||||
}
|
||||
|
||||
val item = loadData()
|
||||
if (item == null) {
|
||||
log.d("missing source data")
|
||||
finish()
|
||||
return@launchAndShowError
|
||||
}
|
||||
|
||||
this@ActHighlightWordEdit.item = item
|
||||
|
||||
views.etName.setText(item.name)
|
||||
showSound()
|
||||
showColor()
|
||||
}
|
||||
|
||||
val item = loadData()
|
||||
if (item == null) {
|
||||
log.d("missing source data")
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
this.item = item
|
||||
|
||||
views.etName.setText(item.name)
|
||||
showSound()
|
||||
showColor()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
|
@ -254,22 +258,26 @@ class ActHighlightWordEdit
|
|||
}
|
||||
|
||||
private fun save() {
|
||||
uiToData()
|
||||
launchAndShowError {
|
||||
uiToData()
|
||||
val name = item.name
|
||||
|
||||
if (item.name.isEmpty()) {
|
||||
showToast(true, R.string.cant_leave_empty_keyword)
|
||||
return
|
||||
if (name.isNullOrBlank()) {
|
||||
showToast(true, R.string.cant_leave_empty_keyword)
|
||||
return@launchAndShowError
|
||||
}
|
||||
|
||||
val other = daoHighlightWord.load(name)
|
||||
if (other != null && other.id != item.id) {
|
||||
showToast(true, R.string.cant_save_duplicated_keyword)
|
||||
return@launchAndShowError
|
||||
}
|
||||
|
||||
daoHighlightWord.save(applicationContext, item)
|
||||
App1.getAppState(applicationContext).enableSpeech()
|
||||
showToast(false, R.string.saved)
|
||||
setResult(RESULT_OK)
|
||||
finish()
|
||||
}
|
||||
|
||||
val other = HighlightWord.load(item.name)
|
||||
if (other != null && other.id != item.id) {
|
||||
showToast(true, R.string.cant_save_duplicated_keyword)
|
||||
return
|
||||
}
|
||||
|
||||
item.save(this)
|
||||
showToast(false, R.string.saved)
|
||||
setResult(RESULT_OK)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,13 +14,16 @@ import jp.juggler.subwaytooter.databinding.ActHighlightListBinding
|
|||
import jp.juggler.subwaytooter.databinding.LvHighlightWordBinding
|
||||
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
||||
import jp.juggler.subwaytooter.table.HighlightWord
|
||||
import jp.juggler.subwaytooter.table.daoHighlightWord
|
||||
import jp.juggler.util.coroutine.AppDispatchers
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.data.cast
|
||||
import jp.juggler.util.data.mayUri
|
||||
import jp.juggler.util.data.notBlank
|
||||
import jp.juggler.util.data.notZero
|
||||
import jp.juggler.util.log.LogCategory
|
||||
import jp.juggler.util.log.errorEx
|
||||
import jp.juggler.util.ui.*
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
class ActHighlightWordList : AppCompatActivity() {
|
||||
|
@ -40,7 +43,7 @@ class ActHighlightWordList : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
fun tryRingTone(context: Context, uri: Uri?): Boolean {
|
||||
private fun tryRingTone(context: Context, uri: Uri?): Boolean {
|
||||
try {
|
||||
uri?.let { RingtoneManager.getRingtone(context, it) }
|
||||
?.let { ringtone ->
|
||||
|
@ -129,21 +132,10 @@ class ActHighlightWordList : AppCompatActivity() {
|
|||
}
|
||||
|
||||
private fun loadData() {
|
||||
try {
|
||||
listAdapter.items = buildList {
|
||||
HighlightWord.createCursor().use { cursor ->
|
||||
val colIdx = HighlightWord.ColIdx(cursor)
|
||||
while (cursor.moveToNext()) {
|
||||
try {
|
||||
add(HighlightWord(cursor, colIdx))
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "load error.")
|
||||
}
|
||||
}
|
||||
}
|
||||
launchAndShowError {
|
||||
listAdapter.items = withContext(AppDispatchers.IO) {
|
||||
daoHighlightWord.listAll()
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
errorEx(ex, "query error.")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -161,15 +153,17 @@ class ActHighlightWordList : AppCompatActivity() {
|
|||
val activity = this
|
||||
launchAndShowError {
|
||||
confirm(getString(R.string.delete_confirm, item.name))
|
||||
item.delete(activity)
|
||||
daoHighlightWord.delete(applicationContext, item)
|
||||
listAdapter.remove(item)
|
||||
App1.getAppState(activity).enableSpeech()
|
||||
}
|
||||
}
|
||||
|
||||
private fun speech(item: HighlightWord?) {
|
||||
item ?: return
|
||||
App1.getAppState(this@ActHighlightWordList)
|
||||
.addSpeech(item.name, dedupMode = DedupMode.None)
|
||||
item?.name?.notBlank()?.let {
|
||||
App1.getAppState(this@ActHighlightWordList)
|
||||
.addSpeech(it, dedupMode = DedupMode.None)
|
||||
}
|
||||
}
|
||||
|
||||
// リスト要素のViewHolder
|
||||
|
|
|
@ -12,12 +12,15 @@ import jp.juggler.subwaytooter.api.ApiPath
|
|||
import jp.juggler.subwaytooter.api.TootApiResult
|
||||
import jp.juggler.subwaytooter.api.entity.*
|
||||
import jp.juggler.subwaytooter.api.runApiTask
|
||||
import jp.juggler.subwaytooter.auth.AuthRepo
|
||||
import jp.juggler.subwaytooter.column.ColumnType
|
||||
import jp.juggler.subwaytooter.databinding.ActKeywordFilterBinding
|
||||
import jp.juggler.subwaytooter.databinding.LvKeywordFilterBinding
|
||||
import jp.juggler.subwaytooter.table.AcctColor
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.daoAcctColor
|
||||
import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||
import jp.juggler.util.backPressed
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.coroutine.launchMain
|
||||
import jp.juggler.util.data.*
|
||||
import jp.juggler.util.int
|
||||
|
@ -96,59 +99,64 @@ class ActKeywordFilter : AppCompatActivity() {
|
|||
|
||||
private val deleteIds = HashSet<String>()
|
||||
|
||||
val authRepo by lazy {
|
||||
AuthRepo(this)
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
backPressed { confirmBack() }
|
||||
super.onCreate(savedInstanceState)
|
||||
App1.setActivityTheme(this)
|
||||
|
||||
val intent = this.intent
|
||||
|
||||
// filter ID の有無はUIに影響するのでinitUIより先に初期化する
|
||||
this.filterId = EntityId.from(intent, EXTRA_FILTER_ID)
|
||||
|
||||
val a = intent.long(EXTRA_ACCOUNT_DB_ID)
|
||||
?.let { SavedAccount.loadAccount(this, it) }
|
||||
if (a == null) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
this.account = a
|
||||
|
||||
initUI()
|
||||
|
||||
showAccount()
|
||||
launchAndShowError {
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
if (filterId != null) {
|
||||
startLoading()
|
||||
} else {
|
||||
views.spExpire.setSelection(1)
|
||||
val initialText = intent.string(EXTRA_INITIAL_PHRASE)?.trim() ?: ""
|
||||
views.etTitle.setText(initialText)
|
||||
addKeywordArea(TootFilterKeyword(keyword = initialText))
|
||||
// filter ID の有無はUIに影響するのでinitUIより先に初期化する
|
||||
filterId = EntityId.from(intent, EXTRA_FILTER_ID)
|
||||
|
||||
val a = intent.long(EXTRA_ACCOUNT_DB_ID)
|
||||
?.let { daoSavedAccount.loadAccount(it) }
|
||||
if (a == null) {
|
||||
finish()
|
||||
return@launchAndShowError
|
||||
}
|
||||
} else {
|
||||
account = a
|
||||
|
||||
savedInstanceState.getStringArrayList(STATE_DELETE_IDS)
|
||||
?.let { deleteIds.addAll(it) }
|
||||
|
||||
savedInstanceState.getStringArrayList(STATE_KEYWORDS)
|
||||
?.mapNotNull { it?.decodeJsonObject() }
|
||||
?.forEach {
|
||||
try {
|
||||
addKeywordArea(TootFilterKeyword(it))
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "can't decode TootFilterKeyword")
|
||||
}
|
||||
showAccount()
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
if (filterId != null) {
|
||||
startLoading()
|
||||
} else {
|
||||
views.spExpire.setSelection(1)
|
||||
val initialText = intent.string(EXTRA_INITIAL_PHRASE)?.trim() ?: ""
|
||||
views.etTitle.setText(initialText)
|
||||
addKeywordArea(TootFilterKeyword(keyword = initialText))
|
||||
}
|
||||
} else {
|
||||
|
||||
savedInstanceState.int(STATE_EXPIRE_SPINNER)
|
||||
?.let { views.spExpire.setSelection(it) }
|
||||
savedInstanceState.getStringArrayList(STATE_DELETE_IDS)
|
||||
?.let { deleteIds.addAll(it) }
|
||||
|
||||
savedInstanceState.long(STATE_EXPIRE_AT)
|
||||
?.let { filterExpire = it }
|
||||
savedInstanceState.getStringArrayList(STATE_KEYWORDS)
|
||||
?.mapNotNull { it?.decodeJsonObject() }
|
||||
?.forEach {
|
||||
try {
|
||||
addKeywordArea(TootFilterKeyword(it))
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "can't decode TootFilterKeyword")
|
||||
}
|
||||
}
|
||||
|
||||
savedInstanceState.int(STATE_EXPIRE_SPINNER)
|
||||
?.let { views.spExpire.setSelection(it) }
|
||||
|
||||
savedInstanceState.long(STATE_EXPIRE_AT)
|
||||
?.let { filterExpire = it }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -218,7 +226,7 @@ class ActKeywordFilter : AppCompatActivity() {
|
|||
}
|
||||
|
||||
private fun showAccount() {
|
||||
views.tvAccount.text = AcctColor.getNicknameWithColor(account.acct)
|
||||
views.tvAccount.text = daoAcctColor.getNicknameWithColor(account.acct)
|
||||
}
|
||||
|
||||
private fun startLoading() {
|
||||
|
|
|
@ -17,8 +17,9 @@ import androidx.core.content.FileProvider
|
|||
import jp.juggler.subwaytooter.api.entity.TootStatus
|
||||
import jp.juggler.subwaytooter.column.Column
|
||||
import jp.juggler.subwaytooter.databinding.ActLanguageFilterBinding
|
||||
import jp.juggler.subwaytooter.dialog.ActionsDialog
|
||||
import jp.juggler.subwaytooter.dialog.actionsDialog
|
||||
import jp.juggler.util.*
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.coroutine.launchProgress
|
||||
import jp.juggler.util.data.*
|
||||
import jp.juggler.util.log.LogCategory
|
||||
|
@ -263,15 +264,17 @@ class ActLanguageFilter : AppCompatActivity(), View.OnClickListener {
|
|||
R.id.btnAdd -> edit(null)
|
||||
|
||||
R.id.btnMore -> {
|
||||
ActionsDialog()
|
||||
.addAction(getString(R.string.clear_all)) {
|
||||
languageList.clear()
|
||||
languageList.add(MyItem(TootStatus.LANGUAGE_CODE_DEFAULT, true))
|
||||
adapter.notifyDataSetChanged()
|
||||
launchAndShowError {
|
||||
actionsDialog {
|
||||
action(getString(R.string.clear_all)) {
|
||||
languageList.clear()
|
||||
languageList.add(MyItem(TootStatus.LANGUAGE_CODE_DEFAULT, true))
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
action(getString(R.string.export)) { export() }
|
||||
action(getString(R.string.import_)) { import() }
|
||||
}
|
||||
.addAction(getString(R.string.export)) { export() }
|
||||
.addAction(getString(R.string.import_)) { import() }
|
||||
.show(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -338,14 +341,18 @@ class ActLanguageFilter : AppCompatActivity(), View.OnClickListener {
|
|||
val languageList =
|
||||
activity.languageNameMap.map { MyItem(it.key, true) }.sortedWith(languageComparator)
|
||||
btnPresets.setOnClickListener {
|
||||
val ad = ActionsDialog()
|
||||
for (a in languageList) {
|
||||
ad.addAction("${a.code} ${activity.getDesc(a)}") {
|
||||
etLanguage.setText(a.code)
|
||||
updateDesc()
|
||||
activity.run {
|
||||
launchAndShowError {
|
||||
actionsDialog(getString(R.string.presets)) {
|
||||
for (a in languageList) {
|
||||
action("${a.code} ${activity.getDesc(a)}") {
|
||||
etLanguage.setText(a.code)
|
||||
updateDesc()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ad.show(activity, activity.getString(R.string.presets))
|
||||
}
|
||||
|
||||
etLanguage.setText(item?.code ?: "")
|
||||
|
|
|
@ -3,7 +3,6 @@ package jp.juggler.subwaytooter
|
|||
import android.app.Activity
|
||||
import android.app.Dialog
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Typeface
|
||||
import android.os.Build
|
||||
|
@ -19,6 +18,7 @@ import android.widget.LinearLayout
|
|||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewpager.widget.ViewPager
|
||||
import jp.juggler.subwaytooter.action.accessTokenPrompt
|
||||
|
@ -32,17 +32,15 @@ import jp.juggler.subwaytooter.column.*
|
|||
import jp.juggler.subwaytooter.dialog.DlgQuickTootMenu
|
||||
import jp.juggler.subwaytooter.itemviewholder.StatusButtonsPopup
|
||||
import jp.juggler.subwaytooter.notification.checkNotificationImmediateAll
|
||||
import jp.juggler.subwaytooter.pref.PrefB
|
||||
import jp.juggler.subwaytooter.pref.PrefI
|
||||
import jp.juggler.subwaytooter.pref.PrefS
|
||||
import jp.juggler.subwaytooter.pref.put
|
||||
import jp.juggler.subwaytooter.pref.*
|
||||
import jp.juggler.subwaytooter.span.MyClickableSpan
|
||||
import jp.juggler.subwaytooter.span.MyClickableSpanHandler
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||
import jp.juggler.subwaytooter.util.*
|
||||
import jp.juggler.subwaytooter.view.MyDrawerLayout
|
||||
import jp.juggler.subwaytooter.view.MyEditText
|
||||
import jp.juggler.util.backPressed
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.data.notEmpty
|
||||
import jp.juggler.util.int
|
||||
import jp.juggler.util.log.LogCategory
|
||||
|
@ -53,6 +51,9 @@ import jp.juggler.util.string
|
|||
import jp.juggler.util.ui.ActivityResultHandler
|
||||
import jp.juggler.util.ui.attrColor
|
||||
import jp.juggler.util.ui.isNotOk
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.internal.toHexString
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.*
|
||||
|
@ -152,7 +153,6 @@ class ActMain : AppCompatActivity(),
|
|||
|
||||
lateinit var completionHelper: CompletionHelper
|
||||
|
||||
lateinit var pref: SharedPreferences
|
||||
lateinit var handler: Handler
|
||||
lateinit var appState: AppState
|
||||
|
||||
|
@ -196,7 +196,7 @@ class ActMain : AppCompatActivity(),
|
|||
override fun run() {
|
||||
handler.removeCallbacks(this)
|
||||
if (!isStartedEx) return
|
||||
if (PrefB.bpRelativeTimestamp(pref)) {
|
||||
if (PrefB.bpRelativeTimestamp.value) {
|
||||
appState.columnList.forEach { it.fireRelativeTime() }
|
||||
handler.postDelayed(this, 10000L)
|
||||
}
|
||||
|
@ -209,7 +209,7 @@ class ActMain : AppCompatActivity(),
|
|||
set(value) {
|
||||
if (value != quickPostVisibility) {
|
||||
quickPostVisibility = value
|
||||
pref.edit().put(PrefS.spQuickTootVisibility, value.id.toString()).apply()
|
||||
PrefS.spQuickTootVisibility.value = value.id.toString()
|
||||
showQuickPostVisibility()
|
||||
}
|
||||
}
|
||||
|
@ -262,7 +262,7 @@ class ActMain : AppCompatActivity(),
|
|||
}
|
||||
|
||||
val arAppSetting = ActivityResultHandler(log) { r ->
|
||||
Column.reloadDefaultColor(this, pref)
|
||||
Column.reloadDefaultColor(this)
|
||||
showFooterColor()
|
||||
updateColumnStrip()
|
||||
if (r.resultCode == RESULT_APP_DATA_IMPORT) {
|
||||
|
@ -282,15 +282,17 @@ class ActMain : AppCompatActivity(),
|
|||
}
|
||||
|
||||
val arAccountSetting = ActivityResultHandler(log) { r ->
|
||||
updateColumnStrip()
|
||||
appState.columnList.forEach { it.fireShowColumnHeader() }
|
||||
when (r.resultCode) {
|
||||
RESULT_OK -> r.data?.data?.let { openBrowser(it) }
|
||||
launchAndShowError {
|
||||
updateColumnStrip()
|
||||
appState.columnList.forEach { it.fireShowColumnHeader() }
|
||||
when (r.resultCode) {
|
||||
RESULT_OK -> r.data?.data?.let { openBrowser(it) }
|
||||
|
||||
ActAccountSetting.RESULT_INPUT_ACCESS_TOKEN ->
|
||||
r.data?.long(ActAccountSetting.EXTRA_DB_ID)
|
||||
?.let { SavedAccount.loadAccount(this, it) }
|
||||
?.let { accessTokenPrompt(it.apiHost) }
|
||||
ActAccountSetting.RESULT_INPUT_ACCESS_TOKEN ->
|
||||
r.data?.long(ActAccountSetting.EXTRA_DB_ID)
|
||||
?.let { daoSavedAccount.loadAccount(it) }
|
||||
?.let { accessTokenPrompt(it.apiHost) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -323,10 +325,12 @@ class ActMain : AppCompatActivity(),
|
|||
}
|
||||
}
|
||||
|
||||
private val prNotification = permissionSpecNotification.requester {
|
||||
val prNotification = permissionSpecNotification.requester {
|
||||
// 特に何もしない
|
||||
}
|
||||
|
||||
private var startAfterJob: WeakReference<Job>? = null
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// ライフサイクルイベント
|
||||
|
||||
|
@ -352,11 +356,10 @@ class ActMain : AppCompatActivity(),
|
|||
|
||||
appState = App1.getAppState(this)
|
||||
handler = appState.handler
|
||||
pref = appState.pref
|
||||
density = appState.density
|
||||
completionHelper = CompletionHelper(this, pref, appState.handler)
|
||||
completionHelper = CompletionHelper(this, appState.handler)
|
||||
|
||||
EmojiDecoder.useTwemoji = PrefB.bpUseTwemoji(pref)
|
||||
EmojiDecoder.useTwemoji = PrefB.bpUseTwemoji.value
|
||||
|
||||
acctPadLr = (0.5f + 4f * density).toInt()
|
||||
reloadTextSize()
|
||||
|
@ -373,8 +376,6 @@ class ActMain : AppCompatActivity(),
|
|||
if (savedInstanceState != null) {
|
||||
sharedIntent2?.let { handleSharedIntent(it) }
|
||||
}
|
||||
|
||||
checkPrivacyPolicy()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
@ -452,91 +453,94 @@ class ActMain : AppCompatActivity(),
|
|||
|
||||
sideMenuAdapter.onActivityStart()
|
||||
|
||||
launchDialogs()
|
||||
|
||||
// 残りの処理はActivityResultの処理より後回しにしたい
|
||||
handler.postDelayed(onStartAfter, 1L)
|
||||
|
||||
prNotification.checkOrLaunch()
|
||||
themeDefaultChangedDialog()
|
||||
}
|
||||
}
|
||||
|
||||
private val onStartAfter = Runnable {
|
||||
benchmark("onStartAfter total") {
|
||||
|
||||
benchmark("sweepBuggieData") {
|
||||
// バグいアカウントデータを消す
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
SavedAccount.sweepBuggieData()
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "sweepBuggieData failed.")
|
||||
}
|
||||
}
|
||||
delay(1L)
|
||||
benchmark("onStartAfter total") {
|
||||
|
||||
val newAccounts = benchmark("loadAccountList") {
|
||||
SavedAccount.loadAccountList(this)
|
||||
}
|
||||
|
||||
benchmark("removeColumnByAccount") {
|
||||
val setDbId = newAccounts.map { it.db_id }.toSet()
|
||||
// アカウント設定から戻ってきたら、カラムを消す必要があるかもしれない
|
||||
appState.columnList
|
||||
.mapIndexedNotNull { index, column ->
|
||||
when {
|
||||
column.accessInfo.isNA -> index
|
||||
setDbId.contains(column.accessInfo.db_id) -> index
|
||||
else -> null
|
||||
benchmark("sweepBuggieData") {
|
||||
// バグいアカウントデータを消す
|
||||
try {
|
||||
daoSavedAccount.sweepBuggieData()
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "sweepBuggieData failed.")
|
||||
}
|
||||
}
|
||||
}.takeIf { it.size != appState.columnCount }
|
||||
?.let { setColumnsOrder(it) }
|
||||
}
|
||||
|
||||
benchmark("fireColumnColor") {
|
||||
// 背景画像を表示しない設定が変更された時にカラムの背景を設定しなおす
|
||||
appState.columnList.forEach { column ->
|
||||
column.viewHolder?.lastAnnouncementShown = 0L
|
||||
column.fireColumnColor()
|
||||
val newAccounts = benchmark("loadAccountList") {
|
||||
daoSavedAccount.loadAccountList()
|
||||
}
|
||||
|
||||
benchmark("removeColumnByAccount") {
|
||||
val setDbId = newAccounts.map { it.db_id }.toSet()
|
||||
// アカウント設定から戻ってきたら、カラムを消す必要があるかもしれない
|
||||
appState.columnList
|
||||
.mapIndexedNotNull { index, column ->
|
||||
when {
|
||||
column.accessInfo.isNA -> index
|
||||
setDbId.contains(column.accessInfo.db_id) -> index
|
||||
else -> null
|
||||
}
|
||||
}.takeIf { it.size != appState.columnCount }
|
||||
?.let { setColumnsOrder(it) }
|
||||
}
|
||||
|
||||
benchmark("fireColumnColor") {
|
||||
// 背景画像を表示しない設定が変更された時にカラムの背景を設定しなおす
|
||||
appState.columnList.forEach { column ->
|
||||
column.viewHolder?.lastAnnouncementShown = 0L
|
||||
column.fireColumnColor()
|
||||
}
|
||||
}
|
||||
benchmark("reloadAccountSetting") {
|
||||
// 各カラムのアカウント設定を読み直す
|
||||
reloadAccountSetting(newAccounts)
|
||||
}
|
||||
benchmark("refreshAfterPost") {
|
||||
// 投稿直後ならカラムの再取得を行う
|
||||
refreshAfterPost()
|
||||
}
|
||||
benchmark("column.onActivityStart") {
|
||||
// 画面復帰時に再取得などを行う
|
||||
appState.columnList.forEach { it.onActivityStart() }
|
||||
}
|
||||
benchmark("streamManager.onScreenStart") {
|
||||
// 画面復帰時にストリーミング接続を開始する
|
||||
appState.streamManager.onScreenStart()
|
||||
}
|
||||
benchmark("updateColumnStripSelection") {
|
||||
// カラムの表示範囲インジケータを更新
|
||||
updateColumnStripSelection(-1, -1f)
|
||||
}
|
||||
benchmark("fireShowContent") {
|
||||
appState.columnList.forEach {
|
||||
it.fireShowContent(reason = "ActMain onStart", reset = true)
|
||||
}
|
||||
}
|
||||
benchmark("proc_updateRelativeTime") {
|
||||
// 相対時刻表示の更新
|
||||
procUpdateRelativeTime.run()
|
||||
}
|
||||
benchmark("enableSpeech") {
|
||||
// スピーチの開始
|
||||
appState.enableSpeech()
|
||||
}
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "startAfter failed.")
|
||||
}
|
||||
}
|
||||
benchmark("reloadAccountSetting") {
|
||||
// 各カラムのアカウント設定を読み直す
|
||||
reloadAccountSetting(newAccounts)
|
||||
}
|
||||
benchmark("refreshAfterPost") {
|
||||
// 投稿直後ならカラムの再取得を行う
|
||||
refreshAfterPost()
|
||||
}
|
||||
benchmark("column.onActivityStart") {
|
||||
// 画面復帰時に再取得などを行う
|
||||
appState.columnList.forEach { it.onActivityStart() }
|
||||
}
|
||||
benchmark("streamManager.onScreenStart") {
|
||||
// 画面復帰時にストリーミング接続を開始する
|
||||
appState.streamManager.onScreenStart()
|
||||
}
|
||||
benchmark("updateColumnStripSelection") {
|
||||
// カラムの表示範囲インジケータを更新
|
||||
updateColumnStripSelection(-1, -1f)
|
||||
}
|
||||
benchmark("fireShowContent") {
|
||||
appState.columnList.forEach {
|
||||
it.fireShowContent(reason = "ActMain onStart", reset = true)
|
||||
}
|
||||
}
|
||||
benchmark("proc_updateRelativeTime") {
|
||||
// 相対時刻表示の更新
|
||||
procUpdateRelativeTime.run()
|
||||
}
|
||||
benchmark("enableSpeech") {
|
||||
// スピーチの開始
|
||||
appState.enableSpeech()
|
||||
}
|
||||
}.let { startAfterJob = WeakReference(it) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
log.d("onStop")
|
||||
isStartedEx = false
|
||||
handler.removeCallbacks(onStartAfter)
|
||||
startAfterJob?.get()?.cancel()
|
||||
startAfterJob = null
|
||||
handler.removeCallbacks(procUpdateRelativeTime)
|
||||
|
||||
completionHelper.closeAcctPopup()
|
||||
|
@ -583,7 +587,7 @@ class ActMain : AppCompatActivity(),
|
|||
at android.os.Binder.execTransact (Binder.java:739)
|
||||
*/
|
||||
|
||||
if (PrefB.bpDontScreenOff(pref)) {
|
||||
if (PrefB.bpDontScreenOff.value) {
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
} else {
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
|
@ -611,7 +615,7 @@ class ActMain : AppCompatActivity(),
|
|||
{ env -> env.pager.currentItem },
|
||||
{ env -> env.visibleColumnsIndices.first })
|
||||
log.d("ipLastColumnPos save $lastPos")
|
||||
pref.edit().put(PrefI.ipLastColumnPos, lastPos).apply()
|
||||
PrefI.ipLastColumnPos.value = lastPos
|
||||
|
||||
appState.columnList.forEach { it.saveScrollPosition() }
|
||||
|
||||
|
@ -701,10 +705,10 @@ class ActMain : AppCompatActivity(),
|
|||
setContentView(R.layout.act_main)
|
||||
|
||||
quickPostVisibility =
|
||||
TootVisibility.parseSavedVisibility(PrefS.spQuickTootVisibility(pref))
|
||||
TootVisibility.parseSavedVisibility(PrefS.spQuickTootVisibility.value)
|
||||
?: quickPostVisibility
|
||||
|
||||
Column.reloadDefaultColor(this, pref)
|
||||
Column.reloadDefaultColor(this)
|
||||
|
||||
galaxyBackgroundWorkaround()
|
||||
|
||||
|
|
|
@ -25,15 +25,14 @@ import com.google.android.exoplayer2.util.RepeatModeUtil.REPEAT_TOGGLE_MODE_ONE
|
|||
import jp.juggler.subwaytooter.api.*
|
||||
import jp.juggler.subwaytooter.api.entity.*
|
||||
import jp.juggler.subwaytooter.databinding.ActMediaViewerBinding
|
||||
import jp.juggler.subwaytooter.dialog.ActionsDialog
|
||||
import jp.juggler.subwaytooter.dialog.actionsDialog
|
||||
import jp.juggler.subwaytooter.drawable.MediaBackgroundDrawable
|
||||
import jp.juggler.subwaytooter.global.appPref
|
||||
import jp.juggler.subwaytooter.pref.PrefI
|
||||
import jp.juggler.subwaytooter.pref.put
|
||||
import jp.juggler.subwaytooter.util.permissionSpecMediaDownload
|
||||
import jp.juggler.subwaytooter.util.requester
|
||||
import jp.juggler.subwaytooter.view.PinchBitmapView
|
||||
import jp.juggler.util.*
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.coroutine.launchMain
|
||||
import jp.juggler.util.data.*
|
||||
import jp.juggler.util.log.LogCategory
|
||||
|
@ -304,7 +303,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
views.pbvImage.background = MediaBackgroundDrawable(
|
||||
context = views.root.context,
|
||||
tileStep = tileStep,
|
||||
kind = MediaBackgroundDrawable.Kind.fromIndex(PrefI.ipMediaBackground(this))
|
||||
kind = MediaBackgroundDrawable.Kind.fromIndex(PrefI.ipMediaBackground.value)
|
||||
)
|
||||
|
||||
val enablePaging = mediaList.size > 1
|
||||
|
@ -444,7 +443,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
|
||||
val url = when {
|
||||
forceLocalUrl -> ta.url
|
||||
else -> ta.getLargeUrl(appPref)
|
||||
else -> ta.getLargeUrl()
|
||||
}
|
||||
if (url == null) {
|
||||
showError("missing media attachment url.")
|
||||
|
@ -573,7 +572,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
pbvImage.visible().setBitmap(null)
|
||||
}
|
||||
|
||||
val urlList = ta.getLargeUrlList(appPref)
|
||||
val urlList = ta.getLargeUrlList()
|
||||
if (urlList.isEmpty()) {
|
||||
showError("missing media attachment url.")
|
||||
return
|
||||
|
@ -650,7 +649,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
?: error("missing DownloadManager system service")
|
||||
|
||||
val url = if (ta is TootAttachment) {
|
||||
ta.getLargeUrl(appPref)
|
||||
ta.getLargeUrl()
|
||||
} else {
|
||||
null
|
||||
} ?: return
|
||||
|
@ -781,65 +780,80 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
|||
}
|
||||
|
||||
private fun more(ta: TootAttachmentLike) {
|
||||
val ad = ActionsDialog()
|
||||
if (ta is TootAttachment) {
|
||||
val url = ta.getLargeUrl(appPref) ?: return
|
||||
ad.addAction(getString(R.string.open_in_browser)) { share(Intent.ACTION_VIEW, url) }
|
||||
ad.addAction(getString(R.string.share_url)) { share(Intent.ACTION_SEND, url) }
|
||||
ad.addAction(getString(R.string.copy_url)) { copy(url) }
|
||||
addMoreMenu(ad, "url", ta.url, Intent.ACTION_VIEW)
|
||||
addMoreMenu(ad, "remote_url", ta.remote_url, Intent.ACTION_VIEW)
|
||||
addMoreMenu(ad, "preview_url", ta.preview_url, Intent.ACTION_VIEW)
|
||||
addMoreMenu(ad, "preview_remote_url", ta.preview_remote_url, Intent.ACTION_VIEW)
|
||||
addMoreMenu(ad, "text_url", ta.text_url, Intent.ACTION_VIEW)
|
||||
} else if (ta is TootAttachmentMSP) {
|
||||
val url = ta.preview_url
|
||||
ad.addAction(getString(R.string.open_in_browser)) { share(Intent.ACTION_VIEW, url) }
|
||||
ad.addAction(getString(R.string.share_url)) { share(Intent.ACTION_SEND, url) }
|
||||
ad.addAction(getString(R.string.copy_url)) { copy(url) }
|
||||
}
|
||||
launchAndShowError {
|
||||
actionsDialog {
|
||||
fun addMoreMenu(
|
||||
captionPrefix: String,
|
||||
url: String?,
|
||||
@Suppress("SameParameterValue") action: String,
|
||||
) {
|
||||
val uri = url.mayUri() ?: return
|
||||
val caption = getString(R.string.open_browser_of, captionPrefix)
|
||||
action(caption) {
|
||||
try {
|
||||
val intent = Intent(action, uri)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
startActivity(intent)
|
||||
} catch (ex: Throwable) {
|
||||
showToast(ex, "can't open app.")
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ta is TootAttachment) {
|
||||
val url = ta.getLargeUrl()
|
||||
if (url != null) {
|
||||
action(getString(R.string.open_in_browser)) {
|
||||
share(Intent.ACTION_VIEW, url)
|
||||
}
|
||||
action(getString(R.string.share_url)) {
|
||||
share(Intent.ACTION_SEND, url)
|
||||
}
|
||||
action(getString(R.string.copy_url)) {
|
||||
copy(url)
|
||||
}
|
||||
}
|
||||
addMoreMenu("url", ta.url, Intent.ACTION_VIEW)
|
||||
addMoreMenu("remote_url", ta.remote_url, Intent.ACTION_VIEW)
|
||||
addMoreMenu("preview_url", ta.preview_url, Intent.ACTION_VIEW)
|
||||
addMoreMenu("preview_remote_url", ta.preview_remote_url, Intent.ACTION_VIEW)
|
||||
addMoreMenu("text_url", ta.text_url, Intent.ACTION_VIEW)
|
||||
} else if (ta is TootAttachmentMSP) {
|
||||
val url = ta.preview_url
|
||||
action(getString(R.string.open_in_browser)) {
|
||||
share(Intent.ACTION_VIEW, url)
|
||||
}
|
||||
action(getString(R.string.share_url)) {
|
||||
share(Intent.ACTION_SEND, url)
|
||||
}
|
||||
action(getString(R.string.copy_url)) {
|
||||
copy(url)
|
||||
}
|
||||
}
|
||||
|
||||
if (TootAttachmentType.Image == mediaList.elementAtOrNull(idx)?.type) {
|
||||
ad.addAction(getString(R.string.background_pattern)) { mediaBackgroundDialog() }
|
||||
}
|
||||
|
||||
ad.show(this, null)
|
||||
}
|
||||
|
||||
private fun addMoreMenu(
|
||||
ad: ActionsDialog,
|
||||
captionPrefix: String,
|
||||
url: String?,
|
||||
@Suppress("SameParameterValue") action: String,
|
||||
) {
|
||||
val uri = url.mayUri() ?: return
|
||||
val caption = getString(R.string.open_browser_of, captionPrefix)
|
||||
ad.addAction(caption) {
|
||||
try {
|
||||
val intent = Intent(action, uri)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
startActivity(intent)
|
||||
} catch (ex: Throwable) {
|
||||
showToast(ex, "can't open app.")
|
||||
if (TootAttachmentType.Image == mediaList.elementAtOrNull(idx)?.type) {
|
||||
action(getString(R.string.background_pattern)) { mediaBackgroundDialog() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun mediaBackgroundDialog() {
|
||||
val ad = ActionsDialog()
|
||||
for (k in MediaBackgroundDrawable.Kind.values()) {
|
||||
if (!k.isMediaBackground) continue
|
||||
ad.addAction(k.name) {
|
||||
val idx = k.toIndex()
|
||||
appPref.edit().put(PrefI.ipMediaBackground, idx).apply()
|
||||
views.pbvImage.background = MediaBackgroundDrawable(
|
||||
context = views.root.context,
|
||||
tileStep = tileStep,
|
||||
kind = k
|
||||
)
|
||||
launchAndShowError {
|
||||
actionsDialog(getString(R.string.background_pattern)) {
|
||||
for (k in MediaBackgroundDrawable.Kind.values()) {
|
||||
if (!k.isMediaBackground) continue
|
||||
action(k.name) {
|
||||
val idx = k.toIndex()
|
||||
PrefI.ipMediaBackground.value = idx
|
||||
views.pbvImage.background = MediaBackgroundDrawable(
|
||||
context = views.root.context,
|
||||
tileStep = tileStep,
|
||||
kind = k
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ad.show(this, getString(R.string.background_pattern))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -9,11 +9,14 @@ import jp.juggler.subwaytooter.databinding.ActWordListBinding
|
|||
import jp.juggler.subwaytooter.databinding.LvMuteAppBinding
|
||||
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
||||
import jp.juggler.subwaytooter.table.MutedApp
|
||||
import jp.juggler.subwaytooter.table.appDatabase
|
||||
import jp.juggler.util.backPressed
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.data.cast
|
||||
import jp.juggler.util.log.LogCategory
|
||||
import jp.juggler.util.ui.setNavigationBack
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class ActMutedApp : AppCompatActivity() {
|
||||
|
||||
|
@ -27,6 +30,8 @@ class ActMutedApp : AppCompatActivity() {
|
|||
|
||||
private val listAdapter by lazy { MyListAdapter() }
|
||||
|
||||
private val daoMutedApp by lazy { MutedApp.Access(appDatabase) }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
backPressed {
|
||||
|
@ -47,48 +52,33 @@ class ActMutedApp : AppCompatActivity() {
|
|||
}
|
||||
|
||||
private fun loadData() {
|
||||
listAdapter.items = buildList {
|
||||
try {
|
||||
MutedApp.createCursor().use { cursor ->
|
||||
val idxId = cursor.getColumnIndex(MutedApp.COL_ID)
|
||||
val idxName = cursor.getColumnIndex(MutedApp.COL_NAME)
|
||||
while (cursor.moveToNext()) {
|
||||
val item = MyItem(
|
||||
id = cursor.getLong(idxId),
|
||||
name = cursor.getString(idxName)
|
||||
)
|
||||
add(item)
|
||||
}
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "loadData failed.")
|
||||
launchAndShowError {
|
||||
listAdapter.items = withContext(Dispatchers.IO) {
|
||||
daoMutedApp.listAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun delete(item: MyItem?) {
|
||||
private fun delete(item: MutedApp?) {
|
||||
item ?: return
|
||||
launchAndShowError {
|
||||
confirm(R.string.delete_confirm, item.name)
|
||||
MutedApp.delete(item.name)
|
||||
daoMutedApp.delete(item.name)
|
||||
listAdapter.remove(item)
|
||||
}
|
||||
}
|
||||
|
||||
// リスト要素のデータ
|
||||
private class MyItem(val id: Long, val name: String)
|
||||
|
||||
// リスト要素のViewHolder
|
||||
private inner class MyViewHolder(parent: ViewGroup?) {
|
||||
val views = LvMuteAppBinding.inflate(layoutInflater, parent, false)
|
||||
var lastItem: MyItem? = null
|
||||
var lastItem: MutedApp? = null
|
||||
|
||||
init {
|
||||
views.root.tag = this
|
||||
views.btnDelete.setOnClickListener { delete(lastItem) }
|
||||
}
|
||||
|
||||
fun bind(item: MyItem?) {
|
||||
fun bind(item: MutedApp?) {
|
||||
item ?: return
|
||||
lastItem = item
|
||||
views.tvName.text = item.name
|
||||
|
@ -96,13 +86,13 @@ class ActMutedApp : AppCompatActivity() {
|
|||
}
|
||||
|
||||
private inner class MyListAdapter : BaseAdapter() {
|
||||
var items: List<MyItem> = emptyList()
|
||||
var items: List<MutedApp> = emptyList()
|
||||
set(value) {
|
||||
field = value
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun remove(item: MyItem) {
|
||||
fun remove(item: MutedApp) {
|
||||
items = items.filter { it != item }
|
||||
}
|
||||
|
||||
|
|
|
@ -9,11 +9,14 @@ import jp.juggler.subwaytooter.databinding.ActWordListBinding
|
|||
import jp.juggler.subwaytooter.databinding.LvMuteAppBinding
|
||||
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
||||
import jp.juggler.subwaytooter.table.UserRelation
|
||||
import jp.juggler.subwaytooter.table.daoUserRelation
|
||||
import jp.juggler.util.backPressed
|
||||
import jp.juggler.util.coroutine.AppDispatchers
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.data.cast
|
||||
import jp.juggler.util.log.LogCategory
|
||||
import jp.juggler.util.ui.setNavigationBack
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class ActMutedPseudoAccount : AppCompatActivity() {
|
||||
|
||||
|
@ -47,62 +50,47 @@ class ActMutedPseudoAccount : AppCompatActivity() {
|
|||
}
|
||||
|
||||
private fun loadData() {
|
||||
listAdapter.items = buildList {
|
||||
try {
|
||||
UserRelation.createCursorPseudoMuted().use { cursor ->
|
||||
val idxId = UserRelation.COL_ID.getIndex(cursor)
|
||||
val idxName = UserRelation.COL_WHO_ID.getIndex(cursor)
|
||||
while (cursor.moveToNext()) {
|
||||
val item = MyItem(
|
||||
id = cursor.getLong(idxId),
|
||||
name = cursor.getString(idxName)
|
||||
)
|
||||
add(item)
|
||||
}
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "loadData failed.")
|
||||
launchAndShowError {
|
||||
listAdapter.items = withContext(AppDispatchers.IO) {
|
||||
daoUserRelation.listPseudoMuted()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun delete(item: MyItem?) {
|
||||
private fun delete(item: UserRelation?) {
|
||||
item ?: return
|
||||
launchAndShowError {
|
||||
confirm(R.string.delete_confirm, item.name)
|
||||
UserRelation.deletePseudo(item.id)
|
||||
confirm(R.string.delete_confirm, item.whoId)
|
||||
daoUserRelation.deletePseudo(item.id)
|
||||
listAdapter.remove(item)
|
||||
}
|
||||
}
|
||||
|
||||
// リスト要素のデータ
|
||||
private class MyItem(val id: Long, val name: String)
|
||||
|
||||
// リスト要素のViewHolder
|
||||
private inner class MyViewHolder(parent: ViewGroup?) {
|
||||
val views = LvMuteAppBinding.inflate(layoutInflater, parent, false)
|
||||
private var lastItem: MyItem? = null
|
||||
private var lastItem: UserRelation? = null
|
||||
|
||||
init {
|
||||
views.root.tag = this
|
||||
views.btnDelete.setOnClickListener { delete(lastItem) }
|
||||
}
|
||||
|
||||
fun bind(item: MyItem?) {
|
||||
fun bind(item: UserRelation?) {
|
||||
item ?: return
|
||||
lastItem = item
|
||||
views.tvName.text = item.name
|
||||
views.tvName.text = item.whoId
|
||||
}
|
||||
}
|
||||
|
||||
private inner class MyListAdapter : BaseAdapter() {
|
||||
var items: List<MyItem> = emptyList()
|
||||
var items: List<UserRelation> = emptyList()
|
||||
set(value) {
|
||||
field = value
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun remove(item: MyItem) {
|
||||
fun remove(item: UserRelation) {
|
||||
items = items.filter { it != item }
|
||||
}
|
||||
|
||||
|
|
|
@ -9,11 +9,14 @@ import jp.juggler.subwaytooter.databinding.ActWordListBinding
|
|||
import jp.juggler.subwaytooter.databinding.LvMuteAppBinding
|
||||
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
||||
import jp.juggler.subwaytooter.table.MutedWord
|
||||
import jp.juggler.subwaytooter.table.daoMutedWord
|
||||
import jp.juggler.util.backPressed
|
||||
import jp.juggler.util.coroutine.AppDispatchers
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.data.cast
|
||||
import jp.juggler.util.log.LogCategory
|
||||
import jp.juggler.util.ui.setNavigationBack
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class ActMutedWord : AppCompatActivity() {
|
||||
|
||||
|
@ -47,48 +50,33 @@ class ActMutedWord : AppCompatActivity() {
|
|||
}
|
||||
|
||||
private fun loadData() {
|
||||
listAdapter.items = buildList {
|
||||
try {
|
||||
MutedWord.createCursor().use { cursor ->
|
||||
val idxId = cursor.getColumnIndex(MutedWord.COL_ID)
|
||||
val idxName = cursor.getColumnIndex(MutedWord.COL_NAME)
|
||||
while (cursor.moveToNext()) {
|
||||
val item = MyItem(
|
||||
id = cursor.getLong(idxId),
|
||||
name = cursor.getString(idxName)
|
||||
)
|
||||
add(item)
|
||||
}
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "loadData failed.")
|
||||
launchAndShowError {
|
||||
listAdapter.items = withContext(AppDispatchers.IO) {
|
||||
daoMutedWord.listAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun delete(item: MyItem?) {
|
||||
private fun delete(item: MutedWord?) {
|
||||
item ?: return
|
||||
launchAndShowError {
|
||||
confirm(R.string.delete_confirm, item.name)
|
||||
MutedWord.delete(item.name)
|
||||
daoMutedWord.delete(item.name)
|
||||
listAdapter.remove(item)
|
||||
}
|
||||
}
|
||||
|
||||
// リスト要素のデータ
|
||||
private class MyItem(val id: Long, val name: String)
|
||||
|
||||
// リスト要素のViewHolder
|
||||
private inner class MyViewHolder(parent: ViewGroup?) {
|
||||
val views = LvMuteAppBinding.inflate(layoutInflater, parent, false)
|
||||
private var lastItem: MyItem? = null
|
||||
private var lastItem: MutedWord? = null
|
||||
|
||||
init {
|
||||
views.root.tag = this
|
||||
views.btnDelete.setOnClickListener { delete(lastItem) }
|
||||
}
|
||||
|
||||
fun bind(item: MyItem?) {
|
||||
fun bind(item: MutedWord?) {
|
||||
item ?: return
|
||||
lastItem = item
|
||||
views.tvName.text = item.name
|
||||
|
@ -96,13 +84,13 @@ class ActMutedWord : AppCompatActivity() {
|
|||
}
|
||||
|
||||
private inner class MyListAdapter : BaseAdapter() {
|
||||
var items: List<MyItem> = emptyList()
|
||||
var items: List<MutedWord> = emptyList()
|
||||
set(value) {
|
||||
field = value
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun remove(item: MyItem?) {
|
||||
fun remove(item: MutedWord?) {
|
||||
items = items.filter { it != item }
|
||||
}
|
||||
|
||||
|
|
|
@ -14,8 +14,10 @@ import com.jrummyapps.android.colorpicker.ColorPickerDialogListener
|
|||
import jp.juggler.subwaytooter.api.entity.Acct
|
||||
import jp.juggler.subwaytooter.databinding.ActNicknameBinding
|
||||
import jp.juggler.subwaytooter.table.AcctColor
|
||||
import jp.juggler.subwaytooter.table.daoAcctColor
|
||||
import jp.juggler.util.backPressed
|
||||
import jp.juggler.util.boolean
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.data.mayUri
|
||||
import jp.juggler.util.data.notEmpty
|
||||
import jp.juggler.util.data.notZero
|
||||
|
@ -134,11 +136,11 @@ class ActNickname : AppCompatActivity(), View.OnClickListener, ColorPickerDialog
|
|||
|
||||
views.tvAcct.text = acctPretty
|
||||
|
||||
val ac = AcctColor.load(acctAscii, acctPretty)
|
||||
colorBg = ac.color_bg
|
||||
colorFg = ac.color_fg
|
||||
val ac = daoAcctColor.load(acctAscii)
|
||||
colorBg = ac.colorBg
|
||||
colorFg = ac.colorFg
|
||||
views.etNickname.setText(ac.nickname)
|
||||
notificationSoundUri = ac.notification_sound
|
||||
notificationSoundUri = ac.notificationSound
|
||||
|
||||
loadingBusy = false
|
||||
show()
|
||||
|
@ -146,14 +148,17 @@ class ActNickname : AppCompatActivity(), View.OnClickListener, ColorPickerDialog
|
|||
|
||||
private fun save() {
|
||||
if (loadingBusy) return
|
||||
AcctColor(
|
||||
acctAscii,
|
||||
acctPretty,
|
||||
views.etNickname.text.toString().trim { it <= ' ' },
|
||||
colorFg,
|
||||
colorBg,
|
||||
notificationSoundUri
|
||||
).save(System.currentTimeMillis())
|
||||
launchAndShowError {
|
||||
daoAcctColor.save(
|
||||
System.currentTimeMillis(),
|
||||
AcctColor(
|
||||
nicknameSave = views.etNickname.text.toString().trim { it <= ' ' },
|
||||
colorFg = colorFg,
|
||||
colorBg = colorBg,
|
||||
notificationSoundSaved = notificationSoundUri ?: "",
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun show() {
|
||||
|
|
|
@ -2,7 +2,6 @@ package jp.juggler.subwaytooter
|
|||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
|
@ -29,11 +28,14 @@ import jp.juggler.subwaytooter.table.SavedAccount
|
|||
import jp.juggler.subwaytooter.util.*
|
||||
import jp.juggler.subwaytooter.view.MyEditText
|
||||
import jp.juggler.subwaytooter.view.MyNetworkImageView
|
||||
import jp.juggler.util.*
|
||||
import jp.juggler.util.backPressed
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.coroutine.launchIO
|
||||
import jp.juggler.util.coroutine.launchMain
|
||||
import jp.juggler.util.data.GetContentResultEntry
|
||||
import jp.juggler.util.log.LogCategory
|
||||
import jp.juggler.util.log.showToast
|
||||
import jp.juggler.util.string
|
||||
import jp.juggler.util.ui.ActivityResultHandler
|
||||
import jp.juggler.util.ui.isNotOk
|
||||
import kotlinx.coroutines.Job
|
||||
|
@ -111,7 +113,6 @@ class ActPost : AppCompatActivity(),
|
|||
lateinit var etChoices: List<MyEditText>
|
||||
|
||||
lateinit var handler: Handler
|
||||
lateinit var pref: SharedPreferences
|
||||
lateinit var appState: AppState
|
||||
lateinit var attachmentUploader: AttachmentUploader
|
||||
lateinit var attachmentPicker: AttachmentPicker
|
||||
|
@ -138,7 +139,7 @@ class ActPost : AppCompatActivity(),
|
|||
|
||||
var states = ActPostStates()
|
||||
|
||||
var accountList: ArrayList<SavedAccount> = ArrayList()
|
||||
var accountList: List<SavedAccount> = emptyList()
|
||||
var account: SavedAccount? = null
|
||||
var attachmentList = ArrayList<PostAttachment>()
|
||||
var isPostComplete: Boolean = false
|
||||
|
@ -169,16 +170,17 @@ class ActPost : AppCompatActivity(),
|
|||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
backPressed {
|
||||
finish()
|
||||
// 戻るボタンを押したときとonPauseで2回保存することになるが、
|
||||
// 同じ内容はDB上は重複しないはず…
|
||||
saveDraft()
|
||||
launchAndShowError {
|
||||
finish()
|
||||
// 戻るボタンを押したときとonPauseで2回保存することになるが、
|
||||
// 同じ内容はDB上は重複しないはず…
|
||||
saveDraft()
|
||||
}
|
||||
}
|
||||
if (isMultiWindowPost) ActMain.refActMain?.get()?.closeList?.add(WeakReference(this))
|
||||
App1.setActivityTheme(this)
|
||||
appState = App1.getAppState(this)
|
||||
handler = appState.handler
|
||||
pref = appState.pref
|
||||
attachmentUploader = AttachmentUploader(this, handler)
|
||||
attachmentPicker = AttachmentPicker(this, this)
|
||||
density = resources.displayMetrics.density
|
||||
|
@ -202,9 +204,11 @@ class ActPost : AppCompatActivity(),
|
|||
|
||||
initUI()
|
||||
|
||||
when (savedInstanceState) {
|
||||
null -> updateText(intent, confirmed = true, saveDraft = false)
|
||||
else -> restoreState(savedInstanceState)
|
||||
launchAndShowError {
|
||||
when (savedInstanceState) {
|
||||
null -> updateText(intent, saveDraft = false)
|
||||
else -> restoreState(savedInstanceState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -242,11 +246,18 @@ class ActPost : AppCompatActivity(),
|
|||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
// 編集中にホーム画面を押したり他アプリに移動する場合は下書きを保存する
|
||||
// やや過剰な気がするが、自アプリに戻ってくるときにランチャーからアイコンタップされると
|
||||
// メイン画面より上にあるアクティビティはすべて消されてしまうので
|
||||
// このタイミングで保存するしかない
|
||||
if (!isPostComplete) saveDraft()
|
||||
if (!isPostComplete) launchMain {
|
||||
try {
|
||||
// 編集中にホーム画面を押したり他アプリに移動する場合は下書きを保存する
|
||||
// やや過剰な気がするが、自アプリに戻ってくるときにランチャーからアイコンタップされると
|
||||
// メイン画面より上にあるアクティビティはすべて消されてしまうので
|
||||
// このタイミングで保存するしかない
|
||||
saveDraft()
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "can't save draft.")
|
||||
showToast(ex, "can't save draft.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClick(v: View) {
|
||||
|
@ -317,7 +328,7 @@ class ActPost : AppCompatActivity(),
|
|||
fun initUI() {
|
||||
setContentView(views.root)
|
||||
|
||||
if (PrefB.bpPostButtonBarTop(pref)) {
|
||||
if (PrefB.bpPostButtonBarTop.value) {
|
||||
val bar = findViewById<View>(R.id.llFooterBar)
|
||||
val parent = bar.parent as ViewGroup
|
||||
parent.removeView(bar)
|
||||
|
@ -401,7 +412,7 @@ class ActPost : AppCompatActivity(),
|
|||
|
||||
views.cbContentWarning.setOnCheckedChangeListener { _, _ -> showContentWarningEnabled() }
|
||||
|
||||
completionHelper = CompletionHelper(this, pref, appState.handler)
|
||||
completionHelper = CompletionHelper(this, appState.handler)
|
||||
completionHelper.attachEditText(
|
||||
views.root,
|
||||
views.etContent,
|
||||
|
|
|
@ -9,15 +9,18 @@ import android.view.MenuItem
|
|||
import androidx.appcompat.app.AppCompatActivity
|
||||
import jp.juggler.subwaytooter.api.entity.TootAccount
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatus
|
||||
import jp.juggler.subwaytooter.auth.AuthRepo
|
||||
import jp.juggler.subwaytooter.databinding.ActTextBinding
|
||||
import jp.juggler.subwaytooter.dialog.pickAccount
|
||||
import jp.juggler.subwaytooter.table.MutedWord
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.daoMutedWord
|
||||
import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||
import jp.juggler.subwaytooter.util.CustomShare
|
||||
import jp.juggler.subwaytooter.util.CustomShareTarget
|
||||
import jp.juggler.subwaytooter.util.TootTextEncoder
|
||||
import jp.juggler.subwaytooter.util.copyToClipboard
|
||||
import jp.juggler.util.*
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.coroutine.launchMain
|
||||
import jp.juggler.util.data.*
|
||||
import jp.juggler.util.log.LogCategory
|
||||
|
@ -114,28 +117,34 @@ class ActText : AppCompatActivity() {
|
|||
return super.onCreateOptionsMenu(menu)
|
||||
}
|
||||
|
||||
private val authRepo by lazy {
|
||||
AuthRepo(this)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
App1.setActivityTheme(this)
|
||||
|
||||
account = intent.long(EXTRA_ACCOUNT_DB_ID)
|
||||
?.let { SavedAccount.loadAccount(this, it) }
|
||||
|
||||
initUI()
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
val sv = intent.string(EXTRA_TEXT) ?: ""
|
||||
val contentStart = intent.int(EXTRA_CONTENT_START) ?: 0
|
||||
val contentEnd = intent.int(EXTRA_CONTENT_END) ?: sv.length
|
||||
views.etText.setText(sv)
|
||||
launchAndShowError {
|
||||
account = intent.long(EXTRA_ACCOUNT_DB_ID)
|
||||
?.let { daoSavedAccount.loadAccount(it) }
|
||||
|
||||
// Android 9 以降ではフォーカスがないとsetSelectionできない
|
||||
if (Build.VERSION.SDK_INT >= 28) {
|
||||
views.etText.requestFocus()
|
||||
views.etText.hideKeyboard()
|
||||
if (savedInstanceState == null) {
|
||||
val sv = intent.string(EXTRA_TEXT) ?: ""
|
||||
val contentStart = intent.int(EXTRA_CONTENT_START) ?: 0
|
||||
val contentEnd = intent.int(EXTRA_CONTENT_END) ?: sv.length
|
||||
views.etText.setText(sv)
|
||||
|
||||
// Android 9 以降ではフォーカスがないとsetSelectionできない
|
||||
if (Build.VERSION.SDK_INT >= 28) {
|
||||
views.etText.requestFocus()
|
||||
views.etText.hideKeyboard()
|
||||
}
|
||||
|
||||
views.etText.setSelection(contentStart, contentEnd)
|
||||
}
|
||||
|
||||
views.etText.setSelection(contentStart, contentEnd)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -192,14 +201,11 @@ class ActText : AppCompatActivity() {
|
|||
}
|
||||
|
||||
private fun muteWord() {
|
||||
selection.trim().notEmpty()?.let {
|
||||
try {
|
||||
MutedWord.save(it)
|
||||
App1.getAppState(this).onMuteUpdated()
|
||||
launchAndShowError {
|
||||
selection.trim().notEmpty()?.let {
|
||||
daoMutedWord.save(it)
|
||||
App1.getAppState(this@ActText).onMuteUpdated()
|
||||
showToast(false, R.string.word_was_muted)
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "muteWord failed.")
|
||||
showToast(ex, "muteWord failed.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package jp.juggler.subwaytooter
|
|||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.util.Log
|
||||
|
@ -19,8 +20,7 @@ import com.bumptech.glide.load.model.GlideUrl
|
|||
import jp.juggler.subwaytooter.api.TootApiClient
|
||||
import jp.juggler.subwaytooter.column.ColumnType
|
||||
import jp.juggler.subwaytooter.emoji.EmojiMap
|
||||
import jp.juggler.subwaytooter.global.Global
|
||||
import jp.juggler.subwaytooter.global.appPref
|
||||
import jp.juggler.subwaytooter.pref.LazyContextHolder
|
||||
import jp.juggler.subwaytooter.pref.PrefI
|
||||
import jp.juggler.subwaytooter.pref.PrefS
|
||||
import jp.juggler.subwaytooter.table.HighlightWord
|
||||
|
@ -30,10 +30,12 @@ import jp.juggler.subwaytooter.util.CustomEmojiLister
|
|||
import jp.juggler.subwaytooter.util.ProgressResponseBody
|
||||
import jp.juggler.util.*
|
||||
import jp.juggler.util.data.asciiPattern
|
||||
import jp.juggler.util.data.notEmpty
|
||||
import jp.juggler.util.log.LogCategory
|
||||
import jp.juggler.util.log.initializeToastUtils
|
||||
import jp.juggler.util.network.MySslSocketFactory
|
||||
import jp.juggler.util.network.toPostRequestBuilder
|
||||
import jp.juggler.util.os.applicationContextSafe
|
||||
import jp.juggler.util.ui.*
|
||||
import okhttp3.*
|
||||
import okhttp3.OkHttpClient
|
||||
|
@ -55,11 +57,17 @@ class App1 : Application() {
|
|||
|
||||
override fun onCreate() {
|
||||
log.d("onCreate")
|
||||
LazyContextHolder.init(applicationContextSafe)
|
||||
super.onCreate()
|
||||
initializeToastUtils(this)
|
||||
prepare(applicationContext, "App1.onCreate")
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
LazyContextHolder.init(applicationContextSafe)
|
||||
}
|
||||
|
||||
override fun onTerminate() {
|
||||
log.d("onTerminate")
|
||||
super.onTerminate()
|
||||
|
@ -134,7 +142,7 @@ class App1 : Application() {
|
|||
"SubwayTooter/${BuildConfig.VERSION_NAME} Android/${Build.VERSION.RELEASE}"
|
||||
|
||||
private fun getUserAgent(): String {
|
||||
val userAgentCustom = PrefS.spUserAgent(appPref)
|
||||
val userAgentCustom = PrefS.spUserAgent.value
|
||||
return when {
|
||||
userAgentCustom.isNotEmpty() && !reNotAllowedInUserAgent.matcher(userAgentCustom)
|
||||
.find() -> userAgentCustom
|
||||
|
@ -142,13 +150,14 @@ class App1 : Application() {
|
|||
}
|
||||
}
|
||||
|
||||
private val user_agent_interceptor = Interceptor { chain ->
|
||||
chain.proceed(
|
||||
chain.request().newBuilder()
|
||||
.header("User-Agent", getUserAgent())
|
||||
.build()
|
||||
)
|
||||
}
|
||||
private fun userAgentInterceptor() =
|
||||
Interceptor { chain ->
|
||||
chain.proceed(
|
||||
chain.request().newBuilder()
|
||||
.header("User-Agent", getUserAgent())
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
private var cookieManager: CookieManager? = null
|
||||
private var cookieJar: CookieJar? = null
|
||||
|
@ -185,7 +194,7 @@ class App1 : Application() {
|
|||
.connectionSpecs(Collections.singletonList(spec))
|
||||
.sslSocketFactory(MySslSocketFactory, MySslSocketFactory.trustManager)
|
||||
.addInterceptor(ProgressResponseBody.makeInterceptor())
|
||||
.addInterceptor(user_agent_interceptor)
|
||||
.addInterceptor(userAgentInterceptor())
|
||||
|
||||
// クッキーの導入は検討中。とりあえずmstdn.jpではクッキー有効でも改善しなかったので現時点では追加しない
|
||||
// .cookieJar(cookieJar)
|
||||
|
@ -228,8 +237,6 @@ class App1 : Application() {
|
|||
|
||||
initializeFont()
|
||||
|
||||
Global.prepare(appContext, "App1.prepare($caller)")
|
||||
|
||||
// We want at least 2 threads and at most 4 threads in the core pool,
|
||||
// preferring to have 1 less than the CPU count to avoid saturating
|
||||
// the CPU with background work
|
||||
|
@ -278,7 +285,7 @@ class App1 : Application() {
|
|||
|
||||
Logger.getLogger(OkHttpClient::class.java.name).level = Level.FINE
|
||||
|
||||
val apiReadTimeout = max(3, PrefS.spApiReadTimeout.toInt(appPref))
|
||||
val apiReadTimeout = max(3, PrefS.spApiReadTimeout.toInt())
|
||||
|
||||
// API用のHTTP設定はキャッシュを使わない
|
||||
ok_http_client = prepareOkHttp(apiReadTimeout, apiReadTimeout)
|
||||
|
@ -294,10 +301,11 @@ class App1 : Application() {
|
|||
.build()
|
||||
|
||||
// 内蔵メディアビューア用のHTTP設定はタイムアウトを調整可能
|
||||
val mediaReadTimeout = max(3, PrefS.spMediaReadTimeout.toInt(appPref))
|
||||
ok_http_client_media_viewer = prepareOkHttp(mediaReadTimeout, mediaReadTimeout)
|
||||
.cache(cache)
|
||||
.build()
|
||||
val mediaReadTimeout = max(3, PrefS.spMediaReadTimeout.toInt())
|
||||
ok_http_client_media_viewer =
|
||||
prepareOkHttp(mediaReadTimeout, mediaReadTimeout)
|
||||
.cache(cache)
|
||||
.build()
|
||||
}
|
||||
|
||||
val handler = Handler(appContext.mainLooper)
|
||||
|
@ -310,7 +318,7 @@ class App1 : Application() {
|
|||
|
||||
log.d("create AppState.")
|
||||
|
||||
state = AppState(appContext, handler, appPref)
|
||||
state = AppState(appContext, handler)
|
||||
appStateX = state
|
||||
|
||||
// getAppState()を使える状態にしてからカラム一覧をロードする
|
||||
|
@ -421,7 +429,7 @@ class App1 : Application() {
|
|||
) {
|
||||
prepare(activity.applicationContext, "setActivityTheme")
|
||||
|
||||
var nTheme = PrefI.ipUiTheme(appPref)
|
||||
var nTheme = PrefI.ipUiTheme.value
|
||||
if (forceDark && nTheme == 0) nTheme = 1
|
||||
activity.setTheme(
|
||||
when (nTheme) {
|
||||
|
@ -484,9 +492,8 @@ class App1 : Application() {
|
|||
.url(url)
|
||||
.cacheControl(CACHE_CONTROL)
|
||||
.also {
|
||||
val access_token = accessInfo?.getAccessToken()
|
||||
if (access_token?.isNotEmpty() == true) {
|
||||
it.header("Authorization", "Bearer $access_token")
|
||||
accessInfo?.bearerAccessToken?.notEmpty()?.let { a ->
|
||||
it.header("Authorization", "Bearer $a")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,10 +17,7 @@ import jp.juggler.subwaytooter.column.getBackgroundImageDir
|
|||
import jp.juggler.subwaytooter.column.onMuteUpdated
|
||||
import jp.juggler.subwaytooter.span.MyClickableSpan
|
||||
import jp.juggler.subwaytooter.streaming.StreamManager
|
||||
import jp.juggler.subwaytooter.table.HighlightWord
|
||||
import jp.juggler.subwaytooter.table.MutedApp
|
||||
import jp.juggler.subwaytooter.table.MutedWord
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.*
|
||||
import jp.juggler.subwaytooter.util.NetworkStateTracker
|
||||
import jp.juggler.subwaytooter.util.PostAttachment
|
||||
import jp.juggler.util.*
|
||||
|
@ -49,7 +46,6 @@ class DedupItem(
|
|||
class AppState(
|
||||
internal val context: Context,
|
||||
internal val handler: Handler,
|
||||
internal val pref: SharedPreferences,
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
@ -190,7 +186,7 @@ class AppState(
|
|||
// TextToSpeech
|
||||
|
||||
private val isTextToSpeechRequired: Boolean
|
||||
get() = columnList.any { it.enableSpeech } || HighlightWord.hasTextToSpeechHighlightWord()
|
||||
get() = columnList.any { it.enableSpeech } || daoHighlightWord.hasTextToSpeechHighlightWord()
|
||||
|
||||
private val ttsReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent?) {
|
||||
|
@ -303,8 +299,8 @@ class AppState(
|
|||
if (list != null) editColumnList(save = false) { it.addAll(list) }
|
||||
|
||||
// ミュートデータのロード
|
||||
TootStatus.muted_app = MutedApp.nameSet
|
||||
TootStatus.muted_word = MutedWord.nameSet
|
||||
TootStatus.muted_app = daoMutedApp.nameSet()
|
||||
TootStatus.muted_word = daoMutedWord.nameSet()
|
||||
|
||||
// 背景フォルダの掃除
|
||||
try {
|
||||
|
@ -599,8 +595,8 @@ class AppState(
|
|||
}
|
||||
|
||||
fun onMuteUpdated() {
|
||||
TootStatus.muted_app = MutedApp.nameSet
|
||||
TootStatus.muted_word = MutedWord.nameSet
|
||||
TootStatus.muted_app = daoMutedApp.nameSet()
|
||||
TootStatus.muted_word = daoMutedWord.nameSet()
|
||||
columnList.forEach { it.onMuteUpdated() }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,10 +5,11 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import jp.juggler.subwaytooter.notification.TrackingType
|
||||
import jp.juggler.subwaytooter.notification.onNotificationDeleted
|
||||
import jp.juggler.subwaytooter.table.NotificationTracking
|
||||
import jp.juggler.subwaytooter.table.daoNotificationTracking
|
||||
import jp.juggler.util.coroutine.launchMain
|
||||
import jp.juggler.util.data.notEmpty
|
||||
import jp.juggler.util.log.LogCategory
|
||||
import jp.juggler.util.os.applicationContextSafe
|
||||
|
||||
class EventReceiver : BroadcastReceiver() {
|
||||
|
||||
|
@ -18,32 +19,36 @@ class EventReceiver : BroadcastReceiver() {
|
|||
}
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent?) {
|
||||
launchMain {
|
||||
try {
|
||||
log.i("onReceive action=${intent?.action}")
|
||||
|
||||
log.i("onReceive action=${intent?.action}")
|
||||
when (val action = intent?.action) {
|
||||
|
||||
when (val action = intent?.action) {
|
||||
|
||||
Intent.ACTION_BOOT_COMPLETED,
|
||||
Intent.ACTION_MY_PACKAGE_REPLACED,
|
||||
-> {
|
||||
App1.prepare(context.applicationContext, action)
|
||||
NotificationTracking.resetPostAll()
|
||||
}
|
||||
|
||||
ACTION_NOTIFICATION_DELETE -> intent.data?.let { uri ->
|
||||
val dbId = uri.getQueryParameter("db_id")?.toLongOrNull()
|
||||
val type = TrackingType.parseStr(uri.getQueryParameter("type"))
|
||||
val typeName = type.typeName
|
||||
val id = uri.getQueryParameter("notificationId")?.notEmpty()
|
||||
log.d("Notification deleted! db_id=$dbId,type=$type,id=$id")
|
||||
if (dbId != null) {
|
||||
launchMain {
|
||||
onNotificationDeleted(dbId, typeName)
|
||||
Intent.ACTION_BOOT_COMPLETED,
|
||||
Intent.ACTION_MY_PACKAGE_REPLACED,
|
||||
-> {
|
||||
App1.prepare(context.applicationContextSafe, action)
|
||||
daoNotificationTracking.resetPostAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> log.e("onReceive: unsupported action $action")
|
||||
ACTION_NOTIFICATION_DELETE,
|
||||
-> intent.data?.let { uri ->
|
||||
val dbId = uri.getQueryParameter("db_id")?.toLongOrNull()
|
||||
val type = TrackingType.parseStr(uri.getQueryParameter("type"))
|
||||
val typeName = type.typeName
|
||||
val id = uri.getQueryParameter("notificationId")?.notEmpty()
|
||||
log.d("Notification deleted! db_id=$dbId,type=$type,id=$id")
|
||||
if (dbId != null) {
|
||||
onNotificationDeleted(dbId, typeName)
|
||||
}
|
||||
}
|
||||
|
||||
else -> log.e("onReceive: unsupported action $action")
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "resetPostAll failed.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,99 +1,101 @@
|
|||
package jp.juggler.subwaytooter
|
||||
|
||||
import com.google.firebase.messaging.FirebaseMessagingService
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import jp.juggler.subwaytooter.notification.PollingChecker
|
||||
import jp.juggler.subwaytooter.notification.restartAllWorker
|
||||
import jp.juggler.subwaytooter.pref.PrefDevice
|
||||
import jp.juggler.subwaytooter.table.NotificationCache
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.util.log.LogCategory
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.util.*
|
||||
|
||||
class MyFirebaseMessagingService : FirebaseMessagingService() {
|
||||
|
||||
companion object {
|
||||
internal val log = LogCategory("MyFirebaseMessagingService")
|
||||
|
||||
private val pushMessageStatus = LinkedList<String>()
|
||||
|
||||
// Pushメッセージが処理済みか調べる
|
||||
private fun isDuplicateMessage(messageId: String) =
|
||||
synchronized(pushMessageStatus) {
|
||||
when (pushMessageStatus.contains(messageId)) {
|
||||
true -> true
|
||||
else -> {
|
||||
pushMessageStatus.addFirst(messageId)
|
||||
while (pushMessageStatus.size > 100) {
|
||||
pushMessageStatus.removeLast()
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewToken(token: String) {
|
||||
try {
|
||||
log.w("onTokenRefresh: token=$token")
|
||||
PrefDevice.from(this).edit().putString(PrefDevice.KEY_DEVICE_TOKEN, token).apply()
|
||||
restartAllWorker(this)
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "onNewToken failed")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMessageReceived(remoteMessage: RemoteMessage) {
|
||||
val context = this
|
||||
|
||||
val messageId = remoteMessage.messageId ?: return
|
||||
if (isDuplicateMessage(messageId)) return
|
||||
|
||||
val accounts = ArrayList<SavedAccount>()
|
||||
for ((key, value) in remoteMessage.data) {
|
||||
log.w("onMessageReceived: $key=$value")
|
||||
when (key) {
|
||||
"notification_tag" -> {
|
||||
SavedAccount.loadByTag(context, value).forEach { sa ->
|
||||
NotificationCache.resetLastLoad(sa.db_id)
|
||||
accounts.add(sa)
|
||||
}
|
||||
}
|
||||
"acct" -> {
|
||||
SavedAccount.loadAccountByAcct(context, value)?.let { sa ->
|
||||
NotificationCache.resetLastLoad(sa.db_id)
|
||||
accounts.add(sa)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (accounts.isEmpty()) {
|
||||
// タグにマッチする情報がなかった場合、全部読み直す
|
||||
NotificationCache.resetLastLoad()
|
||||
accounts.addAll(SavedAccount.loadAccountList(context))
|
||||
}
|
||||
|
||||
log.i("accounts.size=${accounts.size} thred=${Thread.currentThread().name}")
|
||||
runBlocking {
|
||||
accounts.forEach {
|
||||
check(it.db_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun check(accountDbId: Long) {
|
||||
try {
|
||||
PollingChecker(
|
||||
context = this,
|
||||
accountDbId = accountDbId
|
||||
).check { a, s ->
|
||||
val text = "[${a.acct.pretty}]${s.desc}"
|
||||
log.i(text)
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "check failed. accountDbId=$accountDbId")
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
//import com.google.firebase.messaging.FirebaseMessagingService
|
||||
//import com.google.firebase.messaging.RemoteMessage
|
||||
//import jp.juggler.subwaytooter.api.entity.Acct
|
||||
//import jp.juggler.subwaytooter.notification.PollingChecker
|
||||
//import jp.juggler.subwaytooter.notification.restartAllWorker
|
||||
//import jp.juggler.subwaytooter.pref.PrefDevice
|
||||
//import jp.juggler.subwaytooter.pref.prefDevice
|
||||
//import jp.juggler.subwaytooter.table.SavedAccount
|
||||
//import jp.juggler.subwaytooter.table.apiNotificationCache
|
||||
//import jp.juggler.subwaytooter.table.apiSavedAccount
|
||||
//import jp.juggler.util.log.LogCategory
|
||||
//import kotlinx.coroutines.runBlocking
|
||||
//import java.util.*
|
||||
//
|
||||
//class MyFirebaseMessagingService : FirebaseMessagingService() {
|
||||
//
|
||||
// companion object {
|
||||
// internal val log = LogCategory("MyFirebaseMessagingService")
|
||||
//
|
||||
// private val pushMessageStatus = LinkedList<String>()
|
||||
//
|
||||
// // Pushメッセージが処理済みか調べる
|
||||
// private fun isDuplicateMessage(messageId: String) =
|
||||
// synchronized(pushMessageStatus) {
|
||||
// when (pushMessageStatus.contains(messageId)) {
|
||||
// true -> true
|
||||
// else -> {
|
||||
// pushMessageStatus.addFirst(messageId)
|
||||
// while (pushMessageStatus.size > 100) {
|
||||
// pushMessageStatus.removeLast()
|
||||
// }
|
||||
// false
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// override fun onNewToken(token: String) {
|
||||
// try {
|
||||
// log.w("onTokenRefresh: token=$token")
|
||||
// prefDevice.device
|
||||
// pollingWorker2IntervalPrefDevice.from(this).edit().putString(PrefDevice.KEY_DEVICE_TOKEN, token).apply()
|
||||
// restartAllWorker(this)
|
||||
// } catch (ex: Throwable) {
|
||||
// log.e(ex, "onNewToken failed")
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// override fun onMessageReceived(remoteMessage: RemoteMessage) {
|
||||
// val messageId = remoteMessage.messageId ?: return
|
||||
// if (isDuplicateMessage(messageId)) return
|
||||
//
|
||||
// val accounts = ArrayList<SavedAccount>()
|
||||
// for ((key, value) in remoteMessage.data) {
|
||||
// log.w("onMessageReceived: $key=$value")
|
||||
// when (key) {
|
||||
//// "notification_tag" -> {
|
||||
//// apiSavedAccount.(context, value).forEach { sa ->
|
||||
//// apiNotificationCache.resetLastLoad(sa.db_id)
|
||||
//// accounts.add(sa)
|
||||
//// }
|
||||
//// }
|
||||
// "acct" -> {
|
||||
// apiSavedAccount.loadAccountByAcct(Acct.parse(value))?.let { sa ->
|
||||
// apiNotificationCache.resetLastLoad(sa.db_id)
|
||||
// accounts.add(sa)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (accounts.isEmpty()) {
|
||||
// // タグにマッチする情報がなかった場合、全部読み直す
|
||||
// apiNotificationCache.resetLastLoad()
|
||||
// accounts.addAll(apiSavedAccount.loadAccountList())
|
||||
// }
|
||||
//
|
||||
// log.i("accounts.size=${accounts.size} thred=${Thread.currentThread().name}")
|
||||
// runBlocking {
|
||||
// accounts.forEach {
|
||||
// check(it.db_id)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private suspend fun check(accountDbId: Long) {
|
||||
// try {
|
||||
// PollingChecker(
|
||||
// context = this,
|
||||
// accountDbId = accountDbId
|
||||
// ).check { a, s ->
|
||||
// val text = "[${a.acct.pretty}]${s.desc}"
|
||||
// log.i(text)
|
||||
// }
|
||||
// } catch (ex: Throwable) {
|
||||
// log.e(ex, "check failed. accountDbId=$accountDbId")
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
|
@ -23,7 +23,7 @@ import jp.juggler.subwaytooter.api.entity.TootVisibility
|
|||
import jp.juggler.subwaytooter.emoji.EmojiMap
|
||||
import jp.juggler.subwaytooter.pref.PrefB
|
||||
import jp.juggler.subwaytooter.pref.PrefI
|
||||
import jp.juggler.subwaytooter.pref.pref
|
||||
import jp.juggler.subwaytooter.pref.lazyContext
|
||||
import jp.juggler.subwaytooter.span.EmojiImageSpan
|
||||
import jp.juggler.subwaytooter.span.createSpan
|
||||
import jp.juggler.subwaytooter.table.UserRelation
|
||||
|
@ -43,14 +43,14 @@ fun defaultColorIcon(context: Context, iconId: Int): Drawable? =
|
|||
it.setTintMode(PorterDuff.Mode.SRC_IN)
|
||||
}
|
||||
|
||||
fun getVisibilityIconId(isMisskeyData: Boolean, visibility: TootVisibility): Int {
|
||||
val isMisskey = when (PrefI.ipVisibilityStyle()) {
|
||||
fun TootVisibility.getVisibilityIconId(isMisskeyData: Boolean): Int {
|
||||
val isMisskey = when (PrefI.ipVisibilityStyle.value) {
|
||||
PrefI.VS_MASTODON -> false
|
||||
PrefI.VS_MISSKEY -> true
|
||||
else -> isMisskeyData
|
||||
}
|
||||
return when {
|
||||
isMisskey -> when (visibility) {
|
||||
isMisskey -> when (this) {
|
||||
TootVisibility.Public -> R.drawable.ic_public
|
||||
TootVisibility.UnlistedHome -> R.drawable.ic_home
|
||||
TootVisibility.PrivateFollowers -> R.drawable.ic_lock_open
|
||||
|
@ -67,7 +67,7 @@ fun getVisibilityIconId(isMisskeyData: Boolean, visibility: TootVisibility): Int
|
|||
TootVisibility.Limited -> R.drawable.ic_account_circle
|
||||
TootVisibility.Mutual -> R.drawable.ic_bidirectional
|
||||
}
|
||||
else -> when (visibility) {
|
||||
else -> when (this) {
|
||||
TootVisibility.Public -> R.drawable.ic_public
|
||||
TootVisibility.UnlistedHome -> R.drawable.ic_lock_open
|
||||
TootVisibility.PrivateFollowers -> R.drawable.ic_lock
|
||||
|
@ -87,19 +87,15 @@ fun getVisibilityIconId(isMisskeyData: Boolean, visibility: TootVisibility): Int
|
|||
}
|
||||
}
|
||||
|
||||
fun getVisibilityString(
|
||||
context: Context,
|
||||
isMisskeyData: Boolean,
|
||||
visibility: TootVisibility,
|
||||
): String {
|
||||
val isMisskey = when (PrefI.ipVisibilityStyle()) {
|
||||
fun TootVisibility.getVisibilityString(isMisskeyData: Boolean): String {
|
||||
val isMisskey = when (PrefI.ipVisibilityStyle.value) {
|
||||
PrefI.VS_MASTODON -> false
|
||||
PrefI.VS_MISSKEY -> true
|
||||
else -> isMisskeyData
|
||||
}
|
||||
return context.getString(
|
||||
return lazyContext.getString(
|
||||
when {
|
||||
isMisskey -> when (visibility) {
|
||||
isMisskey -> when (this) {
|
||||
TootVisibility.Public -> R.string.visibility_public
|
||||
TootVisibility.UnlistedHome -> R.string.visibility_home
|
||||
TootVisibility.PrivateFollowers -> R.string.visibility_followers
|
||||
|
@ -116,7 +112,7 @@ fun getVisibilityString(
|
|||
TootVisibility.Limited -> R.string.visibility_limited
|
||||
TootVisibility.Mutual -> R.string.visibility_mutual
|
||||
}
|
||||
else -> when (visibility) {
|
||||
else -> when (this) {
|
||||
TootVisibility.Public -> R.string.visibility_public
|
||||
TootVisibility.UnlistedHome -> R.string.visibility_unlisted
|
||||
TootVisibility.PrivateFollowers -> R.string.visibility_followers
|
||||
|
@ -144,8 +140,8 @@ fun getVisibilityCaption(
|
|||
visibility: TootVisibility,
|
||||
): CharSequence {
|
||||
|
||||
val iconId = getVisibilityIconId(isMisskeyData, visibility)
|
||||
val sv = getVisibilityString(context, isMisskeyData, visibility)
|
||||
val iconId = visibility.getVisibilityIconId(isMisskeyData)
|
||||
val sv = visibility.getVisibilityString(isMisskeyData)
|
||||
val color = context.attrColor(R.attr.colorTextContent)
|
||||
val sb = SpannableStringBuilder()
|
||||
|
||||
|
@ -182,11 +178,11 @@ fun setFollowIcon(
|
|||
alphaMultiplier: Float,
|
||||
) {
|
||||
val colorFollowed =
|
||||
PrefI.ipButtonFollowingColor(context.pref()).notZero()
|
||||
PrefI.ipButtonFollowingColor.value.notZero()
|
||||
?: context.attrColor(R.attr.colorButtonAccentFollow)
|
||||
|
||||
val colorFollowRequest =
|
||||
PrefI.ipButtonFollowRequestColor(context.pref()).notZero()
|
||||
PrefI.ipButtonFollowRequestColor.value.notZero()
|
||||
?: context.attrColor(R.attr.colorButtonAccentFollowRequest)
|
||||
|
||||
val colorError = context.attrColor(R.attr.colorRegexFilterError)
|
||||
|
@ -309,7 +305,7 @@ fun fixHorizontalPadding(v: View, dpDelta: Float = 12f) {
|
|||
val widthDp = dm.widthPixels / dm.density
|
||||
if (widthDp >= 640f && v.resources?.configuration?.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
val padLr = (0.5f + dpDelta * dm.density).toInt()
|
||||
when (PrefI.ipJustifyWindowContentPortrait()) {
|
||||
when (PrefI.ipJustifyWindowContentPortrait.value) {
|
||||
PrefI.JWCP_START -> {
|
||||
v.setPaddingRelative(padLr, padT, padLr + dm.widthPixels / 2, padB)
|
||||
return
|
||||
|
@ -338,7 +334,7 @@ fun fixHorizontalMargin(v: View) {
|
|||
log.d("fixHorizontalMargin: orientation=$orientationString, w=${widthDp}dp, h=${dm.heightPixels / dm.density}")
|
||||
|
||||
if (widthDp >= 640f && v.resources?.configuration?.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
when (PrefI.ipJustifyWindowContentPortrait()) {
|
||||
when (PrefI.ipJustifyWindowContentPortrait.value) {
|
||||
PrefI.JWCP_START -> {
|
||||
lp.marginStart = 0
|
||||
lp.marginEnd = dm.widthPixels / 2
|
||||
|
@ -397,7 +393,7 @@ fun SpannableStringBuilder.appendMisskeyReaction(
|
|||
emoji == null ->
|
||||
append("text")
|
||||
|
||||
PrefB.bpUseTwemoji(context) -> {
|
||||
PrefB.bpUseTwemoji.value -> {
|
||||
val start = this.length
|
||||
append(text)
|
||||
val end = this.length
|
||||
|
@ -414,9 +410,10 @@ fun SpannableStringBuilder.appendMisskeyReaction(
|
|||
}
|
||||
|
||||
fun Context.setSwitchColor(root: View?) {
|
||||
root ?: return
|
||||
val colorBg = attrColor(R.attr.colorWindowBackground)
|
||||
val colorOff = attrColor(R.attr.colorSwitchOff)
|
||||
val colorOn = PrefI.ipSwitchOnColor()
|
||||
val colorOn = PrefI.ipSwitchOnColor.value
|
||||
|
||||
val colorDisabled = mixColor(colorBg, colorOff)
|
||||
|
||||
|
@ -451,7 +448,7 @@ fun Context.setSwitchColor(root: View?) {
|
|||
)
|
||||
)
|
||||
|
||||
root?.scan {
|
||||
root.scan {
|
||||
(it as? SwitchCompat)?.apply {
|
||||
thumbTintList = thumbStates
|
||||
trackTintList = trackStates
|
||||
|
@ -487,13 +484,14 @@ fun AppCompatActivity.setStatusBarColor(forceDark: Boolean = false) {
|
|||
|
||||
var c = when {
|
||||
forceDark -> Color.BLACK
|
||||
else -> PrefI.ipStatusBarColor.invoke().notZero() ?: attrColor(R.attr.colorPrimaryDark)
|
||||
else -> PrefI.ipStatusBarColor.value.notZero()
|
||||
?: attrColor(R.attr.colorPrimaryDark)
|
||||
}
|
||||
setStatusBarColorCompat(c)
|
||||
|
||||
c = when {
|
||||
forceDark -> Color.BLACK
|
||||
else -> PrefI.ipNavigationBarColor()
|
||||
else -> PrefI.ipNavigationBarColor.value
|
||||
}
|
||||
setNavigationBarColorCompat(c)
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
package jp.juggler.subwaytooter.action
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import jp.juggler.subwaytooter.api.TootParser
|
||||
import jp.juggler.subwaytooter.api.entity.*
|
||||
import jp.juggler.subwaytooter.api.entity.Acct
|
||||
import jp.juggler.subwaytooter.api.entity.Host
|
||||
import jp.juggler.subwaytooter.api.entity.TootInstance
|
||||
import jp.juggler.subwaytooter.api.runApiTask2
|
||||
import jp.juggler.subwaytooter.api.showApiError
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.UserRelation
|
||||
import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||
import jp.juggler.subwaytooter.util.matchHost
|
||||
import jp.juggler.util.data.JsonObject
|
||||
import jp.juggler.util.data.buildJsonObject
|
||||
|
@ -22,7 +23,6 @@ internal suspend fun AppCompatActivity.addPseudoAccount(
|
|||
host: Host,
|
||||
instanceInfoArg: TootInstance? = null,
|
||||
): SavedAccount? {
|
||||
|
||||
try {
|
||||
suspend fun AppCompatActivity.getInstanceInfo(): TootInstance? {
|
||||
return try {
|
||||
|
@ -35,7 +35,7 @@ internal suspend fun AppCompatActivity.addPseudoAccount(
|
|||
|
||||
val acct = Acct.parse("?", host)
|
||||
|
||||
var account = SavedAccount.loadAccountByAcct(this, acct.ascii)
|
||||
var account = daoSavedAccount.loadAccountByAcct(acct)
|
||||
if (account != null) return account
|
||||
|
||||
val instanceInfo = instanceInfoArg
|
||||
|
@ -47,7 +47,7 @@ internal suspend fun AppCompatActivity.addPseudoAccount(
|
|||
put("acct", acct.username) // ローカルから参照した場合なのでshort acct
|
||||
}
|
||||
|
||||
val rowId = SavedAccount.insert(
|
||||
val rowId = daoSavedAccount.saveNew(
|
||||
acct = acct.ascii,
|
||||
host = host.ascii,
|
||||
domain = instanceInfo.apDomain.ascii,
|
||||
|
@ -56,7 +56,7 @@ internal suspend fun AppCompatActivity.addPseudoAccount(
|
|||
misskeyVersion = instanceInfo.misskeyVersionMajor
|
||||
)
|
||||
|
||||
account = SavedAccount.loadAccount(applicationContext, rowId)
|
||||
account = daoSavedAccount.loadAccount(rowId)
|
||||
?: error("loadAccount returns null.")
|
||||
|
||||
account.notification_follow = false
|
||||
|
@ -68,7 +68,7 @@ internal suspend fun AppCompatActivity.addPseudoAccount(
|
|||
account.notification_vote = false
|
||||
account.notification_post = false
|
||||
account.notification_update = false
|
||||
account.saveSetting()
|
||||
daoSavedAccount.saveSetting(account)
|
||||
return account
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "addPseudoAccount failed.")
|
||||
|
@ -77,22 +77,6 @@ internal suspend fun AppCompatActivity.addPseudoAccount(
|
|||
}
|
||||
}
|
||||
|
||||
internal fun SavedAccount.saveUserRelation(src: TootRelationShip?): UserRelation? {
|
||||
src ?: return null
|
||||
val now = System.currentTimeMillis()
|
||||
return UserRelation.save1Mastodon(now, db_id, src)
|
||||
}
|
||||
|
||||
internal fun SavedAccount.saveUserRelationMisskey(
|
||||
whoId: EntityId,
|
||||
parser: TootParser,
|
||||
): UserRelation? {
|
||||
val now = System.currentTimeMillis()
|
||||
val relation = parser.getMisskeyUserRelation(whoId)
|
||||
UserRelation.save1Misskey(now, db_id, whoId.toString(), relation)
|
||||
return relation
|
||||
}
|
||||
|
||||
//// relationshipを取得
|
||||
//internal fun loadRelation1Mastodon(
|
||||
// client : TootApiClient,
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
package jp.juggler.subwaytooter.action
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import jp.juggler.subwaytooter.*
|
||||
import jp.juggler.subwaytooter.actmain.addColumn
|
||||
import jp.juggler.subwaytooter.actmain.afterAccountVerify
|
||||
|
@ -16,13 +14,11 @@ import jp.juggler.subwaytooter.column.ColumnType
|
|||
import jp.juggler.subwaytooter.dialog.*
|
||||
import jp.juggler.subwaytooter.dialog.DlgCreateAccount.Companion.showUserCreateDialog
|
||||
import jp.juggler.subwaytooter.dialog.LoginForm.Companion.showLoginForm
|
||||
import jp.juggler.subwaytooter.notification.APP_SERVER
|
||||
import jp.juggler.subwaytooter.pref.*
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.util.LinkHelper
|
||||
import jp.juggler.subwaytooter.util.openBrowser
|
||||
import jp.juggler.util.*
|
||||
import jp.juggler.util.coroutine.launchIO
|
||||
import jp.juggler.util.coroutine.launchMain
|
||||
import jp.juggler.util.data.JsonObject
|
||||
import jp.juggler.util.data.encodePercent
|
||||
|
@ -32,7 +28,6 @@ import jp.juggler.util.network.toFormRequestBody
|
|||
import jp.juggler.util.network.toPost
|
||||
import jp.juggler.util.ui.dismissSafe
|
||||
import kotlinx.coroutines.*
|
||||
import ru.gildor.coroutines.okhttp.await
|
||||
|
||||
private val log = LogCategory("Action_Account")
|
||||
|
||||
|
@ -195,53 +190,6 @@ fun ActMain.accessTokenPrompt(
|
|||
)
|
||||
}
|
||||
|
||||
fun AppCompatActivity.accountRemove(account: SavedAccount) {
|
||||
// if account is default account of tablet mode,
|
||||
// reset default.
|
||||
val pref = pref()
|
||||
if (account.db_id == PrefL.lpTabletTootDefaultAccount(pref)) {
|
||||
pref.edit().put(PrefL.lpTabletTootDefaultAccount, -1L).apply()
|
||||
}
|
||||
|
||||
account.delete()
|
||||
appServerUnregister(applicationContext, account)
|
||||
}
|
||||
|
||||
private fun appServerUnregister(context: Context, account: SavedAccount) {
|
||||
launchIO {
|
||||
try {
|
||||
val installId = PrefDevice.from(context).getString(PrefDevice.KEY_INSTALL_ID, null)
|
||||
if (installId?.isEmpty() != false) {
|
||||
error("missing install_id")
|
||||
}
|
||||
|
||||
val tag = account.notification_tag
|
||||
if (tag?.isEmpty() != false) {
|
||||
error("missing notification_tag")
|
||||
}
|
||||
|
||||
val call = App1.ok_http_client.newCall(
|
||||
"instance_url=${
|
||||
"https://${account.apiHost.ascii}".encodePercent()
|
||||
}&app_id=${
|
||||
context.packageName.encodePercent()
|
||||
}&tag=$tag"
|
||||
.toFormRequestBody()
|
||||
.toPost()
|
||||
.url("$APP_SERVER/unregister")
|
||||
.build()
|
||||
)
|
||||
|
||||
val response = call.await()
|
||||
if (!response.isSuccessful) {
|
||||
log.e("appServerUnregister: $response")
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "appServerUnregister failed.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// アカウント設定
|
||||
fun ActMain.accountOpenSetting() {
|
||||
launchMain {
|
||||
|
@ -284,105 +232,3 @@ fun ActMain.accountResendConfirmMail(accessInfo: SavedAccount) {
|
|||
}.show()
|
||||
}
|
||||
|
||||
//
|
||||
fun accountListReorder(
|
||||
src: List<SavedAccount>,
|
||||
pickupHost: Host?,
|
||||
filter: (SavedAccount) -> Boolean = { true },
|
||||
): MutableList<SavedAccount> {
|
||||
val listSameHost = java.util.ArrayList<SavedAccount>()
|
||||
val listOtherHost = java.util.ArrayList<SavedAccount>()
|
||||
for (a in src) {
|
||||
if (!filter(a)) continue
|
||||
when (pickupHost) {
|
||||
null, a.apDomain, a.apiHost -> listSameHost
|
||||
else -> listOtherHost
|
||||
}.add(a)
|
||||
}
|
||||
SavedAccount.sort(listSameHost)
|
||||
SavedAccount.sort(listOtherHost)
|
||||
listSameHost.addAll(listOtherHost)
|
||||
return listSameHost
|
||||
}
|
||||
|
||||
// 疑似アカ以外のアカウントのリスト
|
||||
fun Context.accountListNonPseudo(
|
||||
pickupHost: Host?,
|
||||
) = accountListReorder(
|
||||
SavedAccount.loadAccountList(this),
|
||||
pickupHost
|
||||
) { !it.isPseudo }
|
||||
|
||||
// 条件でフィルタする。サーバ情報を読む場合がある。
|
||||
suspend fun Context.accountListWithFilter(
|
||||
pickupHost: Host?,
|
||||
check: suspend (TootApiClient, SavedAccount) -> Boolean,
|
||||
): MutableList<SavedAccount>? {
|
||||
var resultList: MutableList<SavedAccount>? = null
|
||||
runApiTask { client ->
|
||||
supervisorScope {
|
||||
resultList = SavedAccount.loadAccountList(this@accountListWithFilter)
|
||||
.map {
|
||||
async {
|
||||
try {
|
||||
if (check(client, it)) it else null
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "accountListWithFilter failed.")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
.mapNotNull { it.await() }
|
||||
.let { accountListReorder(it, pickupHost) }
|
||||
}
|
||||
if (client.isApiCancelled()) null else TootApiResult()
|
||||
}
|
||||
return resultList
|
||||
}
|
||||
|
||||
suspend fun ActMain.accountListCanQuote(pickupHost: Host? = null) =
|
||||
accountListWithFilter(pickupHost) { client, a ->
|
||||
when {
|
||||
client.isApiCancelled() -> false
|
||||
a.isPseudo -> false
|
||||
a.isMisskey -> true
|
||||
else -> {
|
||||
val (ti, ri) = TootInstance.getEx(client.copy(), account = a)
|
||||
if (ti == null) {
|
||||
ri?.error?.let { log.w(it) }
|
||||
false
|
||||
} else InstanceCapability.quote(ti)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun ActMain.accountListCanReaction(pickupHost: Host? = null) =
|
||||
accountListWithFilter(pickupHost) { client, a ->
|
||||
when {
|
||||
client.isApiCancelled() -> false
|
||||
a.isPseudo -> false
|
||||
a.isMisskey -> true
|
||||
else -> {
|
||||
val (ti, ri) = TootInstance.getEx(client.copy(), account = a)
|
||||
if (ti == null) {
|
||||
ri?.error?.let { log.w(it) }
|
||||
false
|
||||
} else InstanceCapability.emojiReaction(a, ti)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun ActMain.accountListCanSeeMyReactions(pickupHost: Host? = null) =
|
||||
accountListWithFilter(pickupHost) { client, a ->
|
||||
when {
|
||||
client.isApiCancelled() -> false
|
||||
a.isPseudo -> false
|
||||
else -> {
|
||||
val (ti, ri) = TootInstance.getEx(client.copy(), account = a)
|
||||
if (ti == null) {
|
||||
ri?.error?.let { log.w(it) }
|
||||
false
|
||||
} else InstanceCapability.listMyReactions(a, ti)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package jp.juggler.subwaytooter.action
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.net.Uri
|
||||
import jp.juggler.subwaytooter.ActColumnList
|
||||
import jp.juggler.subwaytooter.ActMain
|
||||
|
@ -8,8 +7,10 @@ import jp.juggler.subwaytooter.R
|
|||
import jp.juggler.subwaytooter.actmain.currentColumn
|
||||
import jp.juggler.subwaytooter.actmain.handleOtherUri
|
||||
import jp.juggler.subwaytooter.api.entity.TootApplication
|
||||
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
||||
import jp.juggler.subwaytooter.dialog.DlgOpenUrl
|
||||
import jp.juggler.subwaytooter.table.MutedApp
|
||||
import jp.juggler.subwaytooter.table.daoMutedApp
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.log.showToast
|
||||
import jp.juggler.util.ui.dismissSafe
|
||||
|
||||
|
@ -18,22 +19,10 @@ fun ActMain.openColumnList() =
|
|||
arColumnList.launch(ActColumnList.createIntent(this, currentColumn))
|
||||
|
||||
// アプリをミュートする
|
||||
fun ActMain.appMute(
|
||||
application: TootApplication?,
|
||||
confirmed: Boolean = false,
|
||||
) {
|
||||
application ?: return
|
||||
if (!confirmed) {
|
||||
AlertDialog.Builder(this)
|
||||
.setMessage(getString(R.string.mute_application_confirm, application.name))
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
appMute(application, confirmed = true)
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
return
|
||||
}
|
||||
MutedApp.save(application.name)
|
||||
fun ActMain.appMute(application: TootApplication?) = launchAndShowError {
|
||||
application ?: return@launchAndShowError
|
||||
confirm(R.string.mute_application_confirm, application.name)
|
||||
daoMutedApp.save(application.name)
|
||||
appState.onMuteUpdated()
|
||||
showToast(false, R.string.app_was_muted)
|
||||
}
|
||||
|
|
|
@ -17,8 +17,10 @@ import jp.juggler.subwaytooter.column.findStatus
|
|||
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
||||
import jp.juggler.subwaytooter.dialog.pickAccount
|
||||
import jp.juggler.subwaytooter.getVisibilityCaption
|
||||
import jp.juggler.subwaytooter.table.AcctColor
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.accountListNonPseudo
|
||||
import jp.juggler.subwaytooter.table.daoAcctColor
|
||||
import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||
import jp.juggler.subwaytooter.util.emptyCallback
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.coroutine.launchMain
|
||||
|
@ -212,7 +214,7 @@ private class BoostImpl(
|
|||
visibility == TootVisibility.PrivateFollowers -> R.string.confirm_private_boost_from
|
||||
else -> R.string.confirm_boost_from
|
||||
},
|
||||
AcctColor.getNickname(accessInfo)
|
||||
daoAcctColor.getNickname(accessInfo)
|
||||
),
|
||||
when (bSet) {
|
||||
true -> accessInfo.confirm_boost
|
||||
|
@ -223,7 +225,7 @@ private class BoostImpl(
|
|||
true -> accessInfo.confirm_boost = newConfirmEnabled
|
||||
else -> accessInfo.confirm_unboost = newConfirmEnabled
|
||||
}
|
||||
accessInfo.saveSetting()
|
||||
daoSavedAccount.saveSetting(accessInfo)
|
||||
activity.reloadAccountSetting(accessInfo)
|
||||
}
|
||||
}
|
||||
|
@ -288,7 +290,7 @@ fun ActMain.boostFromAnotherAccount(
|
|||
|
||||
if (isPrivateToot) {
|
||||
val list = ArrayList<SavedAccount>()
|
||||
for (a in SavedAccount.loadAccountList(applicationContext)) {
|
||||
for (a in daoSavedAccount.loadAccountList()) {
|
||||
if (a.acct == statusOwner) list.add(a)
|
||||
}
|
||||
if (list.isEmpty()) {
|
||||
|
|
|
@ -10,11 +10,14 @@ import jp.juggler.subwaytooter.api.entity.*
|
|||
import jp.juggler.subwaytooter.column.ColumnType
|
||||
import jp.juggler.subwaytooter.column.findStatus
|
||||
import jp.juggler.subwaytooter.columnviewholder.ItemListAdapter
|
||||
import jp.juggler.subwaytooter.dialog.ActionsDialog
|
||||
import jp.juggler.subwaytooter.table.AcctColor
|
||||
import jp.juggler.subwaytooter.dialog.actionsDialog
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.daoAcctColor
|
||||
import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||
import jp.juggler.subwaytooter.table.sortedByNickname
|
||||
import jp.juggler.subwaytooter.util.matchHost
|
||||
import jp.juggler.subwaytooter.util.openCustomTab
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.coroutine.launchMain
|
||||
import jp.juggler.util.data.notEmpty
|
||||
import jp.juggler.util.log.LogCategory
|
||||
|
@ -244,122 +247,126 @@ fun ActMain.conversationOtherInstance(
|
|||
statusIdAccess: EntityId? = null,
|
||||
isReference: Boolean = false,
|
||||
) {
|
||||
|
||||
val activity = this
|
||||
launchAndShowError {
|
||||
actionsDialog(getString(R.string.open_status_from)) {
|
||||
|
||||
val dialog = ActionsDialog()
|
||||
val hostOriginal = Host.parse(urlArg.toUri().authority ?: "")
|
||||
|
||||
val hostOriginal = Host.parse(urlArg.toUri().authority ?: "")
|
||||
// 選択肢:ブラウザで表示する
|
||||
action(getString(R.string.open_web_on_host, hostOriginal.pretty)) {
|
||||
openCustomTab(urlArg)
|
||||
}
|
||||
|
||||
// 選択肢:ブラウザで表示する
|
||||
dialog.addAction(
|
||||
getString(
|
||||
R.string.open_web_on_host,
|
||||
hostOriginal.pretty
|
||||
)
|
||||
) { openCustomTab(urlArg) }
|
||||
// トゥートの投稿元タンスにあるアカウント
|
||||
val localAccountList = ArrayList<SavedAccount>()
|
||||
|
||||
// トゥートの投稿元タンスにあるアカウント
|
||||
val localAccountList = ArrayList<SavedAccount>()
|
||||
// TLを読んだタンスにあるアカウント
|
||||
val accessAccountList = ArrayList<SavedAccount>()
|
||||
|
||||
// TLを読んだタンスにあるアカウント
|
||||
val accessAccountList = ArrayList<SavedAccount>()
|
||||
// その他のタンスにあるアカウント
|
||||
val otherAccountList = ArrayList<SavedAccount>()
|
||||
|
||||
// その他のタンスにあるアカウント
|
||||
val otherAccountList = ArrayList<SavedAccount>()
|
||||
for (a in daoSavedAccount.loadAccountList()) {
|
||||
|
||||
for (a in SavedAccount.loadAccountList(applicationContext)) {
|
||||
// 疑似アカウントは後でまとめて処理する
|
||||
if (a.isPseudo) continue
|
||||
|
||||
// 疑似アカウントは後でまとめて処理する
|
||||
if (a.isPseudo) continue
|
||||
if (isReference && TootInstance.getCached(a)?.canUseReference != true) continue
|
||||
|
||||
if (isReference && TootInstance.getCached(a)?.canUseReference != true) continue
|
||||
if (statusIdOriginal != null && a.matchHost(hostOriginal)) {
|
||||
// アクセス情報+ステータスID でアクセスできるなら
|
||||
// 同タンスのアカウントならステータスIDの変換なしに表示できる
|
||||
localAccountList.add(a)
|
||||
} else if (statusIdAccess != null && a.matchHost(hostAccess)) {
|
||||
// 既に変換済みのステータスIDがあるなら、そのアカウントでもステータスIDの変換は必要ない
|
||||
accessAccountList.add(a)
|
||||
} else {
|
||||
// 別タンスでも実アカウントなら検索APIでステータスIDを変換できる
|
||||
otherAccountList.add(a)
|
||||
}
|
||||
}
|
||||
|
||||
if (statusIdOriginal != null && a.matchHost(hostOriginal)) {
|
||||
// アクセス情報+ステータスID でアクセスできるなら
|
||||
// 同タンスのアカウントならステータスIDの変換なしに表示できる
|
||||
localAccountList.add(a)
|
||||
} else if (statusIdAccess != null && a.matchHost(hostAccess)) {
|
||||
// 既に変換済みのステータスIDがあるなら、そのアカウントでもステータスIDの変換は必要ない
|
||||
accessAccountList.add(a)
|
||||
} else {
|
||||
// 別タンスでも実アカウントなら検索APIでステータスIDを変換できる
|
||||
otherAccountList.add(a)
|
||||
}
|
||||
}
|
||||
// 参照の場合、status URLから/references を除去しないとURLでの検索ができない
|
||||
val url = when {
|
||||
isReference -> """/references\z""".toRegex().replace(urlArg, "")
|
||||
else -> urlArg
|
||||
}
|
||||
|
||||
// 参照の場合、status URLから/references を除去しないとURLでの検索ができない
|
||||
val url = when {
|
||||
isReference -> """/references\z""".toRegex().replace(urlArg, "")
|
||||
else -> urlArg
|
||||
}
|
||||
|
||||
// 同タンスのアカウントがないなら、疑似アカウントで開く選択肢
|
||||
if (localAccountList.isEmpty()) {
|
||||
if (statusIdOriginal != null) {
|
||||
dialog.addAction(
|
||||
getString(R.string.open_in_pseudo_account, "?@${hostOriginal.pretty}")
|
||||
) {
|
||||
launchMain {
|
||||
addPseudoAccount(hostOriginal)?.let { sa ->
|
||||
conversationLocal(pos, sa, statusIdOriginal, isReference = isReference)
|
||||
// 同タンスのアカウントがないなら、疑似アカウントで開く選択肢
|
||||
if (localAccountList.isEmpty()) {
|
||||
if (statusIdOriginal != null) {
|
||||
action(
|
||||
getString(R.string.open_in_pseudo_account, "?@${hostOriginal.pretty}")
|
||||
) {
|
||||
launchMain {
|
||||
addPseudoAccount(hostOriginal)?.let { sa ->
|
||||
conversationLocal(
|
||||
pos,
|
||||
sa,
|
||||
statusIdOriginal,
|
||||
isReference = isReference
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
action(
|
||||
getString(R.string.open_in_pseudo_account, "?@${hostOriginal.pretty}")
|
||||
) {
|
||||
launchMain {
|
||||
addPseudoAccount(hostOriginal)?.let { sa ->
|
||||
conversationRemote(pos, sa, url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dialog.addAction(
|
||||
getString(R.string.open_in_pseudo_account, "?@${hostOriginal.pretty}")
|
||||
) {
|
||||
launchMain {
|
||||
addPseudoAccount(hostOriginal)?.let { sa ->
|
||||
conversationRemote(pos, sa, url)
|
||||
|
||||
// ローカルアカウント
|
||||
if (statusIdOriginal != null) {
|
||||
for (a in localAccountList.sortedByNickname()) {
|
||||
action(
|
||||
daoAcctColor.getStringWithNickname(
|
||||
activity,
|
||||
R.string.open_in_account,
|
||||
a.acct
|
||||
)
|
||||
) {
|
||||
conversationLocal(pos, a, statusIdOriginal, isReference = isReference)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// アクセスしたアカウント
|
||||
if (statusIdAccess != null) {
|
||||
for (a in accessAccountList.sortedByNickname()) {
|
||||
action(
|
||||
daoAcctColor.getStringWithNickname(
|
||||
activity,
|
||||
R.string.open_in_account,
|
||||
a.acct
|
||||
)
|
||||
) {
|
||||
conversationLocal(pos, a, statusIdAccess, isReference = isReference)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// その他の実アカウント
|
||||
for (a in otherAccountList.sortedByNickname()) {
|
||||
action(
|
||||
daoAcctColor.getStringWithNickname(
|
||||
activity,
|
||||
R.string.open_in_account,
|
||||
a.acct
|
||||
)
|
||||
) {
|
||||
conversationRemote(pos, a, url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ローカルアカウント
|
||||
if (statusIdOriginal != null) {
|
||||
SavedAccount.sort(localAccountList)
|
||||
for (a in localAccountList) {
|
||||
dialog.addAction(
|
||||
AcctColor.getStringWithNickname(
|
||||
activity,
|
||||
R.string.open_in_account,
|
||||
a.acct
|
||||
)
|
||||
) { conversationLocal(pos, a, statusIdOriginal, isReference = isReference) }
|
||||
}
|
||||
}
|
||||
|
||||
// アクセスしたアカウント
|
||||
if (statusIdAccess != null) {
|
||||
SavedAccount.sort(accessAccountList)
|
||||
for (a in accessAccountList) {
|
||||
dialog.addAction(
|
||||
AcctColor.getStringWithNickname(
|
||||
activity,
|
||||
R.string.open_in_account,
|
||||
a.acct
|
||||
)
|
||||
) { conversationLocal(pos, a, statusIdAccess, isReference = isReference) }
|
||||
}
|
||||
}
|
||||
|
||||
// その他の実アカウント
|
||||
SavedAccount.sort(otherAccountList)
|
||||
for (a in otherAccountList) {
|
||||
dialog.addAction(
|
||||
AcctColor.getStringWithNickname(
|
||||
activity,
|
||||
R.string.open_in_account,
|
||||
a.acct
|
||||
)
|
||||
) { conversationRemote(pos, a, url) }
|
||||
}
|
||||
|
||||
dialog.show(activity, activity.getString(R.string.open_status_from))
|
||||
}
|
||||
|
||||
// リモートかもしれない会話の流れを表示する
|
||||
|
@ -466,65 +473,59 @@ fun ActMain.conversationFromTootsearch(
|
|||
) {
|
||||
statusArg ?: return
|
||||
|
||||
// step2: 選択したアカウントで投稿を検索して返信元の投稿のIDを調べる
|
||||
fun step2(a: SavedAccount) = launchMain {
|
||||
var tmp: TootStatus? = null
|
||||
runApiTask(a) { client ->
|
||||
val (result, status) = client.syncStatus(a, statusArg)
|
||||
tmp = status
|
||||
result
|
||||
}?.let { result ->
|
||||
val status = tmp
|
||||
val replyId = status?.in_reply_to_id
|
||||
when {
|
||||
status == null -> showToast(true, result.error ?: "?")
|
||||
replyId == null -> showToast(true, "showReplyTootsearch: in_reply_to_id is null")
|
||||
else -> conversationLocal(pos, a, replyId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// step 1: choose account
|
||||
|
||||
val host = statusArg.account.apDomain
|
||||
val localAccountList = ArrayList<SavedAccount>()
|
||||
val otherAccountList = ArrayList<SavedAccount>()
|
||||
|
||||
for (a in SavedAccount.loadAccountList(this)) {
|
||||
|
||||
// 検索APIはログイン必須なので疑似アカウントは使えない
|
||||
if (a.isPseudo) continue
|
||||
|
||||
if (a.matchHost(host)) {
|
||||
localAccountList.add(a)
|
||||
} else {
|
||||
otherAccountList.add(a)
|
||||
for (a in daoSavedAccount.loadAccountList()) {
|
||||
when {
|
||||
// 検索APIはログイン必須なので疑似アカウントは使えない
|
||||
a.isPseudo -> continue
|
||||
a.matchHost(host) -> localAccountList.add(a)
|
||||
else -> otherAccountList.add(a)
|
||||
}
|
||||
}
|
||||
|
||||
val dialog = ActionsDialog()
|
||||
val activity = this
|
||||
launchAndShowError {
|
||||
|
||||
SavedAccount.sort(localAccountList)
|
||||
for (a in localAccountList) {
|
||||
dialog.addAction(
|
||||
AcctColor.getStringWithNickname(
|
||||
this,
|
||||
R.string.open_in_account,
|
||||
a.acct
|
||||
)
|
||||
) { step2(a) }
|
||||
// step2: 選択したアカウントで投稿を検索して返信元の投稿のIDを調べる
|
||||
suspend fun step2(a: SavedAccount) {
|
||||
var tmp: TootStatus? = null
|
||||
runApiTask(a) { client ->
|
||||
val (result, status) = client.syncStatus(a, statusArg)
|
||||
tmp = status
|
||||
result
|
||||
}?.let { result ->
|
||||
val status = tmp
|
||||
val replyId = status?.in_reply_to_id
|
||||
when {
|
||||
status == null -> showToast(true, result.error ?: "?")
|
||||
replyId == null -> showToast(true, "showReplyTootsearch: in_reply_to_id is null")
|
||||
else -> conversationLocal(pos, a, replyId)
|
||||
}
|
||||
}
|
||||
}
|
||||
actionsDialog(getString(R.string.open_status_from)) {
|
||||
for (a in localAccountList.sortedByNickname()) {
|
||||
action(
|
||||
daoAcctColor.getStringWithNickname(
|
||||
activity,
|
||||
R.string.open_in_account,
|
||||
a.acct
|
||||
)
|
||||
) { step2(a) }
|
||||
}
|
||||
|
||||
for (a in otherAccountList.sortedByNickname()) {
|
||||
action(
|
||||
daoAcctColor.getStringWithNickname(
|
||||
activity,
|
||||
R.string.open_in_account,
|
||||
a.acct
|
||||
)
|
||||
) { step2(a) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SavedAccount.sort(otherAccountList)
|
||||
for (a in otherAccountList) {
|
||||
dialog.addAction(
|
||||
AcctColor.getStringWithNickname(
|
||||
this,
|
||||
R.string.open_in_account,
|
||||
a.acct
|
||||
)
|
||||
) { step2(a) }
|
||||
}
|
||||
|
||||
dialog.show(this, getString(R.string.open_status_from))
|
||||
}
|
||||
|
|
|
@ -6,8 +6,8 @@ import jp.juggler.subwaytooter.R
|
|||
import jp.juggler.subwaytooter.api.entity.TootFilter
|
||||
import jp.juggler.subwaytooter.api.runApiTask
|
||||
import jp.juggler.subwaytooter.column.onFilterDeleted
|
||||
import jp.juggler.subwaytooter.dialog.ActionsDialog
|
||||
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
||||
import jp.juggler.subwaytooter.dialog.actionsDialog
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.log.showToast
|
||||
|
@ -17,15 +17,17 @@ import okhttp3.Request
|
|||
|
||||
fun ActMain.openFilterMenu(accessInfo: SavedAccount, item: TootFilter?) {
|
||||
item ?: return
|
||||
|
||||
val ad = ActionsDialog()
|
||||
ad.addAction(getString(R.string.edit)) {
|
||||
ActKeywordFilter.open(this, accessInfo, item.id)
|
||||
val activity = this
|
||||
launchAndShowError {
|
||||
actionsDialog(getString(R.string.filter_of, item.displayString)) {
|
||||
action(getString(R.string.edit)) {
|
||||
ActKeywordFilter.open(activity, accessInfo, item.id)
|
||||
}
|
||||
action(getString(R.string.delete)) {
|
||||
filterDelete(accessInfo, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
ad.addAction(getString(R.string.delete)) {
|
||||
filterDelete(accessInfo, item)
|
||||
}
|
||||
ad.show(this, getString(R.string.filter_of, item.displayString))
|
||||
}
|
||||
|
||||
fun ActMain.filterDelete(
|
||||
|
|
|
@ -12,9 +12,7 @@ import jp.juggler.subwaytooter.column.fireRebindAdapterItems
|
|||
import jp.juggler.subwaytooter.column.removeUser
|
||||
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
||||
import jp.juggler.subwaytooter.dialog.pickAccount
|
||||
import jp.juggler.subwaytooter.table.AcctColor
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.UserRelation
|
||||
import jp.juggler.subwaytooter.table.*
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.coroutine.launchMain
|
||||
import jp.juggler.util.log.showToast
|
||||
|
@ -81,7 +79,7 @@ fun ActMain.clickFollowRequestAccept(
|
|||
accept -> R.string.follow_accept_confirm
|
||||
else -> R.string.follow_deny_confirm
|
||||
},
|
||||
AcctColor.getNickname(accessInfo, who)
|
||||
daoAcctColor.getNickname(accessInfo, who)
|
||||
)
|
||||
followRequestAuthorize(accessInfo, whoRef, accept)
|
||||
}
|
||||
|
@ -152,12 +150,12 @@ fun ActMain.follow(
|
|||
getString(
|
||||
R.string.confirm_follow_request_who_from,
|
||||
whoRef.decoded_display_name,
|
||||
AcctColor.getNickname(accessInfo)
|
||||
daoAcctColor.getNickname(accessInfo)
|
||||
),
|
||||
accessInfo.confirm_follow_locked,
|
||||
) { newConfirmEnabled ->
|
||||
accessInfo.confirm_follow_locked = newConfirmEnabled
|
||||
accessInfo.saveSetting()
|
||||
daoSavedAccount.saveSetting(accessInfo)
|
||||
activity.reloadAccountSetting(accessInfo)
|
||||
}
|
||||
} else if (bFollow) {
|
||||
|
@ -165,12 +163,12 @@ fun ActMain.follow(
|
|||
getString(
|
||||
R.string.confirm_follow_who_from,
|
||||
whoRef.decoded_display_name,
|
||||
AcctColor.getNickname(accessInfo)
|
||||
daoAcctColor.getNickname(accessInfo)
|
||||
),
|
||||
accessInfo.confirm_follow
|
||||
) { newConfirmEnabled ->
|
||||
accessInfo.confirm_follow = newConfirmEnabled
|
||||
accessInfo.saveSetting()
|
||||
daoSavedAccount.saveSetting(accessInfo)
|
||||
activity.reloadAccountSetting(accessInfo)
|
||||
}
|
||||
} else {
|
||||
|
@ -178,12 +176,12 @@ fun ActMain.follow(
|
|||
getString(
|
||||
R.string.confirm_unfollow_who_from,
|
||||
whoRef.decoded_display_name,
|
||||
AcctColor.getNickname(accessInfo)
|
||||
daoAcctColor.getNickname(accessInfo)
|
||||
),
|
||||
accessInfo.confirm_unfollow
|
||||
) { newConfirmEnabled ->
|
||||
accessInfo.confirm_unfollow = newConfirmEnabled
|
||||
accessInfo.saveSetting()
|
||||
daoSavedAccount.saveSetting(accessInfo)
|
||||
activity.reloadAccountSetting(accessInfo)
|
||||
}
|
||||
}
|
||||
|
@ -235,9 +233,9 @@ fun ActMain.follow(
|
|||
)?.also { result ->
|
||||
|
||||
fun saveFollow(f: Boolean) {
|
||||
val ur = UserRelation.load(accessInfo.db_id, userId)
|
||||
val ur = daoUserRelation.load(accessInfo.db_id, userId)
|
||||
ur.following = f
|
||||
UserRelation.save1Misskey(
|
||||
daoUserRelation.save1Misskey(
|
||||
System.currentTimeMillis(),
|
||||
accessInfo.db_id,
|
||||
userId.toString(),
|
||||
|
@ -264,7 +262,7 @@ fun ActMain.follow(
|
|||
"".toFormRequestBody().toPost()
|
||||
)?.also { result ->
|
||||
val newRelation = parseItem(::TootRelationShip, parser, result.jsonObject)
|
||||
resultRelation = accessInfo.saveUserRelation(newRelation)
|
||||
resultRelation = daoUserRelation.saveUserRelation(accessInfo, newRelation)
|
||||
}
|
||||
}
|
||||
}?.let { result ->
|
||||
|
@ -313,26 +311,26 @@ private fun ActMain.followRemote(
|
|||
confirm(
|
||||
getString(
|
||||
R.string.confirm_follow_request_who_from,
|
||||
AcctColor.getNickname(acct),
|
||||
AcctColor.getNickname(accessInfo)
|
||||
daoAcctColor.getNickname(acct),
|
||||
daoAcctColor.getNickname(accessInfo)
|
||||
),
|
||||
accessInfo.confirm_follow_locked,
|
||||
) { newConfirmEnabled ->
|
||||
accessInfo.confirm_follow_locked = newConfirmEnabled
|
||||
accessInfo.saveSetting()
|
||||
daoSavedAccount.saveSetting(accessInfo)
|
||||
reloadAccountSetting(accessInfo)
|
||||
}
|
||||
} else {
|
||||
confirm(
|
||||
getString(
|
||||
R.string.confirm_follow_who_from,
|
||||
AcctColor.getNickname(acct),
|
||||
AcctColor.getNickname(accessInfo)
|
||||
daoAcctColor.getNickname(acct),
|
||||
daoAcctColor.getNickname(accessInfo)
|
||||
),
|
||||
accessInfo.confirm_follow
|
||||
) { newConfirmEnabled ->
|
||||
accessInfo.confirm_follow = newConfirmEnabled
|
||||
accessInfo.saveSetting()
|
||||
daoSavedAccount.saveSetting(accessInfo)
|
||||
reloadAccountSetting(accessInfo)
|
||||
}
|
||||
}
|
||||
|
@ -357,12 +355,16 @@ private fun ActMain.followRemote(
|
|||
result?.error?.contains("already not following") == true
|
||||
) {
|
||||
// DBから読み直して値を変更する
|
||||
resultRelation = UserRelation.load(accessInfo.db_id, userId)
|
||||
resultRelation = daoUserRelation.load(accessInfo.db_id, userId)
|
||||
.apply { following = true }
|
||||
} else {
|
||||
// parserに残ってるRelationをDBに保存する
|
||||
parser.account(result?.jsonObject)?.let {
|
||||
resultRelation = accessInfo.saveUserRelationMisskey(it.id, parser)
|
||||
resultRelation = daoUserRelation.saveUserRelationMisskey(
|
||||
accessInfo,
|
||||
it.id,
|
||||
parser
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -372,7 +374,7 @@ private fun ActMain.followRemote(
|
|||
"".toFormRequestBody().toPost()
|
||||
)?.also { result ->
|
||||
parseItem(::TootRelationShip, parser, result.jsonObject)?.let {
|
||||
resultRelation = accessInfo.saveUserRelation(it)
|
||||
resultRelation = daoUserRelation.saveUserRelation(accessInfo, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -469,7 +471,11 @@ fun ActMain.followRequestAuthorize(
|
|||
val user = parser.account(result?.jsonObject)
|
||||
if (user != null) {
|
||||
// parserに残ってるRelationをDBに保存する
|
||||
accessInfo.saveUserRelationMisskey(user.id, parser)
|
||||
daoUserRelation.saveUserRelationMisskey(
|
||||
accessInfo,
|
||||
user.id,
|
||||
parser
|
||||
)
|
||||
}
|
||||
// 読めなくてもエラー処理は行わない
|
||||
}
|
||||
|
@ -481,7 +487,7 @@ fun ActMain.followRequestAuthorize(
|
|||
// Mastodon 3.0.0 から更新されたリレーションを返す
|
||||
// https//github.com/tootsuite/mastodon/pull/11800
|
||||
val newRelation = parseItem(::TootRelationShip, parser, result.jsonObject)
|
||||
accessInfo.saveUserRelation(newRelation)
|
||||
daoUserRelation.saveUserRelation(accessInfo, newRelation)
|
||||
// 読めなくてもエラー処理は行わない
|
||||
}
|
||||
}
|
||||
|
@ -543,7 +549,7 @@ fun ActMain.followRequestDelete(
|
|||
confirm(
|
||||
R.string.confirm_cancel_follow_request_who_from,
|
||||
whoRef.decoded_display_name,
|
||||
AcctColor.getNickname(accessInfo)
|
||||
daoAcctColor.getNickname(accessInfo)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -572,7 +578,12 @@ fun ActMain.followRequestDelete(
|
|||
)?.also { result ->
|
||||
parser.account(result.jsonObject)?.let {
|
||||
// parserに残ってるRelationをDBに保存する
|
||||
resultRelation = accessInfo.saveUserRelationMisskey(it.id, parser)
|
||||
resultRelation = daoUserRelation.saveUserRelationMisskey(
|
||||
accessInfo,
|
||||
it.id,
|
||||
parser
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,9 +13,9 @@ import jp.juggler.subwaytooter.api.runApiTask
|
|||
import jp.juggler.subwaytooter.column.ColumnType
|
||||
import jp.juggler.subwaytooter.column.onListListUpdated
|
||||
import jp.juggler.subwaytooter.column.onListNameUpdated
|
||||
import jp.juggler.subwaytooter.dialog.ActionsDialog
|
||||
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
||||
import jp.juggler.subwaytooter.dialog.DlgTextInput
|
||||
import jp.juggler.subwaytooter.dialog.actionsDialog
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.coroutine.launchMain
|
||||
|
@ -36,26 +36,28 @@ fun ActMain.clickListTl(pos: Int, accessInfo: SavedAccount, item: TimelineItem?)
|
|||
fun ActMain.clickListMoreButton(pos: Int, accessInfo: SavedAccount, item: TimelineItem?) {
|
||||
when (item) {
|
||||
is TootList -> {
|
||||
ActionsDialog()
|
||||
.addAction(getString(R.string.list_timeline)) {
|
||||
addColumn(pos, accessInfo, ColumnType.LIST_TL, item.id)
|
||||
launchAndShowError {
|
||||
actionsDialog(item.title) {
|
||||
action(getString(R.string.list_timeline)) {
|
||||
addColumn(pos, accessInfo, ColumnType.LIST_TL, item.id)
|
||||
}
|
||||
action(getString(R.string.list_member)) {
|
||||
addColumn(
|
||||
false,
|
||||
pos,
|
||||
accessInfo,
|
||||
ColumnType.LIST_MEMBER,
|
||||
item.id
|
||||
)
|
||||
}
|
||||
action(getString(R.string.rename)) {
|
||||
listRename(accessInfo, item)
|
||||
}
|
||||
action(getString(R.string.delete)) {
|
||||
listDelete(accessInfo, item)
|
||||
}
|
||||
}
|
||||
.addAction(getString(R.string.list_member)) {
|
||||
addColumn(
|
||||
false,
|
||||
pos,
|
||||
accessInfo,
|
||||
ColumnType.LIST_MEMBER,
|
||||
item.id
|
||||
)
|
||||
}
|
||||
.addAction(getString(R.string.rename)) {
|
||||
listRename(accessInfo, item)
|
||||
}
|
||||
.addAction(getString(R.string.delete)) {
|
||||
listDelete(accessInfo, item)
|
||||
}
|
||||
.show(this, item.title)
|
||||
}
|
||||
}
|
||||
|
||||
is MisskeyAntenna -> {
|
||||
|
|
|
@ -12,6 +12,7 @@ import jp.juggler.subwaytooter.api.syncAccountByAcct
|
|||
import jp.juggler.subwaytooter.column.onListMemberUpdated
|
||||
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.daoUserRelation
|
||||
import jp.juggler.util.*
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.coroutine.launchMain
|
||||
|
@ -71,7 +72,8 @@ fun ActMain.listMemberAdd(
|
|||
"".toFormRequestBody().toPost()
|
||||
) ?: return@runApiTask null
|
||||
|
||||
val relation = accessInfo.saveUserRelation(
|
||||
val relation = daoUserRelation.saveUserRelation(
|
||||
accessInfo,
|
||||
parseItem(::TootRelationShip, parser, result.jsonObject)
|
||||
) ?: return@runApiTask TootApiResult("parse error.")
|
||||
|
||||
|
|
|
@ -16,10 +16,13 @@ import jp.juggler.subwaytooter.api.runApiTask
|
|||
import jp.juggler.subwaytooter.api.syncStatus
|
||||
import jp.juggler.subwaytooter.dialog.pickAccount
|
||||
import jp.juggler.subwaytooter.pref.PrefB
|
||||
import jp.juggler.subwaytooter.pref.PrefDevice
|
||||
import jp.juggler.subwaytooter.pref.PrefS
|
||||
import jp.juggler.subwaytooter.pref.prefDevice
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.accountListCanQuote
|
||||
import jp.juggler.subwaytooter.table.accountListNonPseudo
|
||||
import jp.juggler.subwaytooter.util.matchHost
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.coroutine.launchMain
|
||||
import jp.juggler.util.log.LogCategory
|
||||
import jp.juggler.util.log.showToast
|
||||
|
@ -37,7 +40,7 @@ fun ActPost.saveWindowSize() {
|
|||
// WindowMetrics#getBounds() the window size including all system bar areas
|
||||
windowManager?.currentWindowMetrics?.bounds?.let { bounds ->
|
||||
log.d("API=${Build.VERSION.SDK_INT}, WindowMetrics#getBounds() $bounds")
|
||||
PrefDevice.savePostWindowBound(this, bounds.width(), bounds.height())
|
||||
prefDevice.savePostWindowBound(bounds.width(), bounds.height())
|
||||
}
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
|
@ -45,7 +48,7 @@ fun ActPost.saveWindowSize() {
|
|||
val dm = DisplayMetrics()
|
||||
display.getMetrics(dm)
|
||||
log.d("API=${Build.VERSION.SDK_INT}, displayMetrics=${dm.widthPixels},${dm.heightPixels}")
|
||||
PrefDevice.savePostWindowBound(this, dm.widthPixels, dm.heightPixels)
|
||||
prefDevice.savePostWindowBound(dm.widthPixels, dm.heightPixels)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -75,8 +78,8 @@ fun ActMain.openActPostImpl(
|
|||
scheduledStatus: TootScheduled? = null,
|
||||
) {
|
||||
|
||||
val useManyWindow = PrefB.bpManyWindowPost(pref)
|
||||
val useMultiWindow = useManyWindow || PrefB.bpMultiWindowPost(pref)
|
||||
val useManyWindow = PrefB.bpManyWindowPost.value
|
||||
val useMultiWindow = useManyWindow || PrefB.bpMultiWindowPost.value
|
||||
|
||||
val intent = ActPost.createIntent(
|
||||
context = this,
|
||||
|
@ -99,7 +102,9 @@ fun ActMain.openActPostImpl(
|
|||
ActPost.refActPost?.get()
|
||||
?.takeIf { it.isLiveActivity }
|
||||
?.let {
|
||||
it.updateText(intent)
|
||||
launchAndShowError {
|
||||
it.updateText(intent)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -108,11 +113,10 @@ fun ActMain.openActPostImpl(
|
|||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
|
||||
|
||||
var options = ActivityOptionsCompat.makeBasic()
|
||||
PrefDevice.loadPostWindowBound(this)
|
||||
?.let {
|
||||
log.d("ActPost launchBounds $it")
|
||||
options = options.setLaunchBounds(it)
|
||||
}
|
||||
prefDevice.loadPostWindowBound()?.let {
|
||||
log.d("ActPost launchBounds $it")
|
||||
options = options.setLaunchBounds(it)
|
||||
}
|
||||
|
||||
arActPost.launch(intent, options)
|
||||
}
|
||||
|
@ -254,7 +258,7 @@ fun ActMain.quoteFromAnotherAccount(
|
|||
fun ActMain.quoteName(who: TootAccount) {
|
||||
var sv = who.display_name
|
||||
try {
|
||||
val fmt = PrefS.spQuoteNameFormat(pref)
|
||||
val fmt = PrefS.spQuoteNameFormat.value
|
||||
if (fmt.contains("%1\$s")) {
|
||||
sv = String.format(Locale.getDefault(), fmt, sv)
|
||||
}
|
||||
|
|
|
@ -18,8 +18,10 @@ import jp.juggler.subwaytooter.dialog.launchEmojiPicker
|
|||
import jp.juggler.subwaytooter.dialog.pickAccount
|
||||
import jp.juggler.subwaytooter.emoji.CustomEmoji
|
||||
import jp.juggler.subwaytooter.emoji.UnicodeEmoji
|
||||
import jp.juggler.subwaytooter.table.AcctColor
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.accountListCanReaction
|
||||
import jp.juggler.subwaytooter.table.daoAcctColor
|
||||
import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||
import jp.juggler.subwaytooter.util.DecodeOptions
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.coroutine.launchMain
|
||||
|
@ -122,11 +124,15 @@ fun ActMain.reactionAdd(
|
|||
)
|
||||
val emojiSpan = TootReaction.toSpannableStringBuilder(options, code, urlArg)
|
||||
confirm(
|
||||
getString(R.string.confirm_reaction, emojiSpan, AcctColor.getNickname(accessInfo)),
|
||||
getString(
|
||||
R.string.confirm_reaction,
|
||||
emojiSpan,
|
||||
daoAcctColor.getNickname(accessInfo)
|
||||
),
|
||||
accessInfo.confirm_reaction,
|
||||
) { newConfirmEnabled ->
|
||||
accessInfo.confirm_reaction = newConfirmEnabled
|
||||
accessInfo.saveSetting()
|
||||
daoSavedAccount.saveSetting(accessInfo)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -336,16 +342,20 @@ private fun ActMain.reactionWithoutUi(
|
|||
isCustomEmoji && url?.likePleromaStatusUrl() == true -> confirm(
|
||||
R.string.confirm_reaction_to_pleroma,
|
||||
emojiSpan,
|
||||
AcctColor.getNickname(accessInfo),
|
||||
daoAcctColor.getNickname(accessInfo),
|
||||
resolvedStatus.account.acct.host?.pretty ?: "(null)"
|
||||
)
|
||||
|
||||
else -> confirm(
|
||||
getString(R.string.confirm_reaction, emojiSpan, AcctColor.getNickname(accessInfo)),
|
||||
getString(
|
||||
R.string.confirm_reaction,
|
||||
emojiSpan,
|
||||
daoAcctColor.getNickname(accessInfo)
|
||||
),
|
||||
accessInfo.confirm_reaction,
|
||||
) { newConfirmEnabled ->
|
||||
accessInfo.confirm_reaction = newConfirmEnabled
|
||||
accessInfo.saveSetting()
|
||||
daoSavedAccount.saveSetting(accessInfo)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,11 +12,13 @@ import jp.juggler.subwaytooter.api.entity.EntityId
|
|||
import jp.juggler.subwaytooter.api.entity.TootScheduled
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatus
|
||||
import jp.juggler.subwaytooter.column.*
|
||||
import jp.juggler.subwaytooter.dialog.ActionsDialog
|
||||
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
||||
import jp.juggler.subwaytooter.dialog.actionsDialog
|
||||
import jp.juggler.subwaytooter.dialog.pickAccount
|
||||
import jp.juggler.subwaytooter.table.AcctColor
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.accountListNonPseudo
|
||||
import jp.juggler.subwaytooter.table.daoAcctColor
|
||||
import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||
import jp.juggler.subwaytooter.util.emptyCallback
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.coroutine.launchMain
|
||||
|
@ -85,18 +87,20 @@ fun ActMain.clickFavourite(accessInfo: SavedAccount, status: TootStatus, willToa
|
|||
}
|
||||
|
||||
fun ActMain.clickScheduledToot(accessInfo: SavedAccount, item: TootScheduled, column: Column) {
|
||||
ActionsDialog()
|
||||
.addAction(getString(R.string.edit)) {
|
||||
scheduledPostEdit(accessInfo, item)
|
||||
}
|
||||
.addAction(getString(R.string.delete)) {
|
||||
launchAndShowError {
|
||||
scheduledPostDelete(accessInfo, item)
|
||||
column.onScheduleDeleted(item)
|
||||
showToast(false, R.string.scheduled_post_deleted)
|
||||
launchAndShowError {
|
||||
actionsDialog {
|
||||
action(getString(R.string.edit)) {
|
||||
scheduledPostEdit(accessInfo, item)
|
||||
}
|
||||
action(getString(R.string.delete)) {
|
||||
launchAndShowError {
|
||||
scheduledPostDelete(accessInfo, item)
|
||||
column.onScheduleDeleted(item)
|
||||
showToast(false, R.string.scheduled_post_deleted)
|
||||
}
|
||||
}
|
||||
}
|
||||
.show(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun ActMain.launchActText(intent: Intent) = arActText.launch(intent)
|
||||
|
@ -125,7 +129,7 @@ fun ActMain.favourite(
|
|||
true -> R.string.confirm_favourite_from
|
||||
else -> R.string.confirm_unfavourite_from
|
||||
},
|
||||
AcctColor.getNickname(accessInfo)
|
||||
daoAcctColor.getNickname(accessInfo)
|
||||
),
|
||||
when (bSet) {
|
||||
true -> accessInfo.confirm_favourite
|
||||
|
@ -136,7 +140,7 @@ fun ActMain.favourite(
|
|||
true -> accessInfo.confirm_favourite = newConfirmEnabled
|
||||
else -> accessInfo.confirm_unfavourite = newConfirmEnabled
|
||||
}
|
||||
accessInfo.saveSetting()
|
||||
daoSavedAccount.saveSetting(accessInfo)
|
||||
reloadAccountSetting(accessInfo)
|
||||
}
|
||||
}
|
||||
|
@ -294,12 +298,12 @@ fun ActMain.bookmark(
|
|||
confirm(
|
||||
getString(
|
||||
R.string.confirm_unbookmark_from,
|
||||
AcctColor.getNickname(accessInfo)
|
||||
daoAcctColor.getNickname(accessInfo)
|
||||
),
|
||||
accessInfo.confirm_unbookmark
|
||||
) { newConfirmEnabled ->
|
||||
accessInfo.confirm_unbookmark = newConfirmEnabled
|
||||
accessInfo.saveSetting()
|
||||
daoSavedAccount.saveSetting(accessInfo)
|
||||
reloadAccountSetting(accessInfo)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,12 +11,15 @@ import jp.juggler.subwaytooter.api.entity.TootTag
|
|||
import jp.juggler.subwaytooter.api.runApiTask
|
||||
import jp.juggler.subwaytooter.column.ColumnType
|
||||
import jp.juggler.subwaytooter.column.onTagFollowChanged
|
||||
import jp.juggler.subwaytooter.dialog.ActionsDialog
|
||||
import jp.juggler.subwaytooter.table.AcctColor
|
||||
import jp.juggler.subwaytooter.dialog.actionsDialog
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.daoAcctColor
|
||||
import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||
import jp.juggler.subwaytooter.table.sortedByNickname
|
||||
import jp.juggler.subwaytooter.util.matchHost
|
||||
import jp.juggler.subwaytooter.util.openCustomTab
|
||||
import jp.juggler.util.coroutine.AppDispatchers
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.coroutine.launchMain
|
||||
import jp.juggler.util.data.encodePercent
|
||||
import jp.juggler.util.log.LogCategory
|
||||
|
@ -57,23 +60,21 @@ fun ActMain.tagDialog(
|
|||
) {
|
||||
val activity = this
|
||||
val tagWithSharp = "#$tagWithoutSharp"
|
||||
launchMain {
|
||||
try {
|
||||
|
||||
val d = ActionsDialog()
|
||||
.addAction(getString(R.string.open_hashtag_column)) {
|
||||
tagTimelineFromAccount(
|
||||
pos,
|
||||
url,
|
||||
host,
|
||||
tagWithoutSharp
|
||||
)
|
||||
}
|
||||
launchAndShowError {
|
||||
actionsDialog(tagWithSharp) {
|
||||
action(getString(R.string.open_hashtag_column)) {
|
||||
tagTimelineFromAccount(
|
||||
pos,
|
||||
url,
|
||||
host,
|
||||
tagWithoutSharp
|
||||
)
|
||||
}
|
||||
|
||||
// 投稿者別タグTL
|
||||
if (whoAcct != null) {
|
||||
d.addAction(
|
||||
AcctColor.getStringWithNickname(
|
||||
action(
|
||||
daoAcctColor.getStringWithNickname(
|
||||
activity,
|
||||
R.string.open_hashtag_from_account,
|
||||
whoAcct
|
||||
|
@ -89,13 +90,13 @@ fun ActMain.tagDialog(
|
|||
}
|
||||
}
|
||||
|
||||
d.addAction(getString(R.string.open_in_browser)) { openCustomTab(url) }
|
||||
.addAction(
|
||||
getString(
|
||||
R.string.quote_hashtag_of,
|
||||
tagWithSharp
|
||||
)
|
||||
) { openPost("$tagWithSharp ") }
|
||||
action(getString(R.string.open_in_browser)) {
|
||||
openCustomTab(url)
|
||||
}
|
||||
|
||||
action(getString(R.string.quote_hashtag_of, tagWithSharp)) {
|
||||
openPost("$tagWithSharp ")
|
||||
}
|
||||
|
||||
if (tagList != null && tagList.size > 1) {
|
||||
val sb = StringBuilder()
|
||||
|
@ -104,11 +105,8 @@ fun ActMain.tagDialog(
|
|||
sb.append(s)
|
||||
}
|
||||
val tagAll = sb.toString()
|
||||
d.addAction(
|
||||
getString(
|
||||
R.string.quote_all_hashtag_of,
|
||||
tagAll
|
||||
)
|
||||
action(
|
||||
getString(R.string.quote_all_hashtag_of, tagAll)
|
||||
) { openPost("$tagAll ") }
|
||||
}
|
||||
|
||||
|
@ -118,10 +116,12 @@ fun ActMain.tagDialog(
|
|||
if (tag == null) {
|
||||
val result = runApiTask(accessInfo) { client ->
|
||||
client.request("/api/v1/tags/${tagWithoutSharp.encodePercent()}")
|
||||
} ?: return@launchMain //cancelled.
|
||||
TootParser(activity, accessInfo)
|
||||
.tag(result.jsonObject)
|
||||
?.let { tag = it }
|
||||
}
|
||||
if (result != null) {
|
||||
TootParser(activity, accessInfo)
|
||||
.tag(result.jsonObject)
|
||||
?.let { tag = it }
|
||||
}
|
||||
}
|
||||
|
||||
val toggle = !(tag?.following ?: false)
|
||||
|
@ -129,14 +129,10 @@ fun ActMain.tagDialog(
|
|||
true -> R.string.follow_hashtag_of
|
||||
else -> R.string.unfollow_hashtag_of
|
||||
}
|
||||
d.addAction(getString(toggleCaption, tagWithSharp)) {
|
||||
action(getString(toggleCaption, tagWithSharp)) {
|
||||
followHashTag(accessInfo, tagWithoutSharp, toggle)
|
||||
}
|
||||
}
|
||||
|
||||
d.show(activity, tagWithSharp)
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "tagDialog failed.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -175,80 +171,80 @@ fun ActMain.tagTimelineFromAccount(
|
|||
// 「投稿者別タグTL」を開くなら、投稿者のacctを指定する
|
||||
acct: Acct? = null,
|
||||
) {
|
||||
val activity = this
|
||||
launchAndShowError {
|
||||
actionsDialog("#$tagWithoutSharp") {
|
||||
|
||||
val dialog = ActionsDialog()
|
||||
val accountList = daoSavedAccount.loadAccountList().sortedByNickname()
|
||||
|
||||
val accountList = SavedAccount.loadAccountList(this)
|
||||
SavedAccount.sort(accountList)
|
||||
// 分類する
|
||||
val listOriginal = ArrayList<SavedAccount>()
|
||||
val listOriginalPseudo = ArrayList<SavedAccount>()
|
||||
val listOther = ArrayList<SavedAccount>()
|
||||
for (a in accountList) {
|
||||
if (acct == null) {
|
||||
when {
|
||||
!a.matchHost(host) -> listOther.add(a)
|
||||
a.isPseudo -> listOriginalPseudo.add(a)
|
||||
else -> listOriginal.add(a)
|
||||
}
|
||||
} else {
|
||||
when {
|
||||
// 疑似アカウントはacctからaccount idを取得できないので
|
||||
// アカウント別タグTLを開けない
|
||||
a.isPseudo -> Unit
|
||||
|
||||
// 分類する
|
||||
val listOriginal = ArrayList<SavedAccount>()
|
||||
val listOriginalPseudo = ArrayList<SavedAccount>()
|
||||
val listOther = ArrayList<SavedAccount>()
|
||||
for (a in accountList) {
|
||||
if (acct == null) {
|
||||
when {
|
||||
!a.matchHost(host) -> listOther.add(a)
|
||||
a.isPseudo -> listOriginalPseudo.add(a)
|
||||
else -> listOriginal.add(a)
|
||||
// ミスキーはアカウント別タグTLがないので
|
||||
// アカウント別タグTLを開けない
|
||||
a.isMisskey -> Unit
|
||||
|
||||
!a.matchHost(host) -> listOther.add(a)
|
||||
else -> listOriginal.add(a)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
when {
|
||||
// 疑似アカウントはacctからaccount idを取得できないので
|
||||
// アカウント別タグTLを開けない
|
||||
a.isPseudo -> Unit
|
||||
|
||||
// ミスキーはアカウント別タグTLがないので
|
||||
// アカウント別タグTLを開けない
|
||||
a.isMisskey -> Unit
|
||||
// ブラウザで表示する
|
||||
if (!url.isNullOrBlank()) {
|
||||
action(getString(R.string.open_web_on_host, host)) {
|
||||
openCustomTab(url)
|
||||
}
|
||||
}
|
||||
|
||||
!a.matchHost(host) -> listOther.add(a)
|
||||
else -> listOriginal.add(a)
|
||||
// 同タンスのアカウントがない場合は疑似アカウントを作成して開く
|
||||
// ただし疑似アカウントではアカウントの同期ができないため、特定ユーザのタグTLは読めない)
|
||||
if (acct == null && listOriginal.isEmpty() && listOriginalPseudo.isEmpty()) {
|
||||
action(getString(R.string.open_in_pseudo_account, "?@$host")) {
|
||||
launchMain {
|
||||
addPseudoAccount(host)?.let { tagTimeline(pos, it, tagWithoutSharp) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 分類した順に選択肢を追加する
|
||||
for (a in listOriginal) {
|
||||
action(
|
||||
daoAcctColor.getStringWithNickname(activity, R.string.open_in_account, a.acct)
|
||||
) {
|
||||
tagTimeline(pos, a, tagWithoutSharp, acct?.ascii)
|
||||
}
|
||||
}
|
||||
for (a in listOriginalPseudo) {
|
||||
action(
|
||||
daoAcctColor.getStringWithNickname(activity, R.string.open_in_account, a.acct)
|
||||
) {
|
||||
tagTimeline(pos, a, tagWithoutSharp, acct?.ascii)
|
||||
}
|
||||
}
|
||||
for (a in listOther) {
|
||||
action(
|
||||
daoAcctColor.getStringWithNickname(activity, R.string.open_in_account, a.acct)
|
||||
) {
|
||||
tagTimeline(pos, a, tagWithoutSharp, acct?.ascii)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ブラウザで表示する
|
||||
if (!url.isNullOrBlank()) {
|
||||
dialog.addAction(getString(R.string.open_web_on_host, host)) {
|
||||
openCustomTab(url)
|
||||
}
|
||||
}
|
||||
|
||||
// 同タンスのアカウントがない場合は疑似アカウントを作成して開く
|
||||
// ただし疑似アカウントではアカウントの同期ができないため、特定ユーザのタグTLは読めない)
|
||||
if (acct == null && listOriginal.isEmpty() && listOriginalPseudo.isEmpty()) {
|
||||
dialog.addAction(getString(R.string.open_in_pseudo_account, "?@$host")) {
|
||||
launchMain {
|
||||
addPseudoAccount(host)?.let { tagTimeline(pos, it, tagWithoutSharp) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 分類した順に選択肢を追加する
|
||||
for (a in listOriginal) {
|
||||
dialog.addAction(
|
||||
AcctColor.getStringWithNickname(
|
||||
this,
|
||||
R.string.open_in_account,
|
||||
a.acct
|
||||
)
|
||||
) {
|
||||
tagTimeline(pos, a, tagWithoutSharp, acct?.ascii)
|
||||
}
|
||||
}
|
||||
for (a in listOriginalPseudo) {
|
||||
dialog.addAction(AcctColor.getStringWithNickname(this, R.string.open_in_account, a.acct)) {
|
||||
tagTimeline(pos, a, tagWithoutSharp, acct?.ascii)
|
||||
}
|
||||
}
|
||||
for (a in listOther) {
|
||||
dialog.addAction(AcctColor.getStringWithNickname(this, R.string.open_in_account, a.acct)) {
|
||||
tagTimeline(pos, a, tagWithoutSharp, acct?.ascii)
|
||||
}
|
||||
}
|
||||
|
||||
dialog.show(this, "#$tagWithoutSharp")
|
||||
}
|
||||
|
||||
fun ActMain.followHashTag(
|
||||
|
|
|
@ -12,6 +12,9 @@ import jp.juggler.subwaytooter.api.syncStatus
|
|||
import jp.juggler.subwaytooter.column.ColumnType
|
||||
import jp.juggler.subwaytooter.dialog.pickAccount
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||
import jp.juggler.subwaytooter.table.sortInplaceByNickname
|
||||
import jp.juggler.subwaytooter.table.sortedByNickname
|
||||
import jp.juggler.subwaytooter.util.matchHost
|
||||
import jp.juggler.util.coroutine.launchMain
|
||||
import jp.juggler.util.log.showToast
|
||||
|
@ -102,7 +105,7 @@ fun ActMain.timelineLocal(
|
|||
launchMain {
|
||||
// 指定タンスのアカウントを持ってるか?
|
||||
val accountList = ArrayList<SavedAccount>()
|
||||
for (a in SavedAccount.loadAccountList(applicationContext)) {
|
||||
for (a in daoSavedAccount.loadAccountList()) {
|
||||
if (a.matchHost(host)) accountList.add(a)
|
||||
}
|
||||
|
||||
|
@ -113,12 +116,11 @@ fun ActMain.timelineLocal(
|
|||
}
|
||||
} else {
|
||||
// 持ってるならアカウントを選んで開く
|
||||
SavedAccount.sort(accountList)
|
||||
pickAccount(
|
||||
bAllowPseudo = true,
|
||||
bAuto = false,
|
||||
message = getString(R.string.account_picker_add_timeline_of, host),
|
||||
accountListArg = accountList
|
||||
accountListArg = accountList.sortedByNickname()
|
||||
)?.let { addColumn(pos, it, ColumnType.LOCAL) }
|
||||
}
|
||||
}
|
||||
|
@ -168,7 +170,7 @@ fun ActMain.timelineAroundByStatusAnotherAccount(
|
|||
// 利用可能なアカウントを列挙する
|
||||
val accountList1 = ArrayList<SavedAccount>() // 閲覧アカウントとホストが同じ
|
||||
val accountList2 = ArrayList<SavedAccount>() // その他実アカウント
|
||||
label@ for (a in SavedAccount.loadAccountList(this)) {
|
||||
label@ for (a in daoSavedAccount.loadAccountList()) {
|
||||
// Misskeyアカウントはステータスの同期が出来ないので選択させない
|
||||
if (a.isNA || a.isMisskey) continue
|
||||
when {
|
||||
|
@ -179,8 +181,8 @@ fun ActMain.timelineAroundByStatusAnotherAccount(
|
|||
!a.isPseudo -> accountList2.add(a)
|
||||
}
|
||||
}
|
||||
SavedAccount.sort(accountList1)
|
||||
SavedAccount.sort(accountList2)
|
||||
accountList1.sortInplaceByNickname()
|
||||
accountList2.sortInplaceByNickname()
|
||||
accountList1.addAll(accountList2)
|
||||
|
||||
if (accountList1.isEmpty()) {
|
||||
|
|
|
@ -14,10 +14,7 @@ import jp.juggler.subwaytooter.column.*
|
|||
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
||||
import jp.juggler.subwaytooter.dialog.ReportForm
|
||||
import jp.juggler.subwaytooter.dialog.pickAccount
|
||||
import jp.juggler.subwaytooter.table.AcctColor
|
||||
import jp.juggler.subwaytooter.table.FavMute
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.UserRelation
|
||||
import jp.juggler.subwaytooter.table.*
|
||||
import jp.juggler.subwaytooter.util.matchHost
|
||||
import jp.juggler.subwaytooter.util.openCustomTab
|
||||
import jp.juggler.util.*
|
||||
|
@ -72,9 +69,9 @@ fun ActMain.openAvatarImage(who: TootAccount) {
|
|||
fun ActMain.clickHideFavourite(
|
||||
accessInfo: SavedAccount,
|
||||
who: TootAccount,
|
||||
) {
|
||||
) = launchAndShowError {
|
||||
val acct = accessInfo.getFullAcct(who)
|
||||
FavMute.save(acct)
|
||||
daoFavMute.save(acct)
|
||||
showToast(false, R.string.changed)
|
||||
for (column in appState.columnList) {
|
||||
column.onHideFavouriteNotification(acct)
|
||||
|
@ -84,8 +81,8 @@ fun ActMain.clickHideFavourite(
|
|||
fun ActMain.clickShowFavourite(
|
||||
accessInfo: SavedAccount,
|
||||
who: TootAccount,
|
||||
) {
|
||||
FavMute.delete(accessInfo.getFullAcct(who))
|
||||
) = launchAndShowError {
|
||||
daoFavMute.delete(accessInfo.getFullAcct(who))
|
||||
showToast(false, R.string.changed)
|
||||
}
|
||||
|
||||
|
@ -110,133 +107,134 @@ private fun ActMain.userMute(
|
|||
bMute: Boolean,
|
||||
bMuteNotification: Boolean,
|
||||
duration: Int?,
|
||||
) {
|
||||
) = launchAndShowError {
|
||||
val whoAcct = whoAccessInfo.getFullAcct(whoArg)
|
||||
if (accessInfo.isMe(whoAcct)) {
|
||||
showToast(false, R.string.it_is_you)
|
||||
return
|
||||
return@launchAndShowError
|
||||
}
|
||||
|
||||
launchMain {
|
||||
var resultRelation: UserRelation? = null
|
||||
var resultWhoId: EntityId? = null
|
||||
runApiTask(accessInfo) { client ->
|
||||
val parser = TootParser(this, accessInfo)
|
||||
if (accessInfo.isPseudo) {
|
||||
if (!whoAcct.isValidFull) {
|
||||
TootApiResult("can't mute pseudo acct ${whoAcct.pretty}")
|
||||
} else {
|
||||
val relation = UserRelation.loadPseudo(whoAcct)
|
||||
relation.muting = bMute
|
||||
relation.savePseudo(whoAcct.ascii)
|
||||
resultRelation = relation
|
||||
resultWhoId = whoArg.id
|
||||
TootApiResult()
|
||||
var resultRelation: UserRelation? = null
|
||||
var resultWhoId: EntityId? = null
|
||||
runApiTask(accessInfo) { client ->
|
||||
val parser = TootParser(this, accessInfo)
|
||||
if (accessInfo.isPseudo) {
|
||||
if (!whoAcct.isValidFull) {
|
||||
TootApiResult("can't mute pseudo acct ${whoAcct.pretty}")
|
||||
} else {
|
||||
val relation = daoUserRelation.loadPseudo(whoAcct)
|
||||
relation.muting = bMute
|
||||
daoUserRelation.savePseudo(whoAcct.ascii, relation)
|
||||
resultRelation = relation
|
||||
resultWhoId = whoArg.id
|
||||
TootApiResult()
|
||||
}
|
||||
} else {
|
||||
val whoId = if (accessInfo.matchHost(whoAccessInfo)) {
|
||||
whoArg.id
|
||||
} else {
|
||||
val (result, accountRef) = client.syncAccountByAcct(accessInfo, whoAcct)
|
||||
accountRef?.get()?.id ?: return@runApiTask result
|
||||
}
|
||||
resultWhoId = whoId
|
||||
|
||||
if (accessInfo.isMisskey) {
|
||||
client.request(
|
||||
when (bMute) {
|
||||
true -> "/api/mute/create"
|
||||
else -> "/api/mute/delete"
|
||||
},
|
||||
accessInfo.putMisskeyApiToken().apply {
|
||||
put("userId", whoId.toString())
|
||||
}.toPostRequestBuilder()
|
||||
)?.apply {
|
||||
if (jsonObject != null) {
|
||||
// 204 no content
|
||||
|
||||
// update user relation
|
||||
val ur = daoUserRelation.load(accessInfo.db_id, whoId)
|
||||
ur.muting = bMute
|
||||
daoUserRelation.saveUserRelationMisskey(
|
||||
accessInfo,
|
||||
whoId,
|
||||
parser
|
||||
)
|
||||
|
||||
resultRelation = ur
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val whoId = if (accessInfo.matchHost(whoAccessInfo)) {
|
||||
whoArg.id
|
||||
} else {
|
||||
val (result, accountRef) = client.syncAccountByAcct(accessInfo, whoAcct)
|
||||
accountRef?.get()?.id ?: return@runApiTask result
|
||||
}
|
||||
resultWhoId = whoId
|
||||
|
||||
if (accessInfo.isMisskey) {
|
||||
client.request(
|
||||
when (bMute) {
|
||||
true -> "/api/mute/create"
|
||||
else -> "/api/mute/delete"
|
||||
},
|
||||
accessInfo.putMisskeyApiToken().apply {
|
||||
put("userId", whoId.toString())
|
||||
}.toPostRequestBuilder()
|
||||
)?.apply {
|
||||
if (jsonObject != null) {
|
||||
// 204 no content
|
||||
|
||||
// update user relation
|
||||
val ur = UserRelation.load(accessInfo.db_id, whoId)
|
||||
ur.muting = bMute
|
||||
accessInfo.saveUserRelationMisskey(
|
||||
whoId,
|
||||
parser
|
||||
)
|
||||
resultRelation = ur
|
||||
}
|
||||
}
|
||||
} else {
|
||||
client.request(
|
||||
"/api/v1/accounts/$whoId/${if (bMute) "mute" else "unmute"}",
|
||||
when {
|
||||
!bMute -> "".toFormRequestBody()
|
||||
else ->
|
||||
buildJsonObject {
|
||||
put("notifications", bMuteNotification)
|
||||
if (duration != null) put("duration", duration)
|
||||
}.toRequestBody()
|
||||
}.toPost()
|
||||
)?.apply {
|
||||
val jsonObject = jsonObject
|
||||
if (jsonObject != null) {
|
||||
resultRelation = accessInfo.saveUserRelation(
|
||||
parseItem(::TootRelationShip, parser, jsonObject)
|
||||
)
|
||||
}
|
||||
client.request(
|
||||
"/api/v1/accounts/$whoId/${if (bMute) "mute" else "unmute"}",
|
||||
when {
|
||||
!bMute -> "".toFormRequestBody()
|
||||
else ->
|
||||
buildJsonObject {
|
||||
put("notifications", bMuteNotification)
|
||||
if (duration != null) put("duration", duration)
|
||||
}.toRequestBody()
|
||||
}.toPost()
|
||||
)?.apply {
|
||||
val jsonObject = jsonObject
|
||||
if (jsonObject != null) {
|
||||
resultRelation = daoUserRelation.saveUserRelation(
|
||||
accessInfo,
|
||||
parseItem(::TootRelationShip, parser, jsonObject)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}?.let { result ->
|
||||
val relation = resultRelation
|
||||
val whoId = resultWhoId
|
||||
if (relation == null || whoId == null) {
|
||||
showToast(false, result.error)
|
||||
} else {
|
||||
// 未確認だが、自分をミュートしようとするとリクエストは成功するがレスポンス中のmutingはfalseになるはず
|
||||
if (bMute && !relation.muting) {
|
||||
showToast(false, R.string.not_muted)
|
||||
return@launchMain
|
||||
}
|
||||
}
|
||||
}?.let { result ->
|
||||
val relation = resultRelation
|
||||
val whoId = resultWhoId
|
||||
if (relation == null || whoId == null) {
|
||||
showToast(false, result.error)
|
||||
} else {
|
||||
// 未確認だが、自分をミュートしようとするとリクエストは成功するがレスポンス中のmutingはfalseになるはず
|
||||
if (bMute && !relation.muting) {
|
||||
showToast(false, R.string.not_muted)
|
||||
return@launchAndShowError
|
||||
}
|
||||
|
||||
for (column in appState.columnList) {
|
||||
if (column.accessInfo.isPseudo) {
|
||||
if (relation.muting && column.type != ColumnType.PROFILE) {
|
||||
// ミュートしたユーザの情報はTLから消える
|
||||
column.removeAccountInTimelinePseudo(whoAcct)
|
||||
}
|
||||
// フォローアイコンの表示更新が走る
|
||||
column.updateFollowIcons(accessInfo)
|
||||
} else if (column.accessInfo == accessInfo) {
|
||||
when {
|
||||
!relation.muting -> {
|
||||
if (column.type == ColumnType.MUTES) {
|
||||
// ミュート解除したら「ミュートしたユーザ」カラムから消える
|
||||
column.removeUser(accessInfo, ColumnType.MUTES, whoId)
|
||||
} else {
|
||||
// 他のカラムではフォローアイコンの表示更新が走る
|
||||
column.updateFollowIcons(accessInfo)
|
||||
}
|
||||
}
|
||||
|
||||
column.type == ColumnType.PROFILE && column.profileId == whoId -> {
|
||||
// 該当ユーザのプロフページのトゥートはミュートしてても見れる
|
||||
// しかしフォローアイコンの表示更新は必要
|
||||
for (column in appState.columnList) {
|
||||
if (column.accessInfo.isPseudo) {
|
||||
if (relation.muting && column.type != ColumnType.PROFILE) {
|
||||
// ミュートしたユーザの情報はTLから消える
|
||||
column.removeAccountInTimelinePseudo(whoAcct)
|
||||
}
|
||||
// フォローアイコンの表示更新が走る
|
||||
column.updateFollowIcons(accessInfo)
|
||||
} else if (column.accessInfo == accessInfo) {
|
||||
when {
|
||||
!relation.muting -> {
|
||||
if (column.type == ColumnType.MUTES) {
|
||||
// ミュート解除したら「ミュートしたユーザ」カラムから消える
|
||||
column.removeUser(accessInfo, ColumnType.MUTES, whoId)
|
||||
} else {
|
||||
// 他のカラムではフォローアイコンの表示更新が走る
|
||||
column.updateFollowIcons(accessInfo)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
// ミュートしたユーザの情報はTLから消える
|
||||
column.removeAccountInTimeline(accessInfo, whoId)
|
||||
}
|
||||
column.type == ColumnType.PROFILE && column.profileId == whoId -> {
|
||||
// 該当ユーザのプロフページのトゥートはミュートしてても見れる
|
||||
// しかしフォローアイコンの表示更新は必要
|
||||
column.updateFollowIcons(accessInfo)
|
||||
}
|
||||
|
||||
else -> {
|
||||
// ミュートしたユーザの情報はTLから消える
|
||||
column.removeAccountInTimeline(accessInfo, whoId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
showToast(
|
||||
false,
|
||||
if (relation.muting) R.string.mute_succeeded else R.string.unmute_succeeded
|
||||
)
|
||||
}
|
||||
|
||||
showToast(
|
||||
false,
|
||||
if (relation.muting) R.string.mute_succeeded else R.string.unmute_succeeded
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -258,7 +256,7 @@ fun ActMain.userMuteConfirm(
|
|||
accessInfo: SavedAccount,
|
||||
who: TootAccount,
|
||||
whoAccessInfo: SavedAccount,
|
||||
) {
|
||||
) = launchAndShowError {
|
||||
val activity = this@userMuteConfirm
|
||||
|
||||
// Mastodon 3.3から時限ミュート設定ができる
|
||||
|
@ -288,59 +286,56 @@ fun ActMain.userMuteConfirm(
|
|||
cbMuteNotification.vg(hasMuteNotification)
|
||||
?.setText(R.string.confirm_mute_notification_for_user)
|
||||
|
||||
launchMain {
|
||||
val spMuteDuration: Spinner = view.findViewById(R.id.spMuteDuration)
|
||||
val hasMuteDuration = try {
|
||||
when {
|
||||
accessInfo.isMisskey || accessInfo.isPseudo -> false
|
||||
else -> {
|
||||
var resultBoolean = false
|
||||
runApiTask(accessInfo) { client ->
|
||||
val (ti, ri) = TootInstance.get(client)
|
||||
resultBoolean = ti?.versionGE(TootInstance.VERSION_3_3_0_rc1) == true
|
||||
ri
|
||||
}
|
||||
resultBoolean
|
||||
}
|
||||
}
|
||||
} catch (ignored: CancellationException) {
|
||||
// not show error
|
||||
return@launchMain
|
||||
} catch (ex: RuntimeException) {
|
||||
showToast(true, ex.message)
|
||||
return@launchMain
|
||||
}
|
||||
|
||||
if (hasMuteDuration) {
|
||||
view.findViewById<View>(R.id.llMuteDuration).vg(true)
|
||||
spMuteDuration.apply {
|
||||
adapter = ArrayAdapter(
|
||||
activity,
|
||||
android.R.layout.simple_spinner_item,
|
||||
choiceList.map { it.second }.toTypedArray(),
|
||||
).apply {
|
||||
setDropDownViewResource(R.layout.lv_spinner_dropdown)
|
||||
val spMuteDuration: Spinner = view.findViewById(R.id.spMuteDuration)
|
||||
val hasMuteDuration = try {
|
||||
when {
|
||||
accessInfo.isMisskey || accessInfo.isPseudo -> false
|
||||
else -> {
|
||||
var resultBoolean = false
|
||||
runApiTask(accessInfo) { client ->
|
||||
val (ti, ri) = TootInstance.get(client)
|
||||
resultBoolean = ti?.versionGE(TootInstance.VERSION_3_3_0_rc1) == true
|
||||
ri
|
||||
}
|
||||
resultBoolean
|
||||
}
|
||||
}
|
||||
|
||||
AlertDialog.Builder(activity)
|
||||
.setView(view)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
userMute(
|
||||
accessInfo,
|
||||
who,
|
||||
whoAccessInfo,
|
||||
bMute = true,
|
||||
bMuteNotification = cbMuteNotification.isChecked,
|
||||
duration = spMuteDuration.selectedItemPosition
|
||||
.takeIf { hasMuteDuration && it in choiceList.indices }
|
||||
?.let { choiceList[it].first }
|
||||
)
|
||||
}
|
||||
.show()
|
||||
} catch (ignored: CancellationException) {
|
||||
// not show error
|
||||
return@launchAndShowError
|
||||
} catch (ex: RuntimeException) {
|
||||
showToast(true, ex.message)
|
||||
return@launchAndShowError
|
||||
}
|
||||
|
||||
if (hasMuteDuration) {
|
||||
view.findViewById<View>(R.id.llMuteDuration).vg(true)
|
||||
spMuteDuration.apply {
|
||||
adapter = ArrayAdapter(
|
||||
activity,
|
||||
android.R.layout.simple_spinner_item,
|
||||
choiceList.map { it.second }.toTypedArray(),
|
||||
).apply {
|
||||
setDropDownViewResource(R.layout.lv_spinner_dropdown)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AlertDialog.Builder(activity)
|
||||
.setView(view)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
userMute(
|
||||
accessInfo,
|
||||
who,
|
||||
whoAccessInfo,
|
||||
bMute = true,
|
||||
bMuteNotification = cbMuteNotification.isChecked,
|
||||
duration = spMuteDuration.selectedItemPosition
|
||||
.takeIf { hasMuteDuration && it in choiceList.indices }
|
||||
?.let { choiceList[it].first }
|
||||
)
|
||||
}.show()
|
||||
}
|
||||
|
||||
fun ActMain.userMuteFromAnotherAccount(
|
||||
|
@ -381,9 +376,9 @@ fun ActMain.userBlock(
|
|||
if (whoAcct.ascii.contains('?')) {
|
||||
TootApiResult("can't block pseudo account ${whoAcct.pretty}")
|
||||
} else {
|
||||
val relation = UserRelation.loadPseudo(whoAcct)
|
||||
val relation = daoUserRelation.loadPseudo(whoAcct)
|
||||
relation.blocking = bBlock
|
||||
relation.savePseudo(whoAcct.ascii)
|
||||
daoUserRelation.savePseudo(whoAcct.ascii, relation)
|
||||
relationResult = relation
|
||||
TootApiResult()
|
||||
}
|
||||
|
@ -397,11 +392,10 @@ fun ActMain.userBlock(
|
|||
whoIdResult = whoId
|
||||
|
||||
if (accessInfo.isMisskey) {
|
||||
|
||||
fun saveBlock(v: Boolean) {
|
||||
val ur = UserRelation.load(accessInfo.db_id, whoId)
|
||||
val ur = daoUserRelation.load(accessInfo.db_id, whoId)
|
||||
ur.blocking = v
|
||||
UserRelation.save1Misskey(
|
||||
daoUserRelation.save1Misskey(
|
||||
System.currentTimeMillis(),
|
||||
accessInfo.db_id,
|
||||
whoId.toString(),
|
||||
|
@ -434,7 +428,8 @@ fun ActMain.userBlock(
|
|||
"".toFormRequestBody().toPost()
|
||||
)?.also { result ->
|
||||
val parser = TootParser(this, accessInfo)
|
||||
relationResult = accessInfo.saveUserRelation(
|
||||
relationResult = daoUserRelation.saveUserRelation(
|
||||
accessInfo,
|
||||
parseItem(::TootRelationShip, parser, result.jsonObject)
|
||||
)
|
||||
}
|
||||
|
@ -597,7 +592,7 @@ fun ActMain.userProfileFromAnotherAccount(
|
|||
bAuto = false,
|
||||
message = getString(
|
||||
R.string.account_picker_open_user_who,
|
||||
AcctColor.getNickname(accessInfo, who)
|
||||
daoAcctColor.getNickname(accessInfo, who)
|
||||
),
|
||||
accountListArg = accountListNonPseudo(who.apDomain)
|
||||
)?.let { ai ->
|
||||
|
@ -630,7 +625,7 @@ fun ActMain.userProfile(
|
|||
acct: Acct,
|
||||
userUrl: String,
|
||||
originalUrl: String = userUrl,
|
||||
) {
|
||||
) = launchAndShowError {
|
||||
if (accessInfo?.isPseudo == false) {
|
||||
// 文脈のアカウントがあり、疑似アカウントではない
|
||||
|
||||
|
@ -655,51 +650,49 @@ fun ActMain.userProfile(
|
|||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
return@launchAndShowError
|
||||
}
|
||||
|
||||
// 文脈がない、もしくは疑似アカウントだった
|
||||
// 疑似アカウントでは検索APIを使えないため、IDが分からない
|
||||
|
||||
if (!SavedAccount.hasRealAccount()) {
|
||||
if (!daoSavedAccount.hasRealAccount()) {
|
||||
// 疑似アカウントしか登録されていない
|
||||
// chrome tab で開く
|
||||
openCustomTab(originalUrl)
|
||||
return
|
||||
return@launchAndShowError
|
||||
}
|
||||
launchMain {
|
||||
val activity = this@userProfile
|
||||
pickAccount(
|
||||
bAllowPseudo = false,
|
||||
bAuto = false,
|
||||
message = getString(
|
||||
R.string.account_picker_open_user_who,
|
||||
AcctColor.getNickname(acct)
|
||||
),
|
||||
accountListArg = accountListNonPseudo(acct.host),
|
||||
extraCallback = { ll, pad_se, pad_tb ->
|
||||
// chrome tab で開くアクションを追加
|
||||
val lp = LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
val b = AppCompatButton(activity)
|
||||
b.setPaddingRelative(pad_se, pad_tb, pad_se, pad_tb)
|
||||
b.gravity = Gravity.START or Gravity.CENTER_VERTICAL
|
||||
b.isAllCaps = false
|
||||
b.layoutParams = lp
|
||||
b.minHeight = (0.5f + 32f * activity.density).toInt()
|
||||
b.text = getString(R.string.open_in_browser)
|
||||
b.setBackgroundResource(R.drawable.btn_bg_transparent_round6dp)
|
||||
val activity = this@userProfile
|
||||
pickAccount(
|
||||
bAllowPseudo = false,
|
||||
bAuto = false,
|
||||
message = getString(
|
||||
R.string.account_picker_open_user_who,
|
||||
daoAcctColor.getNickname(acct)
|
||||
),
|
||||
accountListArg = accountListNonPseudo(acct.host),
|
||||
extraCallback = { ll, pad_se, pad_tb ->
|
||||
// chrome tab で開くアクションを追加
|
||||
val lp = LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
val b = AppCompatButton(activity)
|
||||
b.setPaddingRelative(pad_se, pad_tb, pad_se, pad_tb)
|
||||
b.gravity = Gravity.START or Gravity.CENTER_VERTICAL
|
||||
b.isAllCaps = false
|
||||
b.layoutParams = lp
|
||||
b.minHeight = (0.5f + 32f * activity.density).toInt()
|
||||
b.text = getString(R.string.open_in_browser)
|
||||
b.setBackgroundResource(R.drawable.btn_bg_transparent_round6dp)
|
||||
|
||||
b.setOnClickListener {
|
||||
openCustomTab(originalUrl)
|
||||
}
|
||||
ll.addView(b, 0)
|
||||
b.setOnClickListener {
|
||||
openCustomTab(originalUrl)
|
||||
}
|
||||
)?.let {
|
||||
userProfileFromUrlOrAcct(pos, it, acct, userUrl)
|
||||
ll.addView(b, 0)
|
||||
}
|
||||
)?.let {
|
||||
userProfileFromUrlOrAcct(pos, it, acct, userUrl)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -798,12 +791,9 @@ fun ActMain.userSetShowBoosts(
|
|||
jsonObjectOf("reblogs" to bShow).toPostRequestBuilder()
|
||||
)?.also { result ->
|
||||
val parser = TootParser(this, accessInfo)
|
||||
resultRelation = accessInfo.saveUserRelation(
|
||||
parseItem(
|
||||
::TootRelationShip,
|
||||
parser,
|
||||
result.jsonObject
|
||||
)
|
||||
resultRelation = daoUserRelation.saveUserRelation(
|
||||
accessInfo,
|
||||
parseItem(::TootRelationShip, parser, result.jsonObject)
|
||||
)
|
||||
}
|
||||
}?.let { result ->
|
||||
|
@ -860,7 +850,7 @@ fun ActMain.userSetStatusNotification(
|
|||
result.jsonObject
|
||||
)
|
||||
if (relation != null) {
|
||||
UserRelation.save1Mastodon(
|
||||
daoUserRelation.save1Mastodon(
|
||||
System.currentTimeMillis(),
|
||||
accessInfo.db_id,
|
||||
relation
|
||||
|
@ -901,7 +891,8 @@ fun ActMain.userEndorsement(
|
|||
)
|
||||
?.also { result ->
|
||||
val parser = TootParser(this, accessInfo)
|
||||
resultRelation = accessInfo.saveUserRelation(
|
||||
resultRelation = daoUserRelation.saveUserRelation(
|
||||
accessInfo,
|
||||
parseItem(::TootRelationShip, parser, result.jsonObject)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import jp.juggler.subwaytooter.ActMain
|
|||
import jp.juggler.subwaytooter.column.fireShowColumnHeader
|
||||
import jp.juggler.subwaytooter.pref.PrefL
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||
|
||||
// デフォルトの投稿先アカウントを探す。アカウント選択が必要な状況ならnull
|
||||
val ActMain.currentPostTarget: SavedAccount?
|
||||
|
@ -17,9 +18,9 @@ val ActMain.currentPostTarget: SavedAccount?
|
|||
},
|
||||
{ env ->
|
||||
|
||||
val dbId = PrefL.lpTabletTootDefaultAccount()
|
||||
val dbId = PrefL.lpTabletTootDefaultAccount.value
|
||||
if (dbId != -1L) {
|
||||
val a = SavedAccount.loadAccount(this@currentPostTarget, dbId)
|
||||
val a = daoSavedAccount.loadAccount(dbId)
|
||||
if (a != null && !a.isPseudo) return a
|
||||
}
|
||||
|
||||
|
@ -47,24 +48,23 @@ val ActMain.currentPostTarget: SavedAccount?
|
|||
})
|
||||
|
||||
fun ActMain.reloadAccountSetting(
|
||||
newAccounts: ArrayList<SavedAccount> = SavedAccount.loadAccountList(
|
||||
this
|
||||
),
|
||||
newAccounts: List<SavedAccount>,
|
||||
) {
|
||||
for (column in appState.columnList) {
|
||||
val a = column.accessInfo
|
||||
if (!a.isNA) a.reloadSetting(this, newAccounts.find { it.acct == a.acct })
|
||||
val b = newAccounts.find { it.acct == a.acct }
|
||||
if (!a.isNA && b != null) daoSavedAccount.reloadSetting(a, b)
|
||||
column.fireShowColumnHeader()
|
||||
}
|
||||
}
|
||||
|
||||
fun ActMain.reloadAccountSetting(account: SavedAccount) {
|
||||
val newData = SavedAccount.loadAccount(this, account.db_id)
|
||||
val newData = daoSavedAccount.loadAccount(account.db_id)
|
||||
?: return
|
||||
for (column in appState.columnList) {
|
||||
val a = column.accessInfo
|
||||
if (a.acct != newData.acct) continue
|
||||
if (!a.isNA) a.reloadSetting(this, newData)
|
||||
if (!a.isNA) daoSavedAccount.reloadSetting(a, newData)
|
||||
column.fireShowColumnHeader()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import android.text.Spannable
|
|||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.work.WorkManager
|
||||
import jp.juggler.subwaytooter.ActMain
|
||||
import jp.juggler.subwaytooter.R
|
||||
import jp.juggler.subwaytooter.action.openColumnList
|
||||
|
@ -17,86 +18,93 @@ import jp.juggler.subwaytooter.columnviewholder.ColumnViewHolder
|
|||
import jp.juggler.subwaytooter.columnviewholder.TabletColumnViewHolder
|
||||
import jp.juggler.subwaytooter.columnviewholder.ViewHolderHeaderBase
|
||||
import jp.juggler.subwaytooter.columnviewholder.ViewHolderItem
|
||||
import jp.juggler.subwaytooter.dialog.ActionsDialog
|
||||
import jp.juggler.subwaytooter.dialog.actionsDialog
|
||||
import jp.juggler.subwaytooter.itemviewholder.ItemViewHolder
|
||||
import jp.juggler.subwaytooter.pref.*
|
||||
import jp.juggler.subwaytooter.push.PushWorker
|
||||
import jp.juggler.subwaytooter.push.pushRepo
|
||||
import jp.juggler.subwaytooter.span.MyClickableSpan
|
||||
import jp.juggler.subwaytooter.util.checkPrivacyPolicy
|
||||
import jp.juggler.subwaytooter.util.openCustomTab
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.data.addTo
|
||||
import jp.juggler.util.data.cast
|
||||
import jp.juggler.util.data.notEmpty
|
||||
import jp.juggler.util.log.LogCategory
|
||||
import jp.juggler.util.log.showToast
|
||||
import jp.juggler.util.ui.dismissSafe
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
private val log = LogCategory("ActMainActions")
|
||||
|
||||
fun ActMain.onBackPressedImpl() {
|
||||
launchAndShowError {
|
||||
|
||||
// メニューが開いていたら閉じる
|
||||
if (drawer.isDrawerOpen(GravityCompat.START)) {
|
||||
drawer.closeDrawer(GravityCompat.START)
|
||||
return
|
||||
}
|
||||
|
||||
// カラムが0個ならアプリを終了する
|
||||
if (appState.columnCount == 0) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
// カラム設定が開いているならカラム設定を閉じる
|
||||
if (closeColumnSetting()) {
|
||||
return
|
||||
}
|
||||
|
||||
fun getClosableColumnList(): List<Column> {
|
||||
val visibleColumnList = ArrayList<Column>()
|
||||
phoneTab({ env ->
|
||||
try {
|
||||
appState.column(env.pager.currentItem)?.addTo(visibleColumnList)
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "getClosableColumnList failed.")
|
||||
}
|
||||
}, { env ->
|
||||
visibleColumnList.addAll(env.visibleColumns)
|
||||
})
|
||||
|
||||
return visibleColumnList.filter { !it.dontClose }
|
||||
}
|
||||
|
||||
// カラムが1個以上ある場合は設定に合わせて挙動を変える
|
||||
when (PrefI.ipBackButtonAction.invoke(pref)) {
|
||||
PrefI.BACK_EXIT_APP -> finish()
|
||||
PrefI.BACK_OPEN_COLUMN_LIST -> openColumnList()
|
||||
PrefI.BACK_CLOSE_COLUMN -> {
|
||||
val closeableColumnList = getClosableColumnList()
|
||||
when (closeableColumnList.size) {
|
||||
0 -> when {
|
||||
PrefB.bpExitAppWhenCloseProtectedColumn(pref) &&
|
||||
PrefB.bpDontConfirmBeforeCloseColumn.invoke(pref) ->
|
||||
finish()
|
||||
else -> showToast(false, R.string.missing_closeable_column)
|
||||
}
|
||||
1 -> closeColumn(closeableColumnList.first())
|
||||
else -> showToast(
|
||||
false,
|
||||
R.string.cant_close_column_by_back_button_when_multiple_column_shown
|
||||
)
|
||||
}
|
||||
// メニューが開いていたら閉じる
|
||||
if (drawer.isDrawerOpen(GravityCompat.START)) {
|
||||
drawer.closeDrawer(GravityCompat.START)
|
||||
return@launchAndShowError
|
||||
}
|
||||
else /* PrefI.BACK_ASK_ALWAYS */ -> {
|
||||
val closeableColumnList = getClosableColumnList()
|
||||
val dialog = ActionsDialog()
|
||||
if (closeableColumnList.size == 1) {
|
||||
val column = closeableColumnList.first()
|
||||
dialog.addAction(getString(R.string.close_column)) {
|
||||
closeColumn(column, bConfirmed = true)
|
||||
|
||||
// カラムが0個ならアプリを終了する
|
||||
if (appState.columnCount == 0) {
|
||||
finish()
|
||||
return@launchAndShowError
|
||||
}
|
||||
|
||||
// カラム設定が開いているならカラム設定を閉じる
|
||||
if (closeColumnSetting()) {
|
||||
return@launchAndShowError
|
||||
}
|
||||
|
||||
fun getClosableColumnList(): List<Column> {
|
||||
val visibleColumnList = ArrayList<Column>()
|
||||
phoneTab({ env ->
|
||||
try {
|
||||
appState.column(env.pager.currentItem)?.addTo(visibleColumnList)
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "getClosableColumnList failed.")
|
||||
}
|
||||
}, { env ->
|
||||
visibleColumnList.addAll(env.visibleColumns)
|
||||
})
|
||||
|
||||
return visibleColumnList.filter { !it.dontClose }
|
||||
}
|
||||
|
||||
// カラムが1個以上ある場合は設定に合わせて挙動を変える
|
||||
when (PrefI.ipBackButtonAction.value) {
|
||||
PrefI.BACK_EXIT_APP -> finish()
|
||||
PrefI.BACK_OPEN_COLUMN_LIST -> openColumnList()
|
||||
PrefI.BACK_CLOSE_COLUMN -> {
|
||||
val closeableColumnList = getClosableColumnList()
|
||||
when (closeableColumnList.size) {
|
||||
0 -> when {
|
||||
PrefB.bpExitAppWhenCloseProtectedColumn.value &&
|
||||
PrefB.bpDontConfirmBeforeCloseColumn.value ->
|
||||
finish()
|
||||
else -> showToast(false, R.string.missing_closeable_column)
|
||||
}
|
||||
1 -> closeColumn(closeableColumnList.first())
|
||||
else -> showToast(
|
||||
false,
|
||||
R.string.cant_close_column_by_back_button_when_multiple_column_shown
|
||||
)
|
||||
}
|
||||
}
|
||||
dialog.addAction(getString(R.string.open_column_list)) { openColumnList() }
|
||||
dialog.addAction(getString(R.string.app_exit)) { finish() }
|
||||
dialog.show(this, null)
|
||||
/* PrefI.BACK_ASK_ALWAYS */
|
||||
else -> actionsDialog {
|
||||
val closeableColumnList = getClosableColumnList()
|
||||
if (closeableColumnList.size == 1) {
|
||||
val column = closeableColumnList.first()
|
||||
action(getString(R.string.close_column)) {
|
||||
closeColumn(column, bConfirmed = true)
|
||||
}
|
||||
}
|
||||
action(getString(R.string.open_column_list)) { openColumnList() }
|
||||
action(getString(R.string.app_exit)) { finish() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -178,23 +186,23 @@ fun ActMain.onMyClickableSpanClickedImpl(viewClicked: View, span: MyClickableSpa
|
|||
)
|
||||
}
|
||||
|
||||
fun ActMain.themeDefaultChangedDialog() {
|
||||
suspend fun ActMain.themeDefaultChangedDialog() {
|
||||
val lpThemeDefaultChangedWarnTime = PrefL.lpThemeDefaultChangedWarnTime
|
||||
val ipUiTheme = PrefI.ipUiTheme
|
||||
val now = System.currentTimeMillis()
|
||||
|
||||
// テーマが未定義でなければ警告しない
|
||||
if (pref.getInt(ipUiTheme.key, -1) != -1) {
|
||||
if (lazyPref.getInt(ipUiTheme.key, -1) != -1) {
|
||||
log.i("themeDefaultChangedDialog: theme was set.")
|
||||
return
|
||||
}
|
||||
|
||||
// 頻繁には警告しない
|
||||
if (now - lpThemeDefaultChangedWarnTime.invoke(pref) < TimeUnit.DAYS.toMillis(60L)) {
|
||||
if (now - lpThemeDefaultChangedWarnTime.value < TimeUnit.DAYS.toMillis(60L)) {
|
||||
log.i("themeDefaultChangedDialog: avoid frequently check.")
|
||||
return
|
||||
}
|
||||
pref.edit().put(lpThemeDefaultChangedWarnTime, now).apply()
|
||||
lpThemeDefaultChangedWarnTime.value = now
|
||||
|
||||
// 色がすべてデフォルトなら警告不要
|
||||
val customizedKeys = ArrayList<String>()
|
||||
|
@ -202,18 +210,50 @@ fun ActMain.themeDefaultChangedDialog() {
|
|||
item.pref?.let { p ->
|
||||
when {
|
||||
p == PrefS.spBoostAlpha -> Unit
|
||||
p.hasNonDefaultValue(pref) -> customizedKeys.add(p.key)
|
||||
p.hasNonDefaultValue() -> customizedKeys.add(p.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
log.w("themeDefaultChangedDialog: customizedKeys=${customizedKeys.joinToString(",")}")
|
||||
if (customizedKeys.isEmpty()) {
|
||||
pref.edit().put(ipUiTheme, ipUiTheme.defVal).apply()
|
||||
ipUiTheme.value = ipUiTheme.defVal
|
||||
return
|
||||
}
|
||||
|
||||
AlertDialog.Builder(this)
|
||||
.setMessage(R.string.color_theme_changed)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show()
|
||||
log.w("themeDefaultChangedDialog: customizedKeys=${customizedKeys.joinToString(",")}")
|
||||
suspendCancellableCoroutine { cont ->
|
||||
val dialog = AlertDialog.Builder(this)
|
||||
.setMessage(R.string.color_theme_changed)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.setOnDismissListener {
|
||||
if (cont.isActive) cont.resume(Unit) {}
|
||||
}
|
||||
.create()
|
||||
cont.invokeOnCancellation { dialog.dismissSafe() }
|
||||
dialog.show()
|
||||
}
|
||||
}
|
||||
|
||||
fun ActMain.launchDialogs() {
|
||||
launchAndShowError {
|
||||
// プライバシーポリシー
|
||||
val agreed = try {
|
||||
checkPrivacyPolicy()
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "checkPrivacyPolicy failed.")
|
||||
return@launchAndShowError
|
||||
}
|
||||
// 同意がないなら残りの何かは表示しない
|
||||
if (!agreed) return@launchAndShowError
|
||||
|
||||
// テーマ告知
|
||||
themeDefaultChangedDialog()
|
||||
|
||||
// 通知権限の確認
|
||||
if(!prNotification.checkOrLaunch()) return@launchAndShowError
|
||||
|
||||
// Workの掃除
|
||||
WorkManager.getInstance(applicationContext).pruneWork()
|
||||
|
||||
// 定期的にendpointを再登録したい
|
||||
PushWorker.enqueueRegisterEndpoint(applicationContext, keepAliveMode = true)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ fun ActMain.refreshAfterPost() {
|
|||
this.postedRedraftId = null
|
||||
}
|
||||
|
||||
val refreshAfterToot = PrefI.ipRefreshAfterToot(pref)
|
||||
val refreshAfterToot = PrefI.ipRefreshAfterToot.value
|
||||
if (refreshAfterToot != PrefI.RAT_DONT_REFRESH) {
|
||||
appState.columnList
|
||||
.filter { it.accessInfo.acct == postedAcct }
|
||||
|
|
|
@ -12,7 +12,7 @@ import java.lang.ref.WeakReference
|
|||
|
||||
// AutoCWの基準幅を計算する
|
||||
fun ActMain.resizeAutoCW(columnW: Int) {
|
||||
val sv = PrefS.spAutoCWLines(pref)
|
||||
val sv = PrefS.spAutoCWLines.value
|
||||
nAutoCwLines = sv.toIntOrNull() ?: -1
|
||||
if (nAutoCwLines > 0) {
|
||||
val lvPad = (0.5f + 12 * density).toInt()
|
||||
|
|
|
@ -16,8 +16,8 @@ import jp.juggler.subwaytooter.columnviewholder.showColumnSetting
|
|||
import jp.juggler.subwaytooter.pref.PrefB
|
||||
import jp.juggler.subwaytooter.pref.PrefI
|
||||
import jp.juggler.subwaytooter.pref.PrefS
|
||||
import jp.juggler.subwaytooter.table.AcctColor
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.daoAcctColor
|
||||
import jp.juggler.util.*
|
||||
import jp.juggler.util.data.clip
|
||||
import jp.juggler.util.log.LogCategory
|
||||
|
@ -103,7 +103,7 @@ fun ActMain.addColumn(
|
|||
vararg params: Any,
|
||||
): Column {
|
||||
return addColumn(
|
||||
PrefB.bpAllowColumnDuplication(pref),
|
||||
PrefB.bpAllowColumnDuplication.value,
|
||||
indexArg,
|
||||
ai,
|
||||
type,
|
||||
|
@ -175,7 +175,7 @@ fun ActMain.updateColumnStrip() {
|
|||
viewRoot.tag = index
|
||||
viewRoot.setOnClickListener { v ->
|
||||
val idx = v.tag as Int
|
||||
if (PrefB.bpScrollTopFromColumnStrip(pref) && isVisibleColumn(idx)) {
|
||||
if (PrefB.bpScrollTopFromColumnStrip.value && isVisibleColumn(idx)) {
|
||||
column.viewHolder?.scrollToTop2()
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
@ -193,9 +193,9 @@ fun ActMain.updateColumnStrip() {
|
|||
ivIcon.imageTintList = ColorStateList.valueOf(column.getHeaderNameColor())
|
||||
|
||||
//
|
||||
val ac = AcctColor.load(column.accessInfo)
|
||||
if (AcctColor.hasColorForeground(ac)) {
|
||||
vAcctColor.setBackgroundColor(ac.color_fg)
|
||||
val ac = daoAcctColor.load(column.accessInfo)
|
||||
if (daoAcctColor.hasColorForeground(ac)) {
|
||||
vAcctColor.setBackgroundColor(ac.colorFg)
|
||||
} else {
|
||||
vAcctColor.visibility = View.INVISIBLE
|
||||
}
|
||||
|
@ -214,7 +214,7 @@ fun ActMain.closeColumn(column: Column, bConfirmed: Boolean = false) {
|
|||
return
|
||||
}
|
||||
|
||||
if (!bConfirmed && !PrefB.bpDontConfirmBeforeCloseColumn(pref)) {
|
||||
if (!bConfirmed && !PrefB.bpDontConfirmBeforeCloseColumn.value) {
|
||||
AlertDialog.Builder(this)
|
||||
.setMessage(R.string.confirm_close_column)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
|
@ -366,7 +366,7 @@ fun ActMain.scrollToColumn(index: Int, smoothScroll: Boolean = true) {
|
|||
fun ActMain.scrollToLastColumn() {
|
||||
if (appState.columnCount <= 0) return
|
||||
|
||||
val columnPos = PrefI.ipLastColumnPos(pref)
|
||||
val columnPos = PrefI.ipLastColumnPos.value
|
||||
log.d("ipLastColumnPos load $columnPos")
|
||||
|
||||
// 前回最後に表示していたカラムの位置にスクロールする
|
||||
|
@ -385,7 +385,7 @@ fun ActMain.scrollToLastColumn() {
|
|||
fun ActMain.resizeColumnWidth(views: ActMainTabletViews) {
|
||||
|
||||
var columnWMinDp = ActMain.COLUMN_WIDTH_MIN_DP
|
||||
val sv = PrefS.spColumnWidth(pref)
|
||||
val sv = PrefS.spColumnWidth.value
|
||||
if (sv.isNotEmpty()) {
|
||||
try {
|
||||
val iv = Integer.parseInt(sv)
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.content.Intent
|
|||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.net.toUri
|
||||
import jp.juggler.subwaytooter.ActMain
|
||||
import jp.juggler.subwaytooter.R
|
||||
|
@ -18,20 +19,35 @@ import jp.juggler.subwaytooter.api.entity.TootStatus.Companion.findStatusIdFromU
|
|||
import jp.juggler.subwaytooter.api.entity.TootVisibility
|
||||
import jp.juggler.subwaytooter.api.runApiTask2
|
||||
import jp.juggler.subwaytooter.api.showApiError
|
||||
import jp.juggler.subwaytooter.auth.authRepo
|
||||
import jp.juggler.subwaytooter.column.ColumnType
|
||||
import jp.juggler.subwaytooter.column.startLoading
|
||||
import jp.juggler.subwaytooter.dialog.actionsDialog
|
||||
import jp.juggler.subwaytooter.dialog.pickAccount
|
||||
import jp.juggler.subwaytooter.dialog.runInProgress
|
||||
import jp.juggler.subwaytooter.notification.PushSubscriptionHelper
|
||||
import jp.juggler.subwaytooter.notification.checkNotificationImmediate
|
||||
import jp.juggler.subwaytooter.notification.checkNotificationImmediateAll
|
||||
import jp.juggler.subwaytooter.notification.recycleClickedNotification
|
||||
import jp.juggler.subwaytooter.pref.PrefDevice
|
||||
import jp.juggler.subwaytooter.pref.prefDevice
|
||||
import jp.juggler.subwaytooter.push.PushWorker
|
||||
import jp.juggler.subwaytooter.push.fcmHandler
|
||||
import jp.juggler.subwaytooter.push.pushRepo
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||
import jp.juggler.util.coroutine.AppDispatchers
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.coroutine.launchMain
|
||||
import jp.juggler.util.data.decodePercent
|
||||
import jp.juggler.util.data.groupEx
|
||||
import jp.juggler.util.log.LogCategory
|
||||
import jp.juggler.util.log.showToast
|
||||
import jp.juggler.util.queryIntentActivitiesCompat
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.unifiedpush.android.connector.UnifiedPush
|
||||
import java.util.ArrayList
|
||||
|
||||
private val log = LogCategory("ActMainIntent")
|
||||
|
||||
|
@ -154,21 +170,22 @@ fun ActMain.handleOtherUri(uri: Uri): Boolean {
|
|||
return false
|
||||
}
|
||||
|
||||
private fun ActMain.handleCustomSchemaUri(uri: Uri) {
|
||||
private fun ActMain.handleCustomSchemaUri(uri: Uri) = launchAndShowError {
|
||||
val dataIdString = uri.getQueryParameter("db_id")
|
||||
if (dataIdString != null) {
|
||||
// subwaytooter://notification_click/?db_id=(db_id)
|
||||
handleNotificationClick(uri, dataIdString)
|
||||
} else {
|
||||
if (dataIdString == null) {
|
||||
// OAuth2 認証コールバック
|
||||
// subwaytooter://oauth(\d*)/?...
|
||||
handleOAuth2Callback(uri)
|
||||
} else {
|
||||
// subwaytooter://notification_click/?db_id=(db_id)
|
||||
handleNotificationClick(uri, dataIdString)
|
||||
}
|
||||
}
|
||||
|
||||
private fun ActMain.handleNotificationClick(uri: Uri, dataIdString: String) {
|
||||
try {
|
||||
val account = dataIdString.toLongOrNull()?.let { SavedAccount.loadAccount(this, it) }
|
||||
val account = dataIdString.toLongOrNull()
|
||||
?.let { daoSavedAccount.loadAccount(it) }
|
||||
if (account == null) {
|
||||
showToast(true, "handleNotificationClick: missing SavedAccount. id=$dataIdString")
|
||||
return
|
||||
|
@ -227,7 +244,7 @@ fun ActMain.afterAccountVerify(auth2Result: Auth2Result): Boolean = auth2Result.
|
|||
// 「アカウント追加のハズが既存アカウントで認証していた」
|
||||
// 「アクセストークン更新のハズが別アカウントで認証していた」
|
||||
// などを防止するため、full acctでアプリ内DBを検索
|
||||
when (val sa = SavedAccount.loadAccountByAcct(this@afterAccountVerify, newAcct.ascii)) {
|
||||
when (val sa = daoSavedAccount.loadAccountByAcct(newAcct)) {
|
||||
null -> afterAccountAdd(newAcct, auth2Result)
|
||||
else -> afterAccessTokenUpdate(auth2Result, sa)
|
||||
}
|
||||
|
@ -238,10 +255,10 @@ private fun ActMain.afterAccessTokenUpdate(
|
|||
sa: SavedAccount,
|
||||
): Boolean {
|
||||
// DBの情報を更新する
|
||||
sa.updateTokenInfo(auth2Result)
|
||||
authRepo.updateTokenInfo(sa, auth2Result)
|
||||
|
||||
// 各カラムの持つアカウント情報をリロードする
|
||||
reloadAccountSetting()
|
||||
reloadAccountSetting(daoSavedAccount.loadAccountList())
|
||||
|
||||
// 自動でリロードする
|
||||
appState.columnList
|
||||
|
@ -252,6 +269,7 @@ private fun ActMain.afterAccessTokenUpdate(
|
|||
PushSubscriptionHelper.clearLastCheck(sa)
|
||||
checkNotificationImmediateAll(this, onlySubscription = true)
|
||||
checkNotificationImmediate(this, sa.db_id)
|
||||
updatePushDistributer()
|
||||
|
||||
showToast(false, R.string.access_token_updated_for, sa.acct.pretty)
|
||||
return true
|
||||
|
@ -263,7 +281,7 @@ private fun ActMain.afterAccountAdd(
|
|||
): Boolean {
|
||||
val ta = auth2Result.tootAccount
|
||||
|
||||
val rowId = SavedAccount.insert(
|
||||
val rowId = daoSavedAccount.saveNew(
|
||||
acct = newAcct.ascii,
|
||||
host = auth2Result.apiHost.ascii,
|
||||
domain = auth2Result.apDomain.ascii,
|
||||
|
@ -271,7 +289,7 @@ private fun ActMain.afterAccountAdd(
|
|||
token = auth2Result.tokenJson,
|
||||
misskeyVersion = auth2Result.tootInstance.misskeyVersionMajor,
|
||||
)
|
||||
val account = SavedAccount.loadAccount(applicationContext, rowId)
|
||||
val account = daoSavedAccount.loadAccount(rowId)
|
||||
if (account == null) {
|
||||
showToast(false, "loadAccount failed.")
|
||||
return false
|
||||
|
@ -298,13 +316,13 @@ private fun ActMain.afterAccountAdd(
|
|||
}
|
||||
|
||||
if (bModified) {
|
||||
account.saveSetting()
|
||||
daoSavedAccount.saveSetting(account)
|
||||
}
|
||||
}
|
||||
|
||||
// 適当にカラムを追加する
|
||||
addColumn(false, defaultInsertPosition, account, ColumnType.HOME)
|
||||
if (SavedAccount.count == 1) {
|
||||
if (daoSavedAccount.isSingleAccount()) {
|
||||
addColumn(false, defaultInsertPosition, account, ColumnType.NOTIFICATIONS)
|
||||
addColumn(false, defaultInsertPosition, account, ColumnType.LOCAL)
|
||||
addColumn(false, defaultInsertPosition, account, ColumnType.FEDERATE)
|
||||
|
@ -313,6 +331,7 @@ private fun ActMain.afterAccountAdd(
|
|||
// 通知の更新が必要かもしれない
|
||||
checkNotificationImmediateAll(this, onlySubscription = true)
|
||||
checkNotificationImmediate(this, account.db_id)
|
||||
updatePushDistributer()
|
||||
showToast(false, R.string.account_confirmed)
|
||||
return true
|
||||
}
|
||||
|
@ -329,3 +348,79 @@ fun ActMain.handleSharedIntent(intent: Intent) {
|
|||
ai?.let { openActPostImpl(it.db_id, sharedIntent = intent) }
|
||||
}
|
||||
}
|
||||
|
||||
// アカウントを追加/更新したらappServerHashの取得をやりなおす
|
||||
fun ActMain.updatePushDistributer(){
|
||||
when {
|
||||
fcmHandler.noFcm && prefDevice.pushDistributor.isNullOrEmpty() -> {
|
||||
try {
|
||||
selectPushDistributor()
|
||||
// 選択したら
|
||||
} catch (_: CancellationException) {
|
||||
// 選択しなかった場合は購読の更新を行わない
|
||||
}
|
||||
}
|
||||
else -> PushWorker.enqueueRegisterEndpoint(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun AppCompatActivity.selectPushDistributor() {
|
||||
val context = this
|
||||
launchAndShowError {
|
||||
val prefDevice = prefDevice
|
||||
val lastDistributor = prefDevice.pushDistributor
|
||||
|
||||
fun String.appendChecked(checked: Boolean) = when (checked) {
|
||||
true -> "$this ✅"
|
||||
else -> this
|
||||
}
|
||||
|
||||
actionsDialog(getString(R.string.select_push_delivery_service)) {
|
||||
if (fcmHandler.hasFcm) {
|
||||
action(
|
||||
getString(R.string.firebase_cloud_messaging)
|
||||
.appendChecked(lastDistributor == PrefDevice.PUSH_DISTRIBUTOR_FCM)
|
||||
) {
|
||||
runInProgress(cancellable = false) { reporter ->
|
||||
withContext(AppDispatchers.DEFAULT) {
|
||||
pushRepo.switchDistributor(
|
||||
PrefDevice.PUSH_DISTRIBUTOR_FCM,
|
||||
reporter = reporter
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (packageName in UnifiedPush.getDistributors(
|
||||
context,
|
||||
features = ArrayList(listOf(UnifiedPush.FEATURE_BYTES_MESSAGE))
|
||||
)) {
|
||||
action(
|
||||
packageName.appendChecked(lastDistributor == packageName)
|
||||
) {
|
||||
runInProgress(cancellable = false) { reporter ->
|
||||
withContext(AppDispatchers.DEFAULT) {
|
||||
pushRepo.switchDistributor(
|
||||
packageName,
|
||||
reporter = reporter
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
action(
|
||||
getString(R.string.none)
|
||||
.appendChecked(lastDistributor == PrefDevice.PUSH_DISTRIBUTOR_NONE)
|
||||
) {
|
||||
runInProgress(cancellable = false) { reporter ->
|
||||
withContext(AppDispatchers.DEFAULT) {
|
||||
pushRepo.switchDistributor(
|
||||
PrefDevice.PUSH_DISTRIBUTOR_NONE,
|
||||
reporter = reporter
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,11 +28,11 @@ val ActMain.quickPostText: String
|
|||
fun ActMain.initUIQuickPost() {
|
||||
etQuickPost.typeface = ActMain.timelineFont
|
||||
|
||||
if (!PrefB.bpQuickPostBar.invoke(pref)) {
|
||||
if (!PrefB.bpQuickPostBar.value) {
|
||||
llQuickPostBar.visibility = View.GONE
|
||||
}
|
||||
|
||||
if (PrefB.bpDontUseActionButtonWithQuickPostBar(pref)) {
|
||||
if (PrefB.bpDontUseActionButtonWithQuickPostBar.value) {
|
||||
etQuickPost.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE
|
||||
etQuickPost.imeOptions = EditorInfo.IME_ACTION_NONE
|
||||
// 最後に指定する必要がある?
|
||||
|
@ -70,7 +70,7 @@ fun ActMain.initUIQuickPost() {
|
|||
|
||||
fun ActMain.showQuickPostVisibility() {
|
||||
btnQuickPostMenu.imageResource =
|
||||
when (val resId = getVisibilityIconId(false, quickPostVisibility)) {
|
||||
when (val resId = quickPostVisibility.getVisibilityIconId(false)) {
|
||||
R.drawable.ic_question -> R.drawable.ic_description
|
||||
else -> resId
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ fun ActMain.toggleQuickPostMenu() {
|
|||
|
||||
fun ActMain.performQuickPost(account: SavedAccount?) {
|
||||
if (account == null) {
|
||||
val a = if (tabletViews != null && !PrefB.bpQuickTootOmitAccountSelection(pref)) {
|
||||
val a = if (tabletViews != null && !PrefB.bpQuickTootOmitAccountSelection.value) {
|
||||
// タブレットモードでオプションが無効なら
|
||||
// 簡易投稿は常にアカウント選択する
|
||||
null
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package jp.juggler.subwaytooter.actmain
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Typeface
|
||||
import android.view.View
|
||||
|
@ -8,15 +9,15 @@ import android.widget.LinearLayout
|
|||
import jp.juggler.subwaytooter.ActMain
|
||||
import jp.juggler.subwaytooter.R
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatus
|
||||
import jp.juggler.subwaytooter.stylerBoostAlpha
|
||||
import jp.juggler.subwaytooter.itemviewholder.ItemViewHolder
|
||||
import jp.juggler.subwaytooter.pref.PrefB
|
||||
import jp.juggler.subwaytooter.pref.PrefF
|
||||
import jp.juggler.subwaytooter.pref.PrefI
|
||||
import jp.juggler.subwaytooter.pref.PrefS
|
||||
import jp.juggler.subwaytooter.pref.impl.StringPref
|
||||
import jp.juggler.subwaytooter.stylerRoundRatio
|
||||
import jp.juggler.subwaytooter.span.MyClickableSpan
|
||||
import jp.juggler.subwaytooter.stylerBoostAlpha
|
||||
import jp.juggler.subwaytooter.stylerRoundRatio
|
||||
import jp.juggler.subwaytooter.util.CustomShare
|
||||
import jp.juggler.subwaytooter.view.ListDivider
|
||||
import jp.juggler.util.data.clip
|
||||
|
@ -31,12 +32,12 @@ import kotlin.math.max
|
|||
|
||||
private val log = LogCategory("ActMainStyle")
|
||||
|
||||
private fun ActMain.dpToPx(dp: Float) =
|
||||
(dp * density + 0.5f).toInt()
|
||||
private fun Float.dpToPx(context: Context) =
|
||||
(this * context.resources.displayMetrics.density + 0.5f).toInt()
|
||||
|
||||
// initUIから呼ばれる
|
||||
fun ActMain.reloadFonts() {
|
||||
ActMain.timelineFont = PrefS.spTimelineFont(pref).notEmpty()?.let {
|
||||
fun reloadFonts() {
|
||||
ActMain.timelineFont = PrefS.spTimelineFont.value.notEmpty()?.let {
|
||||
try {
|
||||
Typeface.createFromFile(it)
|
||||
} catch (ex: Throwable) {
|
||||
|
@ -45,7 +46,7 @@ fun ActMain.reloadFonts() {
|
|||
}
|
||||
} ?: Typeface.DEFAULT
|
||||
|
||||
ActMain.timelineFontBold = PrefS.spTimelineFontBold(pref).notEmpty()?.let {
|
||||
ActMain.timelineFontBold = PrefS.spTimelineFontBold.value.notEmpty()?.let {
|
||||
try {
|
||||
Typeface.createFromFile(it)
|
||||
} catch (ex: Throwable) {
|
||||
|
@ -61,16 +62,14 @@ fun ActMain.reloadFonts() {
|
|||
}
|
||||
|
||||
private fun ActMain.parseIconSize(stringPref: StringPref, minDp: Float = 1f) =
|
||||
dpToPx(
|
||||
try {
|
||||
stringPref(pref)
|
||||
.toFloatOrNull()
|
||||
?.takeIf { it.isFinite() && it >= minDp }
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "parseIconSize failed.")
|
||||
null
|
||||
} ?: stringPref.defVal.toFloat()
|
||||
)
|
||||
(try {
|
||||
stringPref.value
|
||||
.toFloatOrNull()
|
||||
?.takeIf { it.isFinite() && it >= minDp }
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "parseIconSize failed.")
|
||||
null
|
||||
} ?: stringPref.defVal.toFloat()).dpToPx(this)
|
||||
|
||||
// initUIから呼ばれる
|
||||
fun ActMain.reloadIconSize() {
|
||||
|
@ -82,7 +81,7 @@ fun ActMain.reloadIconSize() {
|
|||
ActMain.stripIconSize = parseIconSize(PrefS.spStripIconSize)
|
||||
ActMain.screenBottomPadding = parseIconSize(PrefS.spScreenBottomPadding, minDp = 0f)
|
||||
|
||||
ActMain.eventFadeAlpha = PrefS.spEventTextAlpha()
|
||||
ActMain.eventFadeAlpha = PrefS.spEventTextAlpha.value
|
||||
.toFloatOrNull()
|
||||
?.takeIf { it.isFinite() }
|
||||
?.clip(0f, 1f)
|
||||
|
@ -90,10 +89,10 @@ fun ActMain.reloadIconSize() {
|
|||
}
|
||||
|
||||
// initUIから呼ばれる
|
||||
fun ActMain.reloadRoundRatio() {
|
||||
fun reloadRoundRatio() {
|
||||
val sizeDp = when {
|
||||
PrefB.bpDontRound(pref) -> 0f
|
||||
else -> PrefS.spRoundRatio(pref)
|
||||
PrefB.bpDontRound.value -> 0f
|
||||
else -> PrefS.spRoundRatio.value
|
||||
.toFloatOrNull()
|
||||
?.takeIf { it.isFinite() }
|
||||
?: 33f
|
||||
|
@ -102,8 +101,8 @@ fun ActMain.reloadRoundRatio() {
|
|||
}
|
||||
|
||||
// initUI から呼ばれる
|
||||
fun ActMain.reloadBoostAlpha() {
|
||||
stylerBoostAlpha = PrefS.spBoostAlpha(pref)
|
||||
fun reloadBoostAlpha() {
|
||||
stylerBoostAlpha = PrefS.spBoostAlpha.value
|
||||
.toIntOrNull()
|
||||
?.toFloat()
|
||||
?.let { (it + 0.5f) / 100f }
|
||||
|
@ -112,36 +111,35 @@ fun ActMain.reloadBoostAlpha() {
|
|||
}
|
||||
|
||||
fun ActMain.reloadMediaHeight() {
|
||||
appState.mediaThumbHeight = dpToPx(
|
||||
PrefS.spMediaThumbHeight(pref)
|
||||
.toFloatOrNull()
|
||||
?.takeIf { it >= 32f }
|
||||
?: 64f
|
||||
)
|
||||
appState.mediaThumbHeight = (
|
||||
PrefS.spMediaThumbHeight.value
|
||||
.toFloatOrNull()
|
||||
?.takeIf { it >= 32f }
|
||||
?: 64f
|
||||
).dpToPx(this)
|
||||
}
|
||||
|
||||
private fun Float.clipFontSize(): Float =
|
||||
if (isNaN()) this else max(1f, this)
|
||||
|
||||
fun ActMain.reloadTextSize() {
|
||||
timelineFontSizeSp = PrefF.fpTimelineFontSize.invoke(pref).clipFontSize()
|
||||
acctFontSizeSp = PrefF.fpAcctFontSize(pref).clipFontSize()
|
||||
notificationTlFontSizeSp = PrefF.fpNotificationTlFontSize(pref).clipFontSize()
|
||||
headerTextSizeSp = PrefF.fpHeaderTextSize(pref).clipFontSize()
|
||||
val fv = PrefS.spTimelineSpacing(pref).toFloatOrNull()
|
||||
timelineFontSizeSp = PrefF.fpTimelineFontSize.value.clipFontSize()
|
||||
acctFontSizeSp = PrefF.fpAcctFontSize.value.clipFontSize()
|
||||
notificationTlFontSizeSp = PrefF.fpNotificationTlFontSize.value.clipFontSize()
|
||||
headerTextSizeSp = PrefF.fpHeaderTextSize.value.clipFontSize()
|
||||
val fv = PrefS.spTimelineSpacing.value.toFloatOrNull()
|
||||
timelineSpacing = if (fv != null && fv.isFinite() && fv != 0f) fv else null
|
||||
}
|
||||
|
||||
fun ActMain.loadColumnMin() =
|
||||
dpToPx(
|
||||
PrefS.spColumnWidth(pref)
|
||||
.toFloatOrNull()
|
||||
?.takeIf { it.isFinite() && it >= 100f }
|
||||
?: ActMain.COLUMN_WIDTH_MIN_DP.toFloat()
|
||||
)
|
||||
(PrefS.spColumnWidth.value
|
||||
.toFloatOrNull()
|
||||
?.takeIf { it.isFinite() && it >= 100f }
|
||||
?: ActMain.COLUMN_WIDTH_MIN_DP.toFloat()
|
||||
).dpToPx(this)
|
||||
|
||||
fun ActMain.justifyWindowContentPortrait() {
|
||||
when (PrefI.ipJustifyWindowContentPortrait(pref)) {
|
||||
when (PrefI.ipJustifyWindowContentPortrait.value) {
|
||||
PrefI.JWCP_START -> {
|
||||
val iconW = (ActMain.stripIconSize * 1.5f + 0.5f).toInt()
|
||||
val padding = resources.displayMetrics.widthPixels / 2 - iconW
|
||||
|
@ -161,7 +159,7 @@ fun ActMain.justifyWindowContentPortrait() {
|
|||
|
||||
PrefI.JWCP_END -> {
|
||||
val iconW = (ActMain.stripIconSize * 1.5f + 0.5f).toInt()
|
||||
val borderWidth = dpToPx(1f)
|
||||
val borderWidth = 1f.dpToPx(this)
|
||||
val padding = resources.displayMetrics.widthPixels / 2 - iconW - borderWidth
|
||||
|
||||
fun ViewGroup.addViewAfterFirst(v: View) = addView(v, 1)
|
||||
|
@ -182,10 +180,10 @@ fun ActMain.justifyWindowContentPortrait() {
|
|||
//////////////////////////////////////////////////////
|
||||
|
||||
// onStart時に呼ばれる
|
||||
fun ActMain.reloadTimeZone() {
|
||||
fun reloadTimeZone() {
|
||||
try {
|
||||
var tz = TimeZone.getDefault()
|
||||
val tzId = PrefS.spTimeZone(pref)
|
||||
val tzId = PrefS.spTimeZone.value
|
||||
if (tzId.isNotEmpty()) {
|
||||
tz = TimeZone.getTimeZone(tzId)
|
||||
}
|
||||
|
@ -199,25 +197,25 @@ fun ActMain.reloadTimeZone() {
|
|||
// onStart時に呼ばれる
|
||||
// カラーカスタマイズを読み直す
|
||||
fun ActMain.reloadColors() {
|
||||
ListDivider.color = PrefI.ipListDividerColor(pref)
|
||||
TabletColumnDivider.color = PrefI.ipListDividerColor(pref)
|
||||
ItemViewHolder.toot_color_unlisted = PrefI.ipTootColorUnlisted(pref)
|
||||
ItemViewHolder.toot_color_follower = PrefI.ipTootColorFollower(pref)
|
||||
ItemViewHolder.toot_color_direct_user = PrefI.ipTootColorDirectUser(pref)
|
||||
ItemViewHolder.toot_color_direct_me = PrefI.ipTootColorDirectMe(pref)
|
||||
MyClickableSpan.showLinkUnderline = PrefB.bpShowLinkUnderline(pref)
|
||||
MyClickableSpan.defaultLinkColor = PrefI.ipLinkColor(pref).notZero()
|
||||
ListDivider.color = PrefI.ipListDividerColor.value
|
||||
TabletColumnDivider.color = PrefI.ipListDividerColor.value
|
||||
ItemViewHolder.toot_color_unlisted = PrefI.ipTootColorUnlisted.value
|
||||
ItemViewHolder.toot_color_follower = PrefI.ipTootColorFollower.value
|
||||
ItemViewHolder.toot_color_direct_user = PrefI.ipTootColorDirectUser.value
|
||||
ItemViewHolder.toot_color_direct_me = PrefI.ipTootColorDirectMe.value
|
||||
MyClickableSpan.showLinkUnderline = PrefB.bpShowLinkUnderline.value
|
||||
MyClickableSpan.defaultLinkColor = PrefI.ipLinkColor.value.notZero()
|
||||
?: attrColor(R.attr.colorLink)
|
||||
|
||||
CustomShare.reloadCache(this)
|
||||
}
|
||||
|
||||
fun ActMain.showFooterColor() {
|
||||
val footerButtonBgColor = PrefI.ipFooterButtonBgColor(pref)
|
||||
val footerButtonFgColor = PrefI.ipFooterButtonFgColor(pref)
|
||||
val footerTabBgColor = PrefI.ipFooterTabBgColor(pref)
|
||||
val footerTabDividerColor = PrefI.ipFooterTabDividerColor(pref)
|
||||
val footerTabIndicatorColor = PrefI.ipFooterTabIndicatorColor(pref)
|
||||
val footerButtonBgColor = PrefI.ipFooterButtonBgColor.value
|
||||
val footerButtonFgColor = PrefI.ipFooterButtonFgColor.value
|
||||
val footerTabBgColor = PrefI.ipFooterTabBgColor.value
|
||||
val footerTabDividerColor = PrefI.ipFooterTabDividerColor.value
|
||||
val footerTabIndicatorColor = PrefI.ipFooterTabIndicatorColor.value
|
||||
|
||||
val colorColumnStripBackground = footerTabBgColor.notZero()
|
||||
?: attrColor(R.attr.colorColumnStripBackground)
|
||||
|
|
|
@ -3,18 +3,23 @@ package jp.juggler.subwaytooter.actmain
|
|||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import jp.juggler.subwaytooter.ActMain
|
||||
import jp.juggler.subwaytooter.pref.PrefB
|
||||
import jp.juggler.subwaytooter.R
|
||||
import jp.juggler.subwaytooter.pref.PrefB
|
||||
import jp.juggler.subwaytooter.view.MyViewPager
|
||||
|
||||
// スマホモードならラムダを実行する。タブレットモードならnullを返す
|
||||
inline fun <R : Any?> ActMain.phoneOnly(code: (ActMainPhoneViews) -> R): R? = phoneViews?.let { code(it) }
|
||||
inline fun <R : Any?> ActMain.phoneOnly(code: (ActMainPhoneViews) -> R): R? =
|
||||
phoneViews?.let { code(it) }
|
||||
|
||||
// タブレットモードならラムダを実行する。スマホモードならnullを返す
|
||||
inline fun <R : Any?> ActMain.tabOnly(code: (ActMainTabletViews) -> R): R? = tabletViews?.let { code(it) }
|
||||
inline fun <R : Any?> ActMain.tabOnly(code: (ActMainTabletViews) -> R): R? =
|
||||
tabletViews?.let { code(it) }
|
||||
|
||||
// スマホモードとタブレットモードでコードを切り替える
|
||||
inline fun <R : Any?> ActMain.phoneTab(codePhone: (ActMainPhoneViews) -> R, codeTablet: (ActMainTabletViews) -> R): R {
|
||||
inline fun <R : Any?> ActMain.phoneTab(
|
||||
codePhone: (ActMainPhoneViews) -> R,
|
||||
codeTablet: (ActMainTabletViews) -> R,
|
||||
): R {
|
||||
phoneViews?.let { return codePhone(it) }
|
||||
tabletViews?.let { return codeTablet(it) }
|
||||
error("missing phoneViews/tabletViews")
|
||||
|
@ -27,7 +32,7 @@ fun ActMain.initPhoneTablet() {
|
|||
val tmpTabletPager: RecyclerView = findViewById(R.id.rvPager)
|
||||
|
||||
// スマホモードとタブレットモードの切り替え
|
||||
if (PrefB.bpDisableTabletMode(pref) || sw < columnWMin * 2) {
|
||||
if (PrefB.bpDisableTabletMode.value || sw < columnWMin * 2) {
|
||||
tmpTabletPager.visibility = View.GONE
|
||||
phoneViews = ActMainPhoneViews(this).apply {
|
||||
initUI(tmpPhonePager)
|
||||
|
|
|
@ -92,7 +92,7 @@ class ActMainTabletViews(val actMain: ActMain) {
|
|||
// if( animator is DefaultItemAnimator){
|
||||
// animator.supportsChangeAnimations = false
|
||||
// }
|
||||
if (PrefB.bpTabletSnap()) {
|
||||
if (PrefB.bpTabletSnap.value) {
|
||||
GravitySnapHelper(Gravity.START).attachToRecyclerView(this.tabletPager)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,11 +28,12 @@ import jp.juggler.subwaytooter.dialog.pickAccount
|
|||
import jp.juggler.subwaytooter.pref.PrefB
|
||||
import jp.juggler.subwaytooter.pref.PrefS
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.accountListCanSeeMyReactions
|
||||
import jp.juggler.subwaytooter.util.VersionString
|
||||
import jp.juggler.subwaytooter.util.openBrowser
|
||||
import jp.juggler.util.coroutine.AppDispatchers
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.coroutine.launchIO
|
||||
import jp.juggler.util.coroutine.launchMain
|
||||
import jp.juggler.util.data.JsonObject
|
||||
import jp.juggler.util.data.decodeJsonObject
|
||||
import jp.juggler.util.data.decodeUTF8
|
||||
|
@ -120,7 +121,7 @@ class SideMenuAdapter(
|
|||
)
|
||||
)
|
||||
val newRelease = releaseInfo?.jsonObject(
|
||||
if (PrefB.bpCheckBetaVersion()) "beta" else "stable"
|
||||
if (PrefB.bpCheckBetaVersion.value) "beta" else "stable"
|
||||
)
|
||||
|
||||
// 使用中のアプリバージョンより新しいリリースがある?
|
||||
|
@ -327,7 +328,7 @@ class SideMenuAdapter(
|
|||
timeline(defaultInsertPosition, ColumnType.BOOKMARKS)
|
||||
},
|
||||
Item(icon = R.drawable.ic_face, title = R.string.reactioned_posts) {
|
||||
launchMain {
|
||||
launchAndShowError {
|
||||
accountListCanSeeMyReactions()?.let { list ->
|
||||
if (list.isEmpty()) {
|
||||
showToast(false, R.string.not_available_for_current_accounts)
|
||||
|
@ -518,7 +519,7 @@ class SideMenuAdapter(
|
|||
private fun getTimeZoneString(context: Context): String {
|
||||
try {
|
||||
var tz = TimeZone.getDefault()
|
||||
val tzId = PrefS.spTimeZone()
|
||||
val tzId = PrefS.spTimeZone.value
|
||||
if (tzId.isBlank()) {
|
||||
return tz.displayName + "(" + context.getString(R.string.device_timezone) + ")"
|
||||
}
|
||||
|
|
|
@ -5,8 +5,10 @@ import jp.juggler.subwaytooter.App1
|
|||
import jp.juggler.subwaytooter.R
|
||||
import jp.juggler.subwaytooter.api.entity.TootVisibility
|
||||
import jp.juggler.subwaytooter.dialog.pickAccount
|
||||
import jp.juggler.subwaytooter.table.AcctColor
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.daoAcctColor
|
||||
import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||
import jp.juggler.subwaytooter.table.sortedByNickname
|
||||
import jp.juggler.util.coroutine.launchMain
|
||||
import jp.juggler.util.data.notZero
|
||||
import jp.juggler.util.log.LogCategory
|
||||
|
@ -33,17 +35,17 @@ fun ActPost.selectAccount(a: SavedAccount?) {
|
|||
|
||||
views.spLanguage.setSelection(max(0, languages.indexOfFirst { it.first == a.lang }))
|
||||
|
||||
val ac = AcctColor.load(a)
|
||||
val ac = daoAcctColor.load(a)
|
||||
views.btnAccount.text = ac.nickname
|
||||
|
||||
if (AcctColor.hasColorBackground(ac)) {
|
||||
if (daoAcctColor.hasColorBackground(ac)) {
|
||||
views.btnAccount.background =
|
||||
getAdaptiveRippleDrawableRound(this, ac.color_bg, ac.color_fg)
|
||||
getAdaptiveRippleDrawableRound(this, ac.colorBg, ac.colorFg)
|
||||
} else {
|
||||
views.btnAccount.setBackgroundResource(R.drawable.btn_bg_transparent_round6dp)
|
||||
}
|
||||
|
||||
views.btnAccount.textColor = ac.color_fg.notZero()
|
||||
views.btnAccount.textColor = ac.colorFg.notZero()
|
||||
?: attrColor(android.R.attr.textColorPrimary)
|
||||
}
|
||||
updateTextCount()
|
||||
|
@ -75,8 +77,7 @@ fun ActPost.performAccountChooser() {
|
|||
if (!canSwitchAccount()) return
|
||||
|
||||
if (isMultiWindowPost) {
|
||||
accountList = SavedAccount.loadAccountList(this)
|
||||
SavedAccount.sort(accountList)
|
||||
accountList = daoSavedAccount.loadAccountList().sortedByNickname()
|
||||
}
|
||||
|
||||
launchMain {
|
||||
|
|
|
@ -12,13 +12,14 @@ import jp.juggler.subwaytooter.api.entity.*
|
|||
import jp.juggler.subwaytooter.api.runApiTask
|
||||
import jp.juggler.subwaytooter.calcIconRound
|
||||
import jp.juggler.subwaytooter.defaultColorIcon
|
||||
import jp.juggler.subwaytooter.dialog.ActionsDialog
|
||||
import jp.juggler.subwaytooter.dialog.DlgFocusPoint
|
||||
import jp.juggler.subwaytooter.dialog.DlgTextInput
|
||||
import jp.juggler.subwaytooter.dialog.actionsDialog
|
||||
import jp.juggler.subwaytooter.pref.PrefB
|
||||
import jp.juggler.subwaytooter.util.AttachmentRequest
|
||||
import jp.juggler.subwaytooter.util.PostAttachment
|
||||
import jp.juggler.subwaytooter.view.MyNetworkImageView
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.coroutine.launchMain
|
||||
import jp.juggler.util.data.*
|
||||
import jp.juggler.util.log.LogCategory
|
||||
|
@ -169,7 +170,7 @@ fun ActPost.onPostAttachmentCompleteImpl(pa: PostAttachment) {
|
|||
log.i("onPostAttachmentComplete: upload complete.")
|
||||
|
||||
// 投稿欄の末尾に追記する
|
||||
if (PrefB.bpAppendAttachmentUrlToContent.invoke(pref)) {
|
||||
if (PrefB.bpAppendAttachmentUrlToContent.value) {
|
||||
appendArrachmentUrl(a)
|
||||
}
|
||||
}
|
||||
|
@ -215,35 +216,33 @@ fun ActPost.performAttachmentClick(idx: Int) {
|
|||
showToast(false, ex.withCaption("can't get attachment item[$idx]."))
|
||||
return
|
||||
}
|
||||
|
||||
val a = ActionsDialog()
|
||||
.addAction(getString(R.string.set_description)) {
|
||||
editAttachmentDescription(pa)
|
||||
}
|
||||
|
||||
if (pa.attachment?.canFocus == true) {
|
||||
a.addAction(getString(R.string.set_focus_point)) {
|
||||
openFocusPoint(pa)
|
||||
}
|
||||
}
|
||||
if (account?.isMastodon == true) {
|
||||
when (pa.attachment?.type) {
|
||||
TootAttachmentType.Audio,
|
||||
TootAttachmentType.GIFV,
|
||||
TootAttachmentType.Video,
|
||||
-> a.addAction(getString(R.string.custom_thumbnail)) {
|
||||
attachmentPicker.openCustomThumbnail(pa)
|
||||
launchAndShowError {
|
||||
actionsDialog(getString(R.string.media_attachment)) {
|
||||
action(getString(R.string.set_description)) {
|
||||
editAttachmentDescription(pa)
|
||||
}
|
||||
if (pa.attachment?.canFocus == true) {
|
||||
action(getString(R.string.set_focus_point)) {
|
||||
openFocusPoint(pa)
|
||||
}
|
||||
}
|
||||
if (account?.isMastodon == true) {
|
||||
when (pa.attachment?.type) {
|
||||
TootAttachmentType.Audio,
|
||||
TootAttachmentType.GIFV,
|
||||
TootAttachmentType.Video,
|
||||
-> action(getString(R.string.custom_thumbnail)) {
|
||||
attachmentPicker.openCustomThumbnail(pa)
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
action(getString(R.string.delete)) {
|
||||
deleteAttachment(pa)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a.addAction(getString(R.string.delete)) {
|
||||
deleteAttachment(pa)
|
||||
}
|
||||
|
||||
a.show(this, title = getString(R.string.media_attachment))
|
||||
}
|
||||
|
||||
fun ActPost.deleteAttachment(pa: PostAttachment) {
|
||||
|
|
|
@ -9,8 +9,9 @@ import jp.juggler.subwaytooter.api.TootApiClient
|
|||
import jp.juggler.subwaytooter.api.TootParser
|
||||
import jp.juggler.subwaytooter.api.entity.*
|
||||
import jp.juggler.subwaytooter.dialog.DlgDraftPicker
|
||||
import jp.juggler.subwaytooter.table.PostDraft
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.daoPostDraft
|
||||
import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||
import jp.juggler.subwaytooter.util.DecodeOptions
|
||||
import jp.juggler.subwaytooter.util.PostAttachment
|
||||
import jp.juggler.util.coroutine.launchProgress
|
||||
|
@ -129,7 +130,7 @@ fun ActPost.saveDraft() {
|
|||
states.visibility?.id?.toString()?.let { json.put(DRAFT_VISIBILITY, it) }
|
||||
states.inReplyToId?.putTo(json, DRAFT_REPLY_ID)
|
||||
|
||||
PostDraft.save(System.currentTimeMillis(), json)
|
||||
daoPostDraft.save(System.currentTimeMillis(), json)
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "saveDraft failed.")
|
||||
}
|
||||
|
@ -152,7 +153,7 @@ fun ActPost.restoreDraft(draft: JsonObject) {
|
|||
draft.jsonArray(DRAFT_ATTACHMENT_LIST)?.objectList()?.toMutableList()
|
||||
|
||||
val accountDbId = draft.long(DRAFT_ACCOUNT_DB_ID) ?: -1L
|
||||
val account = SavedAccount.loadAccount(this@restoreDraft, accountDbId)
|
||||
val account = daoSavedAccount.loadAccount(accountDbId)
|
||||
if (account == null) {
|
||||
listWarning.add(getString(R.string.account_in_draft_is_lost))
|
||||
try {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package jp.juggler.subwaytooter.actpost
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import jp.juggler.subwaytooter.ActMain
|
||||
import jp.juggler.subwaytooter.ActPost
|
||||
|
@ -11,16 +10,20 @@ import jp.juggler.subwaytooter.actmain.onCompleteActPost
|
|||
import jp.juggler.subwaytooter.api.entity.TootPollsType
|
||||
import jp.juggler.subwaytooter.api.entity.TootVisibility
|
||||
import jp.juggler.subwaytooter.api.entity.unknownHostAndDomain
|
||||
import jp.juggler.subwaytooter.dialog.ActionsDialog
|
||||
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
||||
import jp.juggler.subwaytooter.dialog.actionsDialog
|
||||
import jp.juggler.subwaytooter.pref.PrefB
|
||||
import jp.juggler.subwaytooter.table.PostDraft
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.daoPostDraft
|
||||
import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||
import jp.juggler.subwaytooter.table.sortedByNickname
|
||||
import jp.juggler.subwaytooter.util.DecodeOptions
|
||||
import jp.juggler.subwaytooter.util.PostAttachment
|
||||
import jp.juggler.subwaytooter.util.PostImpl
|
||||
import jp.juggler.subwaytooter.util.PostResult
|
||||
import jp.juggler.util.*
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.coroutine.launchMain
|
||||
import jp.juggler.util.data.CharacterGroup
|
||||
import jp.juggler.util.log.LogCategory
|
||||
import jp.juggler.util.log.showToast
|
||||
|
@ -104,27 +107,27 @@ fun ActPost.hasContent(): Boolean {
|
|||
}
|
||||
|
||||
fun ActPost.resetText() {
|
||||
isPostComplete = false
|
||||
launchMain {
|
||||
isPostComplete = false
|
||||
|
||||
resetReply()
|
||||
resetReply()
|
||||
|
||||
resetMushroom()
|
||||
states.redraftStatusId = null
|
||||
states.editStatusId = null
|
||||
states.timeSchedule = 0L
|
||||
attachmentPicker.reset()
|
||||
scheduledStatus = null
|
||||
attachmentList.clear()
|
||||
views.cbQuote.isChecked = false
|
||||
views.etContent.setText("")
|
||||
views.spPollType.setSelection(0, false)
|
||||
etChoices.forEach { it.setText("") }
|
||||
accountList = SavedAccount.loadAccountList(this)
|
||||
SavedAccount.sort(accountList)
|
||||
if (accountList.isEmpty()) {
|
||||
showToast(true, R.string.please_add_account)
|
||||
finish()
|
||||
return
|
||||
resetMushroom()
|
||||
states.redraftStatusId = null
|
||||
states.editStatusId = null
|
||||
states.timeSchedule = 0L
|
||||
attachmentPicker.reset()
|
||||
scheduledStatus = null
|
||||
attachmentList.clear()
|
||||
views.cbQuote.isChecked = false
|
||||
views.etContent.setText("")
|
||||
views.spPollType.setSelection(0, false)
|
||||
etChoices.forEach { it.setText("") }
|
||||
accountList = daoSavedAccount.loadAccountList().sortedByNickname()
|
||||
if (accountList.isEmpty()) {
|
||||
showToast(true, R.string.please_add_account)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -148,28 +151,18 @@ fun ActPost.afterUpdateText() {
|
|||
}
|
||||
|
||||
// 初期化時と投稿完了時とリセット確認後に呼ばれる
|
||||
fun ActPost.updateText(
|
||||
suspend fun ActPost.updateText(
|
||||
intent: Intent,
|
||||
confirmed: Boolean = false,
|
||||
saveDraft: Boolean = true,
|
||||
resetAccount: Boolean = true,
|
||||
) {
|
||||
if (!canSwitchAccount()) return
|
||||
|
||||
if (!confirmed && hasContent()) {
|
||||
AlertDialog.Builder(this)
|
||||
.setMessage("編集中のテキストや文脈を下書きに退避して、新しい投稿を編集しますか? ")
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
updateText(intent, confirmed = true)
|
||||
}
|
||||
.setCancelable(true)
|
||||
.show()
|
||||
return
|
||||
if (saveDraft && hasContent()) {
|
||||
confirm(R.string.post_reset_confirm)
|
||||
saveDraft()
|
||||
}
|
||||
|
||||
if (saveDraft) saveDraft()
|
||||
|
||||
resetText()
|
||||
|
||||
// Android 9 から、明示的にフォーカスを当てる必要がある
|
||||
|
@ -262,7 +255,7 @@ fun ActPost.initializeFromSharedIntent(sharedIntent: Intent) {
|
|||
else -> false
|
||||
}
|
||||
|
||||
if (!hasUri || !PrefB.bpIgnoreTextInSharedMedia(pref)) {
|
||||
if (!hasUri || !PrefB.bpIgnoreTextInSharedMedia.value) {
|
||||
appendContentText(sharedIntent)
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
|
@ -271,33 +264,33 @@ fun ActPost.initializeFromSharedIntent(sharedIntent: Intent) {
|
|||
}
|
||||
|
||||
fun ActPost.performMore() {
|
||||
val dialog = ActionsDialog()
|
||||
launchAndShowError {
|
||||
actionsDialog {
|
||||
action(getString(R.string.open_picker_emoji)) {
|
||||
completionHelper.openEmojiPickerFromMore()
|
||||
}
|
||||
|
||||
dialog.addAction(getString(R.string.open_picker_emoji)) {
|
||||
completionHelper.openEmojiPickerFromMore()
|
||||
action(getString(R.string.clear_text)) {
|
||||
views.etContent.setText("")
|
||||
views.etContentWarning.setText("")
|
||||
}
|
||||
|
||||
action(getString(R.string.clear_text_and_media)) {
|
||||
views.etContent.setText("")
|
||||
views.etContentWarning.setText("")
|
||||
attachmentList.clear()
|
||||
showMediaAttachment()
|
||||
}
|
||||
|
||||
if (daoPostDraft.hasDraft()) action(getString(R.string.restore_draft)) {
|
||||
openDraftPicker()
|
||||
}
|
||||
|
||||
action(getString(R.string.recommended_plugin)) {
|
||||
showRecommendedPlugin(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dialog.addAction(getString(R.string.clear_text)) {
|
||||
views.etContent.setText("")
|
||||
views.etContentWarning.setText("")
|
||||
}
|
||||
|
||||
dialog.addAction(getString(R.string.clear_text_and_media)) {
|
||||
views.etContent.setText("")
|
||||
views.etContentWarning.setText("")
|
||||
attachmentList.clear()
|
||||
showMediaAttachment()
|
||||
}
|
||||
|
||||
if (PostDraft.hasDraft()) dialog.addAction(getString(R.string.restore_draft)) {
|
||||
openDraftPicker()
|
||||
}
|
||||
|
||||
dialog.addAction(getString(R.string.recommended_plugin)) {
|
||||
showRecommendedPlugin(null)
|
||||
}
|
||||
|
||||
dialog.show(this, null)
|
||||
}
|
||||
|
||||
fun ActPost.performPost() {
|
||||
|
@ -366,7 +359,7 @@ fun ActPost.performPost() {
|
|||
|
||||
if (isMultiWindowPost) {
|
||||
resetText()
|
||||
updateText(Intent(), confirmed = true, saveDraft = false, resetAccount = false)
|
||||
updateText(Intent(), saveDraft = false, resetAccount = false)
|
||||
afterUpdateText()
|
||||
} else {
|
||||
// ActMainの復元が必要な場合に備えてintentのdataでも渡す
|
||||
|
@ -382,7 +375,7 @@ fun ActPost.performPost() {
|
|||
|
||||
if (isMultiWindowPost) {
|
||||
resetText()
|
||||
updateText(Intent(), confirmed = true, saveDraft = false, resetAccount = false)
|
||||
updateText(Intent(), saveDraft = false, resetAccount = false)
|
||||
afterUpdateText()
|
||||
ActMain.refActMain?.get()?.onCompleteActPost(data)
|
||||
} else {
|
||||
|
|
|
@ -10,10 +10,8 @@ import jp.juggler.subwaytooter.getVisibilityCaption
|
|||
import jp.juggler.subwaytooter.getVisibilityIconId
|
||||
|
||||
fun ActPost.showVisibility() {
|
||||
val iconId = getVisibilityIconId(
|
||||
account?.isMisskey == true,
|
||||
states.visibility ?: TootVisibility.Public
|
||||
)
|
||||
val iconId = (states.visibility ?: TootVisibility.Public)
|
||||
.getVisibilityIconId(account?.isMisskey == true)
|
||||
views.btnVisibility.setImageResource(iconId)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package jp.juggler.subwaytooter.actpost
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Handler
|
||||
import android.text.*
|
||||
import android.text.style.ForegroundColorSpan
|
||||
|
@ -9,21 +8,20 @@ import androidx.appcompat.app.AppCompatActivity
|
|||
import jp.juggler.subwaytooter.App1
|
||||
import jp.juggler.subwaytooter.R
|
||||
import jp.juggler.subwaytooter.api.entity.TootTag
|
||||
import jp.juggler.subwaytooter.dialog.ActionsDialog
|
||||
import jp.juggler.subwaytooter.dialog.actionsDialog
|
||||
import jp.juggler.subwaytooter.dialog.launchEmojiPicker
|
||||
import jp.juggler.subwaytooter.emoji.CustomEmoji
|
||||
import jp.juggler.subwaytooter.emoji.EmojiBase
|
||||
import jp.juggler.subwaytooter.emoji.UnicodeEmoji
|
||||
import jp.juggler.subwaytooter.pref.PrefB
|
||||
import jp.juggler.subwaytooter.span.NetworkEmojiSpan
|
||||
import jp.juggler.subwaytooter.table.AcctSet
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.TagSet
|
||||
import jp.juggler.subwaytooter.table.*
|
||||
import jp.juggler.subwaytooter.util.DecodeOptions
|
||||
import jp.juggler.subwaytooter.util.EmojiDecoder
|
||||
import jp.juggler.subwaytooter.util.PopupAutoCompleteAcct
|
||||
import jp.juggler.subwaytooter.view.MyEditText
|
||||
import jp.juggler.util.*
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.data.asciiRegex
|
||||
import jp.juggler.util.log.LogCategory
|
||||
import jp.juggler.util.ui.attrColor
|
||||
|
@ -33,7 +31,6 @@ import kotlin.math.min
|
|||
// 入力補完機能
|
||||
class CompletionHelper(
|
||||
private val activity: AppCompatActivity,
|
||||
private val pref: SharedPreferences,
|
||||
private val handler: Handler,
|
||||
) {
|
||||
companion object {
|
||||
|
@ -161,7 +158,7 @@ class CompletionHelper(
|
|||
|
||||
val limit = 100
|
||||
val s = src.substring(start, end)
|
||||
val acctList = AcctSet.searchPrefix(s, limit)
|
||||
val acctList = daoAcctSet.searchPrefix(s, limit)
|
||||
log.d("search for $s, result=${acctList.size}")
|
||||
if (acctList.isEmpty()) {
|
||||
closeAcctPopup()
|
||||
|
@ -187,7 +184,7 @@ class CompletionHelper(
|
|||
|
||||
val limit = 100
|
||||
val s = src.substring(lastSharp + 1, end)
|
||||
val tagList = TagSet.searchPrefix(s, limit)
|
||||
val tagList = daoTagHistory.searchPrefix(s, limit)
|
||||
log.d("search for $s, result=${tagList.size}")
|
||||
if (tagList.isEmpty()) {
|
||||
closeAcctPopup()
|
||||
|
@ -448,7 +445,7 @@ class CompletionHelper(
|
|||
launchEmojiPicker(
|
||||
activity,
|
||||
accessInfo,
|
||||
closeOnSelected = PrefB.bpEmojiPickerCloseOnSelected(pref)
|
||||
closeOnSelected = PrefB.bpEmojiPickerCloseOnSelected.value
|
||||
) { emoji, bInstanceHasCustomEmoji ->
|
||||
val et = this@CompletionHelper.et ?: return@launchEmojiPicker
|
||||
|
||||
|
@ -482,7 +479,7 @@ class CompletionHelper(
|
|||
launchEmojiPicker(
|
||||
activity,
|
||||
accessInfo,
|
||||
closeOnSelected = PrefB.bpEmojiPickerCloseOnSelected(pref)
|
||||
closeOnSelected = PrefB.bpEmojiPickerCloseOnSelected.value
|
||||
) { emoji, bInstanceHasCustomEmoji ->
|
||||
val et = this@CompletionHelper.et ?: return@launchEmojiPicker
|
||||
|
||||
|
@ -514,49 +511,50 @@ class CompletionHelper(
|
|||
}
|
||||
|
||||
fun openFeaturedTagList(list: List<TootTag>?) {
|
||||
val ad = ActionsDialog()
|
||||
list?.forEach { tag ->
|
||||
ad.addAction("#${tag.name}") {
|
||||
val et = this.et ?: return@addAction
|
||||
val et = this@CompletionHelper.et ?: return
|
||||
activity.run {
|
||||
launchAndShowError {
|
||||
actionsDialog(getString(R.string.featured_hashtags)) {
|
||||
list?.forEach { tag ->
|
||||
action("#${tag.name}") {
|
||||
val src = et.text ?: ""
|
||||
val srcLength = src.length
|
||||
val start = min(srcLength, et.selectionStart)
|
||||
val end = min(srcLength, et.selectionEnd)
|
||||
|
||||
val src = et.text ?: ""
|
||||
val srcLength = src.length
|
||||
val start = min(srcLength, et.selectionStart)
|
||||
val end = min(srcLength, et.selectionEnd)
|
||||
val sb = SpannableStringBuilder()
|
||||
.append(src.subSequence(0, start))
|
||||
.appendHashTag(tag.name)
|
||||
val newSelection = sb.length
|
||||
if (end < srcLength) sb.append(src.subSequence(end, srcLength))
|
||||
|
||||
val sb = SpannableStringBuilder()
|
||||
.append(src.subSequence(0, start))
|
||||
.appendHashTag(tag.name)
|
||||
val newSelection = sb.length
|
||||
if (end < srcLength) sb.append(src.subSequence(end, srcLength))
|
||||
et.text = sb
|
||||
et.setSelection(newSelection)
|
||||
|
||||
et.text = sb
|
||||
et.setSelection(newSelection)
|
||||
procTextChanged.run()
|
||||
}
|
||||
}
|
||||
action(activity.getString(R.string.input_sharp_itself)) {
|
||||
val src = et.text ?: ""
|
||||
val srcLength = src.length
|
||||
val start = min(srcLength, et.selectionStart)
|
||||
val end = min(srcLength, et.selectionEnd)
|
||||
|
||||
procTextChanged.run()
|
||||
val sb = SpannableStringBuilder()
|
||||
sb.append(src.subSequence(0, start))
|
||||
if (!EmojiDecoder.canStartHashtag(sb, sb.length)) sb.append(' ')
|
||||
sb.append('#')
|
||||
|
||||
val newSelection = sb.length
|
||||
if (end < srcLength) sb.append(src.subSequence(end, srcLength))
|
||||
et.text = sb
|
||||
et.setSelection(newSelection)
|
||||
|
||||
procTextChanged.run()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ad.addAction(activity.getString(R.string.input_sharp_itself)) {
|
||||
val et = this.et ?: return@addAction
|
||||
|
||||
val src = et.text ?: ""
|
||||
val srcLength = src.length
|
||||
val start = min(srcLength, et.selectionStart)
|
||||
val end = min(srcLength, et.selectionEnd)
|
||||
|
||||
val sb = SpannableStringBuilder()
|
||||
sb.append(src.subSequence(0, start))
|
||||
if (!EmojiDecoder.canStartHashtag(sb, sb.length)) sb.append(' ')
|
||||
sb.append('#')
|
||||
|
||||
val newSelection = sb.length
|
||||
if (end < srcLength) sb.append(src.subSequence(end, srcLength))
|
||||
et.text = sb
|
||||
et.setSelection(newSelection)
|
||||
|
||||
procTextChanged.run()
|
||||
}
|
||||
ad.show(activity, activity.getString(R.string.featured_hashtags))
|
||||
}
|
||||
|
||||
// final ActionMode.Callback action_mode_callback = new ActionMode.Callback() {
|
||||
|
|
|
@ -0,0 +1,206 @@
|
|||
package jp.juggler.subwaytooter.api
|
||||
|
||||
import jp.juggler.util.coroutine.AppDispatchers
|
||||
import jp.juggler.util.data.*
|
||||
import jp.juggler.util.log.LogCategory
|
||||
import jp.juggler.util.log.withCaption
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import okhttp3.internal.closeQuietly
|
||||
import ru.gildor.coroutines.okhttp.await
|
||||
import java.io.IOException
|
||||
|
||||
private val log = LogCategory("ApiUtils2")
|
||||
|
||||
const val JSON_SERVER_TYPE = "<>serverType"
|
||||
const val SERVER_MISSKEY = "misskey"
|
||||
const val SERVER_MASTODON = "mastodon"
|
||||
|
||||
val DEFAULT_JSON_ERROR_PARSER =
|
||||
{ json: JsonObject -> json["error"]?.toString() }
|
||||
|
||||
private val reWhiteSpace = """\s+""".toRegex()
|
||||
private val reStartJsonArray = """\A\s*\[""".toRegex()
|
||||
private val reStartJsonObject = """\A\s*\{""".toRegex()
|
||||
|
||||
fun Request.Builder.authorizationBearer(token: String?) =
|
||||
apply { token.notEmpty()?.let { header("Authorization", "Bearer $it") } }
|
||||
|
||||
class ApiError(
|
||||
message: String,
|
||||
cause: Throwable? = null,
|
||||
val response: Response? = null,
|
||||
) : IOException(message, cause)
|
||||
|
||||
private fun Response.formatError(caption: String? = null) = when {
|
||||
caption.isNullOrBlank() -> "HTTP $code $message ${request.method} ${request.url}"
|
||||
else -> "$caption: HTTP $code $message ${request.method} ${request.url}"
|
||||
}
|
||||
|
||||
private fun Request.formatError(ex: Throwable, caption: String? = null) = when {
|
||||
caption.isNullOrBlank() -> "${ex.withCaption()} $method $url"
|
||||
else -> "$caption: ${ex.withCaption()} $method $url"
|
||||
}
|
||||
|
||||
/**
|
||||
* 応答ボディのHTMLやテキストを整形する
|
||||
*/
|
||||
private fun simplifyErrorHtml(body: String): String {
|
||||
// // JsonObjectとして解釈できるならエラーメッセージを検出する
|
||||
// try {
|
||||
// val json = body.decodeJsonObject()
|
||||
// jsonErrorParser(json)?.notEmpty()?.let { return it }
|
||||
// } catch (_: Throwable) {
|
||||
// }
|
||||
|
||||
// // HTMLならタグの除去を試みる
|
||||
// try {
|
||||
// val ct = response.body?.contentType()
|
||||
// if (ct?.subtype == "html") {
|
||||
// // XXX HTMLデコードを省略
|
||||
// return reWhiteSpace.replace(body," ").trim()
|
||||
// }
|
||||
// } catch (_: Throwable) {
|
||||
// }
|
||||
|
||||
// XXX: Amazon S3 が403を返した場合にcontent-typeが?/xmlでserverがAmazonならXMLをパースしてエラーを整形することもできるが、多分必要ない
|
||||
|
||||
// 通常テキストの空白や改行を整理した文字列を返す
|
||||
try {
|
||||
return reWhiteSpace.replace(body, " ").trim()
|
||||
} catch (_: Throwable) {
|
||||
}
|
||||
|
||||
// 全部失敗したら入力そのまま
|
||||
return body
|
||||
}
|
||||
|
||||
/**
|
||||
* エラー応答のステータス部分や本文を文字列にする
|
||||
*/
|
||||
fun parseErrorResponse(response: Response, body: String? = null): String =
|
||||
try {
|
||||
val request = response.request
|
||||
StringBuilder().apply {
|
||||
// 応答ボディのテキストがあれば追加
|
||||
if (body.isNullOrBlank()) {
|
||||
append("(missing response body)")
|
||||
} else {
|
||||
append(simplifyErrorHtml(body))
|
||||
}
|
||||
if (isNotEmpty()) append(' ')
|
||||
append("(HTTP ").append(response.code.toString())
|
||||
response.message.notBlank()?.let { message ->
|
||||
append(' ')
|
||||
append(message)
|
||||
}
|
||||
append(") ${request.method} ${request.url}")
|
||||
}.toString().replace("""[\x0d\x0a]+""".toRegex(), "\n")
|
||||
} catch (ex: Throwable) {
|
||||
log.e(ex, "parseErrorResponse failed.")
|
||||
"(can't parse response body)"
|
||||
}
|
||||
|
||||
suspend fun Request.await(okHttp: OkHttpClient) =
|
||||
try {
|
||||
okHttp.newCall(this).await()
|
||||
} catch (ex: Throwable) {
|
||||
throw ApiError(cause = ex, message = this.formatError(ex))
|
||||
}
|
||||
|
||||
/**
|
||||
* レスポンスボディを文字列として読む
|
||||
* ボディがない場合はnullを返す
|
||||
* その他はSendExceptionを返す
|
||||
*/
|
||||
private suspend fun Response.readString(): String? {
|
||||
val response = this
|
||||
return try {
|
||||
// XXX: 進捗表示
|
||||
withContext(AppDispatchers.IO) {
|
||||
val bodyString = response.body?.string()
|
||||
if (bodyString.isNullOrEmpty()) {
|
||||
if (response.code in 200 until 300) {
|
||||
// Misskey の /api/notes/favorites/create は 204(no content)を返す。ボディはカラになる。
|
||||
return@withContext ""
|
||||
} else if (!response.isSuccessful) {
|
||||
throw ApiError(
|
||||
response = response,
|
||||
message = parseErrorResponse(response = response, body = ""),
|
||||
)
|
||||
}
|
||||
}
|
||||
bodyString
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
when (ex) {
|
||||
is CancellationException, is ApiError -> throw ex
|
||||
else -> {
|
||||
log.e(ex, "readString failed.")
|
||||
throw ApiError(
|
||||
response = response,
|
||||
message = parseErrorResponse(
|
||||
response = response,
|
||||
ex.withCaption("readString failed.")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
response.body?.closeQuietly()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ResponseWith<String?> をResponseWith<JsonObject?>に変換する
|
||||
*/
|
||||
suspend fun String?.stringToJsonObject(response: Response): JsonObject =
|
||||
try {
|
||||
val content = this
|
||||
withContext(AppDispatchers.IO) {
|
||||
when {
|
||||
content == null -> throw ApiError(
|
||||
response = response,
|
||||
message = response.formatError("response body is null.")
|
||||
)
|
||||
|
||||
// 204 no content は 空オブジェクトと解釈する
|
||||
content == "" -> JsonObject()
|
||||
|
||||
reStartJsonArray.containsMatchIn(content) ->
|
||||
jsonObjectOf("root" to content.decodeJsonArray())
|
||||
|
||||
reStartJsonObject.containsMatchIn(content) -> {
|
||||
val json = content.decodeJsonObject()
|
||||
DEFAULT_JSON_ERROR_PARSER(json)?.let { error ->
|
||||
throw ApiError(
|
||||
response = response,
|
||||
message = response.formatError(error)
|
||||
)
|
||||
}
|
||||
json
|
||||
}
|
||||
|
||||
else -> throw ApiError(
|
||||
response = response,
|
||||
message = response.formatError("not a JSON object.")
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
when (ex) {
|
||||
is CancellationException, is ApiError -> throw ex
|
||||
else -> {
|
||||
throw ApiError(
|
||||
response = response,
|
||||
message = response.formatError("readJsonObject failed."),
|
||||
cause = ex,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun Response.readJsonObject() = readString().stringToJsonObject(this)
|
|
@ -6,7 +6,6 @@ import jp.juggler.subwaytooter.App1
|
|||
import jp.juggler.subwaytooter.R
|
||||
import jp.juggler.subwaytooter.api.auth.AuthBase
|
||||
import jp.juggler.subwaytooter.api.entity.*
|
||||
import jp.juggler.subwaytooter.pref.pref
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.util.*
|
||||
import jp.juggler.util.data.*
|
||||
|
@ -56,7 +55,6 @@ class TootApiClient(
|
|||
}
|
||||
|
||||
// 認証に関する設定を保存する
|
||||
internal val pref = context.pref()
|
||||
|
||||
// インスタンスのホスト名
|
||||
var apiHost: Host? = null
|
||||
|
@ -407,8 +405,9 @@ class TootApiClient(
|
|||
|
||||
requestBuilder.url(url)
|
||||
|
||||
(forceAccessToken ?: account?.getAccessToken())
|
||||
?.notEmpty()?.let { requestBuilder.header("Authorization", "Bearer $it") }
|
||||
(forceAccessToken ?: account?.bearerAccessToken)?.notEmpty()?.let {
|
||||
requestBuilder.header("Authorization", "Bearer $it")
|
||||
}
|
||||
|
||||
requestBuilder.build()
|
||||
.also { log.d("request: ${it.method} $url") }
|
||||
|
@ -503,10 +502,9 @@ class TootApiClient(
|
|||
url = "$url${delm}i=${accessToken.encodePercent()}"
|
||||
}
|
||||
} else {
|
||||
val accessToken = account.getAccessToken()
|
||||
if (accessToken?.isNotEmpty() == true) {
|
||||
val delm = if (-1 != url.indexOf('?')) '&' else '?'
|
||||
url = "$url${delm}access_token=${accessToken.encodePercent()}"
|
||||
account.bearerAccessToken.notEmpty()?.let {
|
||||
val delm = if (url.contains('?')) '&' else '?'
|
||||
url = "$url${delm}access_token=${it.encodePercent()}"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ abstract class AuthBase {
|
|||
val clientName
|
||||
get() = arrayOf(
|
||||
testClientName,
|
||||
PrefS.spClientName.invoke(),
|
||||
PrefS.spClientName.value,
|
||||
).firstNotNullOfOrNull { it.notBlank() }
|
||||
?: DEFAULT_CLIENT_NAME
|
||||
|
||||
|
|
|
@ -8,8 +8,8 @@ import jp.juggler.subwaytooter.api.entity.EntityId
|
|||
import jp.juggler.subwaytooter.api.entity.Host
|
||||
import jp.juggler.subwaytooter.api.entity.InstanceType
|
||||
import jp.juggler.subwaytooter.api.entity.TootInstance
|
||||
import jp.juggler.subwaytooter.table.ClientInfo
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.daoClientInfo
|
||||
import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||
import jp.juggler.subwaytooter.util.LinkHelper
|
||||
import jp.juggler.util.data.JsonObject
|
||||
import jp.juggler.util.data.buildJsonObject
|
||||
|
@ -84,7 +84,7 @@ class MastodonAuth(override val client: TootApiClient) : AuthBase() {
|
|||
}
|
||||
}
|
||||
clientInfo[KEY_CLIENT_CREDENTIAL] = clientCredential
|
||||
ClientInfo.save(apiHost, clientName, clientInfo.toString())
|
||||
daoClientInfo.save(apiHost, clientName, clientInfo.toString())
|
||||
return clientCredential
|
||||
}
|
||||
|
||||
|
@ -95,7 +95,7 @@ class MastodonAuth(override val client: TootApiClient) : AuthBase() {
|
|||
tootInstance: TootInstance?,
|
||||
forceUpdateClient: Boolean,
|
||||
): JsonObject {
|
||||
var clientInfo = ClientInfo.load(apiHost, clientName)
|
||||
var clientInfo = daoClientInfo.load(apiHost, clientName)
|
||||
|
||||
// スコープ一覧を取得する
|
||||
val scopeString = mastodonScope(tootInstance)
|
||||
|
@ -126,7 +126,7 @@ class MastodonAuth(override val client: TootApiClient) : AuthBase() {
|
|||
else -> try {
|
||||
// マストドン2.4でスコープが追加された
|
||||
// 取得時のスコープ指定がマッチしない(もしくは記録されていない)ならクライアント情報を再利用してはいけない
|
||||
ClientInfo.delete(apiHost, clientName)
|
||||
daoClientInfo.delete(apiHost, clientName)
|
||||
|
||||
// クライアントアプリ情報そのものはまだサーバに残っているが、明示的に消す方法は現状存在しない
|
||||
// client credential だけは消せる
|
||||
|
@ -254,7 +254,7 @@ class MastodonAuth(override val client: TootApiClient) : AuthBase() {
|
|||
when {
|
||||
param.startsWith("db:") -> try {
|
||||
val dataId = param.substring(3).toLong(10)
|
||||
val sa = SavedAccount.loadAccount(context, dataId)
|
||||
val sa = daoSavedAccount.loadAccount(dataId)
|
||||
?: error("missing account db_id=$dataId")
|
||||
client.account = sa
|
||||
} catch (ex: Throwable) {
|
||||
|
@ -272,7 +272,7 @@ class MastodonAuth(override val client: TootApiClient) : AuthBase() {
|
|||
val apiHost = client.apiHost
|
||||
?: error("can't get apiHost from callback parameter.")
|
||||
|
||||
val clientInfo = ClientInfo.load(apiHost, clientName)
|
||||
val clientInfo = daoClientInfo.load(apiHost, clientName)
|
||||
?: error("can't find client info for apiHost=$apiHost, clientName=$clientName")
|
||||
|
||||
val tokenInfo = api.authStep2(
|
||||
|
|
|
@ -8,9 +8,9 @@ import jp.juggler.subwaytooter.api.TootParser
|
|||
import jp.juggler.subwaytooter.api.entity.EntityId
|
||||
import jp.juggler.subwaytooter.api.entity.Host
|
||||
import jp.juggler.subwaytooter.api.entity.TootInstance
|
||||
import jp.juggler.subwaytooter.pref.PrefDevice
|
||||
import jp.juggler.subwaytooter.table.ClientInfo
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.pref.prefDevice
|
||||
import jp.juggler.subwaytooter.table.daoClientInfo
|
||||
import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||
import jp.juggler.subwaytooter.util.LinkHelper
|
||||
import jp.juggler.util.data.*
|
||||
import jp.juggler.util.log.LogCategory
|
||||
|
@ -96,14 +96,12 @@ class MisskeyAuth10(override val client: TootApiClient) : AuthBase() {
|
|||
* {"token":"0ba88e2d-4b7d-4599-8d90-dc341a005637","url":"https://misskey.xyz/auth/0ba88e2d-4b7d-4599-8d90-dc341a005637"}
|
||||
*/
|
||||
private suspend fun createAuthUri(apiHost: Host, appSecret: String): Uri {
|
||||
PrefDevice.from(context).edit().apply {
|
||||
putString(PrefDevice.LAST_AUTH_INSTANCE, apiHost.ascii)
|
||||
putString(PrefDevice.LAST_AUTH_SECRET, appSecret)
|
||||
when (val account = account) {
|
||||
null -> remove(PrefDevice.LAST_AUTH_DB_ID)
|
||||
else -> putLong(PrefDevice.LAST_AUTH_DB_ID, account.db_id)
|
||||
}
|
||||
}.apply()
|
||||
context.prefDevice.saveLastAuth(
|
||||
host = apiHost.ascii,
|
||||
secret = appSecret,
|
||||
dbId = account?.db_id, //nullable
|
||||
)
|
||||
|
||||
return api.authSessionGenerate(apiHost, appSecret)
|
||||
.string("url").notEmpty()?.toUri()
|
||||
?: error("missing 'url' in session/generate.")
|
||||
|
@ -122,7 +120,7 @@ class MisskeyAuth10(override val client: TootApiClient) : AuthBase() {
|
|||
): Uri {
|
||||
val apiHost = apiHost ?: error("missing apiHost")
|
||||
|
||||
val clientInfo = ClientInfo.load(apiHost, clientName)
|
||||
val clientInfo = daoClientInfo.load(apiHost, clientName)
|
||||
|
||||
// スコープ一覧を取得する
|
||||
val scopeArray = getScopeArrayMisskey(ti)
|
||||
|
@ -173,7 +171,7 @@ class MisskeyAuth10(override val client: TootApiClient) : AuthBase() {
|
|||
val appSecret = appJson.string(KEY_MISSKEY_APP_SECRET)
|
||||
.notBlank() ?: error(context.getString(R.string.cant_get_misskey_app_secret))
|
||||
|
||||
ClientInfo.save(apiHost, clientName, appJson.toString())
|
||||
daoClientInfo.save(apiHost, clientName, appJson.toString())
|
||||
|
||||
return createAuthUri(apiHost, appSecret)
|
||||
}
|
||||
|
@ -183,20 +181,21 @@ class MisskeyAuth10(override val client: TootApiClient) : AuthBase() {
|
|||
*/
|
||||
override suspend fun authStep2(uri: Uri): Auth2Result {
|
||||
|
||||
val prefDevice = PrefDevice.from(context)
|
||||
val prefDevice = context.prefDevice
|
||||
|
||||
val token = uri.getQueryParameter("token")
|
||||
?.notBlank() ?: error("missing token in callback URL")
|
||||
|
||||
val hostStr = prefDevice.getString(PrefDevice.LAST_AUTH_INSTANCE, null)
|
||||
val hostStr = prefDevice.lastAuthInstance
|
||||
?.notBlank() ?: error("missing instance name.")
|
||||
|
||||
val apiHost = Host.parse(hostStr)
|
||||
|
||||
when (val dbId = prefDevice.getLong(PrefDevice.LAST_AUTH_DB_ID, -1L)) {
|
||||
when (val dbId = prefDevice.lastAuthDbId) {
|
||||
// new registration
|
||||
-1L -> client.apiHost = apiHost
|
||||
null -> client.apiHost = apiHost
|
||||
// update access token
|
||||
else -> SavedAccount.loadAccount(context, dbId)?.also {
|
||||
else -> daoSavedAccount.loadAccount(dbId)?.also {
|
||||
client.account = it
|
||||
} ?: error("missing account db_id=$dbId")
|
||||
}
|
||||
|
@ -209,7 +208,7 @@ class MisskeyAuth10(override val client: TootApiClient) : AuthBase() {
|
|||
)
|
||||
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
val clientInfo = ClientInfo.load(apiHost, clientName)
|
||||
val clientInfo = daoClientInfo.load(apiHost, clientName)
|
||||
?.notEmpty() ?: error("missing client id")
|
||||
|
||||
val appSecret = clientInfo.string(KEY_MISSKEY_APP_SECRET)
|
||||
|
|
|
@ -7,8 +7,8 @@ import jp.juggler.subwaytooter.api.auth.MisskeyAuth10.Companion.encodeScopeArray
|
|||
import jp.juggler.subwaytooter.api.auth.MisskeyAuth10.Companion.getScopeArrayMisskey
|
||||
import jp.juggler.subwaytooter.api.entity.Host
|
||||
import jp.juggler.subwaytooter.api.entity.TootInstance
|
||||
import jp.juggler.subwaytooter.pref.PrefDevice
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.pref.prefDevice
|
||||
import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||
import jp.juggler.subwaytooter.util.LinkHelper
|
||||
import jp.juggler.util.data.JsonObject
|
||||
import jp.juggler.util.data.notEmpty
|
||||
|
@ -60,14 +60,11 @@ class MisskeyAuth13(override val client: TootApiClient) : AuthBase() {
|
|||
|
||||
val sessionId = UUID.randomUUID().toString()
|
||||
|
||||
PrefDevice.from(client.context).edit().apply {
|
||||
putString(PrefDevice.LAST_AUTH_INSTANCE, apiHost.ascii)
|
||||
putString(PrefDevice.LAST_AUTH_SECRET, sessionId)
|
||||
when (val account = account) {
|
||||
null -> remove(PrefDevice.LAST_AUTH_DB_ID)
|
||||
else -> putLong(PrefDevice.LAST_AUTH_DB_ID, account.db_id)
|
||||
}
|
||||
}.apply()
|
||||
client.context.prefDevice.saveLastAuth(
|
||||
host = apiHost.ascii,
|
||||
secret = sessionId,
|
||||
dbId = account?.db_id,
|
||||
)
|
||||
|
||||
return api13.createAuthUrl(
|
||||
apiHost = apiHost,
|
||||
|
@ -82,20 +79,20 @@ class MisskeyAuth13(override val client: TootApiClient) : AuthBase() {
|
|||
override suspend fun authStep2(uri: Uri): Auth2Result {
|
||||
|
||||
// 認証開始時に保存した情報
|
||||
val prefDevice = PrefDevice.from(client.context)
|
||||
val savedSessionId = prefDevice.getString(PrefDevice.LAST_AUTH_SECRET, null)
|
||||
val prefDevice = client.context.prefDevice
|
||||
val savedSessionId = prefDevice.lastAuthSecret
|
||||
|
||||
val apiHost = prefDevice.getString(PrefDevice.LAST_AUTH_INSTANCE, null)
|
||||
val apiHost = prefDevice.lastAuthInstance
|
||||
?.let { Host.parse(it) }
|
||||
?: error("missing apiHost")
|
||||
|
||||
when (val dbId = prefDevice.getLong(PrefDevice.LAST_AUTH_DB_ID, -1L)) {
|
||||
when (val dbId = prefDevice.lastAuthDbId) {
|
||||
// new registration
|
||||
-1L -> client.apiHost = apiHost
|
||||
null -> client.apiHost = apiHost
|
||||
|
||||
// update access token
|
||||
else -> {
|
||||
val sa = SavedAccount.loadAccount(context, dbId)
|
||||
val sa = daoSavedAccount.loadAccount(dbId)
|
||||
?: error("missing account db_id=$dbId")
|
||||
client.account = sa
|
||||
}
|
||||
|
@ -131,7 +128,7 @@ class MisskeyAuth13(override val client: TootApiClient) : AuthBase() {
|
|||
.account(accountJson)
|
||||
?: error("can't parse user json.")
|
||||
|
||||
prefDevice.edit().remove(PrefDevice.LAST_AUTH_SECRET).apply()
|
||||
prefDevice.removeLastAuth()
|
||||
|
||||
return Auth2Result(
|
||||
tootInstance = ti,
|
||||
|
|
|
@ -29,7 +29,7 @@ class EntityId(val x: String) : Comparable<EntityId> {
|
|||
|
||||
fun mayNull(x: String?) = if (x == null) null else EntityId(x)
|
||||
|
||||
fun String.decode(): EntityId? {
|
||||
fun String.decodeEntityId(): EntityId? {
|
||||
if (this.isEmpty()) return null
|
||||
// first character is 'L' for EntityIdLong, 'S' for EntityIdString.
|
||||
// integer id is removed at https://source.joinmastodon.org/mastodon/docs/commit/e086d478afa140e7b0b9a60183655315966ad9ff
|
||||
|
@ -37,26 +37,24 @@ class EntityId(val x: String) : Comparable<EntityId> {
|
|||
}
|
||||
|
||||
fun from(intent: Intent?, key: String) =
|
||||
intent?.string(key)?.decode()
|
||||
intent?.string(key)?.decodeEntityId()
|
||||
|
||||
fun from(bundle: Bundle?, key: String) =
|
||||
bundle?.string(key)?.decode()
|
||||
bundle?.string(key)?.decodeEntityId()
|
||||
|
||||
// 内部保存データのデコード用。APIレスポンスのパースに使ってはいけない
|
||||
fun from(data: JsonObject?, key: String): EntityId? {
|
||||
val o = data?.get(key)
|
||||
if (o is Long) return EntityId(o.toString())
|
||||
return (o as? String)?.decode()
|
||||
return (o as? String)?.decodeEntityId()
|
||||
}
|
||||
|
||||
fun from(cursor: Cursor, key: String) =
|
||||
cursor.getStringOrNull(key)?.decode()
|
||||
cursor.getStringOrNull(key)?.decodeEntityId()
|
||||
}
|
||||
|
||||
private fun encode(): String {
|
||||
val prefix = 'S'
|
||||
return "$prefix$this"
|
||||
}
|
||||
// 昔は文字列とLong値を区別していたが、今はもうない
|
||||
fun encode(): String = "S$this"
|
||||
|
||||
fun putTo(data: Intent, key: String): Intent = data.putExtra(key, encode())
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import jp.juggler.subwaytooter.emoji.CustomEmoji
|
|||
import jp.juggler.subwaytooter.pref.PrefB
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.UserRelation
|
||||
import jp.juggler.subwaytooter.table.daoUserRelation
|
||||
import jp.juggler.subwaytooter.util.DecodeOptions
|
||||
import jp.juggler.subwaytooter.util.LinkHelper
|
||||
import jp.juggler.subwaytooter.util.NetworkEmojiInvalidator
|
||||
|
@ -215,7 +216,7 @@ open class TootAccount(parser: TootParser, src: JsonObject) : HostAndDomain {
|
|||
|
||||
this.fields = parseMisskeyFields(src)
|
||||
|
||||
UserRelation.fromAccount(parser, src, id)
|
||||
daoUserRelation.fromAccount(parser, src, id)
|
||||
|
||||
@Suppress("LeakingThis")
|
||||
MisskeyAccountDetailMap.fromAccount(parser, this, id)
|
||||
|
@ -504,7 +505,7 @@ open class TootAccount(parser: TootParser, src: JsonObject) : HostAndDomain {
|
|||
.append(suggestionSource)
|
||||
}
|
||||
|
||||
if (PrefB.bpDirectoryLastActive() && last_status_at > 0L) {
|
||||
if (PrefB.bpDirectoryLastActive.value && last_status_at > 0L) {
|
||||
prepareSb()
|
||||
.append(context.getString(R.string.last_active))
|
||||
.append(delm)
|
||||
|
@ -519,7 +520,7 @@ open class TootAccount(parser: TootParser, src: JsonObject) : HostAndDomain {
|
|||
}
|
||||
|
||||
if (!fromProfileHeader) {
|
||||
if (PrefB.bpDirectoryTootCount() &&
|
||||
if (PrefB.bpDirectoryTootCount.value &&
|
||||
(statuses_count ?: 0L) > 0L
|
||||
) {
|
||||
prepareSb()
|
||||
|
@ -528,8 +529,8 @@ open class TootAccount(parser: TootParser, src: JsonObject) : HostAndDomain {
|
|||
.append(statuses_count.toString())
|
||||
}
|
||||
|
||||
if (PrefB.bpDirectoryFollowers() &&
|
||||
!PrefB.bpHideFollowCount() &&
|
||||
if (PrefB.bpDirectoryFollowers.value &&
|
||||
!PrefB.bpHideFollowCount.value &&
|
||||
(followers_count ?: 0L) > 0L
|
||||
) {
|
||||
prepareSb()
|
||||
|
@ -538,7 +539,7 @@ open class TootAccount(parser: TootParser, src: JsonObject) : HostAndDomain {
|
|||
.append(followers_count.toString())
|
||||
}
|
||||
|
||||
if (PrefB.bpDirectoryNote() && note?.isNotEmpty() == true) {
|
||||
if (PrefB.bpDirectoryNote.value && note?.isNotEmpty() == true) {
|
||||
val decodedNote = DecodeOptions(
|
||||
context,
|
||||
accessInfo,
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package jp.juggler.subwaytooter.api.entity
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import jp.juggler.subwaytooter.api.TootParser
|
||||
import jp.juggler.subwaytooter.pref.PrefB
|
||||
import jp.juggler.util.*
|
||||
|
@ -191,26 +190,26 @@ class TootAttachment : TootAttachmentLike {
|
|||
private fun parseType(src: String?) =
|
||||
TootAttachmentType.values().find { it.id == src }
|
||||
|
||||
override fun urlForThumbnail(pref: SharedPreferences) =
|
||||
if (PrefB.bpPriorLocalURL(pref)) {
|
||||
override fun urlForThumbnail() =
|
||||
if (PrefB.bpPriorLocalURL.value) {
|
||||
preview_url.notEmpty() ?: preview_remote_url.notEmpty()
|
||||
} else {
|
||||
preview_remote_url.notEmpty() ?: preview_url.notEmpty()
|
||||
} ?: when (type) {
|
||||
TootAttachmentType.Image -> getLargeUrl(pref)
|
||||
TootAttachmentType.Image -> getLargeUrl()
|
||||
else -> null
|
||||
}
|
||||
|
||||
fun getLargeUrl(pref: SharedPreferences) =
|
||||
if (PrefB.bpPriorLocalURL(pref)) {
|
||||
fun getLargeUrl() =
|
||||
if (PrefB.bpPriorLocalURL.value) {
|
||||
url.notEmpty() ?: remote_url
|
||||
} else {
|
||||
remote_url.notEmpty() ?: url
|
||||
}
|
||||
|
||||
fun getLargeUrlList(pref: SharedPreferences) =
|
||||
fun getLargeUrlList() =
|
||||
ArrayList<String>().apply {
|
||||
if (PrefB.bpPriorLocalURL(pref)) {
|
||||
if (PrefB.bpPriorLocalURL.value) {
|
||||
url.notEmpty()?.addTo(this)
|
||||
remote_url.notEmpty()?.addTo(this)
|
||||
} else {
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package jp.juggler.subwaytooter.api.entity
|
||||
|
||||
import android.content.SharedPreferences
|
||||
|
||||
enum class TootAttachmentType(val id: String) {
|
||||
Unknown("unknown"),
|
||||
Image("image"),
|
||||
|
@ -16,7 +14,7 @@ interface TootAttachmentLike {
|
|||
val description: String?
|
||||
|
||||
// url for thumbnail, or null or empty
|
||||
fun urlForThumbnail(pref: SharedPreferences): String?
|
||||
fun urlForThumbnail(): String?
|
||||
|
||||
// url for description, or null or empty
|
||||
val urlForDescription: String?
|
||||
|
|
|
@ -14,7 +14,7 @@ class TootAttachmentMSP(
|
|||
override val description: String?
|
||||
get() = null
|
||||
|
||||
override fun urlForThumbnail(pref: SharedPreferences) = preview_url
|
||||
override fun urlForThumbnail() = preview_url
|
||||
|
||||
override val urlForDescription: String
|
||||
get() = preview_url
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package jp.juggler.subwaytooter.api.entity
|
||||
|
||||
import jp.juggler.subwaytooter.api.TootParser
|
||||
import jp.juggler.subwaytooter.pref.pref
|
||||
import jp.juggler.subwaytooter.util.DecodeOptions
|
||||
import jp.juggler.util.data.JsonObject
|
||||
import jp.juggler.util.data.filterNotEmpty
|
||||
|
@ -61,7 +60,7 @@ class TootCard(
|
|||
},
|
||||
image = src.media_attachments
|
||||
?.firstOrNull()
|
||||
?.urlForThumbnail(parser.context.pref())
|
||||
?.urlForThumbnail()
|
||||
?: src.account.avatar_static,
|
||||
type = "photo"
|
||||
)
|
||||
|
|
|
@ -310,8 +310,9 @@ class TootInstance(parser: TootParser, src: JsonObject) {
|
|||
if (sendRequest(result) {
|
||||
val builder = Request.Builder().url("https://${apiHost?.ascii}/api/v1/instance")
|
||||
|
||||
(forceAccessToken ?: account?.getAccessToken())
|
||||
?.notEmpty()?.let { builder.header("Authorization", "Bearer $it") }
|
||||
(forceAccessToken ?: account?.bearerAccessToken)?.notEmpty()?.let {
|
||||
builder.header("Authorization", "Bearer $it")
|
||||
}
|
||||
builder.build()
|
||||
}
|
||||
) {
|
||||
|
@ -428,7 +429,7 @@ class TootInstance(parser: TootParser, src: JsonObject) {
|
|||
|
||||
when {
|
||||
qrr.first?.instanceType == InstanceType.Pixelfed &&
|
||||
!PrefB.bpEnablePixelfed() &&
|
||||
!PrefB.bpEnablePixelfed.value &&
|
||||
!req.allowPixelfed ->
|
||||
tiError("currently Pixelfed instance is not supported.")
|
||||
else -> qrr
|
||||
|
|
|
@ -9,7 +9,6 @@ import jp.juggler.subwaytooter.span.NetworkEmojiSpan
|
|||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.util.DecodeOptions
|
||||
import jp.juggler.subwaytooter.util.EmojiDecoder
|
||||
import jp.juggler.util.*
|
||||
import jp.juggler.util.data.*
|
||||
import java.util.*
|
||||
|
||||
|
@ -162,7 +161,7 @@ class TootReaction(
|
|||
}
|
||||
|
||||
private fun chooseUrl() = when {
|
||||
PrefB.bpDisableEmojiAnimation() -> staticUrl
|
||||
PrefB.bpDisableEmojiAnimation.value -> staticUrl
|
||||
else -> url
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ import android.content.Context
|
|||
import android.text.Spannable
|
||||
import android.text.SpannableString
|
||||
import androidx.annotation.StringRes
|
||||
import jp.juggler.subwaytooter.App1
|
||||
import jp.juggler.subwaytooter.R
|
||||
import jp.juggler.subwaytooter.api.TootAccountMap
|
||||
import jp.juggler.subwaytooter.api.TootParser
|
||||
|
@ -365,7 +364,7 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
|
|||
|
||||
this.mentions = mergeMentions(mentions1, mentions2)
|
||||
this.decoded_mentions =
|
||||
HTMLDecoder.decodeMentions(parser.linkHelper, this)
|
||||
HTMLDecoder.decodeMentions(parser, this)
|
||||
?: EMPTY_SPANNABLE
|
||||
|
||||
// contentを読んだ後にアンケートのデコード
|
||||
|
@ -484,7 +483,7 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
|
|||
this.muted = false
|
||||
this.language = null
|
||||
this.decoded_mentions =
|
||||
HTMLDecoder.decodeMentions(parser.linkHelper, this)
|
||||
HTMLDecoder.decodeMentions(parser, this)
|
||||
?: EMPTY_SPANNABLE
|
||||
|
||||
val quote = when {
|
||||
|
@ -696,7 +695,7 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
|
|||
this.muted = src.optBoolean("muted")
|
||||
this.language = src.string("language")?.notEmpty()
|
||||
this.decoded_mentions =
|
||||
HTMLDecoder.decodeMentions(parser.linkHelper, this)
|
||||
HTMLDecoder.decodeMentions(parser, this)
|
||||
?: EMPTY_SPANNABLE
|
||||
|
||||
val quote = when {
|
||||
|
@ -1100,7 +1099,7 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
|
|||
|
||||
fun markDeleted(context: Context, deletedAt: Long?): Boolean {
|
||||
|
||||
if (PrefB.bpDontRemoveDeletedToot(App1.getAppState(context).pref)) return false
|
||||
if (PrefB.bpDontRemoveDeletedToot.value) return false
|
||||
|
||||
var sv = if (deletedAt != null) {
|
||||
context.getString(R.string.status_deleted_at, formatTime(context, deletedAt, false))
|
||||
|
@ -1131,7 +1130,7 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
|
|||
internal val log = LogCategory("TootStatus")
|
||||
|
||||
@Volatile
|
||||
internal var muted_app: HashSet<String>? = null
|
||||
internal var muted_app: Set<String>? = null
|
||||
|
||||
@Volatile
|
||||
internal var muted_word: WordTrieTree? = null
|
||||
|
@ -1419,7 +1418,7 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
|
|||
formatDate(t, date_format2, omitZeroSecond = false, omitYear = true)
|
||||
}
|
||||
|
||||
if (bAllowRelative && PrefB.bpRelativeTimestamp()) {
|
||||
if (bAllowRelative && PrefB.bpRelativeTimestamp.value) {
|
||||
|
||||
delta = abs(delta)
|
||||
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
package jp.juggler.subwaytooter.api.push
|
||||
|
||||
import jp.juggler.subwaytooter.api.await
|
||||
import jp.juggler.subwaytooter.api.readJsonObject
|
||||
import jp.juggler.subwaytooter.column.encodeQuery
|
||||
import jp.juggler.util.coroutine.AppDispatchers
|
||||
import jp.juggler.util.data.JsonObject
|
||||
import jp.juggler.util.data.buildJsonObject
|
||||
import jp.juggler.util.data.jsonArrayOf
|
||||
import jp.juggler.util.network.toPostRequestBuilder
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
|
||||
/**
|
||||
* アプリサーバのAPI
|
||||
*/
|
||||
class ApiPushAppServer(
|
||||
private val okHttp: OkHttpClient,
|
||||
private val appServerPrefix: String = "https://mastodon-msg.juggler.jp/api/v2",
|
||||
) {
|
||||
/**
|
||||
* 中継エンドポイントが無効になったら削除する
|
||||
*/
|
||||
suspend fun endpointRemove(
|
||||
upUrl: String? = null,
|
||||
fcmToken: String? = null,
|
||||
hashId:String? = null,
|
||||
): JsonObject = buildJsonObject {
|
||||
upUrl?.let { put("upUrl", it) }
|
||||
fcmToken?.let { put("fcmToken", it) }
|
||||
hashId?.let{ put("hashId", it) }
|
||||
}.encodeQuery().let {
|
||||
Request.Builder()
|
||||
.url("${appServerPrefix}/endpoint/remove?$it")
|
||||
}.delete().build()
|
||||
.await(okHttp)
|
||||
.readJsonObject()
|
||||
|
||||
/**
|
||||
* エンドポイントとアカウントハッシュをアプリサーバに登録する
|
||||
*/
|
||||
suspend fun endpointUpsert(
|
||||
upUrl: String?,
|
||||
fcmToken: String?,
|
||||
acctHashList: List<String>,
|
||||
): JsonObject =
|
||||
buildJsonObject {
|
||||
upUrl?.let { put("upUrl", it) }
|
||||
fcmToken?.let { put("fcmToken", it) }
|
||||
put("acctHashList", jsonArrayOf(*(acctHashList.toTypedArray())))
|
||||
}.toPostRequestBuilder()
|
||||
.url("${appServerPrefix}/endpoint/upsert")
|
||||
.build()
|
||||
.await(okHttp)
|
||||
.readJsonObject()
|
||||
|
||||
suspend fun getLargeObject(
|
||||
largeObjectId: String
|
||||
): ByteArray? = withContext(AppDispatchers.IO) {
|
||||
Request.Builder()
|
||||
.url("${appServerPrefix}/l/$largeObjectId")
|
||||
.build()
|
||||
.await(okHttp)
|
||||
.body?.bytes()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
package jp.juggler.subwaytooter.api.push
|
||||
|
||||
import jp.juggler.subwaytooter.api.authorizationBearer
|
||||
import jp.juggler.subwaytooter.api.await
|
||||
import jp.juggler.subwaytooter.api.readJsonObject
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.util.data.JsonObject
|
||||
import jp.juggler.util.data.buildJsonObject
|
||||
import jp.juggler.util.network.toPostRequestBuilder
|
||||
import jp.juggler.util.network.toPutRequestBuilder
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
|
||||
class ApiPushMastodon(
|
||||
private val okHttp: OkHttpClient,
|
||||
) {
|
||||
companion object {
|
||||
val alertTypes = arrayOf(
|
||||
"mention",
|
||||
"status",
|
||||
"reblog",
|
||||
"follow",
|
||||
"follow_request",
|
||||
"favourite",
|
||||
"poll",
|
||||
"update",
|
||||
"admin.sign_up",
|
||||
"admin.report",
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* アクセストークンに設定されたプッシュ購読を見る
|
||||
*/
|
||||
suspend fun getPushSubscription(
|
||||
a: SavedAccount,
|
||||
): JsonObject = Request.Builder()
|
||||
.url("https://${a.apiHost}/api/v1/push/subscription")
|
||||
.authorizationBearer(a.bearerAccessToken)
|
||||
.build()
|
||||
.await(okHttp)
|
||||
.readJsonObject()
|
||||
|
||||
/**
|
||||
* アクセストークンに設定されたプッシュ購読を削除する
|
||||
*/
|
||||
suspend fun deletePushSubscription(
|
||||
a: SavedAccount,
|
||||
): JsonObject = Request.Builder()
|
||||
.delete()
|
||||
.url("https://${a.apiHost}/api/v1/push/subscription")
|
||||
.authorizationBearer(a.bearerAccessToken)
|
||||
.build()
|
||||
.await(okHttp)
|
||||
.readJsonObject()
|
||||
|
||||
/**
|
||||
* アクセストークンに対してプッシュ購読を登録する
|
||||
*/
|
||||
suspend fun createPushSubscription(
|
||||
a: SavedAccount,
|
||||
// REQUIRED String. The endpoint URL that is called when a notification event occurs.
|
||||
endpointUrl: String,
|
||||
// REQUIRED String. User agent public key.
|
||||
// Base64 encoded string of a public key from a ECDH keypair using the prime256v1 curve.
|
||||
p256dh: String,
|
||||
// REQUIRED String. Auth secret. Base64 encoded string of 16 bytes of random data.
|
||||
auth: String,
|
||||
// map of alert type to boolean, true to receive for alert type. false? null?
|
||||
alerts: Map<String, Boolean>,
|
||||
// whether to receive push notifications from all, followed, follower, or none users.
|
||||
policy: String,
|
||||
): JsonObject = buildJsonObject {
|
||||
put("subscription", buildJsonObject {
|
||||
put("endpoint", endpointUrl)
|
||||
put("keys", buildJsonObject {
|
||||
put("p256dh", p256dh)
|
||||
put("auth", auth)
|
||||
})
|
||||
})
|
||||
put("data", buildJsonObject {
|
||||
put("alerts", buildJsonObject {
|
||||
for (t in alertTypes) {
|
||||
alerts[t]?.let { put(t, it) }
|
||||
}
|
||||
})
|
||||
})
|
||||
put("policy", policy)
|
||||
}.toPostRequestBuilder()
|
||||
.url("https://${a.apiHost}/api/v1/push/subscription")
|
||||
.authorizationBearer(a.bearerAccessToken)
|
||||
.build()
|
||||
.await(okHttp)
|
||||
.readJsonObject()
|
||||
|
||||
/**
|
||||
* 購読のdata部分を更新する
|
||||
*/
|
||||
suspend fun updatePushSubscriptionData(
|
||||
a: SavedAccount,
|
||||
alerts: Map<String, Boolean>,
|
||||
policy: String,
|
||||
): JsonObject = buildJsonObject {
|
||||
put("data", buildJsonObject {
|
||||
put("alerts", buildJsonObject {
|
||||
for (t in alertTypes) {
|
||||
alerts[t]?.let { put(t, it) }
|
||||
}
|
||||
})
|
||||
})
|
||||
put("policy", policy)
|
||||
}.toPutRequestBuilder()
|
||||
.url("https://${a.apiHost}/api/v1/push/subscription")
|
||||
.authorizationBearer(a.bearerAccessToken)
|
||||
.build()
|
||||
.await(okHttp)
|
||||
.readJsonObject()
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
package jp.juggler.subwaytooter.api.push
|
||||
|
||||
import jp.juggler.subwaytooter.api.await
|
||||
import jp.juggler.subwaytooter.api.readJsonObject
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.util.data.JsonObject
|
||||
import jp.juggler.util.data.buildJsonObject
|
||||
import jp.juggler.util.network.toPostRequestBuilder
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
class ApiPushMisskey(
|
||||
private val okHttp: OkHttpClient,
|
||||
) {
|
||||
|
||||
/**
|
||||
* エンドポイントURLを指定してプッシュ購読の情報を取得する
|
||||
*/
|
||||
suspend fun getPushSubscription(
|
||||
a: SavedAccount,
|
||||
endpoint: String,
|
||||
): JsonObject = buildJsonObject {
|
||||
a.misskeyApiToken?.let { put("i", it) }
|
||||
put("endpoint", endpoint)
|
||||
}.toPostRequestBuilder()
|
||||
.url("https://${a.apiHost}/api/sw/show-registration")
|
||||
.build()
|
||||
.await(okHttp)
|
||||
.readJsonObject()
|
||||
|
||||
suspend fun deletePushSubscription(
|
||||
a: SavedAccount,
|
||||
endpoint: String,
|
||||
): JsonObject = buildJsonObject {
|
||||
a.misskeyApiToken?.let { put("i", it) }
|
||||
put("endpoint", endpoint)
|
||||
}.toPostRequestBuilder()
|
||||
.url("https://${a.apiHost}/api/sw/unregister")
|
||||
.build()
|
||||
.await(okHttp)
|
||||
.readJsonObject()
|
||||
|
||||
/**
|
||||
* プッシュ購読を更新する。
|
||||
* endpointのURLはクエリに使われる。変更できるのはsendReadMessageだけ。
|
||||
*/
|
||||
suspend fun updatePushSubscription(
|
||||
a: SavedAccount,
|
||||
endpoint: String,
|
||||
sendReadMessage: Boolean,
|
||||
): JsonObject = buildJsonObject {
|
||||
a.misskeyApiToken?.let { put("i", it) }
|
||||
put("endpoint", endpoint)
|
||||
put("sendReadMessage", sendReadMessage)
|
||||
}.toPostRequestBuilder()
|
||||
.url("https://${a.apiHost}/api/sw/update-registration")
|
||||
.build()
|
||||
.await(okHttp)
|
||||
.readJsonObject()
|
||||
|
||||
suspend fun createPushSubscription(
|
||||
a: SavedAccount,
|
||||
endpoint: String,
|
||||
auth: String,
|
||||
publicKey: String,
|
||||
sendReadMessage: Boolean,
|
||||
): JsonObject = buildJsonObject {
|
||||
a.misskeyApiToken?.let { put("i", it) }
|
||||
put("endpoint", endpoint)
|
||||
put("auth", auth)
|
||||
put("publickey", publicKey)
|
||||
put("sendReadMessage", sendReadMessage)
|
||||
}.toPostRequestBuilder()
|
||||
.url("https://${a.apiHost}/api/sw/register")
|
||||
.build()
|
||||
.await(okHttp)
|
||||
.readJsonObject()
|
||||
}
|
|
@ -5,7 +5,6 @@ import android.content.ContentValues
|
|||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.database.Cursor
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.net.Uri
|
||||
import android.provider.BaseColumns
|
||||
import android.util.JsonReader
|
||||
|
@ -17,10 +16,9 @@ import jp.juggler.subwaytooter.api.entity.EntityId
|
|||
import jp.juggler.subwaytooter.column.Column
|
||||
import jp.juggler.subwaytooter.column.ColumnEncoder
|
||||
import jp.juggler.subwaytooter.column.getBackgroundImageDir
|
||||
import jp.juggler.subwaytooter.global.appDatabase
|
||||
import jp.juggler.subwaytooter.pref.PrefL
|
||||
import jp.juggler.subwaytooter.pref.impl.*
|
||||
import jp.juggler.subwaytooter.pref.put
|
||||
import jp.juggler.subwaytooter.pref.lazyPref
|
||||
import jp.juggler.subwaytooter.table.*
|
||||
import jp.juggler.util.*
|
||||
import jp.juggler.util.data.*
|
||||
|
@ -117,50 +115,49 @@ object AppDataExporter {
|
|||
writer.name(jsonKey)
|
||||
writer.beginArray()
|
||||
|
||||
appDatabase.query(table, null, null, null, null, null, null)
|
||||
?.use { cursor ->
|
||||
val names = ArrayList<String>()
|
||||
val column_count = cursor.columnCount
|
||||
for (i in 0 until column_count) {
|
||||
names.add(cursor.getColumnName(i))
|
||||
}
|
||||
while (cursor.moveToNext()) {
|
||||
writer.beginObject()
|
||||
|
||||
for (i in 0 until column_count) {
|
||||
when (cursor.getType(i)) {
|
||||
Cursor.FIELD_TYPE_NULL -> {
|
||||
writer.name(names[i])
|
||||
writer.nullValue()
|
||||
}
|
||||
|
||||
Cursor.FIELD_TYPE_INTEGER -> {
|
||||
writer.name(names[i])
|
||||
writer.value(cursor.getLong(i))
|
||||
}
|
||||
|
||||
Cursor.FIELD_TYPE_STRING -> {
|
||||
writer.name(names[i])
|
||||
writer.value(cursor.getString(i))
|
||||
}
|
||||
|
||||
Cursor.FIELD_TYPE_FLOAT -> {
|
||||
val d = cursor.getDouble(i)
|
||||
if (d.isNaN() || d.isInfinite()) {
|
||||
log.w("column ${names[i]} is nan or infinite value.")
|
||||
} else {
|
||||
writer.name(names[i])
|
||||
writer.value(d)
|
||||
}
|
||||
}
|
||||
|
||||
Cursor.FIELD_TYPE_BLOB -> log.w("column ${names[i]} is blob.")
|
||||
}
|
||||
}
|
||||
|
||||
writer.endObject()
|
||||
}
|
||||
appDatabase.rawQuery("select from $table", emptyArray()).use { cursor ->
|
||||
val names = ArrayList<String>()
|
||||
val column_count = cursor.columnCount
|
||||
for (i in 0 until column_count) {
|
||||
names.add(cursor.getColumnName(i))
|
||||
}
|
||||
while (cursor.moveToNext()) {
|
||||
writer.beginObject()
|
||||
|
||||
for (i in 0 until column_count) {
|
||||
when (cursor.getType(i)) {
|
||||
Cursor.FIELD_TYPE_NULL -> {
|
||||
writer.name(names[i])
|
||||
writer.nullValue()
|
||||
}
|
||||
|
||||
Cursor.FIELD_TYPE_INTEGER -> {
|
||||
writer.name(names[i])
|
||||
writer.value(cursor.getLong(i))
|
||||
}
|
||||
|
||||
Cursor.FIELD_TYPE_STRING -> {
|
||||
writer.name(names[i])
|
||||
writer.value(cursor.getString(i))
|
||||
}
|
||||
|
||||
Cursor.FIELD_TYPE_FLOAT -> {
|
||||
val d = cursor.getDouble(i)
|
||||
if (d.isNaN() || d.isInfinite()) {
|
||||
log.w("column ${names[i]} is nan or infinite value.")
|
||||
} else {
|
||||
writer.name(names[i])
|
||||
writer.value(d)
|
||||
}
|
||||
}
|
||||
|
||||
Cursor.FIELD_TYPE_BLOB -> log.w("column ${names[i]} is blob.")
|
||||
}
|
||||
}
|
||||
|
||||
writer.endObject()
|
||||
}
|
||||
}
|
||||
writer.endArray()
|
||||
}
|
||||
|
||||
|
@ -198,19 +195,21 @@ object AppDataExporter {
|
|||
}
|
||||
|
||||
if (SavedAccount.table == table) {
|
||||
// 一時的に存在したが現在のDBスキーマにはない項目は読み飛ばす
|
||||
if ("nickname" == name || "color" == name) {
|
||||
reader.skipValue()
|
||||
continue
|
||||
}
|
||||
|
||||
// リアルタイム通知に関連する項目は読み飛ばす
|
||||
if (SavedAccount.COL_NOTIFICATION_TAG.name == name ||
|
||||
SavedAccount.COL_REGISTER_KEY.name == name ||
|
||||
SavedAccount.COL_REGISTER_TIME.name == name
|
||||
) {
|
||||
reader.skipValue()
|
||||
continue
|
||||
when (name) {
|
||||
// 一時的に存在したが現在のDBスキーマにはない項目は読み飛ばす
|
||||
"nickname",
|
||||
"color",
|
||||
"notification_server",
|
||||
"register_key",
|
||||
"register_time",
|
||||
"last_notification_error",
|
||||
"last_subscription_error",
|
||||
"last_push_endpoint",
|
||||
-> {
|
||||
reader.skipValue()
|
||||
continue
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -230,8 +229,7 @@ object AppDataExporter {
|
|||
}
|
||||
}
|
||||
reader.endObject()
|
||||
val new_id =
|
||||
db.insertWithOnConflict(table, null, cv, SQLiteDatabase.CONFLICT_REPLACE)
|
||||
val new_id = db.replace(table, null, cv)
|
||||
if (new_id == -1L) error("importTable: invalid row_id")
|
||||
idMap?.put(old_id, new_id)
|
||||
}
|
||||
|
@ -359,7 +357,7 @@ object AppDataExporter {
|
|||
|
||||
val app_state = App1.getAppState(context)
|
||||
|
||||
writePref(writer, app_state.pref)
|
||||
writePref(writer, lazyPref)
|
||||
|
||||
writeFromTable(writer, KEY_ACCOUNT, SavedAccount.table)
|
||||
writeFromTable(writer, KEY_ACCT_COLOR, AcctColor.table)
|
||||
|
@ -386,12 +384,12 @@ object AppDataExporter {
|
|||
|
||||
while (reader.hasNext()) {
|
||||
when (reader.nextName()) {
|
||||
KEY_PREF -> importPref(reader, app_state.pref)
|
||||
KEY_PREF -> importPref(reader, lazyPref)
|
||||
KEY_ACCOUNT -> importTable(reader, SavedAccount.table, account_id_map)
|
||||
|
||||
KEY_ACCT_COLOR -> {
|
||||
importTable(reader, AcctColor.table, null)
|
||||
AcctColor.clearMemoryCache()
|
||||
daoAcctColor.clearMemoryCache()
|
||||
}
|
||||
|
||||
KEY_MUTED_APP -> importTable(reader, MutedApp.table, null)
|
||||
|
@ -408,10 +406,10 @@ object AppDataExporter {
|
|||
}
|
||||
|
||||
run {
|
||||
val old_id = PrefL.lpTabletTootDefaultAccount(app_state.pref)
|
||||
val old_id = PrefL.lpTabletTootDefaultAccount.value
|
||||
if (old_id != -1L) {
|
||||
val new_id = account_id_map[old_id]
|
||||
app_state.pref.edit().put(PrefL.lpTabletTootDefaultAccount, new_id ?: -1L).apply()
|
||||
PrefL.lpTabletTootDefaultAccount.value = new_id ?: -1L
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,12 +10,17 @@ import android.widget.TextView
|
|||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import jp.juggler.subwaytooter.*
|
||||
import jp.juggler.subwaytooter.actmain.selectPushDistributor
|
||||
import jp.juggler.subwaytooter.dialog.runInProgress
|
||||
import jp.juggler.subwaytooter.drawable.MediaBackgroundDrawable
|
||||
import jp.juggler.subwaytooter.itemviewholder.AdditionalButtonsPosition
|
||||
import jp.juggler.subwaytooter.pref.*
|
||||
import jp.juggler.subwaytooter.pref.impl.*
|
||||
import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||
import jp.juggler.subwaytooter.table.sortedByNickname
|
||||
import jp.juggler.subwaytooter.util.CustomShareTarget
|
||||
import jp.juggler.subwaytooter.util.openBrowser
|
||||
import jp.juggler.util.coroutine.launchAndShowError
|
||||
import jp.juggler.util.data.cast
|
||||
import jp.juggler.util.data.intentOpenDocument
|
||||
import jp.juggler.util.data.notZero
|
||||
|
@ -24,6 +29,7 @@ import jp.juggler.util.ui.InputTypeEx
|
|||
import jp.juggler.util.ui.attrColor
|
||||
import jp.juggler.util.ui.getAdaptiveRippleDrawable
|
||||
import jp.juggler.util.ui.getAdaptiveRippleDrawableRound
|
||||
import kotlinx.coroutines.delay
|
||||
import org.jetbrains.anko.backgroundDrawable
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
|
@ -227,6 +233,10 @@ class AppSettingItem(
|
|||
val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_setting).apply {
|
||||
|
||||
section(R.string.notifications) {
|
||||
action(R.string.push_distributor) {
|
||||
action = { selectPushDistributor() }
|
||||
desc = R.string.push_distributor_desc
|
||||
}
|
||||
|
||||
text(
|
||||
PrefS.spPullNotificationCheckInterval,
|
||||
|
@ -447,14 +457,18 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
|
|||
) {
|
||||
val lp = pref.cast<LongPref>()!!
|
||||
spinnerInitializer = { spinner ->
|
||||
val adapter = AccountAdapter()
|
||||
spinner.adapter = adapter
|
||||
spinner.setSelection(adapter.getIndexFromId(lp(pref)))
|
||||
launchAndShowError {
|
||||
val list = daoSavedAccount.loadAccountList()
|
||||
.sortedByNickname()
|
||||
val adapter = AccountAdapter(list)
|
||||
spinner.adapter = adapter
|
||||
spinner.setSelection(adapter.getIndexFromId(lp.value))
|
||||
}
|
||||
}
|
||||
spinnerOnSelected = { spinner, index ->
|
||||
val adapter = spinner.adapter.cast<ActAppSetting.AccountAdapter>()
|
||||
?: error("spinnerOnSelected: missing AccountAdapter")
|
||||
pref.edit().put(lp, adapter.getIdFromIndex(index)).apply()
|
||||
spinner.adapter.cast<ActAppSetting.AccountAdapter>()
|
||||
?.getIdFromIndex(index)
|
||||
?.let { lp.value = it }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -522,12 +536,12 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
|
|||
spinnerInitializer = { spinner ->
|
||||
val adapter = TimeZoneAdapter()
|
||||
spinner.adapter = adapter
|
||||
spinner.setSelection(adapter.getIndexFromId(sp(pref)))
|
||||
spinner.setSelection(adapter.getIndexFromId(sp.value))
|
||||
}
|
||||
spinnerOnSelected = { spinner, index ->
|
||||
val adapter = spinner.adapter.cast<ActAppSetting.TimeZoneAdapter>()
|
||||
?: error("spinnerOnSelected: missing TimeZoneAdapter")
|
||||
pref.edit().put(sp, adapter.getIdFromIndex(index)).apply()
|
||||
sp.value = adapter.getIdFromIndex(index)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -581,7 +595,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
|
|||
}
|
||||
}
|
||||
onClickReset = {
|
||||
pref.edit().remove(item.pref?.key).apply()
|
||||
item.pref?.removeValue()
|
||||
showTimelineFont(item)
|
||||
}
|
||||
showTextView = { showTimelineFont(item, it) }
|
||||
|
@ -602,7 +616,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
|
|||
}
|
||||
}
|
||||
onClickReset = {
|
||||
pref.edit().remove(item.pref?.key).apply()
|
||||
item.pref?.removeValue()
|
||||
showTimelineFont(AppSettingItem.TIMELINE_FONT_BOLD)
|
||||
}
|
||||
showTextView = { showTimelineFont(item, it) }
|
||||
|
@ -621,7 +635,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
|
|||
fromFloat = { formatFontSize(it) }
|
||||
|
||||
captionFontSize = {
|
||||
val fv = fp(pref)
|
||||
val fv = fp.value
|
||||
when {
|
||||
!fv.isFinite() -> PrefF.default_timeline_font_size
|
||||
fv < 1f -> 1f
|
||||
|
@ -629,7 +643,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
|
|||
}
|
||||
}
|
||||
captionSpacing = {
|
||||
PrefS.spTimelineSpacing(pref).toFloatOrNull()
|
||||
PrefS.spTimelineSpacing.value.toFloatOrNull()
|
||||
}
|
||||
changed = {
|
||||
findItemViewHolder(item)?.updateCaption()
|
||||
|
@ -644,7 +658,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
|
|||
fromFloat = { formatFontSize(it) }
|
||||
|
||||
captionFontSize = {
|
||||
val fv = fp(pref)
|
||||
val fv = fp.value
|
||||
when {
|
||||
!fv.isFinite() -> PrefF.default_acct_font_size
|
||||
fv < 1f -> 1f
|
||||
|
@ -667,7 +681,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
|
|||
fromFloat = { formatFontSize(it) }
|
||||
|
||||
captionFontSize = {
|
||||
val fv = fp(pref)
|
||||
val fv = fp.value
|
||||
when {
|
||||
!fv.isFinite() -> PrefF.default_notification_tl_font_size
|
||||
fv < 1f -> 1f
|
||||
|
@ -675,7 +689,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
|
|||
}
|
||||
}
|
||||
captionSpacing = {
|
||||
PrefS.spTimelineSpacing(pref).toFloatOrNull()
|
||||
PrefS.spTimelineSpacing.value.toFloatOrNull()
|
||||
}
|
||||
changed = {
|
||||
findItemViewHolder(item)?.updateCaption()
|
||||
|
@ -718,7 +732,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
|
|||
fromFloat = { formatFontSize(it) }
|
||||
|
||||
captionFontSize = {
|
||||
val fv = fp(pref)
|
||||
val fv = fp.value
|
||||
when {
|
||||
!fv.isFinite() -> PrefF.default_header_font_size
|
||||
fv < 1f -> 1f
|
||||
|
@ -843,8 +857,8 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
|
|||
val ivColumnHeader: ImageView = viewRoot.findViewById(R.id.ivColumnHeader)
|
||||
val tvColumnName: TextView = viewRoot.findViewById(R.id.tvColumnName)
|
||||
|
||||
val colorColumnHeaderBg = PrefI.ipCcdHeaderBg(activity.pref)
|
||||
val colorColumnHeaderFg = PrefI.ipCcdHeaderFg(activity.pref)
|
||||
val colorColumnHeaderBg = PrefI.ipCcdHeaderBg.value
|
||||
val colorColumnHeaderFg = PrefI.ipCcdHeaderFg.value
|
||||
|
||||
val headerBg = when {
|
||||
colorColumnHeaderBg != 0 -> colorColumnHeaderBg
|
||||
|
@ -876,9 +890,9 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
|
|||
val tvSampleAcct: TextView = viewRoot.findViewById(R.id.tvSampleAcct)
|
||||
val tvSampleContent: TextView = viewRoot.findViewById(R.id.tvSampleContent)
|
||||
|
||||
val colorColumnBg = PrefI.ipCcdContentBg(activity.pref)
|
||||
val colorColumnAcct = PrefI.ipCcdContentAcct(activity.pref)
|
||||
val colorColumnText = PrefI.ipCcdContentText(activity.pref)
|
||||
val colorColumnBg = PrefI.ipCcdContentBg.value
|
||||
val colorColumnAcct = PrefI.ipCcdContentAcct.value
|
||||
val colorColumnText = PrefI.ipCcdContentText.value
|
||||
|
||||
flColumnBackground.setBackgroundColor(colorColumnBg) // may 0
|
||||
|
||||
|
@ -909,7 +923,6 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
|
|||
group(R.string.footer_color) {
|
||||
AppSettingItem.SAMPLE_FOOTER =
|
||||
sample(R.layout.setting_sample_footer) { activity, viewRoot ->
|
||||
val pref = activity.pref
|
||||
val ivFooterToot: AppCompatImageView = viewRoot.findViewById(R.id.ivFooterToot)
|
||||
val ivFooterMenu: AppCompatImageView = viewRoot.findViewById(R.id.ivFooterMenu)
|
||||
val llFooterBG: View = viewRoot.findViewById(R.id.llFooterBG)
|
||||
|
@ -917,11 +930,11 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
|
|||
val vFooterDivider2: View = viewRoot.findViewById(R.id.vFooterDivider2)
|
||||
val vIndicator: View = viewRoot.findViewById(R.id.vIndicator)
|
||||
|
||||
val footerButtonBgColor = PrefI.ipFooterButtonBgColor(pref)
|
||||
val footerButtonFgColor = PrefI.ipFooterButtonFgColor(pref)
|
||||
val footerTabBgColor = PrefI.ipFooterTabBgColor(pref)
|
||||
val footerTabDividerColor = PrefI.ipFooterTabDividerColor(pref)
|
||||
val footerTabIndicatorColor = PrefI.ipFooterTabIndicatorColor(pref)
|
||||
val footerButtonBgColor = PrefI.ipFooterButtonBgColor.value
|
||||
val footerButtonFgColor = PrefI.ipFooterButtonFgColor.value
|
||||
val footerTabBgColor = PrefI.ipFooterTabBgColor.value
|
||||
val footerTabDividerColor = PrefI.ipFooterTabDividerColor.value
|
||||
val footerTabIndicatorColor = PrefI.ipFooterTabIndicatorColor.value
|
||||
|
||||
val colorColumnStripBackground = footerTabBgColor.notZero()
|
||||
?: activity.attrColor(R.attr.colorColumnStripBackground)
|
||||
|
@ -1023,6 +1036,21 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
action(R.string.test_progress_dialog){
|
||||
action={
|
||||
launchAndShowError {
|
||||
runInProgress(cancellable=true) {
|
||||
it.setMessage("message")
|
||||
it.setTitle("title")
|
||||
delay(2000L)
|
||||
it.setMessage("message ".repeat(30))
|
||||
it.setTitle("title ".repeat(30))
|
||||
delay(2000L)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
action(R.string.app_data_export) {
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
package jp.juggler.subwaytooter.auth
|
||||
|
||||
import android.content.Context
|
||||
import jp.juggler.subwaytooter.api.TootApiClient
|
||||
import jp.juggler.subwaytooter.api.TootParser
|
||||
import jp.juggler.subwaytooter.api.auth.Auth2Result
|
||||
import jp.juggler.subwaytooter.api.entity.EntityId
|
||||
import jp.juggler.subwaytooter.notification.checkNotificationImmediate
|
||||
import jp.juggler.subwaytooter.notification.checkNotificationImmediateAll
|
||||
import jp.juggler.subwaytooter.pref.PrefL
|
||||
import jp.juggler.subwaytooter.pref.lazyContext
|
||||
import jp.juggler.subwaytooter.table.AcctColor
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.appDatabase
|
||||
import jp.juggler.util.log.LogCategory
|
||||
|
||||
val Context.authRepo
|
||||
get() = AuthRepo(
|
||||
context = this,
|
||||
daoAcctColor = AcctColor.Access(appDatabase),
|
||||
daoSavedAccount = SavedAccount.Access(appDatabase, lazyContext),
|
||||
)
|
||||
|
||||
class AuthRepo(
|
||||
private val context: Context = lazyContext,
|
||||
private val daoAcctColor: AcctColor.Access =
|
||||
AcctColor.Access(appDatabase),
|
||||
private val daoSavedAccount: SavedAccount.Access =
|
||||
SavedAccount.Access(appDatabase, lazyContext),
|
||||
) {
|
||||
companion object {
|
||||
private val log = LogCategory("AuthRepo")
|
||||
}
|
||||
|
||||
/**
|
||||
* ユーザ登録の確認手順が完了しているかどうか
|
||||
*
|
||||
* - マストドン以外だと何もしないはず
|
||||
*/
|
||||
suspend fun checkConfirmed(item: SavedAccount, client: TootApiClient) {
|
||||
// 承認待ち状態ではないならチェックしない
|
||||
if (item.loginAccount?.id != EntityId.CONFIRMING) return
|
||||
|
||||
// DBに保存されていないならチェックしない
|
||||
if (item.db_id == SavedAccount.INVALID_DB_ID) return
|
||||
|
||||
// アクセストークンがないならチェックしない
|
||||
val accessToken = item.bearerAccessToken ?: return
|
||||
|
||||
// ユーザ情報を取得してみる。承認済みなら読めるはず
|
||||
// 読めなければ例外が出る
|
||||
val userJson = client.verifyAccount(
|
||||
accessToken = accessToken,
|
||||
outTokenInfo = null,
|
||||
misskeyVersion = 0, // Mastodon only
|
||||
)
|
||||
// 読めたらアプリ内の記録を更新する
|
||||
TootParser(context, item).account(userJson)?.let { ta ->
|
||||
item.loginAccount = ta
|
||||
daoSavedAccount.saveSetting(item)
|
||||
checkNotificationImmediateAll(context, onlySubscription = true)
|
||||
checkNotificationImmediate(context, item.db_id)
|
||||
}
|
||||
}
|
||||
|
||||
fun accountRemove(account: SavedAccount) {
|
||||
// if account is default account of tablet mode,
|
||||
// reset default.
|
||||
if (account.db_id == PrefL.lpTabletTootDefaultAccount.value) {
|
||||
PrefL.lpTabletTootDefaultAccount.value = -1L
|
||||
}
|
||||
daoSavedAccount.delete(account.db_id)
|
||||
// appServerUnregister(context.applicationContextSafe, account)
|
||||
}
|
||||
|
||||
fun updateTokenInfo(item: SavedAccount, auth2Result: Auth2Result) {
|
||||
item.token_info = auth2Result.tokenJson
|
||||
item.loginAccount = auth2Result.tootAccount
|
||||
item.misskeyVersion = auth2Result.tootInstance.misskeyVersionMajor
|
||||
daoSavedAccount.saveSetting(item)
|
||||
}
|
||||
|
||||
// notification_tagがもう使われてない
|
||||
// private fun appServerUnregister(context: Context, account: SavedAccount) {
|
||||
// launchIO {
|
||||
// try {
|
||||
// val installId = PrefDevice.from(context).getString(PrefDevice.KEY_INSTALL_ID, null)
|
||||
// if (installId?.isEmpty() != false) {
|
||||
// error("missing install_id")
|
||||
// }
|
||||
//
|
||||
// val tag = "" // notification_tagはもう使われてない
|
||||
// if (tag.isNullO) {
|
||||
// error("missing notification_tag")
|
||||
// }
|
||||
//
|
||||
// val call = App1.ok_http_client.newCall(
|
||||
// "instance_url=${
|
||||
// "https://${account.apiHost.ascii}".encodePercent()
|
||||
// }&app_id=${
|
||||
// context.packageName.encodePercent()
|
||||
// }&tag=$tag"
|
||||
// .toFormRequestBody()
|
||||
// .toPost()
|
||||
// .url("$APP_SERVER/unregister")
|
||||
// .build()
|
||||
// )
|
||||
//
|
||||
// val response = call.await()
|
||||
// if (!response.isSuccessful) {
|
||||
// log.e("appServerUnregister: $response")
|
||||
// }
|
||||
// } catch (ex: Throwable) {
|
||||
// log.e(ex, "appServerUnregister failed.")
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
package jp.juggler.subwaytooter.column
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.util.SparseArray
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import jp.juggler.subwaytooter.AppState
|
||||
|
@ -13,6 +12,7 @@ import jp.juggler.subwaytooter.pref.PrefI
|
|||
import jp.juggler.subwaytooter.streaming.StreamCallback
|
||||
import jp.juggler.subwaytooter.streaming.StreamStatus
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||
import jp.juggler.subwaytooter.util.BucketList
|
||||
import jp.juggler.subwaytooter.util.ScrollPosition
|
||||
import jp.juggler.util.data.*
|
||||
|
@ -64,10 +64,10 @@ class Column(
|
|||
internal const val QUICK_FILTER_VOTE = 6
|
||||
internal const val QUICK_FILTER_POST = 7
|
||||
|
||||
fun loadAccount(context: Context, src: JsonObject): SavedAccount {
|
||||
fun loadAccount(src: JsonObject): SavedAccount {
|
||||
val account_db_id = src.long(ColumnEncoder.KEY_ACCOUNT_ROW_ID) ?: -1L
|
||||
return if (account_db_id >= 0) {
|
||||
SavedAccount.loadAccount(context, account_db_id)
|
||||
return if (account_db_id > 0) {
|
||||
daoSavedAccount.loadAccount(account_db_id)
|
||||
?: error("missing account")
|
||||
} else {
|
||||
SavedAccount.na
|
||||
|
@ -91,24 +91,24 @@ class Column(
|
|||
var defaultColorContentAcct = 0
|
||||
var defaultColorContentText = 0
|
||||
|
||||
fun reloadDefaultColor(activity: AppCompatActivity, pref: SharedPreferences) {
|
||||
fun reloadDefaultColor(activity: AppCompatActivity) {
|
||||
|
||||
defaultColorHeaderBg = PrefI.ipCcdHeaderBg(pref).notZero()
|
||||
defaultColorHeaderBg = PrefI.ipCcdHeaderBg.value.notZero()
|
||||
?: activity.attrColor(R.attr.color_column_header)
|
||||
|
||||
defaultColorHeaderName = PrefI.ipCcdHeaderFg(pref).notZero()
|
||||
defaultColorHeaderName = PrefI.ipCcdHeaderFg.value.notZero()
|
||||
?: activity.attrColor(R.attr.colorColumnHeaderName)
|
||||
|
||||
defaultColorHeaderPageNumber = PrefI.ipCcdHeaderFg(pref).notZero()
|
||||
defaultColorHeaderPageNumber = PrefI.ipCcdHeaderFg.value.notZero()
|
||||
?: activity.attrColor(R.attr.colorColumnHeaderPageNumber)
|
||||
|
||||
defaultColorContentBg = PrefI.ipCcdContentBg(pref)
|
||||
defaultColorContentBg = PrefI.ipCcdContentBg.value
|
||||
// may zero
|
||||
|
||||
defaultColorContentAcct = PrefI.ipCcdContentAcct(pref).notZero()
|
||||
defaultColorContentAcct = PrefI.ipCcdContentAcct.value.notZero()
|
||||
?: activity.attrColor(R.attr.colorTimeSmall)
|
||||
|
||||
defaultColorContentText = PrefI.ipCcdContentText(pref).notZero()
|
||||
defaultColorContentText = PrefI.ipCcdContentText.value.notZero()
|
||||
?: activity.attrColor(R.attr.colorTextContent)
|
||||
}
|
||||
|
||||
|
@ -257,7 +257,7 @@ class Column(
|
|||
var keywordFilterTrees: FilterTrees? = null
|
||||
|
||||
@Volatile
|
||||
var favMuteSet: HashSet<Acct>? = null
|
||||
var favMuteSet: Set<Acct>? = null
|
||||
|
||||
@Volatile
|
||||
var highlightTrie: WordTrieTree? = null
|
||||
|
@ -341,7 +341,7 @@ class Column(
|
|||
internal constructor(appState: AppState, src: JsonObject) : this(
|
||||
appState,
|
||||
appState.context,
|
||||
loadAccount(appState.context, src),
|
||||
loadAccount(src),
|
||||
src.optInt(ColumnEncoder.KEY_TYPE),
|
||||
ColumnEncoder.decodeColumnId(src)
|
||||
) {
|
||||
|
|
|
@ -192,7 +192,7 @@ fun Column.removeNotifications() {
|
|||
listData.clear()
|
||||
duplicateMap.clear()
|
||||
fireShowContent(reason = "removeNotifications", reset = true)
|
||||
onNotificationCleared(context, accessInfo.db_id)
|
||||
onNotificationCleared(accessInfo.db_id)
|
||||
}
|
||||
|
||||
// 通知を削除した後に呼ばれる
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package jp.juggler.subwaytooter.column
|
||||
|
||||
import jp.juggler.subwaytooter.api.entity.EntityId
|
||||
import jp.juggler.subwaytooter.table.AcctColor
|
||||
import jp.juggler.subwaytooter.table.daoAcctColor
|
||||
import jp.juggler.util.data.JsonException
|
||||
import jp.juggler.util.data.JsonObject
|
||||
import jp.juggler.util.data.encodeBase64Url
|
||||
|
@ -248,11 +248,11 @@ object ColumnEncoder {
|
|||
}
|
||||
|
||||
// 以下は保存には必要ないが、カラムリスト画面で使う
|
||||
val ac = AcctColor.load(accessInfo)
|
||||
val ac = daoAcctColor.load(accessInfo)
|
||||
dst[KEY_COLUMN_ACCESS_ACCT] = accessInfo.acct.ascii
|
||||
dst[KEY_COLUMN_ACCESS_STR] = ac.nickname
|
||||
dst[KEY_COLUMN_ACCESS_COLOR] = ac.color_fg
|
||||
dst[KEY_COLUMN_ACCESS_COLOR_BG] = ac.color_bg
|
||||
dst[KEY_COLUMN_ACCESS_COLOR] = ac.colorFg
|
||||
dst[KEY_COLUMN_ACCESS_COLOR_BG] = ac.colorBg
|
||||
dst[KEY_COLUMN_NAME] = getColumnName(true)
|
||||
dst[KEY_OLD_INDEX] = oldIndex
|
||||
}
|
||||
|
|
|
@ -197,7 +197,7 @@ fun Column.onActivityStart() {
|
|||
|
||||
if (!bRefreshLoading &&
|
||||
canAutoRefresh() &&
|
||||
!PrefB.bpDontRefreshOnResume(appState.pref) &&
|
||||
!PrefB.bpDontRefreshOnResume.value &&
|
||||
!dontAutoRefresh
|
||||
) {
|
||||
// リフレッシュしてからストリーミング開始
|
||||
|
@ -237,7 +237,7 @@ fun Column.startLoading() {
|
|||
|
||||
initFilter()
|
||||
|
||||
Column.showOpenSticker = PrefB.bpOpenSticker(appState.pref)
|
||||
Column.showOpenSticker = PrefB.bpOpenSticker.value
|
||||
|
||||
mRefreshLoadingErrorPopupState = 0
|
||||
mRefreshLoadingError = ""
|
||||
|
|
|
@ -7,10 +7,7 @@ import jp.juggler.subwaytooter.api.ApiTask
|
|||
import jp.juggler.subwaytooter.api.TootApiClient
|
||||
import jp.juggler.subwaytooter.api.entity.*
|
||||
import jp.juggler.subwaytooter.api.runApiTask
|
||||
import jp.juggler.subwaytooter.table.FavMute
|
||||
import jp.juggler.subwaytooter.table.HighlightWord
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.UserRelation
|
||||
import jp.juggler.subwaytooter.table.*
|
||||
import jp.juggler.util.*
|
||||
import jp.juggler.util.coroutine.launchMain
|
||||
import jp.juggler.util.data.WordTrieTree
|
||||
|
@ -171,8 +168,8 @@ fun Column.initFilter() {
|
|||
}
|
||||
}
|
||||
|
||||
favMuteSet = FavMute.acctSet
|
||||
highlightTrie = HighlightWord.nameSet
|
||||
favMuteSet = daoFavMute.acctSet()
|
||||
highlightTrie = daoHighlightWord.nameSet()
|
||||
}
|
||||
|
||||
private fun Column.isFilteredByAttachment(status: TootStatus): Boolean {
|
||||
|
@ -252,10 +249,10 @@ fun Column.isFiltered(status: TootStatus): Boolean {
|
|||
if (checkLanguageFilter(status)) return true
|
||||
|
||||
if (accessInfo.isPseudo) {
|
||||
var r = UserRelation.loadPseudo(accessInfo.getFullAcct(status.account))
|
||||
var r = daoUserRelation.loadPseudo(accessInfo.getFullAcct(status.account))
|
||||
if (r.muting || r.blocking) return true
|
||||
if (reblog != null) {
|
||||
r = UserRelation.loadPseudo(accessInfo.getFullAcct(reblog.account))
|
||||
r = daoUserRelation.loadPseudo(accessInfo.getFullAcct(reblog.account))
|
||||
if (r.muting || r.blocking) return true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -167,8 +167,8 @@ fun Column.mergeStreamingMessage() {
|
|||
App1.sound(it)
|
||||
}
|
||||
}
|
||||
o.highlightSpeech?.let {
|
||||
appState.addSpeech(it.name, dedupMode = DedupMode.RecentExpire)
|
||||
o.highlightSpeech?.name?.notEmpty()?.let {
|
||||
appState.addSpeech(it, dedupMode = DedupMode.RecentExpire)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package jp.juggler.subwaytooter.column
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.os.SystemClock
|
||||
import jp.juggler.subwaytooter.api.ApiPath
|
||||
import jp.juggler.subwaytooter.api.TootApiClient
|
||||
|
@ -72,9 +71,6 @@ abstract class ColumnTask(
|
|||
val misskeyVersion: Int
|
||||
get() = accessInfo.misskeyVersion
|
||||
|
||||
val pref: SharedPreferences
|
||||
get() = column.appState.pref
|
||||
|
||||
internal fun JsonObject.addMisskeyNotificationFilter() = addMisskeyNotificationFilter(column)
|
||||
internal fun JsonObject.addRangeMisskey(bBottom: Boolean) = addRangeMisskey(column, bBottom)
|
||||
|
||||
|
|
|
@ -128,7 +128,7 @@ class ColumnTask_Gap(
|
|||
val iv = when {
|
||||
isHead -> PrefI.ipGapHeadScrollPosition
|
||||
else -> PrefI.ipGapTailScrollPosition
|
||||
}.invoke(pref)
|
||||
}.value
|
||||
val scrollHead = iv == PrefI.GSP_HEAD
|
||||
|
||||
if (scrollHead) {
|
||||
|
|
|
@ -5,6 +5,7 @@ import jp.juggler.subwaytooter.R
|
|||
import jp.juggler.subwaytooter.api.*
|
||||
import jp.juggler.subwaytooter.api.entity.*
|
||||
import jp.juggler.subwaytooter.api.finder.*
|
||||
import jp.juggler.subwaytooter.auth.authRepo
|
||||
import jp.juggler.subwaytooter.columnviewholder.scrollToTop
|
||||
import jp.juggler.subwaytooter.notification.injectData
|
||||
import jp.juggler.subwaytooter.pref.PrefB
|
||||
|
@ -33,7 +34,7 @@ class ColumnTask_Loading(
|
|||
override suspend fun background(): TootApiResult? {
|
||||
ctStarted.set(true)
|
||||
|
||||
if (PrefB.bpOpenSticker(pref)) {
|
||||
if (PrefB.bpOpenSticker.value) {
|
||||
OpenSticker.loadAndWait()
|
||||
}
|
||||
|
||||
|
@ -52,7 +53,7 @@ class ColumnTask_Loading(
|
|||
client.account = accessInfo
|
||||
|
||||
try {
|
||||
accessInfo.checkConfirmed(context, client)
|
||||
context.authRepo.checkConfirmed(accessInfo, client)
|
||||
|
||||
column.keywordFilterTrees = column.encodeFilterTree(column.loadFilter2(client))
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ import jp.juggler.util.coroutine.runOnMainLooper
|
|||
import jp.juggler.util.coroutine.runOnMainLooperDelayed
|
||||
import jp.juggler.util.data.JsonArray
|
||||
import jp.juggler.util.data.JsonObject
|
||||
import jp.juggler.util.data.notEmpty
|
||||
import jp.juggler.util.log.LogCategory
|
||||
import jp.juggler.util.log.withCaption
|
||||
import jp.juggler.util.network.toPostRequestBuilder
|
||||
|
@ -157,8 +158,8 @@ class ColumnTask_Refresh(
|
|||
App1.sound(it)
|
||||
}
|
||||
}
|
||||
o.highlightSpeech?.let {
|
||||
column.appState.addSpeech(it.name, dedupMode = DedupMode.RecentExpire)
|
||||
o.highlightSpeech?.name.notEmpty()?.let {
|
||||
column.appState.addSpeech(it, dedupMode = DedupMode.RecentExpire)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -306,7 +307,7 @@ class ColumnTask_Refresh(
|
|||
isCancelled -> false
|
||||
listTmp?.isNotEmpty() != true -> false
|
||||
willAddGap -> true
|
||||
else -> PrefB.bpForceGap()
|
||||
else -> PrefB.bpForceGap.value
|
||||
}
|
||||
|
||||
if (doesAddGap()) {
|
||||
|
@ -495,7 +496,7 @@ class ColumnTask_Refresh(
|
|||
|
||||
if (!isCancelled &&
|
||||
listTmp?.isNotEmpty() == true &&
|
||||
(willAddGap || PrefB.bpForceGap(context))
|
||||
(willAddGap || PrefB.bpForceGap.value)
|
||||
) {
|
||||
addOne(listTmp, TootGap.mayNull(maxId, lastSinceId), head = addToHead)
|
||||
}
|
||||
|
@ -582,7 +583,7 @@ class ColumnTask_Refresh(
|
|||
|
||||
if (!isCancelled &&
|
||||
listTmp?.isNotEmpty() == true &&
|
||||
(willAddGap || PrefB.bpForceGap(context))
|
||||
(willAddGap || PrefB.bpForceGap.value)
|
||||
) {
|
||||
addOne(listTmp, TootGap.mayNull(maxId, lastSinceId), head = addToHead)
|
||||
}
|
||||
|
@ -690,16 +691,14 @@ class ColumnTask_Refresh(
|
|||
params.apply {
|
||||
if (!bBottom) {
|
||||
if (first) {
|
||||
|
||||
addRangeMisskey(bBottom)
|
||||
addRangeMisskey(bBottom = false)
|
||||
} else {
|
||||
putMisskeySince(column.idRecent)
|
||||
}
|
||||
} else {
|
||||
if (first) {
|
||||
|
||||
when (column.pagingType) {
|
||||
ColumnPagingType.Default -> addRangeMisskey(bBottom)
|
||||
ColumnPagingType.Default -> addRangeMisskey(bBottom = true)
|
||||
ColumnPagingType.Offset -> put("offset", column.offsetNext)
|
||||
ColumnPagingType.Cursor -> put("cursor", column.idOld)
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ import jp.juggler.subwaytooter.search.NotestockHelper.refreshNotestock
|
|||
import jp.juggler.subwaytooter.search.TootsearchHelper.loadingTootsearch
|
||||
import jp.juggler.subwaytooter.search.TootsearchHelper.refreshTootsearch
|
||||
import jp.juggler.subwaytooter.streaming.StreamSpec
|
||||
import jp.juggler.subwaytooter.table.AcctColor
|
||||
import jp.juggler.subwaytooter.table.daoAcctColor
|
||||
import jp.juggler.util.*
|
||||
import jp.juggler.util.data.*
|
||||
import jp.juggler.util.log.LogCategory
|
||||
|
@ -685,7 +685,7 @@ enum class ColumnType(
|
|||
R.string.profile_of,
|
||||
when (who) {
|
||||
null -> profileId.toString()
|
||||
else -> AcctColor.getNickname(accessInfo, who)
|
||||
else -> daoAcctColor.getNickname(accessInfo, who)
|
||||
}
|
||||
)
|
||||
},
|
||||
|
|
|
@ -3,12 +3,13 @@ package jp.juggler.subwaytooter.column
|
|||
import jp.juggler.subwaytooter.api.TootApiClient
|
||||
import jp.juggler.subwaytooter.api.TootParser
|
||||
import jp.juggler.subwaytooter.api.entity.*
|
||||
import jp.juggler.subwaytooter.table.AcctSet
|
||||
import jp.juggler.subwaytooter.table.TagSet
|
||||
import jp.juggler.subwaytooter.table.UserRelation
|
||||
import jp.juggler.subwaytooter.table.daoAcctSet
|
||||
import jp.juggler.subwaytooter.table.daoTagHistory
|
||||
import jp.juggler.subwaytooter.table.daoUserRelation
|
||||
import jp.juggler.util.data.toJsonArray
|
||||
import jp.juggler.util.log.LogCategory
|
||||
import jp.juggler.util.network.toPostRequestBuilder
|
||||
import kotlin.math.min
|
||||
|
||||
class UserRelationLoader(val column: Column) {
|
||||
companion object {
|
||||
|
@ -63,7 +64,13 @@ class UserRelationLoader(val column: Column) {
|
|||
while (start < end) {
|
||||
var step = end - start
|
||||
if (step > Column.RELATIONSHIP_LOAD_STEP) step = Column.RELATIONSHIP_LOAD_STEP
|
||||
UserRelation.saveListMisskey(now, column.accessInfo.db_id, whoList, start, step)
|
||||
daoUserRelation.saveListMisskey(
|
||||
now,
|
||||
column.accessInfo.db_id,
|
||||
whoList,
|
||||
start,
|
||||
step
|
||||
)
|
||||
start += step
|
||||
}
|
||||
log.d("updateRelation: update $end relations.")
|
||||
|
@ -108,7 +115,11 @@ class UserRelationLoader(val column: Column) {
|
|||
for (i in 0 until list.size) {
|
||||
list[i].id = userIdList[i]
|
||||
}
|
||||
UserRelation.saveListMisskeyRelationApi(now, column.accessInfo.db_id, list)
|
||||
daoUserRelation.saveListMisskeyRelationApi(
|
||||
now,
|
||||
column.accessInfo.db_id,
|
||||
list
|
||||
)
|
||||
}
|
||||
}
|
||||
log.d("updateRelation: update $n relations.")
|
||||
|
@ -134,7 +145,7 @@ class UserRelationLoader(val column: Column) {
|
|||
}
|
||||
val result = client.request(sb.toString()) ?: break // cancelled.
|
||||
val list = parseList(::TootRelationShip, parser, result.jsonArray)
|
||||
if (list.size > 0) UserRelation.saveListMastodon(
|
||||
if (list.size > 0) daoUserRelation.saveListMastodon(
|
||||
now,
|
||||
column.accessInfo.db_id,
|
||||
list
|
||||
|
@ -156,7 +167,7 @@ class UserRelationLoader(val column: Column) {
|
|||
while (n < acctList.size) {
|
||||
var length = size - n
|
||||
if (length > Column.ACCT_DB_STEP) length = Column.ACCT_DB_STEP
|
||||
AcctSet.saveList(now, acctList, n, length)
|
||||
daoAcctSet.saveList(now, acctList, n, length)
|
||||
n += length
|
||||
}
|
||||
log.d("updateRelation: update $n acct.")
|
||||
|
@ -171,11 +182,10 @@ class UserRelationLoader(val column: Column) {
|
|||
val now = System.currentTimeMillis()
|
||||
|
||||
n = 0
|
||||
while (n < tagList.size) {
|
||||
var length = size - n
|
||||
if (length > Column.ACCT_DB_STEP) length = Column.ACCT_DB_STEP
|
||||
TagSet.saveList(now, tagList, n, length)
|
||||
n += length
|
||||
while (n < size) {
|
||||
val step = min(Column.ACCT_DB_STEP, size - n)
|
||||
daoTagHistory.saveList(now, tagList, n, step)
|
||||
n += step
|
||||
}
|
||||
log.d("updateRelation: update $n tag.")
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ import jp.juggler.subwaytooter.*
|
|||
import jp.juggler.subwaytooter.column.*
|
||||
import jp.juggler.subwaytooter.pref.PrefB
|
||||
import jp.juggler.subwaytooter.streaming.*
|
||||
import jp.juggler.subwaytooter.table.AcctColor
|
||||
import jp.juggler.subwaytooter.table.daoAcctColor
|
||||
import jp.juggler.subwaytooter.util.*
|
||||
import jp.juggler.subwaytooter.view.MyLinkMovementMethod
|
||||
import jp.juggler.subwaytooter.view.MyTextView
|
||||
|
@ -219,15 +219,15 @@ class ColumnViewHolder(
|
|||
val column = this.column
|
||||
if (column == null || column.isDispose.get()) return@Runnable
|
||||
|
||||
val ac = AcctColor.load(column.accessInfo)
|
||||
val ac = daoAcctColor.load(column.accessInfo)
|
||||
|
||||
tvColumnContext.text = ac.nickname
|
||||
tvColumnContext.setTextColor(
|
||||
ac.color_fg.notZero()
|
||||
ac.colorFg.notZero()
|
||||
?: activity.attrColor(R.attr.colorTimeSmall)
|
||||
)
|
||||
|
||||
tvColumnContext.setBackgroundColor(ac.color_bg)
|
||||
tvColumnContext.setBackgroundColor(ac.colorBg)
|
||||
tvColumnContext.setPaddingRelative(activity.acctPadLr, 0, activity.acctPadLr, 0)
|
||||
|
||||
tvColumnName.text = column.getColumnName(false)
|
||||
|
@ -358,7 +358,7 @@ class ColumnViewHolder(
|
|||
}
|
||||
}
|
||||
|
||||
if (PrefB.bpShareViewPool(activity.pref)) {
|
||||
if (PrefB.bpShareViewPool.value) {
|
||||
listView.setRecycledViewPool(activity.viewPool)
|
||||
}
|
||||
listView.itemAnimator = null
|
||||
|
@ -432,7 +432,7 @@ class ColumnViewHolder(
|
|||
cbWithHighlight,
|
||||
).forEach { it.setOnCheckedChangeListener(this) }
|
||||
|
||||
if (PrefB.bpMoveNotificationsQuickFilter(activity.pref)) {
|
||||
if (PrefB.bpMoveNotificationsQuickFilter.value) {
|
||||
(svQuickFilter.parent as? ViewGroup)?.removeView(svQuickFilter)
|
||||
llColumnSettingInside.addView(svQuickFilter, 0)
|
||||
|
||||
|
|
|
@ -337,7 +337,7 @@ private fun ColumnViewHolder.showReactions(
|
|||
)
|
||||
|
||||
val actMain = activity
|
||||
val disableEmojiAnimation = PrefB.bpDisableEmojiAnimation(actMain.pref)
|
||||
val disableEmojiAnimation = PrefB.bpDisableEmojiAnimation.value
|
||||
|
||||
for (reaction in reactions) {
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ fun ColumnViewHolder.closeBitmaps() {
|
|||
|
||||
fun ColumnViewHolder.loadBackgroundImage(iv: ImageView, url: String?) {
|
||||
try {
|
||||
if (url == null || url.isEmpty() || PrefB.bpDontShowColumnBackgroundImage(activity.pref)) {
|
||||
if (url == null || url.isEmpty() || PrefB.bpDontShowColumnBackgroundImage.value) {
|
||||
// 指定がないなら閉じる
|
||||
closeBitmaps()
|
||||
return
|
||||
|
@ -128,7 +128,7 @@ fun ColumnViewHolder.onPageCreate(column: Column, pageIdx: Int, pageCount: Int)
|
|||
|
||||
ColumnViewHolder.log.d("onPageCreate [$pageIdx] ${column.getColumnName(true)}")
|
||||
|
||||
val bSimpleList = !column.isConversation && PrefB.bpSimpleList(activity.pref)
|
||||
val bSimpleList = !column.isConversation && PrefB.bpSimpleList.value
|
||||
|
||||
tvColumnIndex.text = activity.getString(R.string.column_index, pageIdx + 1, pageCount)
|
||||
tvColumnStatus.text = "?"
|
||||
|
@ -221,7 +221,7 @@ fun ColumnViewHolder.onPageCreate(column: Column, pageIdx: Int, pageCount: Int)
|
|||
btnEmojiAdd.vg(false)
|
||||
|
||||
etSearch.vg(true)
|
||||
btnSearchClear.vg(PrefB.bpShowSearchClear(activity.pref))
|
||||
btnSearchClear.vg(PrefB.bpShowSearchClear.value)
|
||||
cbResolve.vg(column.type == ColumnType.SEARCH)
|
||||
}
|
||||
|
||||
|
@ -281,7 +281,7 @@ fun ColumnViewHolder.onPageCreate(column: Column, pageIdx: Int, pageCount: Int)
|
|||
fun dip(dp: Int): Int = (activity.density * dp + 0.5f).toInt()
|
||||
val context = activity
|
||||
|
||||
val announcementsBgColor = PrefI.ipAnnouncementsBgColor().notZero()
|
||||
val announcementsBgColor = PrefI.ipAnnouncementsBgColor.value.notZero()
|
||||
?: context.attrColor(R.attr.colorSearchFormBackground)
|
||||
|
||||
btnAnnouncementsCutout.apply {
|
||||
|
@ -294,7 +294,7 @@ fun ColumnViewHolder.onPageCreate(column: Column, pageIdx: Int, pageCount: Int)
|
|||
setPadding(0, padV, 0, padV)
|
||||
}
|
||||
|
||||
val searchBgColor = PrefI.ipSearchBgColor().notZero()
|
||||
val searchBgColor = PrefI.ipSearchBgColor.value.notZero()
|
||||
?: context.attrColor(R.attr.colorSearchFormBackground)
|
||||
|
||||
llSearch.apply {
|
||||
|
|
|
@ -30,7 +30,7 @@ fun ColumnViewHolder.showQuickFilter() {
|
|||
btnQuickFilterReaction.vg(column.isMisskey)
|
||||
btnQuickFilterFavourite.vg(!column.isMisskey)
|
||||
|
||||
val insideColumnSetting = PrefB.bpMoveNotificationsQuickFilter(activity.pref)
|
||||
val insideColumnSetting = PrefB.bpMoveNotificationsQuickFilter.value
|
||||
|
||||
val showQuickFilterButton: (btn: View, iconId: Int, selected: Boolean) -> Unit
|
||||
|
||||
|
|
|
@ -28,9 +28,7 @@ import jp.juggler.subwaytooter.span.EmojiImageSpan
|
|||
import jp.juggler.subwaytooter.span.LinkInfo
|
||||
import jp.juggler.subwaytooter.span.MyClickableSpan
|
||||
import jp.juggler.subwaytooter.span.createSpan
|
||||
import jp.juggler.subwaytooter.table.AcctColor
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.UserRelation
|
||||
import jp.juggler.subwaytooter.table.*
|
||||
import jp.juggler.subwaytooter.util.DecodeOptions
|
||||
import jp.juggler.subwaytooter.util.NetworkEmojiInvalidator
|
||||
import jp.juggler.subwaytooter.util.openCustomTab
|
||||
|
@ -356,7 +354,7 @@ internal class ViewHolderHeaderProfile(
|
|||
whoDetail?.statuses_count ?: who.statuses_count
|
||||
}"
|
||||
|
||||
val hideFollowCount = PrefB.bpHideFollowCount(activity.pref)
|
||||
val hideFollowCount = PrefB.bpHideFollowCount.value
|
||||
|
||||
var caption = activity.getString(R.string.following)
|
||||
btnFollowing.text = when {
|
||||
|
@ -370,7 +368,7 @@ internal class ViewHolderHeaderProfile(
|
|||
else -> "${caption}\n${whoDetail?.followers_count ?: who.followers_count}"
|
||||
}
|
||||
|
||||
val relation = UserRelation.load(accessInfo.db_id, who.id)
|
||||
val relation = daoUserRelation.load(accessInfo.db_id, who.id)
|
||||
this.relation = relation
|
||||
setFollowIcon(
|
||||
activity,
|
||||
|
@ -414,7 +412,7 @@ internal class ViewHolderHeaderProfile(
|
|||
|
||||
setAcct(tvMovedAcct, accessInfo, moved)
|
||||
|
||||
val relation = UserRelation.load(accessInfo.db_id, moved.id)
|
||||
val relation = daoUserRelation.load(accessInfo.db_id, moved.id)
|
||||
setFollowIcon(
|
||||
activity,
|
||||
btnMoved,
|
||||
|
@ -482,7 +480,11 @@ internal class ViewHolderHeaderProfile(
|
|||
val lastColumn = column
|
||||
DlgTextInput.show(
|
||||
activity,
|
||||
AcctColor.getStringWithNickname(activity, R.string.personal_notes_of, who.acct),
|
||||
daoAcctColor.getStringWithNickname(
|
||||
activity,
|
||||
R.string.personal_notes_of,
|
||||
who.acct
|
||||
),
|
||||
relation?.note ?: "",
|
||||
allowEmpty = true,
|
||||
callback = object : DlgTextInput.Callback {
|
||||
|
@ -551,16 +553,16 @@ internal class ViewHolderHeaderProfile(
|
|||
}
|
||||
|
||||
private fun setAcct(tv: TextView, accessInfo: SavedAccount, who: TootAccount) {
|
||||
val ac = AcctColor.load(accessInfo, who)
|
||||
val ac = daoAcctColor.load(accessInfo, who)
|
||||
tv.text = when {
|
||||
AcctColor.hasNickname(ac) -> ac.nickname
|
||||
PrefB.bpShortAcctLocalUser() -> "@${who.acct.pretty}"
|
||||
daoAcctColor.hasNickname(ac) -> ac.nickname
|
||||
PrefB.bpShortAcctLocalUser.value -> "@${who.acct.pretty}"
|
||||
else -> "@${ac.nickname}"
|
||||
}
|
||||
|
||||
tv.textColor = ac.color_fg.notZero() ?: column.getAcctColor()
|
||||
tv.textColor = ac.colorFg.notZero() ?: column.getAcctColor()
|
||||
|
||||
tv.setBackgroundColor(ac.color_bg) // may 0
|
||||
tv.setBackgroundColor(ac.colorBg) // may 0
|
||||
tv.setPaddingRelative(activity.acctPadLr, 0, activity.acctPadLr, 0)
|
||||
}
|
||||
|
||||
|
@ -594,7 +596,7 @@ internal class ViewHolderHeaderProfile(
|
|||
when {
|
||||
emoji == null ->
|
||||
append("locked")
|
||||
PrefB.bpUseTwemoji() ->
|
||||
PrefB.bpUseTwemoji.value ->
|
||||
appendSpan("locked", emoji.createSpan(activity))
|
||||
else ->
|
||||
append(emoji.unifiedCode)
|
||||
|
@ -607,7 +609,7 @@ internal class ViewHolderHeaderProfile(
|
|||
when {
|
||||
emoji == null ->
|
||||
append("bot")
|
||||
PrefB.bpUseTwemoji() ->
|
||||
PrefB.bpUseTwemoji.value ->
|
||||
appendSpan("bot", emoji.createSpan(activity))
|
||||
else ->
|
||||
append(emoji.unifiedCode)
|
||||
|
@ -620,7 +622,7 @@ internal class ViewHolderHeaderProfile(
|
|||
when {
|
||||
emoji == null ->
|
||||
append("suspended")
|
||||
PrefB.bpUseTwemoji() ->
|
||||
PrefB.bpUseTwemoji.value ->
|
||||
appendSpan("suspended", emoji.createSpan(activity))
|
||||
else ->
|
||||
append(emoji.unifiedCode)
|
||||
|
@ -715,7 +717,7 @@ internal class ViewHolderHeaderProfile(
|
|||
valueText.append(TootStatus.formatTime(activity, item.verified_at, false))
|
||||
val end = valueText.length
|
||||
|
||||
val linkFgColor = PrefI.ipVerifiedLinkFgColor(activity.pref).notZero()
|
||||
val linkFgColor = PrefI.ipVerifiedLinkFgColor.value.notZero()
|
||||
?: (Color.BLACK or 0x7fbc99)
|
||||
|
||||
valueText.setSpan(
|
||||
|
@ -737,7 +739,7 @@ internal class ViewHolderHeaderProfile(
|
|||
valueView.movementMethod = MyLinkMovementMethod
|
||||
|
||||
if (item.verified_at > 0L) {
|
||||
val linkBgColor = PrefI.ipVerifiedLinkBgColor(activity.pref).notZero()
|
||||
val linkBgColor = PrefI.ipVerifiedLinkBgColor.value.notZero()
|
||||
?: (0x337fbc99)
|
||||
|
||||
valueView.setBackgroundColor(linkBgColor)
|
||||
|
|
|
@ -3,9 +3,7 @@ package jp.juggler.subwaytooter.dialog
|
|||
import android.annotation.SuppressLint
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.style.RelativeSizeSpan
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
|
@ -13,8 +11,7 @@ import android.widget.TextView
|
|||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.AppCompatButton
|
||||
import jp.juggler.subwaytooter.R
|
||||
import jp.juggler.subwaytooter.table.AcctColor
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.table.*
|
||||
import jp.juggler.util.log.showToast
|
||||
import jp.juggler.util.ui.dismissSafe
|
||||
import jp.juggler.util.ui.getAdaptiveRippleDrawableRound
|
||||
|
@ -30,7 +27,7 @@ suspend fun AppCompatActivity.pickAccount(
|
|||
bAllowMastodon: Boolean = true,
|
||||
bAuto: Boolean = false,
|
||||
message: String? = null,
|
||||
accountListArg: MutableList<SavedAccount>? = null,
|
||||
accountListArg: List<SavedAccount>? = null,
|
||||
dismissCallback: (dialog: DialogInterface) -> Unit = {},
|
||||
extraCallback: (LinearLayout, Int, Int) -> Unit = { _, _, _ -> },
|
||||
): SavedAccount? {
|
||||
|
@ -54,11 +51,10 @@ suspend fun AppCompatActivity.pickAccount(
|
|||
else -> 0
|
||||
}
|
||||
|
||||
val accountList: MutableList<SavedAccount> = accountListArg
|
||||
?: SavedAccount.loadAccountList(activity)
|
||||
val accountList = accountListArg
|
||||
?: daoSavedAccount.loadAccountList()
|
||||
.filter { 0 == it.checkMastodon() + it.checkMisskey() + it.checkPseudo() }
|
||||
.toMutableList()
|
||||
.also { SavedAccount.sort(it) }
|
||||
.sortedByNickname()
|
||||
|
||||
if (accountList.isEmpty()) {
|
||||
|
||||
|
@ -127,17 +123,21 @@ suspend fun AppCompatActivity.pickAccount(
|
|||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
|
||||
val ac = AcctColor.load(a)
|
||||
val ac = daoAcctColor.load(a)
|
||||
|
||||
val b = AppCompatButton(activity)
|
||||
|
||||
if (AcctColor.hasColorBackground(ac)) {
|
||||
b.background = getAdaptiveRippleDrawableRound(activity, ac.color_bg, ac.color_fg)
|
||||
if (daoAcctColor.hasColorBackground(ac)) {
|
||||
b.background = getAdaptiveRippleDrawableRound(
|
||||
activity,
|
||||
ac.colorBg,
|
||||
ac.colorFg
|
||||
)
|
||||
} else {
|
||||
b.setBackgroundResource(R.drawable.btn_bg_transparent_round6dp)
|
||||
}
|
||||
if (AcctColor.hasColorForeground(ac)) {
|
||||
b.textColor = ac.color_fg
|
||||
if (daoAcctColor.hasColorForeground(ac)) {
|
||||
b.textColor = ac.colorFg
|
||||
}
|
||||
|
||||
b.setPaddingRelative(padX, padY, padX, padY)
|
||||
|
@ -147,19 +147,20 @@ suspend fun AppCompatActivity.pickAccount(
|
|||
b.minHeight = (0.5f + 32f * density).toInt()
|
||||
|
||||
val sb = SpannableStringBuilder(ac.nickname)
|
||||
if (a.lastNotificationError?.isNotEmpty() == true) {
|
||||
sb.append("\n")
|
||||
val start = sb.length
|
||||
sb.append(a.lastNotificationError)
|
||||
val end = sb.length
|
||||
sb.setSpan(RelativeSizeSpan(0.7f), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
} else if (a.last_subscription_error?.isNotEmpty() == true) {
|
||||
sb.append("\n")
|
||||
val start = sb.length
|
||||
sb.append(a.last_subscription_error)
|
||||
val end = sb.length
|
||||
sb.setSpan(RelativeSizeSpan(0.7f), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
// TODO エラー状態を表示する
|
||||
// if (a.lastNotificationError?.isNotEmpty() == true) {
|
||||
// sb.append("\n")
|
||||
// val start = sb.length
|
||||
// sb.append(a.lastNotificationError)
|
||||
// val end = sb.length
|
||||
// sb.setSpan(RelativeSizeSpan(0.7f), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
// } else if (a.last_subscription_error?.isNotEmpty() == true) {
|
||||
// sb.append("\n")
|
||||
// val start = sb.length
|
||||
// sb.append(a.last_subscription_error)
|
||||
// val end = sb.length
|
||||
// sb.setSpan(RelativeSizeSpan(0.7f), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
// }
|
||||
b.text = sb
|
||||
|
||||
b.setOnClickListener {
|
||||
|
|
|
@ -1,35 +1,50 @@
|
|||
package jp.juggler.subwaytooter.dialog
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import jp.juggler.subwaytooter.R
|
||||
import jp.juggler.util.data.notEmpty
|
||||
import jp.juggler.util.ui.dismissSafe
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import java.util.*
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
class ActionsDialog {
|
||||
class ActionsDialogInitializer(
|
||||
val title: CharSequence? = null,
|
||||
) {
|
||||
class Action(val caption: CharSequence, val action: suspend () -> Unit)
|
||||
|
||||
private val actionList = ArrayList<Action>()
|
||||
val list = ArrayList<Action>()
|
||||
|
||||
private class Action(val caption: CharSequence, val action: () -> Unit)
|
||||
|
||||
fun addAction(caption: CharSequence, action: () -> Unit): ActionsDialog {
|
||||
|
||||
actionList.add(Action(caption, action))
|
||||
|
||||
return this
|
||||
fun action(caption: CharSequence, action: suspend () -> Unit) {
|
||||
list.add(Action(caption, action))
|
||||
}
|
||||
|
||||
fun show(context: Context, title: CharSequence? = null): ActionsDialog {
|
||||
AlertDialog.Builder(context).apply {
|
||||
setNegativeButton(R.string.cancel, null)
|
||||
setItems(actionList.map { it.caption }.toTypedArray()) { _, which ->
|
||||
if (which >= 0 && which < actionList.size) {
|
||||
actionList[which].action()
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
suspend fun showSuspend(context: Context): Action =
|
||||
suspendCancellableCoroutine { cont ->
|
||||
val dialog = android.app.AlertDialog.Builder(context).apply {
|
||||
title?.notEmpty()?.let { setTitle(it) }
|
||||
setNegativeButton(android.R.string.cancel, null)
|
||||
setItems(list.map { it.caption }.toTypedArray()) { d, i ->
|
||||
if (cont.isActive) cont.resume(list[i]) {}
|
||||
d.dismissSafe()
|
||||
}
|
||||
}
|
||||
title?.notEmpty()?.let { setTitle(it) }
|
||||
}.show()
|
||||
|
||||
return this
|
||||
}
|
||||
setOnDismissListener {
|
||||
if (cont.isActive) cont.resumeWithException(CancellationException())
|
||||
}
|
||||
}.create()
|
||||
cont.invokeOnCancellation { dialog.dismissSafe() }
|
||||
dialog.show()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun Context.actionsDialog(
|
||||
title: String? = null,
|
||||
init: suspend ActionsDialogInitializer.() -> Unit,
|
||||
) {
|
||||
ActionsDialogInitializer(title)
|
||||
.apply { init() }
|
||||
.showSuspend(this)
|
||||
.action.invoke()
|
||||
}
|
||||
|
|
|
@ -64,13 +64,13 @@ object DlgConfirm {
|
|||
// }
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
suspend fun AppCompatActivity.confirm(
|
||||
suspend inline fun AppCompatActivity.confirm(
|
||||
message: String,
|
||||
getConfirmEnabled: Boolean,
|
||||
isConfirmEnabled: Boolean,
|
||||
setConfirmEnabled: (newConfirmEnabled: Boolean) -> Unit,
|
||||
) {
|
||||
if (!getConfirmEnabled) return
|
||||
suspendCancellableCoroutine<Unit> { cont ->
|
||||
if (!isConfirmEnabled) return
|
||||
val skipNext = suspendCancellableCoroutine { cont ->
|
||||
try {
|
||||
val views = DlgConfirmBinding.inflate(layoutInflater)
|
||||
views.tvMessage.text = message
|
||||
|
@ -79,10 +79,7 @@ object DlgConfirm {
|
|||
.setCancelable(true)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
if (views.cbSkipNext.isChecked) {
|
||||
setConfirmEnabled(false)
|
||||
}
|
||||
if (cont.isActive) cont.resume(Unit)
|
||||
if (cont.isActive) cont.resume(views.cbSkipNext.isChecked)
|
||||
}
|
||||
dialog.setOnDismissListener {
|
||||
if (cont.isActive) cont.resumeWithException(CancellationException("dialog cancelled."))
|
||||
|
@ -92,6 +89,7 @@ object DlgConfirm {
|
|||
cont.resumeWithException(ex)
|
||||
}
|
||||
}
|
||||
if (skipNext) setConfirmEnabled(false)
|
||||
}
|
||||
|
||||
suspend fun AppCompatActivity.confirm(@StringRes messageId: Int, vararg args: Any?) =
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue