とりあえずアプリサーバV2に対応する
This commit is contained in:
parent
ffdd474c73
commit
6c804a2a5d
|
@ -34,14 +34,14 @@ inline val AnkoContext<*>.resources: Resources
|
||||||
inline val AnkoContext<*>.assets: AssetManager
|
inline val AnkoContext<*>.assets: AssetManager
|
||||||
get() = ctx.assets
|
get() = ctx.assets
|
||||||
|
|
||||||
inline val AnkoContext<*>.defaultSharedPreferences: SharedPreferences
|
//inline val AnkoContext<*>.defaultSharedPreferences: SharedPreferences
|
||||||
get() = PreferenceManager.getDefaultSharedPreferences(ctx)
|
// get() = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||||
|
//
|
||||||
inline val Context.defaultSharedPreferences: SharedPreferences
|
//inline val Context.defaultSharedPreferences: SharedPreferences
|
||||||
get() = PreferenceManager.getDefaultSharedPreferences(this)
|
// get() = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
//
|
||||||
inline val Fragment.defaultSharedPreferences: SharedPreferences
|
//inline val Fragment.defaultSharedPreferences: SharedPreferences
|
||||||
get() = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
// get() = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
|
|
||||||
inline val Fragment.act: Activity?
|
inline val Fragment.act: Activity?
|
||||||
get() = activity
|
get() = activity
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
import io.gitlab.arturbosch.detekt.Detekt
|
import io.gitlab.arturbosch.detekt.Detekt
|
||||||
|
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.regex.Matcher
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
apply plugin: 'com.android.application'
|
plugins {
|
||||||
apply plugin: 'kotlin-android'
|
id("com.android.application")
|
||||||
apply plugin: 'kotlin-kapt'
|
id("org.jetbrains.kotlin.android")
|
||||||
apply plugin: 'org.jetbrains.kotlin.plugin.serialization'
|
id("org.jetbrains.kotlin.kapt")
|
||||||
apply plugin: 'com.google.gms.google-services'
|
id("org.jetbrains.kotlin.plugin.serialization")
|
||||||
apply plugin: "io.gitlab.arturbosch.detekt"
|
id("com.google.devtools.ksp").version("1.8.0-1.0.9")
|
||||||
|
id("io.gitlab.arturbosch.detekt")
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion stCompileSdkVersion
|
compileSdkVersion stCompileSdkVersion
|
||||||
|
@ -58,25 +61,30 @@ android {
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
minifyEnabled true
|
minifyEnabled false
|
||||||
shrinkResources true
|
shrinkResources false
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
||||||
|
|
||||||
lintOptions {
|
lintOptions {
|
||||||
disable 'MissingTranslation'
|
disable "MissingTranslation"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
debug{
|
debug {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specifies comma-separated list of flavor dimensions.
|
// Specifies comma-separated list of flavor dimensions.
|
||||||
flavorDimensions "rcOrDev"
|
flavorDimensions "fcmType"
|
||||||
|
|
||||||
productFlavors {
|
productFlavors {
|
||||||
rc {
|
nofcm {
|
||||||
dimension "rcOrDev"
|
dimension "fcmType"
|
||||||
|
versionNameSuffix "-noFcm"
|
||||||
|
}
|
||||||
|
fcm {
|
||||||
|
dimension "fcmType"
|
||||||
|
versionNameSuffix "-play"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,18 +105,18 @@ android {
|
||||||
|
|
||||||
packagingOptions {
|
packagingOptions {
|
||||||
resources {
|
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
|
// 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.base"
|
||||||
useLibrary 'android.test.mock'
|
useLibrary "android.test.mock"
|
||||||
lint {
|
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"
|
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:$desugarLibVersion"
|
||||||
|
|
||||||
implementation(project(":base"))
|
implementation(project(":base"))
|
||||||
implementation project(':colorpicker')
|
implementation project(":colorpicker")
|
||||||
implementation project(':emoji')
|
implementation project(":emoji")
|
||||||
implementation project(':apng_android')
|
implementation project(":apng_android")
|
||||||
implementation project(':anko')
|
implementation project(":anko")
|
||||||
implementation fileTree(include: ['*.aar'], dir: 'src/main/libs')
|
implementation fileTree(include: ["*.aar"], dir: "src/main/libs")
|
||||||
|
|
||||||
// implementation "org.conscrypt:conscrypt-android:$conscryptVersion"
|
// implementation "org.conscrypt:conscrypt-android:$conscryptVersion"
|
||||||
api "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.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")
|
detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:$detektVersion")
|
||||||
|
|
||||||
|
@ -189,6 +199,29 @@ repositories {
|
||||||
mavenCentral()
|
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) {
|
tasks.register("detektAll", Detekt) {
|
||||||
description = "Custom DETEKT build for all modules"
|
description = "Custom DETEKT build for all modules"
|
||||||
|
@ -247,3 +280,4 @@ tasks.register("detektAll", Detekt) {
|
||||||
sarif.outputLocation = file("$buildDir/reports/detekt/st-${name}.sarif")
|
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
|
<application
|
||||||
android:name=".App1"
|
android:name=".App1"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
|
android:enableOnBackInvokedCallback="true"
|
||||||
android:fullBackupContent="@xml/backup_spec"
|
android:fullBackupContent="@xml/backup_spec"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:largeHeap="true"
|
android:largeHeap="true"
|
||||||
|
android:localeConfig="@xml/locales_config"
|
||||||
android:maxAspectRatio="100"
|
android:maxAspectRatio="100"
|
||||||
android:resizeableActivity="true"
|
android:resizeableActivity="true"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme.Light"
|
android:theme="@style/AppTheme.Light"
|
||||||
tools:ignore="DataExtractionRules,UnusedAttribute">
|
tools:ignore="DataExtractionRules,UnusedAttribute">
|
||||||
<!-- android:localeConfig="@xml/locales_config" -->
|
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ActMain"
|
android:name=".ActMain"
|
||||||
|
@ -362,23 +363,38 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</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
|
<provider
|
||||||
android:name="androidx.startup.InitializationProvider"
|
android:name="androidx.startup.InitializationProvider"
|
||||||
android:authorities="${applicationId}.androidx-startup"
|
android:authorities="${applicationId}.androidx-startup"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
tools:node="merge">
|
tools:node="merge">
|
||||||
<meta-data
|
<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" />
|
android:value="androidx.startup" />
|
||||||
</provider>
|
</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>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
@ -2,6 +2,7 @@ package jp.juggler.subwaytooter
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
@ -15,24 +16,29 @@ import android.view.View
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import jp.juggler.subwaytooter.action.accountRemove
|
|
||||||
import jp.juggler.subwaytooter.api.*
|
import jp.juggler.subwaytooter.api.*
|
||||||
import jp.juggler.subwaytooter.api.auth.AuthBase
|
import jp.juggler.subwaytooter.api.auth.AuthBase
|
||||||
import jp.juggler.subwaytooter.api.entity.*
|
import jp.juggler.subwaytooter.api.entity.*
|
||||||
|
import jp.juggler.subwaytooter.auth.AuthRepo
|
||||||
import jp.juggler.subwaytooter.databinding.ActAccountSettingBinding
|
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.notification.*
|
||||||
import jp.juggler.subwaytooter.pref.PrefB
|
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.SavedAccount
|
||||||
|
import jp.juggler.subwaytooter.table.daoAcctColor
|
||||||
|
import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||||
import jp.juggler.subwaytooter.util.*
|
import jp.juggler.subwaytooter.util.*
|
||||||
import jp.juggler.util.*
|
import jp.juggler.util.*
|
||||||
import jp.juggler.util.coroutine.AppDispatchers
|
import jp.juggler.util.coroutine.AppDispatchers
|
||||||
|
import jp.juggler.util.coroutine.launchAndShowError
|
||||||
import jp.juggler.util.coroutine.launchMain
|
import jp.juggler.util.coroutine.launchMain
|
||||||
import jp.juggler.util.coroutine.launchProgress
|
import jp.juggler.util.coroutine.launchProgress
|
||||||
import jp.juggler.util.data.*
|
import jp.juggler.util.data.*
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
import jp.juggler.util.log.showToast
|
import jp.juggler.util.log.showToast
|
||||||
|
import jp.juggler.util.log.withCaption
|
||||||
import jp.juggler.util.media.ResizeConfig
|
import jp.juggler.util.media.ResizeConfig
|
||||||
import jp.juggler.util.media.ResizeType
|
import jp.juggler.util.media.ResizeType
|
||||||
import jp.juggler.util.media.createResizedBitmap
|
import jp.juggler.util.media.createResizedBitmap
|
||||||
|
@ -124,6 +130,10 @@ class ActAccountSetting : AppCompatActivity(),
|
||||||
ActAccountSettingBinding.inflate(layoutInflater, null, false)
|
ActAccountSettingBinding.inflate(layoutInflater, null, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val authRepo by lazy {
|
||||||
|
AuthRepo(this)
|
||||||
|
}
|
||||||
|
|
||||||
private lateinit var nameInvalidator: NetworkEmojiInvalidator
|
private lateinit var nameInvalidator: NetworkEmojiInvalidator
|
||||||
private lateinit var noteInvalidator: NetworkEmojiInvalidator
|
private lateinit var noteInvalidator: NetworkEmojiInvalidator
|
||||||
private lateinit var defaultTextInvalidator: NetworkEmojiInvalidator
|
private lateinit var defaultTextInvalidator: NetworkEmojiInvalidator
|
||||||
|
@ -218,20 +228,22 @@ class ActAccountSetting : AppCompatActivity(),
|
||||||
|
|
||||||
initUI()
|
initUI()
|
||||||
|
|
||||||
val a = intent.long(KEY_ACCOUNT_DB_ID)
|
launchAndShowError {
|
||||||
?.let { SavedAccount.loadAccount(this, it) }
|
val a = intent.long(KEY_ACCOUNT_DB_ID)
|
||||||
if (a == null) {
|
?.let { daoSavedAccount.loadAccount(it) }
|
||||||
finish()
|
if (a == null) {
|
||||||
return
|
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) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
@ -336,7 +348,7 @@ class ActAccountSetting : AppCompatActivity(),
|
||||||
R.id.etFieldValue4
|
R.id.etFieldValue4
|
||||||
).map { findViewById(it) }
|
).map { findViewById(it) }
|
||||||
|
|
||||||
btnNotificationStyleEditReply.vg(PrefB.bpSeparateReplyNotificationGroup.invoke())
|
btnNotificationStyleEditReply.vg(PrefB.bpSeparateReplyNotificationGroup.value)
|
||||||
|
|
||||||
nameInvalidator = NetworkEmojiInvalidator(handler, etDisplayName)
|
nameInvalidator = NetworkEmojiInvalidator(handler, etDisplayName)
|
||||||
noteInvalidator = NetworkEmojiInvalidator(handler, etNote)
|
noteInvalidator = NetworkEmojiInvalidator(handler, etNote)
|
||||||
|
@ -504,72 +516,76 @@ class ActAccountSetting : AppCompatActivity(),
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showAcctColor() {
|
private fun showAcctColor() {
|
||||||
|
|
||||||
val sa = this.account
|
val sa = this.account
|
||||||
val ac = AcctColor.load(sa)
|
val ac = daoAcctColor.load(sa)
|
||||||
views.tvUserCustom.apply {
|
views.tvUserCustom.apply {
|
||||||
backgroundColor = ac.color_bg
|
backgroundColor = ac.colorBg
|
||||||
text = ac.nickname
|
text = ac.nickname
|
||||||
textColor = ac.color_fg.notZero() ?: attrColor(R.attr.colorTimeSmall)
|
textColor = ac.colorFg.notZero()
|
||||||
|
?: attrColor(R.attr.colorTimeSmall)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveUIToData() {
|
private fun saveUIToData() {
|
||||||
if (!::account.isInitialized) return
|
if (!::account.isInitialized) return
|
||||||
if (loadingBusy) return
|
if (loadingBusy) return
|
||||||
account.visibility = visibility
|
launchAndShowError {
|
||||||
|
|
||||||
views.apply {
|
account.visibility = visibility
|
||||||
|
|
||||||
account.dont_hide_nsfw = swNSFWOpen.isChecked
|
views.apply {
|
||||||
account.dont_show_timeout = swDontShowTimeout.isChecked
|
account.dont_hide_nsfw = swNSFWOpen.isChecked
|
||||||
account.expand_cw = swExpandCW.isChecked
|
account.dont_show_timeout = swDontShowTimeout.isChecked
|
||||||
account.default_sensitive = swMarkSensitive.isChecked
|
account.expand_cw = swExpandCW.isChecked
|
||||||
account.notification_mention = cbNotificationMention.isChecked
|
account.default_sensitive = swMarkSensitive.isChecked
|
||||||
account.notification_boost = cbNotificationBoost.isChecked
|
account.notification_mention = cbNotificationMention.isChecked
|
||||||
account.notification_favourite = cbNotificationFavourite.isChecked
|
account.notification_boost = cbNotificationBoost.isChecked
|
||||||
account.notification_follow = cbNotificationFollow.isChecked
|
account.notification_favourite = cbNotificationFavourite.isChecked
|
||||||
account.notification_follow_request = cbNotificationFollowRequest.isChecked
|
account.notification_follow = cbNotificationFollow.isChecked
|
||||||
account.notification_reaction = cbNotificationReaction.isChecked
|
account.notification_follow_request = cbNotificationFollowRequest.isChecked
|
||||||
account.notification_vote = cbNotificationVote.isChecked
|
account.notification_reaction = cbNotificationReaction.isChecked
|
||||||
account.notification_post = cbNotificationPost.isChecked
|
account.notification_vote = cbNotificationVote.isChecked
|
||||||
account.notification_update = cbNotificationUpdate.isChecked
|
account.notification_post = cbNotificationPost.isChecked
|
||||||
account.notification_status_reference = cbNotificationStatusReference.isChecked
|
account.notification_update = cbNotificationUpdate.isChecked
|
||||||
|
account.notification_status_reference = cbNotificationStatusReference.isChecked
|
||||||
|
|
||||||
account.confirm_follow = cbConfirmFollow.isChecked
|
account.confirm_follow = cbConfirmFollow.isChecked
|
||||||
account.confirm_follow_locked = cbConfirmFollowLockedUser.isChecked
|
account.confirm_follow_locked = cbConfirmFollowLockedUser.isChecked
|
||||||
account.confirm_unfollow = cbConfirmUnfollow.isChecked
|
account.confirm_unfollow = cbConfirmUnfollow.isChecked
|
||||||
account.confirm_boost = cbConfirmBoost.isChecked
|
account.confirm_boost = cbConfirmBoost.isChecked
|
||||||
account.confirm_favourite = cbConfirmFavourite.isChecked
|
account.confirm_favourite = cbConfirmFavourite.isChecked
|
||||||
account.confirm_unboost = cbConfirmUnboost.isChecked
|
account.confirm_unboost = cbConfirmUnboost.isChecked
|
||||||
account.confirm_unfavourite = cbConfirmUnfavourite.isChecked
|
account.confirm_unfavourite = cbConfirmUnfavourite.isChecked
|
||||||
account.confirm_post = cbConfirmToot.isChecked
|
account.confirm_post = cbConfirmToot.isChecked
|
||||||
account.confirm_reaction = cbConfirmReaction.isChecked
|
account.confirm_reaction = cbConfirmReaction.isChecked
|
||||||
account.confirm_unbookmark = cbConfirmUnbookmark.isChecked
|
account.confirm_unbookmark = cbConfirmUnbookmark.isChecked
|
||||||
|
|
||||||
account.sound_uri = ""
|
account.sound_uri = ""
|
||||||
account.default_text = etDefaultText.text.toString()
|
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.movie_max_megabytes = etMovieSizeMax.text.toString().trim()
|
||||||
account.image_max_megabytes = etMediaSizeMax.text.toString().trim()
|
account.image_max_megabytes = etMediaSizeMax.text.toString().trim()
|
||||||
account.image_resize = (
|
account.image_resize = (
|
||||||
imageResizeItems.elementAtOrNull(spResizeImage.selectedItemPosition)?.config
|
imageResizeItems.elementAtOrNull(spResizeImage.selectedItemPosition)?.config
|
||||||
?: SavedAccount.defaultResizeConfig
|
?: SavedAccount.defaultResizeConfig
|
||||||
).spec
|
).spec
|
||||||
|
|
||||||
account.push_policy =
|
account.push_policy =
|
||||||
pushPolicyItems.elementAtOrNull(spPushPolicy.selectedItemPosition)?.id
|
pushPolicyItems.elementAtOrNull(spPushPolicy.selectedItemPosition)?.id
|
||||||
|
|
||||||
account.movieTranscodeMode = spMovieTranscodeMode.selectedItemPosition
|
account.movieTranscodeMode = spMovieTranscodeMode.selectedItemPosition
|
||||||
account.movieTranscodeBitrate = etMovieBitrate.text.toString()
|
account.movieTranscodeBitrate = etMovieBitrate.text.toString()
|
||||||
account.movieTranscodeFramerate = etMovieFrameRate.text.toString()
|
account.movieTranscodeFramerate = etMovieFrameRate.text.toString()
|
||||||
account.movieTranscodeSquarePixels = etMovieSquarePixels.text.toString()
|
account.movieTranscodeSquarePixels = etMovieSquarePixels.text.toString()
|
||||||
account.lang = languages.elementAtOrNull(spLanguageCode.selectedItemPosition)?.first
|
account.lang = languages.elementAtOrNull(spLanguageCode.selectedItemPosition)?.first
|
||||||
?: SavedAccount.LANG_WEB
|
?: SavedAccount.LANG_WEB
|
||||||
|
}
|
||||||
|
|
||||||
|
daoSavedAccount.saveSetting(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
account.saveSetting()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {
|
override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {
|
||||||
|
@ -617,24 +633,20 @@ class ActAccountSetting : AppCompatActivity(),
|
||||||
R.id.btnFields -> sendFields()
|
R.id.btnFields -> sendFields()
|
||||||
|
|
||||||
R.id.btnNotificationStyleEdit ->
|
R.id.btnNotificationStyleEdit ->
|
||||||
MessageNotification.openNotificationChannelSetting(
|
PullNotification.openNotificationChannelSetting(
|
||||||
this,
|
this
|
||||||
account,
|
|
||||||
MessageNotification.TRACKING_NAME_DEFAULT
|
|
||||||
)
|
)
|
||||||
|
|
||||||
R.id.btnNotificationStyleEditReply ->
|
R.id.btnNotificationStyleEditReply ->
|
||||||
MessageNotification.openNotificationChannelSetting(
|
PullNotification.openNotificationChannelSetting(
|
||||||
this,
|
this
|
||||||
account,
|
|
||||||
MessageNotification.TRACKING_NAME_REPLY
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showVisibility() {
|
private fun showVisibility() {
|
||||||
views.btnVisibility.text =
|
views.btnVisibility.text =
|
||||||
getVisibilityString(this, account.isMisskey, visibility)
|
visibility.getVisibilityString(account.isMisskey)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun performVisibility() {
|
private fun performVisibility() {
|
||||||
|
@ -734,10 +746,11 @@ class ActAccountSetting : AppCompatActivity(),
|
||||||
.setMessage(R.string.confirm_account_remove)
|
.setMessage(R.string.confirm_account_remove)
|
||||||
.setNegativeButton(R.string.cancel, null)
|
.setNegativeButton(R.string.cancel, null)
|
||||||
.setPositiveButton(R.string.ok) { _, _ ->
|
.setPositiveButton(R.string.ok) { _, _ ->
|
||||||
accountRemove(account)
|
launchAndShowError {
|
||||||
finish()
|
authRepo.accountRemove(account)
|
||||||
}
|
finish()
|
||||||
.show()
|
}
|
||||||
|
}.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////
|
///////////////////////////////////////////////////
|
||||||
|
@ -823,7 +836,7 @@ class ActAccountSetting : AppCompatActivity(),
|
||||||
result.jsonObject
|
result.jsonObject
|
||||||
} else {
|
} else {
|
||||||
// 承認待ち状態のチェック
|
// 承認待ち状態のチェック
|
||||||
account.checkConfirmed(this, client)
|
authRepo.checkConfirmed(account, client)
|
||||||
|
|
||||||
val result = client.request(
|
val result = client.request(
|
||||||
"/api/v1/accounts/verify_credentials"
|
"/api/v1/accounts/verify_credentials"
|
||||||
|
@ -1259,21 +1272,21 @@ class ActAccountSetting : AppCompatActivity(),
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openPicker(permissionRequester: PermissionRequester) {
|
private fun openPicker(permissionRequester: PermissionRequester) {
|
||||||
if (!permissionRequester.checkOrLaunch()) return
|
launchAndShowError {
|
||||||
|
if (!permissionRequester.checkOrLaunch()) return@launchAndShowError
|
||||||
val propName = when (permissionRequester) {
|
val propName = when (permissionRequester) {
|
||||||
prPickHeader -> "header"
|
prPickHeader -> "header"
|
||||||
else -> "avatar"
|
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) {
|
private fun performAttachment(propName: String) {
|
||||||
|
@ -1416,19 +1429,57 @@ class ActAccountSetting : AppCompatActivity(),
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updatePushSubscription(force: Boolean) {
|
private fun updatePushSubscription(force: Boolean) {
|
||||||
val wps = PushSubscriptionHelper(applicationContext, account, verbose = true)
|
val activity = this
|
||||||
launchMain {
|
launchAndShowError {
|
||||||
runApiTask(account) { client ->
|
val anyNotificationWanted = account.notification_boost ||
|
||||||
wps.updateSubscription(client, force = force)
|
account.notification_favourite ||
|
||||||
}?.let {
|
account.notification_follow ||
|
||||||
val log = wps.logString
|
account.notification_mention ||
|
||||||
if (log.isNotEmpty()) {
|
account.notification_reaction ||
|
||||||
AlertDialog.Builder(this@ActAccountSetting)
|
account.notification_vote ||
|
||||||
.setMessage(log)
|
account.notification_follow_request ||
|
||||||
.setPositiveButton(R.string.close, null)
|
account.notification_post ||
|
||||||
.show()
|
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
|
package jp.juggler.subwaytooter
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.content.pm.ResolveInfo
|
import android.content.pm.ResolveInfo
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.graphics.Typeface
|
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.AppSettingItem
|
||||||
import jp.juggler.subwaytooter.appsetting.SettingType
|
import jp.juggler.subwaytooter.appsetting.SettingType
|
||||||
import jp.juggler.subwaytooter.appsetting.appSettingRoot
|
import jp.juggler.subwaytooter.appsetting.appSettingRoot
|
||||||
|
import jp.juggler.subwaytooter.auth.AuthRepo
|
||||||
import jp.juggler.subwaytooter.databinding.ActAppSettingBinding
|
import jp.juggler.subwaytooter.databinding.ActAppSettingBinding
|
||||||
import jp.juggler.subwaytooter.databinding.LvSettingItemBinding
|
import jp.juggler.subwaytooter.databinding.LvSettingItemBinding
|
||||||
import jp.juggler.subwaytooter.dialog.DlgAppPicker
|
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.FloatPref
|
||||||
import jp.juggler.subwaytooter.pref.impl.IntPref
|
import jp.juggler.subwaytooter.pref.impl.IntPref
|
||||||
import jp.juggler.subwaytooter.pref.impl.StringPref
|
import jp.juggler.subwaytooter.pref.impl.StringPref
|
||||||
import jp.juggler.subwaytooter.pref.pref
|
import jp.juggler.subwaytooter.pref.lazyPref
|
||||||
import jp.juggler.subwaytooter.pref.put
|
|
||||||
import jp.juggler.subwaytooter.pref.remove
|
|
||||||
import jp.juggler.subwaytooter.table.AcctColor
|
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
import jp.juggler.subwaytooter.table.SavedAccount
|
||||||
|
import jp.juggler.subwaytooter.table.daoAcctColor
|
||||||
import jp.juggler.subwaytooter.util.CustomShare
|
import jp.juggler.subwaytooter.util.CustomShare
|
||||||
import jp.juggler.subwaytooter.util.CustomShareTarget
|
import jp.juggler.subwaytooter.util.CustomShareTarget
|
||||||
import jp.juggler.subwaytooter.util.cn
|
import jp.juggler.subwaytooter.util.cn
|
||||||
|
@ -92,7 +90,6 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
||||||
|
|
||||||
private var customShareTarget: CustomShareTarget? = null
|
private var customShareTarget: CustomShareTarget? = null
|
||||||
|
|
||||||
lateinit var pref: SharedPreferences
|
|
||||||
lateinit var handler: Handler
|
lateinit var handler: Handler
|
||||||
|
|
||||||
val views by lazy {
|
val views by lazy {
|
||||||
|
@ -103,6 +100,10 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
||||||
MyAdapter()
|
MyAdapter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val authRepo by lazy {
|
||||||
|
AuthRepo(this)
|
||||||
|
}
|
||||||
|
|
||||||
private val arNoop = ActivityResultHandler(log) { }
|
private val arNoop = ActivityResultHandler(log) { }
|
||||||
|
|
||||||
private val arImportAppData = ActivityResultHandler(log) { r ->
|
private val arImportAppData = ActivityResultHandler(log) { r ->
|
||||||
|
@ -159,7 +160,6 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
||||||
App1.setActivityTheme(this)
|
App1.setActivityTheme(this)
|
||||||
|
|
||||||
this.handler = App1.getAppState(this).handler
|
this.handler = App1.getAppState(this).handler
|
||||||
this.pref = pref()
|
|
||||||
|
|
||||||
// val intent = this.intent
|
// val intent = this.intent
|
||||||
// val layoutId = intent.getIntExtra(EXTRA_LAYOUT_ID, 0)
|
// val layoutId = intent.getIntExtra(EXTRA_LAYOUT_ID, 0)
|
||||||
|
@ -218,12 +218,12 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeDefaultPref() {
|
private fun removeDefaultPref() {
|
||||||
val e = pref.edit()
|
val e = lazyPref.edit()
|
||||||
var changed = false
|
var changed = false
|
||||||
appSettingRoot.scan {
|
appSettingRoot.scan {
|
||||||
when {
|
when {
|
||||||
(it.pref as? IntPref)?.noRemove == true -> Unit
|
(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()
|
if (changed) e.apply()
|
||||||
|
@ -371,7 +371,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
||||||
SettingType.ColorAlpha -> newColor.notZero() ?: 1
|
SettingType.ColorAlpha -> newColor.notZero() ?: 1
|
||||||
else -> newColor or Color.BLACK
|
else -> newColor or Color.BLACK
|
||||||
}
|
}
|
||||||
pref.edit().put(ip, c).apply()
|
ip.value = c
|
||||||
findItemViewHolder(colorTarget)?.showColor()
|
findItemViewHolder(colorTarget)?.showColor()
|
||||||
colorTarget.changed(this)
|
colorTarget.changed(this)
|
||||||
}
|
}
|
||||||
|
@ -512,8 +512,6 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
||||||
private val tvDesc = views.tvDesc
|
private val tvDesc = views.tvDesc
|
||||||
private val tvError = views.tvError
|
private val tvError = views.tvError
|
||||||
|
|
||||||
private val pref = actAppSetting.pref
|
|
||||||
|
|
||||||
var item: AppSettingItem? = null
|
var item: AppSettingItem? = null
|
||||||
|
|
||||||
private var bindingBusy = false
|
private var bindingBusy = false
|
||||||
|
@ -575,7 +573,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
||||||
vg(false) // skip animation
|
vg(false) // skip animation
|
||||||
text = name
|
text = name
|
||||||
isEnabledAlpha = item.enabled
|
isEnabledAlpha = item.enabled
|
||||||
isChecked = bp(pref)
|
isChecked = bp.value
|
||||||
vg(true)
|
vg(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -586,7 +584,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
||||||
vg(false) // skip animation
|
vg(false) // skip animation
|
||||||
actAppSetting.setSwitchColor(views.swSwitch)
|
actAppSetting.setSwitchColor(views.swSwitch)
|
||||||
isEnabledAlpha = item.enabled
|
isEnabledAlpha = item.enabled
|
||||||
isChecked = bp(pref)
|
isChecked = bp.value
|
||||||
vg(true)
|
vg(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -608,12 +606,12 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
||||||
showCaption(name)
|
showCaption(name)
|
||||||
views.llButtonBar.vg(true)
|
views.llButtonBar.vg(true)
|
||||||
views.vColor.vg(true)
|
views.vColor.vg(true)
|
||||||
views.vColor.setBackgroundColor(ip(pref))
|
views.vColor.setBackgroundColor(ip.value)
|
||||||
views.btnEdit.isEnabledAlpha = item.enabled
|
views.btnEdit.isEnabledAlpha = item.enabled
|
||||||
views.btnReset.isEnabledAlpha = item.enabled
|
views.btnReset.isEnabledAlpha = item.enabled
|
||||||
views.btnEdit.setOnClickListener {
|
views.btnEdit.setOnClickListener {
|
||||||
actAppSetting.colorTarget = item
|
actAppSetting.colorTarget = item
|
||||||
val color = ip(pref)
|
val color = ip.value
|
||||||
val builder = ColorPickerDialog.newBuilder()
|
val builder = ColorPickerDialog.newBuilder()
|
||||||
.setDialogType(ColorPickerDialog.TYPE_CUSTOM)
|
.setDialogType(ColorPickerDialog.TYPE_CUSTOM)
|
||||||
.setAllowPresets(true)
|
.setAllowPresets(true)
|
||||||
|
@ -623,7 +621,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
||||||
builder.show(actAppSetting)
|
builder.show(actAppSetting)
|
||||||
}
|
}
|
||||||
views.btnReset.setOnClickListener {
|
views.btnReset.setOnClickListener {
|
||||||
pref.edit().remove(ip).apply()
|
ip.removeValue()
|
||||||
showColor()
|
showColor()
|
||||||
item.changed.invoke(actAppSetting)
|
item.changed.invoke(actAppSetting)
|
||||||
}
|
}
|
||||||
|
@ -644,7 +642,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
||||||
argsInt?.map { actAppSetting.getString(it) }
|
argsInt?.map { actAppSetting.getString(it) }
|
||||||
?: item.spinnerArgsProc(actAppSetting)
|
?: item.spinnerArgsProc(actAppSetting)
|
||||||
)
|
)
|
||||||
views.spSpinner.setSelection(pi.invoke(pref))
|
views.spSpinner.setSelection(pi.value)
|
||||||
} else {
|
} else {
|
||||||
item.spinnerInitializer.invoke(actAppSetting, views.spSpinner)
|
item.spinnerInitializer.invoke(actAppSetting, views.spSpinner)
|
||||||
}
|
}
|
||||||
|
@ -655,9 +653,9 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
||||||
views.etEditText.vg(true)?.let { etEditText ->
|
views.etEditText.vg(true)?.let { etEditText ->
|
||||||
val text = when (val pi = item.pref) {
|
val text = when (val pi = item.pref) {
|
||||||
is FloatPref ->
|
is FloatPref ->
|
||||||
item.fromFloat.invoke(actAppSetting, pi(pref))
|
item.fromFloat.invoke(actAppSetting, pi.value)
|
||||||
is StringPref ->
|
is StringPref ->
|
||||||
pi(pref)
|
pi.value
|
||||||
else -> error("EditText has incorrect pref $pi")
|
else -> error("EditText has incorrect pref $pi")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -736,7 +734,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
||||||
fun showColor() {
|
fun showColor() {
|
||||||
val item = item ?: return
|
val item = item ?: return
|
||||||
val ip = item.pref.cast<IntPref>() ?: return
|
val ip = item.pref.cast<IntPref>() ?: return
|
||||||
val c = ip(pref)
|
val c = ip.value
|
||||||
views.vColor.setBackgroundColor(c)
|
views.vColor.setBackgroundColor(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -753,15 +751,14 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
||||||
val sv = item.filter.invoke(p0?.toString() ?: "")
|
val sv = item.filter.invoke(p0?.toString() ?: "")
|
||||||
|
|
||||||
when (val pi = item.pref) {
|
when (val pi = item.pref) {
|
||||||
is StringPref ->
|
is StringPref -> pi.value = sv
|
||||||
pref.edit().put(pi, sv).apply()
|
|
||||||
|
|
||||||
is FloatPref -> {
|
is FloatPref -> {
|
||||||
val fv = item.toFloat.invoke(actAppSetting, sv)
|
val fv = item.toFloat.invoke(actAppSetting, sv)
|
||||||
if (fv.isFinite()) {
|
if (fv.isFinite()) {
|
||||||
pref.edit().put(pi, fv).apply()
|
pi.value = fv
|
||||||
} else {
|
} else {
|
||||||
pref.edit().remove(pi.key).apply()
|
pi.removeValue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -785,7 +782,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
||||||
if (bindingBusy) return
|
if (bindingBusy) return
|
||||||
val item = item ?: return
|
val item = item ?: return
|
||||||
when (val pi = item.pref) {
|
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)
|
else -> item.spinnerOnSelected.invoke(actAppSetting, views.spSpinner, position)
|
||||||
}
|
}
|
||||||
item.changed.invoke(actAppSetting)
|
item.changed.invoke(actAppSetting)
|
||||||
|
@ -795,7 +792,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
||||||
if (bindingBusy) return
|
if (bindingBusy) return
|
||||||
val item = item ?: return
|
val item = item ?: return
|
||||||
when (val pi = item.pref) {
|
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")
|
else -> error("CompoundButton has no booleanPref $pi")
|
||||||
}
|
}
|
||||||
item.changed.invoke(actAppSetting)
|
item.changed.invoke(actAppSetting)
|
||||||
|
@ -946,7 +943,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
||||||
data.handleGetContentResult(contentResolver).firstOrNull()?.uri?.let {
|
data.handleGetContentResult(contentResolver).firstOrNull()?.uri?.let {
|
||||||
val file = saveTimelineFont(it, fileName)
|
val file = saveTimelineFont(it, fileName)
|
||||||
if (file != null) {
|
if (file != null) {
|
||||||
pref.edit().put(item.pref.cast()!!, file.absolutePath).apply()
|
(item.pref as? StringPref)?.value = file.absolutePath
|
||||||
showTimelineFont(item)
|
showTimelineFont(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -959,19 +956,17 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showTimelineFont(item: AppSettingItem, tv: TextView) {
|
fun showTimelineFont(item: AppSettingItem, tv: TextView) {
|
||||||
val fontUrl = item.pref.cast<StringPref>()!!.invoke(this)
|
|
||||||
try {
|
try {
|
||||||
if (fontUrl.isNotEmpty()) {
|
item.pref.cast<StringPref>()?.value.notEmpty()?.let { url ->
|
||||||
tv.typeface = Typeface.DEFAULT
|
tv.typeface = Typeface.DEFAULT
|
||||||
val face = Typeface.createFromFile(fontUrl)
|
val face = Typeface.createFromFile(url)
|
||||||
tv.typeface = face
|
tv.typeface = face
|
||||||
tv.text = fontUrl
|
tv.text = url
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
log.e(ex, "showTimelineFont failed.")
|
log.e(ex, "showTimelineFont failed.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// fallback
|
// fallback
|
||||||
tv.text = getString(R.string.not_selected)
|
tv.text = getString(R.string.not_selected)
|
||||||
tv.typeface = Typeface.DEFAULT
|
tv.typeface = Typeface.DEFAULT
|
||||||
|
@ -1026,17 +1021,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
||||||
|
|
||||||
//////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////
|
||||||
|
|
||||||
inner class AccountAdapter internal constructor() : BaseAdapter() {
|
inner class AccountAdapter(val list: List<SavedAccount>) : BaseAdapter() {
|
||||||
|
|
||||||
internal val list = ArrayList<SavedAccount>()
|
|
||||||
|
|
||||||
init {
|
|
||||||
for (a in SavedAccount.loadAccountList(this@ActAppSetting)) {
|
|
||||||
if (a.isPseudo) continue
|
|
||||||
list.add(a)
|
|
||||||
}
|
|
||||||
SavedAccount.sort(list)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getCount(): Int {
|
override fun getCount(): Int {
|
||||||
return 1 + list.size
|
return 1 + list.size
|
||||||
|
@ -1058,7 +1043,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
||||||
)
|
)
|
||||||
view.findViewById<TextView>(android.R.id.text1).text = when (position) {
|
view.findViewById<TextView>(android.R.id.text1).text = when (position) {
|
||||||
0 -> getString(R.string.ask_always)
|
0 -> getString(R.string.ask_always)
|
||||||
else -> AcctColor.getNickname(list[position - 1])
|
else -> daoAcctColor.getNickname(list[position - 1])
|
||||||
}
|
}
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
@ -1068,7 +1053,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
||||||
viewOld ?: layoutInflater.inflate(R.layout.lv_spinner_dropdown, parent, false)
|
viewOld ?: layoutInflater.inflate(R.layout.lv_spinner_dropdown, parent, false)
|
||||||
view.findViewById<TextView>(android.R.id.text1).text = when (position) {
|
view.findViewById<TextView>(android.R.id.text1).text = when (position) {
|
||||||
0 -> getString(R.string.ask_always)
|
0 -> getString(R.string.ask_always)
|
||||||
else -> AcctColor.getNickname(list[position - 1])
|
else -> daoAcctColor.getNickname(list[position - 1])
|
||||||
}
|
}
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
@ -1201,7 +1186,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
||||||
|
|
||||||
fun setCustomShare(appSettingItem: AppSettingItem, target: CustomShareTarget, value: String) {
|
fun setCustomShare(appSettingItem: AppSettingItem, target: CustomShareTarget, value: String) {
|
||||||
val sp: StringPref = appSettingItem.pref.cast() ?: error("$target: not StringPref")
|
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)
|
showCustomShareIcon(findItemViewHolder(appSettingItem)?.views?.textView1, target)
|
||||||
}
|
}
|
||||||
|
@ -1238,7 +1223,7 @@ class ActAppSetting : AppCompatActivity(), ColorPickerDialogListener, View.OnCli
|
||||||
private fun setWebBrowser(appSettingItem: AppSettingItem, value: String) {
|
private fun setWebBrowser(appSettingItem: AppSettingItem, value: String) {
|
||||||
val sp: StringPref = appSettingItem.pref.cast()
|
val sp: StringPref = appSettingItem.pref.cast()
|
||||||
?: error("${getString(appSettingItem.caption)}: not StringPref")
|
?: error("${getString(appSettingItem.caption)}: not StringPref")
|
||||||
pref.edit().put(sp, value).apply()
|
sp.value = value
|
||||||
|
|
||||||
showWebBrowser(findItemViewHolder(appSettingItem)?.views?.textView1, 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.ActWordListBinding
|
||||||
import jp.juggler.subwaytooter.databinding.LvMuteAppBinding
|
import jp.juggler.subwaytooter.databinding.LvMuteAppBinding
|
||||||
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
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.backPressed
|
||||||
|
import jp.juggler.util.coroutine.AppDispatchers
|
||||||
import jp.juggler.util.coroutine.launchAndShowError
|
import jp.juggler.util.coroutine.launchAndShowError
|
||||||
import jp.juggler.util.data.cast
|
import jp.juggler.util.data.cast
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
import jp.juggler.util.ui.setNavigationBack
|
import jp.juggler.util.ui.setNavigationBack
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class ActFavMute : AppCompatActivity() {
|
class ActFavMute : AppCompatActivity() {
|
||||||
|
|
||||||
|
@ -52,27 +54,20 @@ class ActFavMute : AppCompatActivity() {
|
||||||
item ?: return
|
item ?: return
|
||||||
launchAndShowError {
|
launchAndShowError {
|
||||||
confirm(R.string.delete_confirm, item.acct.pretty)
|
confirm(R.string.delete_confirm, item.acct.pretty)
|
||||||
FavMute.delete(item.acct)
|
daoFavMute.delete(item.acct)
|
||||||
listAdapter.remove(item)
|
listAdapter.remove(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadData() {
|
private fun loadData() {
|
||||||
listAdapter.items = buildList {
|
launchAndShowError {
|
||||||
try {
|
listAdapter.items = withContext(AppDispatchers.IO) {
|
||||||
FavMute.createCursor().use { cursor ->
|
daoFavMute.listAll().map {
|
||||||
val idxId = cursor.getColumnIndex(FavMute.COL_ID)
|
MyItem(
|
||||||
val idxName = cursor.getColumnIndex(FavMute.COL_ACCT)
|
id = it.id,
|
||||||
while (cursor.moveToNext()) {
|
acct = Acct.parse(it.acct),
|
||||||
val item = MyItem(
|
)
|
||||||
id = cursor.getLong(idxId),
|
|
||||||
acct = Acct.parse(cursor.getString(idxName)),
|
|
||||||
)
|
|
||||||
add(item)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} 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 com.jrummyapps.android.colorpicker.ColorPickerDialogListener
|
||||||
import jp.juggler.subwaytooter.databinding.ActHighlightEditBinding
|
import jp.juggler.subwaytooter.databinding.ActHighlightEditBinding
|
||||||
import jp.juggler.subwaytooter.table.HighlightWord
|
import jp.juggler.subwaytooter.table.HighlightWord
|
||||||
|
import jp.juggler.subwaytooter.table.daoHighlightWord
|
||||||
import jp.juggler.util.backPressed
|
import jp.juggler.util.backPressed
|
||||||
|
import jp.juggler.util.coroutine.launchAndShowError
|
||||||
import jp.juggler.util.data.decodeJsonObject
|
import jp.juggler.util.data.decodeJsonObject
|
||||||
import jp.juggler.util.data.mayUri
|
import jp.juggler.util.data.mayUri
|
||||||
import jp.juggler.util.data.notEmpty
|
import jp.juggler.util.data.notEmpty
|
||||||
|
@ -83,32 +85,34 @@ class ActHighlightWordEdit
|
||||||
|
|
||||||
setResult(RESULT_CANCELED)
|
setResult(RESULT_CANCELED)
|
||||||
|
|
||||||
fun loadData(): HighlightWord? {
|
launchAndShowError {
|
||||||
savedInstanceState?.getString(STATE_ITEM)
|
fun loadData(): HighlightWord? {
|
||||||
?.decodeJsonObject()
|
savedInstanceState?.getString(STATE_ITEM)
|
||||||
?.let { return HighlightWord(it) }
|
?.decodeJsonObject()
|
||||||
|
?.let { return HighlightWord(it) }
|
||||||
|
|
||||||
intent?.string(EXTRA_INITIAL_TEXT)
|
intent?.string(EXTRA_INITIAL_TEXT)
|
||||||
?.let { return HighlightWord(it) }
|
?.let { return HighlightWord(it) }
|
||||||
|
|
||||||
intent?.long(EXTRA_ITEM_ID)
|
intent?.long(EXTRA_ITEM_ID)
|
||||||
?.let { return HighlightWord.load(it) }
|
?.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) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
@ -254,22 +258,26 @@ class ActHighlightWordEdit
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun save() {
|
private fun save() {
|
||||||
uiToData()
|
launchAndShowError {
|
||||||
|
uiToData()
|
||||||
|
val name = item.name
|
||||||
|
|
||||||
if (item.name.isEmpty()) {
|
if (name.isNullOrBlank()) {
|
||||||
showToast(true, R.string.cant_leave_empty_keyword)
|
showToast(true, R.string.cant_leave_empty_keyword)
|
||||||
return
|
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.databinding.LvHighlightWordBinding
|
||||||
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
||||||
import jp.juggler.subwaytooter.table.HighlightWord
|
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.coroutine.launchAndShowError
|
||||||
import jp.juggler.util.data.cast
|
import jp.juggler.util.data.cast
|
||||||
import jp.juggler.util.data.mayUri
|
import jp.juggler.util.data.mayUri
|
||||||
|
import jp.juggler.util.data.notBlank
|
||||||
import jp.juggler.util.data.notZero
|
import jp.juggler.util.data.notZero
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
import jp.juggler.util.log.errorEx
|
|
||||||
import jp.juggler.util.ui.*
|
import jp.juggler.util.ui.*
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
|
|
||||||
class ActHighlightWordList : AppCompatActivity() {
|
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 {
|
try {
|
||||||
uri?.let { RingtoneManager.getRingtone(context, it) }
|
uri?.let { RingtoneManager.getRingtone(context, it) }
|
||||||
?.let { ringtone ->
|
?.let { ringtone ->
|
||||||
|
@ -129,21 +132,10 @@ class ActHighlightWordList : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadData() {
|
private fun loadData() {
|
||||||
try {
|
launchAndShowError {
|
||||||
listAdapter.items = buildList {
|
listAdapter.items = withContext(AppDispatchers.IO) {
|
||||||
HighlightWord.createCursor().use { cursor ->
|
daoHighlightWord.listAll()
|
||||||
val colIdx = HighlightWord.ColIdx(cursor)
|
|
||||||
while (cursor.moveToNext()) {
|
|
||||||
try {
|
|
||||||
add(HighlightWord(cursor, colIdx))
|
|
||||||
} catch (ex: Throwable) {
|
|
||||||
log.e(ex, "load error.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (ex: Throwable) {
|
|
||||||
errorEx(ex, "query error.")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,15 +153,17 @@ class ActHighlightWordList : AppCompatActivity() {
|
||||||
val activity = this
|
val activity = this
|
||||||
launchAndShowError {
|
launchAndShowError {
|
||||||
confirm(getString(R.string.delete_confirm, item.name))
|
confirm(getString(R.string.delete_confirm, item.name))
|
||||||
item.delete(activity)
|
daoHighlightWord.delete(applicationContext, item)
|
||||||
listAdapter.remove(item)
|
listAdapter.remove(item)
|
||||||
|
App1.getAppState(activity).enableSpeech()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun speech(item: HighlightWord?) {
|
private fun speech(item: HighlightWord?) {
|
||||||
item ?: return
|
item?.name?.notBlank()?.let {
|
||||||
App1.getAppState(this@ActHighlightWordList)
|
App1.getAppState(this@ActHighlightWordList)
|
||||||
.addSpeech(item.name, dedupMode = DedupMode.None)
|
.addSpeech(it, dedupMode = DedupMode.None)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// リスト要素のViewHolder
|
// リスト要素のViewHolder
|
||||||
|
|
|
@ -12,12 +12,15 @@ import jp.juggler.subwaytooter.api.ApiPath
|
||||||
import jp.juggler.subwaytooter.api.TootApiResult
|
import jp.juggler.subwaytooter.api.TootApiResult
|
||||||
import jp.juggler.subwaytooter.api.entity.*
|
import jp.juggler.subwaytooter.api.entity.*
|
||||||
import jp.juggler.subwaytooter.api.runApiTask
|
import jp.juggler.subwaytooter.api.runApiTask
|
||||||
|
import jp.juggler.subwaytooter.auth.AuthRepo
|
||||||
import jp.juggler.subwaytooter.column.ColumnType
|
import jp.juggler.subwaytooter.column.ColumnType
|
||||||
import jp.juggler.subwaytooter.databinding.ActKeywordFilterBinding
|
import jp.juggler.subwaytooter.databinding.ActKeywordFilterBinding
|
||||||
import jp.juggler.subwaytooter.databinding.LvKeywordFilterBinding
|
import jp.juggler.subwaytooter.databinding.LvKeywordFilterBinding
|
||||||
import jp.juggler.subwaytooter.table.AcctColor
|
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
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.backPressed
|
||||||
|
import jp.juggler.util.coroutine.launchAndShowError
|
||||||
import jp.juggler.util.coroutine.launchMain
|
import jp.juggler.util.coroutine.launchMain
|
||||||
import jp.juggler.util.data.*
|
import jp.juggler.util.data.*
|
||||||
import jp.juggler.util.int
|
import jp.juggler.util.int
|
||||||
|
@ -96,59 +99,64 @@ class ActKeywordFilter : AppCompatActivity() {
|
||||||
|
|
||||||
private val deleteIds = HashSet<String>()
|
private val deleteIds = HashSet<String>()
|
||||||
|
|
||||||
|
val authRepo by lazy {
|
||||||
|
AuthRepo(this)
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////
|
///////////////////////////////////////////////////
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
backPressed { confirmBack() }
|
backPressed { confirmBack() }
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
App1.setActivityTheme(this)
|
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()
|
initUI()
|
||||||
|
|
||||||
showAccount()
|
launchAndShowError {
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
// filter ID の有無はUIに影響するのでinitUIより先に初期化する
|
||||||
if (filterId != null) {
|
filterId = EntityId.from(intent, EXTRA_FILTER_ID)
|
||||||
startLoading()
|
|
||||||
} else {
|
val a = intent.long(EXTRA_ACCOUNT_DB_ID)
|
||||||
views.spExpire.setSelection(1)
|
?.let { daoSavedAccount.loadAccount(it) }
|
||||||
val initialText = intent.string(EXTRA_INITIAL_PHRASE)?.trim() ?: ""
|
if (a == null) {
|
||||||
views.etTitle.setText(initialText)
|
finish()
|
||||||
addKeywordArea(TootFilterKeyword(keyword = initialText))
|
return@launchAndShowError
|
||||||
}
|
}
|
||||||
} else {
|
account = a
|
||||||
|
|
||||||
savedInstanceState.getStringArrayList(STATE_DELETE_IDS)
|
|
||||||
?.let { deleteIds.addAll(it) }
|
|
||||||
|
|
||||||
savedInstanceState.getStringArrayList(STATE_KEYWORDS)
|
showAccount()
|
||||||
?.mapNotNull { it?.decodeJsonObject() }
|
|
||||||
?.forEach {
|
if (savedInstanceState == null) {
|
||||||
try {
|
if (filterId != null) {
|
||||||
addKeywordArea(TootFilterKeyword(it))
|
startLoading()
|
||||||
} catch (ex: Throwable) {
|
} else {
|
||||||
log.e(ex, "can't decode TootFilterKeyword")
|
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)
|
savedInstanceState.getStringArrayList(STATE_DELETE_IDS)
|
||||||
?.let { views.spExpire.setSelection(it) }
|
?.let { deleteIds.addAll(it) }
|
||||||
|
|
||||||
savedInstanceState.long(STATE_EXPIRE_AT)
|
savedInstanceState.getStringArrayList(STATE_KEYWORDS)
|
||||||
?.let { filterExpire = it }
|
?.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() {
|
private fun showAccount() {
|
||||||
views.tvAccount.text = AcctColor.getNicknameWithColor(account.acct)
|
views.tvAccount.text = daoAcctColor.getNicknameWithColor(account.acct)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startLoading() {
|
private fun startLoading() {
|
||||||
|
|
|
@ -17,8 +17,9 @@ import androidx.core.content.FileProvider
|
||||||
import jp.juggler.subwaytooter.api.entity.TootStatus
|
import jp.juggler.subwaytooter.api.entity.TootStatus
|
||||||
import jp.juggler.subwaytooter.column.Column
|
import jp.juggler.subwaytooter.column.Column
|
||||||
import jp.juggler.subwaytooter.databinding.ActLanguageFilterBinding
|
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.*
|
||||||
|
import jp.juggler.util.coroutine.launchAndShowError
|
||||||
import jp.juggler.util.coroutine.launchProgress
|
import jp.juggler.util.coroutine.launchProgress
|
||||||
import jp.juggler.util.data.*
|
import jp.juggler.util.data.*
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
|
@ -263,15 +264,17 @@ class ActLanguageFilter : AppCompatActivity(), View.OnClickListener {
|
||||||
R.id.btnAdd -> edit(null)
|
R.id.btnAdd -> edit(null)
|
||||||
|
|
||||||
R.id.btnMore -> {
|
R.id.btnMore -> {
|
||||||
ActionsDialog()
|
launchAndShowError {
|
||||||
.addAction(getString(R.string.clear_all)) {
|
actionsDialog {
|
||||||
languageList.clear()
|
action(getString(R.string.clear_all)) {
|
||||||
languageList.add(MyItem(TootStatus.LANGUAGE_CODE_DEFAULT, true))
|
languageList.clear()
|
||||||
adapter.notifyDataSetChanged()
|
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 =
|
val languageList =
|
||||||
activity.languageNameMap.map { MyItem(it.key, true) }.sortedWith(languageComparator)
|
activity.languageNameMap.map { MyItem(it.key, true) }.sortedWith(languageComparator)
|
||||||
btnPresets.setOnClickListener {
|
btnPresets.setOnClickListener {
|
||||||
val ad = ActionsDialog()
|
activity.run {
|
||||||
for (a in languageList) {
|
launchAndShowError {
|
||||||
ad.addAction("${a.code} ${activity.getDesc(a)}") {
|
actionsDialog(getString(R.string.presets)) {
|
||||||
etLanguage.setText(a.code)
|
for (a in languageList) {
|
||||||
updateDesc()
|
action("${a.code} ${activity.getDesc(a)}") {
|
||||||
|
etLanguage.setText(a.code)
|
||||||
|
updateDesc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ad.show(activity, activity.getString(R.string.presets))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
etLanguage.setText(item?.code ?: "")
|
etLanguage.setText(item?.code ?: "")
|
||||||
|
|
|
@ -3,7 +3,6 @@ package jp.juggler.subwaytooter
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
@ -19,6 +18,7 @@ import android.widget.LinearLayout
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.drawerlayout.widget.DrawerLayout
|
import androidx.drawerlayout.widget.DrawerLayout
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.viewpager.widget.ViewPager
|
import androidx.viewpager.widget.ViewPager
|
||||||
import jp.juggler.subwaytooter.action.accessTokenPrompt
|
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.dialog.DlgQuickTootMenu
|
||||||
import jp.juggler.subwaytooter.itemviewholder.StatusButtonsPopup
|
import jp.juggler.subwaytooter.itemviewholder.StatusButtonsPopup
|
||||||
import jp.juggler.subwaytooter.notification.checkNotificationImmediateAll
|
import jp.juggler.subwaytooter.notification.checkNotificationImmediateAll
|
||||||
import jp.juggler.subwaytooter.pref.PrefB
|
import jp.juggler.subwaytooter.pref.*
|
||||||
import jp.juggler.subwaytooter.pref.PrefI
|
|
||||||
import jp.juggler.subwaytooter.pref.PrefS
|
|
||||||
import jp.juggler.subwaytooter.pref.put
|
|
||||||
import jp.juggler.subwaytooter.span.MyClickableSpan
|
import jp.juggler.subwaytooter.span.MyClickableSpan
|
||||||
import jp.juggler.subwaytooter.span.MyClickableSpanHandler
|
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.util.*
|
||||||
import jp.juggler.subwaytooter.view.MyDrawerLayout
|
import jp.juggler.subwaytooter.view.MyDrawerLayout
|
||||||
import jp.juggler.subwaytooter.view.MyEditText
|
import jp.juggler.subwaytooter.view.MyEditText
|
||||||
import jp.juggler.util.backPressed
|
import jp.juggler.util.backPressed
|
||||||
|
import jp.juggler.util.coroutine.launchAndShowError
|
||||||
import jp.juggler.util.data.notEmpty
|
import jp.juggler.util.data.notEmpty
|
||||||
import jp.juggler.util.int
|
import jp.juggler.util.int
|
||||||
import jp.juggler.util.log.LogCategory
|
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.ActivityResultHandler
|
||||||
import jp.juggler.util.ui.attrColor
|
import jp.juggler.util.ui.attrColor
|
||||||
import jp.juggler.util.ui.isNotOk
|
import jp.juggler.util.ui.isNotOk
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import okhttp3.internal.toHexString
|
import okhttp3.internal.toHexString
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
@ -152,7 +153,6 @@ class ActMain : AppCompatActivity(),
|
||||||
|
|
||||||
lateinit var completionHelper: CompletionHelper
|
lateinit var completionHelper: CompletionHelper
|
||||||
|
|
||||||
lateinit var pref: SharedPreferences
|
|
||||||
lateinit var handler: Handler
|
lateinit var handler: Handler
|
||||||
lateinit var appState: AppState
|
lateinit var appState: AppState
|
||||||
|
|
||||||
|
@ -196,7 +196,7 @@ class ActMain : AppCompatActivity(),
|
||||||
override fun run() {
|
override fun run() {
|
||||||
handler.removeCallbacks(this)
|
handler.removeCallbacks(this)
|
||||||
if (!isStartedEx) return
|
if (!isStartedEx) return
|
||||||
if (PrefB.bpRelativeTimestamp(pref)) {
|
if (PrefB.bpRelativeTimestamp.value) {
|
||||||
appState.columnList.forEach { it.fireRelativeTime() }
|
appState.columnList.forEach { it.fireRelativeTime() }
|
||||||
handler.postDelayed(this, 10000L)
|
handler.postDelayed(this, 10000L)
|
||||||
}
|
}
|
||||||
|
@ -209,7 +209,7 @@ class ActMain : AppCompatActivity(),
|
||||||
set(value) {
|
set(value) {
|
||||||
if (value != quickPostVisibility) {
|
if (value != quickPostVisibility) {
|
||||||
quickPostVisibility = value
|
quickPostVisibility = value
|
||||||
pref.edit().put(PrefS.spQuickTootVisibility, value.id.toString()).apply()
|
PrefS.spQuickTootVisibility.value = value.id.toString()
|
||||||
showQuickPostVisibility()
|
showQuickPostVisibility()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -262,7 +262,7 @@ class ActMain : AppCompatActivity(),
|
||||||
}
|
}
|
||||||
|
|
||||||
val arAppSetting = ActivityResultHandler(log) { r ->
|
val arAppSetting = ActivityResultHandler(log) { r ->
|
||||||
Column.reloadDefaultColor(this, pref)
|
Column.reloadDefaultColor(this)
|
||||||
showFooterColor()
|
showFooterColor()
|
||||||
updateColumnStrip()
|
updateColumnStrip()
|
||||||
if (r.resultCode == RESULT_APP_DATA_IMPORT) {
|
if (r.resultCode == RESULT_APP_DATA_IMPORT) {
|
||||||
|
@ -282,15 +282,17 @@ class ActMain : AppCompatActivity(),
|
||||||
}
|
}
|
||||||
|
|
||||||
val arAccountSetting = ActivityResultHandler(log) { r ->
|
val arAccountSetting = ActivityResultHandler(log) { r ->
|
||||||
updateColumnStrip()
|
launchAndShowError {
|
||||||
appState.columnList.forEach { it.fireShowColumnHeader() }
|
updateColumnStrip()
|
||||||
when (r.resultCode) {
|
appState.columnList.forEach { it.fireShowColumnHeader() }
|
||||||
RESULT_OK -> r.data?.data?.let { openBrowser(it) }
|
when (r.resultCode) {
|
||||||
|
RESULT_OK -> r.data?.data?.let { openBrowser(it) }
|
||||||
|
|
||||||
ActAccountSetting.RESULT_INPUT_ACCESS_TOKEN ->
|
ActAccountSetting.RESULT_INPUT_ACCESS_TOKEN ->
|
||||||
r.data?.long(ActAccountSetting.EXTRA_DB_ID)
|
r.data?.long(ActAccountSetting.EXTRA_DB_ID)
|
||||||
?.let { SavedAccount.loadAccount(this, it) }
|
?.let { daoSavedAccount.loadAccount(it) }
|
||||||
?.let { accessTokenPrompt(it.apiHost) }
|
?.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)
|
appState = App1.getAppState(this)
|
||||||
handler = appState.handler
|
handler = appState.handler
|
||||||
pref = appState.pref
|
|
||||||
density = appState.density
|
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()
|
acctPadLr = (0.5f + 4f * density).toInt()
|
||||||
reloadTextSize()
|
reloadTextSize()
|
||||||
|
@ -373,8 +376,6 @@ class ActMain : AppCompatActivity(),
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
sharedIntent2?.let { handleSharedIntent(it) }
|
sharedIntent2?.let { handleSharedIntent(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
checkPrivacyPolicy()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
@ -452,91 +453,94 @@ class ActMain : AppCompatActivity(),
|
||||||
|
|
||||||
sideMenuAdapter.onActivityStart()
|
sideMenuAdapter.onActivityStart()
|
||||||
|
|
||||||
|
launchDialogs()
|
||||||
|
|
||||||
// 残りの処理はActivityResultの処理より後回しにしたい
|
// 残りの処理はActivityResultの処理より後回しにしたい
|
||||||
handler.postDelayed(onStartAfter, 1L)
|
lifecycleScope.launch {
|
||||||
|
|
||||||
prNotification.checkOrLaunch()
|
|
||||||
themeDefaultChangedDialog()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val onStartAfter = Runnable {
|
|
||||||
benchmark("onStartAfter total") {
|
|
||||||
|
|
||||||
benchmark("sweepBuggieData") {
|
|
||||||
// バグいアカウントデータを消す
|
|
||||||
try {
|
try {
|
||||||
SavedAccount.sweepBuggieData()
|
delay(1L)
|
||||||
} catch (ex: Throwable) {
|
benchmark("onStartAfter total") {
|
||||||
log.e(ex, "sweepBuggieData failed.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val newAccounts = benchmark("loadAccountList") {
|
benchmark("sweepBuggieData") {
|
||||||
SavedAccount.loadAccountList(this)
|
// バグいアカウントデータを消す
|
||||||
}
|
try {
|
||||||
|
daoSavedAccount.sweepBuggieData()
|
||||||
benchmark("removeColumnByAccount") {
|
} catch (ex: Throwable) {
|
||||||
val setDbId = newAccounts.map { it.db_id }.toSet()
|
log.e(ex, "sweepBuggieData failed.")
|
||||||
// アカウント設定から戻ってきたら、カラムを消す必要があるかもしれない
|
}
|
||||||
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") {
|
val newAccounts = benchmark("loadAccountList") {
|
||||||
// 背景画像を表示しない設定が変更された時にカラムの背景を設定しなおす
|
daoSavedAccount.loadAccountList()
|
||||||
appState.columnList.forEach { column ->
|
}
|
||||||
column.viewHolder?.lastAnnouncementShown = 0L
|
|
||||||
column.fireColumnColor()
|
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.")
|
||||||
}
|
}
|
||||||
}
|
}.let { startAfterJob = WeakReference(it) }
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStop() {
|
override fun onStop() {
|
||||||
log.d("onStop")
|
log.d("onStop")
|
||||||
isStartedEx = false
|
isStartedEx = false
|
||||||
handler.removeCallbacks(onStartAfter)
|
startAfterJob?.get()?.cancel()
|
||||||
|
startAfterJob = null
|
||||||
handler.removeCallbacks(procUpdateRelativeTime)
|
handler.removeCallbacks(procUpdateRelativeTime)
|
||||||
|
|
||||||
completionHelper.closeAcctPopup()
|
completionHelper.closeAcctPopup()
|
||||||
|
@ -583,7 +587,7 @@ class ActMain : AppCompatActivity(),
|
||||||
at android.os.Binder.execTransact (Binder.java:739)
|
at android.os.Binder.execTransact (Binder.java:739)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (PrefB.bpDontScreenOff(pref)) {
|
if (PrefB.bpDontScreenOff.value) {
|
||||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
} else {
|
} else {
|
||||||
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
|
@ -611,7 +615,7 @@ class ActMain : AppCompatActivity(),
|
||||||
{ env -> env.pager.currentItem },
|
{ env -> env.pager.currentItem },
|
||||||
{ env -> env.visibleColumnsIndices.first })
|
{ env -> env.visibleColumnsIndices.first })
|
||||||
log.d("ipLastColumnPos save $lastPos")
|
log.d("ipLastColumnPos save $lastPos")
|
||||||
pref.edit().put(PrefI.ipLastColumnPos, lastPos).apply()
|
PrefI.ipLastColumnPos.value = lastPos
|
||||||
|
|
||||||
appState.columnList.forEach { it.saveScrollPosition() }
|
appState.columnList.forEach { it.saveScrollPosition() }
|
||||||
|
|
||||||
|
@ -701,10 +705,10 @@ class ActMain : AppCompatActivity(),
|
||||||
setContentView(R.layout.act_main)
|
setContentView(R.layout.act_main)
|
||||||
|
|
||||||
quickPostVisibility =
|
quickPostVisibility =
|
||||||
TootVisibility.parseSavedVisibility(PrefS.spQuickTootVisibility(pref))
|
TootVisibility.parseSavedVisibility(PrefS.spQuickTootVisibility.value)
|
||||||
?: quickPostVisibility
|
?: quickPostVisibility
|
||||||
|
|
||||||
Column.reloadDefaultColor(this, pref)
|
Column.reloadDefaultColor(this)
|
||||||
|
|
||||||
galaxyBackgroundWorkaround()
|
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.*
|
||||||
import jp.juggler.subwaytooter.api.entity.*
|
import jp.juggler.subwaytooter.api.entity.*
|
||||||
import jp.juggler.subwaytooter.databinding.ActMediaViewerBinding
|
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.drawable.MediaBackgroundDrawable
|
||||||
import jp.juggler.subwaytooter.global.appPref
|
|
||||||
import jp.juggler.subwaytooter.pref.PrefI
|
import jp.juggler.subwaytooter.pref.PrefI
|
||||||
import jp.juggler.subwaytooter.pref.put
|
|
||||||
import jp.juggler.subwaytooter.util.permissionSpecMediaDownload
|
import jp.juggler.subwaytooter.util.permissionSpecMediaDownload
|
||||||
import jp.juggler.subwaytooter.util.requester
|
import jp.juggler.subwaytooter.util.requester
|
||||||
import jp.juggler.subwaytooter.view.PinchBitmapView
|
import jp.juggler.subwaytooter.view.PinchBitmapView
|
||||||
import jp.juggler.util.*
|
import jp.juggler.util.*
|
||||||
|
import jp.juggler.util.coroutine.launchAndShowError
|
||||||
import jp.juggler.util.coroutine.launchMain
|
import jp.juggler.util.coroutine.launchMain
|
||||||
import jp.juggler.util.data.*
|
import jp.juggler.util.data.*
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
|
@ -304,7 +303,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
||||||
views.pbvImage.background = MediaBackgroundDrawable(
|
views.pbvImage.background = MediaBackgroundDrawable(
|
||||||
context = views.root.context,
|
context = views.root.context,
|
||||||
tileStep = tileStep,
|
tileStep = tileStep,
|
||||||
kind = MediaBackgroundDrawable.Kind.fromIndex(PrefI.ipMediaBackground(this))
|
kind = MediaBackgroundDrawable.Kind.fromIndex(PrefI.ipMediaBackground.value)
|
||||||
)
|
)
|
||||||
|
|
||||||
val enablePaging = mediaList.size > 1
|
val enablePaging = mediaList.size > 1
|
||||||
|
@ -444,7 +443,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
||||||
|
|
||||||
val url = when {
|
val url = when {
|
||||||
forceLocalUrl -> ta.url
|
forceLocalUrl -> ta.url
|
||||||
else -> ta.getLargeUrl(appPref)
|
else -> ta.getLargeUrl()
|
||||||
}
|
}
|
||||||
if (url == null) {
|
if (url == null) {
|
||||||
showError("missing media attachment url.")
|
showError("missing media attachment url.")
|
||||||
|
@ -573,7 +572,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
||||||
pbvImage.visible().setBitmap(null)
|
pbvImage.visible().setBitmap(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
val urlList = ta.getLargeUrlList(appPref)
|
val urlList = ta.getLargeUrlList()
|
||||||
if (urlList.isEmpty()) {
|
if (urlList.isEmpty()) {
|
||||||
showError("missing media attachment url.")
|
showError("missing media attachment url.")
|
||||||
return
|
return
|
||||||
|
@ -650,7 +649,7 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
||||||
?: error("missing DownloadManager system service")
|
?: error("missing DownloadManager system service")
|
||||||
|
|
||||||
val url = if (ta is TootAttachment) {
|
val url = if (ta is TootAttachment) {
|
||||||
ta.getLargeUrl(appPref)
|
ta.getLargeUrl()
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
} ?: return
|
} ?: return
|
||||||
|
@ -781,65 +780,80 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun more(ta: TootAttachmentLike) {
|
private fun more(ta: TootAttachmentLike) {
|
||||||
val ad = ActionsDialog()
|
launchAndShowError {
|
||||||
if (ta is TootAttachment) {
|
actionsDialog {
|
||||||
val url = ta.getLargeUrl(appPref) ?: return
|
fun addMoreMenu(
|
||||||
ad.addAction(getString(R.string.open_in_browser)) { share(Intent.ACTION_VIEW, url) }
|
captionPrefix: String,
|
||||||
ad.addAction(getString(R.string.share_url)) { share(Intent.ACTION_SEND, url) }
|
url: String?,
|
||||||
ad.addAction(getString(R.string.copy_url)) { copy(url) }
|
@Suppress("SameParameterValue") action: String,
|
||||||
addMoreMenu(ad, "url", ta.url, Intent.ACTION_VIEW)
|
) {
|
||||||
addMoreMenu(ad, "remote_url", ta.remote_url, Intent.ACTION_VIEW)
|
val uri = url.mayUri() ?: return
|
||||||
addMoreMenu(ad, "preview_url", ta.preview_url, Intent.ACTION_VIEW)
|
val caption = getString(R.string.open_browser_of, captionPrefix)
|
||||||
addMoreMenu(ad, "preview_remote_url", ta.preview_remote_url, Intent.ACTION_VIEW)
|
action(caption) {
|
||||||
addMoreMenu(ad, "text_url", ta.text_url, Intent.ACTION_VIEW)
|
try {
|
||||||
} else if (ta is TootAttachmentMSP) {
|
val intent = Intent(action, uri)
|
||||||
val url = ta.preview_url
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
ad.addAction(getString(R.string.open_in_browser)) { share(Intent.ACTION_VIEW, url) }
|
startActivity(intent)
|
||||||
ad.addAction(getString(R.string.share_url)) { share(Intent.ACTION_SEND, url) }
|
} catch (ex: Throwable) {
|
||||||
ad.addAction(getString(R.string.copy_url)) { copy(url) }
|
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) {
|
if (TootAttachmentType.Image == mediaList.elementAtOrNull(idx)?.type) {
|
||||||
ad.addAction(getString(R.string.background_pattern)) { mediaBackgroundDialog() }
|
action(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.")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun mediaBackgroundDialog() {
|
private fun mediaBackgroundDialog() {
|
||||||
val ad = ActionsDialog()
|
launchAndShowError {
|
||||||
for (k in MediaBackgroundDrawable.Kind.values()) {
|
actionsDialog(getString(R.string.background_pattern)) {
|
||||||
if (!k.isMediaBackground) continue
|
for (k in MediaBackgroundDrawable.Kind.values()) {
|
||||||
ad.addAction(k.name) {
|
if (!k.isMediaBackground) continue
|
||||||
val idx = k.toIndex()
|
action(k.name) {
|
||||||
appPref.edit().put(PrefI.ipMediaBackground, idx).apply()
|
val idx = k.toIndex()
|
||||||
views.pbvImage.background = MediaBackgroundDrawable(
|
PrefI.ipMediaBackground.value = idx
|
||||||
context = views.root.context,
|
views.pbvImage.background = MediaBackgroundDrawable(
|
||||||
tileStep = tileStep,
|
context = views.root.context,
|
||||||
kind = k
|
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.databinding.LvMuteAppBinding
|
||||||
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
||||||
import jp.juggler.subwaytooter.table.MutedApp
|
import jp.juggler.subwaytooter.table.MutedApp
|
||||||
|
import jp.juggler.subwaytooter.table.appDatabase
|
||||||
import jp.juggler.util.backPressed
|
import jp.juggler.util.backPressed
|
||||||
import jp.juggler.util.coroutine.launchAndShowError
|
import jp.juggler.util.coroutine.launchAndShowError
|
||||||
import jp.juggler.util.data.cast
|
import jp.juggler.util.data.cast
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
import jp.juggler.util.ui.setNavigationBack
|
import jp.juggler.util.ui.setNavigationBack
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class ActMutedApp : AppCompatActivity() {
|
class ActMutedApp : AppCompatActivity() {
|
||||||
|
|
||||||
|
@ -27,6 +30,8 @@ class ActMutedApp : AppCompatActivity() {
|
||||||
|
|
||||||
private val listAdapter by lazy { MyListAdapter() }
|
private val listAdapter by lazy { MyListAdapter() }
|
||||||
|
|
||||||
|
private val daoMutedApp by lazy { MutedApp.Access(appDatabase) }
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
backPressed {
|
backPressed {
|
||||||
|
@ -47,48 +52,33 @@ class ActMutedApp : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadData() {
|
private fun loadData() {
|
||||||
listAdapter.items = buildList {
|
launchAndShowError {
|
||||||
try {
|
listAdapter.items = withContext(Dispatchers.IO) {
|
||||||
MutedApp.createCursor().use { cursor ->
|
daoMutedApp.listAll()
|
||||||
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.")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun delete(item: MyItem?) {
|
private fun delete(item: MutedApp?) {
|
||||||
item ?: return
|
item ?: return
|
||||||
launchAndShowError {
|
launchAndShowError {
|
||||||
confirm(R.string.delete_confirm, item.name)
|
confirm(R.string.delete_confirm, item.name)
|
||||||
MutedApp.delete(item.name)
|
daoMutedApp.delete(item.name)
|
||||||
listAdapter.remove(item)
|
listAdapter.remove(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// リスト要素のデータ
|
|
||||||
private class MyItem(val id: Long, val name: String)
|
|
||||||
|
|
||||||
// リスト要素のViewHolder
|
// リスト要素のViewHolder
|
||||||
private inner class MyViewHolder(parent: ViewGroup?) {
|
private inner class MyViewHolder(parent: ViewGroup?) {
|
||||||
val views = LvMuteAppBinding.inflate(layoutInflater, parent, false)
|
val views = LvMuteAppBinding.inflate(layoutInflater, parent, false)
|
||||||
var lastItem: MyItem? = null
|
var lastItem: MutedApp? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
views.root.tag = this
|
views.root.tag = this
|
||||||
views.btnDelete.setOnClickListener { delete(lastItem) }
|
views.btnDelete.setOnClickListener { delete(lastItem) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bind(item: MyItem?) {
|
fun bind(item: MutedApp?) {
|
||||||
item ?: return
|
item ?: return
|
||||||
lastItem = item
|
lastItem = item
|
||||||
views.tvName.text = item.name
|
views.tvName.text = item.name
|
||||||
|
@ -96,13 +86,13 @@ class ActMutedApp : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class MyListAdapter : BaseAdapter() {
|
private inner class MyListAdapter : BaseAdapter() {
|
||||||
var items: List<MyItem> = emptyList()
|
var items: List<MutedApp> = emptyList()
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun remove(item: MyItem) {
|
fun remove(item: MutedApp) {
|
||||||
items = items.filter { it != item }
|
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.databinding.LvMuteAppBinding
|
||||||
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
||||||
import jp.juggler.subwaytooter.table.UserRelation
|
import jp.juggler.subwaytooter.table.UserRelation
|
||||||
|
import jp.juggler.subwaytooter.table.daoUserRelation
|
||||||
import jp.juggler.util.backPressed
|
import jp.juggler.util.backPressed
|
||||||
|
import jp.juggler.util.coroutine.AppDispatchers
|
||||||
import jp.juggler.util.coroutine.launchAndShowError
|
import jp.juggler.util.coroutine.launchAndShowError
|
||||||
import jp.juggler.util.data.cast
|
import jp.juggler.util.data.cast
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
import jp.juggler.util.ui.setNavigationBack
|
import jp.juggler.util.ui.setNavigationBack
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class ActMutedPseudoAccount : AppCompatActivity() {
|
class ActMutedPseudoAccount : AppCompatActivity() {
|
||||||
|
|
||||||
|
@ -47,62 +50,47 @@ class ActMutedPseudoAccount : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadData() {
|
private fun loadData() {
|
||||||
listAdapter.items = buildList {
|
launchAndShowError {
|
||||||
try {
|
listAdapter.items = withContext(AppDispatchers.IO) {
|
||||||
UserRelation.createCursorPseudoMuted().use { cursor ->
|
daoUserRelation.listPseudoMuted()
|
||||||
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.")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun delete(item: MyItem?) {
|
private fun delete(item: UserRelation?) {
|
||||||
item ?: return
|
item ?: return
|
||||||
launchAndShowError {
|
launchAndShowError {
|
||||||
confirm(R.string.delete_confirm, item.name)
|
confirm(R.string.delete_confirm, item.whoId)
|
||||||
UserRelation.deletePseudo(item.id)
|
daoUserRelation.deletePseudo(item.id)
|
||||||
listAdapter.remove(item)
|
listAdapter.remove(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// リスト要素のデータ
|
|
||||||
private class MyItem(val id: Long, val name: String)
|
|
||||||
|
|
||||||
// リスト要素のViewHolder
|
// リスト要素のViewHolder
|
||||||
private inner class MyViewHolder(parent: ViewGroup?) {
|
private inner class MyViewHolder(parent: ViewGroup?) {
|
||||||
val views = LvMuteAppBinding.inflate(layoutInflater, parent, false)
|
val views = LvMuteAppBinding.inflate(layoutInflater, parent, false)
|
||||||
private var lastItem: MyItem? = null
|
private var lastItem: UserRelation? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
views.root.tag = this
|
views.root.tag = this
|
||||||
views.btnDelete.setOnClickListener { delete(lastItem) }
|
views.btnDelete.setOnClickListener { delete(lastItem) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bind(item: MyItem?) {
|
fun bind(item: UserRelation?) {
|
||||||
item ?: return
|
item ?: return
|
||||||
lastItem = item
|
lastItem = item
|
||||||
views.tvName.text = item.name
|
views.tvName.text = item.whoId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class MyListAdapter : BaseAdapter() {
|
private inner class MyListAdapter : BaseAdapter() {
|
||||||
var items: List<MyItem> = emptyList()
|
var items: List<UserRelation> = emptyList()
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun remove(item: MyItem) {
|
fun remove(item: UserRelation) {
|
||||||
items = items.filter { it != item }
|
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.databinding.LvMuteAppBinding
|
||||||
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
||||||
import jp.juggler.subwaytooter.table.MutedWord
|
import jp.juggler.subwaytooter.table.MutedWord
|
||||||
|
import jp.juggler.subwaytooter.table.daoMutedWord
|
||||||
import jp.juggler.util.backPressed
|
import jp.juggler.util.backPressed
|
||||||
|
import jp.juggler.util.coroutine.AppDispatchers
|
||||||
import jp.juggler.util.coroutine.launchAndShowError
|
import jp.juggler.util.coroutine.launchAndShowError
|
||||||
import jp.juggler.util.data.cast
|
import jp.juggler.util.data.cast
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
import jp.juggler.util.ui.setNavigationBack
|
import jp.juggler.util.ui.setNavigationBack
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class ActMutedWord : AppCompatActivity() {
|
class ActMutedWord : AppCompatActivity() {
|
||||||
|
|
||||||
|
@ -47,48 +50,33 @@ class ActMutedWord : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadData() {
|
private fun loadData() {
|
||||||
listAdapter.items = buildList {
|
launchAndShowError {
|
||||||
try {
|
listAdapter.items = withContext(AppDispatchers.IO) {
|
||||||
MutedWord.createCursor().use { cursor ->
|
daoMutedWord.listAll()
|
||||||
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.")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun delete(item: MyItem?) {
|
private fun delete(item: MutedWord?) {
|
||||||
item ?: return
|
item ?: return
|
||||||
launchAndShowError {
|
launchAndShowError {
|
||||||
confirm(R.string.delete_confirm, item.name)
|
confirm(R.string.delete_confirm, item.name)
|
||||||
MutedWord.delete(item.name)
|
daoMutedWord.delete(item.name)
|
||||||
listAdapter.remove(item)
|
listAdapter.remove(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// リスト要素のデータ
|
|
||||||
private class MyItem(val id: Long, val name: String)
|
|
||||||
|
|
||||||
// リスト要素のViewHolder
|
// リスト要素のViewHolder
|
||||||
private inner class MyViewHolder(parent: ViewGroup?) {
|
private inner class MyViewHolder(parent: ViewGroup?) {
|
||||||
val views = LvMuteAppBinding.inflate(layoutInflater, parent, false)
|
val views = LvMuteAppBinding.inflate(layoutInflater, parent, false)
|
||||||
private var lastItem: MyItem? = null
|
private var lastItem: MutedWord? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
views.root.tag = this
|
views.root.tag = this
|
||||||
views.btnDelete.setOnClickListener { delete(lastItem) }
|
views.btnDelete.setOnClickListener { delete(lastItem) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bind(item: MyItem?) {
|
fun bind(item: MutedWord?) {
|
||||||
item ?: return
|
item ?: return
|
||||||
lastItem = item
|
lastItem = item
|
||||||
views.tvName.text = item.name
|
views.tvName.text = item.name
|
||||||
|
@ -96,13 +84,13 @@ class ActMutedWord : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class MyListAdapter : BaseAdapter() {
|
private inner class MyListAdapter : BaseAdapter() {
|
||||||
var items: List<MyItem> = emptyList()
|
var items: List<MutedWord> = emptyList()
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun remove(item: MyItem?) {
|
fun remove(item: MutedWord?) {
|
||||||
items = items.filter { it != item }
|
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.api.entity.Acct
|
||||||
import jp.juggler.subwaytooter.databinding.ActNicknameBinding
|
import jp.juggler.subwaytooter.databinding.ActNicknameBinding
|
||||||
import jp.juggler.subwaytooter.table.AcctColor
|
import jp.juggler.subwaytooter.table.AcctColor
|
||||||
|
import jp.juggler.subwaytooter.table.daoAcctColor
|
||||||
import jp.juggler.util.backPressed
|
import jp.juggler.util.backPressed
|
||||||
import jp.juggler.util.boolean
|
import jp.juggler.util.boolean
|
||||||
|
import jp.juggler.util.coroutine.launchAndShowError
|
||||||
import jp.juggler.util.data.mayUri
|
import jp.juggler.util.data.mayUri
|
||||||
import jp.juggler.util.data.notEmpty
|
import jp.juggler.util.data.notEmpty
|
||||||
import jp.juggler.util.data.notZero
|
import jp.juggler.util.data.notZero
|
||||||
|
@ -134,11 +136,11 @@ class ActNickname : AppCompatActivity(), View.OnClickListener, ColorPickerDialog
|
||||||
|
|
||||||
views.tvAcct.text = acctPretty
|
views.tvAcct.text = acctPretty
|
||||||
|
|
||||||
val ac = AcctColor.load(acctAscii, acctPretty)
|
val ac = daoAcctColor.load(acctAscii)
|
||||||
colorBg = ac.color_bg
|
colorBg = ac.colorBg
|
||||||
colorFg = ac.color_fg
|
colorFg = ac.colorFg
|
||||||
views.etNickname.setText(ac.nickname)
|
views.etNickname.setText(ac.nickname)
|
||||||
notificationSoundUri = ac.notification_sound
|
notificationSoundUri = ac.notificationSound
|
||||||
|
|
||||||
loadingBusy = false
|
loadingBusy = false
|
||||||
show()
|
show()
|
||||||
|
@ -146,14 +148,17 @@ class ActNickname : AppCompatActivity(), View.OnClickListener, ColorPickerDialog
|
||||||
|
|
||||||
private fun save() {
|
private fun save() {
|
||||||
if (loadingBusy) return
|
if (loadingBusy) return
|
||||||
AcctColor(
|
launchAndShowError {
|
||||||
acctAscii,
|
daoAcctColor.save(
|
||||||
acctPretty,
|
System.currentTimeMillis(),
|
||||||
views.etNickname.text.toString().trim { it <= ' ' },
|
AcctColor(
|
||||||
colorFg,
|
nicknameSave = views.etNickname.text.toString().trim { it <= ' ' },
|
||||||
colorBg,
|
colorFg = colorFg,
|
||||||
notificationSoundUri
|
colorBg = colorBg,
|
||||||
).save(System.currentTimeMillis())
|
notificationSoundSaved = notificationSoundUri ?: "",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun show() {
|
private fun show() {
|
||||||
|
|
|
@ -2,7 +2,6 @@ package jp.juggler.subwaytooter
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
|
@ -29,11 +28,14 @@ import jp.juggler.subwaytooter.table.SavedAccount
|
||||||
import jp.juggler.subwaytooter.util.*
|
import jp.juggler.subwaytooter.util.*
|
||||||
import jp.juggler.subwaytooter.view.MyEditText
|
import jp.juggler.subwaytooter.view.MyEditText
|
||||||
import jp.juggler.subwaytooter.view.MyNetworkImageView
|
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.launchIO
|
||||||
import jp.juggler.util.coroutine.launchMain
|
import jp.juggler.util.coroutine.launchMain
|
||||||
import jp.juggler.util.data.GetContentResultEntry
|
import jp.juggler.util.data.GetContentResultEntry
|
||||||
import jp.juggler.util.log.LogCategory
|
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.ActivityResultHandler
|
||||||
import jp.juggler.util.ui.isNotOk
|
import jp.juggler.util.ui.isNotOk
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
@ -111,7 +113,6 @@ class ActPost : AppCompatActivity(),
|
||||||
lateinit var etChoices: List<MyEditText>
|
lateinit var etChoices: List<MyEditText>
|
||||||
|
|
||||||
lateinit var handler: Handler
|
lateinit var handler: Handler
|
||||||
lateinit var pref: SharedPreferences
|
|
||||||
lateinit var appState: AppState
|
lateinit var appState: AppState
|
||||||
lateinit var attachmentUploader: AttachmentUploader
|
lateinit var attachmentUploader: AttachmentUploader
|
||||||
lateinit var attachmentPicker: AttachmentPicker
|
lateinit var attachmentPicker: AttachmentPicker
|
||||||
|
@ -138,7 +139,7 @@ class ActPost : AppCompatActivity(),
|
||||||
|
|
||||||
var states = ActPostStates()
|
var states = ActPostStates()
|
||||||
|
|
||||||
var accountList: ArrayList<SavedAccount> = ArrayList()
|
var accountList: List<SavedAccount> = emptyList()
|
||||||
var account: SavedAccount? = null
|
var account: SavedAccount? = null
|
||||||
var attachmentList = ArrayList<PostAttachment>()
|
var attachmentList = ArrayList<PostAttachment>()
|
||||||
var isPostComplete: Boolean = false
|
var isPostComplete: Boolean = false
|
||||||
|
@ -169,16 +170,17 @@ class ActPost : AppCompatActivity(),
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
backPressed {
|
backPressed {
|
||||||
finish()
|
launchAndShowError {
|
||||||
// 戻るボタンを押したときとonPauseで2回保存することになるが、
|
finish()
|
||||||
// 同じ内容はDB上は重複しないはず…
|
// 戻るボタンを押したときとonPauseで2回保存することになるが、
|
||||||
saveDraft()
|
// 同じ内容はDB上は重複しないはず…
|
||||||
|
saveDraft()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (isMultiWindowPost) ActMain.refActMain?.get()?.closeList?.add(WeakReference(this))
|
if (isMultiWindowPost) ActMain.refActMain?.get()?.closeList?.add(WeakReference(this))
|
||||||
App1.setActivityTheme(this)
|
App1.setActivityTheme(this)
|
||||||
appState = App1.getAppState(this)
|
appState = App1.getAppState(this)
|
||||||
handler = appState.handler
|
handler = appState.handler
|
||||||
pref = appState.pref
|
|
||||||
attachmentUploader = AttachmentUploader(this, handler)
|
attachmentUploader = AttachmentUploader(this, handler)
|
||||||
attachmentPicker = AttachmentPicker(this, this)
|
attachmentPicker = AttachmentPicker(this, this)
|
||||||
density = resources.displayMetrics.density
|
density = resources.displayMetrics.density
|
||||||
|
@ -202,9 +204,11 @@ class ActPost : AppCompatActivity(),
|
||||||
|
|
||||||
initUI()
|
initUI()
|
||||||
|
|
||||||
when (savedInstanceState) {
|
launchAndShowError {
|
||||||
null -> updateText(intent, confirmed = true, saveDraft = false)
|
when (savedInstanceState) {
|
||||||
else -> restoreState(savedInstanceState)
|
null -> updateText(intent, saveDraft = false)
|
||||||
|
else -> restoreState(savedInstanceState)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,11 +246,18 @@ class ActPost : AppCompatActivity(),
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
// 編集中にホーム画面を押したり他アプリに移動する場合は下書きを保存する
|
if (!isPostComplete) launchMain {
|
||||||
// やや過剰な気がするが、自アプリに戻ってくるときにランチャーからアイコンタップされると
|
try {
|
||||||
// メイン画面より上にあるアクティビティはすべて消されてしまうので
|
// 編集中にホーム画面を押したり他アプリに移動する場合は下書きを保存する
|
||||||
// このタイミングで保存するしかない
|
// やや過剰な気がするが、自アプリに戻ってくるときにランチャーからアイコンタップされると
|
||||||
if (!isPostComplete) saveDraft()
|
// メイン画面より上にあるアクティビティはすべて消されてしまうので
|
||||||
|
// このタイミングで保存するしかない
|
||||||
|
saveDraft()
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
log.e(ex, "can't save draft.")
|
||||||
|
showToast(ex, "can't save draft.")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(v: View) {
|
override fun onClick(v: View) {
|
||||||
|
@ -317,7 +328,7 @@ class ActPost : AppCompatActivity(),
|
||||||
fun initUI() {
|
fun initUI() {
|
||||||
setContentView(views.root)
|
setContentView(views.root)
|
||||||
|
|
||||||
if (PrefB.bpPostButtonBarTop(pref)) {
|
if (PrefB.bpPostButtonBarTop.value) {
|
||||||
val bar = findViewById<View>(R.id.llFooterBar)
|
val bar = findViewById<View>(R.id.llFooterBar)
|
||||||
val parent = bar.parent as ViewGroup
|
val parent = bar.parent as ViewGroup
|
||||||
parent.removeView(bar)
|
parent.removeView(bar)
|
||||||
|
@ -401,7 +412,7 @@ class ActPost : AppCompatActivity(),
|
||||||
|
|
||||||
views.cbContentWarning.setOnCheckedChangeListener { _, _ -> showContentWarningEnabled() }
|
views.cbContentWarning.setOnCheckedChangeListener { _, _ -> showContentWarningEnabled() }
|
||||||
|
|
||||||
completionHelper = CompletionHelper(this, pref, appState.handler)
|
completionHelper = CompletionHelper(this, appState.handler)
|
||||||
completionHelper.attachEditText(
|
completionHelper.attachEditText(
|
||||||
views.root,
|
views.root,
|
||||||
views.etContent,
|
views.etContent,
|
||||||
|
|
|
@ -9,15 +9,18 @@ import android.view.MenuItem
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import jp.juggler.subwaytooter.api.entity.TootAccount
|
import jp.juggler.subwaytooter.api.entity.TootAccount
|
||||||
import jp.juggler.subwaytooter.api.entity.TootStatus
|
import jp.juggler.subwaytooter.api.entity.TootStatus
|
||||||
|
import jp.juggler.subwaytooter.auth.AuthRepo
|
||||||
import jp.juggler.subwaytooter.databinding.ActTextBinding
|
import jp.juggler.subwaytooter.databinding.ActTextBinding
|
||||||
import jp.juggler.subwaytooter.dialog.pickAccount
|
import jp.juggler.subwaytooter.dialog.pickAccount
|
||||||
import jp.juggler.subwaytooter.table.MutedWord
|
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
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.CustomShare
|
||||||
import jp.juggler.subwaytooter.util.CustomShareTarget
|
import jp.juggler.subwaytooter.util.CustomShareTarget
|
||||||
import jp.juggler.subwaytooter.util.TootTextEncoder
|
import jp.juggler.subwaytooter.util.TootTextEncoder
|
||||||
import jp.juggler.subwaytooter.util.copyToClipboard
|
import jp.juggler.subwaytooter.util.copyToClipboard
|
||||||
import jp.juggler.util.*
|
import jp.juggler.util.*
|
||||||
|
import jp.juggler.util.coroutine.launchAndShowError
|
||||||
import jp.juggler.util.coroutine.launchMain
|
import jp.juggler.util.coroutine.launchMain
|
||||||
import jp.juggler.util.data.*
|
import jp.juggler.util.data.*
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
|
@ -114,28 +117,34 @@ class ActText : AppCompatActivity() {
|
||||||
return super.onCreateOptionsMenu(menu)
|
return super.onCreateOptionsMenu(menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val authRepo by lazy {
|
||||||
|
AuthRepo(this)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
App1.setActivityTheme(this)
|
App1.setActivityTheme(this)
|
||||||
|
|
||||||
account = intent.long(EXTRA_ACCOUNT_DB_ID)
|
|
||||||
?.let { SavedAccount.loadAccount(this, it) }
|
|
||||||
|
|
||||||
initUI()
|
initUI()
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
launchAndShowError {
|
||||||
val sv = intent.string(EXTRA_TEXT) ?: ""
|
account = intent.long(EXTRA_ACCOUNT_DB_ID)
|
||||||
val contentStart = intent.int(EXTRA_CONTENT_START) ?: 0
|
?.let { daoSavedAccount.loadAccount(it) }
|
||||||
val contentEnd = intent.int(EXTRA_CONTENT_END) ?: sv.length
|
|
||||||
views.etText.setText(sv)
|
|
||||||
|
|
||||||
// Android 9 以降ではフォーカスがないとsetSelectionできない
|
if (savedInstanceState == null) {
|
||||||
if (Build.VERSION.SDK_INT >= 28) {
|
val sv = intent.string(EXTRA_TEXT) ?: ""
|
||||||
views.etText.requestFocus()
|
val contentStart = intent.int(EXTRA_CONTENT_START) ?: 0
|
||||||
views.etText.hideKeyboard()
|
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() {
|
private fun muteWord() {
|
||||||
selection.trim().notEmpty()?.let {
|
launchAndShowError {
|
||||||
try {
|
selection.trim().notEmpty()?.let {
|
||||||
MutedWord.save(it)
|
daoMutedWord.save(it)
|
||||||
App1.getAppState(this).onMuteUpdated()
|
App1.getAppState(this@ActText).onMuteUpdated()
|
||||||
showToast(false, R.string.word_was_muted)
|
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.annotation.SuppressLint
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.res.Configuration
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.util.Log
|
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.api.TootApiClient
|
||||||
import jp.juggler.subwaytooter.column.ColumnType
|
import jp.juggler.subwaytooter.column.ColumnType
|
||||||
import jp.juggler.subwaytooter.emoji.EmojiMap
|
import jp.juggler.subwaytooter.emoji.EmojiMap
|
||||||
import jp.juggler.subwaytooter.global.Global
|
import jp.juggler.subwaytooter.pref.LazyContextHolder
|
||||||
import jp.juggler.subwaytooter.global.appPref
|
|
||||||
import jp.juggler.subwaytooter.pref.PrefI
|
import jp.juggler.subwaytooter.pref.PrefI
|
||||||
import jp.juggler.subwaytooter.pref.PrefS
|
import jp.juggler.subwaytooter.pref.PrefS
|
||||||
import jp.juggler.subwaytooter.table.HighlightWord
|
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.subwaytooter.util.ProgressResponseBody
|
||||||
import jp.juggler.util.*
|
import jp.juggler.util.*
|
||||||
import jp.juggler.util.data.asciiPattern
|
import jp.juggler.util.data.asciiPattern
|
||||||
|
import jp.juggler.util.data.notEmpty
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
import jp.juggler.util.log.initializeToastUtils
|
import jp.juggler.util.log.initializeToastUtils
|
||||||
import jp.juggler.util.network.MySslSocketFactory
|
import jp.juggler.util.network.MySslSocketFactory
|
||||||
import jp.juggler.util.network.toPostRequestBuilder
|
import jp.juggler.util.network.toPostRequestBuilder
|
||||||
|
import jp.juggler.util.os.applicationContextSafe
|
||||||
import jp.juggler.util.ui.*
|
import jp.juggler.util.ui.*
|
||||||
import okhttp3.*
|
import okhttp3.*
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
@ -55,11 +57,17 @@ class App1 : Application() {
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
log.d("onCreate")
|
log.d("onCreate")
|
||||||
|
LazyContextHolder.init(applicationContextSafe)
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
initializeToastUtils(this)
|
initializeToastUtils(this)
|
||||||
prepare(applicationContext, "App1.onCreate")
|
prepare(applicationContext, "App1.onCreate")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||||
|
super.onConfigurationChanged(newConfig)
|
||||||
|
LazyContextHolder.init(applicationContextSafe)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onTerminate() {
|
override fun onTerminate() {
|
||||||
log.d("onTerminate")
|
log.d("onTerminate")
|
||||||
super.onTerminate()
|
super.onTerminate()
|
||||||
|
@ -134,7 +142,7 @@ class App1 : Application() {
|
||||||
"SubwayTooter/${BuildConfig.VERSION_NAME} Android/${Build.VERSION.RELEASE}"
|
"SubwayTooter/${BuildConfig.VERSION_NAME} Android/${Build.VERSION.RELEASE}"
|
||||||
|
|
||||||
private fun getUserAgent(): String {
|
private fun getUserAgent(): String {
|
||||||
val userAgentCustom = PrefS.spUserAgent(appPref)
|
val userAgentCustom = PrefS.spUserAgent.value
|
||||||
return when {
|
return when {
|
||||||
userAgentCustom.isNotEmpty() && !reNotAllowedInUserAgent.matcher(userAgentCustom)
|
userAgentCustom.isNotEmpty() && !reNotAllowedInUserAgent.matcher(userAgentCustom)
|
||||||
.find() -> userAgentCustom
|
.find() -> userAgentCustom
|
||||||
|
@ -142,13 +150,14 @@ class App1 : Application() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val user_agent_interceptor = Interceptor { chain ->
|
private fun userAgentInterceptor() =
|
||||||
chain.proceed(
|
Interceptor { chain ->
|
||||||
chain.request().newBuilder()
|
chain.proceed(
|
||||||
.header("User-Agent", getUserAgent())
|
chain.request().newBuilder()
|
||||||
.build()
|
.header("User-Agent", getUserAgent())
|
||||||
)
|
.build()
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private var cookieManager: CookieManager? = null
|
private var cookieManager: CookieManager? = null
|
||||||
private var cookieJar: CookieJar? = null
|
private var cookieJar: CookieJar? = null
|
||||||
|
@ -185,7 +194,7 @@ class App1 : Application() {
|
||||||
.connectionSpecs(Collections.singletonList(spec))
|
.connectionSpecs(Collections.singletonList(spec))
|
||||||
.sslSocketFactory(MySslSocketFactory, MySslSocketFactory.trustManager)
|
.sslSocketFactory(MySslSocketFactory, MySslSocketFactory.trustManager)
|
||||||
.addInterceptor(ProgressResponseBody.makeInterceptor())
|
.addInterceptor(ProgressResponseBody.makeInterceptor())
|
||||||
.addInterceptor(user_agent_interceptor)
|
.addInterceptor(userAgentInterceptor())
|
||||||
|
|
||||||
// クッキーの導入は検討中。とりあえずmstdn.jpではクッキー有効でも改善しなかったので現時点では追加しない
|
// クッキーの導入は検討中。とりあえずmstdn.jpではクッキー有効でも改善しなかったので現時点では追加しない
|
||||||
// .cookieJar(cookieJar)
|
// .cookieJar(cookieJar)
|
||||||
|
@ -228,8 +237,6 @@ class App1 : Application() {
|
||||||
|
|
||||||
initializeFont()
|
initializeFont()
|
||||||
|
|
||||||
Global.prepare(appContext, "App1.prepare($caller)")
|
|
||||||
|
|
||||||
// We want at least 2 threads and at most 4 threads in the core pool,
|
// 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
|
// preferring to have 1 less than the CPU count to avoid saturating
|
||||||
// the CPU with background work
|
// the CPU with background work
|
||||||
|
@ -278,7 +285,7 @@ class App1 : Application() {
|
||||||
|
|
||||||
Logger.getLogger(OkHttpClient::class.java.name).level = Level.FINE
|
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設定はキャッシュを使わない
|
// API用のHTTP設定はキャッシュを使わない
|
||||||
ok_http_client = prepareOkHttp(apiReadTimeout, apiReadTimeout)
|
ok_http_client = prepareOkHttp(apiReadTimeout, apiReadTimeout)
|
||||||
|
@ -294,10 +301,11 @@ class App1 : Application() {
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
// 内蔵メディアビューア用のHTTP設定はタイムアウトを調整可能
|
// 内蔵メディアビューア用のHTTP設定はタイムアウトを調整可能
|
||||||
val mediaReadTimeout = max(3, PrefS.spMediaReadTimeout.toInt(appPref))
|
val mediaReadTimeout = max(3, PrefS.spMediaReadTimeout.toInt())
|
||||||
ok_http_client_media_viewer = prepareOkHttp(mediaReadTimeout, mediaReadTimeout)
|
ok_http_client_media_viewer =
|
||||||
.cache(cache)
|
prepareOkHttp(mediaReadTimeout, mediaReadTimeout)
|
||||||
.build()
|
.cache(cache)
|
||||||
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
val handler = Handler(appContext.mainLooper)
|
val handler = Handler(appContext.mainLooper)
|
||||||
|
@ -310,7 +318,7 @@ class App1 : Application() {
|
||||||
|
|
||||||
log.d("create AppState.")
|
log.d("create AppState.")
|
||||||
|
|
||||||
state = AppState(appContext, handler, appPref)
|
state = AppState(appContext, handler)
|
||||||
appStateX = state
|
appStateX = state
|
||||||
|
|
||||||
// getAppState()を使える状態にしてからカラム一覧をロードする
|
// getAppState()を使える状態にしてからカラム一覧をロードする
|
||||||
|
@ -421,7 +429,7 @@ class App1 : Application() {
|
||||||
) {
|
) {
|
||||||
prepare(activity.applicationContext, "setActivityTheme")
|
prepare(activity.applicationContext, "setActivityTheme")
|
||||||
|
|
||||||
var nTheme = PrefI.ipUiTheme(appPref)
|
var nTheme = PrefI.ipUiTheme.value
|
||||||
if (forceDark && nTheme == 0) nTheme = 1
|
if (forceDark && nTheme == 0) nTheme = 1
|
||||||
activity.setTheme(
|
activity.setTheme(
|
||||||
when (nTheme) {
|
when (nTheme) {
|
||||||
|
@ -484,9 +492,8 @@ class App1 : Application() {
|
||||||
.url(url)
|
.url(url)
|
||||||
.cacheControl(CACHE_CONTROL)
|
.cacheControl(CACHE_CONTROL)
|
||||||
.also {
|
.also {
|
||||||
val access_token = accessInfo?.getAccessToken()
|
accessInfo?.bearerAccessToken?.notEmpty()?.let { a ->
|
||||||
if (access_token?.isNotEmpty() == true) {
|
it.header("Authorization", "Bearer $a")
|
||||||
it.header("Authorization", "Bearer $access_token")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,10 +17,7 @@ import jp.juggler.subwaytooter.column.getBackgroundImageDir
|
||||||
import jp.juggler.subwaytooter.column.onMuteUpdated
|
import jp.juggler.subwaytooter.column.onMuteUpdated
|
||||||
import jp.juggler.subwaytooter.span.MyClickableSpan
|
import jp.juggler.subwaytooter.span.MyClickableSpan
|
||||||
import jp.juggler.subwaytooter.streaming.StreamManager
|
import jp.juggler.subwaytooter.streaming.StreamManager
|
||||||
import jp.juggler.subwaytooter.table.HighlightWord
|
import jp.juggler.subwaytooter.table.*
|
||||||
import jp.juggler.subwaytooter.table.MutedApp
|
|
||||||
import jp.juggler.subwaytooter.table.MutedWord
|
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
|
||||||
import jp.juggler.subwaytooter.util.NetworkStateTracker
|
import jp.juggler.subwaytooter.util.NetworkStateTracker
|
||||||
import jp.juggler.subwaytooter.util.PostAttachment
|
import jp.juggler.subwaytooter.util.PostAttachment
|
||||||
import jp.juggler.util.*
|
import jp.juggler.util.*
|
||||||
|
@ -49,7 +46,6 @@ class DedupItem(
|
||||||
class AppState(
|
class AppState(
|
||||||
internal val context: Context,
|
internal val context: Context,
|
||||||
internal val handler: Handler,
|
internal val handler: Handler,
|
||||||
internal val pref: SharedPreferences,
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -190,7 +186,7 @@ class AppState(
|
||||||
// TextToSpeech
|
// TextToSpeech
|
||||||
|
|
||||||
private val isTextToSpeechRequired: Boolean
|
private val isTextToSpeechRequired: Boolean
|
||||||
get() = columnList.any { it.enableSpeech } || HighlightWord.hasTextToSpeechHighlightWord()
|
get() = columnList.any { it.enableSpeech } || daoHighlightWord.hasTextToSpeechHighlightWord()
|
||||||
|
|
||||||
private val ttsReceiver = object : BroadcastReceiver() {
|
private val ttsReceiver = object : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context, intent: Intent?) {
|
override fun onReceive(context: Context, intent: Intent?) {
|
||||||
|
@ -303,8 +299,8 @@ class AppState(
|
||||||
if (list != null) editColumnList(save = false) { it.addAll(list) }
|
if (list != null) editColumnList(save = false) { it.addAll(list) }
|
||||||
|
|
||||||
// ミュートデータのロード
|
// ミュートデータのロード
|
||||||
TootStatus.muted_app = MutedApp.nameSet
|
TootStatus.muted_app = daoMutedApp.nameSet()
|
||||||
TootStatus.muted_word = MutedWord.nameSet
|
TootStatus.muted_word = daoMutedWord.nameSet()
|
||||||
|
|
||||||
// 背景フォルダの掃除
|
// 背景フォルダの掃除
|
||||||
try {
|
try {
|
||||||
|
@ -599,8 +595,8 @@ class AppState(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onMuteUpdated() {
|
fun onMuteUpdated() {
|
||||||
TootStatus.muted_app = MutedApp.nameSet
|
TootStatus.muted_app = daoMutedApp.nameSet()
|
||||||
TootStatus.muted_word = MutedWord.nameSet
|
TootStatus.muted_word = daoMutedWord.nameSet()
|
||||||
columnList.forEach { it.onMuteUpdated() }
|
columnList.forEach { it.onMuteUpdated() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,11 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import jp.juggler.subwaytooter.notification.TrackingType
|
import jp.juggler.subwaytooter.notification.TrackingType
|
||||||
import jp.juggler.subwaytooter.notification.onNotificationDeleted
|
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.coroutine.launchMain
|
||||||
import jp.juggler.util.data.notEmpty
|
import jp.juggler.util.data.notEmpty
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
|
import jp.juggler.util.os.applicationContextSafe
|
||||||
|
|
||||||
class EventReceiver : BroadcastReceiver() {
|
class EventReceiver : BroadcastReceiver() {
|
||||||
|
|
||||||
|
@ -18,32 +19,36 @@ class EventReceiver : BroadcastReceiver() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onReceive(context: Context, intent: Intent?) {
|
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,
|
||||||
Intent.ACTION_BOOT_COMPLETED,
|
-> {
|
||||||
Intent.ACTION_MY_PACKAGE_REPLACED,
|
App1.prepare(context.applicationContextSafe, action)
|
||||||
-> {
|
daoNotificationTracking.resetPostAll()
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
package jp.juggler.subwaytooter
|
||||||
|
//
|
||||||
import com.google.firebase.messaging.FirebaseMessagingService
|
//import com.google.firebase.messaging.FirebaseMessagingService
|
||||||
import com.google.firebase.messaging.RemoteMessage
|
//import com.google.firebase.messaging.RemoteMessage
|
||||||
import jp.juggler.subwaytooter.notification.PollingChecker
|
//import jp.juggler.subwaytooter.api.entity.Acct
|
||||||
import jp.juggler.subwaytooter.notification.restartAllWorker
|
//import jp.juggler.subwaytooter.notification.PollingChecker
|
||||||
import jp.juggler.subwaytooter.pref.PrefDevice
|
//import jp.juggler.subwaytooter.notification.restartAllWorker
|
||||||
import jp.juggler.subwaytooter.table.NotificationCache
|
//import jp.juggler.subwaytooter.pref.PrefDevice
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
//import jp.juggler.subwaytooter.pref.prefDevice
|
||||||
import jp.juggler.util.log.LogCategory
|
//import jp.juggler.subwaytooter.table.SavedAccount
|
||||||
import kotlinx.coroutines.runBlocking
|
//import jp.juggler.subwaytooter.table.apiNotificationCache
|
||||||
import java.util.*
|
//import jp.juggler.subwaytooter.table.apiSavedAccount
|
||||||
|
//import jp.juggler.util.log.LogCategory
|
||||||
class MyFirebaseMessagingService : FirebaseMessagingService() {
|
//import kotlinx.coroutines.runBlocking
|
||||||
|
//import java.util.*
|
||||||
companion object {
|
//
|
||||||
internal val log = LogCategory("MyFirebaseMessagingService")
|
//class MyFirebaseMessagingService : FirebaseMessagingService() {
|
||||||
|
//
|
||||||
private val pushMessageStatus = LinkedList<String>()
|
// companion object {
|
||||||
|
// internal val log = LogCategory("MyFirebaseMessagingService")
|
||||||
// Pushメッセージが処理済みか調べる
|
//
|
||||||
private fun isDuplicateMessage(messageId: String) =
|
// private val pushMessageStatus = LinkedList<String>()
|
||||||
synchronized(pushMessageStatus) {
|
//
|
||||||
when (pushMessageStatus.contains(messageId)) {
|
// // Pushメッセージが処理済みか調べる
|
||||||
true -> true
|
// private fun isDuplicateMessage(messageId: String) =
|
||||||
else -> {
|
// synchronized(pushMessageStatus) {
|
||||||
pushMessageStatus.addFirst(messageId)
|
// when (pushMessageStatus.contains(messageId)) {
|
||||||
while (pushMessageStatus.size > 100) {
|
// true -> true
|
||||||
pushMessageStatus.removeLast()
|
// else -> {
|
||||||
}
|
// pushMessageStatus.addFirst(messageId)
|
||||||
false
|
// 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()
|
// override fun onNewToken(token: String) {
|
||||||
restartAllWorker(this)
|
// try {
|
||||||
} catch (ex: Throwable) {
|
// log.w("onTokenRefresh: token=$token")
|
||||||
log.e(ex, "onNewToken failed")
|
// prefDevice.device
|
||||||
}
|
// pollingWorker2IntervalPrefDevice.from(this).edit().putString(PrefDevice.KEY_DEVICE_TOKEN, token).apply()
|
||||||
}
|
// restartAllWorker(this)
|
||||||
|
// } catch (ex: Throwable) {
|
||||||
override fun onMessageReceived(remoteMessage: RemoteMessage) {
|
// log.e(ex, "onNewToken failed")
|
||||||
val context = this
|
// }
|
||||||
|
// }
|
||||||
val messageId = remoteMessage.messageId ?: return
|
//
|
||||||
if (isDuplicateMessage(messageId)) return
|
// override fun onMessageReceived(remoteMessage: RemoteMessage) {
|
||||||
|
// val messageId = remoteMessage.messageId ?: return
|
||||||
val accounts = ArrayList<SavedAccount>()
|
// if (isDuplicateMessage(messageId)) return
|
||||||
for ((key, value) in remoteMessage.data) {
|
//
|
||||||
log.w("onMessageReceived: $key=$value")
|
// val accounts = ArrayList<SavedAccount>()
|
||||||
when (key) {
|
// for ((key, value) in remoteMessage.data) {
|
||||||
"notification_tag" -> {
|
// log.w("onMessageReceived: $key=$value")
|
||||||
SavedAccount.loadByTag(context, value).forEach { sa ->
|
// when (key) {
|
||||||
NotificationCache.resetLastLoad(sa.db_id)
|
//// "notification_tag" -> {
|
||||||
accounts.add(sa)
|
//// apiSavedAccount.(context, value).forEach { sa ->
|
||||||
}
|
//// apiNotificationCache.resetLastLoad(sa.db_id)
|
||||||
}
|
//// accounts.add(sa)
|
||||||
"acct" -> {
|
//// }
|
||||||
SavedAccount.loadAccountByAcct(context, value)?.let { sa ->
|
//// }
|
||||||
NotificationCache.resetLastLoad(sa.db_id)
|
// "acct" -> {
|
||||||
accounts.add(sa)
|
// apiSavedAccount.loadAccountByAcct(Acct.parse(value))?.let { sa ->
|
||||||
}
|
// apiNotificationCache.resetLastLoad(sa.db_id)
|
||||||
}
|
// accounts.add(sa)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
if (accounts.isEmpty()) {
|
// }
|
||||||
// タグにマッチする情報がなかった場合、全部読み直す
|
//
|
||||||
NotificationCache.resetLastLoad()
|
// if (accounts.isEmpty()) {
|
||||||
accounts.addAll(SavedAccount.loadAccountList(context))
|
// // タグにマッチする情報がなかった場合、全部読み直す
|
||||||
}
|
// apiNotificationCache.resetLastLoad()
|
||||||
|
// accounts.addAll(apiSavedAccount.loadAccountList())
|
||||||
log.i("accounts.size=${accounts.size} thred=${Thread.currentThread().name}")
|
// }
|
||||||
runBlocking {
|
//
|
||||||
accounts.forEach {
|
// log.i("accounts.size=${accounts.size} thred=${Thread.currentThread().name}")
|
||||||
check(it.db_id)
|
// runBlocking {
|
||||||
}
|
// accounts.forEach {
|
||||||
}
|
// check(it.db_id)
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
private suspend fun check(accountDbId: Long) {
|
// }
|
||||||
try {
|
//
|
||||||
PollingChecker(
|
// private suspend fun check(accountDbId: Long) {
|
||||||
context = this,
|
// try {
|
||||||
accountDbId = accountDbId
|
// PollingChecker(
|
||||||
).check { a, s ->
|
// context = this,
|
||||||
val text = "[${a.acct.pretty}]${s.desc}"
|
// accountDbId = accountDbId
|
||||||
log.i(text)
|
// ).check { a, s ->
|
||||||
}
|
// val text = "[${a.acct.pretty}]${s.desc}"
|
||||||
} catch (ex: Throwable) {
|
// log.i(text)
|
||||||
log.e(ex, "check failed. accountDbId=$accountDbId")
|
// }
|
||||||
}
|
// } 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.emoji.EmojiMap
|
||||||
import jp.juggler.subwaytooter.pref.PrefB
|
import jp.juggler.subwaytooter.pref.PrefB
|
||||||
import jp.juggler.subwaytooter.pref.PrefI
|
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.EmojiImageSpan
|
||||||
import jp.juggler.subwaytooter.span.createSpan
|
import jp.juggler.subwaytooter.span.createSpan
|
||||||
import jp.juggler.subwaytooter.table.UserRelation
|
import jp.juggler.subwaytooter.table.UserRelation
|
||||||
|
@ -43,14 +43,14 @@ fun defaultColorIcon(context: Context, iconId: Int): Drawable? =
|
||||||
it.setTintMode(PorterDuff.Mode.SRC_IN)
|
it.setTintMode(PorterDuff.Mode.SRC_IN)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getVisibilityIconId(isMisskeyData: Boolean, visibility: TootVisibility): Int {
|
fun TootVisibility.getVisibilityIconId(isMisskeyData: Boolean): Int {
|
||||||
val isMisskey = when (PrefI.ipVisibilityStyle()) {
|
val isMisskey = when (PrefI.ipVisibilityStyle.value) {
|
||||||
PrefI.VS_MASTODON -> false
|
PrefI.VS_MASTODON -> false
|
||||||
PrefI.VS_MISSKEY -> true
|
PrefI.VS_MISSKEY -> true
|
||||||
else -> isMisskeyData
|
else -> isMisskeyData
|
||||||
}
|
}
|
||||||
return when {
|
return when {
|
||||||
isMisskey -> when (visibility) {
|
isMisskey -> when (this) {
|
||||||
TootVisibility.Public -> R.drawable.ic_public
|
TootVisibility.Public -> R.drawable.ic_public
|
||||||
TootVisibility.UnlistedHome -> R.drawable.ic_home
|
TootVisibility.UnlistedHome -> R.drawable.ic_home
|
||||||
TootVisibility.PrivateFollowers -> R.drawable.ic_lock_open
|
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.Limited -> R.drawable.ic_account_circle
|
||||||
TootVisibility.Mutual -> R.drawable.ic_bidirectional
|
TootVisibility.Mutual -> R.drawable.ic_bidirectional
|
||||||
}
|
}
|
||||||
else -> when (visibility) {
|
else -> when (this) {
|
||||||
TootVisibility.Public -> R.drawable.ic_public
|
TootVisibility.Public -> R.drawable.ic_public
|
||||||
TootVisibility.UnlistedHome -> R.drawable.ic_lock_open
|
TootVisibility.UnlistedHome -> R.drawable.ic_lock_open
|
||||||
TootVisibility.PrivateFollowers -> R.drawable.ic_lock
|
TootVisibility.PrivateFollowers -> R.drawable.ic_lock
|
||||||
|
@ -87,19 +87,15 @@ fun getVisibilityIconId(isMisskeyData: Boolean, visibility: TootVisibility): Int
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getVisibilityString(
|
fun TootVisibility.getVisibilityString(isMisskeyData: Boolean): String {
|
||||||
context: Context,
|
val isMisskey = when (PrefI.ipVisibilityStyle.value) {
|
||||||
isMisskeyData: Boolean,
|
|
||||||
visibility: TootVisibility,
|
|
||||||
): String {
|
|
||||||
val isMisskey = when (PrefI.ipVisibilityStyle()) {
|
|
||||||
PrefI.VS_MASTODON -> false
|
PrefI.VS_MASTODON -> false
|
||||||
PrefI.VS_MISSKEY -> true
|
PrefI.VS_MISSKEY -> true
|
||||||
else -> isMisskeyData
|
else -> isMisskeyData
|
||||||
}
|
}
|
||||||
return context.getString(
|
return lazyContext.getString(
|
||||||
when {
|
when {
|
||||||
isMisskey -> when (visibility) {
|
isMisskey -> when (this) {
|
||||||
TootVisibility.Public -> R.string.visibility_public
|
TootVisibility.Public -> R.string.visibility_public
|
||||||
TootVisibility.UnlistedHome -> R.string.visibility_home
|
TootVisibility.UnlistedHome -> R.string.visibility_home
|
||||||
TootVisibility.PrivateFollowers -> R.string.visibility_followers
|
TootVisibility.PrivateFollowers -> R.string.visibility_followers
|
||||||
|
@ -116,7 +112,7 @@ fun getVisibilityString(
|
||||||
TootVisibility.Limited -> R.string.visibility_limited
|
TootVisibility.Limited -> R.string.visibility_limited
|
||||||
TootVisibility.Mutual -> R.string.visibility_mutual
|
TootVisibility.Mutual -> R.string.visibility_mutual
|
||||||
}
|
}
|
||||||
else -> when (visibility) {
|
else -> when (this) {
|
||||||
TootVisibility.Public -> R.string.visibility_public
|
TootVisibility.Public -> R.string.visibility_public
|
||||||
TootVisibility.UnlistedHome -> R.string.visibility_unlisted
|
TootVisibility.UnlistedHome -> R.string.visibility_unlisted
|
||||||
TootVisibility.PrivateFollowers -> R.string.visibility_followers
|
TootVisibility.PrivateFollowers -> R.string.visibility_followers
|
||||||
|
@ -144,8 +140,8 @@ fun getVisibilityCaption(
|
||||||
visibility: TootVisibility,
|
visibility: TootVisibility,
|
||||||
): CharSequence {
|
): CharSequence {
|
||||||
|
|
||||||
val iconId = getVisibilityIconId(isMisskeyData, visibility)
|
val iconId = visibility.getVisibilityIconId(isMisskeyData)
|
||||||
val sv = getVisibilityString(context, isMisskeyData, visibility)
|
val sv = visibility.getVisibilityString(isMisskeyData)
|
||||||
val color = context.attrColor(R.attr.colorTextContent)
|
val color = context.attrColor(R.attr.colorTextContent)
|
||||||
val sb = SpannableStringBuilder()
|
val sb = SpannableStringBuilder()
|
||||||
|
|
||||||
|
@ -182,11 +178,11 @@ fun setFollowIcon(
|
||||||
alphaMultiplier: Float,
|
alphaMultiplier: Float,
|
||||||
) {
|
) {
|
||||||
val colorFollowed =
|
val colorFollowed =
|
||||||
PrefI.ipButtonFollowingColor(context.pref()).notZero()
|
PrefI.ipButtonFollowingColor.value.notZero()
|
||||||
?: context.attrColor(R.attr.colorButtonAccentFollow)
|
?: context.attrColor(R.attr.colorButtonAccentFollow)
|
||||||
|
|
||||||
val colorFollowRequest =
|
val colorFollowRequest =
|
||||||
PrefI.ipButtonFollowRequestColor(context.pref()).notZero()
|
PrefI.ipButtonFollowRequestColor.value.notZero()
|
||||||
?: context.attrColor(R.attr.colorButtonAccentFollowRequest)
|
?: context.attrColor(R.attr.colorButtonAccentFollowRequest)
|
||||||
|
|
||||||
val colorError = context.attrColor(R.attr.colorRegexFilterError)
|
val colorError = context.attrColor(R.attr.colorRegexFilterError)
|
||||||
|
@ -309,7 +305,7 @@ fun fixHorizontalPadding(v: View, dpDelta: Float = 12f) {
|
||||||
val widthDp = dm.widthPixels / dm.density
|
val widthDp = dm.widthPixels / dm.density
|
||||||
if (widthDp >= 640f && v.resources?.configuration?.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
if (widthDp >= 640f && v.resources?.configuration?.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||||
val padLr = (0.5f + dpDelta * dm.density).toInt()
|
val padLr = (0.5f + dpDelta * dm.density).toInt()
|
||||||
when (PrefI.ipJustifyWindowContentPortrait()) {
|
when (PrefI.ipJustifyWindowContentPortrait.value) {
|
||||||
PrefI.JWCP_START -> {
|
PrefI.JWCP_START -> {
|
||||||
v.setPaddingRelative(padLr, padT, padLr + dm.widthPixels / 2, padB)
|
v.setPaddingRelative(padLr, padT, padLr + dm.widthPixels / 2, padB)
|
||||||
return
|
return
|
||||||
|
@ -338,7 +334,7 @@ fun fixHorizontalMargin(v: View) {
|
||||||
log.d("fixHorizontalMargin: orientation=$orientationString, w=${widthDp}dp, h=${dm.heightPixels / dm.density}")
|
log.d("fixHorizontalMargin: orientation=$orientationString, w=${widthDp}dp, h=${dm.heightPixels / dm.density}")
|
||||||
|
|
||||||
if (widthDp >= 640f && v.resources?.configuration?.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
if (widthDp >= 640f && v.resources?.configuration?.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||||
when (PrefI.ipJustifyWindowContentPortrait()) {
|
when (PrefI.ipJustifyWindowContentPortrait.value) {
|
||||||
PrefI.JWCP_START -> {
|
PrefI.JWCP_START -> {
|
||||||
lp.marginStart = 0
|
lp.marginStart = 0
|
||||||
lp.marginEnd = dm.widthPixels / 2
|
lp.marginEnd = dm.widthPixels / 2
|
||||||
|
@ -397,7 +393,7 @@ fun SpannableStringBuilder.appendMisskeyReaction(
|
||||||
emoji == null ->
|
emoji == null ->
|
||||||
append("text")
|
append("text")
|
||||||
|
|
||||||
PrefB.bpUseTwemoji(context) -> {
|
PrefB.bpUseTwemoji.value -> {
|
||||||
val start = this.length
|
val start = this.length
|
||||||
append(text)
|
append(text)
|
||||||
val end = this.length
|
val end = this.length
|
||||||
|
@ -414,9 +410,10 @@ fun SpannableStringBuilder.appendMisskeyReaction(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.setSwitchColor(root: View?) {
|
fun Context.setSwitchColor(root: View?) {
|
||||||
|
root ?: return
|
||||||
val colorBg = attrColor(R.attr.colorWindowBackground)
|
val colorBg = attrColor(R.attr.colorWindowBackground)
|
||||||
val colorOff = attrColor(R.attr.colorSwitchOff)
|
val colorOff = attrColor(R.attr.colorSwitchOff)
|
||||||
val colorOn = PrefI.ipSwitchOnColor()
|
val colorOn = PrefI.ipSwitchOnColor.value
|
||||||
|
|
||||||
val colorDisabled = mixColor(colorBg, colorOff)
|
val colorDisabled = mixColor(colorBg, colorOff)
|
||||||
|
|
||||||
|
@ -451,7 +448,7 @@ fun Context.setSwitchColor(root: View?) {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
root?.scan {
|
root.scan {
|
||||||
(it as? SwitchCompat)?.apply {
|
(it as? SwitchCompat)?.apply {
|
||||||
thumbTintList = thumbStates
|
thumbTintList = thumbStates
|
||||||
trackTintList = trackStates
|
trackTintList = trackStates
|
||||||
|
@ -487,13 +484,14 @@ fun AppCompatActivity.setStatusBarColor(forceDark: Boolean = false) {
|
||||||
|
|
||||||
var c = when {
|
var c = when {
|
||||||
forceDark -> Color.BLACK
|
forceDark -> Color.BLACK
|
||||||
else -> PrefI.ipStatusBarColor.invoke().notZero() ?: attrColor(R.attr.colorPrimaryDark)
|
else -> PrefI.ipStatusBarColor.value.notZero()
|
||||||
|
?: attrColor(R.attr.colorPrimaryDark)
|
||||||
}
|
}
|
||||||
setStatusBarColorCompat(c)
|
setStatusBarColorCompat(c)
|
||||||
|
|
||||||
c = when {
|
c = when {
|
||||||
forceDark -> Color.BLACK
|
forceDark -> Color.BLACK
|
||||||
else -> PrefI.ipNavigationBarColor()
|
else -> PrefI.ipNavigationBarColor.value
|
||||||
}
|
}
|
||||||
setNavigationBarColorCompat(c)
|
setNavigationBarColorCompat(c)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
package jp.juggler.subwaytooter.action
|
package jp.juggler.subwaytooter.action
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import jp.juggler.subwaytooter.api.TootParser
|
import jp.juggler.subwaytooter.api.entity.Acct
|
||||||
import jp.juggler.subwaytooter.api.entity.*
|
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.runApiTask2
|
||||||
import jp.juggler.subwaytooter.api.showApiError
|
import jp.juggler.subwaytooter.api.showApiError
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
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.subwaytooter.util.matchHost
|
||||||
import jp.juggler.util.data.JsonObject
|
import jp.juggler.util.data.JsonObject
|
||||||
import jp.juggler.util.data.buildJsonObject
|
import jp.juggler.util.data.buildJsonObject
|
||||||
|
@ -22,7 +23,6 @@ internal suspend fun AppCompatActivity.addPseudoAccount(
|
||||||
host: Host,
|
host: Host,
|
||||||
instanceInfoArg: TootInstance? = null,
|
instanceInfoArg: TootInstance? = null,
|
||||||
): SavedAccount? {
|
): SavedAccount? {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
suspend fun AppCompatActivity.getInstanceInfo(): TootInstance? {
|
suspend fun AppCompatActivity.getInstanceInfo(): TootInstance? {
|
||||||
return try {
|
return try {
|
||||||
|
@ -35,7 +35,7 @@ internal suspend fun AppCompatActivity.addPseudoAccount(
|
||||||
|
|
||||||
val acct = Acct.parse("?", host)
|
val acct = Acct.parse("?", host)
|
||||||
|
|
||||||
var account = SavedAccount.loadAccountByAcct(this, acct.ascii)
|
var account = daoSavedAccount.loadAccountByAcct(acct)
|
||||||
if (account != null) return account
|
if (account != null) return account
|
||||||
|
|
||||||
val instanceInfo = instanceInfoArg
|
val instanceInfo = instanceInfoArg
|
||||||
|
@ -47,7 +47,7 @@ internal suspend fun AppCompatActivity.addPseudoAccount(
|
||||||
put("acct", acct.username) // ローカルから参照した場合なのでshort acct
|
put("acct", acct.username) // ローカルから参照した場合なのでshort acct
|
||||||
}
|
}
|
||||||
|
|
||||||
val rowId = SavedAccount.insert(
|
val rowId = daoSavedAccount.saveNew(
|
||||||
acct = acct.ascii,
|
acct = acct.ascii,
|
||||||
host = host.ascii,
|
host = host.ascii,
|
||||||
domain = instanceInfo.apDomain.ascii,
|
domain = instanceInfo.apDomain.ascii,
|
||||||
|
@ -56,7 +56,7 @@ internal suspend fun AppCompatActivity.addPseudoAccount(
|
||||||
misskeyVersion = instanceInfo.misskeyVersionMajor
|
misskeyVersion = instanceInfo.misskeyVersionMajor
|
||||||
)
|
)
|
||||||
|
|
||||||
account = SavedAccount.loadAccount(applicationContext, rowId)
|
account = daoSavedAccount.loadAccount(rowId)
|
||||||
?: error("loadAccount returns null.")
|
?: error("loadAccount returns null.")
|
||||||
|
|
||||||
account.notification_follow = false
|
account.notification_follow = false
|
||||||
|
@ -68,7 +68,7 @@ internal suspend fun AppCompatActivity.addPseudoAccount(
|
||||||
account.notification_vote = false
|
account.notification_vote = false
|
||||||
account.notification_post = false
|
account.notification_post = false
|
||||||
account.notification_update = false
|
account.notification_update = false
|
||||||
account.saveSetting()
|
daoSavedAccount.saveSetting(account)
|
||||||
return account
|
return account
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
log.e(ex, "addPseudoAccount failed.")
|
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を取得
|
//// relationshipを取得
|
||||||
//internal fun loadRelation1Mastodon(
|
//internal fun loadRelation1Mastodon(
|
||||||
// client : TootApiClient,
|
// client : TootApiClient,
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
package jp.juggler.subwaytooter.action
|
package jp.juggler.subwaytooter.action
|
||||||
|
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.Context
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import jp.juggler.subwaytooter.*
|
import jp.juggler.subwaytooter.*
|
||||||
import jp.juggler.subwaytooter.actmain.addColumn
|
import jp.juggler.subwaytooter.actmain.addColumn
|
||||||
import jp.juggler.subwaytooter.actmain.afterAccountVerify
|
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.*
|
||||||
import jp.juggler.subwaytooter.dialog.DlgCreateAccount.Companion.showUserCreateDialog
|
import jp.juggler.subwaytooter.dialog.DlgCreateAccount.Companion.showUserCreateDialog
|
||||||
import jp.juggler.subwaytooter.dialog.LoginForm.Companion.showLoginForm
|
import jp.juggler.subwaytooter.dialog.LoginForm.Companion.showLoginForm
|
||||||
import jp.juggler.subwaytooter.notification.APP_SERVER
|
|
||||||
import jp.juggler.subwaytooter.pref.*
|
import jp.juggler.subwaytooter.pref.*
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
import jp.juggler.subwaytooter.table.SavedAccount
|
||||||
import jp.juggler.subwaytooter.util.LinkHelper
|
import jp.juggler.subwaytooter.util.LinkHelper
|
||||||
import jp.juggler.subwaytooter.util.openBrowser
|
import jp.juggler.subwaytooter.util.openBrowser
|
||||||
import jp.juggler.util.*
|
import jp.juggler.util.*
|
||||||
import jp.juggler.util.coroutine.launchIO
|
|
||||||
import jp.juggler.util.coroutine.launchMain
|
import jp.juggler.util.coroutine.launchMain
|
||||||
import jp.juggler.util.data.JsonObject
|
import jp.juggler.util.data.JsonObject
|
||||||
import jp.juggler.util.data.encodePercent
|
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.network.toPost
|
||||||
import jp.juggler.util.ui.dismissSafe
|
import jp.juggler.util.ui.dismissSafe
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import ru.gildor.coroutines.okhttp.await
|
|
||||||
|
|
||||||
private val log = LogCategory("Action_Account")
|
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() {
|
fun ActMain.accountOpenSetting() {
|
||||||
launchMain {
|
launchMain {
|
||||||
|
@ -284,105 +232,3 @@ fun ActMain.accountResendConfirmMail(accessInfo: SavedAccount) {
|
||||||
}.show()
|
}.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
|
package jp.juggler.subwaytooter.action
|
||||||
|
|
||||||
import android.app.AlertDialog
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import jp.juggler.subwaytooter.ActColumnList
|
import jp.juggler.subwaytooter.ActColumnList
|
||||||
import jp.juggler.subwaytooter.ActMain
|
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.currentColumn
|
||||||
import jp.juggler.subwaytooter.actmain.handleOtherUri
|
import jp.juggler.subwaytooter.actmain.handleOtherUri
|
||||||
import jp.juggler.subwaytooter.api.entity.TootApplication
|
import jp.juggler.subwaytooter.api.entity.TootApplication
|
||||||
|
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
||||||
import jp.juggler.subwaytooter.dialog.DlgOpenUrl
|
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.log.showToast
|
||||||
import jp.juggler.util.ui.dismissSafe
|
import jp.juggler.util.ui.dismissSafe
|
||||||
|
|
||||||
|
@ -18,22 +19,10 @@ fun ActMain.openColumnList() =
|
||||||
arColumnList.launch(ActColumnList.createIntent(this, currentColumn))
|
arColumnList.launch(ActColumnList.createIntent(this, currentColumn))
|
||||||
|
|
||||||
// アプリをミュートする
|
// アプリをミュートする
|
||||||
fun ActMain.appMute(
|
fun ActMain.appMute(application: TootApplication?) = launchAndShowError {
|
||||||
application: TootApplication?,
|
application ?: return@launchAndShowError
|
||||||
confirmed: Boolean = false,
|
confirm(R.string.mute_application_confirm, application.name)
|
||||||
) {
|
daoMutedApp.save(application.name)
|
||||||
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)
|
|
||||||
appState.onMuteUpdated()
|
appState.onMuteUpdated()
|
||||||
showToast(false, R.string.app_was_muted)
|
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.DlgConfirm.confirm
|
||||||
import jp.juggler.subwaytooter.dialog.pickAccount
|
import jp.juggler.subwaytooter.dialog.pickAccount
|
||||||
import jp.juggler.subwaytooter.getVisibilityCaption
|
import jp.juggler.subwaytooter.getVisibilityCaption
|
||||||
import jp.juggler.subwaytooter.table.AcctColor
|
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
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.subwaytooter.util.emptyCallback
|
||||||
import jp.juggler.util.coroutine.launchAndShowError
|
import jp.juggler.util.coroutine.launchAndShowError
|
||||||
import jp.juggler.util.coroutine.launchMain
|
import jp.juggler.util.coroutine.launchMain
|
||||||
|
@ -212,7 +214,7 @@ private class BoostImpl(
|
||||||
visibility == TootVisibility.PrivateFollowers -> R.string.confirm_private_boost_from
|
visibility == TootVisibility.PrivateFollowers -> R.string.confirm_private_boost_from
|
||||||
else -> R.string.confirm_boost_from
|
else -> R.string.confirm_boost_from
|
||||||
},
|
},
|
||||||
AcctColor.getNickname(accessInfo)
|
daoAcctColor.getNickname(accessInfo)
|
||||||
),
|
),
|
||||||
when (bSet) {
|
when (bSet) {
|
||||||
true -> accessInfo.confirm_boost
|
true -> accessInfo.confirm_boost
|
||||||
|
@ -223,7 +225,7 @@ private class BoostImpl(
|
||||||
true -> accessInfo.confirm_boost = newConfirmEnabled
|
true -> accessInfo.confirm_boost = newConfirmEnabled
|
||||||
else -> accessInfo.confirm_unboost = newConfirmEnabled
|
else -> accessInfo.confirm_unboost = newConfirmEnabled
|
||||||
}
|
}
|
||||||
accessInfo.saveSetting()
|
daoSavedAccount.saveSetting(accessInfo)
|
||||||
activity.reloadAccountSetting(accessInfo)
|
activity.reloadAccountSetting(accessInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -288,7 +290,7 @@ fun ActMain.boostFromAnotherAccount(
|
||||||
|
|
||||||
if (isPrivateToot) {
|
if (isPrivateToot) {
|
||||||
val list = ArrayList<SavedAccount>()
|
val list = ArrayList<SavedAccount>()
|
||||||
for (a in SavedAccount.loadAccountList(applicationContext)) {
|
for (a in daoSavedAccount.loadAccountList()) {
|
||||||
if (a.acct == statusOwner) list.add(a)
|
if (a.acct == statusOwner) list.add(a)
|
||||||
}
|
}
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
|
|
|
@ -10,11 +10,14 @@ import jp.juggler.subwaytooter.api.entity.*
|
||||||
import jp.juggler.subwaytooter.column.ColumnType
|
import jp.juggler.subwaytooter.column.ColumnType
|
||||||
import jp.juggler.subwaytooter.column.findStatus
|
import jp.juggler.subwaytooter.column.findStatus
|
||||||
import jp.juggler.subwaytooter.columnviewholder.ItemListAdapter
|
import jp.juggler.subwaytooter.columnviewholder.ItemListAdapter
|
||||||
import jp.juggler.subwaytooter.dialog.ActionsDialog
|
import jp.juggler.subwaytooter.dialog.actionsDialog
|
||||||
import jp.juggler.subwaytooter.table.AcctColor
|
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
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.matchHost
|
||||||
import jp.juggler.subwaytooter.util.openCustomTab
|
import jp.juggler.subwaytooter.util.openCustomTab
|
||||||
|
import jp.juggler.util.coroutine.launchAndShowError
|
||||||
import jp.juggler.util.coroutine.launchMain
|
import jp.juggler.util.coroutine.launchMain
|
||||||
import jp.juggler.util.data.notEmpty
|
import jp.juggler.util.data.notEmpty
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
|
@ -244,122 +247,126 @@ fun ActMain.conversationOtherInstance(
|
||||||
statusIdAccess: EntityId? = null,
|
statusIdAccess: EntityId? = null,
|
||||||
isReference: Boolean = false,
|
isReference: Boolean = false,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val activity = this
|
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(
|
val localAccountList = ArrayList<SavedAccount>()
|
||||||
getString(
|
|
||||||
R.string.open_web_on_host,
|
|
||||||
hostOriginal.pretty
|
|
||||||
)
|
|
||||||
) { openCustomTab(urlArg) }
|
|
||||||
|
|
||||||
// トゥートの投稿元タンスにあるアカウント
|
// TLを読んだタンスにあるアカウント
|
||||||
val localAccountList = ArrayList<SavedAccount>()
|
val accessAccountList = ArrayList<SavedAccount>()
|
||||||
|
|
||||||
// TLを読んだタンスにあるアカウント
|
// その他のタンスにあるアカウント
|
||||||
val accessAccountList = ArrayList<SavedAccount>()
|
val otherAccountList = ArrayList<SavedAccount>()
|
||||||
|
|
||||||
// その他のタンスにあるアカウント
|
for (a in daoSavedAccount.loadAccountList()) {
|
||||||
val otherAccountList = ArrayList<SavedAccount>()
|
|
||||||
|
|
||||||
for (a in SavedAccount.loadAccountList(applicationContext)) {
|
// 疑似アカウントは後でまとめて処理する
|
||||||
|
if (a.isPseudo) continue
|
||||||
|
|
||||||
// 疑似アカウントは後でまとめて処理する
|
if (isReference && TootInstance.getCached(a)?.canUseReference != true) continue
|
||||||
if (a.isPseudo) 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)) {
|
// 参照の場合、status URLから/references を除去しないとURLでの検索ができない
|
||||||
// アクセス情報+ステータスID でアクセスできるなら
|
val url = when {
|
||||||
// 同タンスのアカウントならステータスIDの変換なしに表示できる
|
isReference -> """/references\z""".toRegex().replace(urlArg, "")
|
||||||
localAccountList.add(a)
|
else -> urlArg
|
||||||
} 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 {
|
if (localAccountList.isEmpty()) {
|
||||||
isReference -> """/references\z""".toRegex().replace(urlArg, "")
|
if (statusIdOriginal != null) {
|
||||||
else -> urlArg
|
action(
|
||||||
}
|
getString(R.string.open_in_pseudo_account, "?@${hostOriginal.pretty}")
|
||||||
|
) {
|
||||||
// 同タンスのアカウントがないなら、疑似アカウントで開く選択肢
|
launchMain {
|
||||||
if (localAccountList.isEmpty()) {
|
addPseudoAccount(hostOriginal)?.let { sa ->
|
||||||
if (statusIdOriginal != null) {
|
conversationLocal(
|
||||||
dialog.addAction(
|
pos,
|
||||||
getString(R.string.open_in_pseudo_account, "?@${hostOriginal.pretty}")
|
sa,
|
||||||
) {
|
statusIdOriginal,
|
||||||
launchMain {
|
isReference = isReference
|
||||||
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}")
|
if (statusIdOriginal != null) {
|
||||||
) {
|
for (a in localAccountList.sortedByNickname()) {
|
||||||
launchMain {
|
action(
|
||||||
addPseudoAccount(hostOriginal)?.let { sa ->
|
daoAcctColor.getStringWithNickname(
|
||||||
conversationRemote(pos, sa, url)
|
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
|
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
|
// step 1: choose account
|
||||||
|
|
||||||
val host = statusArg.account.apDomain
|
val host = statusArg.account.apDomain
|
||||||
val localAccountList = ArrayList<SavedAccount>()
|
val localAccountList = ArrayList<SavedAccount>()
|
||||||
val otherAccountList = ArrayList<SavedAccount>()
|
val otherAccountList = ArrayList<SavedAccount>()
|
||||||
|
for (a in daoSavedAccount.loadAccountList()) {
|
||||||
for (a in SavedAccount.loadAccountList(this)) {
|
when {
|
||||||
|
// 検索APIはログイン必須なので疑似アカウントは使えない
|
||||||
// 検索APIはログイン必須なので疑似アカウントは使えない
|
a.isPseudo -> continue
|
||||||
if (a.isPseudo) continue
|
a.matchHost(host) -> localAccountList.add(a)
|
||||||
|
else -> otherAccountList.add(a)
|
||||||
if (a.matchHost(host)) {
|
|
||||||
localAccountList.add(a)
|
|
||||||
} else {
|
|
||||||
otherAccountList.add(a)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val dialog = ActionsDialog()
|
val activity = this
|
||||||
|
launchAndShowError {
|
||||||
|
|
||||||
SavedAccount.sort(localAccountList)
|
// step2: 選択したアカウントで投稿を検索して返信元の投稿のIDを調べる
|
||||||
for (a in localAccountList) {
|
suspend fun step2(a: SavedAccount) {
|
||||||
dialog.addAction(
|
var tmp: TootStatus? = null
|
||||||
AcctColor.getStringWithNickname(
|
runApiTask(a) { client ->
|
||||||
this,
|
val (result, status) = client.syncStatus(a, statusArg)
|
||||||
R.string.open_in_account,
|
tmp = status
|
||||||
a.acct
|
result
|
||||||
)
|
}?.let { result ->
|
||||||
) { step2(a) }
|
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.entity.TootFilter
|
||||||
import jp.juggler.subwaytooter.api.runApiTask
|
import jp.juggler.subwaytooter.api.runApiTask
|
||||||
import jp.juggler.subwaytooter.column.onFilterDeleted
|
import jp.juggler.subwaytooter.column.onFilterDeleted
|
||||||
import jp.juggler.subwaytooter.dialog.ActionsDialog
|
|
||||||
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
||||||
|
import jp.juggler.subwaytooter.dialog.actionsDialog
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
import jp.juggler.subwaytooter.table.SavedAccount
|
||||||
import jp.juggler.util.coroutine.launchAndShowError
|
import jp.juggler.util.coroutine.launchAndShowError
|
||||||
import jp.juggler.util.log.showToast
|
import jp.juggler.util.log.showToast
|
||||||
|
@ -17,15 +17,17 @@ import okhttp3.Request
|
||||||
|
|
||||||
fun ActMain.openFilterMenu(accessInfo: SavedAccount, item: TootFilter?) {
|
fun ActMain.openFilterMenu(accessInfo: SavedAccount, item: TootFilter?) {
|
||||||
item ?: return
|
item ?: return
|
||||||
|
val activity = this
|
||||||
val ad = ActionsDialog()
|
launchAndShowError {
|
||||||
ad.addAction(getString(R.string.edit)) {
|
actionsDialog(getString(R.string.filter_of, item.displayString)) {
|
||||||
ActKeywordFilter.open(this, accessInfo, item.id)
|
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(
|
fun ActMain.filterDelete(
|
||||||
|
|
|
@ -12,9 +12,7 @@ import jp.juggler.subwaytooter.column.fireRebindAdapterItems
|
||||||
import jp.juggler.subwaytooter.column.removeUser
|
import jp.juggler.subwaytooter.column.removeUser
|
||||||
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
||||||
import jp.juggler.subwaytooter.dialog.pickAccount
|
import jp.juggler.subwaytooter.dialog.pickAccount
|
||||||
import jp.juggler.subwaytooter.table.AcctColor
|
import jp.juggler.subwaytooter.table.*
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
|
||||||
import jp.juggler.subwaytooter.table.UserRelation
|
|
||||||
import jp.juggler.util.coroutine.launchAndShowError
|
import jp.juggler.util.coroutine.launchAndShowError
|
||||||
import jp.juggler.util.coroutine.launchMain
|
import jp.juggler.util.coroutine.launchMain
|
||||||
import jp.juggler.util.log.showToast
|
import jp.juggler.util.log.showToast
|
||||||
|
@ -81,7 +79,7 @@ fun ActMain.clickFollowRequestAccept(
|
||||||
accept -> R.string.follow_accept_confirm
|
accept -> R.string.follow_accept_confirm
|
||||||
else -> R.string.follow_deny_confirm
|
else -> R.string.follow_deny_confirm
|
||||||
},
|
},
|
||||||
AcctColor.getNickname(accessInfo, who)
|
daoAcctColor.getNickname(accessInfo, who)
|
||||||
)
|
)
|
||||||
followRequestAuthorize(accessInfo, whoRef, accept)
|
followRequestAuthorize(accessInfo, whoRef, accept)
|
||||||
}
|
}
|
||||||
|
@ -152,12 +150,12 @@ fun ActMain.follow(
|
||||||
getString(
|
getString(
|
||||||
R.string.confirm_follow_request_who_from,
|
R.string.confirm_follow_request_who_from,
|
||||||
whoRef.decoded_display_name,
|
whoRef.decoded_display_name,
|
||||||
AcctColor.getNickname(accessInfo)
|
daoAcctColor.getNickname(accessInfo)
|
||||||
),
|
),
|
||||||
accessInfo.confirm_follow_locked,
|
accessInfo.confirm_follow_locked,
|
||||||
) { newConfirmEnabled ->
|
) { newConfirmEnabled ->
|
||||||
accessInfo.confirm_follow_locked = newConfirmEnabled
|
accessInfo.confirm_follow_locked = newConfirmEnabled
|
||||||
accessInfo.saveSetting()
|
daoSavedAccount.saveSetting(accessInfo)
|
||||||
activity.reloadAccountSetting(accessInfo)
|
activity.reloadAccountSetting(accessInfo)
|
||||||
}
|
}
|
||||||
} else if (bFollow) {
|
} else if (bFollow) {
|
||||||
|
@ -165,12 +163,12 @@ fun ActMain.follow(
|
||||||
getString(
|
getString(
|
||||||
R.string.confirm_follow_who_from,
|
R.string.confirm_follow_who_from,
|
||||||
whoRef.decoded_display_name,
|
whoRef.decoded_display_name,
|
||||||
AcctColor.getNickname(accessInfo)
|
daoAcctColor.getNickname(accessInfo)
|
||||||
),
|
),
|
||||||
accessInfo.confirm_follow
|
accessInfo.confirm_follow
|
||||||
) { newConfirmEnabled ->
|
) { newConfirmEnabled ->
|
||||||
accessInfo.confirm_follow = newConfirmEnabled
|
accessInfo.confirm_follow = newConfirmEnabled
|
||||||
accessInfo.saveSetting()
|
daoSavedAccount.saveSetting(accessInfo)
|
||||||
activity.reloadAccountSetting(accessInfo)
|
activity.reloadAccountSetting(accessInfo)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -178,12 +176,12 @@ fun ActMain.follow(
|
||||||
getString(
|
getString(
|
||||||
R.string.confirm_unfollow_who_from,
|
R.string.confirm_unfollow_who_from,
|
||||||
whoRef.decoded_display_name,
|
whoRef.decoded_display_name,
|
||||||
AcctColor.getNickname(accessInfo)
|
daoAcctColor.getNickname(accessInfo)
|
||||||
),
|
),
|
||||||
accessInfo.confirm_unfollow
|
accessInfo.confirm_unfollow
|
||||||
) { newConfirmEnabled ->
|
) { newConfirmEnabled ->
|
||||||
accessInfo.confirm_unfollow = newConfirmEnabled
|
accessInfo.confirm_unfollow = newConfirmEnabled
|
||||||
accessInfo.saveSetting()
|
daoSavedAccount.saveSetting(accessInfo)
|
||||||
activity.reloadAccountSetting(accessInfo)
|
activity.reloadAccountSetting(accessInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -235,9 +233,9 @@ fun ActMain.follow(
|
||||||
)?.also { result ->
|
)?.also { result ->
|
||||||
|
|
||||||
fun saveFollow(f: Boolean) {
|
fun saveFollow(f: Boolean) {
|
||||||
val ur = UserRelation.load(accessInfo.db_id, userId)
|
val ur = daoUserRelation.load(accessInfo.db_id, userId)
|
||||||
ur.following = f
|
ur.following = f
|
||||||
UserRelation.save1Misskey(
|
daoUserRelation.save1Misskey(
|
||||||
System.currentTimeMillis(),
|
System.currentTimeMillis(),
|
||||||
accessInfo.db_id,
|
accessInfo.db_id,
|
||||||
userId.toString(),
|
userId.toString(),
|
||||||
|
@ -264,7 +262,7 @@ fun ActMain.follow(
|
||||||
"".toFormRequestBody().toPost()
|
"".toFormRequestBody().toPost()
|
||||||
)?.also { result ->
|
)?.also { result ->
|
||||||
val newRelation = parseItem(::TootRelationShip, parser, result.jsonObject)
|
val newRelation = parseItem(::TootRelationShip, parser, result.jsonObject)
|
||||||
resultRelation = accessInfo.saveUserRelation(newRelation)
|
resultRelation = daoUserRelation.saveUserRelation(accessInfo, newRelation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}?.let { result ->
|
}?.let { result ->
|
||||||
|
@ -313,26 +311,26 @@ private fun ActMain.followRemote(
|
||||||
confirm(
|
confirm(
|
||||||
getString(
|
getString(
|
||||||
R.string.confirm_follow_request_who_from,
|
R.string.confirm_follow_request_who_from,
|
||||||
AcctColor.getNickname(acct),
|
daoAcctColor.getNickname(acct),
|
||||||
AcctColor.getNickname(accessInfo)
|
daoAcctColor.getNickname(accessInfo)
|
||||||
),
|
),
|
||||||
accessInfo.confirm_follow_locked,
|
accessInfo.confirm_follow_locked,
|
||||||
) { newConfirmEnabled ->
|
) { newConfirmEnabled ->
|
||||||
accessInfo.confirm_follow_locked = newConfirmEnabled
|
accessInfo.confirm_follow_locked = newConfirmEnabled
|
||||||
accessInfo.saveSetting()
|
daoSavedAccount.saveSetting(accessInfo)
|
||||||
reloadAccountSetting(accessInfo)
|
reloadAccountSetting(accessInfo)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
confirm(
|
confirm(
|
||||||
getString(
|
getString(
|
||||||
R.string.confirm_follow_who_from,
|
R.string.confirm_follow_who_from,
|
||||||
AcctColor.getNickname(acct),
|
daoAcctColor.getNickname(acct),
|
||||||
AcctColor.getNickname(accessInfo)
|
daoAcctColor.getNickname(accessInfo)
|
||||||
),
|
),
|
||||||
accessInfo.confirm_follow
|
accessInfo.confirm_follow
|
||||||
) { newConfirmEnabled ->
|
) { newConfirmEnabled ->
|
||||||
accessInfo.confirm_follow = newConfirmEnabled
|
accessInfo.confirm_follow = newConfirmEnabled
|
||||||
accessInfo.saveSetting()
|
daoSavedAccount.saveSetting(accessInfo)
|
||||||
reloadAccountSetting(accessInfo)
|
reloadAccountSetting(accessInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -357,12 +355,16 @@ private fun ActMain.followRemote(
|
||||||
result?.error?.contains("already not following") == true
|
result?.error?.contains("already not following") == true
|
||||||
) {
|
) {
|
||||||
// DBから読み直して値を変更する
|
// DBから読み直して値を変更する
|
||||||
resultRelation = UserRelation.load(accessInfo.db_id, userId)
|
resultRelation = daoUserRelation.load(accessInfo.db_id, userId)
|
||||||
.apply { following = true }
|
.apply { following = true }
|
||||||
} else {
|
} else {
|
||||||
// parserに残ってるRelationをDBに保存する
|
// parserに残ってるRelationをDBに保存する
|
||||||
parser.account(result?.jsonObject)?.let {
|
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()
|
"".toFormRequestBody().toPost()
|
||||||
)?.also { result ->
|
)?.also { result ->
|
||||||
parseItem(::TootRelationShip, parser, result.jsonObject)?.let {
|
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)
|
val user = parser.account(result?.jsonObject)
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
// parserに残ってるRelationをDBに保存する
|
// 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 から更新されたリレーションを返す
|
// Mastodon 3.0.0 から更新されたリレーションを返す
|
||||||
// https//github.com/tootsuite/mastodon/pull/11800
|
// https//github.com/tootsuite/mastodon/pull/11800
|
||||||
val newRelation = parseItem(::TootRelationShip, parser, result.jsonObject)
|
val newRelation = parseItem(::TootRelationShip, parser, result.jsonObject)
|
||||||
accessInfo.saveUserRelation(newRelation)
|
daoUserRelation.saveUserRelation(accessInfo, newRelation)
|
||||||
// 読めなくてもエラー処理は行わない
|
// 読めなくてもエラー処理は行わない
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -543,7 +549,7 @@ fun ActMain.followRequestDelete(
|
||||||
confirm(
|
confirm(
|
||||||
R.string.confirm_cancel_follow_request_who_from,
|
R.string.confirm_cancel_follow_request_who_from,
|
||||||
whoRef.decoded_display_name,
|
whoRef.decoded_display_name,
|
||||||
AcctColor.getNickname(accessInfo)
|
daoAcctColor.getNickname(accessInfo)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -572,7 +578,12 @@ fun ActMain.followRequestDelete(
|
||||||
)?.also { result ->
|
)?.also { result ->
|
||||||
parser.account(result.jsonObject)?.let {
|
parser.account(result.jsonObject)?.let {
|
||||||
// parserに残ってるRelationをDBに保存する
|
// 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.ColumnType
|
||||||
import jp.juggler.subwaytooter.column.onListListUpdated
|
import jp.juggler.subwaytooter.column.onListListUpdated
|
||||||
import jp.juggler.subwaytooter.column.onListNameUpdated
|
import jp.juggler.subwaytooter.column.onListNameUpdated
|
||||||
import jp.juggler.subwaytooter.dialog.ActionsDialog
|
|
||||||
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
||||||
import jp.juggler.subwaytooter.dialog.DlgTextInput
|
import jp.juggler.subwaytooter.dialog.DlgTextInput
|
||||||
|
import jp.juggler.subwaytooter.dialog.actionsDialog
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
import jp.juggler.subwaytooter.table.SavedAccount
|
||||||
import jp.juggler.util.coroutine.launchAndShowError
|
import jp.juggler.util.coroutine.launchAndShowError
|
||||||
import jp.juggler.util.coroutine.launchMain
|
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?) {
|
fun ActMain.clickListMoreButton(pos: Int, accessInfo: SavedAccount, item: TimelineItem?) {
|
||||||
when (item) {
|
when (item) {
|
||||||
is TootList -> {
|
is TootList -> {
|
||||||
ActionsDialog()
|
launchAndShowError {
|
||||||
.addAction(getString(R.string.list_timeline)) {
|
actionsDialog(item.title) {
|
||||||
addColumn(pos, accessInfo, ColumnType.LIST_TL, item.id)
|
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 -> {
|
is MisskeyAntenna -> {
|
||||||
|
|
|
@ -12,6 +12,7 @@ import jp.juggler.subwaytooter.api.syncAccountByAcct
|
||||||
import jp.juggler.subwaytooter.column.onListMemberUpdated
|
import jp.juggler.subwaytooter.column.onListMemberUpdated
|
||||||
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
import jp.juggler.subwaytooter.table.SavedAccount
|
||||||
|
import jp.juggler.subwaytooter.table.daoUserRelation
|
||||||
import jp.juggler.util.*
|
import jp.juggler.util.*
|
||||||
import jp.juggler.util.coroutine.launchAndShowError
|
import jp.juggler.util.coroutine.launchAndShowError
|
||||||
import jp.juggler.util.coroutine.launchMain
|
import jp.juggler.util.coroutine.launchMain
|
||||||
|
@ -71,7 +72,8 @@ fun ActMain.listMemberAdd(
|
||||||
"".toFormRequestBody().toPost()
|
"".toFormRequestBody().toPost()
|
||||||
) ?: return@runApiTask null
|
) ?: return@runApiTask null
|
||||||
|
|
||||||
val relation = accessInfo.saveUserRelation(
|
val relation = daoUserRelation.saveUserRelation(
|
||||||
|
accessInfo,
|
||||||
parseItem(::TootRelationShip, parser, result.jsonObject)
|
parseItem(::TootRelationShip, parser, result.jsonObject)
|
||||||
) ?: return@runApiTask TootApiResult("parse error.")
|
) ?: 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.api.syncStatus
|
||||||
import jp.juggler.subwaytooter.dialog.pickAccount
|
import jp.juggler.subwaytooter.dialog.pickAccount
|
||||||
import jp.juggler.subwaytooter.pref.PrefB
|
import jp.juggler.subwaytooter.pref.PrefB
|
||||||
import jp.juggler.subwaytooter.pref.PrefDevice
|
|
||||||
import jp.juggler.subwaytooter.pref.PrefS
|
import jp.juggler.subwaytooter.pref.PrefS
|
||||||
|
import jp.juggler.subwaytooter.pref.prefDevice
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
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.subwaytooter.util.matchHost
|
||||||
|
import jp.juggler.util.coroutine.launchAndShowError
|
||||||
import jp.juggler.util.coroutine.launchMain
|
import jp.juggler.util.coroutine.launchMain
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
import jp.juggler.util.log.showToast
|
import jp.juggler.util.log.showToast
|
||||||
|
@ -37,7 +40,7 @@ fun ActPost.saveWindowSize() {
|
||||||
// WindowMetrics#getBounds() the window size including all system bar areas
|
// WindowMetrics#getBounds() the window size including all system bar areas
|
||||||
windowManager?.currentWindowMetrics?.bounds?.let { bounds ->
|
windowManager?.currentWindowMetrics?.bounds?.let { bounds ->
|
||||||
log.d("API=${Build.VERSION.SDK_INT}, WindowMetrics#getBounds() $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 {
|
} else {
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
|
@ -45,7 +48,7 @@ fun ActPost.saveWindowSize() {
|
||||||
val dm = DisplayMetrics()
|
val dm = DisplayMetrics()
|
||||||
display.getMetrics(dm)
|
display.getMetrics(dm)
|
||||||
log.d("API=${Build.VERSION.SDK_INT}, displayMetrics=${dm.widthPixels},${dm.heightPixels}")
|
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,
|
scheduledStatus: TootScheduled? = null,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val useManyWindow = PrefB.bpManyWindowPost(pref)
|
val useManyWindow = PrefB.bpManyWindowPost.value
|
||||||
val useMultiWindow = useManyWindow || PrefB.bpMultiWindowPost(pref)
|
val useMultiWindow = useManyWindow || PrefB.bpMultiWindowPost.value
|
||||||
|
|
||||||
val intent = ActPost.createIntent(
|
val intent = ActPost.createIntent(
|
||||||
context = this,
|
context = this,
|
||||||
|
@ -99,7 +102,9 @@ fun ActMain.openActPostImpl(
|
||||||
ActPost.refActPost?.get()
|
ActPost.refActPost?.get()
|
||||||
?.takeIf { it.isLiveActivity }
|
?.takeIf { it.isLiveActivity }
|
||||||
?.let {
|
?.let {
|
||||||
it.updateText(intent)
|
launchAndShowError {
|
||||||
|
it.updateText(intent)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,11 +113,10 @@ fun ActMain.openActPostImpl(
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
|
||||||
|
|
||||||
var options = ActivityOptionsCompat.makeBasic()
|
var options = ActivityOptionsCompat.makeBasic()
|
||||||
PrefDevice.loadPostWindowBound(this)
|
prefDevice.loadPostWindowBound()?.let {
|
||||||
?.let {
|
log.d("ActPost launchBounds $it")
|
||||||
log.d("ActPost launchBounds $it")
|
options = options.setLaunchBounds(it)
|
||||||
options = options.setLaunchBounds(it)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
arActPost.launch(intent, options)
|
arActPost.launch(intent, options)
|
||||||
}
|
}
|
||||||
|
@ -254,7 +258,7 @@ fun ActMain.quoteFromAnotherAccount(
|
||||||
fun ActMain.quoteName(who: TootAccount) {
|
fun ActMain.quoteName(who: TootAccount) {
|
||||||
var sv = who.display_name
|
var sv = who.display_name
|
||||||
try {
|
try {
|
||||||
val fmt = PrefS.spQuoteNameFormat(pref)
|
val fmt = PrefS.spQuoteNameFormat.value
|
||||||
if (fmt.contains("%1\$s")) {
|
if (fmt.contains("%1\$s")) {
|
||||||
sv = String.format(Locale.getDefault(), fmt, sv)
|
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.dialog.pickAccount
|
||||||
import jp.juggler.subwaytooter.emoji.CustomEmoji
|
import jp.juggler.subwaytooter.emoji.CustomEmoji
|
||||||
import jp.juggler.subwaytooter.emoji.UnicodeEmoji
|
import jp.juggler.subwaytooter.emoji.UnicodeEmoji
|
||||||
import jp.juggler.subwaytooter.table.AcctColor
|
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
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.subwaytooter.util.DecodeOptions
|
||||||
import jp.juggler.util.coroutine.launchAndShowError
|
import jp.juggler.util.coroutine.launchAndShowError
|
||||||
import jp.juggler.util.coroutine.launchMain
|
import jp.juggler.util.coroutine.launchMain
|
||||||
|
@ -122,11 +124,15 @@ fun ActMain.reactionAdd(
|
||||||
)
|
)
|
||||||
val emojiSpan = TootReaction.toSpannableStringBuilder(options, code, urlArg)
|
val emojiSpan = TootReaction.toSpannableStringBuilder(options, code, urlArg)
|
||||||
confirm(
|
confirm(
|
||||||
getString(R.string.confirm_reaction, emojiSpan, AcctColor.getNickname(accessInfo)),
|
getString(
|
||||||
|
R.string.confirm_reaction,
|
||||||
|
emojiSpan,
|
||||||
|
daoAcctColor.getNickname(accessInfo)
|
||||||
|
),
|
||||||
accessInfo.confirm_reaction,
|
accessInfo.confirm_reaction,
|
||||||
) { newConfirmEnabled ->
|
) { newConfirmEnabled ->
|
||||||
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(
|
isCustomEmoji && url?.likePleromaStatusUrl() == true -> confirm(
|
||||||
R.string.confirm_reaction_to_pleroma,
|
R.string.confirm_reaction_to_pleroma,
|
||||||
emojiSpan,
|
emojiSpan,
|
||||||
AcctColor.getNickname(accessInfo),
|
daoAcctColor.getNickname(accessInfo),
|
||||||
resolvedStatus.account.acct.host?.pretty ?: "(null)"
|
resolvedStatus.account.acct.host?.pretty ?: "(null)"
|
||||||
)
|
)
|
||||||
|
|
||||||
else -> confirm(
|
else -> confirm(
|
||||||
getString(R.string.confirm_reaction, emojiSpan, AcctColor.getNickname(accessInfo)),
|
getString(
|
||||||
|
R.string.confirm_reaction,
|
||||||
|
emojiSpan,
|
||||||
|
daoAcctColor.getNickname(accessInfo)
|
||||||
|
),
|
||||||
accessInfo.confirm_reaction,
|
accessInfo.confirm_reaction,
|
||||||
) { newConfirmEnabled ->
|
) { newConfirmEnabled ->
|
||||||
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.TootScheduled
|
||||||
import jp.juggler.subwaytooter.api.entity.TootStatus
|
import jp.juggler.subwaytooter.api.entity.TootStatus
|
||||||
import jp.juggler.subwaytooter.column.*
|
import jp.juggler.subwaytooter.column.*
|
||||||
import jp.juggler.subwaytooter.dialog.ActionsDialog
|
|
||||||
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
||||||
|
import jp.juggler.subwaytooter.dialog.actionsDialog
|
||||||
import jp.juggler.subwaytooter.dialog.pickAccount
|
import jp.juggler.subwaytooter.dialog.pickAccount
|
||||||
import jp.juggler.subwaytooter.table.AcctColor
|
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
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.subwaytooter.util.emptyCallback
|
||||||
import jp.juggler.util.coroutine.launchAndShowError
|
import jp.juggler.util.coroutine.launchAndShowError
|
||||||
import jp.juggler.util.coroutine.launchMain
|
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) {
|
fun ActMain.clickScheduledToot(accessInfo: SavedAccount, item: TootScheduled, column: Column) {
|
||||||
ActionsDialog()
|
launchAndShowError {
|
||||||
.addAction(getString(R.string.edit)) {
|
actionsDialog {
|
||||||
scheduledPostEdit(accessInfo, item)
|
action(getString(R.string.edit)) {
|
||||||
}
|
scheduledPostEdit(accessInfo, item)
|
||||||
.addAction(getString(R.string.delete)) {
|
}
|
||||||
launchAndShowError {
|
action(getString(R.string.delete)) {
|
||||||
scheduledPostDelete(accessInfo, item)
|
launchAndShowError {
|
||||||
column.onScheduleDeleted(item)
|
scheduledPostDelete(accessInfo, item)
|
||||||
showToast(false, R.string.scheduled_post_deleted)
|
column.onScheduleDeleted(item)
|
||||||
|
showToast(false, R.string.scheduled_post_deleted)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.show(this)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ActMain.launchActText(intent: Intent) = arActText.launch(intent)
|
fun ActMain.launchActText(intent: Intent) = arActText.launch(intent)
|
||||||
|
@ -125,7 +129,7 @@ fun ActMain.favourite(
|
||||||
true -> R.string.confirm_favourite_from
|
true -> R.string.confirm_favourite_from
|
||||||
else -> R.string.confirm_unfavourite_from
|
else -> R.string.confirm_unfavourite_from
|
||||||
},
|
},
|
||||||
AcctColor.getNickname(accessInfo)
|
daoAcctColor.getNickname(accessInfo)
|
||||||
),
|
),
|
||||||
when (bSet) {
|
when (bSet) {
|
||||||
true -> accessInfo.confirm_favourite
|
true -> accessInfo.confirm_favourite
|
||||||
|
@ -136,7 +140,7 @@ fun ActMain.favourite(
|
||||||
true -> accessInfo.confirm_favourite = newConfirmEnabled
|
true -> accessInfo.confirm_favourite = newConfirmEnabled
|
||||||
else -> accessInfo.confirm_unfavourite = newConfirmEnabled
|
else -> accessInfo.confirm_unfavourite = newConfirmEnabled
|
||||||
}
|
}
|
||||||
accessInfo.saveSetting()
|
daoSavedAccount.saveSetting(accessInfo)
|
||||||
reloadAccountSetting(accessInfo)
|
reloadAccountSetting(accessInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -294,12 +298,12 @@ fun ActMain.bookmark(
|
||||||
confirm(
|
confirm(
|
||||||
getString(
|
getString(
|
||||||
R.string.confirm_unbookmark_from,
|
R.string.confirm_unbookmark_from,
|
||||||
AcctColor.getNickname(accessInfo)
|
daoAcctColor.getNickname(accessInfo)
|
||||||
),
|
),
|
||||||
accessInfo.confirm_unbookmark
|
accessInfo.confirm_unbookmark
|
||||||
) { newConfirmEnabled ->
|
) { newConfirmEnabled ->
|
||||||
accessInfo.confirm_unbookmark = newConfirmEnabled
|
accessInfo.confirm_unbookmark = newConfirmEnabled
|
||||||
accessInfo.saveSetting()
|
daoSavedAccount.saveSetting(accessInfo)
|
||||||
reloadAccountSetting(accessInfo)
|
reloadAccountSetting(accessInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,12 +11,15 @@ import jp.juggler.subwaytooter.api.entity.TootTag
|
||||||
import jp.juggler.subwaytooter.api.runApiTask
|
import jp.juggler.subwaytooter.api.runApiTask
|
||||||
import jp.juggler.subwaytooter.column.ColumnType
|
import jp.juggler.subwaytooter.column.ColumnType
|
||||||
import jp.juggler.subwaytooter.column.onTagFollowChanged
|
import jp.juggler.subwaytooter.column.onTagFollowChanged
|
||||||
import jp.juggler.subwaytooter.dialog.ActionsDialog
|
import jp.juggler.subwaytooter.dialog.actionsDialog
|
||||||
import jp.juggler.subwaytooter.table.AcctColor
|
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
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.matchHost
|
||||||
import jp.juggler.subwaytooter.util.openCustomTab
|
import jp.juggler.subwaytooter.util.openCustomTab
|
||||||
import jp.juggler.util.coroutine.AppDispatchers
|
import jp.juggler.util.coroutine.AppDispatchers
|
||||||
|
import jp.juggler.util.coroutine.launchAndShowError
|
||||||
import jp.juggler.util.coroutine.launchMain
|
import jp.juggler.util.coroutine.launchMain
|
||||||
import jp.juggler.util.data.encodePercent
|
import jp.juggler.util.data.encodePercent
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
|
@ -57,23 +60,21 @@ fun ActMain.tagDialog(
|
||||||
) {
|
) {
|
||||||
val activity = this
|
val activity = this
|
||||||
val tagWithSharp = "#$tagWithoutSharp"
|
val tagWithSharp = "#$tagWithoutSharp"
|
||||||
launchMain {
|
launchAndShowError {
|
||||||
try {
|
actionsDialog(tagWithSharp) {
|
||||||
|
action(getString(R.string.open_hashtag_column)) {
|
||||||
val d = ActionsDialog()
|
tagTimelineFromAccount(
|
||||||
.addAction(getString(R.string.open_hashtag_column)) {
|
pos,
|
||||||
tagTimelineFromAccount(
|
url,
|
||||||
pos,
|
host,
|
||||||
url,
|
tagWithoutSharp
|
||||||
host,
|
)
|
||||||
tagWithoutSharp
|
}
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 投稿者別タグTL
|
// 投稿者別タグTL
|
||||||
if (whoAcct != null) {
|
if (whoAcct != null) {
|
||||||
d.addAction(
|
action(
|
||||||
AcctColor.getStringWithNickname(
|
daoAcctColor.getStringWithNickname(
|
||||||
activity,
|
activity,
|
||||||
R.string.open_hashtag_from_account,
|
R.string.open_hashtag_from_account,
|
||||||
whoAcct
|
whoAcct
|
||||||
|
@ -89,13 +90,13 @@ fun ActMain.tagDialog(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
d.addAction(getString(R.string.open_in_browser)) { openCustomTab(url) }
|
action(getString(R.string.open_in_browser)) {
|
||||||
.addAction(
|
openCustomTab(url)
|
||||||
getString(
|
}
|
||||||
R.string.quote_hashtag_of,
|
|
||||||
tagWithSharp
|
action(getString(R.string.quote_hashtag_of, tagWithSharp)) {
|
||||||
)
|
openPost("$tagWithSharp ")
|
||||||
) { openPost("$tagWithSharp ") }
|
}
|
||||||
|
|
||||||
if (tagList != null && tagList.size > 1) {
|
if (tagList != null && tagList.size > 1) {
|
||||||
val sb = StringBuilder()
|
val sb = StringBuilder()
|
||||||
|
@ -104,11 +105,8 @@ fun ActMain.tagDialog(
|
||||||
sb.append(s)
|
sb.append(s)
|
||||||
}
|
}
|
||||||
val tagAll = sb.toString()
|
val tagAll = sb.toString()
|
||||||
d.addAction(
|
action(
|
||||||
getString(
|
getString(R.string.quote_all_hashtag_of, tagAll)
|
||||||
R.string.quote_all_hashtag_of,
|
|
||||||
tagAll
|
|
||||||
)
|
|
||||||
) { openPost("$tagAll ") }
|
) { openPost("$tagAll ") }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,10 +116,12 @@ fun ActMain.tagDialog(
|
||||||
if (tag == null) {
|
if (tag == null) {
|
||||||
val result = runApiTask(accessInfo) { client ->
|
val result = runApiTask(accessInfo) { client ->
|
||||||
client.request("/api/v1/tags/${tagWithoutSharp.encodePercent()}")
|
client.request("/api/v1/tags/${tagWithoutSharp.encodePercent()}")
|
||||||
} ?: return@launchMain //cancelled.
|
}
|
||||||
TootParser(activity, accessInfo)
|
if (result != null) {
|
||||||
.tag(result.jsonObject)
|
TootParser(activity, accessInfo)
|
||||||
?.let { tag = it }
|
.tag(result.jsonObject)
|
||||||
|
?.let { tag = it }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val toggle = !(tag?.following ?: false)
|
val toggle = !(tag?.following ?: false)
|
||||||
|
@ -129,14 +129,10 @@ fun ActMain.tagDialog(
|
||||||
true -> R.string.follow_hashtag_of
|
true -> R.string.follow_hashtag_of
|
||||||
else -> R.string.unfollow_hashtag_of
|
else -> R.string.unfollow_hashtag_of
|
||||||
}
|
}
|
||||||
d.addAction(getString(toggleCaption, tagWithSharp)) {
|
action(getString(toggleCaption, tagWithSharp)) {
|
||||||
followHashTag(accessInfo, tagWithoutSharp, toggle)
|
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を指定する
|
// 「投稿者別タグTL」を開くなら、投稿者のacctを指定する
|
||||||
acct: Acct? = null,
|
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
|
||||||
|
|
||||||
// 分類する
|
// ミスキーはアカウント別タグTLがないので
|
||||||
val listOriginal = ArrayList<SavedAccount>()
|
// アカウント別タグTLを開けない
|
||||||
val listOriginalPseudo = ArrayList<SavedAccount>()
|
a.isMisskey -> Unit
|
||||||
val listOther = ArrayList<SavedAccount>()
|
|
||||||
for (a in accountList) {
|
!a.matchHost(host) -> listOther.add(a)
|
||||||
if (acct == null) {
|
else -> listOriginal.add(a)
|
||||||
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
|
|
||||||
|
|
||||||
// ミスキーはアカウント別タグTLがないので
|
// ブラウザで表示する
|
||||||
// アカウント別タグTLを開けない
|
if (!url.isNullOrBlank()) {
|
||||||
a.isMisskey -> Unit
|
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(
|
fun ActMain.followHashTag(
|
||||||
|
|
|
@ -12,6 +12,9 @@ import jp.juggler.subwaytooter.api.syncStatus
|
||||||
import jp.juggler.subwaytooter.column.ColumnType
|
import jp.juggler.subwaytooter.column.ColumnType
|
||||||
import jp.juggler.subwaytooter.dialog.pickAccount
|
import jp.juggler.subwaytooter.dialog.pickAccount
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
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.subwaytooter.util.matchHost
|
||||||
import jp.juggler.util.coroutine.launchMain
|
import jp.juggler.util.coroutine.launchMain
|
||||||
import jp.juggler.util.log.showToast
|
import jp.juggler.util.log.showToast
|
||||||
|
@ -102,7 +105,7 @@ fun ActMain.timelineLocal(
|
||||||
launchMain {
|
launchMain {
|
||||||
// 指定タンスのアカウントを持ってるか?
|
// 指定タンスのアカウントを持ってるか?
|
||||||
val accountList = ArrayList<SavedAccount>()
|
val accountList = ArrayList<SavedAccount>()
|
||||||
for (a in SavedAccount.loadAccountList(applicationContext)) {
|
for (a in daoSavedAccount.loadAccountList()) {
|
||||||
if (a.matchHost(host)) accountList.add(a)
|
if (a.matchHost(host)) accountList.add(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,12 +116,11 @@ fun ActMain.timelineLocal(
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 持ってるならアカウントを選んで開く
|
// 持ってるならアカウントを選んで開く
|
||||||
SavedAccount.sort(accountList)
|
|
||||||
pickAccount(
|
pickAccount(
|
||||||
bAllowPseudo = true,
|
bAllowPseudo = true,
|
||||||
bAuto = false,
|
bAuto = false,
|
||||||
message = getString(R.string.account_picker_add_timeline_of, host),
|
message = getString(R.string.account_picker_add_timeline_of, host),
|
||||||
accountListArg = accountList
|
accountListArg = accountList.sortedByNickname()
|
||||||
)?.let { addColumn(pos, it, ColumnType.LOCAL) }
|
)?.let { addColumn(pos, it, ColumnType.LOCAL) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -168,7 +170,7 @@ fun ActMain.timelineAroundByStatusAnotherAccount(
|
||||||
// 利用可能なアカウントを列挙する
|
// 利用可能なアカウントを列挙する
|
||||||
val accountList1 = ArrayList<SavedAccount>() // 閲覧アカウントとホストが同じ
|
val accountList1 = ArrayList<SavedAccount>() // 閲覧アカウントとホストが同じ
|
||||||
val accountList2 = ArrayList<SavedAccount>() // その他実アカウント
|
val accountList2 = ArrayList<SavedAccount>() // その他実アカウント
|
||||||
label@ for (a in SavedAccount.loadAccountList(this)) {
|
label@ for (a in daoSavedAccount.loadAccountList()) {
|
||||||
// Misskeyアカウントはステータスの同期が出来ないので選択させない
|
// Misskeyアカウントはステータスの同期が出来ないので選択させない
|
||||||
if (a.isNA || a.isMisskey) continue
|
if (a.isNA || a.isMisskey) continue
|
||||||
when {
|
when {
|
||||||
|
@ -179,8 +181,8 @@ fun ActMain.timelineAroundByStatusAnotherAccount(
|
||||||
!a.isPseudo -> accountList2.add(a)
|
!a.isPseudo -> accountList2.add(a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SavedAccount.sort(accountList1)
|
accountList1.sortInplaceByNickname()
|
||||||
SavedAccount.sort(accountList2)
|
accountList2.sortInplaceByNickname()
|
||||||
accountList1.addAll(accountList2)
|
accountList1.addAll(accountList2)
|
||||||
|
|
||||||
if (accountList1.isEmpty()) {
|
if (accountList1.isEmpty()) {
|
||||||
|
|
|
@ -14,10 +14,7 @@ import jp.juggler.subwaytooter.column.*
|
||||||
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
import jp.juggler.subwaytooter.dialog.DlgConfirm.confirm
|
||||||
import jp.juggler.subwaytooter.dialog.ReportForm
|
import jp.juggler.subwaytooter.dialog.ReportForm
|
||||||
import jp.juggler.subwaytooter.dialog.pickAccount
|
import jp.juggler.subwaytooter.dialog.pickAccount
|
||||||
import jp.juggler.subwaytooter.table.AcctColor
|
import jp.juggler.subwaytooter.table.*
|
||||||
import jp.juggler.subwaytooter.table.FavMute
|
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
|
||||||
import jp.juggler.subwaytooter.table.UserRelation
|
|
||||||
import jp.juggler.subwaytooter.util.matchHost
|
import jp.juggler.subwaytooter.util.matchHost
|
||||||
import jp.juggler.subwaytooter.util.openCustomTab
|
import jp.juggler.subwaytooter.util.openCustomTab
|
||||||
import jp.juggler.util.*
|
import jp.juggler.util.*
|
||||||
|
@ -72,9 +69,9 @@ fun ActMain.openAvatarImage(who: TootAccount) {
|
||||||
fun ActMain.clickHideFavourite(
|
fun ActMain.clickHideFavourite(
|
||||||
accessInfo: SavedAccount,
|
accessInfo: SavedAccount,
|
||||||
who: TootAccount,
|
who: TootAccount,
|
||||||
) {
|
) = launchAndShowError {
|
||||||
val acct = accessInfo.getFullAcct(who)
|
val acct = accessInfo.getFullAcct(who)
|
||||||
FavMute.save(acct)
|
daoFavMute.save(acct)
|
||||||
showToast(false, R.string.changed)
|
showToast(false, R.string.changed)
|
||||||
for (column in appState.columnList) {
|
for (column in appState.columnList) {
|
||||||
column.onHideFavouriteNotification(acct)
|
column.onHideFavouriteNotification(acct)
|
||||||
|
@ -84,8 +81,8 @@ fun ActMain.clickHideFavourite(
|
||||||
fun ActMain.clickShowFavourite(
|
fun ActMain.clickShowFavourite(
|
||||||
accessInfo: SavedAccount,
|
accessInfo: SavedAccount,
|
||||||
who: TootAccount,
|
who: TootAccount,
|
||||||
) {
|
) = launchAndShowError {
|
||||||
FavMute.delete(accessInfo.getFullAcct(who))
|
daoFavMute.delete(accessInfo.getFullAcct(who))
|
||||||
showToast(false, R.string.changed)
|
showToast(false, R.string.changed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,133 +107,134 @@ private fun ActMain.userMute(
|
||||||
bMute: Boolean,
|
bMute: Boolean,
|
||||||
bMuteNotification: Boolean,
|
bMuteNotification: Boolean,
|
||||||
duration: Int?,
|
duration: Int?,
|
||||||
) {
|
) = launchAndShowError {
|
||||||
val whoAcct = whoAccessInfo.getFullAcct(whoArg)
|
val whoAcct = whoAccessInfo.getFullAcct(whoArg)
|
||||||
if (accessInfo.isMe(whoAcct)) {
|
if (accessInfo.isMe(whoAcct)) {
|
||||||
showToast(false, R.string.it_is_you)
|
showToast(false, R.string.it_is_you)
|
||||||
return
|
return@launchAndShowError
|
||||||
}
|
}
|
||||||
|
|
||||||
launchMain {
|
var resultRelation: UserRelation? = null
|
||||||
var resultRelation: UserRelation? = null
|
var resultWhoId: EntityId? = null
|
||||||
var resultWhoId: EntityId? = null
|
runApiTask(accessInfo) { client ->
|
||||||
runApiTask(accessInfo) { client ->
|
val parser = TootParser(this, accessInfo)
|
||||||
val parser = TootParser(this, accessInfo)
|
if (accessInfo.isPseudo) {
|
||||||
if (accessInfo.isPseudo) {
|
if (!whoAcct.isValidFull) {
|
||||||
if (!whoAcct.isValidFull) {
|
TootApiResult("can't mute pseudo acct ${whoAcct.pretty}")
|
||||||
TootApiResult("can't mute pseudo acct ${whoAcct.pretty}")
|
} else {
|
||||||
} else {
|
val relation = daoUserRelation.loadPseudo(whoAcct)
|
||||||
val relation = UserRelation.loadPseudo(whoAcct)
|
relation.muting = bMute
|
||||||
relation.muting = bMute
|
daoUserRelation.savePseudo(whoAcct.ascii, relation)
|
||||||
relation.savePseudo(whoAcct.ascii)
|
resultRelation = relation
|
||||||
resultRelation = relation
|
resultWhoId = whoArg.id
|
||||||
resultWhoId = whoArg.id
|
TootApiResult()
|
||||||
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 {
|
} else {
|
||||||
val whoId = if (accessInfo.matchHost(whoAccessInfo)) {
|
client.request(
|
||||||
whoArg.id
|
"/api/v1/accounts/$whoId/${if (bMute) "mute" else "unmute"}",
|
||||||
} else {
|
when {
|
||||||
val (result, accountRef) = client.syncAccountByAcct(accessInfo, whoAcct)
|
!bMute -> "".toFormRequestBody()
|
||||||
accountRef?.get()?.id ?: return@runApiTask result
|
else ->
|
||||||
}
|
buildJsonObject {
|
||||||
resultWhoId = whoId
|
put("notifications", bMuteNotification)
|
||||||
|
if (duration != null) put("duration", duration)
|
||||||
if (accessInfo.isMisskey) {
|
}.toRequestBody()
|
||||||
client.request(
|
}.toPost()
|
||||||
when (bMute) {
|
)?.apply {
|
||||||
true -> "/api/mute/create"
|
val jsonObject = jsonObject
|
||||||
else -> "/api/mute/delete"
|
if (jsonObject != null) {
|
||||||
},
|
resultRelation = daoUserRelation.saveUserRelation(
|
||||||
accessInfo.putMisskeyApiToken().apply {
|
accessInfo,
|
||||||
put("userId", whoId.toString())
|
parseItem(::TootRelationShip, parser, jsonObject)
|
||||||
}.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)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}?.let { result ->
|
}
|
||||||
val relation = resultRelation
|
}?.let { result ->
|
||||||
val whoId = resultWhoId
|
val relation = resultRelation
|
||||||
if (relation == null || whoId == null) {
|
val whoId = resultWhoId
|
||||||
showToast(false, result.error)
|
if (relation == null || whoId == null) {
|
||||||
} else {
|
showToast(false, result.error)
|
||||||
// 未確認だが、自分をミュートしようとするとリクエストは成功するがレスポンス中のmutingはfalseになるはず
|
} else {
|
||||||
if (bMute && !relation.muting) {
|
// 未確認だが、自分をミュートしようとするとリクエストは成功するがレスポンス中のmutingはfalseになるはず
|
||||||
showToast(false, R.string.not_muted)
|
if (bMute && !relation.muting) {
|
||||||
return@launchMain
|
showToast(false, R.string.not_muted)
|
||||||
}
|
return@launchAndShowError
|
||||||
|
}
|
||||||
|
|
||||||
for (column in appState.columnList) {
|
for (column in appState.columnList) {
|
||||||
if (column.accessInfo.isPseudo) {
|
if (column.accessInfo.isPseudo) {
|
||||||
if (relation.muting && column.type != ColumnType.PROFILE) {
|
if (relation.muting && column.type != ColumnType.PROFILE) {
|
||||||
// ミュートしたユーザの情報はTLから消える
|
// ミュートしたユーザの情報はTLから消える
|
||||||
column.removeAccountInTimelinePseudo(whoAcct)
|
column.removeAccountInTimelinePseudo(whoAcct)
|
||||||
}
|
}
|
||||||
// フォローアイコンの表示更新が走る
|
// フォローアイコンの表示更新が走る
|
||||||
column.updateFollowIcons(accessInfo)
|
column.updateFollowIcons(accessInfo)
|
||||||
} else if (column.accessInfo == accessInfo) {
|
} else if (column.accessInfo == accessInfo) {
|
||||||
when {
|
when {
|
||||||
!relation.muting -> {
|
!relation.muting -> {
|
||||||
if (column.type == ColumnType.MUTES) {
|
if (column.type == ColumnType.MUTES) {
|
||||||
// ミュート解除したら「ミュートしたユーザ」カラムから消える
|
// ミュート解除したら「ミュートしたユーザ」カラムから消える
|
||||||
column.removeUser(accessInfo, ColumnType.MUTES, whoId)
|
column.removeUser(accessInfo, ColumnType.MUTES, whoId)
|
||||||
} else {
|
} else {
|
||||||
// 他のカラムではフォローアイコンの表示更新が走る
|
// 他のカラムではフォローアイコンの表示更新が走る
|
||||||
column.updateFollowIcons(accessInfo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
column.type == ColumnType.PROFILE && column.profileId == whoId -> {
|
|
||||||
// 該当ユーザのプロフページのトゥートはミュートしてても見れる
|
|
||||||
// しかしフォローアイコンの表示更新は必要
|
|
||||||
column.updateFollowIcons(accessInfo)
|
column.updateFollowIcons(accessInfo)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
else -> {
|
column.type == ColumnType.PROFILE && column.profileId == whoId -> {
|
||||||
// ミュートしたユーザの情報はTLから消える
|
// 該当ユーザのプロフページのトゥートはミュートしてても見れる
|
||||||
column.removeAccountInTimeline(accessInfo, 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,
|
accessInfo: SavedAccount,
|
||||||
who: TootAccount,
|
who: TootAccount,
|
||||||
whoAccessInfo: SavedAccount,
|
whoAccessInfo: SavedAccount,
|
||||||
) {
|
) = launchAndShowError {
|
||||||
val activity = this@userMuteConfirm
|
val activity = this@userMuteConfirm
|
||||||
|
|
||||||
// Mastodon 3.3から時限ミュート設定ができる
|
// Mastodon 3.3から時限ミュート設定ができる
|
||||||
|
@ -288,59 +286,56 @@ fun ActMain.userMuteConfirm(
|
||||||
cbMuteNotification.vg(hasMuteNotification)
|
cbMuteNotification.vg(hasMuteNotification)
|
||||||
?.setText(R.string.confirm_mute_notification_for_user)
|
?.setText(R.string.confirm_mute_notification_for_user)
|
||||||
|
|
||||||
launchMain {
|
val spMuteDuration: Spinner = view.findViewById(R.id.spMuteDuration)
|
||||||
val spMuteDuration: Spinner = view.findViewById(R.id.spMuteDuration)
|
val hasMuteDuration = try {
|
||||||
val hasMuteDuration = try {
|
when {
|
||||||
when {
|
accessInfo.isMisskey || accessInfo.isPseudo -> false
|
||||||
accessInfo.isMisskey || accessInfo.isPseudo -> false
|
else -> {
|
||||||
else -> {
|
var resultBoolean = false
|
||||||
var resultBoolean = false
|
runApiTask(accessInfo) { client ->
|
||||||
runApiTask(accessInfo) { client ->
|
val (ti, ri) = TootInstance.get(client)
|
||||||
val (ti, ri) = TootInstance.get(client)
|
resultBoolean = ti?.versionGE(TootInstance.VERSION_3_3_0_rc1) == true
|
||||||
resultBoolean = ti?.versionGE(TootInstance.VERSION_3_3_0_rc1) == true
|
ri
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
resultBoolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (ignored: CancellationException) {
|
||||||
AlertDialog.Builder(activity)
|
// not show error
|
||||||
.setView(view)
|
return@launchAndShowError
|
||||||
.setNegativeButton(R.string.cancel, null)
|
} catch (ex: RuntimeException) {
|
||||||
.setPositiveButton(R.string.ok) { _, _ ->
|
showToast(true, ex.message)
|
||||||
userMute(
|
return@launchAndShowError
|
||||||
accessInfo,
|
|
||||||
who,
|
|
||||||
whoAccessInfo,
|
|
||||||
bMute = true,
|
|
||||||
bMuteNotification = cbMuteNotification.isChecked,
|
|
||||||
duration = spMuteDuration.selectedItemPosition
|
|
||||||
.takeIf { hasMuteDuration && it in choiceList.indices }
|
|
||||||
?.let { choiceList[it].first }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.show()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(
|
fun ActMain.userMuteFromAnotherAccount(
|
||||||
|
@ -381,9 +376,9 @@ fun ActMain.userBlock(
|
||||||
if (whoAcct.ascii.contains('?')) {
|
if (whoAcct.ascii.contains('?')) {
|
||||||
TootApiResult("can't block pseudo account ${whoAcct.pretty}")
|
TootApiResult("can't block pseudo account ${whoAcct.pretty}")
|
||||||
} else {
|
} else {
|
||||||
val relation = UserRelation.loadPseudo(whoAcct)
|
val relation = daoUserRelation.loadPseudo(whoAcct)
|
||||||
relation.blocking = bBlock
|
relation.blocking = bBlock
|
||||||
relation.savePseudo(whoAcct.ascii)
|
daoUserRelation.savePseudo(whoAcct.ascii, relation)
|
||||||
relationResult = relation
|
relationResult = relation
|
||||||
TootApiResult()
|
TootApiResult()
|
||||||
}
|
}
|
||||||
|
@ -397,11 +392,10 @@ fun ActMain.userBlock(
|
||||||
whoIdResult = whoId
|
whoIdResult = whoId
|
||||||
|
|
||||||
if (accessInfo.isMisskey) {
|
if (accessInfo.isMisskey) {
|
||||||
|
|
||||||
fun saveBlock(v: Boolean) {
|
fun saveBlock(v: Boolean) {
|
||||||
val ur = UserRelation.load(accessInfo.db_id, whoId)
|
val ur = daoUserRelation.load(accessInfo.db_id, whoId)
|
||||||
ur.blocking = v
|
ur.blocking = v
|
||||||
UserRelation.save1Misskey(
|
daoUserRelation.save1Misskey(
|
||||||
System.currentTimeMillis(),
|
System.currentTimeMillis(),
|
||||||
accessInfo.db_id,
|
accessInfo.db_id,
|
||||||
whoId.toString(),
|
whoId.toString(),
|
||||||
|
@ -434,7 +428,8 @@ fun ActMain.userBlock(
|
||||||
"".toFormRequestBody().toPost()
|
"".toFormRequestBody().toPost()
|
||||||
)?.also { result ->
|
)?.also { result ->
|
||||||
val parser = TootParser(this, accessInfo)
|
val parser = TootParser(this, accessInfo)
|
||||||
relationResult = accessInfo.saveUserRelation(
|
relationResult = daoUserRelation.saveUserRelation(
|
||||||
|
accessInfo,
|
||||||
parseItem(::TootRelationShip, parser, result.jsonObject)
|
parseItem(::TootRelationShip, parser, result.jsonObject)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -597,7 +592,7 @@ fun ActMain.userProfileFromAnotherAccount(
|
||||||
bAuto = false,
|
bAuto = false,
|
||||||
message = getString(
|
message = getString(
|
||||||
R.string.account_picker_open_user_who,
|
R.string.account_picker_open_user_who,
|
||||||
AcctColor.getNickname(accessInfo, who)
|
daoAcctColor.getNickname(accessInfo, who)
|
||||||
),
|
),
|
||||||
accountListArg = accountListNonPseudo(who.apDomain)
|
accountListArg = accountListNonPseudo(who.apDomain)
|
||||||
)?.let { ai ->
|
)?.let { ai ->
|
||||||
|
@ -630,7 +625,7 @@ fun ActMain.userProfile(
|
||||||
acct: Acct,
|
acct: Acct,
|
||||||
userUrl: String,
|
userUrl: String,
|
||||||
originalUrl: String = userUrl,
|
originalUrl: String = userUrl,
|
||||||
) {
|
) = launchAndShowError {
|
||||||
if (accessInfo?.isPseudo == false) {
|
if (accessInfo?.isPseudo == false) {
|
||||||
// 文脈のアカウントがあり、疑似アカウントではない
|
// 文脈のアカウントがあり、疑似アカウントではない
|
||||||
|
|
||||||
|
@ -655,51 +650,49 @@ fun ActMain.userProfile(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return@launchAndShowError
|
||||||
}
|
}
|
||||||
|
|
||||||
// 文脈がない、もしくは疑似アカウントだった
|
// 文脈がない、もしくは疑似アカウントだった
|
||||||
// 疑似アカウントでは検索APIを使えないため、IDが分からない
|
// 疑似アカウントでは検索APIを使えないため、IDが分からない
|
||||||
|
|
||||||
if (!SavedAccount.hasRealAccount()) {
|
if (!daoSavedAccount.hasRealAccount()) {
|
||||||
// 疑似アカウントしか登録されていない
|
// 疑似アカウントしか登録されていない
|
||||||
// chrome tab で開く
|
// chrome tab で開く
|
||||||
openCustomTab(originalUrl)
|
openCustomTab(originalUrl)
|
||||||
return
|
return@launchAndShowError
|
||||||
}
|
}
|
||||||
launchMain {
|
val activity = this@userProfile
|
||||||
val activity = this@userProfile
|
pickAccount(
|
||||||
pickAccount(
|
bAllowPseudo = false,
|
||||||
bAllowPseudo = false,
|
bAuto = false,
|
||||||
bAuto = false,
|
message = getString(
|
||||||
message = getString(
|
R.string.account_picker_open_user_who,
|
||||||
R.string.account_picker_open_user_who,
|
daoAcctColor.getNickname(acct)
|
||||||
AcctColor.getNickname(acct)
|
),
|
||||||
),
|
accountListArg = accountListNonPseudo(acct.host),
|
||||||
accountListArg = accountListNonPseudo(acct.host),
|
extraCallback = { ll, pad_se, pad_tb ->
|
||||||
extraCallback = { ll, pad_se, pad_tb ->
|
// chrome tab で開くアクションを追加
|
||||||
// chrome tab で開くアクションを追加
|
val lp = LinearLayout.LayoutParams(
|
||||||
val lp = LinearLayout.LayoutParams(
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
)
|
||||||
)
|
val b = AppCompatButton(activity)
|
||||||
val b = AppCompatButton(activity)
|
b.setPaddingRelative(pad_se, pad_tb, pad_se, pad_tb)
|
||||||
b.setPaddingRelative(pad_se, pad_tb, pad_se, pad_tb)
|
b.gravity = Gravity.START or Gravity.CENTER_VERTICAL
|
||||||
b.gravity = Gravity.START or Gravity.CENTER_VERTICAL
|
b.isAllCaps = false
|
||||||
b.isAllCaps = false
|
b.layoutParams = lp
|
||||||
b.layoutParams = lp
|
b.minHeight = (0.5f + 32f * activity.density).toInt()
|
||||||
b.minHeight = (0.5f + 32f * activity.density).toInt()
|
b.text = getString(R.string.open_in_browser)
|
||||||
b.text = getString(R.string.open_in_browser)
|
b.setBackgroundResource(R.drawable.btn_bg_transparent_round6dp)
|
||||||
b.setBackgroundResource(R.drawable.btn_bg_transparent_round6dp)
|
|
||||||
|
|
||||||
b.setOnClickListener {
|
b.setOnClickListener {
|
||||||
openCustomTab(originalUrl)
|
openCustomTab(originalUrl)
|
||||||
}
|
|
||||||
ll.addView(b, 0)
|
|
||||||
}
|
}
|
||||||
)?.let {
|
ll.addView(b, 0)
|
||||||
userProfileFromUrlOrAcct(pos, it, acct, userUrl)
|
|
||||||
}
|
}
|
||||||
|
)?.let {
|
||||||
|
userProfileFromUrlOrAcct(pos, it, acct, userUrl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -798,12 +791,9 @@ fun ActMain.userSetShowBoosts(
|
||||||
jsonObjectOf("reblogs" to bShow).toPostRequestBuilder()
|
jsonObjectOf("reblogs" to bShow).toPostRequestBuilder()
|
||||||
)?.also { result ->
|
)?.also { result ->
|
||||||
val parser = TootParser(this, accessInfo)
|
val parser = TootParser(this, accessInfo)
|
||||||
resultRelation = accessInfo.saveUserRelation(
|
resultRelation = daoUserRelation.saveUserRelation(
|
||||||
parseItem(
|
accessInfo,
|
||||||
::TootRelationShip,
|
parseItem(::TootRelationShip, parser, result.jsonObject)
|
||||||
parser,
|
|
||||||
result.jsonObject
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}?.let { result ->
|
}?.let { result ->
|
||||||
|
@ -860,7 +850,7 @@ fun ActMain.userSetStatusNotification(
|
||||||
result.jsonObject
|
result.jsonObject
|
||||||
)
|
)
|
||||||
if (relation != null) {
|
if (relation != null) {
|
||||||
UserRelation.save1Mastodon(
|
daoUserRelation.save1Mastodon(
|
||||||
System.currentTimeMillis(),
|
System.currentTimeMillis(),
|
||||||
accessInfo.db_id,
|
accessInfo.db_id,
|
||||||
relation
|
relation
|
||||||
|
@ -901,7 +891,8 @@ fun ActMain.userEndorsement(
|
||||||
)
|
)
|
||||||
?.also { result ->
|
?.also { result ->
|
||||||
val parser = TootParser(this, accessInfo)
|
val parser = TootParser(this, accessInfo)
|
||||||
resultRelation = accessInfo.saveUserRelation(
|
resultRelation = daoUserRelation.saveUserRelation(
|
||||||
|
accessInfo,
|
||||||
parseItem(::TootRelationShip, parser, result.jsonObject)
|
parseItem(::TootRelationShip, parser, result.jsonObject)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import jp.juggler.subwaytooter.ActMain
|
||||||
import jp.juggler.subwaytooter.column.fireShowColumnHeader
|
import jp.juggler.subwaytooter.column.fireShowColumnHeader
|
||||||
import jp.juggler.subwaytooter.pref.PrefL
|
import jp.juggler.subwaytooter.pref.PrefL
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
import jp.juggler.subwaytooter.table.SavedAccount
|
||||||
|
import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||||
|
|
||||||
// デフォルトの投稿先アカウントを探す。アカウント選択が必要な状況ならnull
|
// デフォルトの投稿先アカウントを探す。アカウント選択が必要な状況ならnull
|
||||||
val ActMain.currentPostTarget: SavedAccount?
|
val ActMain.currentPostTarget: SavedAccount?
|
||||||
|
@ -17,9 +18,9 @@ val ActMain.currentPostTarget: SavedAccount?
|
||||||
},
|
},
|
||||||
{ env ->
|
{ env ->
|
||||||
|
|
||||||
val dbId = PrefL.lpTabletTootDefaultAccount()
|
val dbId = PrefL.lpTabletTootDefaultAccount.value
|
||||||
if (dbId != -1L) {
|
if (dbId != -1L) {
|
||||||
val a = SavedAccount.loadAccount(this@currentPostTarget, dbId)
|
val a = daoSavedAccount.loadAccount(dbId)
|
||||||
if (a != null && !a.isPseudo) return a
|
if (a != null && !a.isPseudo) return a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,24 +48,23 @@ val ActMain.currentPostTarget: SavedAccount?
|
||||||
})
|
})
|
||||||
|
|
||||||
fun ActMain.reloadAccountSetting(
|
fun ActMain.reloadAccountSetting(
|
||||||
newAccounts: ArrayList<SavedAccount> = SavedAccount.loadAccountList(
|
newAccounts: List<SavedAccount>,
|
||||||
this
|
|
||||||
),
|
|
||||||
) {
|
) {
|
||||||
for (column in appState.columnList) {
|
for (column in appState.columnList) {
|
||||||
val a = column.accessInfo
|
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()
|
column.fireShowColumnHeader()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ActMain.reloadAccountSetting(account: SavedAccount) {
|
fun ActMain.reloadAccountSetting(account: SavedAccount) {
|
||||||
val newData = SavedAccount.loadAccount(this, account.db_id)
|
val newData = daoSavedAccount.loadAccount(account.db_id)
|
||||||
?: return
|
?: return
|
||||||
for (column in appState.columnList) {
|
for (column in appState.columnList) {
|
||||||
val a = column.accessInfo
|
val a = column.accessInfo
|
||||||
if (a.acct != newData.acct) continue
|
if (a.acct != newData.acct) continue
|
||||||
if (!a.isNA) a.reloadSetting(this, newData)
|
if (!a.isNA) daoSavedAccount.reloadSetting(a, newData)
|
||||||
column.fireShowColumnHeader()
|
column.fireShowColumnHeader()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import android.text.Spannable
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.core.view.GravityCompat
|
import androidx.core.view.GravityCompat
|
||||||
|
import androidx.work.WorkManager
|
||||||
import jp.juggler.subwaytooter.ActMain
|
import jp.juggler.subwaytooter.ActMain
|
||||||
import jp.juggler.subwaytooter.R
|
import jp.juggler.subwaytooter.R
|
||||||
import jp.juggler.subwaytooter.action.openColumnList
|
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.TabletColumnViewHolder
|
||||||
import jp.juggler.subwaytooter.columnviewholder.ViewHolderHeaderBase
|
import jp.juggler.subwaytooter.columnviewholder.ViewHolderHeaderBase
|
||||||
import jp.juggler.subwaytooter.columnviewholder.ViewHolderItem
|
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.itemviewholder.ItemViewHolder
|
||||||
import jp.juggler.subwaytooter.pref.*
|
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.span.MyClickableSpan
|
||||||
|
import jp.juggler.subwaytooter.util.checkPrivacyPolicy
|
||||||
import jp.juggler.subwaytooter.util.openCustomTab
|
import jp.juggler.subwaytooter.util.openCustomTab
|
||||||
|
import jp.juggler.util.coroutine.launchAndShowError
|
||||||
import jp.juggler.util.data.addTo
|
import jp.juggler.util.data.addTo
|
||||||
import jp.juggler.util.data.cast
|
import jp.juggler.util.data.cast
|
||||||
import jp.juggler.util.data.notEmpty
|
import jp.juggler.util.data.notEmpty
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
import jp.juggler.util.log.showToast
|
import jp.juggler.util.log.showToast
|
||||||
|
import jp.juggler.util.ui.dismissSafe
|
||||||
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
private val log = LogCategory("ActMainActions")
|
private val log = LogCategory("ActMainActions")
|
||||||
|
|
||||||
fun ActMain.onBackPressedImpl() {
|
fun ActMain.onBackPressedImpl() {
|
||||||
|
launchAndShowError {
|
||||||
|
|
||||||
// メニューが開いていたら閉じる
|
// メニューが開いていたら閉じる
|
||||||
if (drawer.isDrawerOpen(GravityCompat.START)) {
|
if (drawer.isDrawerOpen(GravityCompat.START)) {
|
||||||
drawer.closeDrawer(GravityCompat.START)
|
drawer.closeDrawer(GravityCompat.START)
|
||||||
return
|
return@launchAndShowError
|
||||||
}
|
|
||||||
|
|
||||||
// カラムが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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else /* PrefI.BACK_ASK_ALWAYS */ -> {
|
|
||||||
val closeableColumnList = getClosableColumnList()
|
// カラムが0個ならアプリを終了する
|
||||||
val dialog = ActionsDialog()
|
if (appState.columnCount == 0) {
|
||||||
if (closeableColumnList.size == 1) {
|
finish()
|
||||||
val column = closeableColumnList.first()
|
return@launchAndShowError
|
||||||
dialog.addAction(getString(R.string.close_column)) {
|
}
|
||||||
closeColumn(column, bConfirmed = true)
|
|
||||||
|
// カラム設定が開いているならカラム設定を閉じる
|
||||||
|
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() }
|
/* PrefI.BACK_ASK_ALWAYS */
|
||||||
dialog.addAction(getString(R.string.app_exit)) { finish() }
|
else -> actionsDialog {
|
||||||
dialog.show(this, null)
|
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 lpThemeDefaultChangedWarnTime = PrefL.lpThemeDefaultChangedWarnTime
|
||||||
val ipUiTheme = PrefI.ipUiTheme
|
val ipUiTheme = PrefI.ipUiTheme
|
||||||
val now = System.currentTimeMillis()
|
val now = System.currentTimeMillis()
|
||||||
|
|
||||||
// テーマが未定義でなければ警告しない
|
// テーマが未定義でなければ警告しない
|
||||||
if (pref.getInt(ipUiTheme.key, -1) != -1) {
|
if (lazyPref.getInt(ipUiTheme.key, -1) != -1) {
|
||||||
log.i("themeDefaultChangedDialog: theme was set.")
|
log.i("themeDefaultChangedDialog: theme was set.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 頻繁には警告しない
|
// 頻繁には警告しない
|
||||||
if (now - lpThemeDefaultChangedWarnTime.invoke(pref) < TimeUnit.DAYS.toMillis(60L)) {
|
if (now - lpThemeDefaultChangedWarnTime.value < TimeUnit.DAYS.toMillis(60L)) {
|
||||||
log.i("themeDefaultChangedDialog: avoid frequently check.")
|
log.i("themeDefaultChangedDialog: avoid frequently check.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pref.edit().put(lpThemeDefaultChangedWarnTime, now).apply()
|
lpThemeDefaultChangedWarnTime.value = now
|
||||||
|
|
||||||
// 色がすべてデフォルトなら警告不要
|
// 色がすべてデフォルトなら警告不要
|
||||||
val customizedKeys = ArrayList<String>()
|
val customizedKeys = ArrayList<String>()
|
||||||
|
@ -202,18 +210,50 @@ fun ActMain.themeDefaultChangedDialog() {
|
||||||
item.pref?.let { p ->
|
item.pref?.let { p ->
|
||||||
when {
|
when {
|
||||||
p == PrefS.spBoostAlpha -> Unit
|
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()) {
|
if (customizedKeys.isEmpty()) {
|
||||||
pref.edit().put(ipUiTheme, ipUiTheme.defVal).apply()
|
ipUiTheme.value = ipUiTheme.defVal
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
log.w("themeDefaultChangedDialog: customizedKeys=${customizedKeys.joinToString(",")}")
|
||||||
AlertDialog.Builder(this)
|
suspendCancellableCoroutine { cont ->
|
||||||
.setMessage(R.string.color_theme_changed)
|
val dialog = AlertDialog.Builder(this)
|
||||||
.setPositiveButton(android.R.string.ok, null)
|
.setMessage(R.string.color_theme_changed)
|
||||||
.show()
|
.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
|
this.postedRedraftId = null
|
||||||
}
|
}
|
||||||
|
|
||||||
val refreshAfterToot = PrefI.ipRefreshAfterToot(pref)
|
val refreshAfterToot = PrefI.ipRefreshAfterToot.value
|
||||||
if (refreshAfterToot != PrefI.RAT_DONT_REFRESH) {
|
if (refreshAfterToot != PrefI.RAT_DONT_REFRESH) {
|
||||||
appState.columnList
|
appState.columnList
|
||||||
.filter { it.accessInfo.acct == postedAcct }
|
.filter { it.accessInfo.acct == postedAcct }
|
||||||
|
|
|
@ -12,7 +12,7 @@ import java.lang.ref.WeakReference
|
||||||
|
|
||||||
// AutoCWの基準幅を計算する
|
// AutoCWの基準幅を計算する
|
||||||
fun ActMain.resizeAutoCW(columnW: Int) {
|
fun ActMain.resizeAutoCW(columnW: Int) {
|
||||||
val sv = PrefS.spAutoCWLines(pref)
|
val sv = PrefS.spAutoCWLines.value
|
||||||
nAutoCwLines = sv.toIntOrNull() ?: -1
|
nAutoCwLines = sv.toIntOrNull() ?: -1
|
||||||
if (nAutoCwLines > 0) {
|
if (nAutoCwLines > 0) {
|
||||||
val lvPad = (0.5f + 12 * density).toInt()
|
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.PrefB
|
||||||
import jp.juggler.subwaytooter.pref.PrefI
|
import jp.juggler.subwaytooter.pref.PrefI
|
||||||
import jp.juggler.subwaytooter.pref.PrefS
|
import jp.juggler.subwaytooter.pref.PrefS
|
||||||
import jp.juggler.subwaytooter.table.AcctColor
|
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
import jp.juggler.subwaytooter.table.SavedAccount
|
||||||
|
import jp.juggler.subwaytooter.table.daoAcctColor
|
||||||
import jp.juggler.util.*
|
import jp.juggler.util.*
|
||||||
import jp.juggler.util.data.clip
|
import jp.juggler.util.data.clip
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
|
@ -103,7 +103,7 @@ fun ActMain.addColumn(
|
||||||
vararg params: Any,
|
vararg params: Any,
|
||||||
): Column {
|
): Column {
|
||||||
return addColumn(
|
return addColumn(
|
||||||
PrefB.bpAllowColumnDuplication(pref),
|
PrefB.bpAllowColumnDuplication.value,
|
||||||
indexArg,
|
indexArg,
|
||||||
ai,
|
ai,
|
||||||
type,
|
type,
|
||||||
|
@ -175,7 +175,7 @@ fun ActMain.updateColumnStrip() {
|
||||||
viewRoot.tag = index
|
viewRoot.tag = index
|
||||||
viewRoot.setOnClickListener { v ->
|
viewRoot.setOnClickListener { v ->
|
||||||
val idx = v.tag as Int
|
val idx = v.tag as Int
|
||||||
if (PrefB.bpScrollTopFromColumnStrip(pref) && isVisibleColumn(idx)) {
|
if (PrefB.bpScrollTopFromColumnStrip.value && isVisibleColumn(idx)) {
|
||||||
column.viewHolder?.scrollToTop2()
|
column.viewHolder?.scrollToTop2()
|
||||||
return@setOnClickListener
|
return@setOnClickListener
|
||||||
}
|
}
|
||||||
|
@ -193,9 +193,9 @@ fun ActMain.updateColumnStrip() {
|
||||||
ivIcon.imageTintList = ColorStateList.valueOf(column.getHeaderNameColor())
|
ivIcon.imageTintList = ColorStateList.valueOf(column.getHeaderNameColor())
|
||||||
|
|
||||||
//
|
//
|
||||||
val ac = AcctColor.load(column.accessInfo)
|
val ac = daoAcctColor.load(column.accessInfo)
|
||||||
if (AcctColor.hasColorForeground(ac)) {
|
if (daoAcctColor.hasColorForeground(ac)) {
|
||||||
vAcctColor.setBackgroundColor(ac.color_fg)
|
vAcctColor.setBackgroundColor(ac.colorFg)
|
||||||
} else {
|
} else {
|
||||||
vAcctColor.visibility = View.INVISIBLE
|
vAcctColor.visibility = View.INVISIBLE
|
||||||
}
|
}
|
||||||
|
@ -214,7 +214,7 @@ fun ActMain.closeColumn(column: Column, bConfirmed: Boolean = false) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bConfirmed && !PrefB.bpDontConfirmBeforeCloseColumn(pref)) {
|
if (!bConfirmed && !PrefB.bpDontConfirmBeforeCloseColumn.value) {
|
||||||
AlertDialog.Builder(this)
|
AlertDialog.Builder(this)
|
||||||
.setMessage(R.string.confirm_close_column)
|
.setMessage(R.string.confirm_close_column)
|
||||||
.setNegativeButton(R.string.cancel, null)
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
@ -366,7 +366,7 @@ fun ActMain.scrollToColumn(index: Int, smoothScroll: Boolean = true) {
|
||||||
fun ActMain.scrollToLastColumn() {
|
fun ActMain.scrollToLastColumn() {
|
||||||
if (appState.columnCount <= 0) return
|
if (appState.columnCount <= 0) return
|
||||||
|
|
||||||
val columnPos = PrefI.ipLastColumnPos(pref)
|
val columnPos = PrefI.ipLastColumnPos.value
|
||||||
log.d("ipLastColumnPos load $columnPos")
|
log.d("ipLastColumnPos load $columnPos")
|
||||||
|
|
||||||
// 前回最後に表示していたカラムの位置にスクロールする
|
// 前回最後に表示していたカラムの位置にスクロールする
|
||||||
|
@ -385,7 +385,7 @@ fun ActMain.scrollToLastColumn() {
|
||||||
fun ActMain.resizeColumnWidth(views: ActMainTabletViews) {
|
fun ActMain.resizeColumnWidth(views: ActMainTabletViews) {
|
||||||
|
|
||||||
var columnWMinDp = ActMain.COLUMN_WIDTH_MIN_DP
|
var columnWMinDp = ActMain.COLUMN_WIDTH_MIN_DP
|
||||||
val sv = PrefS.spColumnWidth(pref)
|
val sv = PrefS.spColumnWidth.value
|
||||||
if (sv.isNotEmpty()) {
|
if (sv.isNotEmpty()) {
|
||||||
try {
|
try {
|
||||||
val iv = Integer.parseInt(sv)
|
val iv = Integer.parseInt(sv)
|
||||||
|
|
|
@ -4,6 +4,7 @@ import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import jp.juggler.subwaytooter.ActMain
|
import jp.juggler.subwaytooter.ActMain
|
||||||
import jp.juggler.subwaytooter.R
|
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.entity.TootVisibility
|
||||||
import jp.juggler.subwaytooter.api.runApiTask2
|
import jp.juggler.subwaytooter.api.runApiTask2
|
||||||
import jp.juggler.subwaytooter.api.showApiError
|
import jp.juggler.subwaytooter.api.showApiError
|
||||||
|
import jp.juggler.subwaytooter.auth.authRepo
|
||||||
import jp.juggler.subwaytooter.column.ColumnType
|
import jp.juggler.subwaytooter.column.ColumnType
|
||||||
import jp.juggler.subwaytooter.column.startLoading
|
import jp.juggler.subwaytooter.column.startLoading
|
||||||
|
import jp.juggler.subwaytooter.dialog.actionsDialog
|
||||||
import jp.juggler.subwaytooter.dialog.pickAccount
|
import jp.juggler.subwaytooter.dialog.pickAccount
|
||||||
|
import jp.juggler.subwaytooter.dialog.runInProgress
|
||||||
import jp.juggler.subwaytooter.notification.PushSubscriptionHelper
|
import jp.juggler.subwaytooter.notification.PushSubscriptionHelper
|
||||||
import jp.juggler.subwaytooter.notification.checkNotificationImmediate
|
import jp.juggler.subwaytooter.notification.checkNotificationImmediate
|
||||||
import jp.juggler.subwaytooter.notification.checkNotificationImmediateAll
|
import jp.juggler.subwaytooter.notification.checkNotificationImmediateAll
|
||||||
import jp.juggler.subwaytooter.notification.recycleClickedNotification
|
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.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.coroutine.launchMain
|
||||||
import jp.juggler.util.data.decodePercent
|
import jp.juggler.util.data.decodePercent
|
||||||
import jp.juggler.util.data.groupEx
|
import jp.juggler.util.data.groupEx
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
import jp.juggler.util.log.showToast
|
import jp.juggler.util.log.showToast
|
||||||
import jp.juggler.util.queryIntentActivitiesCompat
|
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")
|
private val log = LogCategory("ActMainIntent")
|
||||||
|
|
||||||
|
@ -154,21 +170,22 @@ fun ActMain.handleOtherUri(uri: Uri): Boolean {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ActMain.handleCustomSchemaUri(uri: Uri) {
|
private fun ActMain.handleCustomSchemaUri(uri: Uri) = launchAndShowError {
|
||||||
val dataIdString = uri.getQueryParameter("db_id")
|
val dataIdString = uri.getQueryParameter("db_id")
|
||||||
if (dataIdString != null) {
|
if (dataIdString == null) {
|
||||||
// subwaytooter://notification_click/?db_id=(db_id)
|
|
||||||
handleNotificationClick(uri, dataIdString)
|
|
||||||
} else {
|
|
||||||
// OAuth2 認証コールバック
|
// OAuth2 認証コールバック
|
||||||
// subwaytooter://oauth(\d*)/?...
|
// subwaytooter://oauth(\d*)/?...
|
||||||
handleOAuth2Callback(uri)
|
handleOAuth2Callback(uri)
|
||||||
|
} else {
|
||||||
|
// subwaytooter://notification_click/?db_id=(db_id)
|
||||||
|
handleNotificationClick(uri, dataIdString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ActMain.handleNotificationClick(uri: Uri, dataIdString: String) {
|
private fun ActMain.handleNotificationClick(uri: Uri, dataIdString: String) {
|
||||||
try {
|
try {
|
||||||
val account = dataIdString.toLongOrNull()?.let { SavedAccount.loadAccount(this, it) }
|
val account = dataIdString.toLongOrNull()
|
||||||
|
?.let { daoSavedAccount.loadAccount(it) }
|
||||||
if (account == null) {
|
if (account == null) {
|
||||||
showToast(true, "handleNotificationClick: missing SavedAccount. id=$dataIdString")
|
showToast(true, "handleNotificationClick: missing SavedAccount. id=$dataIdString")
|
||||||
return
|
return
|
||||||
|
@ -227,7 +244,7 @@ fun ActMain.afterAccountVerify(auth2Result: Auth2Result): Boolean = auth2Result.
|
||||||
// 「アカウント追加のハズが既存アカウントで認証していた」
|
// 「アカウント追加のハズが既存アカウントで認証していた」
|
||||||
// 「アクセストークン更新のハズが別アカウントで認証していた」
|
// 「アクセストークン更新のハズが別アカウントで認証していた」
|
||||||
// などを防止するため、full acctでアプリ内DBを検索
|
// などを防止するため、full acctでアプリ内DBを検索
|
||||||
when (val sa = SavedAccount.loadAccountByAcct(this@afterAccountVerify, newAcct.ascii)) {
|
when (val sa = daoSavedAccount.loadAccountByAcct(newAcct)) {
|
||||||
null -> afterAccountAdd(newAcct, auth2Result)
|
null -> afterAccountAdd(newAcct, auth2Result)
|
||||||
else -> afterAccessTokenUpdate(auth2Result, sa)
|
else -> afterAccessTokenUpdate(auth2Result, sa)
|
||||||
}
|
}
|
||||||
|
@ -238,10 +255,10 @@ private fun ActMain.afterAccessTokenUpdate(
|
||||||
sa: SavedAccount,
|
sa: SavedAccount,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
// DBの情報を更新する
|
// DBの情報を更新する
|
||||||
sa.updateTokenInfo(auth2Result)
|
authRepo.updateTokenInfo(sa, auth2Result)
|
||||||
|
|
||||||
// 各カラムの持つアカウント情報をリロードする
|
// 各カラムの持つアカウント情報をリロードする
|
||||||
reloadAccountSetting()
|
reloadAccountSetting(daoSavedAccount.loadAccountList())
|
||||||
|
|
||||||
// 自動でリロードする
|
// 自動でリロードする
|
||||||
appState.columnList
|
appState.columnList
|
||||||
|
@ -252,6 +269,7 @@ private fun ActMain.afterAccessTokenUpdate(
|
||||||
PushSubscriptionHelper.clearLastCheck(sa)
|
PushSubscriptionHelper.clearLastCheck(sa)
|
||||||
checkNotificationImmediateAll(this, onlySubscription = true)
|
checkNotificationImmediateAll(this, onlySubscription = true)
|
||||||
checkNotificationImmediate(this, sa.db_id)
|
checkNotificationImmediate(this, sa.db_id)
|
||||||
|
updatePushDistributer()
|
||||||
|
|
||||||
showToast(false, R.string.access_token_updated_for, sa.acct.pretty)
|
showToast(false, R.string.access_token_updated_for, sa.acct.pretty)
|
||||||
return true
|
return true
|
||||||
|
@ -263,7 +281,7 @@ private fun ActMain.afterAccountAdd(
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val ta = auth2Result.tootAccount
|
val ta = auth2Result.tootAccount
|
||||||
|
|
||||||
val rowId = SavedAccount.insert(
|
val rowId = daoSavedAccount.saveNew(
|
||||||
acct = newAcct.ascii,
|
acct = newAcct.ascii,
|
||||||
host = auth2Result.apiHost.ascii,
|
host = auth2Result.apiHost.ascii,
|
||||||
domain = auth2Result.apDomain.ascii,
|
domain = auth2Result.apDomain.ascii,
|
||||||
|
@ -271,7 +289,7 @@ private fun ActMain.afterAccountAdd(
|
||||||
token = auth2Result.tokenJson,
|
token = auth2Result.tokenJson,
|
||||||
misskeyVersion = auth2Result.tootInstance.misskeyVersionMajor,
|
misskeyVersion = auth2Result.tootInstance.misskeyVersionMajor,
|
||||||
)
|
)
|
||||||
val account = SavedAccount.loadAccount(applicationContext, rowId)
|
val account = daoSavedAccount.loadAccount(rowId)
|
||||||
if (account == null) {
|
if (account == null) {
|
||||||
showToast(false, "loadAccount failed.")
|
showToast(false, "loadAccount failed.")
|
||||||
return false
|
return false
|
||||||
|
@ -298,13 +316,13 @@ private fun ActMain.afterAccountAdd(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bModified) {
|
if (bModified) {
|
||||||
account.saveSetting()
|
daoSavedAccount.saveSetting(account)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 適当にカラムを追加する
|
// 適当にカラムを追加する
|
||||||
addColumn(false, defaultInsertPosition, account, ColumnType.HOME)
|
addColumn(false, defaultInsertPosition, account, ColumnType.HOME)
|
||||||
if (SavedAccount.count == 1) {
|
if (daoSavedAccount.isSingleAccount()) {
|
||||||
addColumn(false, defaultInsertPosition, account, ColumnType.NOTIFICATIONS)
|
addColumn(false, defaultInsertPosition, account, ColumnType.NOTIFICATIONS)
|
||||||
addColumn(false, defaultInsertPosition, account, ColumnType.LOCAL)
|
addColumn(false, defaultInsertPosition, account, ColumnType.LOCAL)
|
||||||
addColumn(false, defaultInsertPosition, account, ColumnType.FEDERATE)
|
addColumn(false, defaultInsertPosition, account, ColumnType.FEDERATE)
|
||||||
|
@ -313,6 +331,7 @@ private fun ActMain.afterAccountAdd(
|
||||||
// 通知の更新が必要かもしれない
|
// 通知の更新が必要かもしれない
|
||||||
checkNotificationImmediateAll(this, onlySubscription = true)
|
checkNotificationImmediateAll(this, onlySubscription = true)
|
||||||
checkNotificationImmediate(this, account.db_id)
|
checkNotificationImmediate(this, account.db_id)
|
||||||
|
updatePushDistributer()
|
||||||
showToast(false, R.string.account_confirmed)
|
showToast(false, R.string.account_confirmed)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -329,3 +348,79 @@ fun ActMain.handleSharedIntent(intent: Intent) {
|
||||||
ai?.let { openActPostImpl(it.db_id, sharedIntent = 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() {
|
fun ActMain.initUIQuickPost() {
|
||||||
etQuickPost.typeface = ActMain.timelineFont
|
etQuickPost.typeface = ActMain.timelineFont
|
||||||
|
|
||||||
if (!PrefB.bpQuickPostBar.invoke(pref)) {
|
if (!PrefB.bpQuickPostBar.value) {
|
||||||
llQuickPostBar.visibility = View.GONE
|
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.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE
|
||||||
etQuickPost.imeOptions = EditorInfo.IME_ACTION_NONE
|
etQuickPost.imeOptions = EditorInfo.IME_ACTION_NONE
|
||||||
// 最後に指定する必要がある?
|
// 最後に指定する必要がある?
|
||||||
|
@ -70,7 +70,7 @@ fun ActMain.initUIQuickPost() {
|
||||||
|
|
||||||
fun ActMain.showQuickPostVisibility() {
|
fun ActMain.showQuickPostVisibility() {
|
||||||
btnQuickPostMenu.imageResource =
|
btnQuickPostMenu.imageResource =
|
||||||
when (val resId = getVisibilityIconId(false, quickPostVisibility)) {
|
when (val resId = quickPostVisibility.getVisibilityIconId(false)) {
|
||||||
R.drawable.ic_question -> R.drawable.ic_description
|
R.drawable.ic_question -> R.drawable.ic_description
|
||||||
else -> resId
|
else -> resId
|
||||||
}
|
}
|
||||||
|
@ -82,7 +82,7 @@ fun ActMain.toggleQuickPostMenu() {
|
||||||
|
|
||||||
fun ActMain.performQuickPost(account: SavedAccount?) {
|
fun ActMain.performQuickPost(account: SavedAccount?) {
|
||||||
if (account == null) {
|
if (account == null) {
|
||||||
val a = if (tabletViews != null && !PrefB.bpQuickTootOmitAccountSelection(pref)) {
|
val a = if (tabletViews != null && !PrefB.bpQuickTootOmitAccountSelection.value) {
|
||||||
// タブレットモードでオプションが無効なら
|
// タブレットモードでオプションが無効なら
|
||||||
// 簡易投稿は常にアカウント選択する
|
// 簡易投稿は常にアカウント選択する
|
||||||
null
|
null
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package jp.juggler.subwaytooter.actmain
|
package jp.juggler.subwaytooter.actmain
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
@ -8,15 +9,15 @@ import android.widget.LinearLayout
|
||||||
import jp.juggler.subwaytooter.ActMain
|
import jp.juggler.subwaytooter.ActMain
|
||||||
import jp.juggler.subwaytooter.R
|
import jp.juggler.subwaytooter.R
|
||||||
import jp.juggler.subwaytooter.api.entity.TootStatus
|
import jp.juggler.subwaytooter.api.entity.TootStatus
|
||||||
import jp.juggler.subwaytooter.stylerBoostAlpha
|
|
||||||
import jp.juggler.subwaytooter.itemviewholder.ItemViewHolder
|
import jp.juggler.subwaytooter.itemviewholder.ItemViewHolder
|
||||||
import jp.juggler.subwaytooter.pref.PrefB
|
import jp.juggler.subwaytooter.pref.PrefB
|
||||||
import jp.juggler.subwaytooter.pref.PrefF
|
import jp.juggler.subwaytooter.pref.PrefF
|
||||||
import jp.juggler.subwaytooter.pref.PrefI
|
import jp.juggler.subwaytooter.pref.PrefI
|
||||||
import jp.juggler.subwaytooter.pref.PrefS
|
import jp.juggler.subwaytooter.pref.PrefS
|
||||||
import jp.juggler.subwaytooter.pref.impl.StringPref
|
import jp.juggler.subwaytooter.pref.impl.StringPref
|
||||||
import jp.juggler.subwaytooter.stylerRoundRatio
|
|
||||||
import jp.juggler.subwaytooter.span.MyClickableSpan
|
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.util.CustomShare
|
||||||
import jp.juggler.subwaytooter.view.ListDivider
|
import jp.juggler.subwaytooter.view.ListDivider
|
||||||
import jp.juggler.util.data.clip
|
import jp.juggler.util.data.clip
|
||||||
|
@ -31,12 +32,12 @@ import kotlin.math.max
|
||||||
|
|
||||||
private val log = LogCategory("ActMainStyle")
|
private val log = LogCategory("ActMainStyle")
|
||||||
|
|
||||||
private fun ActMain.dpToPx(dp: Float) =
|
private fun Float.dpToPx(context: Context) =
|
||||||
(dp * density + 0.5f).toInt()
|
(this * context.resources.displayMetrics.density + 0.5f).toInt()
|
||||||
|
|
||||||
// initUIから呼ばれる
|
// initUIから呼ばれる
|
||||||
fun ActMain.reloadFonts() {
|
fun reloadFonts() {
|
||||||
ActMain.timelineFont = PrefS.spTimelineFont(pref).notEmpty()?.let {
|
ActMain.timelineFont = PrefS.spTimelineFont.value.notEmpty()?.let {
|
||||||
try {
|
try {
|
||||||
Typeface.createFromFile(it)
|
Typeface.createFromFile(it)
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
|
@ -45,7 +46,7 @@ fun ActMain.reloadFonts() {
|
||||||
}
|
}
|
||||||
} ?: Typeface.DEFAULT
|
} ?: Typeface.DEFAULT
|
||||||
|
|
||||||
ActMain.timelineFontBold = PrefS.spTimelineFontBold(pref).notEmpty()?.let {
|
ActMain.timelineFontBold = PrefS.spTimelineFontBold.value.notEmpty()?.let {
|
||||||
try {
|
try {
|
||||||
Typeface.createFromFile(it)
|
Typeface.createFromFile(it)
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
|
@ -61,16 +62,14 @@ fun ActMain.reloadFonts() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ActMain.parseIconSize(stringPref: StringPref, minDp: Float = 1f) =
|
private fun ActMain.parseIconSize(stringPref: StringPref, minDp: Float = 1f) =
|
||||||
dpToPx(
|
(try {
|
||||||
try {
|
stringPref.value
|
||||||
stringPref(pref)
|
.toFloatOrNull()
|
||||||
.toFloatOrNull()
|
?.takeIf { it.isFinite() && it >= minDp }
|
||||||
?.takeIf { it.isFinite() && it >= minDp }
|
} catch (ex: Throwable) {
|
||||||
} catch (ex: Throwable) {
|
log.e(ex, "parseIconSize failed.")
|
||||||
log.e(ex, "parseIconSize failed.")
|
null
|
||||||
null
|
} ?: stringPref.defVal.toFloat()).dpToPx(this)
|
||||||
} ?: stringPref.defVal.toFloat()
|
|
||||||
)
|
|
||||||
|
|
||||||
// initUIから呼ばれる
|
// initUIから呼ばれる
|
||||||
fun ActMain.reloadIconSize() {
|
fun ActMain.reloadIconSize() {
|
||||||
|
@ -82,7 +81,7 @@ fun ActMain.reloadIconSize() {
|
||||||
ActMain.stripIconSize = parseIconSize(PrefS.spStripIconSize)
|
ActMain.stripIconSize = parseIconSize(PrefS.spStripIconSize)
|
||||||
ActMain.screenBottomPadding = parseIconSize(PrefS.spScreenBottomPadding, minDp = 0f)
|
ActMain.screenBottomPadding = parseIconSize(PrefS.spScreenBottomPadding, minDp = 0f)
|
||||||
|
|
||||||
ActMain.eventFadeAlpha = PrefS.spEventTextAlpha()
|
ActMain.eventFadeAlpha = PrefS.spEventTextAlpha.value
|
||||||
.toFloatOrNull()
|
.toFloatOrNull()
|
||||||
?.takeIf { it.isFinite() }
|
?.takeIf { it.isFinite() }
|
||||||
?.clip(0f, 1f)
|
?.clip(0f, 1f)
|
||||||
|
@ -90,10 +89,10 @@ fun ActMain.reloadIconSize() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// initUIから呼ばれる
|
// initUIから呼ばれる
|
||||||
fun ActMain.reloadRoundRatio() {
|
fun reloadRoundRatio() {
|
||||||
val sizeDp = when {
|
val sizeDp = when {
|
||||||
PrefB.bpDontRound(pref) -> 0f
|
PrefB.bpDontRound.value -> 0f
|
||||||
else -> PrefS.spRoundRatio(pref)
|
else -> PrefS.spRoundRatio.value
|
||||||
.toFloatOrNull()
|
.toFloatOrNull()
|
||||||
?.takeIf { it.isFinite() }
|
?.takeIf { it.isFinite() }
|
||||||
?: 33f
|
?: 33f
|
||||||
|
@ -102,8 +101,8 @@ fun ActMain.reloadRoundRatio() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// initUI から呼ばれる
|
// initUI から呼ばれる
|
||||||
fun ActMain.reloadBoostAlpha() {
|
fun reloadBoostAlpha() {
|
||||||
stylerBoostAlpha = PrefS.spBoostAlpha(pref)
|
stylerBoostAlpha = PrefS.spBoostAlpha.value
|
||||||
.toIntOrNull()
|
.toIntOrNull()
|
||||||
?.toFloat()
|
?.toFloat()
|
||||||
?.let { (it + 0.5f) / 100f }
|
?.let { (it + 0.5f) / 100f }
|
||||||
|
@ -112,36 +111,35 @@ fun ActMain.reloadBoostAlpha() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ActMain.reloadMediaHeight() {
|
fun ActMain.reloadMediaHeight() {
|
||||||
appState.mediaThumbHeight = dpToPx(
|
appState.mediaThumbHeight = (
|
||||||
PrefS.spMediaThumbHeight(pref)
|
PrefS.spMediaThumbHeight.value
|
||||||
.toFloatOrNull()
|
.toFloatOrNull()
|
||||||
?.takeIf { it >= 32f }
|
?.takeIf { it >= 32f }
|
||||||
?: 64f
|
?: 64f
|
||||||
)
|
).dpToPx(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Float.clipFontSize(): Float =
|
private fun Float.clipFontSize(): Float =
|
||||||
if (isNaN()) this else max(1f, this)
|
if (isNaN()) this else max(1f, this)
|
||||||
|
|
||||||
fun ActMain.reloadTextSize() {
|
fun ActMain.reloadTextSize() {
|
||||||
timelineFontSizeSp = PrefF.fpTimelineFontSize.invoke(pref).clipFontSize()
|
timelineFontSizeSp = PrefF.fpTimelineFontSize.value.clipFontSize()
|
||||||
acctFontSizeSp = PrefF.fpAcctFontSize(pref).clipFontSize()
|
acctFontSizeSp = PrefF.fpAcctFontSize.value.clipFontSize()
|
||||||
notificationTlFontSizeSp = PrefF.fpNotificationTlFontSize(pref).clipFontSize()
|
notificationTlFontSizeSp = PrefF.fpNotificationTlFontSize.value.clipFontSize()
|
||||||
headerTextSizeSp = PrefF.fpHeaderTextSize(pref).clipFontSize()
|
headerTextSizeSp = PrefF.fpHeaderTextSize.value.clipFontSize()
|
||||||
val fv = PrefS.spTimelineSpacing(pref).toFloatOrNull()
|
val fv = PrefS.spTimelineSpacing.value.toFloatOrNull()
|
||||||
timelineSpacing = if (fv != null && fv.isFinite() && fv != 0f) fv else null
|
timelineSpacing = if (fv != null && fv.isFinite() && fv != 0f) fv else null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ActMain.loadColumnMin() =
|
fun ActMain.loadColumnMin() =
|
||||||
dpToPx(
|
(PrefS.spColumnWidth.value
|
||||||
PrefS.spColumnWidth(pref)
|
.toFloatOrNull()
|
||||||
.toFloatOrNull()
|
?.takeIf { it.isFinite() && it >= 100f }
|
||||||
?.takeIf { it.isFinite() && it >= 100f }
|
?: ActMain.COLUMN_WIDTH_MIN_DP.toFloat()
|
||||||
?: ActMain.COLUMN_WIDTH_MIN_DP.toFloat()
|
).dpToPx(this)
|
||||||
)
|
|
||||||
|
|
||||||
fun ActMain.justifyWindowContentPortrait() {
|
fun ActMain.justifyWindowContentPortrait() {
|
||||||
when (PrefI.ipJustifyWindowContentPortrait(pref)) {
|
when (PrefI.ipJustifyWindowContentPortrait.value) {
|
||||||
PrefI.JWCP_START -> {
|
PrefI.JWCP_START -> {
|
||||||
val iconW = (ActMain.stripIconSize * 1.5f + 0.5f).toInt()
|
val iconW = (ActMain.stripIconSize * 1.5f + 0.5f).toInt()
|
||||||
val padding = resources.displayMetrics.widthPixels / 2 - iconW
|
val padding = resources.displayMetrics.widthPixels / 2 - iconW
|
||||||
|
@ -161,7 +159,7 @@ fun ActMain.justifyWindowContentPortrait() {
|
||||||
|
|
||||||
PrefI.JWCP_END -> {
|
PrefI.JWCP_END -> {
|
||||||
val iconW = (ActMain.stripIconSize * 1.5f + 0.5f).toInt()
|
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
|
val padding = resources.displayMetrics.widthPixels / 2 - iconW - borderWidth
|
||||||
|
|
||||||
fun ViewGroup.addViewAfterFirst(v: View) = addView(v, 1)
|
fun ViewGroup.addViewAfterFirst(v: View) = addView(v, 1)
|
||||||
|
@ -182,10 +180,10 @@ fun ActMain.justifyWindowContentPortrait() {
|
||||||
//////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////
|
||||||
|
|
||||||
// onStart時に呼ばれる
|
// onStart時に呼ばれる
|
||||||
fun ActMain.reloadTimeZone() {
|
fun reloadTimeZone() {
|
||||||
try {
|
try {
|
||||||
var tz = TimeZone.getDefault()
|
var tz = TimeZone.getDefault()
|
||||||
val tzId = PrefS.spTimeZone(pref)
|
val tzId = PrefS.spTimeZone.value
|
||||||
if (tzId.isNotEmpty()) {
|
if (tzId.isNotEmpty()) {
|
||||||
tz = TimeZone.getTimeZone(tzId)
|
tz = TimeZone.getTimeZone(tzId)
|
||||||
}
|
}
|
||||||
|
@ -199,25 +197,25 @@ fun ActMain.reloadTimeZone() {
|
||||||
// onStart時に呼ばれる
|
// onStart時に呼ばれる
|
||||||
// カラーカスタマイズを読み直す
|
// カラーカスタマイズを読み直す
|
||||||
fun ActMain.reloadColors() {
|
fun ActMain.reloadColors() {
|
||||||
ListDivider.color = PrefI.ipListDividerColor(pref)
|
ListDivider.color = PrefI.ipListDividerColor.value
|
||||||
TabletColumnDivider.color = PrefI.ipListDividerColor(pref)
|
TabletColumnDivider.color = PrefI.ipListDividerColor.value
|
||||||
ItemViewHolder.toot_color_unlisted = PrefI.ipTootColorUnlisted(pref)
|
ItemViewHolder.toot_color_unlisted = PrefI.ipTootColorUnlisted.value
|
||||||
ItemViewHolder.toot_color_follower = PrefI.ipTootColorFollower(pref)
|
ItemViewHolder.toot_color_follower = PrefI.ipTootColorFollower.value
|
||||||
ItemViewHolder.toot_color_direct_user = PrefI.ipTootColorDirectUser(pref)
|
ItemViewHolder.toot_color_direct_user = PrefI.ipTootColorDirectUser.value
|
||||||
ItemViewHolder.toot_color_direct_me = PrefI.ipTootColorDirectMe(pref)
|
ItemViewHolder.toot_color_direct_me = PrefI.ipTootColorDirectMe.value
|
||||||
MyClickableSpan.showLinkUnderline = PrefB.bpShowLinkUnderline(pref)
|
MyClickableSpan.showLinkUnderline = PrefB.bpShowLinkUnderline.value
|
||||||
MyClickableSpan.defaultLinkColor = PrefI.ipLinkColor(pref).notZero()
|
MyClickableSpan.defaultLinkColor = PrefI.ipLinkColor.value.notZero()
|
||||||
?: attrColor(R.attr.colorLink)
|
?: attrColor(R.attr.colorLink)
|
||||||
|
|
||||||
CustomShare.reloadCache(this)
|
CustomShare.reloadCache(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ActMain.showFooterColor() {
|
fun ActMain.showFooterColor() {
|
||||||
val footerButtonBgColor = PrefI.ipFooterButtonBgColor(pref)
|
val footerButtonBgColor = PrefI.ipFooterButtonBgColor.value
|
||||||
val footerButtonFgColor = PrefI.ipFooterButtonFgColor(pref)
|
val footerButtonFgColor = PrefI.ipFooterButtonFgColor.value
|
||||||
val footerTabBgColor = PrefI.ipFooterTabBgColor(pref)
|
val footerTabBgColor = PrefI.ipFooterTabBgColor.value
|
||||||
val footerTabDividerColor = PrefI.ipFooterTabDividerColor(pref)
|
val footerTabDividerColor = PrefI.ipFooterTabDividerColor.value
|
||||||
val footerTabIndicatorColor = PrefI.ipFooterTabIndicatorColor(pref)
|
val footerTabIndicatorColor = PrefI.ipFooterTabIndicatorColor.value
|
||||||
|
|
||||||
val colorColumnStripBackground = footerTabBgColor.notZero()
|
val colorColumnStripBackground = footerTabBgColor.notZero()
|
||||||
?: attrColor(R.attr.colorColumnStripBackground)
|
?: attrColor(R.attr.colorColumnStripBackground)
|
||||||
|
|
|
@ -3,18 +3,23 @@ package jp.juggler.subwaytooter.actmain
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import jp.juggler.subwaytooter.ActMain
|
import jp.juggler.subwaytooter.ActMain
|
||||||
import jp.juggler.subwaytooter.pref.PrefB
|
|
||||||
import jp.juggler.subwaytooter.R
|
import jp.juggler.subwaytooter.R
|
||||||
|
import jp.juggler.subwaytooter.pref.PrefB
|
||||||
import jp.juggler.subwaytooter.view.MyViewPager
|
import jp.juggler.subwaytooter.view.MyViewPager
|
||||||
|
|
||||||
// スマホモードならラムダを実行する。タブレットモードならnullを返す
|
// スマホモードならラムダを実行する。タブレットモードなら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を返す
|
// タブレットモードならラムダを実行する。スマホモードなら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) }
|
phoneViews?.let { return codePhone(it) }
|
||||||
tabletViews?.let { return codeTablet(it) }
|
tabletViews?.let { return codeTablet(it) }
|
||||||
error("missing phoneViews/tabletViews")
|
error("missing phoneViews/tabletViews")
|
||||||
|
@ -27,7 +32,7 @@ fun ActMain.initPhoneTablet() {
|
||||||
val tmpTabletPager: RecyclerView = findViewById(R.id.rvPager)
|
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
|
tmpTabletPager.visibility = View.GONE
|
||||||
phoneViews = ActMainPhoneViews(this).apply {
|
phoneViews = ActMainPhoneViews(this).apply {
|
||||||
initUI(tmpPhonePager)
|
initUI(tmpPhonePager)
|
||||||
|
|
|
@ -92,7 +92,7 @@ class ActMainTabletViews(val actMain: ActMain) {
|
||||||
// if( animator is DefaultItemAnimator){
|
// if( animator is DefaultItemAnimator){
|
||||||
// animator.supportsChangeAnimations = false
|
// animator.supportsChangeAnimations = false
|
||||||
// }
|
// }
|
||||||
if (PrefB.bpTabletSnap()) {
|
if (PrefB.bpTabletSnap.value) {
|
||||||
GravitySnapHelper(Gravity.START).attachToRecyclerView(this.tabletPager)
|
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.PrefB
|
||||||
import jp.juggler.subwaytooter.pref.PrefS
|
import jp.juggler.subwaytooter.pref.PrefS
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
import jp.juggler.subwaytooter.table.SavedAccount
|
||||||
|
import jp.juggler.subwaytooter.table.accountListCanSeeMyReactions
|
||||||
import jp.juggler.subwaytooter.util.VersionString
|
import jp.juggler.subwaytooter.util.VersionString
|
||||||
import jp.juggler.subwaytooter.util.openBrowser
|
import jp.juggler.subwaytooter.util.openBrowser
|
||||||
import jp.juggler.util.coroutine.AppDispatchers
|
import jp.juggler.util.coroutine.AppDispatchers
|
||||||
|
import jp.juggler.util.coroutine.launchAndShowError
|
||||||
import jp.juggler.util.coroutine.launchIO
|
import jp.juggler.util.coroutine.launchIO
|
||||||
import jp.juggler.util.coroutine.launchMain
|
|
||||||
import jp.juggler.util.data.JsonObject
|
import jp.juggler.util.data.JsonObject
|
||||||
import jp.juggler.util.data.decodeJsonObject
|
import jp.juggler.util.data.decodeJsonObject
|
||||||
import jp.juggler.util.data.decodeUTF8
|
import jp.juggler.util.data.decodeUTF8
|
||||||
|
@ -120,7 +121,7 @@ class SideMenuAdapter(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val newRelease = releaseInfo?.jsonObject(
|
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)
|
timeline(defaultInsertPosition, ColumnType.BOOKMARKS)
|
||||||
},
|
},
|
||||||
Item(icon = R.drawable.ic_face, title = R.string.reactioned_posts) {
|
Item(icon = R.drawable.ic_face, title = R.string.reactioned_posts) {
|
||||||
launchMain {
|
launchAndShowError {
|
||||||
accountListCanSeeMyReactions()?.let { list ->
|
accountListCanSeeMyReactions()?.let { list ->
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
showToast(false, R.string.not_available_for_current_accounts)
|
showToast(false, R.string.not_available_for_current_accounts)
|
||||||
|
@ -518,7 +519,7 @@ class SideMenuAdapter(
|
||||||
private fun getTimeZoneString(context: Context): String {
|
private fun getTimeZoneString(context: Context): String {
|
||||||
try {
|
try {
|
||||||
var tz = TimeZone.getDefault()
|
var tz = TimeZone.getDefault()
|
||||||
val tzId = PrefS.spTimeZone()
|
val tzId = PrefS.spTimeZone.value
|
||||||
if (tzId.isBlank()) {
|
if (tzId.isBlank()) {
|
||||||
return tz.displayName + "(" + context.getString(R.string.device_timezone) + ")"
|
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.R
|
||||||
import jp.juggler.subwaytooter.api.entity.TootVisibility
|
import jp.juggler.subwaytooter.api.entity.TootVisibility
|
||||||
import jp.juggler.subwaytooter.dialog.pickAccount
|
import jp.juggler.subwaytooter.dialog.pickAccount
|
||||||
import jp.juggler.subwaytooter.table.AcctColor
|
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
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.coroutine.launchMain
|
||||||
import jp.juggler.util.data.notZero
|
import jp.juggler.util.data.notZero
|
||||||
import jp.juggler.util.log.LogCategory
|
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 }))
|
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
|
views.btnAccount.text = ac.nickname
|
||||||
|
|
||||||
if (AcctColor.hasColorBackground(ac)) {
|
if (daoAcctColor.hasColorBackground(ac)) {
|
||||||
views.btnAccount.background =
|
views.btnAccount.background =
|
||||||
getAdaptiveRippleDrawableRound(this, ac.color_bg, ac.color_fg)
|
getAdaptiveRippleDrawableRound(this, ac.colorBg, ac.colorFg)
|
||||||
} else {
|
} else {
|
||||||
views.btnAccount.setBackgroundResource(R.drawable.btn_bg_transparent_round6dp)
|
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)
|
?: attrColor(android.R.attr.textColorPrimary)
|
||||||
}
|
}
|
||||||
updateTextCount()
|
updateTextCount()
|
||||||
|
@ -75,8 +77,7 @@ fun ActPost.performAccountChooser() {
|
||||||
if (!canSwitchAccount()) return
|
if (!canSwitchAccount()) return
|
||||||
|
|
||||||
if (isMultiWindowPost) {
|
if (isMultiWindowPost) {
|
||||||
accountList = SavedAccount.loadAccountList(this)
|
accountList = daoSavedAccount.loadAccountList().sortedByNickname()
|
||||||
SavedAccount.sort(accountList)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
launchMain {
|
launchMain {
|
||||||
|
|
|
@ -12,13 +12,14 @@ import jp.juggler.subwaytooter.api.entity.*
|
||||||
import jp.juggler.subwaytooter.api.runApiTask
|
import jp.juggler.subwaytooter.api.runApiTask
|
||||||
import jp.juggler.subwaytooter.calcIconRound
|
import jp.juggler.subwaytooter.calcIconRound
|
||||||
import jp.juggler.subwaytooter.defaultColorIcon
|
import jp.juggler.subwaytooter.defaultColorIcon
|
||||||
import jp.juggler.subwaytooter.dialog.ActionsDialog
|
|
||||||
import jp.juggler.subwaytooter.dialog.DlgFocusPoint
|
import jp.juggler.subwaytooter.dialog.DlgFocusPoint
|
||||||
import jp.juggler.subwaytooter.dialog.DlgTextInput
|
import jp.juggler.subwaytooter.dialog.DlgTextInput
|
||||||
|
import jp.juggler.subwaytooter.dialog.actionsDialog
|
||||||
import jp.juggler.subwaytooter.pref.PrefB
|
import jp.juggler.subwaytooter.pref.PrefB
|
||||||
import jp.juggler.subwaytooter.util.AttachmentRequest
|
import jp.juggler.subwaytooter.util.AttachmentRequest
|
||||||
import jp.juggler.subwaytooter.util.PostAttachment
|
import jp.juggler.subwaytooter.util.PostAttachment
|
||||||
import jp.juggler.subwaytooter.view.MyNetworkImageView
|
import jp.juggler.subwaytooter.view.MyNetworkImageView
|
||||||
|
import jp.juggler.util.coroutine.launchAndShowError
|
||||||
import jp.juggler.util.coroutine.launchMain
|
import jp.juggler.util.coroutine.launchMain
|
||||||
import jp.juggler.util.data.*
|
import jp.juggler.util.data.*
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
|
@ -169,7 +170,7 @@ fun ActPost.onPostAttachmentCompleteImpl(pa: PostAttachment) {
|
||||||
log.i("onPostAttachmentComplete: upload complete.")
|
log.i("onPostAttachmentComplete: upload complete.")
|
||||||
|
|
||||||
// 投稿欄の末尾に追記する
|
// 投稿欄の末尾に追記する
|
||||||
if (PrefB.bpAppendAttachmentUrlToContent.invoke(pref)) {
|
if (PrefB.bpAppendAttachmentUrlToContent.value) {
|
||||||
appendArrachmentUrl(a)
|
appendArrachmentUrl(a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -215,35 +216,33 @@ fun ActPost.performAttachmentClick(idx: Int) {
|
||||||
showToast(false, ex.withCaption("can't get attachment item[$idx]."))
|
showToast(false, ex.withCaption("can't get attachment item[$idx]."))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
launchAndShowError {
|
||||||
val a = ActionsDialog()
|
actionsDialog(getString(R.string.media_attachment)) {
|
||||||
.addAction(getString(R.string.set_description)) {
|
action(getString(R.string.set_description)) {
|
||||||
editAttachmentDescription(pa)
|
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)
|
|
||||||
}
|
}
|
||||||
|
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) {
|
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.TootParser
|
||||||
import jp.juggler.subwaytooter.api.entity.*
|
import jp.juggler.subwaytooter.api.entity.*
|
||||||
import jp.juggler.subwaytooter.dialog.DlgDraftPicker
|
import jp.juggler.subwaytooter.dialog.DlgDraftPicker
|
||||||
import jp.juggler.subwaytooter.table.PostDraft
|
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
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.DecodeOptions
|
||||||
import jp.juggler.subwaytooter.util.PostAttachment
|
import jp.juggler.subwaytooter.util.PostAttachment
|
||||||
import jp.juggler.util.coroutine.launchProgress
|
import jp.juggler.util.coroutine.launchProgress
|
||||||
|
@ -129,7 +130,7 @@ fun ActPost.saveDraft() {
|
||||||
states.visibility?.id?.toString()?.let { json.put(DRAFT_VISIBILITY, it) }
|
states.visibility?.id?.toString()?.let { json.put(DRAFT_VISIBILITY, it) }
|
||||||
states.inReplyToId?.putTo(json, DRAFT_REPLY_ID)
|
states.inReplyToId?.putTo(json, DRAFT_REPLY_ID)
|
||||||
|
|
||||||
PostDraft.save(System.currentTimeMillis(), json)
|
daoPostDraft.save(System.currentTimeMillis(), json)
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
log.e(ex, "saveDraft failed.")
|
log.e(ex, "saveDraft failed.")
|
||||||
}
|
}
|
||||||
|
@ -152,7 +153,7 @@ fun ActPost.restoreDraft(draft: JsonObject) {
|
||||||
draft.jsonArray(DRAFT_ATTACHMENT_LIST)?.objectList()?.toMutableList()
|
draft.jsonArray(DRAFT_ATTACHMENT_LIST)?.objectList()?.toMutableList()
|
||||||
|
|
||||||
val accountDbId = draft.long(DRAFT_ACCOUNT_DB_ID) ?: -1L
|
val accountDbId = draft.long(DRAFT_ACCOUNT_DB_ID) ?: -1L
|
||||||
val account = SavedAccount.loadAccount(this@restoreDraft, accountDbId)
|
val account = daoSavedAccount.loadAccount(accountDbId)
|
||||||
if (account == null) {
|
if (account == null) {
|
||||||
listWarning.add(getString(R.string.account_in_draft_is_lost))
|
listWarning.add(getString(R.string.account_in_draft_is_lost))
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package jp.juggler.subwaytooter.actpost
|
package jp.juggler.subwaytooter.actpost
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import jp.juggler.subwaytooter.ActMain
|
import jp.juggler.subwaytooter.ActMain
|
||||||
import jp.juggler.subwaytooter.ActPost
|
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.TootPollsType
|
||||||
import jp.juggler.subwaytooter.api.entity.TootVisibility
|
import jp.juggler.subwaytooter.api.entity.TootVisibility
|
||||||
import jp.juggler.subwaytooter.api.entity.unknownHostAndDomain
|
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.pref.PrefB
|
||||||
import jp.juggler.subwaytooter.table.PostDraft
|
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
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.DecodeOptions
|
||||||
import jp.juggler.subwaytooter.util.PostAttachment
|
import jp.juggler.subwaytooter.util.PostAttachment
|
||||||
import jp.juggler.subwaytooter.util.PostImpl
|
import jp.juggler.subwaytooter.util.PostImpl
|
||||||
import jp.juggler.subwaytooter.util.PostResult
|
import jp.juggler.subwaytooter.util.PostResult
|
||||||
import jp.juggler.util.*
|
import jp.juggler.util.*
|
||||||
import jp.juggler.util.coroutine.launchAndShowError
|
import jp.juggler.util.coroutine.launchAndShowError
|
||||||
|
import jp.juggler.util.coroutine.launchMain
|
||||||
import jp.juggler.util.data.CharacterGroup
|
import jp.juggler.util.data.CharacterGroup
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
import jp.juggler.util.log.showToast
|
import jp.juggler.util.log.showToast
|
||||||
|
@ -104,27 +107,27 @@ fun ActPost.hasContent(): Boolean {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ActPost.resetText() {
|
fun ActPost.resetText() {
|
||||||
isPostComplete = false
|
launchMain {
|
||||||
|
isPostComplete = false
|
||||||
|
|
||||||
resetReply()
|
resetReply()
|
||||||
|
|
||||||
resetMushroom()
|
resetMushroom()
|
||||||
states.redraftStatusId = null
|
states.redraftStatusId = null
|
||||||
states.editStatusId = null
|
states.editStatusId = null
|
||||||
states.timeSchedule = 0L
|
states.timeSchedule = 0L
|
||||||
attachmentPicker.reset()
|
attachmentPicker.reset()
|
||||||
scheduledStatus = null
|
scheduledStatus = null
|
||||||
attachmentList.clear()
|
attachmentList.clear()
|
||||||
views.cbQuote.isChecked = false
|
views.cbQuote.isChecked = false
|
||||||
views.etContent.setText("")
|
views.etContent.setText("")
|
||||||
views.spPollType.setSelection(0, false)
|
views.spPollType.setSelection(0, false)
|
||||||
etChoices.forEach { it.setText("") }
|
etChoices.forEach { it.setText("") }
|
||||||
accountList = SavedAccount.loadAccountList(this)
|
accountList = daoSavedAccount.loadAccountList().sortedByNickname()
|
||||||
SavedAccount.sort(accountList)
|
if (accountList.isEmpty()) {
|
||||||
if (accountList.isEmpty()) {
|
showToast(true, R.string.please_add_account)
|
||||||
showToast(true, R.string.please_add_account)
|
finish()
|
||||||
finish()
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,28 +151,18 @@ fun ActPost.afterUpdateText() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初期化時と投稿完了時とリセット確認後に呼ばれる
|
// 初期化時と投稿完了時とリセット確認後に呼ばれる
|
||||||
fun ActPost.updateText(
|
suspend fun ActPost.updateText(
|
||||||
intent: Intent,
|
intent: Intent,
|
||||||
confirmed: Boolean = false,
|
|
||||||
saveDraft: Boolean = true,
|
saveDraft: Boolean = true,
|
||||||
resetAccount: Boolean = true,
|
resetAccount: Boolean = true,
|
||||||
) {
|
) {
|
||||||
if (!canSwitchAccount()) return
|
if (!canSwitchAccount()) return
|
||||||
|
|
||||||
if (!confirmed && hasContent()) {
|
if (saveDraft && hasContent()) {
|
||||||
AlertDialog.Builder(this)
|
confirm(R.string.post_reset_confirm)
|
||||||
.setMessage("編集中のテキストや文脈を下書きに退避して、新しい投稿を編集しますか? ")
|
saveDraft()
|
||||||
.setNegativeButton(R.string.cancel, null)
|
|
||||||
.setPositiveButton(R.string.ok) { _, _ ->
|
|
||||||
updateText(intent, confirmed = true)
|
|
||||||
}
|
|
||||||
.setCancelable(true)
|
|
||||||
.show()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (saveDraft) saveDraft()
|
|
||||||
|
|
||||||
resetText()
|
resetText()
|
||||||
|
|
||||||
// Android 9 から、明示的にフォーカスを当てる必要がある
|
// Android 9 から、明示的にフォーカスを当てる必要がある
|
||||||
|
@ -262,7 +255,7 @@ fun ActPost.initializeFromSharedIntent(sharedIntent: Intent) {
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasUri || !PrefB.bpIgnoreTextInSharedMedia(pref)) {
|
if (!hasUri || !PrefB.bpIgnoreTextInSharedMedia.value) {
|
||||||
appendContentText(sharedIntent)
|
appendContentText(sharedIntent)
|
||||||
}
|
}
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
|
@ -271,33 +264,33 @@ fun ActPost.initializeFromSharedIntent(sharedIntent: Intent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ActPost.performMore() {
|
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)) {
|
action(getString(R.string.clear_text)) {
|
||||||
completionHelper.openEmojiPickerFromMore()
|
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() {
|
fun ActPost.performPost() {
|
||||||
|
@ -366,7 +359,7 @@ fun ActPost.performPost() {
|
||||||
|
|
||||||
if (isMultiWindowPost) {
|
if (isMultiWindowPost) {
|
||||||
resetText()
|
resetText()
|
||||||
updateText(Intent(), confirmed = true, saveDraft = false, resetAccount = false)
|
updateText(Intent(), saveDraft = false, resetAccount = false)
|
||||||
afterUpdateText()
|
afterUpdateText()
|
||||||
} else {
|
} else {
|
||||||
// ActMainの復元が必要な場合に備えてintentのdataでも渡す
|
// ActMainの復元が必要な場合に備えてintentのdataでも渡す
|
||||||
|
@ -382,7 +375,7 @@ fun ActPost.performPost() {
|
||||||
|
|
||||||
if (isMultiWindowPost) {
|
if (isMultiWindowPost) {
|
||||||
resetText()
|
resetText()
|
||||||
updateText(Intent(), confirmed = true, saveDraft = false, resetAccount = false)
|
updateText(Intent(), saveDraft = false, resetAccount = false)
|
||||||
afterUpdateText()
|
afterUpdateText()
|
||||||
ActMain.refActMain?.get()?.onCompleteActPost(data)
|
ActMain.refActMain?.get()?.onCompleteActPost(data)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -10,10 +10,8 @@ import jp.juggler.subwaytooter.getVisibilityCaption
|
||||||
import jp.juggler.subwaytooter.getVisibilityIconId
|
import jp.juggler.subwaytooter.getVisibilityIconId
|
||||||
|
|
||||||
fun ActPost.showVisibility() {
|
fun ActPost.showVisibility() {
|
||||||
val iconId = getVisibilityIconId(
|
val iconId = (states.visibility ?: TootVisibility.Public)
|
||||||
account?.isMisskey == true,
|
.getVisibilityIconId(account?.isMisskey == true)
|
||||||
states.visibility ?: TootVisibility.Public
|
|
||||||
)
|
|
||||||
views.btnVisibility.setImageResource(iconId)
|
views.btnVisibility.setImageResource(iconId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package jp.juggler.subwaytooter.actpost
|
package jp.juggler.subwaytooter.actpost
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.text.*
|
import android.text.*
|
||||||
import android.text.style.ForegroundColorSpan
|
import android.text.style.ForegroundColorSpan
|
||||||
|
@ -9,21 +8,20 @@ import androidx.appcompat.app.AppCompatActivity
|
||||||
import jp.juggler.subwaytooter.App1
|
import jp.juggler.subwaytooter.App1
|
||||||
import jp.juggler.subwaytooter.R
|
import jp.juggler.subwaytooter.R
|
||||||
import jp.juggler.subwaytooter.api.entity.TootTag
|
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.dialog.launchEmojiPicker
|
||||||
import jp.juggler.subwaytooter.emoji.CustomEmoji
|
import jp.juggler.subwaytooter.emoji.CustomEmoji
|
||||||
import jp.juggler.subwaytooter.emoji.EmojiBase
|
import jp.juggler.subwaytooter.emoji.EmojiBase
|
||||||
import jp.juggler.subwaytooter.emoji.UnicodeEmoji
|
import jp.juggler.subwaytooter.emoji.UnicodeEmoji
|
||||||
import jp.juggler.subwaytooter.pref.PrefB
|
import jp.juggler.subwaytooter.pref.PrefB
|
||||||
import jp.juggler.subwaytooter.span.NetworkEmojiSpan
|
import jp.juggler.subwaytooter.span.NetworkEmojiSpan
|
||||||
import jp.juggler.subwaytooter.table.AcctSet
|
import jp.juggler.subwaytooter.table.*
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
|
||||||
import jp.juggler.subwaytooter.table.TagSet
|
|
||||||
import jp.juggler.subwaytooter.util.DecodeOptions
|
import jp.juggler.subwaytooter.util.DecodeOptions
|
||||||
import jp.juggler.subwaytooter.util.EmojiDecoder
|
import jp.juggler.subwaytooter.util.EmojiDecoder
|
||||||
import jp.juggler.subwaytooter.util.PopupAutoCompleteAcct
|
import jp.juggler.subwaytooter.util.PopupAutoCompleteAcct
|
||||||
import jp.juggler.subwaytooter.view.MyEditText
|
import jp.juggler.subwaytooter.view.MyEditText
|
||||||
import jp.juggler.util.*
|
import jp.juggler.util.*
|
||||||
|
import jp.juggler.util.coroutine.launchAndShowError
|
||||||
import jp.juggler.util.data.asciiRegex
|
import jp.juggler.util.data.asciiRegex
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
import jp.juggler.util.ui.attrColor
|
import jp.juggler.util.ui.attrColor
|
||||||
|
@ -33,7 +31,6 @@ import kotlin.math.min
|
||||||
// 入力補完機能
|
// 入力補完機能
|
||||||
class CompletionHelper(
|
class CompletionHelper(
|
||||||
private val activity: AppCompatActivity,
|
private val activity: AppCompatActivity,
|
||||||
private val pref: SharedPreferences,
|
|
||||||
private val handler: Handler,
|
private val handler: Handler,
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -161,7 +158,7 @@ class CompletionHelper(
|
||||||
|
|
||||||
val limit = 100
|
val limit = 100
|
||||||
val s = src.substring(start, end)
|
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}")
|
log.d("search for $s, result=${acctList.size}")
|
||||||
if (acctList.isEmpty()) {
|
if (acctList.isEmpty()) {
|
||||||
closeAcctPopup()
|
closeAcctPopup()
|
||||||
|
@ -187,7 +184,7 @@ class CompletionHelper(
|
||||||
|
|
||||||
val limit = 100
|
val limit = 100
|
||||||
val s = src.substring(lastSharp + 1, end)
|
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}")
|
log.d("search for $s, result=${tagList.size}")
|
||||||
if (tagList.isEmpty()) {
|
if (tagList.isEmpty()) {
|
||||||
closeAcctPopup()
|
closeAcctPopup()
|
||||||
|
@ -448,7 +445,7 @@ class CompletionHelper(
|
||||||
launchEmojiPicker(
|
launchEmojiPicker(
|
||||||
activity,
|
activity,
|
||||||
accessInfo,
|
accessInfo,
|
||||||
closeOnSelected = PrefB.bpEmojiPickerCloseOnSelected(pref)
|
closeOnSelected = PrefB.bpEmojiPickerCloseOnSelected.value
|
||||||
) { emoji, bInstanceHasCustomEmoji ->
|
) { emoji, bInstanceHasCustomEmoji ->
|
||||||
val et = this@CompletionHelper.et ?: return@launchEmojiPicker
|
val et = this@CompletionHelper.et ?: return@launchEmojiPicker
|
||||||
|
|
||||||
|
@ -482,7 +479,7 @@ class CompletionHelper(
|
||||||
launchEmojiPicker(
|
launchEmojiPicker(
|
||||||
activity,
|
activity,
|
||||||
accessInfo,
|
accessInfo,
|
||||||
closeOnSelected = PrefB.bpEmojiPickerCloseOnSelected(pref)
|
closeOnSelected = PrefB.bpEmojiPickerCloseOnSelected.value
|
||||||
) { emoji, bInstanceHasCustomEmoji ->
|
) { emoji, bInstanceHasCustomEmoji ->
|
||||||
val et = this@CompletionHelper.et ?: return@launchEmojiPicker
|
val et = this@CompletionHelper.et ?: return@launchEmojiPicker
|
||||||
|
|
||||||
|
@ -514,49 +511,50 @@ class CompletionHelper(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun openFeaturedTagList(list: List<TootTag>?) {
|
fun openFeaturedTagList(list: List<TootTag>?) {
|
||||||
val ad = ActionsDialog()
|
val et = this@CompletionHelper.et ?: return
|
||||||
list?.forEach { tag ->
|
activity.run {
|
||||||
ad.addAction("#${tag.name}") {
|
launchAndShowError {
|
||||||
val et = this.et ?: return@addAction
|
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 sb = SpannableStringBuilder()
|
||||||
val srcLength = src.length
|
.append(src.subSequence(0, start))
|
||||||
val start = min(srcLength, et.selectionStart)
|
.appendHashTag(tag.name)
|
||||||
val end = min(srcLength, et.selectionEnd)
|
val newSelection = sb.length
|
||||||
|
if (end < srcLength) sb.append(src.subSequence(end, srcLength))
|
||||||
|
|
||||||
val sb = SpannableStringBuilder()
|
et.text = sb
|
||||||
.append(src.subSequence(0, start))
|
et.setSelection(newSelection)
|
||||||
.appendHashTag(tag.name)
|
|
||||||
val newSelection = sb.length
|
|
||||||
if (end < srcLength) sb.append(src.subSequence(end, srcLength))
|
|
||||||
|
|
||||||
et.text = sb
|
procTextChanged.run()
|
||||||
et.setSelection(newSelection)
|
}
|
||||||
|
}
|
||||||
|
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() {
|
// 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.R
|
||||||
import jp.juggler.subwaytooter.api.auth.AuthBase
|
import jp.juggler.subwaytooter.api.auth.AuthBase
|
||||||
import jp.juggler.subwaytooter.api.entity.*
|
import jp.juggler.subwaytooter.api.entity.*
|
||||||
import jp.juggler.subwaytooter.pref.pref
|
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
import jp.juggler.subwaytooter.table.SavedAccount
|
||||||
import jp.juggler.subwaytooter.util.*
|
import jp.juggler.subwaytooter.util.*
|
||||||
import jp.juggler.util.data.*
|
import jp.juggler.util.data.*
|
||||||
|
@ -56,7 +55,6 @@ class TootApiClient(
|
||||||
}
|
}
|
||||||
|
|
||||||
// 認証に関する設定を保存する
|
// 認証に関する設定を保存する
|
||||||
internal val pref = context.pref()
|
|
||||||
|
|
||||||
// インスタンスのホスト名
|
// インスタンスのホスト名
|
||||||
var apiHost: Host? = null
|
var apiHost: Host? = null
|
||||||
|
@ -407,8 +405,9 @@ class TootApiClient(
|
||||||
|
|
||||||
requestBuilder.url(url)
|
requestBuilder.url(url)
|
||||||
|
|
||||||
(forceAccessToken ?: account?.getAccessToken())
|
(forceAccessToken ?: account?.bearerAccessToken)?.notEmpty()?.let {
|
||||||
?.notEmpty()?.let { requestBuilder.header("Authorization", "Bearer $it") }
|
requestBuilder.header("Authorization", "Bearer $it")
|
||||||
|
}
|
||||||
|
|
||||||
requestBuilder.build()
|
requestBuilder.build()
|
||||||
.also { log.d("request: ${it.method} $url") }
|
.also { log.d("request: ${it.method} $url") }
|
||||||
|
@ -503,10 +502,9 @@ class TootApiClient(
|
||||||
url = "$url${delm}i=${accessToken.encodePercent()}"
|
url = "$url${delm}i=${accessToken.encodePercent()}"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val accessToken = account.getAccessToken()
|
account.bearerAccessToken.notEmpty()?.let {
|
||||||
if (accessToken?.isNotEmpty() == true) {
|
val delm = if (url.contains('?')) '&' else '?'
|
||||||
val delm = if (-1 != url.indexOf('?')) '&' else '?'
|
url = "$url${delm}access_token=${it.encodePercent()}"
|
||||||
url = "$url${delm}access_token=${accessToken.encodePercent()}"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ abstract class AuthBase {
|
||||||
val clientName
|
val clientName
|
||||||
get() = arrayOf(
|
get() = arrayOf(
|
||||||
testClientName,
|
testClientName,
|
||||||
PrefS.spClientName.invoke(),
|
PrefS.spClientName.value,
|
||||||
).firstNotNullOfOrNull { it.notBlank() }
|
).firstNotNullOfOrNull { it.notBlank() }
|
||||||
?: DEFAULT_CLIENT_NAME
|
?: 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.Host
|
||||||
import jp.juggler.subwaytooter.api.entity.InstanceType
|
import jp.juggler.subwaytooter.api.entity.InstanceType
|
||||||
import jp.juggler.subwaytooter.api.entity.TootInstance
|
import jp.juggler.subwaytooter.api.entity.TootInstance
|
||||||
import jp.juggler.subwaytooter.table.ClientInfo
|
import jp.juggler.subwaytooter.table.daoClientInfo
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||||
import jp.juggler.subwaytooter.util.LinkHelper
|
import jp.juggler.subwaytooter.util.LinkHelper
|
||||||
import jp.juggler.util.data.JsonObject
|
import jp.juggler.util.data.JsonObject
|
||||||
import jp.juggler.util.data.buildJsonObject
|
import jp.juggler.util.data.buildJsonObject
|
||||||
|
@ -84,7 +84,7 @@ class MastodonAuth(override val client: TootApiClient) : AuthBase() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
clientInfo[KEY_CLIENT_CREDENTIAL] = clientCredential
|
clientInfo[KEY_CLIENT_CREDENTIAL] = clientCredential
|
||||||
ClientInfo.save(apiHost, clientName, clientInfo.toString())
|
daoClientInfo.save(apiHost, clientName, clientInfo.toString())
|
||||||
return clientCredential
|
return clientCredential
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ class MastodonAuth(override val client: TootApiClient) : AuthBase() {
|
||||||
tootInstance: TootInstance?,
|
tootInstance: TootInstance?,
|
||||||
forceUpdateClient: Boolean,
|
forceUpdateClient: Boolean,
|
||||||
): JsonObject {
|
): JsonObject {
|
||||||
var clientInfo = ClientInfo.load(apiHost, clientName)
|
var clientInfo = daoClientInfo.load(apiHost, clientName)
|
||||||
|
|
||||||
// スコープ一覧を取得する
|
// スコープ一覧を取得する
|
||||||
val scopeString = mastodonScope(tootInstance)
|
val scopeString = mastodonScope(tootInstance)
|
||||||
|
@ -126,7 +126,7 @@ class MastodonAuth(override val client: TootApiClient) : AuthBase() {
|
||||||
else -> try {
|
else -> try {
|
||||||
// マストドン2.4でスコープが追加された
|
// マストドン2.4でスコープが追加された
|
||||||
// 取得時のスコープ指定がマッチしない(もしくは記録されていない)ならクライアント情報を再利用してはいけない
|
// 取得時のスコープ指定がマッチしない(もしくは記録されていない)ならクライアント情報を再利用してはいけない
|
||||||
ClientInfo.delete(apiHost, clientName)
|
daoClientInfo.delete(apiHost, clientName)
|
||||||
|
|
||||||
// クライアントアプリ情報そのものはまだサーバに残っているが、明示的に消す方法は現状存在しない
|
// クライアントアプリ情報そのものはまだサーバに残っているが、明示的に消す方法は現状存在しない
|
||||||
// client credential だけは消せる
|
// client credential だけは消せる
|
||||||
|
@ -254,7 +254,7 @@ class MastodonAuth(override val client: TootApiClient) : AuthBase() {
|
||||||
when {
|
when {
|
||||||
param.startsWith("db:") -> try {
|
param.startsWith("db:") -> try {
|
||||||
val dataId = param.substring(3).toLong(10)
|
val dataId = param.substring(3).toLong(10)
|
||||||
val sa = SavedAccount.loadAccount(context, dataId)
|
val sa = daoSavedAccount.loadAccount(dataId)
|
||||||
?: error("missing account db_id=$dataId")
|
?: error("missing account db_id=$dataId")
|
||||||
client.account = sa
|
client.account = sa
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
|
@ -272,7 +272,7 @@ class MastodonAuth(override val client: TootApiClient) : AuthBase() {
|
||||||
val apiHost = client.apiHost
|
val apiHost = client.apiHost
|
||||||
?: error("can't get apiHost from callback parameter.")
|
?: 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")
|
?: error("can't find client info for apiHost=$apiHost, clientName=$clientName")
|
||||||
|
|
||||||
val tokenInfo = api.authStep2(
|
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.EntityId
|
||||||
import jp.juggler.subwaytooter.api.entity.Host
|
import jp.juggler.subwaytooter.api.entity.Host
|
||||||
import jp.juggler.subwaytooter.api.entity.TootInstance
|
import jp.juggler.subwaytooter.api.entity.TootInstance
|
||||||
import jp.juggler.subwaytooter.pref.PrefDevice
|
import jp.juggler.subwaytooter.pref.prefDevice
|
||||||
import jp.juggler.subwaytooter.table.ClientInfo
|
import jp.juggler.subwaytooter.table.daoClientInfo
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||||
import jp.juggler.subwaytooter.util.LinkHelper
|
import jp.juggler.subwaytooter.util.LinkHelper
|
||||||
import jp.juggler.util.data.*
|
import jp.juggler.util.data.*
|
||||||
import jp.juggler.util.log.LogCategory
|
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"}
|
* {"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 {
|
private suspend fun createAuthUri(apiHost: Host, appSecret: String): Uri {
|
||||||
PrefDevice.from(context).edit().apply {
|
context.prefDevice.saveLastAuth(
|
||||||
putString(PrefDevice.LAST_AUTH_INSTANCE, apiHost.ascii)
|
host = apiHost.ascii,
|
||||||
putString(PrefDevice.LAST_AUTH_SECRET, appSecret)
|
secret = appSecret,
|
||||||
when (val account = account) {
|
dbId = account?.db_id, //nullable
|
||||||
null -> remove(PrefDevice.LAST_AUTH_DB_ID)
|
)
|
||||||
else -> putLong(PrefDevice.LAST_AUTH_DB_ID, account.db_id)
|
|
||||||
}
|
|
||||||
}.apply()
|
|
||||||
return api.authSessionGenerate(apiHost, appSecret)
|
return api.authSessionGenerate(apiHost, appSecret)
|
||||||
.string("url").notEmpty()?.toUri()
|
.string("url").notEmpty()?.toUri()
|
||||||
?: error("missing 'url' in session/generate.")
|
?: error("missing 'url' in session/generate.")
|
||||||
|
@ -122,7 +120,7 @@ class MisskeyAuth10(override val client: TootApiClient) : AuthBase() {
|
||||||
): Uri {
|
): Uri {
|
||||||
val apiHost = apiHost ?: error("missing apiHost")
|
val apiHost = apiHost ?: error("missing apiHost")
|
||||||
|
|
||||||
val clientInfo = ClientInfo.load(apiHost, clientName)
|
val clientInfo = daoClientInfo.load(apiHost, clientName)
|
||||||
|
|
||||||
// スコープ一覧を取得する
|
// スコープ一覧を取得する
|
||||||
val scopeArray = getScopeArrayMisskey(ti)
|
val scopeArray = getScopeArrayMisskey(ti)
|
||||||
|
@ -173,7 +171,7 @@ class MisskeyAuth10(override val client: TootApiClient) : AuthBase() {
|
||||||
val appSecret = appJson.string(KEY_MISSKEY_APP_SECRET)
|
val appSecret = appJson.string(KEY_MISSKEY_APP_SECRET)
|
||||||
.notBlank() ?: error(context.getString(R.string.cant_get_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)
|
return createAuthUri(apiHost, appSecret)
|
||||||
}
|
}
|
||||||
|
@ -183,20 +181,21 @@ class MisskeyAuth10(override val client: TootApiClient) : AuthBase() {
|
||||||
*/
|
*/
|
||||||
override suspend fun authStep2(uri: Uri): Auth2Result {
|
override suspend fun authStep2(uri: Uri): Auth2Result {
|
||||||
|
|
||||||
val prefDevice = PrefDevice.from(context)
|
val prefDevice = context.prefDevice
|
||||||
|
|
||||||
val token = uri.getQueryParameter("token")
|
val token = uri.getQueryParameter("token")
|
||||||
?.notBlank() ?: error("missing token in callback URL")
|
?.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.")
|
?.notBlank() ?: error("missing instance name.")
|
||||||
|
|
||||||
val apiHost = Host.parse(hostStr)
|
val apiHost = Host.parse(hostStr)
|
||||||
|
|
||||||
when (val dbId = prefDevice.getLong(PrefDevice.LAST_AUTH_DB_ID, -1L)) {
|
when (val dbId = prefDevice.lastAuthDbId) {
|
||||||
// new registration
|
// new registration
|
||||||
-1L -> client.apiHost = apiHost
|
null -> client.apiHost = apiHost
|
||||||
// update access token
|
// update access token
|
||||||
else -> SavedAccount.loadAccount(context, dbId)?.also {
|
else -> daoSavedAccount.loadAccount(dbId)?.also {
|
||||||
client.account = it
|
client.account = it
|
||||||
} ?: error("missing account db_id=$dbId")
|
} ?: error("missing account db_id=$dbId")
|
||||||
}
|
}
|
||||||
|
@ -209,7 +208,7 @@ class MisskeyAuth10(override val client: TootApiClient) : AuthBase() {
|
||||||
)
|
)
|
||||||
|
|
||||||
@Suppress("UNUSED_VARIABLE")
|
@Suppress("UNUSED_VARIABLE")
|
||||||
val clientInfo = ClientInfo.load(apiHost, clientName)
|
val clientInfo = daoClientInfo.load(apiHost, clientName)
|
||||||
?.notEmpty() ?: error("missing client id")
|
?.notEmpty() ?: error("missing client id")
|
||||||
|
|
||||||
val appSecret = clientInfo.string(KEY_MISSKEY_APP_SECRET)
|
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.auth.MisskeyAuth10.Companion.getScopeArrayMisskey
|
||||||
import jp.juggler.subwaytooter.api.entity.Host
|
import jp.juggler.subwaytooter.api.entity.Host
|
||||||
import jp.juggler.subwaytooter.api.entity.TootInstance
|
import jp.juggler.subwaytooter.api.entity.TootInstance
|
||||||
import jp.juggler.subwaytooter.pref.PrefDevice
|
import jp.juggler.subwaytooter.pref.prefDevice
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||||
import jp.juggler.subwaytooter.util.LinkHelper
|
import jp.juggler.subwaytooter.util.LinkHelper
|
||||||
import jp.juggler.util.data.JsonObject
|
import jp.juggler.util.data.JsonObject
|
||||||
import jp.juggler.util.data.notEmpty
|
import jp.juggler.util.data.notEmpty
|
||||||
|
@ -60,14 +60,11 @@ class MisskeyAuth13(override val client: TootApiClient) : AuthBase() {
|
||||||
|
|
||||||
val sessionId = UUID.randomUUID().toString()
|
val sessionId = UUID.randomUUID().toString()
|
||||||
|
|
||||||
PrefDevice.from(client.context).edit().apply {
|
client.context.prefDevice.saveLastAuth(
|
||||||
putString(PrefDevice.LAST_AUTH_INSTANCE, apiHost.ascii)
|
host = apiHost.ascii,
|
||||||
putString(PrefDevice.LAST_AUTH_SECRET, sessionId)
|
secret = sessionId,
|
||||||
when (val account = account) {
|
dbId = account?.db_id,
|
||||||
null -> remove(PrefDevice.LAST_AUTH_DB_ID)
|
)
|
||||||
else -> putLong(PrefDevice.LAST_AUTH_DB_ID, account.db_id)
|
|
||||||
}
|
|
||||||
}.apply()
|
|
||||||
|
|
||||||
return api13.createAuthUrl(
|
return api13.createAuthUrl(
|
||||||
apiHost = apiHost,
|
apiHost = apiHost,
|
||||||
|
@ -82,20 +79,20 @@ class MisskeyAuth13(override val client: TootApiClient) : AuthBase() {
|
||||||
override suspend fun authStep2(uri: Uri): Auth2Result {
|
override suspend fun authStep2(uri: Uri): Auth2Result {
|
||||||
|
|
||||||
// 認証開始時に保存した情報
|
// 認証開始時に保存した情報
|
||||||
val prefDevice = PrefDevice.from(client.context)
|
val prefDevice = client.context.prefDevice
|
||||||
val savedSessionId = prefDevice.getString(PrefDevice.LAST_AUTH_SECRET, null)
|
val savedSessionId = prefDevice.lastAuthSecret
|
||||||
|
|
||||||
val apiHost = prefDevice.getString(PrefDevice.LAST_AUTH_INSTANCE, null)
|
val apiHost = prefDevice.lastAuthInstance
|
||||||
?.let { Host.parse(it) }
|
?.let { Host.parse(it) }
|
||||||
?: error("missing apiHost")
|
?: error("missing apiHost")
|
||||||
|
|
||||||
when (val dbId = prefDevice.getLong(PrefDevice.LAST_AUTH_DB_ID, -1L)) {
|
when (val dbId = prefDevice.lastAuthDbId) {
|
||||||
// new registration
|
// new registration
|
||||||
-1L -> client.apiHost = apiHost
|
null -> client.apiHost = apiHost
|
||||||
|
|
||||||
// update access token
|
// update access token
|
||||||
else -> {
|
else -> {
|
||||||
val sa = SavedAccount.loadAccount(context, dbId)
|
val sa = daoSavedAccount.loadAccount(dbId)
|
||||||
?: error("missing account db_id=$dbId")
|
?: error("missing account db_id=$dbId")
|
||||||
client.account = sa
|
client.account = sa
|
||||||
}
|
}
|
||||||
|
@ -131,7 +128,7 @@ class MisskeyAuth13(override val client: TootApiClient) : AuthBase() {
|
||||||
.account(accountJson)
|
.account(accountJson)
|
||||||
?: error("can't parse user json.")
|
?: error("can't parse user json.")
|
||||||
|
|
||||||
prefDevice.edit().remove(PrefDevice.LAST_AUTH_SECRET).apply()
|
prefDevice.removeLastAuth()
|
||||||
|
|
||||||
return Auth2Result(
|
return Auth2Result(
|
||||||
tootInstance = ti,
|
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 mayNull(x: String?) = if (x == null) null else EntityId(x)
|
||||||
|
|
||||||
fun String.decode(): EntityId? {
|
fun String.decodeEntityId(): EntityId? {
|
||||||
if (this.isEmpty()) return null
|
if (this.isEmpty()) return null
|
||||||
// first character is 'L' for EntityIdLong, 'S' for EntityIdString.
|
// first character is 'L' for EntityIdLong, 'S' for EntityIdString.
|
||||||
// integer id is removed at https://source.joinmastodon.org/mastodon/docs/commit/e086d478afa140e7b0b9a60183655315966ad9ff
|
// 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) =
|
fun from(intent: Intent?, key: String) =
|
||||||
intent?.string(key)?.decode()
|
intent?.string(key)?.decodeEntityId()
|
||||||
|
|
||||||
fun from(bundle: Bundle?, key: String) =
|
fun from(bundle: Bundle?, key: String) =
|
||||||
bundle?.string(key)?.decode()
|
bundle?.string(key)?.decodeEntityId()
|
||||||
|
|
||||||
// 内部保存データのデコード用。APIレスポンスのパースに使ってはいけない
|
// 内部保存データのデコード用。APIレスポンスのパースに使ってはいけない
|
||||||
fun from(data: JsonObject?, key: String): EntityId? {
|
fun from(data: JsonObject?, key: String): EntityId? {
|
||||||
val o = data?.get(key)
|
val o = data?.get(key)
|
||||||
if (o is Long) return EntityId(o.toString())
|
if (o is Long) return EntityId(o.toString())
|
||||||
return (o as? String)?.decode()
|
return (o as? String)?.decodeEntityId()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun from(cursor: Cursor, key: String) =
|
fun from(cursor: Cursor, key: String) =
|
||||||
cursor.getStringOrNull(key)?.decode()
|
cursor.getStringOrNull(key)?.decodeEntityId()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun encode(): String {
|
// 昔は文字列とLong値を区別していたが、今はもうない
|
||||||
val prefix = 'S'
|
fun encode(): String = "S$this"
|
||||||
return "$prefix$this"
|
|
||||||
}
|
|
||||||
|
|
||||||
fun putTo(data: Intent, key: String): Intent = data.putExtra(key, encode())
|
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.pref.PrefB
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
import jp.juggler.subwaytooter.table.SavedAccount
|
||||||
import jp.juggler.subwaytooter.table.UserRelation
|
import jp.juggler.subwaytooter.table.UserRelation
|
||||||
|
import jp.juggler.subwaytooter.table.daoUserRelation
|
||||||
import jp.juggler.subwaytooter.util.DecodeOptions
|
import jp.juggler.subwaytooter.util.DecodeOptions
|
||||||
import jp.juggler.subwaytooter.util.LinkHelper
|
import jp.juggler.subwaytooter.util.LinkHelper
|
||||||
import jp.juggler.subwaytooter.util.NetworkEmojiInvalidator
|
import jp.juggler.subwaytooter.util.NetworkEmojiInvalidator
|
||||||
|
@ -215,7 +216,7 @@ open class TootAccount(parser: TootParser, src: JsonObject) : HostAndDomain {
|
||||||
|
|
||||||
this.fields = parseMisskeyFields(src)
|
this.fields = parseMisskeyFields(src)
|
||||||
|
|
||||||
UserRelation.fromAccount(parser, src, id)
|
daoUserRelation.fromAccount(parser, src, id)
|
||||||
|
|
||||||
@Suppress("LeakingThis")
|
@Suppress("LeakingThis")
|
||||||
MisskeyAccountDetailMap.fromAccount(parser, this, id)
|
MisskeyAccountDetailMap.fromAccount(parser, this, id)
|
||||||
|
@ -504,7 +505,7 @@ open class TootAccount(parser: TootParser, src: JsonObject) : HostAndDomain {
|
||||||
.append(suggestionSource)
|
.append(suggestionSource)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PrefB.bpDirectoryLastActive() && last_status_at > 0L) {
|
if (PrefB.bpDirectoryLastActive.value && last_status_at > 0L) {
|
||||||
prepareSb()
|
prepareSb()
|
||||||
.append(context.getString(R.string.last_active))
|
.append(context.getString(R.string.last_active))
|
||||||
.append(delm)
|
.append(delm)
|
||||||
|
@ -519,7 +520,7 @@ open class TootAccount(parser: TootParser, src: JsonObject) : HostAndDomain {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fromProfileHeader) {
|
if (!fromProfileHeader) {
|
||||||
if (PrefB.bpDirectoryTootCount() &&
|
if (PrefB.bpDirectoryTootCount.value &&
|
||||||
(statuses_count ?: 0L) > 0L
|
(statuses_count ?: 0L) > 0L
|
||||||
) {
|
) {
|
||||||
prepareSb()
|
prepareSb()
|
||||||
|
@ -528,8 +529,8 @@ open class TootAccount(parser: TootParser, src: JsonObject) : HostAndDomain {
|
||||||
.append(statuses_count.toString())
|
.append(statuses_count.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PrefB.bpDirectoryFollowers() &&
|
if (PrefB.bpDirectoryFollowers.value &&
|
||||||
!PrefB.bpHideFollowCount() &&
|
!PrefB.bpHideFollowCount.value &&
|
||||||
(followers_count ?: 0L) > 0L
|
(followers_count ?: 0L) > 0L
|
||||||
) {
|
) {
|
||||||
prepareSb()
|
prepareSb()
|
||||||
|
@ -538,7 +539,7 @@ open class TootAccount(parser: TootParser, src: JsonObject) : HostAndDomain {
|
||||||
.append(followers_count.toString())
|
.append(followers_count.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PrefB.bpDirectoryNote() && note?.isNotEmpty() == true) {
|
if (PrefB.bpDirectoryNote.value && note?.isNotEmpty() == true) {
|
||||||
val decodedNote = DecodeOptions(
|
val decodedNote = DecodeOptions(
|
||||||
context,
|
context,
|
||||||
accessInfo,
|
accessInfo,
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package jp.juggler.subwaytooter.api.entity
|
package jp.juggler.subwaytooter.api.entity
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import jp.juggler.subwaytooter.api.TootParser
|
import jp.juggler.subwaytooter.api.TootParser
|
||||||
import jp.juggler.subwaytooter.pref.PrefB
|
import jp.juggler.subwaytooter.pref.PrefB
|
||||||
import jp.juggler.util.*
|
import jp.juggler.util.*
|
||||||
|
@ -191,26 +190,26 @@ class TootAttachment : TootAttachmentLike {
|
||||||
private fun parseType(src: String?) =
|
private fun parseType(src: String?) =
|
||||||
TootAttachmentType.values().find { it.id == src }
|
TootAttachmentType.values().find { it.id == src }
|
||||||
|
|
||||||
override fun urlForThumbnail(pref: SharedPreferences) =
|
override fun urlForThumbnail() =
|
||||||
if (PrefB.bpPriorLocalURL(pref)) {
|
if (PrefB.bpPriorLocalURL.value) {
|
||||||
preview_url.notEmpty() ?: preview_remote_url.notEmpty()
|
preview_url.notEmpty() ?: preview_remote_url.notEmpty()
|
||||||
} else {
|
} else {
|
||||||
preview_remote_url.notEmpty() ?: preview_url.notEmpty()
|
preview_remote_url.notEmpty() ?: preview_url.notEmpty()
|
||||||
} ?: when (type) {
|
} ?: when (type) {
|
||||||
TootAttachmentType.Image -> getLargeUrl(pref)
|
TootAttachmentType.Image -> getLargeUrl()
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLargeUrl(pref: SharedPreferences) =
|
fun getLargeUrl() =
|
||||||
if (PrefB.bpPriorLocalURL(pref)) {
|
if (PrefB.bpPriorLocalURL.value) {
|
||||||
url.notEmpty() ?: remote_url
|
url.notEmpty() ?: remote_url
|
||||||
} else {
|
} else {
|
||||||
remote_url.notEmpty() ?: url
|
remote_url.notEmpty() ?: url
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLargeUrlList(pref: SharedPreferences) =
|
fun getLargeUrlList() =
|
||||||
ArrayList<String>().apply {
|
ArrayList<String>().apply {
|
||||||
if (PrefB.bpPriorLocalURL(pref)) {
|
if (PrefB.bpPriorLocalURL.value) {
|
||||||
url.notEmpty()?.addTo(this)
|
url.notEmpty()?.addTo(this)
|
||||||
remote_url.notEmpty()?.addTo(this)
|
remote_url.notEmpty()?.addTo(this)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package jp.juggler.subwaytooter.api.entity
|
package jp.juggler.subwaytooter.api.entity
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
|
|
||||||
enum class TootAttachmentType(val id: String) {
|
enum class TootAttachmentType(val id: String) {
|
||||||
Unknown("unknown"),
|
Unknown("unknown"),
|
||||||
Image("image"),
|
Image("image"),
|
||||||
|
@ -16,7 +14,7 @@ interface TootAttachmentLike {
|
||||||
val description: String?
|
val description: String?
|
||||||
|
|
||||||
// url for thumbnail, or null or empty
|
// url for thumbnail, or null or empty
|
||||||
fun urlForThumbnail(pref: SharedPreferences): String?
|
fun urlForThumbnail(): String?
|
||||||
|
|
||||||
// url for description, or null or empty
|
// url for description, or null or empty
|
||||||
val urlForDescription: String?
|
val urlForDescription: String?
|
||||||
|
|
|
@ -14,7 +14,7 @@ class TootAttachmentMSP(
|
||||||
override val description: String?
|
override val description: String?
|
||||||
get() = null
|
get() = null
|
||||||
|
|
||||||
override fun urlForThumbnail(pref: SharedPreferences) = preview_url
|
override fun urlForThumbnail() = preview_url
|
||||||
|
|
||||||
override val urlForDescription: String
|
override val urlForDescription: String
|
||||||
get() = preview_url
|
get() = preview_url
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package jp.juggler.subwaytooter.api.entity
|
package jp.juggler.subwaytooter.api.entity
|
||||||
|
|
||||||
import jp.juggler.subwaytooter.api.TootParser
|
import jp.juggler.subwaytooter.api.TootParser
|
||||||
import jp.juggler.subwaytooter.pref.pref
|
|
||||||
import jp.juggler.subwaytooter.util.DecodeOptions
|
import jp.juggler.subwaytooter.util.DecodeOptions
|
||||||
import jp.juggler.util.data.JsonObject
|
import jp.juggler.util.data.JsonObject
|
||||||
import jp.juggler.util.data.filterNotEmpty
|
import jp.juggler.util.data.filterNotEmpty
|
||||||
|
@ -61,7 +60,7 @@ class TootCard(
|
||||||
},
|
},
|
||||||
image = src.media_attachments
|
image = src.media_attachments
|
||||||
?.firstOrNull()
|
?.firstOrNull()
|
||||||
?.urlForThumbnail(parser.context.pref())
|
?.urlForThumbnail()
|
||||||
?: src.account.avatar_static,
|
?: src.account.avatar_static,
|
||||||
type = "photo"
|
type = "photo"
|
||||||
)
|
)
|
||||||
|
|
|
@ -310,8 +310,9 @@ class TootInstance(parser: TootParser, src: JsonObject) {
|
||||||
if (sendRequest(result) {
|
if (sendRequest(result) {
|
||||||
val builder = Request.Builder().url("https://${apiHost?.ascii}/api/v1/instance")
|
val builder = Request.Builder().url("https://${apiHost?.ascii}/api/v1/instance")
|
||||||
|
|
||||||
(forceAccessToken ?: account?.getAccessToken())
|
(forceAccessToken ?: account?.bearerAccessToken)?.notEmpty()?.let {
|
||||||
?.notEmpty()?.let { builder.header("Authorization", "Bearer $it") }
|
builder.header("Authorization", "Bearer $it")
|
||||||
|
}
|
||||||
builder.build()
|
builder.build()
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
@ -428,7 +429,7 @@ class TootInstance(parser: TootParser, src: JsonObject) {
|
||||||
|
|
||||||
when {
|
when {
|
||||||
qrr.first?.instanceType == InstanceType.Pixelfed &&
|
qrr.first?.instanceType == InstanceType.Pixelfed &&
|
||||||
!PrefB.bpEnablePixelfed() &&
|
!PrefB.bpEnablePixelfed.value &&
|
||||||
!req.allowPixelfed ->
|
!req.allowPixelfed ->
|
||||||
tiError("currently Pixelfed instance is not supported.")
|
tiError("currently Pixelfed instance is not supported.")
|
||||||
else -> qrr
|
else -> qrr
|
||||||
|
|
|
@ -9,7 +9,6 @@ import jp.juggler.subwaytooter.span.NetworkEmojiSpan
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
import jp.juggler.subwaytooter.table.SavedAccount
|
||||||
import jp.juggler.subwaytooter.util.DecodeOptions
|
import jp.juggler.subwaytooter.util.DecodeOptions
|
||||||
import jp.juggler.subwaytooter.util.EmojiDecoder
|
import jp.juggler.subwaytooter.util.EmojiDecoder
|
||||||
import jp.juggler.util.*
|
|
||||||
import jp.juggler.util.data.*
|
import jp.juggler.util.data.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
@ -162,7 +161,7 @@ class TootReaction(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun chooseUrl() = when {
|
private fun chooseUrl() = when {
|
||||||
PrefB.bpDisableEmojiAnimation() -> staticUrl
|
PrefB.bpDisableEmojiAnimation.value -> staticUrl
|
||||||
else -> url
|
else -> url
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ import android.content.Context
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import jp.juggler.subwaytooter.App1
|
|
||||||
import jp.juggler.subwaytooter.R
|
import jp.juggler.subwaytooter.R
|
||||||
import jp.juggler.subwaytooter.api.TootAccountMap
|
import jp.juggler.subwaytooter.api.TootAccountMap
|
||||||
import jp.juggler.subwaytooter.api.TootParser
|
import jp.juggler.subwaytooter.api.TootParser
|
||||||
|
@ -365,7 +364,7 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
|
||||||
|
|
||||||
this.mentions = mergeMentions(mentions1, mentions2)
|
this.mentions = mergeMentions(mentions1, mentions2)
|
||||||
this.decoded_mentions =
|
this.decoded_mentions =
|
||||||
HTMLDecoder.decodeMentions(parser.linkHelper, this)
|
HTMLDecoder.decodeMentions(parser, this)
|
||||||
?: EMPTY_SPANNABLE
|
?: EMPTY_SPANNABLE
|
||||||
|
|
||||||
// contentを読んだ後にアンケートのデコード
|
// contentを読んだ後にアンケートのデコード
|
||||||
|
@ -484,7 +483,7 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
|
||||||
this.muted = false
|
this.muted = false
|
||||||
this.language = null
|
this.language = null
|
||||||
this.decoded_mentions =
|
this.decoded_mentions =
|
||||||
HTMLDecoder.decodeMentions(parser.linkHelper, this)
|
HTMLDecoder.decodeMentions(parser, this)
|
||||||
?: EMPTY_SPANNABLE
|
?: EMPTY_SPANNABLE
|
||||||
|
|
||||||
val quote = when {
|
val quote = when {
|
||||||
|
@ -696,7 +695,7 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
|
||||||
this.muted = src.optBoolean("muted")
|
this.muted = src.optBoolean("muted")
|
||||||
this.language = src.string("language")?.notEmpty()
|
this.language = src.string("language")?.notEmpty()
|
||||||
this.decoded_mentions =
|
this.decoded_mentions =
|
||||||
HTMLDecoder.decodeMentions(parser.linkHelper, this)
|
HTMLDecoder.decodeMentions(parser, this)
|
||||||
?: EMPTY_SPANNABLE
|
?: EMPTY_SPANNABLE
|
||||||
|
|
||||||
val quote = when {
|
val quote = when {
|
||||||
|
@ -1100,7 +1099,7 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
|
||||||
|
|
||||||
fun markDeleted(context: Context, deletedAt: Long?): Boolean {
|
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) {
|
var sv = if (deletedAt != null) {
|
||||||
context.getString(R.string.status_deleted_at, formatTime(context, deletedAt, false))
|
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")
|
internal val log = LogCategory("TootStatus")
|
||||||
|
|
||||||
@Volatile
|
@Volatile
|
||||||
internal var muted_app: HashSet<String>? = null
|
internal var muted_app: Set<String>? = null
|
||||||
|
|
||||||
@Volatile
|
@Volatile
|
||||||
internal var muted_word: WordTrieTree? = null
|
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)
|
formatDate(t, date_format2, omitZeroSecond = false, omitYear = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bAllowRelative && PrefB.bpRelativeTimestamp()) {
|
if (bAllowRelative && PrefB.bpRelativeTimestamp.value) {
|
||||||
|
|
||||||
delta = abs(delta)
|
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.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.database.sqlite.SQLiteDatabase
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.provider.BaseColumns
|
import android.provider.BaseColumns
|
||||||
import android.util.JsonReader
|
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.Column
|
||||||
import jp.juggler.subwaytooter.column.ColumnEncoder
|
import jp.juggler.subwaytooter.column.ColumnEncoder
|
||||||
import jp.juggler.subwaytooter.column.getBackgroundImageDir
|
import jp.juggler.subwaytooter.column.getBackgroundImageDir
|
||||||
import jp.juggler.subwaytooter.global.appDatabase
|
|
||||||
import jp.juggler.subwaytooter.pref.PrefL
|
import jp.juggler.subwaytooter.pref.PrefL
|
||||||
import jp.juggler.subwaytooter.pref.impl.*
|
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.subwaytooter.table.*
|
||||||
import jp.juggler.util.*
|
import jp.juggler.util.*
|
||||||
import jp.juggler.util.data.*
|
import jp.juggler.util.data.*
|
||||||
|
@ -117,50 +115,49 @@ object AppDataExporter {
|
||||||
writer.name(jsonKey)
|
writer.name(jsonKey)
|
||||||
writer.beginArray()
|
writer.beginArray()
|
||||||
|
|
||||||
appDatabase.query(table, null, null, null, null, null, null)
|
appDatabase.rawQuery("select from $table", emptyArray()).use { cursor ->
|
||||||
?.use { cursor ->
|
val names = ArrayList<String>()
|
||||||
val names = ArrayList<String>()
|
val column_count = cursor.columnCount
|
||||||
val column_count = cursor.columnCount
|
for (i in 0 until column_count) {
|
||||||
for (i in 0 until column_count) {
|
names.add(cursor.getColumnName(i))
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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()
|
writer.endArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,19 +195,21 @@ object AppDataExporter {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SavedAccount.table == table) {
|
if (SavedAccount.table == table) {
|
||||||
// 一時的に存在したが現在のDBスキーマにはない項目は読み飛ばす
|
when (name) {
|
||||||
if ("nickname" == name || "color" == name) {
|
// 一時的に存在したが現在のDBスキーマにはない項目は読み飛ばす
|
||||||
reader.skipValue()
|
"nickname",
|
||||||
continue
|
"color",
|
||||||
}
|
"notification_server",
|
||||||
|
"register_key",
|
||||||
// リアルタイム通知に関連する項目は読み飛ばす
|
"register_time",
|
||||||
if (SavedAccount.COL_NOTIFICATION_TAG.name == name ||
|
"last_notification_error",
|
||||||
SavedAccount.COL_REGISTER_KEY.name == name ||
|
"last_subscription_error",
|
||||||
SavedAccount.COL_REGISTER_TIME.name == name
|
"last_push_endpoint",
|
||||||
) {
|
-> {
|
||||||
reader.skipValue()
|
reader.skipValue()
|
||||||
continue
|
continue
|
||||||
|
}
|
||||||
|
else -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,8 +229,7 @@ object AppDataExporter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reader.endObject()
|
reader.endObject()
|
||||||
val new_id =
|
val new_id = db.replace(table, null, cv)
|
||||||
db.insertWithOnConflict(table, null, cv, SQLiteDatabase.CONFLICT_REPLACE)
|
|
||||||
if (new_id == -1L) error("importTable: invalid row_id")
|
if (new_id == -1L) error("importTable: invalid row_id")
|
||||||
idMap?.put(old_id, new_id)
|
idMap?.put(old_id, new_id)
|
||||||
}
|
}
|
||||||
|
@ -359,7 +357,7 @@ object AppDataExporter {
|
||||||
|
|
||||||
val app_state = App1.getAppState(context)
|
val app_state = App1.getAppState(context)
|
||||||
|
|
||||||
writePref(writer, app_state.pref)
|
writePref(writer, lazyPref)
|
||||||
|
|
||||||
writeFromTable(writer, KEY_ACCOUNT, SavedAccount.table)
|
writeFromTable(writer, KEY_ACCOUNT, SavedAccount.table)
|
||||||
writeFromTable(writer, KEY_ACCT_COLOR, AcctColor.table)
|
writeFromTable(writer, KEY_ACCT_COLOR, AcctColor.table)
|
||||||
|
@ -386,12 +384,12 @@ object AppDataExporter {
|
||||||
|
|
||||||
while (reader.hasNext()) {
|
while (reader.hasNext()) {
|
||||||
when (reader.nextName()) {
|
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_ACCOUNT -> importTable(reader, SavedAccount.table, account_id_map)
|
||||||
|
|
||||||
KEY_ACCT_COLOR -> {
|
KEY_ACCT_COLOR -> {
|
||||||
importTable(reader, AcctColor.table, null)
|
importTable(reader, AcctColor.table, null)
|
||||||
AcctColor.clearMemoryCache()
|
daoAcctColor.clearMemoryCache()
|
||||||
}
|
}
|
||||||
|
|
||||||
KEY_MUTED_APP -> importTable(reader, MutedApp.table, null)
|
KEY_MUTED_APP -> importTable(reader, MutedApp.table, null)
|
||||||
|
@ -408,10 +406,10 @@ object AppDataExporter {
|
||||||
}
|
}
|
||||||
|
|
||||||
run {
|
run {
|
||||||
val old_id = PrefL.lpTabletTootDefaultAccount(app_state.pref)
|
val old_id = PrefL.lpTabletTootDefaultAccount.value
|
||||||
if (old_id != -1L) {
|
if (old_id != -1L) {
|
||||||
val new_id = account_id_map[old_id]
|
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.annotation.StringRes
|
||||||
import androidx.appcompat.widget.AppCompatImageView
|
import androidx.appcompat.widget.AppCompatImageView
|
||||||
import jp.juggler.subwaytooter.*
|
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.drawable.MediaBackgroundDrawable
|
||||||
import jp.juggler.subwaytooter.itemviewholder.AdditionalButtonsPosition
|
import jp.juggler.subwaytooter.itemviewholder.AdditionalButtonsPosition
|
||||||
import jp.juggler.subwaytooter.pref.*
|
import jp.juggler.subwaytooter.pref.*
|
||||||
import jp.juggler.subwaytooter.pref.impl.*
|
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.CustomShareTarget
|
||||||
import jp.juggler.subwaytooter.util.openBrowser
|
import jp.juggler.subwaytooter.util.openBrowser
|
||||||
|
import jp.juggler.util.coroutine.launchAndShowError
|
||||||
import jp.juggler.util.data.cast
|
import jp.juggler.util.data.cast
|
||||||
import jp.juggler.util.data.intentOpenDocument
|
import jp.juggler.util.data.intentOpenDocument
|
||||||
import jp.juggler.util.data.notZero
|
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.attrColor
|
||||||
import jp.juggler.util.ui.getAdaptiveRippleDrawable
|
import jp.juggler.util.ui.getAdaptiveRippleDrawable
|
||||||
import jp.juggler.util.ui.getAdaptiveRippleDrawableRound
|
import jp.juggler.util.ui.getAdaptiveRippleDrawableRound
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import org.jetbrains.anko.backgroundDrawable
|
import org.jetbrains.anko.backgroundDrawable
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
|
@ -227,6 +233,10 @@ class AppSettingItem(
|
||||||
val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_setting).apply {
|
val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_setting).apply {
|
||||||
|
|
||||||
section(R.string.notifications) {
|
section(R.string.notifications) {
|
||||||
|
action(R.string.push_distributor) {
|
||||||
|
action = { selectPushDistributor() }
|
||||||
|
desc = R.string.push_distributor_desc
|
||||||
|
}
|
||||||
|
|
||||||
text(
|
text(
|
||||||
PrefS.spPullNotificationCheckInterval,
|
PrefS.spPullNotificationCheckInterval,
|
||||||
|
@ -447,14 +457,18 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
|
||||||
) {
|
) {
|
||||||
val lp = pref.cast<LongPref>()!!
|
val lp = pref.cast<LongPref>()!!
|
||||||
spinnerInitializer = { spinner ->
|
spinnerInitializer = { spinner ->
|
||||||
val adapter = AccountAdapter()
|
launchAndShowError {
|
||||||
spinner.adapter = adapter
|
val list = daoSavedAccount.loadAccountList()
|
||||||
spinner.setSelection(adapter.getIndexFromId(lp(pref)))
|
.sortedByNickname()
|
||||||
|
val adapter = AccountAdapter(list)
|
||||||
|
spinner.adapter = adapter
|
||||||
|
spinner.setSelection(adapter.getIndexFromId(lp.value))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
spinnerOnSelected = { spinner, index ->
|
spinnerOnSelected = { spinner, index ->
|
||||||
val adapter = spinner.adapter.cast<ActAppSetting.AccountAdapter>()
|
spinner.adapter.cast<ActAppSetting.AccountAdapter>()
|
||||||
?: error("spinnerOnSelected: missing AccountAdapter")
|
?.getIdFromIndex(index)
|
||||||
pref.edit().put(lp, adapter.getIdFromIndex(index)).apply()
|
?.let { lp.value = it }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -522,12 +536,12 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
|
||||||
spinnerInitializer = { spinner ->
|
spinnerInitializer = { spinner ->
|
||||||
val adapter = TimeZoneAdapter()
|
val adapter = TimeZoneAdapter()
|
||||||
spinner.adapter = adapter
|
spinner.adapter = adapter
|
||||||
spinner.setSelection(adapter.getIndexFromId(sp(pref)))
|
spinner.setSelection(adapter.getIndexFromId(sp.value))
|
||||||
}
|
}
|
||||||
spinnerOnSelected = { spinner, index ->
|
spinnerOnSelected = { spinner, index ->
|
||||||
val adapter = spinner.adapter.cast<ActAppSetting.TimeZoneAdapter>()
|
val adapter = spinner.adapter.cast<ActAppSetting.TimeZoneAdapter>()
|
||||||
?: error("spinnerOnSelected: missing 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 = {
|
onClickReset = {
|
||||||
pref.edit().remove(item.pref?.key).apply()
|
item.pref?.removeValue()
|
||||||
showTimelineFont(item)
|
showTimelineFont(item)
|
||||||
}
|
}
|
||||||
showTextView = { showTimelineFont(item, it) }
|
showTextView = { showTimelineFont(item, it) }
|
||||||
|
@ -602,7 +616,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onClickReset = {
|
onClickReset = {
|
||||||
pref.edit().remove(item.pref?.key).apply()
|
item.pref?.removeValue()
|
||||||
showTimelineFont(AppSettingItem.TIMELINE_FONT_BOLD)
|
showTimelineFont(AppSettingItem.TIMELINE_FONT_BOLD)
|
||||||
}
|
}
|
||||||
showTextView = { showTimelineFont(item, it) }
|
showTextView = { showTimelineFont(item, it) }
|
||||||
|
@ -621,7 +635,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
|
||||||
fromFloat = { formatFontSize(it) }
|
fromFloat = { formatFontSize(it) }
|
||||||
|
|
||||||
captionFontSize = {
|
captionFontSize = {
|
||||||
val fv = fp(pref)
|
val fv = fp.value
|
||||||
when {
|
when {
|
||||||
!fv.isFinite() -> PrefF.default_timeline_font_size
|
!fv.isFinite() -> PrefF.default_timeline_font_size
|
||||||
fv < 1f -> 1f
|
fv < 1f -> 1f
|
||||||
|
@ -629,7 +643,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
captionSpacing = {
|
captionSpacing = {
|
||||||
PrefS.spTimelineSpacing(pref).toFloatOrNull()
|
PrefS.spTimelineSpacing.value.toFloatOrNull()
|
||||||
}
|
}
|
||||||
changed = {
|
changed = {
|
||||||
findItemViewHolder(item)?.updateCaption()
|
findItemViewHolder(item)?.updateCaption()
|
||||||
|
@ -644,7 +658,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
|
||||||
fromFloat = { formatFontSize(it) }
|
fromFloat = { formatFontSize(it) }
|
||||||
|
|
||||||
captionFontSize = {
|
captionFontSize = {
|
||||||
val fv = fp(pref)
|
val fv = fp.value
|
||||||
when {
|
when {
|
||||||
!fv.isFinite() -> PrefF.default_acct_font_size
|
!fv.isFinite() -> PrefF.default_acct_font_size
|
||||||
fv < 1f -> 1f
|
fv < 1f -> 1f
|
||||||
|
@ -667,7 +681,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
|
||||||
fromFloat = { formatFontSize(it) }
|
fromFloat = { formatFontSize(it) }
|
||||||
|
|
||||||
captionFontSize = {
|
captionFontSize = {
|
||||||
val fv = fp(pref)
|
val fv = fp.value
|
||||||
when {
|
when {
|
||||||
!fv.isFinite() -> PrefF.default_notification_tl_font_size
|
!fv.isFinite() -> PrefF.default_notification_tl_font_size
|
||||||
fv < 1f -> 1f
|
fv < 1f -> 1f
|
||||||
|
@ -675,7 +689,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
captionSpacing = {
|
captionSpacing = {
|
||||||
PrefS.spTimelineSpacing(pref).toFloatOrNull()
|
PrefS.spTimelineSpacing.value.toFloatOrNull()
|
||||||
}
|
}
|
||||||
changed = {
|
changed = {
|
||||||
findItemViewHolder(item)?.updateCaption()
|
findItemViewHolder(item)?.updateCaption()
|
||||||
|
@ -718,7 +732,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
|
||||||
fromFloat = { formatFontSize(it) }
|
fromFloat = { formatFontSize(it) }
|
||||||
|
|
||||||
captionFontSize = {
|
captionFontSize = {
|
||||||
val fv = fp(pref)
|
val fv = fp.value
|
||||||
when {
|
when {
|
||||||
!fv.isFinite() -> PrefF.default_header_font_size
|
!fv.isFinite() -> PrefF.default_header_font_size
|
||||||
fv < 1f -> 1f
|
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 ivColumnHeader: ImageView = viewRoot.findViewById(R.id.ivColumnHeader)
|
||||||
val tvColumnName: TextView = viewRoot.findViewById(R.id.tvColumnName)
|
val tvColumnName: TextView = viewRoot.findViewById(R.id.tvColumnName)
|
||||||
|
|
||||||
val colorColumnHeaderBg = PrefI.ipCcdHeaderBg(activity.pref)
|
val colorColumnHeaderBg = PrefI.ipCcdHeaderBg.value
|
||||||
val colorColumnHeaderFg = PrefI.ipCcdHeaderFg(activity.pref)
|
val colorColumnHeaderFg = PrefI.ipCcdHeaderFg.value
|
||||||
|
|
||||||
val headerBg = when {
|
val headerBg = when {
|
||||||
colorColumnHeaderBg != 0 -> colorColumnHeaderBg
|
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 tvSampleAcct: TextView = viewRoot.findViewById(R.id.tvSampleAcct)
|
||||||
val tvSampleContent: TextView = viewRoot.findViewById(R.id.tvSampleContent)
|
val tvSampleContent: TextView = viewRoot.findViewById(R.id.tvSampleContent)
|
||||||
|
|
||||||
val colorColumnBg = PrefI.ipCcdContentBg(activity.pref)
|
val colorColumnBg = PrefI.ipCcdContentBg.value
|
||||||
val colorColumnAcct = PrefI.ipCcdContentAcct(activity.pref)
|
val colorColumnAcct = PrefI.ipCcdContentAcct.value
|
||||||
val colorColumnText = PrefI.ipCcdContentText(activity.pref)
|
val colorColumnText = PrefI.ipCcdContentText.value
|
||||||
|
|
||||||
flColumnBackground.setBackgroundColor(colorColumnBg) // may 0
|
flColumnBackground.setBackgroundColor(colorColumnBg) // may 0
|
||||||
|
|
||||||
|
@ -909,7 +923,6 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
|
||||||
group(R.string.footer_color) {
|
group(R.string.footer_color) {
|
||||||
AppSettingItem.SAMPLE_FOOTER =
|
AppSettingItem.SAMPLE_FOOTER =
|
||||||
sample(R.layout.setting_sample_footer) { activity, viewRoot ->
|
sample(R.layout.setting_sample_footer) { activity, viewRoot ->
|
||||||
val pref = activity.pref
|
|
||||||
val ivFooterToot: AppCompatImageView = viewRoot.findViewById(R.id.ivFooterToot)
|
val ivFooterToot: AppCompatImageView = viewRoot.findViewById(R.id.ivFooterToot)
|
||||||
val ivFooterMenu: AppCompatImageView = viewRoot.findViewById(R.id.ivFooterMenu)
|
val ivFooterMenu: AppCompatImageView = viewRoot.findViewById(R.id.ivFooterMenu)
|
||||||
val llFooterBG: View = viewRoot.findViewById(R.id.llFooterBG)
|
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 vFooterDivider2: View = viewRoot.findViewById(R.id.vFooterDivider2)
|
||||||
val vIndicator: View = viewRoot.findViewById(R.id.vIndicator)
|
val vIndicator: View = viewRoot.findViewById(R.id.vIndicator)
|
||||||
|
|
||||||
val footerButtonBgColor = PrefI.ipFooterButtonBgColor(pref)
|
val footerButtonBgColor = PrefI.ipFooterButtonBgColor.value
|
||||||
val footerButtonFgColor = PrefI.ipFooterButtonFgColor(pref)
|
val footerButtonFgColor = PrefI.ipFooterButtonFgColor.value
|
||||||
val footerTabBgColor = PrefI.ipFooterTabBgColor(pref)
|
val footerTabBgColor = PrefI.ipFooterTabBgColor.value
|
||||||
val footerTabDividerColor = PrefI.ipFooterTabDividerColor(pref)
|
val footerTabDividerColor = PrefI.ipFooterTabDividerColor.value
|
||||||
val footerTabIndicatorColor = PrefI.ipFooterTabIndicatorColor(pref)
|
val footerTabIndicatorColor = PrefI.ipFooterTabIndicatorColor.value
|
||||||
|
|
||||||
val colorColumnStripBackground = footerTabBgColor.notZero()
|
val colorColumnStripBackground = footerTabBgColor.notZero()
|
||||||
?: activity.attrColor(R.attr.colorColumnStripBackground)
|
?: 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) {
|
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
|
package jp.juggler.subwaytooter.column
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.util.SparseArray
|
import android.util.SparseArray
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import jp.juggler.subwaytooter.AppState
|
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.StreamCallback
|
||||||
import jp.juggler.subwaytooter.streaming.StreamStatus
|
import jp.juggler.subwaytooter.streaming.StreamStatus
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
import jp.juggler.subwaytooter.table.SavedAccount
|
||||||
|
import jp.juggler.subwaytooter.table.daoSavedAccount
|
||||||
import jp.juggler.subwaytooter.util.BucketList
|
import jp.juggler.subwaytooter.util.BucketList
|
||||||
import jp.juggler.subwaytooter.util.ScrollPosition
|
import jp.juggler.subwaytooter.util.ScrollPosition
|
||||||
import jp.juggler.util.data.*
|
import jp.juggler.util.data.*
|
||||||
|
@ -64,10 +64,10 @@ class Column(
|
||||||
internal const val QUICK_FILTER_VOTE = 6
|
internal const val QUICK_FILTER_VOTE = 6
|
||||||
internal const val QUICK_FILTER_POST = 7
|
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
|
val account_db_id = src.long(ColumnEncoder.KEY_ACCOUNT_ROW_ID) ?: -1L
|
||||||
return if (account_db_id >= 0) {
|
return if (account_db_id > 0) {
|
||||||
SavedAccount.loadAccount(context, account_db_id)
|
daoSavedAccount.loadAccount(account_db_id)
|
||||||
?: error("missing account")
|
?: error("missing account")
|
||||||
} else {
|
} else {
|
||||||
SavedAccount.na
|
SavedAccount.na
|
||||||
|
@ -91,24 +91,24 @@ class Column(
|
||||||
var defaultColorContentAcct = 0
|
var defaultColorContentAcct = 0
|
||||||
var defaultColorContentText = 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)
|
?: activity.attrColor(R.attr.color_column_header)
|
||||||
|
|
||||||
defaultColorHeaderName = PrefI.ipCcdHeaderFg(pref).notZero()
|
defaultColorHeaderName = PrefI.ipCcdHeaderFg.value.notZero()
|
||||||
?: activity.attrColor(R.attr.colorColumnHeaderName)
|
?: activity.attrColor(R.attr.colorColumnHeaderName)
|
||||||
|
|
||||||
defaultColorHeaderPageNumber = PrefI.ipCcdHeaderFg(pref).notZero()
|
defaultColorHeaderPageNumber = PrefI.ipCcdHeaderFg.value.notZero()
|
||||||
?: activity.attrColor(R.attr.colorColumnHeaderPageNumber)
|
?: activity.attrColor(R.attr.colorColumnHeaderPageNumber)
|
||||||
|
|
||||||
defaultColorContentBg = PrefI.ipCcdContentBg(pref)
|
defaultColorContentBg = PrefI.ipCcdContentBg.value
|
||||||
// may zero
|
// may zero
|
||||||
|
|
||||||
defaultColorContentAcct = PrefI.ipCcdContentAcct(pref).notZero()
|
defaultColorContentAcct = PrefI.ipCcdContentAcct.value.notZero()
|
||||||
?: activity.attrColor(R.attr.colorTimeSmall)
|
?: activity.attrColor(R.attr.colorTimeSmall)
|
||||||
|
|
||||||
defaultColorContentText = PrefI.ipCcdContentText(pref).notZero()
|
defaultColorContentText = PrefI.ipCcdContentText.value.notZero()
|
||||||
?: activity.attrColor(R.attr.colorTextContent)
|
?: activity.attrColor(R.attr.colorTextContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,7 +257,7 @@ class Column(
|
||||||
var keywordFilterTrees: FilterTrees? = null
|
var keywordFilterTrees: FilterTrees? = null
|
||||||
|
|
||||||
@Volatile
|
@Volatile
|
||||||
var favMuteSet: HashSet<Acct>? = null
|
var favMuteSet: Set<Acct>? = null
|
||||||
|
|
||||||
@Volatile
|
@Volatile
|
||||||
var highlightTrie: WordTrieTree? = null
|
var highlightTrie: WordTrieTree? = null
|
||||||
|
@ -341,7 +341,7 @@ class Column(
|
||||||
internal constructor(appState: AppState, src: JsonObject) : this(
|
internal constructor(appState: AppState, src: JsonObject) : this(
|
||||||
appState,
|
appState,
|
||||||
appState.context,
|
appState.context,
|
||||||
loadAccount(appState.context, src),
|
loadAccount(src),
|
||||||
src.optInt(ColumnEncoder.KEY_TYPE),
|
src.optInt(ColumnEncoder.KEY_TYPE),
|
||||||
ColumnEncoder.decodeColumnId(src)
|
ColumnEncoder.decodeColumnId(src)
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -192,7 +192,7 @@ fun Column.removeNotifications() {
|
||||||
listData.clear()
|
listData.clear()
|
||||||
duplicateMap.clear()
|
duplicateMap.clear()
|
||||||
fireShowContent(reason = "removeNotifications", reset = true)
|
fireShowContent(reason = "removeNotifications", reset = true)
|
||||||
onNotificationCleared(context, accessInfo.db_id)
|
onNotificationCleared(accessInfo.db_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 通知を削除した後に呼ばれる
|
// 通知を削除した後に呼ばれる
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package jp.juggler.subwaytooter.column
|
package jp.juggler.subwaytooter.column
|
||||||
|
|
||||||
import jp.juggler.subwaytooter.api.entity.EntityId
|
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.JsonException
|
||||||
import jp.juggler.util.data.JsonObject
|
import jp.juggler.util.data.JsonObject
|
||||||
import jp.juggler.util.data.encodeBase64Url
|
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_ACCT] = accessInfo.acct.ascii
|
||||||
dst[KEY_COLUMN_ACCESS_STR] = ac.nickname
|
dst[KEY_COLUMN_ACCESS_STR] = ac.nickname
|
||||||
dst[KEY_COLUMN_ACCESS_COLOR] = ac.color_fg
|
dst[KEY_COLUMN_ACCESS_COLOR] = ac.colorFg
|
||||||
dst[KEY_COLUMN_ACCESS_COLOR_BG] = ac.color_bg
|
dst[KEY_COLUMN_ACCESS_COLOR_BG] = ac.colorBg
|
||||||
dst[KEY_COLUMN_NAME] = getColumnName(true)
|
dst[KEY_COLUMN_NAME] = getColumnName(true)
|
||||||
dst[KEY_OLD_INDEX] = oldIndex
|
dst[KEY_OLD_INDEX] = oldIndex
|
||||||
}
|
}
|
||||||
|
|
|
@ -197,7 +197,7 @@ fun Column.onActivityStart() {
|
||||||
|
|
||||||
if (!bRefreshLoading &&
|
if (!bRefreshLoading &&
|
||||||
canAutoRefresh() &&
|
canAutoRefresh() &&
|
||||||
!PrefB.bpDontRefreshOnResume(appState.pref) &&
|
!PrefB.bpDontRefreshOnResume.value &&
|
||||||
!dontAutoRefresh
|
!dontAutoRefresh
|
||||||
) {
|
) {
|
||||||
// リフレッシュしてからストリーミング開始
|
// リフレッシュしてからストリーミング開始
|
||||||
|
@ -237,7 +237,7 @@ fun Column.startLoading() {
|
||||||
|
|
||||||
initFilter()
|
initFilter()
|
||||||
|
|
||||||
Column.showOpenSticker = PrefB.bpOpenSticker(appState.pref)
|
Column.showOpenSticker = PrefB.bpOpenSticker.value
|
||||||
|
|
||||||
mRefreshLoadingErrorPopupState = 0
|
mRefreshLoadingErrorPopupState = 0
|
||||||
mRefreshLoadingError = ""
|
mRefreshLoadingError = ""
|
||||||
|
|
|
@ -7,10 +7,7 @@ import jp.juggler.subwaytooter.api.ApiTask
|
||||||
import jp.juggler.subwaytooter.api.TootApiClient
|
import jp.juggler.subwaytooter.api.TootApiClient
|
||||||
import jp.juggler.subwaytooter.api.entity.*
|
import jp.juggler.subwaytooter.api.entity.*
|
||||||
import jp.juggler.subwaytooter.api.runApiTask
|
import jp.juggler.subwaytooter.api.runApiTask
|
||||||
import jp.juggler.subwaytooter.table.FavMute
|
import jp.juggler.subwaytooter.table.*
|
||||||
import jp.juggler.subwaytooter.table.HighlightWord
|
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
|
||||||
import jp.juggler.subwaytooter.table.UserRelation
|
|
||||||
import jp.juggler.util.*
|
import jp.juggler.util.*
|
||||||
import jp.juggler.util.coroutine.launchMain
|
import jp.juggler.util.coroutine.launchMain
|
||||||
import jp.juggler.util.data.WordTrieTree
|
import jp.juggler.util.data.WordTrieTree
|
||||||
|
@ -171,8 +168,8 @@ fun Column.initFilter() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
favMuteSet = FavMute.acctSet
|
favMuteSet = daoFavMute.acctSet()
|
||||||
highlightTrie = HighlightWord.nameSet
|
highlightTrie = daoHighlightWord.nameSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Column.isFilteredByAttachment(status: TootStatus): Boolean {
|
private fun Column.isFilteredByAttachment(status: TootStatus): Boolean {
|
||||||
|
@ -252,10 +249,10 @@ fun Column.isFiltered(status: TootStatus): Boolean {
|
||||||
if (checkLanguageFilter(status)) return true
|
if (checkLanguageFilter(status)) return true
|
||||||
|
|
||||||
if (accessInfo.isPseudo) {
|
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 (r.muting || r.blocking) return true
|
||||||
if (reblog != null) {
|
if (reblog != null) {
|
||||||
r = UserRelation.loadPseudo(accessInfo.getFullAcct(reblog.account))
|
r = daoUserRelation.loadPseudo(accessInfo.getFullAcct(reblog.account))
|
||||||
if (r.muting || r.blocking) return true
|
if (r.muting || r.blocking) return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -167,8 +167,8 @@ fun Column.mergeStreamingMessage() {
|
||||||
App1.sound(it)
|
App1.sound(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
o.highlightSpeech?.let {
|
o.highlightSpeech?.name?.notEmpty()?.let {
|
||||||
appState.addSpeech(it.name, dedupMode = DedupMode.RecentExpire)
|
appState.addSpeech(it, dedupMode = DedupMode.RecentExpire)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package jp.juggler.subwaytooter.column
|
package jp.juggler.subwaytooter.column
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import jp.juggler.subwaytooter.api.ApiPath
|
import jp.juggler.subwaytooter.api.ApiPath
|
||||||
import jp.juggler.subwaytooter.api.TootApiClient
|
import jp.juggler.subwaytooter.api.TootApiClient
|
||||||
|
@ -72,9 +71,6 @@ abstract class ColumnTask(
|
||||||
val misskeyVersion: Int
|
val misskeyVersion: Int
|
||||||
get() = accessInfo.misskeyVersion
|
get() = accessInfo.misskeyVersion
|
||||||
|
|
||||||
val pref: SharedPreferences
|
|
||||||
get() = column.appState.pref
|
|
||||||
|
|
||||||
internal fun JsonObject.addMisskeyNotificationFilter() = addMisskeyNotificationFilter(column)
|
internal fun JsonObject.addMisskeyNotificationFilter() = addMisskeyNotificationFilter(column)
|
||||||
internal fun JsonObject.addRangeMisskey(bBottom: Boolean) = addRangeMisskey(column, bBottom)
|
internal fun JsonObject.addRangeMisskey(bBottom: Boolean) = addRangeMisskey(column, bBottom)
|
||||||
|
|
||||||
|
|
|
@ -128,7 +128,7 @@ class ColumnTask_Gap(
|
||||||
val iv = when {
|
val iv = when {
|
||||||
isHead -> PrefI.ipGapHeadScrollPosition
|
isHead -> PrefI.ipGapHeadScrollPosition
|
||||||
else -> PrefI.ipGapTailScrollPosition
|
else -> PrefI.ipGapTailScrollPosition
|
||||||
}.invoke(pref)
|
}.value
|
||||||
val scrollHead = iv == PrefI.GSP_HEAD
|
val scrollHead = iv == PrefI.GSP_HEAD
|
||||||
|
|
||||||
if (scrollHead) {
|
if (scrollHead) {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import jp.juggler.subwaytooter.R
|
||||||
import jp.juggler.subwaytooter.api.*
|
import jp.juggler.subwaytooter.api.*
|
||||||
import jp.juggler.subwaytooter.api.entity.*
|
import jp.juggler.subwaytooter.api.entity.*
|
||||||
import jp.juggler.subwaytooter.api.finder.*
|
import jp.juggler.subwaytooter.api.finder.*
|
||||||
|
import jp.juggler.subwaytooter.auth.authRepo
|
||||||
import jp.juggler.subwaytooter.columnviewholder.scrollToTop
|
import jp.juggler.subwaytooter.columnviewholder.scrollToTop
|
||||||
import jp.juggler.subwaytooter.notification.injectData
|
import jp.juggler.subwaytooter.notification.injectData
|
||||||
import jp.juggler.subwaytooter.pref.PrefB
|
import jp.juggler.subwaytooter.pref.PrefB
|
||||||
|
@ -33,7 +34,7 @@ class ColumnTask_Loading(
|
||||||
override suspend fun background(): TootApiResult? {
|
override suspend fun background(): TootApiResult? {
|
||||||
ctStarted.set(true)
|
ctStarted.set(true)
|
||||||
|
|
||||||
if (PrefB.bpOpenSticker(pref)) {
|
if (PrefB.bpOpenSticker.value) {
|
||||||
OpenSticker.loadAndWait()
|
OpenSticker.loadAndWait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +53,7 @@ class ColumnTask_Loading(
|
||||||
client.account = accessInfo
|
client.account = accessInfo
|
||||||
|
|
||||||
try {
|
try {
|
||||||
accessInfo.checkConfirmed(context, client)
|
context.authRepo.checkConfirmed(accessInfo, client)
|
||||||
|
|
||||||
column.keywordFilterTrees = column.encodeFilterTree(column.loadFilter2(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.coroutine.runOnMainLooperDelayed
|
||||||
import jp.juggler.util.data.JsonArray
|
import jp.juggler.util.data.JsonArray
|
||||||
import jp.juggler.util.data.JsonObject
|
import jp.juggler.util.data.JsonObject
|
||||||
|
import jp.juggler.util.data.notEmpty
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
import jp.juggler.util.log.withCaption
|
import jp.juggler.util.log.withCaption
|
||||||
import jp.juggler.util.network.toPostRequestBuilder
|
import jp.juggler.util.network.toPostRequestBuilder
|
||||||
|
@ -157,8 +158,8 @@ class ColumnTask_Refresh(
|
||||||
App1.sound(it)
|
App1.sound(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
o.highlightSpeech?.let {
|
o.highlightSpeech?.name.notEmpty()?.let {
|
||||||
column.appState.addSpeech(it.name, dedupMode = DedupMode.RecentExpire)
|
column.appState.addSpeech(it, dedupMode = DedupMode.RecentExpire)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -306,7 +307,7 @@ class ColumnTask_Refresh(
|
||||||
isCancelled -> false
|
isCancelled -> false
|
||||||
listTmp?.isNotEmpty() != true -> false
|
listTmp?.isNotEmpty() != true -> false
|
||||||
willAddGap -> true
|
willAddGap -> true
|
||||||
else -> PrefB.bpForceGap()
|
else -> PrefB.bpForceGap.value
|
||||||
}
|
}
|
||||||
|
|
||||||
if (doesAddGap()) {
|
if (doesAddGap()) {
|
||||||
|
@ -495,7 +496,7 @@ class ColumnTask_Refresh(
|
||||||
|
|
||||||
if (!isCancelled &&
|
if (!isCancelled &&
|
||||||
listTmp?.isNotEmpty() == true &&
|
listTmp?.isNotEmpty() == true &&
|
||||||
(willAddGap || PrefB.bpForceGap(context))
|
(willAddGap || PrefB.bpForceGap.value)
|
||||||
) {
|
) {
|
||||||
addOne(listTmp, TootGap.mayNull(maxId, lastSinceId), head = addToHead)
|
addOne(listTmp, TootGap.mayNull(maxId, lastSinceId), head = addToHead)
|
||||||
}
|
}
|
||||||
|
@ -582,7 +583,7 @@ class ColumnTask_Refresh(
|
||||||
|
|
||||||
if (!isCancelled &&
|
if (!isCancelled &&
|
||||||
listTmp?.isNotEmpty() == true &&
|
listTmp?.isNotEmpty() == true &&
|
||||||
(willAddGap || PrefB.bpForceGap(context))
|
(willAddGap || PrefB.bpForceGap.value)
|
||||||
) {
|
) {
|
||||||
addOne(listTmp, TootGap.mayNull(maxId, lastSinceId), head = addToHead)
|
addOne(listTmp, TootGap.mayNull(maxId, lastSinceId), head = addToHead)
|
||||||
}
|
}
|
||||||
|
@ -690,16 +691,14 @@ class ColumnTask_Refresh(
|
||||||
params.apply {
|
params.apply {
|
||||||
if (!bBottom) {
|
if (!bBottom) {
|
||||||
if (first) {
|
if (first) {
|
||||||
|
addRangeMisskey(bBottom = false)
|
||||||
addRangeMisskey(bBottom)
|
|
||||||
} else {
|
} else {
|
||||||
putMisskeySince(column.idRecent)
|
putMisskeySince(column.idRecent)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (first) {
|
if (first) {
|
||||||
|
|
||||||
when (column.pagingType) {
|
when (column.pagingType) {
|
||||||
ColumnPagingType.Default -> addRangeMisskey(bBottom)
|
ColumnPagingType.Default -> addRangeMisskey(bBottom = true)
|
||||||
ColumnPagingType.Offset -> put("offset", column.offsetNext)
|
ColumnPagingType.Offset -> put("offset", column.offsetNext)
|
||||||
ColumnPagingType.Cursor -> put("cursor", column.idOld)
|
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.loadingTootsearch
|
||||||
import jp.juggler.subwaytooter.search.TootsearchHelper.refreshTootsearch
|
import jp.juggler.subwaytooter.search.TootsearchHelper.refreshTootsearch
|
||||||
import jp.juggler.subwaytooter.streaming.StreamSpec
|
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.*
|
||||||
import jp.juggler.util.data.*
|
import jp.juggler.util.data.*
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
|
@ -685,7 +685,7 @@ enum class ColumnType(
|
||||||
R.string.profile_of,
|
R.string.profile_of,
|
||||||
when (who) {
|
when (who) {
|
||||||
null -> profileId.toString()
|
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.TootApiClient
|
||||||
import jp.juggler.subwaytooter.api.TootParser
|
import jp.juggler.subwaytooter.api.TootParser
|
||||||
import jp.juggler.subwaytooter.api.entity.*
|
import jp.juggler.subwaytooter.api.entity.*
|
||||||
import jp.juggler.subwaytooter.table.AcctSet
|
import jp.juggler.subwaytooter.table.daoAcctSet
|
||||||
import jp.juggler.subwaytooter.table.TagSet
|
import jp.juggler.subwaytooter.table.daoTagHistory
|
||||||
import jp.juggler.subwaytooter.table.UserRelation
|
import jp.juggler.subwaytooter.table.daoUserRelation
|
||||||
import jp.juggler.util.data.toJsonArray
|
import jp.juggler.util.data.toJsonArray
|
||||||
import jp.juggler.util.log.LogCategory
|
import jp.juggler.util.log.LogCategory
|
||||||
import jp.juggler.util.network.toPostRequestBuilder
|
import jp.juggler.util.network.toPostRequestBuilder
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
class UserRelationLoader(val column: Column) {
|
class UserRelationLoader(val column: Column) {
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -63,7 +64,13 @@ class UserRelationLoader(val column: Column) {
|
||||||
while (start < end) {
|
while (start < end) {
|
||||||
var step = end - start
|
var step = end - start
|
||||||
if (step > Column.RELATIONSHIP_LOAD_STEP) step = Column.RELATIONSHIP_LOAD_STEP
|
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
|
start += step
|
||||||
}
|
}
|
||||||
log.d("updateRelation: update $end relations.")
|
log.d("updateRelation: update $end relations.")
|
||||||
|
@ -108,7 +115,11 @@ class UserRelationLoader(val column: Column) {
|
||||||
for (i in 0 until list.size) {
|
for (i in 0 until list.size) {
|
||||||
list[i].id = userIdList[i]
|
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.")
|
log.d("updateRelation: update $n relations.")
|
||||||
|
@ -134,7 +145,7 @@ class UserRelationLoader(val column: Column) {
|
||||||
}
|
}
|
||||||
val result = client.request(sb.toString()) ?: break // cancelled.
|
val result = client.request(sb.toString()) ?: break // cancelled.
|
||||||
val list = parseList(::TootRelationShip, parser, result.jsonArray)
|
val list = parseList(::TootRelationShip, parser, result.jsonArray)
|
||||||
if (list.size > 0) UserRelation.saveListMastodon(
|
if (list.size > 0) daoUserRelation.saveListMastodon(
|
||||||
now,
|
now,
|
||||||
column.accessInfo.db_id,
|
column.accessInfo.db_id,
|
||||||
list
|
list
|
||||||
|
@ -156,7 +167,7 @@ class UserRelationLoader(val column: Column) {
|
||||||
while (n < acctList.size) {
|
while (n < acctList.size) {
|
||||||
var length = size - n
|
var length = size - n
|
||||||
if (length > Column.ACCT_DB_STEP) length = Column.ACCT_DB_STEP
|
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
|
n += length
|
||||||
}
|
}
|
||||||
log.d("updateRelation: update $n acct.")
|
log.d("updateRelation: update $n acct.")
|
||||||
|
@ -171,11 +182,10 @@ class UserRelationLoader(val column: Column) {
|
||||||
val now = System.currentTimeMillis()
|
val now = System.currentTimeMillis()
|
||||||
|
|
||||||
n = 0
|
n = 0
|
||||||
while (n < tagList.size) {
|
while (n < size) {
|
||||||
var length = size - n
|
val step = min(Column.ACCT_DB_STEP, size - n)
|
||||||
if (length > Column.ACCT_DB_STEP) length = Column.ACCT_DB_STEP
|
daoTagHistory.saveList(now, tagList, n, step)
|
||||||
TagSet.saveList(now, tagList, n, length)
|
n += step
|
||||||
n += length
|
|
||||||
}
|
}
|
||||||
log.d("updateRelation: update $n tag.")
|
log.d("updateRelation: update $n tag.")
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ import jp.juggler.subwaytooter.*
|
||||||
import jp.juggler.subwaytooter.column.*
|
import jp.juggler.subwaytooter.column.*
|
||||||
import jp.juggler.subwaytooter.pref.PrefB
|
import jp.juggler.subwaytooter.pref.PrefB
|
||||||
import jp.juggler.subwaytooter.streaming.*
|
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.util.*
|
||||||
import jp.juggler.subwaytooter.view.MyLinkMovementMethod
|
import jp.juggler.subwaytooter.view.MyLinkMovementMethod
|
||||||
import jp.juggler.subwaytooter.view.MyTextView
|
import jp.juggler.subwaytooter.view.MyTextView
|
||||||
|
@ -219,15 +219,15 @@ class ColumnViewHolder(
|
||||||
val column = this.column
|
val column = this.column
|
||||||
if (column == null || column.isDispose.get()) return@Runnable
|
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.text = ac.nickname
|
||||||
tvColumnContext.setTextColor(
|
tvColumnContext.setTextColor(
|
||||||
ac.color_fg.notZero()
|
ac.colorFg.notZero()
|
||||||
?: activity.attrColor(R.attr.colorTimeSmall)
|
?: activity.attrColor(R.attr.colorTimeSmall)
|
||||||
)
|
)
|
||||||
|
|
||||||
tvColumnContext.setBackgroundColor(ac.color_bg)
|
tvColumnContext.setBackgroundColor(ac.colorBg)
|
||||||
tvColumnContext.setPaddingRelative(activity.acctPadLr, 0, activity.acctPadLr, 0)
|
tvColumnContext.setPaddingRelative(activity.acctPadLr, 0, activity.acctPadLr, 0)
|
||||||
|
|
||||||
tvColumnName.text = column.getColumnName(false)
|
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.setRecycledViewPool(activity.viewPool)
|
||||||
}
|
}
|
||||||
listView.itemAnimator = null
|
listView.itemAnimator = null
|
||||||
|
@ -432,7 +432,7 @@ class ColumnViewHolder(
|
||||||
cbWithHighlight,
|
cbWithHighlight,
|
||||||
).forEach { it.setOnCheckedChangeListener(this) }
|
).forEach { it.setOnCheckedChangeListener(this) }
|
||||||
|
|
||||||
if (PrefB.bpMoveNotificationsQuickFilter(activity.pref)) {
|
if (PrefB.bpMoveNotificationsQuickFilter.value) {
|
||||||
(svQuickFilter.parent as? ViewGroup)?.removeView(svQuickFilter)
|
(svQuickFilter.parent as? ViewGroup)?.removeView(svQuickFilter)
|
||||||
llColumnSettingInside.addView(svQuickFilter, 0)
|
llColumnSettingInside.addView(svQuickFilter, 0)
|
||||||
|
|
||||||
|
|
|
@ -337,7 +337,7 @@ private fun ColumnViewHolder.showReactions(
|
||||||
)
|
)
|
||||||
|
|
||||||
val actMain = activity
|
val actMain = activity
|
||||||
val disableEmojiAnimation = PrefB.bpDisableEmojiAnimation(actMain.pref)
|
val disableEmojiAnimation = PrefB.bpDisableEmojiAnimation.value
|
||||||
|
|
||||||
for (reaction in reactions) {
|
for (reaction in reactions) {
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ fun ColumnViewHolder.closeBitmaps() {
|
||||||
|
|
||||||
fun ColumnViewHolder.loadBackgroundImage(iv: ImageView, url: String?) {
|
fun ColumnViewHolder.loadBackgroundImage(iv: ImageView, url: String?) {
|
||||||
try {
|
try {
|
||||||
if (url == null || url.isEmpty() || PrefB.bpDontShowColumnBackgroundImage(activity.pref)) {
|
if (url == null || url.isEmpty() || PrefB.bpDontShowColumnBackgroundImage.value) {
|
||||||
// 指定がないなら閉じる
|
// 指定がないなら閉じる
|
||||||
closeBitmaps()
|
closeBitmaps()
|
||||||
return
|
return
|
||||||
|
@ -128,7 +128,7 @@ fun ColumnViewHolder.onPageCreate(column: Column, pageIdx: Int, pageCount: Int)
|
||||||
|
|
||||||
ColumnViewHolder.log.d("onPageCreate [$pageIdx] ${column.getColumnName(true)}")
|
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)
|
tvColumnIndex.text = activity.getString(R.string.column_index, pageIdx + 1, pageCount)
|
||||||
tvColumnStatus.text = "?"
|
tvColumnStatus.text = "?"
|
||||||
|
@ -221,7 +221,7 @@ fun ColumnViewHolder.onPageCreate(column: Column, pageIdx: Int, pageCount: Int)
|
||||||
btnEmojiAdd.vg(false)
|
btnEmojiAdd.vg(false)
|
||||||
|
|
||||||
etSearch.vg(true)
|
etSearch.vg(true)
|
||||||
btnSearchClear.vg(PrefB.bpShowSearchClear(activity.pref))
|
btnSearchClear.vg(PrefB.bpShowSearchClear.value)
|
||||||
cbResolve.vg(column.type == ColumnType.SEARCH)
|
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()
|
fun dip(dp: Int): Int = (activity.density * dp + 0.5f).toInt()
|
||||||
val context = activity
|
val context = activity
|
||||||
|
|
||||||
val announcementsBgColor = PrefI.ipAnnouncementsBgColor().notZero()
|
val announcementsBgColor = PrefI.ipAnnouncementsBgColor.value.notZero()
|
||||||
?: context.attrColor(R.attr.colorSearchFormBackground)
|
?: context.attrColor(R.attr.colorSearchFormBackground)
|
||||||
|
|
||||||
btnAnnouncementsCutout.apply {
|
btnAnnouncementsCutout.apply {
|
||||||
|
@ -294,7 +294,7 @@ fun ColumnViewHolder.onPageCreate(column: Column, pageIdx: Int, pageCount: Int)
|
||||||
setPadding(0, padV, 0, padV)
|
setPadding(0, padV, 0, padV)
|
||||||
}
|
}
|
||||||
|
|
||||||
val searchBgColor = PrefI.ipSearchBgColor().notZero()
|
val searchBgColor = PrefI.ipSearchBgColor.value.notZero()
|
||||||
?: context.attrColor(R.attr.colorSearchFormBackground)
|
?: context.attrColor(R.attr.colorSearchFormBackground)
|
||||||
|
|
||||||
llSearch.apply {
|
llSearch.apply {
|
||||||
|
|
|
@ -30,7 +30,7 @@ fun ColumnViewHolder.showQuickFilter() {
|
||||||
btnQuickFilterReaction.vg(column.isMisskey)
|
btnQuickFilterReaction.vg(column.isMisskey)
|
||||||
btnQuickFilterFavourite.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
|
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.LinkInfo
|
||||||
import jp.juggler.subwaytooter.span.MyClickableSpan
|
import jp.juggler.subwaytooter.span.MyClickableSpan
|
||||||
import jp.juggler.subwaytooter.span.createSpan
|
import jp.juggler.subwaytooter.span.createSpan
|
||||||
import jp.juggler.subwaytooter.table.AcctColor
|
import jp.juggler.subwaytooter.table.*
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
|
||||||
import jp.juggler.subwaytooter.table.UserRelation
|
|
||||||
import jp.juggler.subwaytooter.util.DecodeOptions
|
import jp.juggler.subwaytooter.util.DecodeOptions
|
||||||
import jp.juggler.subwaytooter.util.NetworkEmojiInvalidator
|
import jp.juggler.subwaytooter.util.NetworkEmojiInvalidator
|
||||||
import jp.juggler.subwaytooter.util.openCustomTab
|
import jp.juggler.subwaytooter.util.openCustomTab
|
||||||
|
@ -356,7 +354,7 @@ internal class ViewHolderHeaderProfile(
|
||||||
whoDetail?.statuses_count ?: who.statuses_count
|
whoDetail?.statuses_count ?: who.statuses_count
|
||||||
}"
|
}"
|
||||||
|
|
||||||
val hideFollowCount = PrefB.bpHideFollowCount(activity.pref)
|
val hideFollowCount = PrefB.bpHideFollowCount.value
|
||||||
|
|
||||||
var caption = activity.getString(R.string.following)
|
var caption = activity.getString(R.string.following)
|
||||||
btnFollowing.text = when {
|
btnFollowing.text = when {
|
||||||
|
@ -370,7 +368,7 @@ internal class ViewHolderHeaderProfile(
|
||||||
else -> "${caption}\n${whoDetail?.followers_count ?: who.followers_count}"
|
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
|
this.relation = relation
|
||||||
setFollowIcon(
|
setFollowIcon(
|
||||||
activity,
|
activity,
|
||||||
|
@ -414,7 +412,7 @@ internal class ViewHolderHeaderProfile(
|
||||||
|
|
||||||
setAcct(tvMovedAcct, accessInfo, moved)
|
setAcct(tvMovedAcct, accessInfo, moved)
|
||||||
|
|
||||||
val relation = UserRelation.load(accessInfo.db_id, moved.id)
|
val relation = daoUserRelation.load(accessInfo.db_id, moved.id)
|
||||||
setFollowIcon(
|
setFollowIcon(
|
||||||
activity,
|
activity,
|
||||||
btnMoved,
|
btnMoved,
|
||||||
|
@ -482,7 +480,11 @@ internal class ViewHolderHeaderProfile(
|
||||||
val lastColumn = column
|
val lastColumn = column
|
||||||
DlgTextInput.show(
|
DlgTextInput.show(
|
||||||
activity,
|
activity,
|
||||||
AcctColor.getStringWithNickname(activity, R.string.personal_notes_of, who.acct),
|
daoAcctColor.getStringWithNickname(
|
||||||
|
activity,
|
||||||
|
R.string.personal_notes_of,
|
||||||
|
who.acct
|
||||||
|
),
|
||||||
relation?.note ?: "",
|
relation?.note ?: "",
|
||||||
allowEmpty = true,
|
allowEmpty = true,
|
||||||
callback = object : DlgTextInput.Callback {
|
callback = object : DlgTextInput.Callback {
|
||||||
|
@ -551,16 +553,16 @@ internal class ViewHolderHeaderProfile(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setAcct(tv: TextView, accessInfo: SavedAccount, who: TootAccount) {
|
private fun setAcct(tv: TextView, accessInfo: SavedAccount, who: TootAccount) {
|
||||||
val ac = AcctColor.load(accessInfo, who)
|
val ac = daoAcctColor.load(accessInfo, who)
|
||||||
tv.text = when {
|
tv.text = when {
|
||||||
AcctColor.hasNickname(ac) -> ac.nickname
|
daoAcctColor.hasNickname(ac) -> ac.nickname
|
||||||
PrefB.bpShortAcctLocalUser() -> "@${who.acct.pretty}"
|
PrefB.bpShortAcctLocalUser.value -> "@${who.acct.pretty}"
|
||||||
else -> "@${ac.nickname}"
|
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)
|
tv.setPaddingRelative(activity.acctPadLr, 0, activity.acctPadLr, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -594,7 +596,7 @@ internal class ViewHolderHeaderProfile(
|
||||||
when {
|
when {
|
||||||
emoji == null ->
|
emoji == null ->
|
||||||
append("locked")
|
append("locked")
|
||||||
PrefB.bpUseTwemoji() ->
|
PrefB.bpUseTwemoji.value ->
|
||||||
appendSpan("locked", emoji.createSpan(activity))
|
appendSpan("locked", emoji.createSpan(activity))
|
||||||
else ->
|
else ->
|
||||||
append(emoji.unifiedCode)
|
append(emoji.unifiedCode)
|
||||||
|
@ -607,7 +609,7 @@ internal class ViewHolderHeaderProfile(
|
||||||
when {
|
when {
|
||||||
emoji == null ->
|
emoji == null ->
|
||||||
append("bot")
|
append("bot")
|
||||||
PrefB.bpUseTwemoji() ->
|
PrefB.bpUseTwemoji.value ->
|
||||||
appendSpan("bot", emoji.createSpan(activity))
|
appendSpan("bot", emoji.createSpan(activity))
|
||||||
else ->
|
else ->
|
||||||
append(emoji.unifiedCode)
|
append(emoji.unifiedCode)
|
||||||
|
@ -620,7 +622,7 @@ internal class ViewHolderHeaderProfile(
|
||||||
when {
|
when {
|
||||||
emoji == null ->
|
emoji == null ->
|
||||||
append("suspended")
|
append("suspended")
|
||||||
PrefB.bpUseTwemoji() ->
|
PrefB.bpUseTwemoji.value ->
|
||||||
appendSpan("suspended", emoji.createSpan(activity))
|
appendSpan("suspended", emoji.createSpan(activity))
|
||||||
else ->
|
else ->
|
||||||
append(emoji.unifiedCode)
|
append(emoji.unifiedCode)
|
||||||
|
@ -715,7 +717,7 @@ internal class ViewHolderHeaderProfile(
|
||||||
valueText.append(TootStatus.formatTime(activity, item.verified_at, false))
|
valueText.append(TootStatus.formatTime(activity, item.verified_at, false))
|
||||||
val end = valueText.length
|
val end = valueText.length
|
||||||
|
|
||||||
val linkFgColor = PrefI.ipVerifiedLinkFgColor(activity.pref).notZero()
|
val linkFgColor = PrefI.ipVerifiedLinkFgColor.value.notZero()
|
||||||
?: (Color.BLACK or 0x7fbc99)
|
?: (Color.BLACK or 0x7fbc99)
|
||||||
|
|
||||||
valueText.setSpan(
|
valueText.setSpan(
|
||||||
|
@ -737,7 +739,7 @@ internal class ViewHolderHeaderProfile(
|
||||||
valueView.movementMethod = MyLinkMovementMethod
|
valueView.movementMethod = MyLinkMovementMethod
|
||||||
|
|
||||||
if (item.verified_at > 0L) {
|
if (item.verified_at > 0L) {
|
||||||
val linkBgColor = PrefI.ipVerifiedLinkBgColor(activity.pref).notZero()
|
val linkBgColor = PrefI.ipVerifiedLinkBgColor.value.notZero()
|
||||||
?: (0x337fbc99)
|
?: (0x337fbc99)
|
||||||
|
|
||||||
valueView.setBackgroundColor(linkBgColor)
|
valueView.setBackgroundColor(linkBgColor)
|
||||||
|
|
|
@ -3,9 +3,7 @@ package jp.juggler.subwaytooter.dialog
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.text.Spannable
|
|
||||||
import android.text.SpannableStringBuilder
|
import android.text.SpannableStringBuilder
|
||||||
import android.text.style.RelativeSizeSpan
|
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
|
@ -13,8 +11,7 @@ import android.widget.TextView
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.widget.AppCompatButton
|
import androidx.appcompat.widget.AppCompatButton
|
||||||
import jp.juggler.subwaytooter.R
|
import jp.juggler.subwaytooter.R
|
||||||
import jp.juggler.subwaytooter.table.AcctColor
|
import jp.juggler.subwaytooter.table.*
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
|
||||||
import jp.juggler.util.log.showToast
|
import jp.juggler.util.log.showToast
|
||||||
import jp.juggler.util.ui.dismissSafe
|
import jp.juggler.util.ui.dismissSafe
|
||||||
import jp.juggler.util.ui.getAdaptiveRippleDrawableRound
|
import jp.juggler.util.ui.getAdaptiveRippleDrawableRound
|
||||||
|
@ -30,7 +27,7 @@ suspend fun AppCompatActivity.pickAccount(
|
||||||
bAllowMastodon: Boolean = true,
|
bAllowMastodon: Boolean = true,
|
||||||
bAuto: Boolean = false,
|
bAuto: Boolean = false,
|
||||||
message: String? = null,
|
message: String? = null,
|
||||||
accountListArg: MutableList<SavedAccount>? = null,
|
accountListArg: List<SavedAccount>? = null,
|
||||||
dismissCallback: (dialog: DialogInterface) -> Unit = {},
|
dismissCallback: (dialog: DialogInterface) -> Unit = {},
|
||||||
extraCallback: (LinearLayout, Int, Int) -> Unit = { _, _, _ -> },
|
extraCallback: (LinearLayout, Int, Int) -> Unit = { _, _, _ -> },
|
||||||
): SavedAccount? {
|
): SavedAccount? {
|
||||||
|
@ -54,11 +51,10 @@ suspend fun AppCompatActivity.pickAccount(
|
||||||
else -> 0
|
else -> 0
|
||||||
}
|
}
|
||||||
|
|
||||||
val accountList: MutableList<SavedAccount> = accountListArg
|
val accountList = accountListArg
|
||||||
?: SavedAccount.loadAccountList(activity)
|
?: daoSavedAccount.loadAccountList()
|
||||||
.filter { 0 == it.checkMastodon() + it.checkMisskey() + it.checkPseudo() }
|
.filter { 0 == it.checkMastodon() + it.checkMisskey() + it.checkPseudo() }
|
||||||
.toMutableList()
|
.sortedByNickname()
|
||||||
.also { SavedAccount.sort(it) }
|
|
||||||
|
|
||||||
if (accountList.isEmpty()) {
|
if (accountList.isEmpty()) {
|
||||||
|
|
||||||
|
@ -127,17 +123,21 @@ suspend fun AppCompatActivity.pickAccount(
|
||||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
)
|
)
|
||||||
|
|
||||||
val ac = AcctColor.load(a)
|
val ac = daoAcctColor.load(a)
|
||||||
|
|
||||||
val b = AppCompatButton(activity)
|
val b = AppCompatButton(activity)
|
||||||
|
|
||||||
if (AcctColor.hasColorBackground(ac)) {
|
if (daoAcctColor.hasColorBackground(ac)) {
|
||||||
b.background = getAdaptiveRippleDrawableRound(activity, ac.color_bg, ac.color_fg)
|
b.background = getAdaptiveRippleDrawableRound(
|
||||||
|
activity,
|
||||||
|
ac.colorBg,
|
||||||
|
ac.colorFg
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
b.setBackgroundResource(R.drawable.btn_bg_transparent_round6dp)
|
b.setBackgroundResource(R.drawable.btn_bg_transparent_round6dp)
|
||||||
}
|
}
|
||||||
if (AcctColor.hasColorForeground(ac)) {
|
if (daoAcctColor.hasColorForeground(ac)) {
|
||||||
b.textColor = ac.color_fg
|
b.textColor = ac.colorFg
|
||||||
}
|
}
|
||||||
|
|
||||||
b.setPaddingRelative(padX, padY, padX, padY)
|
b.setPaddingRelative(padX, padY, padX, padY)
|
||||||
|
@ -147,19 +147,20 @@ suspend fun AppCompatActivity.pickAccount(
|
||||||
b.minHeight = (0.5f + 32f * density).toInt()
|
b.minHeight = (0.5f + 32f * density).toInt()
|
||||||
|
|
||||||
val sb = SpannableStringBuilder(ac.nickname)
|
val sb = SpannableStringBuilder(ac.nickname)
|
||||||
if (a.lastNotificationError?.isNotEmpty() == true) {
|
// TODO エラー状態を表示する
|
||||||
sb.append("\n")
|
// if (a.lastNotificationError?.isNotEmpty() == true) {
|
||||||
val start = sb.length
|
// sb.append("\n")
|
||||||
sb.append(a.lastNotificationError)
|
// val start = sb.length
|
||||||
val end = sb.length
|
// sb.append(a.lastNotificationError)
|
||||||
sb.setSpan(RelativeSizeSpan(0.7f), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
// val end = sb.length
|
||||||
} else if (a.last_subscription_error?.isNotEmpty() == true) {
|
// sb.setSpan(RelativeSizeSpan(0.7f), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
sb.append("\n")
|
// } else if (a.last_subscription_error?.isNotEmpty() == true) {
|
||||||
val start = sb.length
|
// sb.append("\n")
|
||||||
sb.append(a.last_subscription_error)
|
// val start = sb.length
|
||||||
val end = sb.length
|
// sb.append(a.last_subscription_error)
|
||||||
sb.setSpan(RelativeSizeSpan(0.7f), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
// val end = sb.length
|
||||||
}
|
// sb.setSpan(RelativeSizeSpan(0.7f), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
|
// }
|
||||||
b.text = sb
|
b.text = sb
|
||||||
|
|
||||||
b.setOnClickListener {
|
b.setOnClickListener {
|
||||||
|
|
|
@ -1,35 +1,50 @@
|
||||||
package jp.juggler.subwaytooter.dialog
|
package jp.juggler.subwaytooter.dialog
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import jp.juggler.subwaytooter.R
|
|
||||||
import jp.juggler.util.data.notEmpty
|
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 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 action(caption: CharSequence, action: suspend () -> Unit) {
|
||||||
|
list.add(Action(caption, action))
|
||||||
fun addAction(caption: CharSequence, action: () -> Unit): ActionsDialog {
|
|
||||||
|
|
||||||
actionList.add(Action(caption, action))
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun show(context: Context, title: CharSequence? = null): ActionsDialog {
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
AlertDialog.Builder(context).apply {
|
suspend fun showSuspend(context: Context): Action =
|
||||||
setNegativeButton(R.string.cancel, null)
|
suspendCancellableCoroutine { cont ->
|
||||||
setItems(actionList.map { it.caption }.toTypedArray()) { _, which ->
|
val dialog = android.app.AlertDialog.Builder(context).apply {
|
||||||
if (which >= 0 && which < actionList.size) {
|
title?.notEmpty()?.let { setTitle(it) }
|
||||||
actionList[which].action()
|
setNegativeButton(android.R.string.cancel, null)
|
||||||
|
setItems(list.map { it.caption }.toTypedArray()) { d, i ->
|
||||||
|
if (cont.isActive) cont.resume(list[i]) {}
|
||||||
|
d.dismissSafe()
|
||||||
}
|
}
|
||||||
}
|
setOnDismissListener {
|
||||||
title?.notEmpty()?.let { setTitle(it) }
|
if (cont.isActive) cont.resumeWithException(CancellationException())
|
||||||
}.show()
|
}
|
||||||
|
}.create()
|
||||||
return this
|
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")
|
@SuppressLint("InflateParams")
|
||||||
suspend fun AppCompatActivity.confirm(
|
suspend inline fun AppCompatActivity.confirm(
|
||||||
message: String,
|
message: String,
|
||||||
getConfirmEnabled: Boolean,
|
isConfirmEnabled: Boolean,
|
||||||
setConfirmEnabled: (newConfirmEnabled: Boolean) -> Unit,
|
setConfirmEnabled: (newConfirmEnabled: Boolean) -> Unit,
|
||||||
) {
|
) {
|
||||||
if (!getConfirmEnabled) return
|
if (!isConfirmEnabled) return
|
||||||
suspendCancellableCoroutine<Unit> { cont ->
|
val skipNext = suspendCancellableCoroutine { cont ->
|
||||||
try {
|
try {
|
||||||
val views = DlgConfirmBinding.inflate(layoutInflater)
|
val views = DlgConfirmBinding.inflate(layoutInflater)
|
||||||
views.tvMessage.text = message
|
views.tvMessage.text = message
|
||||||
|
@ -79,10 +79,7 @@ object DlgConfirm {
|
||||||
.setCancelable(true)
|
.setCancelable(true)
|
||||||
.setNegativeButton(R.string.cancel, null)
|
.setNegativeButton(R.string.cancel, null)
|
||||||
.setPositiveButton(R.string.ok) { _, _ ->
|
.setPositiveButton(R.string.ok) { _, _ ->
|
||||||
if (views.cbSkipNext.isChecked) {
|
if (cont.isActive) cont.resume(views.cbSkipNext.isChecked)
|
||||||
setConfirmEnabled(false)
|
|
||||||
}
|
|
||||||
if (cont.isActive) cont.resume(Unit)
|
|
||||||
}
|
}
|
||||||
dialog.setOnDismissListener {
|
dialog.setOnDismissListener {
|
||||||
if (cont.isActive) cont.resumeWithException(CancellationException("dialog cancelled."))
|
if (cont.isActive) cont.resumeWithException(CancellationException("dialog cancelled."))
|
||||||
|
@ -92,6 +89,7 @@ object DlgConfirm {
|
||||||
cont.resumeWithException(ex)
|
cont.resumeWithException(ex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (skipNext) setConfirmEnabled(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun AppCompatActivity.confirm(@StringRes messageId: Int, vararg args: Any?) =
|
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