more split...
This commit is contained in:
parent
6de96b4852
commit
9395774a48
|
@ -7,10 +7,8 @@ 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.*
|
import android.os.*
|
||||||
import android.text.InputType
|
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import android.view.inputmethod.EditorInfo
|
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.GravityCompat
|
import androidx.core.view.GravityCompat
|
||||||
|
@ -40,8 +38,7 @@ class ActMain : AppCompatActivity(),
|
||||||
MyClickableSpanHandler {
|
MyClickableSpanHandler {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private val log = LogCategory("ActMain")
|
||||||
val log = LogCategory("ActMain")
|
|
||||||
|
|
||||||
// リザルト
|
// リザルト
|
||||||
const val RESULT_APP_DATA_IMPORT = Activity.RESULT_FIRST_USER
|
const val RESULT_APP_DATA_IMPORT = Activity.RESULT_FIRST_USER
|
||||||
|
@ -122,9 +119,7 @@ class ActMain : AppCompatActivity(),
|
||||||
|
|
||||||
var quickTootVisibility: TootVisibility = TootVisibility.AccountSetting
|
var quickTootVisibility: TootVisibility = TootVisibility.AccountSetting
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////
|
lateinit var llFormRoot: LinearLayout
|
||||||
// 変更しない変数(lateinit)
|
|
||||||
|
|
||||||
lateinit var llQuickTootBar: LinearLayout
|
lateinit var llQuickTootBar: LinearLayout
|
||||||
lateinit var etQuickToot: MyEditText
|
lateinit var etQuickToot: MyEditText
|
||||||
lateinit var btnQuickToot: ImageButton
|
lateinit var btnQuickToot: ImageButton
|
||||||
|
@ -879,106 +874,11 @@ class ActMain : AppCompatActivity(),
|
||||||
return rv
|
return rv
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun initUI() {
|
// lateinitなビュー変数を初期化する
|
||||||
setContentView(R.layout.act_main)
|
fun findViews() {
|
||||||
App1.initEdgeToEdge(this)
|
llFormRoot = findViewById(R.id.llFormRoot)
|
||||||
|
|
||||||
quickTootVisibility =
|
|
||||||
TootVisibility.parseSavedVisibility(PrefS.spQuickTootVisibility(pref))
|
|
||||||
?: quickTootVisibility
|
|
||||||
|
|
||||||
Column.reloadDefaultColor(this, pref)
|
|
||||||
|
|
||||||
var sv = PrefS.spTimelineFont(pref)
|
|
||||||
if (sv.isNotEmpty()) {
|
|
||||||
try {
|
|
||||||
timelineFont = Typeface.createFromFile(sv)
|
|
||||||
} catch (ex: Throwable) {
|
|
||||||
log.trace(ex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sv = PrefS.spTimelineFontBold(pref)
|
|
||||||
if (sv.isNotEmpty()) {
|
|
||||||
try {
|
|
||||||
timeline_font_bold = Typeface.createFromFile(sv)
|
|
||||||
} catch (ex: Throwable) {
|
|
||||||
log.trace(ex)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
timeline_font_bold = Typeface.create(timelineFont, Typeface.BOLD)
|
|
||||||
} catch (ex: Throwable) {
|
|
||||||
log.trace(ex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun parseIconSize(stringPref: StringPref, minDp: Float = 1f): Int {
|
|
||||||
var iconSizeDp = stringPref.defVal.toFloat()
|
|
||||||
try {
|
|
||||||
sv = stringPref(pref)
|
|
||||||
val fv = if (sv.isEmpty()) Float.NaN else sv.toFloat()
|
|
||||||
if (fv.isFinite() && fv >= minDp) {
|
|
||||||
iconSizeDp = fv
|
|
||||||
}
|
|
||||||
} catch (ex: Throwable) {
|
|
||||||
log.trace(ex)
|
|
||||||
}
|
|
||||||
return (0.5f + iconSizeDp * density).toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
avatarIconSize = parseIconSize(PrefS.spAvatarIconSize)
|
|
||||||
notificationTlIconSize = parseIconSize(PrefS.spNotificationTlIconSize)
|
|
||||||
boostButtonSize = parseIconSize(PrefS.spBoostButtonSize)
|
|
||||||
replyIconSize = parseIconSize(PrefS.spReplyIconSize)
|
|
||||||
headerIconSize = parseIconSize(PrefS.spHeaderIconSize)
|
|
||||||
stripIconSize = parseIconSize(PrefS.spStripIconSize)
|
|
||||||
screenBottomPadding = parseIconSize(PrefS.spScreenBottomPadding, minDp = 0f)
|
|
||||||
|
|
||||||
run {
|
|
||||||
var roundRatio = 33f
|
|
||||||
try {
|
|
||||||
if (PrefB.bpDontRound(pref)) {
|
|
||||||
roundRatio = 0f
|
|
||||||
} else {
|
|
||||||
sv = PrefS.spRoundRatio(pref)
|
|
||||||
if (sv.isNotEmpty()) {
|
|
||||||
val fv = sv.toFloat()
|
|
||||||
if (fv.isFinite()) {
|
|
||||||
roundRatio = fv
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (ex: Throwable) {
|
|
||||||
log.trace(ex)
|
|
||||||
}
|
|
||||||
Styler.round_ratio = clipRange(0f, 1f, roundRatio / 100f) * 0.5f
|
|
||||||
}
|
|
||||||
|
|
||||||
run {
|
|
||||||
var boostAlpha = 0.8f
|
|
||||||
try {
|
|
||||||
val f = (PrefS.spBoostAlpha.toInt(pref).toFloat() + 0.5f) / 100f
|
|
||||||
boostAlpha = when {
|
|
||||||
f >= 1f -> 1f
|
|
||||||
f < 0f -> 0.66f
|
|
||||||
else -> f
|
|
||||||
}
|
|
||||||
} catch (ex: Throwable) {
|
|
||||||
log.trace(ex)
|
|
||||||
}
|
|
||||||
Styler.boostAlpha = boostAlpha
|
|
||||||
}
|
|
||||||
|
|
||||||
llEmpty = findViewById(R.id.llEmpty)
|
llEmpty = findViewById(R.id.llEmpty)
|
||||||
|
|
||||||
drawer = findViewById(R.id.drawer_layout)
|
drawer = findViewById(R.id.drawer_layout)
|
||||||
drawer.addDrawerListener(this)
|
|
||||||
|
|
||||||
drawer.setExclusionSize(stripIconSize)
|
|
||||||
|
|
||||||
SideMenuAdapter(this, handler, findViewById(R.id.nav_view), drawer)
|
|
||||||
|
|
||||||
btnMenu = findViewById(R.id.btnMenu)
|
btnMenu = findViewById(R.id.btnMenu)
|
||||||
btnToot = findViewById(R.id.btnToot)
|
btnToot = findViewById(R.id.btnToot)
|
||||||
vFooterDivider1 = findViewById(R.id.vFooterDivider1)
|
vFooterDivider1 = findViewById(R.id.vFooterDivider1)
|
||||||
|
@ -990,128 +890,58 @@ class ActMain : AppCompatActivity(),
|
||||||
btnQuickToot = findViewById(R.id.btnQuickToot)
|
btnQuickToot = findViewById(R.id.btnQuickToot)
|
||||||
btnQuickTootMenu = findViewById(R.id.btnQuickTootMenu)
|
btnQuickTootMenu = findViewById(R.id.btnQuickTootMenu)
|
||||||
|
|
||||||
val llFormRoot: LinearLayout = findViewById(R.id.llFormRoot)
|
|
||||||
|
|
||||||
llFormRoot.setPadding(0, 0, 0, screenBottomPadding)
|
|
||||||
|
|
||||||
etQuickToot.typeface = timelineFont
|
|
||||||
|
|
||||||
when (PrefI.ipJustifyWindowContentPortrait(pref)) {
|
|
||||||
PrefI.JWCP_START -> {
|
|
||||||
val iconW = (stripIconSize * 1.5f + 0.5f).toInt()
|
|
||||||
val padding = resources.displayMetrics.widthPixels / 2 - iconW
|
|
||||||
|
|
||||||
fun ViewGroup.addViewBeforeLast(v: View) = addView(v, childCount - 1)
|
|
||||||
(svColumnStrip.parent as LinearLayout).addViewBeforeLast(
|
|
||||||
View(this).apply {
|
|
||||||
layoutParams = LinearLayout.LayoutParams(padding, 0)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
llQuickTootBar.addViewBeforeLast(
|
|
||||||
View(this).apply {
|
|
||||||
layoutParams = LinearLayout.LayoutParams(padding, 0)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
PrefI.JWCP_END -> {
|
|
||||||
val iconW = (stripIconSize * 1.5f + 0.5f).toInt()
|
|
||||||
val borderWidth = (1f * density + 0.5f).toInt()
|
|
||||||
val padding = resources.displayMetrics.widthPixels / 2 - iconW - borderWidth
|
|
||||||
|
|
||||||
fun ViewGroup.addViewAfterFirst(v: View) = addView(v, 1)
|
|
||||||
(svColumnStrip.parent as LinearLayout).addViewAfterFirst(
|
|
||||||
View(this).apply {
|
|
||||||
layoutParams = LinearLayout.LayoutParams(padding, 0)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
llQuickTootBar.addViewAfterFirst(
|
|
||||||
View(this).apply {
|
|
||||||
layoutParams = LinearLayout.LayoutParams(padding, 0)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!PrefB.bpQuickTootBar(pref)) {
|
|
||||||
llQuickTootBar.visibility = View.GONE
|
|
||||||
}
|
|
||||||
|
|
||||||
btnToot.setOnClickListener(this)
|
btnToot.setOnClickListener(this)
|
||||||
btnMenu.setOnClickListener(this)
|
btnMenu.setOnClickListener(this)
|
||||||
btnQuickToot.setOnClickListener(this)
|
btnQuickToot.setOnClickListener(this)
|
||||||
btnQuickTootMenu.setOnClickListener(this)
|
btnQuickTootMenu.setOnClickListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
if (PrefB.bpDontUseActionButtonWithQuickTootBar(pref)) {
|
internal fun initUI() {
|
||||||
etQuickToot.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE
|
setContentView(R.layout.act_main)
|
||||||
etQuickToot.imeOptions = EditorInfo.IME_ACTION_NONE
|
App1.initEdgeToEdge(this)
|
||||||
// 最後に指定する必要がある?
|
|
||||||
etQuickToot.maxLines = 5
|
quickTootVisibility =
|
||||||
etQuickToot.isVerticalScrollBarEnabled = true
|
TootVisibility.parseSavedVisibility(PrefS.spQuickTootVisibility(pref))
|
||||||
etQuickToot.isScrollbarFadingEnabled = false
|
?: quickTootVisibility
|
||||||
} else {
|
|
||||||
etQuickToot.inputType = InputType.TYPE_CLASS_TEXT
|
Column.reloadDefaultColor(this, pref)
|
||||||
etQuickToot.imeOptions = EditorInfo.IME_ACTION_SEND
|
|
||||||
etQuickToot.setOnEditorActionListener(TextView.OnEditorActionListener { _, actionId, _ ->
|
reloadFonts()
|
||||||
if (actionId == EditorInfo.IME_ACTION_SEND) {
|
reloadIconSize()
|
||||||
btnQuickToot.performClick()
|
reloadRoundRatio()
|
||||||
return@OnEditorActionListener true
|
reloadBoostAlpha()
|
||||||
}
|
|
||||||
false
|
findViews()
|
||||||
})
|
|
||||||
// 最後に指定する必要がある?
|
drawer.addDrawerListener(this)
|
||||||
etQuickToot.maxLines = 1
|
drawer.setExclusionSize(stripIconSize)
|
||||||
}
|
|
||||||
|
SideMenuAdapter(this, handler, findViewById(R.id.nav_view), drawer)
|
||||||
|
|
||||||
|
llFormRoot.setPadding(0, 0, 0, screenBottomPadding)
|
||||||
|
|
||||||
|
justifyWindowContentPortrait()
|
||||||
|
|
||||||
|
initUIQuickToot()
|
||||||
|
|
||||||
svColumnStrip.isHorizontalFadingEdgeEnabled = true
|
svColumnStrip.isHorizontalFadingEdgeEnabled = true
|
||||||
|
|
||||||
completionHelper = CompletionHelper(this, pref, appState.handler)
|
completionHelper = CompletionHelper(this, pref, appState.handler)
|
||||||
|
|
||||||
val dm = resources.displayMetrics
|
val dm = resources.displayMetrics
|
||||||
|
|
||||||
val density = dm.density
|
val density = dm.density
|
||||||
|
reloadMediaHeight()
|
||||||
var mediaThumbHeightDp = 64
|
val columnWMin = loadColumnMin(density)
|
||||||
sv = PrefS.spMediaThumbHeight(pref)
|
|
||||||
if (sv.isNotEmpty()) {
|
|
||||||
try {
|
|
||||||
val iv = Integer.parseInt(sv)
|
|
||||||
if (iv >= 32) {
|
|
||||||
mediaThumbHeightDp = iv
|
|
||||||
}
|
|
||||||
} catch (ex: Throwable) {
|
|
||||||
log.trace(ex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
appState.mediaThumbHeight = (0.5f + mediaThumbHeightDp * density).toInt()
|
|
||||||
|
|
||||||
var columnWMinDp = COLUMN_WIDTH_MIN_DP
|
|
||||||
sv = PrefS.spColumnWidth(pref)
|
|
||||||
if (sv.isNotEmpty()) {
|
|
||||||
try {
|
|
||||||
val iv = Integer.parseInt(sv)
|
|
||||||
if (iv >= 100) {
|
|
||||||
columnWMinDp = iv
|
|
||||||
}
|
|
||||||
} catch (ex: Throwable) {
|
|
||||||
log.trace(ex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val columnWMin = (0.5f + columnWMinDp * density).toInt()
|
|
||||||
|
|
||||||
val sw = dm.widthPixels
|
val sw = dm.widthPixels
|
||||||
|
|
||||||
|
// スマホモードとタブレットモードの切り替え
|
||||||
if (PrefB.bpDisableTabletMode(pref) || sw < columnWMin * 2) {
|
if (PrefB.bpDisableTabletMode(pref) || sw < columnWMin * 2) {
|
||||||
// SmartPhone mode
|
|
||||||
phoneViews = PhoneViews(this)
|
phoneViews = PhoneViews(this)
|
||||||
} else {
|
} else {
|
||||||
// Tablet mode
|
|
||||||
tabletViews = TabletViews(this)
|
tabletViews = TabletViews(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
val tmpPhonePager: MyViewPager = findViewById(R.id.viewPager)
|
val tmpPhonePager: MyViewPager = findViewById(R.id.viewPager)
|
||||||
val tmpTabletPager: RecyclerView = findViewById(R.id.rvPager)
|
val tmpTabletPager: RecyclerView = findViewById(R.id.rvPager)
|
||||||
|
|
||||||
phoneTab({ env ->
|
phoneTab({ env ->
|
||||||
tmpTabletPager.visibility = View.GONE
|
tmpTabletPager.visibility = View.GONE
|
||||||
env.initUI(tmpPhonePager)
|
env.initUI(tmpPhonePager)
|
||||||
|
@ -1119,23 +949,8 @@ class ActMain : AppCompatActivity(),
|
||||||
}, { env ->
|
}, { env ->
|
||||||
tmpPhonePager.visibility = View.GONE
|
tmpPhonePager.visibility = View.GONE
|
||||||
env.initUI(tmpTabletPager)
|
env.initUI(tmpTabletPager)
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
showFooterColor()
|
showFooterColor()
|
||||||
|
|
||||||
completionHelper.attachEditText(
|
|
||||||
llFormRoot,
|
|
||||||
etQuickToot,
|
|
||||||
true,
|
|
||||||
object : CompletionHelper.Callback2 {
|
|
||||||
override fun onTextUpdate() {}
|
|
||||||
|
|
||||||
override fun canOpenPopup(): Boolean {
|
|
||||||
return !drawer.isDrawerOpen(GravityCompat.START)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
showQuickTootVisibility()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,4 +63,3 @@ fun ActMain.refreshAfterPost() {
|
||||||
this.postedAcct = null
|
this.postedAcct = null
|
||||||
this.postedStatusId = null
|
this.postedStatusId = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,14 @@
|
||||||
package jp.juggler.subwaytooter
|
package jp.juggler.subwaytooter
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.text.SpannableStringBuilder
|
import android.text.SpannableStringBuilder
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.annotation.RawRes
|
import androidx.annotation.RawRes
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import jp.juggler.subwaytooter.api.entity.TootStatus
|
import jp.juggler.subwaytooter.api.entity.TootStatus
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
|
||||||
import jp.juggler.util.*
|
import jp.juggler.util.*
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import kotlin.math.abs
|
|
||||||
import kotlin.math.min
|
|
||||||
|
|
||||||
|
|
||||||
fun ActMain.resizeAutoCW(columnW: Int) {
|
fun ActMain.resizeAutoCW(columnW: Int) {
|
||||||
val sv = PrefS.spAutoCWLines(pref)
|
val sv = PrefS.spAutoCWLines(pref)
|
||||||
|
@ -144,4 +138,4 @@ fun ActMain.closeListItemPopup() {
|
||||||
} catch (ignored: Throwable) {
|
} catch (ignored: Throwable) {
|
||||||
}
|
}
|
||||||
listItemPopup = null
|
listItemPopup = null
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
package jp.juggler.subwaytooter
|
package jp.juggler.subwaytooter
|
||||||
|
|
||||||
|
import android.text.InputType
|
||||||
|
import android.view.View
|
||||||
|
import android.view.inputmethod.EditorInfo
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.view.GravityCompat
|
||||||
import jp.juggler.subwaytooter.api.entity.TootStatus
|
import jp.juggler.subwaytooter.api.entity.TootStatus
|
||||||
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.SavedAccount
|
import jp.juggler.subwaytooter.table.SavedAccount
|
||||||
|
import jp.juggler.subwaytooter.util.CompletionHelper
|
||||||
import jp.juggler.subwaytooter.util.PostCompleteCallback
|
import jp.juggler.subwaytooter.util.PostCompleteCallback
|
||||||
import jp.juggler.subwaytooter.util.PostImpl
|
import jp.juggler.subwaytooter.util.PostImpl
|
||||||
import jp.juggler.util.hideKeyboard
|
import jp.juggler.util.hideKeyboard
|
||||||
|
@ -14,6 +20,49 @@ import org.jetbrains.anko.imageResource
|
||||||
val ActMain.quickTootText: String
|
val ActMain.quickTootText: String
|
||||||
get() = etQuickToot.text.toString()
|
get() = etQuickToot.text.toString()
|
||||||
|
|
||||||
|
fun ActMain.initUIQuickToot() {
|
||||||
|
etQuickToot.typeface = ActMain.timelineFont
|
||||||
|
|
||||||
|
if (!PrefB.bpQuickTootBar(pref)) {
|
||||||
|
llQuickTootBar.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PrefB.bpDontUseActionButtonWithQuickTootBar(pref)) {
|
||||||
|
etQuickToot.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE
|
||||||
|
etQuickToot.imeOptions = EditorInfo.IME_ACTION_NONE
|
||||||
|
// 最後に指定する必要がある?
|
||||||
|
etQuickToot.maxLines = 5
|
||||||
|
etQuickToot.isVerticalScrollBarEnabled = true
|
||||||
|
etQuickToot.isScrollbarFadingEnabled = false
|
||||||
|
} else {
|
||||||
|
etQuickToot.inputType = InputType.TYPE_CLASS_TEXT
|
||||||
|
etQuickToot.imeOptions = EditorInfo.IME_ACTION_SEND
|
||||||
|
etQuickToot.setOnEditorActionListener(TextView.OnEditorActionListener { _, actionId, _ ->
|
||||||
|
if (actionId == EditorInfo.IME_ACTION_SEND) {
|
||||||
|
btnQuickToot.performClick()
|
||||||
|
return@OnEditorActionListener true
|
||||||
|
}
|
||||||
|
false
|
||||||
|
})
|
||||||
|
// 最後に指定する必要がある?
|
||||||
|
etQuickToot.maxLines = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
completionHelper.attachEditText(
|
||||||
|
llFormRoot,
|
||||||
|
etQuickToot,
|
||||||
|
true,
|
||||||
|
object : CompletionHelper.Callback2 {
|
||||||
|
override fun onTextUpdate() {}
|
||||||
|
|
||||||
|
override fun canOpenPopup(): Boolean {
|
||||||
|
return !drawer.isDrawerOpen(GravityCompat.START)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
showQuickTootVisibility()
|
||||||
|
}
|
||||||
|
|
||||||
fun ActMain.showQuickTootVisibility() {
|
fun ActMain.showQuickTootVisibility() {
|
||||||
btnQuickTootMenu.imageResource =
|
btnQuickTootMenu.imageResource =
|
||||||
when (val resId = Styler.getVisibilityIconId(false, quickTootVisibility)) {
|
when (val resId = Styler.getVisibilityIconId(false, quickTootVisibility)) {
|
||||||
|
|
|
@ -1,19 +1,183 @@
|
||||||
package jp.juggler.subwaytooter
|
package jp.juggler.subwaytooter
|
||||||
|
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
|
import android.graphics.Typeface
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.LinearLayout
|
||||||
import jp.juggler.subwaytooter.api.entity.TootStatus
|
import jp.juggler.subwaytooter.api.entity.TootStatus
|
||||||
import jp.juggler.subwaytooter.span.MyClickableSpan
|
import jp.juggler.subwaytooter.span.MyClickableSpan
|
||||||
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.subwaytooter.view.TabletColumnDivider
|
import jp.juggler.subwaytooter.view.TabletColumnDivider
|
||||||
import jp.juggler.util.attrColor
|
import jp.juggler.util.*
|
||||||
import jp.juggler.util.getAdaptiveRippleDrawableRound
|
|
||||||
import jp.juggler.util.notZero
|
|
||||||
import org.jetbrains.anko.backgroundDrawable
|
import org.jetbrains.anko.backgroundDrawable
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
private val log = LogCategory("ActMainStyle")
|
||||||
|
|
||||||
|
// initUIから呼ばれる
|
||||||
|
fun ActMain.reloadFonts() {
|
||||||
|
var sv = PrefS.spTimelineFont(pref)
|
||||||
|
if (sv.isNotEmpty()) {
|
||||||
|
try {
|
||||||
|
ActMain.timelineFont = Typeface.createFromFile(sv)
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
log.trace(ex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sv = PrefS.spTimelineFontBold(pref)
|
||||||
|
if (sv.isNotEmpty()) {
|
||||||
|
try {
|
||||||
|
ActMain.timeline_font_bold = Typeface.createFromFile(sv)
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
log.trace(ex)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
ActMain.timeline_font_bold = Typeface.create(ActMain.timelineFont, Typeface.BOLD)
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
log.trace(ex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ActMain.parseIconSize(stringPref: StringPref, minDp: Float = 1f): Int {
|
||||||
|
var iconSizeDp = stringPref.defVal.toFloat()
|
||||||
|
try {
|
||||||
|
val sv = stringPref(pref)
|
||||||
|
val fv = if (sv.isEmpty()) Float.NaN else sv.toFloat()
|
||||||
|
if (fv.isFinite() && fv >= minDp) {
|
||||||
|
iconSizeDp = fv
|
||||||
|
}
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
log.trace(ex)
|
||||||
|
}
|
||||||
|
return (0.5f + iconSizeDp * density).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
// initUIから呼ばれる
|
||||||
|
fun ActMain.reloadIconSize() {
|
||||||
|
avatarIconSize = parseIconSize(PrefS.spAvatarIconSize)
|
||||||
|
notificationTlIconSize = parseIconSize(PrefS.spNotificationTlIconSize)
|
||||||
|
ActMain.boostButtonSize = parseIconSize(PrefS.spBoostButtonSize)
|
||||||
|
ActMain.replyIconSize = parseIconSize(PrefS.spReplyIconSize)
|
||||||
|
ActMain.headerIconSize = parseIconSize(PrefS.spHeaderIconSize)
|
||||||
|
ActMain.stripIconSize = parseIconSize(PrefS.spStripIconSize)
|
||||||
|
ActMain.screenBottomPadding = parseIconSize(PrefS.spScreenBottomPadding, minDp = 0f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// initUIから呼ばれる
|
||||||
|
fun ActMain.reloadRoundRatio() {
|
||||||
|
var roundRatio = 33f
|
||||||
|
try {
|
||||||
|
if (PrefB.bpDontRound(pref)) {
|
||||||
|
roundRatio = 0f
|
||||||
|
} else {
|
||||||
|
val sv = PrefS.spRoundRatio(pref)
|
||||||
|
if (sv.isNotEmpty()) {
|
||||||
|
val fv = sv.toFloat()
|
||||||
|
if (fv.isFinite()) {
|
||||||
|
roundRatio = fv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
log.trace(ex)
|
||||||
|
}
|
||||||
|
Styler.round_ratio = clipRange(0f, 1f, roundRatio / 100f) * 0.5f
|
||||||
|
}
|
||||||
|
|
||||||
|
// initUI から呼ばれる
|
||||||
|
fun ActMain.reloadBoostAlpha() {
|
||||||
|
var boostAlpha = 0.8f
|
||||||
|
try {
|
||||||
|
val f = (PrefS.spBoostAlpha.toInt(pref).toFloat() + 0.5f) / 100f
|
||||||
|
boostAlpha = when {
|
||||||
|
f >= 1f -> 1f
|
||||||
|
f < 0f -> 0.66f
|
||||||
|
else -> f
|
||||||
|
}
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
log.trace(ex)
|
||||||
|
}
|
||||||
|
Styler.boostAlpha = boostAlpha
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ActMain.reloadMediaHeight() {
|
||||||
|
var mediaThumbHeightDp = 64
|
||||||
|
val sv = PrefS.spMediaThumbHeight(pref)
|
||||||
|
if (sv.isNotEmpty()) {
|
||||||
|
try {
|
||||||
|
val iv = Integer.parseInt(sv)
|
||||||
|
if (iv >= 32) mediaThumbHeightDp = iv
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
log.trace(ex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
appState.mediaThumbHeight = (0.5f + mediaThumbHeightDp * density).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ActMain.loadColumnMin(density: Float): Int {
|
||||||
|
var x = ActMain.COLUMN_WIDTH_MIN_DP.toFloat()
|
||||||
|
val sv = PrefS.spColumnWidth(pref)
|
||||||
|
if (sv.isNotEmpty()) {
|
||||||
|
try {
|
||||||
|
val fv = sv.toFloat()
|
||||||
|
if (fv.isFinite() && fv >= 100f) {
|
||||||
|
x = fv
|
||||||
|
}
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
log.trace(ex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (0.5f + x * density).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ActMain.justifyWindowContentPortrait() {
|
||||||
|
when (PrefI.ipJustifyWindowContentPortrait(pref)) {
|
||||||
|
PrefI.JWCP_START -> {
|
||||||
|
val iconW = (ActMain.stripIconSize * 1.5f + 0.5f).toInt()
|
||||||
|
val padding = resources.displayMetrics.widthPixels / 2 - iconW
|
||||||
|
|
||||||
|
fun ViewGroup.addViewBeforeLast(v: View) = addView(v, childCount - 1)
|
||||||
|
(svColumnStrip.parent as LinearLayout).addViewBeforeLast(
|
||||||
|
View(this).apply {
|
||||||
|
layoutParams = LinearLayout.LayoutParams(padding, 0)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
llQuickTootBar.addViewBeforeLast(
|
||||||
|
View(this).apply {
|
||||||
|
layoutParams = LinearLayout.LayoutParams(padding, 0)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
PrefI.JWCP_END -> {
|
||||||
|
val iconW = (ActMain.stripIconSize * 1.5f + 0.5f).toInt()
|
||||||
|
val borderWidth = (1f * density + 0.5f).toInt()
|
||||||
|
val padding = resources.displayMetrics.widthPixels / 2 - iconW - borderWidth
|
||||||
|
|
||||||
|
fun ViewGroup.addViewAfterFirst(v: View) = addView(v, 1)
|
||||||
|
(svColumnStrip.parent as LinearLayout).addViewAfterFirst(
|
||||||
|
View(this).apply {
|
||||||
|
layoutParams = LinearLayout.LayoutParams(padding, 0)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
llQuickTootBar.addViewAfterFirst(
|
||||||
|
View(this).apply {
|
||||||
|
layoutParams = LinearLayout.LayoutParams(padding, 0)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////
|
||||||
|
|
||||||
// onStart時に呼ばれる
|
// onStart時に呼ばれる
|
||||||
fun ActMain.reloadTimeZone(){
|
fun ActMain.reloadTimeZone() {
|
||||||
try {
|
try {
|
||||||
var tz = TimeZone.getDefault()
|
var tz = TimeZone.getDefault()
|
||||||
val tzId = PrefS.spTimeZone(pref)
|
val tzId = PrefS.spTimeZone(pref)
|
||||||
|
@ -22,13 +186,13 @@ fun ActMain.reloadTimeZone(){
|
||||||
}
|
}
|
||||||
TootStatus.date_format.timeZone = tz
|
TootStatus.date_format.timeZone = tz
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
ActMain.log.e(ex, "getTimeZone failed.")
|
log.e(ex, "getTimeZone failed.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// onStart時に呼ばれる
|
// onStart時に呼ばれる
|
||||||
// カラーカスタマイズを読み直す
|
// カラーカスタマイズを読み直す
|
||||||
fun ActMain.reloadColors(){
|
fun ActMain.reloadColors() {
|
||||||
ListDivider.color = PrefI.ipListDividerColor(pref)
|
ListDivider.color = PrefI.ipListDividerColor(pref)
|
||||||
TabletColumnDivider.color = PrefI.ipListDividerColor(pref)
|
TabletColumnDivider.color = PrefI.ipListDividerColor(pref)
|
||||||
ItemViewHolder.toot_color_unlisted = PrefI.ipTootColorUnlisted(pref)
|
ItemViewHolder.toot_color_unlisted = PrefI.ipTootColorUnlisted(pref)
|
||||||
|
|
|
@ -30,6 +30,7 @@ import jp.juggler.subwaytooter.view.MyNetworkImageView
|
||||||
import jp.juggler.util.*
|
import jp.juggler.util.*
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
|
import okhttp3.internal.closeQuietly
|
||||||
import ru.gildor.coroutines.okhttp.await
|
import ru.gildor.coroutines.okhttp.await
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
@ -40,7 +41,6 @@ class ActPost : AppCompatActivity(),
|
||||||
MyClickableSpanHandler, AttachmentPicker.Callback {
|
MyClickableSpanHandler, AttachmentPicker.Callback {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
internal val log = LogCategory("ActPost")
|
internal val log = LogCategory("ActPost")
|
||||||
|
|
||||||
var refActPost: WeakReference<ActPost>? = null
|
var refActPost: WeakReference<ActPost>? = null
|
||||||
|
@ -69,8 +69,8 @@ class ActPost : AppCompatActivity(),
|
||||||
/////////////////////////////////////////////////
|
/////////////////////////////////////////////////
|
||||||
|
|
||||||
fun createIntent(
|
fun createIntent(
|
||||||
|
|
||||||
activity: Activity,
|
activity: Activity,
|
||||||
|
|
||||||
accountDbId: Long,
|
accountDbId: Long,
|
||||||
|
|
||||||
multiWindowMode: Boolean,
|
multiWindowMode: Boolean,
|
||||||
|
@ -127,9 +127,12 @@ class ActPost : AppCompatActivity(),
|
||||||
val request = Request.Builder().url(url).build()
|
val request = Request.Builder().url(url).build()
|
||||||
val call = App1.ok_http_client.newCall(request)
|
val call = App1.ok_http_client.newCall(request)
|
||||||
val response = call.await()
|
val response = call.await()
|
||||||
if (response.isSuccessful) return true
|
try {
|
||||||
|
if (response.isSuccessful) return true
|
||||||
log.e(TootApiClient.formatResponse(response, "check_exist failed."))
|
log.e(TootApiClient.formatResponse(response, "check_exist failed."))
|
||||||
|
} finally {
|
||||||
|
response.closeQuietly()
|
||||||
|
}
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
log.trace(ex)
|
log.trace(ex)
|
||||||
}
|
}
|
||||||
|
@ -165,7 +168,7 @@ class ActPost : AppCompatActivity(),
|
||||||
|
|
||||||
lateinit var cbQuote: CheckBox
|
lateinit var cbQuote: CheckBox
|
||||||
|
|
||||||
lateinit var spEnquete: Spinner
|
lateinit var spPollType: Spinner
|
||||||
lateinit var llEnquete: View
|
lateinit var llEnquete: View
|
||||||
lateinit var etChoices: List<MyEditText>
|
lateinit var etChoices: List<MyEditText>
|
||||||
|
|
||||||
|
@ -197,8 +200,6 @@ class ActPost : AppCompatActivity(),
|
||||||
|
|
||||||
///////////////////////////////////////////////////
|
///////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var states = ActPostStates()
|
var states = ActPostStates()
|
||||||
|
|
||||||
internal var account: SavedAccount? = null
|
internal var account: SavedAccount? = null
|
||||||
|
@ -220,18 +221,6 @@ class ActPost : AppCompatActivity(),
|
||||||
|
|
||||||
var paThumbnailTarget: PostAttachment? = null
|
var paThumbnailTarget: PostAttachment? = null
|
||||||
|
|
||||||
val textWatcher: TextWatcher = object : TextWatcher {
|
|
||||||
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun afterTextChanged(editable: Editable) {
|
|
||||||
updateTextCount()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val scrollListener: ViewTreeObserver.OnScrollChangedListener =
|
val scrollListener: ViewTreeObserver.OnScrollChangedListener =
|
||||||
ViewTreeObserver.OnScrollChangedListener { completionHelper.onScrollChanged() }
|
ViewTreeObserver.OnScrollChangedListener { completionHelper.onScrollChanged() }
|
||||||
|
|
||||||
|
@ -292,24 +281,65 @@ class ActPost : AppCompatActivity(),
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
return when {
|
super.onCreate(savedInstanceState)
|
||||||
super.onKeyDown(keyCode, event) -> true
|
if (isMultiWindowPost) ActMain.refActMain?.get()?.closeList?.add(WeakReference(this))
|
||||||
event == null -> false
|
App1.setActivityTheme(this, noActionBar = true)
|
||||||
else -> event.isCtrlPressed
|
appState = App1.getAppState(this)
|
||||||
|
handler = appState.handler
|
||||||
|
pref = appState.pref
|
||||||
|
attachmentUploader = AttachmentUploader(this, handler)
|
||||||
|
attachmentPicker = AttachmentPicker(this, this)
|
||||||
|
density = resources.displayMetrics.density
|
||||||
|
arMushroom.register(this, log)
|
||||||
|
|
||||||
|
initUI()
|
||||||
|
|
||||||
|
when (savedInstanceState) {
|
||||||
|
null -> updateText(intent, confirmed = true, saveDraft = false)
|
||||||
|
else -> restoreState(savedInstanceState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
|
override fun onDestroy() {
|
||||||
val rv = super.onKeyUp(keyCode, event)
|
completionHelper.onDestroy()
|
||||||
if (event?.isCtrlPressed == true) {
|
attachmentUploader.onActivityDestroy()
|
||||||
ActMain.log.d("onKeyUp code=$keyCode rv=$rv")
|
super.onDestroy()
|
||||||
when (keyCode) {
|
}
|
||||||
KeyEvent.KEYCODE_T -> btnPost.performClick()
|
|
||||||
}
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
return true
|
super.onSaveInstanceState(outState)
|
||||||
}
|
saveState(outState)
|
||||||
return rv
|
}
|
||||||
|
|
||||||
|
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
|
||||||
|
super.onRestoreInstanceState(savedInstanceState)
|
||||||
|
showContentWarningEnabled()
|
||||||
|
showMediaAttachment()
|
||||||
|
showVisibility()
|
||||||
|
updateTextCount()
|
||||||
|
showReplyTo()
|
||||||
|
showPoll()
|
||||||
|
showQuotedRenote()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
refActPost = WeakReference(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
// 編集中にホーム画面を押したり他アプリに移動する場合は下書きを保存する
|
||||||
|
// やや過剰な気がするが、自アプリに戻ってくるときにランチャーからアイコンタップされると
|
||||||
|
// メイン画面より上にあるアクティビティはすべて消されてしまうので
|
||||||
|
// このタイミングで保存するしかない
|
||||||
|
if (!isPostComplete) saveDraft()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBackPressed() {
|
||||||
|
saveDraft()
|
||||||
|
super.onBackPressed()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(v: View) {
|
override fun onClick(v: View) {
|
||||||
|
@ -335,93 +365,31 @@ class ActPost : AppCompatActivity(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// unused? for REQUEST_CODE_ATTACHMENT
|
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||||
// fun handleAttachmentResult(ar: ActivityResult?) {
|
return when {
|
||||||
// if (ar?.resultCode == RESULT_OK) {
|
super.onKeyDown(keyCode, event) -> true
|
||||||
// ar.data?.handleGetContentResult(contentResolver)?.let { checkAttachments(it) }
|
event == null -> false
|
||||||
// }
|
else -> event.isCtrlPressed
|
||||||
// }
|
|
||||||
|
|
||||||
override fun onBackPressed() {
|
|
||||||
saveDraft()
|
|
||||||
super.onBackPressed()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
refActPost = WeakReference(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPause() {
|
|
||||||
super.onPause()
|
|
||||||
|
|
||||||
// 編集中にホーム画面を押したり他アプリに移動する場合は下書きを保存する
|
|
||||||
// やや過剰な気がするが、自アプリに戻ってくるときにランチャーからアイコンタップされると
|
|
||||||
// メイン画面より上にあるアクティビティはすべて消されてしまうので
|
|
||||||
// このタイミングで保存するしかない
|
|
||||||
if (!isPostComplete) {
|
|
||||||
saveDraft()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
|
||||||
|
val rv = super.onKeyUp(keyCode, event)
|
||||||
super.onCreate(savedInstanceState)
|
if (event?.isCtrlPressed == true) {
|
||||||
|
ActMain.log.d("onKeyUp code=$keyCode rv=$rv")
|
||||||
if (isMultiWindowPost) ActMain.refActMain?.get()?.closeList?.add(WeakReference(this))
|
when (keyCode) {
|
||||||
|
KeyEvent.KEYCODE_T -> btnPost.performClick()
|
||||||
App1.setActivityTheme(this, noActionBar = true)
|
}
|
||||||
|
return true
|
||||||
appState = App1.getAppState(this)
|
|
||||||
handler = appState.handler
|
|
||||||
pref = appState.pref
|
|
||||||
attachmentUploader = AttachmentUploader(this, handler)
|
|
||||||
attachmentPicker = AttachmentPicker(this, this)
|
|
||||||
|
|
||||||
arMushroom.register(this, log)
|
|
||||||
|
|
||||||
initUI()
|
|
||||||
|
|
||||||
if (savedInstanceState != null) {
|
|
||||||
restoreState(savedInstanceState)
|
|
||||||
} else {
|
|
||||||
updateText(intent, confirmed = true, saveDraft = false)
|
|
||||||
}
|
}
|
||||||
}
|
return rv
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
completionHelper.onDestroy()
|
|
||||||
attachmentUploader.onActivityDestroy()
|
|
||||||
super.onDestroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
|
||||||
super.onSaveInstanceState(outState)
|
|
||||||
saveState(outState)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
|
|
||||||
super.onRestoreInstanceState(savedInstanceState)
|
|
||||||
showContentWarningEnabled()
|
|
||||||
showMediaAttachment()
|
|
||||||
showVisibility()
|
|
||||||
updateTextCount()
|
|
||||||
showReplyTo()
|
|
||||||
showPoll()
|
|
||||||
showQuotedRenote()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMyClickableSpanClicked(viewClicked: View, span: MyClickableSpan) {
|
override fun onMyClickableSpanClicked(viewClicked: View, span: MyClickableSpan) {
|
||||||
openBrowser(span.linkInfo.url)
|
openBrowser(span.linkInfo.url)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRequestPermissionsResult(
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
||||||
requestCode: Int,
|
|
||||||
permissions: Array<String>,
|
|
||||||
grantResults: IntArray,
|
|
||||||
) {
|
|
||||||
attachmentPicker.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
attachmentPicker.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
}
|
}
|
||||||
|
@ -439,12 +407,10 @@ class ActPost : AppCompatActivity(),
|
||||||
}
|
}
|
||||||
|
|
||||||
fun initUI() {
|
fun initUI() {
|
||||||
density = resources.displayMetrics.density
|
|
||||||
|
|
||||||
setContentView(R.layout.act_post)
|
setContentView(R.layout.act_post)
|
||||||
App1.initEdgeToEdge(this)
|
App1.initEdgeToEdge(this)
|
||||||
|
|
||||||
if (PrefB.bpPostButtonBarTop(this)) {
|
if (PrefB.bpPostButtonBarTop(pref)) {
|
||||||
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)
|
||||||
|
@ -480,7 +446,7 @@ class ActPost : AppCompatActivity(),
|
||||||
|
|
||||||
cbQuote = findViewById(R.id.cbQuote)
|
cbQuote = findViewById(R.id.cbQuote)
|
||||||
|
|
||||||
spEnquete = findViewById<Spinner>(R.id.spEnquete).apply {
|
spPollType = findViewById<Spinner>(R.id.spEnquete).apply {
|
||||||
this.adapter = ArrayAdapter(
|
this.adapter = ArrayAdapter(
|
||||||
this@ActPost,
|
this@ActPost,
|
||||||
android.R.layout.simple_spinner_item,
|
android.R.layout.simple_spinner_item,
|
||||||
|
@ -499,17 +465,13 @@ class ActPost : AppCompatActivity(),
|
||||||
updateTextCount()
|
updateTextCount()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onItemSelected(
|
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||||
parent: AdapterView<*>?,
|
|
||||||
view: View?,
|
|
||||||
position: Int,
|
|
||||||
id: Long,
|
|
||||||
) {
|
|
||||||
showPoll()
|
showPoll()
|
||||||
updateTextCount()
|
updateTextCount()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
llEnquete = findViewById(R.id.llEnquete)
|
llEnquete = findViewById(R.id.llEnquete)
|
||||||
llExpire = findViewById(R.id.llExpire)
|
llExpire = findViewById(R.id.llExpire)
|
||||||
cbHideTotals = findViewById(R.id.cbHideTotals)
|
cbHideTotals = findViewById(R.id.cbHideTotals)
|
||||||
|
@ -542,7 +504,6 @@ class ActPost : AppCompatActivity(),
|
||||||
ibSchedule = findViewById(R.id.ibSchedule)
|
ibSchedule = findViewById(R.id.ibSchedule)
|
||||||
ibScheduleReset = findViewById(R.id.ibScheduleReset)
|
ibScheduleReset = findViewById(R.id.ibScheduleReset)
|
||||||
|
|
||||||
|
|
||||||
arrayOf(
|
arrayOf(
|
||||||
ibSchedule,
|
ibSchedule,
|
||||||
ibScheduleReset,
|
ibScheduleReset,
|
||||||
|
@ -559,9 +520,7 @@ class ActPost : AppCompatActivity(),
|
||||||
|
|
||||||
ivMedia.forEach { it.setOnClickListener(this) }
|
ivMedia.forEach { it.setOnClickListener(this) }
|
||||||
|
|
||||||
cbContentWarning.setOnCheckedChangeListener { _, _ ->
|
cbContentWarning.setOnCheckedChangeListener { _, _ -> showContentWarningEnabled() }
|
||||||
showContentWarningEnabled()
|
|
||||||
}
|
|
||||||
|
|
||||||
completionHelper = CompletionHelper(this, pref, appState.handler)
|
completionHelper = CompletionHelper(this, pref, appState.handler)
|
||||||
completionHelper.attachEditText(formRoot, etContent, false, object : CompletionHelper.Callback2 {
|
completionHelper.attachEditText(formRoot, etContent, false, object : CompletionHelper.Callback2 {
|
||||||
|
@ -569,12 +528,23 @@ class ActPost : AppCompatActivity(),
|
||||||
updateTextCount()
|
updateTextCount()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun canOpenPopup(): Boolean {
|
override fun canOpenPopup(): Boolean = true
|
||||||
return true
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
val textWatcher: TextWatcher = object : TextWatcher {
|
||||||
|
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun afterTextChanged(editable: Editable) {
|
||||||
|
updateTextCount()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
etContentWarning.addTextChangedListener(textWatcher)
|
etContentWarning.addTextChangedListener(textWatcher)
|
||||||
|
|
||||||
for (et in etChoices) {
|
for (et in etChoices) {
|
||||||
et.addTextChangedListener(textWatcher)
|
et.addTextChangedListener(textWatcher)
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,6 @@ fun ActPost.decodeAttachments(sv: String) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun ActPost.showMediaAttachment() {
|
fun ActPost.showMediaAttachment() {
|
||||||
if (isFinishing) return
|
if (isFinishing) return
|
||||||
llAttachment.vg(attachmentList.isNotEmpty())
|
llAttachment.vg(attachmentList.isNotEmpty())
|
||||||
|
|
|
@ -74,7 +74,7 @@ fun ActPost.updateTextCount() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
when (spEnquete.selectedItemPosition) {
|
when (spPollType.selectedItemPosition) {
|
||||||
1 -> checkEnqueteLength()
|
1 -> checkEnqueteLength()
|
||||||
|
|
||||||
2 -> {
|
2 -> {
|
||||||
|
|
|
@ -102,7 +102,7 @@ fun ActPost.resetText() {
|
||||||
attachmentList.clear()
|
attachmentList.clear()
|
||||||
cbQuote.isChecked = false
|
cbQuote.isChecked = false
|
||||||
etContent.setText("")
|
etContent.setText("")
|
||||||
spEnquete.setSelection(0, false)
|
spPollType.setSelection(0, false)
|
||||||
etChoices.forEach { it.setText("") }
|
etChoices.forEach { it.setText("") }
|
||||||
accountList = SavedAccount.loadAccountList(this)
|
accountList = SavedAccount.loadAccountList(this)
|
||||||
SavedAccount.sort(accountList)
|
SavedAccount.sort(accountList)
|
||||||
|
@ -132,7 +132,6 @@ fun ActPost.afterUpdateText() {
|
||||||
updateTextCount()
|
updateTextCount()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 初期化時と投稿完了時とリセット確認後に呼ばれる
|
// 初期化時と投稿完了時とリセット確認後に呼ばれる
|
||||||
fun ActPost.updateText(
|
fun ActPost.updateText(
|
||||||
intent: Intent,
|
intent: Intent,
|
||||||
|
@ -294,7 +293,7 @@ fun ActPost.performPost() {
|
||||||
var pollExpireSeconds = 0
|
var pollExpireSeconds = 0
|
||||||
var pollHideTotals = false
|
var pollHideTotals = false
|
||||||
var pollMultipleChoice = false
|
var pollMultipleChoice = false
|
||||||
when (spEnquete.selectedItemPosition) {
|
when (spPollType.selectedItemPosition) {
|
||||||
1 -> {
|
1 -> {
|
||||||
pollType = TootPollsType.Mastodon
|
pollType = TootPollsType.Mastodon
|
||||||
pollItems = pollChoiceList()
|
pollItems = pollChoiceList()
|
||||||
|
|
|
@ -5,7 +5,7 @@ import jp.juggler.util.notEmpty
|
||||||
import jp.juggler.util.vg
|
import jp.juggler.util.vg
|
||||||
|
|
||||||
fun ActPost.showPoll() {
|
fun ActPost.showPoll() {
|
||||||
val i = spEnquete.selectedItemPosition
|
val i = spPollType.selectedItemPosition
|
||||||
llEnquete.vg(i != 0)
|
llEnquete.vg(i != 0)
|
||||||
llExpire.vg(i == 1)
|
llExpire.vg(i == 1)
|
||||||
cbHideTotals.vg(i == 1)
|
cbHideTotals.vg(i == 1)
|
||||||
|
@ -13,9 +13,9 @@ fun ActPost.showPoll() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 投票が有効で何か入力済みなら真
|
// 投票が有効で何か入力済みなら真
|
||||||
fun ActPost.hasPoll():Boolean{
|
fun ActPost.hasPoll(): Boolean {
|
||||||
if( spEnquete.selectedItemPosition <= 0) return false
|
if (spPollType.selectedItemPosition <= 0) return false
|
||||||
return etChoices.any{ it.text.toString().isNotBlank()}
|
return etChoices.any { it.text.toString().isNotBlank() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ActPost.pollChoiceList() = ArrayList<String>().apply {
|
fun ActPost.pollChoiceList() = ArrayList<String>().apply {
|
||||||
|
@ -30,4 +30,3 @@ fun ActPost.pollExpireSeconds(): Int {
|
||||||
val m = etExpireMinutes.text.toString().trim().toDoubleOrNull().finiteOrZero()
|
val m = etExpireMinutes.text.toString().trim().toDoubleOrNull().finiteOrZero()
|
||||||
return (d * 86400.0 + h * 3600.0 + m * 60.0).toInt()
|
return (d * 86400.0 + h * 3600.0 + m * 60.0).toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,13 +38,12 @@ private const val DRAFT_POLL_EXPIRE_MINUTE = "poll_expire_minute"
|
||||||
private const val DRAFT_ENQUETE_ITEMS = "enquete_items"
|
private const val DRAFT_ENQUETE_ITEMS = "enquete_items"
|
||||||
private const val DRAFT_QUOTE = "quotedRenote" // 歴史的な理由で名前がMisskey用になってる
|
private const val DRAFT_QUOTE = "quotedRenote" // 歴史的な理由で名前がMisskey用になってる
|
||||||
|
|
||||||
|
|
||||||
fun ActPost.saveDraft() {
|
fun ActPost.saveDraft() {
|
||||||
val content = etContent.text.toString()
|
val content = etContent.text.toString()
|
||||||
val contentWarning =
|
val contentWarning =
|
||||||
if (cbContentWarning.isChecked) etContentWarning.text.toString() else ""
|
if (cbContentWarning.isChecked) etContentWarning.text.toString() else ""
|
||||||
|
|
||||||
val isEnquete = spEnquete.selectedItemPosition > 0
|
val isEnquete = spPollType.selectedItemPosition > 0
|
||||||
|
|
||||||
val strChoice = arrayOf(
|
val strChoice = arrayOf(
|
||||||
if (isEnquete) etChoices[0].text.toString() else "",
|
if (isEnquete) etChoices[0].text.toString() else "",
|
||||||
|
@ -88,7 +87,7 @@ fun ActPost.saveDraft() {
|
||||||
// deprecated. but still used in old draft.
|
// deprecated. but still used in old draft.
|
||||||
// json.put(DRAFT_IS_ENQUETE, isEnquete)
|
// json.put(DRAFT_IS_ENQUETE, isEnquete)
|
||||||
|
|
||||||
json[DRAFT_POLL_TYPE] = spEnquete.selectedItemPosition.toPollTypeString()
|
json[DRAFT_POLL_TYPE] = spPollType.selectedItemPosition.toPollTypeString()
|
||||||
|
|
||||||
json[DRAFT_POLL_MULTIPLE] = cbMultipleChoice.isChecked
|
json[DRAFT_POLL_MULTIPLE] = cbMultipleChoice.isChecked
|
||||||
json[DRAFT_POLL_HIDE_TOTALS] = cbHideTotals.isChecked
|
json[DRAFT_POLL_HIDE_TOTALS] = cbHideTotals.isChecked
|
||||||
|
@ -236,11 +235,11 @@ fun ActPost.restoreDraft(draft: JsonObject) {
|
||||||
|
|
||||||
val sv = draft.string(DRAFT_POLL_TYPE)
|
val sv = draft.string(DRAFT_POLL_TYPE)
|
||||||
if (sv != null) {
|
if (sv != null) {
|
||||||
spEnquete.setSelection(sv.toPollTypeIndex())
|
spPollType.setSelection(sv.toPollTypeIndex())
|
||||||
} else {
|
} else {
|
||||||
// old draft
|
// old draft
|
||||||
val bv = draft.optBoolean(DRAFT_IS_ENQUETE, false)
|
val bv = draft.optBoolean(DRAFT_IS_ENQUETE, false)
|
||||||
spEnquete.setSelection(if (bv) 2 else 0)
|
spPollType.setSelection(if (bv) 2 else 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
cbMultipleChoice.isChecked = draft.optBoolean(DRAFT_POLL_MULTIPLE)
|
cbMultipleChoice.isChecked = draft.optBoolean(DRAFT_POLL_MULTIPLE)
|
||||||
|
@ -369,7 +368,7 @@ fun ActPost.initializeFromRedraftStatus(account: SavedAccount, jsonText: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
spEnquete.setSelection(
|
spPollType.setSelection(
|
||||||
if (srcEnquete.pollType == TootPollsType.FriendsNico) {
|
if (srcEnquete.pollType == TootPollsType.FriendsNico) {
|
||||||
2
|
2
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package jp.juggler.subwaytooter
|
package jp.juggler.subwaytooter
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import jp.juggler.subwaytooter.api.TootParser
|
import jp.juggler.subwaytooter.api.TootParser
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
package jp.juggler.subwaytooter
|
package jp.juggler.subwaytooter
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import jp.juggler.subwaytooter.api.entity.TootVisibility
|
|
||||||
|
|
||||||
fun ActPost.showContentWarningEnabled() {
|
fun ActPost.showContentWarningEnabled() {
|
||||||
etContentWarning.visibility = if (cbContentWarning.isChecked) View.VISIBLE else View.GONE
|
etContentWarning.visibility = if (cbContentWarning.isChecked) View.VISIBLE else View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -611,4 +611,4 @@ class App1 : Application() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val kJson = kotlinx.serialization.json.Json{ ignoreUnknownKeys = true }
|
val kJson = kotlinx.serialization.json.Json { ignoreUnknownKeys = true }
|
||||||
|
|
|
@ -4,11 +4,10 @@ import android.content.Context
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.util.LruCache
|
import android.util.LruCache
|
||||||
import androidx.annotation.RawRes
|
import androidx.annotation.RawRes
|
||||||
import jp.juggler.subwaytooter.api.ApiPath.READ_LIMIT
|
|
||||||
import jp.juggler.subwaytooter.Column.Companion.log
|
import jp.juggler.subwaytooter.Column.Companion.log
|
||||||
import jp.juggler.subwaytooter.api.*
|
import jp.juggler.subwaytooter.api.*
|
||||||
|
import jp.juggler.subwaytooter.api.ApiPath.READ_LIMIT
|
||||||
import jp.juggler.subwaytooter.api.entity.*
|
import jp.juggler.subwaytooter.api.entity.*
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
|
||||||
import jp.juggler.util.*
|
import jp.juggler.util.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
@ -826,94 +825,6 @@ fun Column.makeProfileStatusesUrl(profileId: EntityId?): String {
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
val misskeyArrayFinderUsers = { it: JsonObject ->
|
|
||||||
it.jsonArray("users")
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// account list parser
|
|
||||||
|
|
||||||
val nullArrayFinder: (JsonObject) -> JsonArray? =
|
|
||||||
{ null }
|
|
||||||
|
|
||||||
val defaultAccountListParser: (parser: TootParser, jsonArray: JsonArray) -> List<TootAccountRef> =
|
|
||||||
{ parser, jsonArray -> parser.accountList(jsonArray) }
|
|
||||||
|
|
||||||
private fun misskeyUnwrapRelationAccount(parser: TootParser, srcList: JsonArray, key: String) =
|
|
||||||
srcList.objectList().mapNotNull {
|
|
||||||
when (val relationId = EntityId.mayNull(it.string("id"))) {
|
|
||||||
null -> null
|
|
||||||
else -> TootAccountRef.mayNull(parser, parser.account(it.jsonObject(key)))
|
|
||||||
?.apply { _orderId = relationId }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val misskey11FollowingParser: (TootParser, JsonArray) -> List<TootAccountRef> =
|
|
||||||
{ parser, jsonArray -> misskeyUnwrapRelationAccount(parser, jsonArray, "followee") }
|
|
||||||
|
|
||||||
val misskey11FollowersParser: (TootParser, JsonArray) -> List<TootAccountRef> =
|
|
||||||
{ parser, jsonArray -> misskeyUnwrapRelationAccount(parser, jsonArray, "follower") }
|
|
||||||
|
|
||||||
val misskeyCustomParserFollowRequest: (TootParser, JsonArray) -> List<TootAccountRef> =
|
|
||||||
{ parser, jsonArray -> misskeyUnwrapRelationAccount(parser, jsonArray, "follower") }
|
|
||||||
|
|
||||||
val misskeyCustomParserMutes: (TootParser, JsonArray) -> List<TootAccountRef> =
|
|
||||||
{ parser, jsonArray -> misskeyUnwrapRelationAccount(parser, jsonArray, "mutee") }
|
|
||||||
|
|
||||||
val misskeyCustomParserBlocks: (TootParser, JsonArray) -> List<TootAccountRef> =
|
|
||||||
{ parser, jsonArray -> misskeyUnwrapRelationAccount(parser, jsonArray, "blockee") }
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// status list parser
|
|
||||||
|
|
||||||
val defaultStatusListParser: (parser: TootParser, jsonArray: JsonArray) -> List<TootStatus> =
|
|
||||||
{ parser, jsonArray -> parser.statusList(jsonArray) }
|
|
||||||
|
|
||||||
val misskeyCustomParserFavorites: (TootParser, JsonArray) -> List<TootStatus> =
|
|
||||||
{ parser, jsonArray ->
|
|
||||||
jsonArray.objectList().mapNotNull {
|
|
||||||
when (val relationId = EntityId.mayNull(it.string("id"))) {
|
|
||||||
null -> null
|
|
||||||
else -> parser.status(it.jsonObject("note"))?.apply {
|
|
||||||
favourited = true
|
|
||||||
_orderId = relationId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// notification list parser
|
|
||||||
|
|
||||||
val defaultNotificationListParser: (parser: TootParser, jsonArray: JsonArray) -> List<TootNotification> =
|
|
||||||
{ parser, jsonArray -> parser.notificationList(jsonArray) }
|
|
||||||
|
|
||||||
val defaultDomainBlockListParser: (parser: TootParser, jsonArray: JsonArray) -> List<TootDomainBlock> =
|
|
||||||
{ _, jsonArray -> TootDomainBlock.parseList(jsonArray) }
|
|
||||||
|
|
||||||
val defaultReportListParser: (parser: TootParser, jsonArray: JsonArray) -> List<TootReport> =
|
|
||||||
{ _, jsonArray -> parseList(::TootReport, jsonArray) }
|
|
||||||
|
|
||||||
val defaultConversationSummaryListParser: (parser: TootParser, jsonArray: JsonArray) -> List<TootConversationSummary> =
|
|
||||||
{ parser, jsonArray -> parseList(::TootConversationSummary, parser, jsonArray) }
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
val mastodonFollowSuggestion2ListParser: (parser: TootParser, jsonArray: JsonArray) -> List<TootAccountRef> =
|
|
||||||
{ parser, jsonArray ->
|
|
||||||
TootAccountRef.wrapList(parser,
|
|
||||||
jsonArray.objectList().mapNotNull {
|
|
||||||
parser.account(it.jsonObject("account"))?.also { a ->
|
|
||||||
SuggestionSource.set(
|
|
||||||
(parser.linkHelper as? SavedAccount)?.db_id,
|
|
||||||
a.acct,
|
|
||||||
it.string("source")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
private const val DIR_BACKGROUND_IMAGE = "columnBackground"
|
private const val DIR_BACKGROUND_IMAGE = "columnBackground"
|
||||||
|
|
|
@ -7,6 +7,7 @@ import jp.juggler.subwaytooter.notification.PollingWorker
|
||||||
import jp.juggler.subwaytooter.streaming.StreamManager
|
import jp.juggler.subwaytooter.streaming.StreamManager
|
||||||
import jp.juggler.subwaytooter.streaming.StreamStatus
|
import jp.juggler.subwaytooter.streaming.StreamStatus
|
||||||
import jp.juggler.subwaytooter.util.ScrollPosition
|
import jp.juggler.subwaytooter.util.ScrollPosition
|
||||||
|
import jp.juggler.util.notEmpty
|
||||||
import jp.juggler.util.runOnMainLooper
|
import jp.juggler.util.runOnMainLooper
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
|
@ -73,37 +74,22 @@ fun Column.mergeStreamingMessage() {
|
||||||
|
|
||||||
lastShowStreamData.set(now)
|
lastShowStreamData.set(now)
|
||||||
|
|
||||||
|
// read items while queue is not empty
|
||||||
val tmpList = ArrayList<TimelineItem>()
|
val tmpList = ArrayList<TimelineItem>()
|
||||||
while (true) tmpList.add(streamDataQueue.poll() ?: break)
|
.apply { while (true) add(streamDataQueue.poll() ?: break) }.notEmpty()
|
||||||
if (tmpList.isEmpty()) return
|
?: return
|
||||||
|
|
||||||
// キューから読めた件数が0の場合を除き、少し後に再処理させることでマージ漏れを防ぐ
|
// キューから読めた件数が0の場合を除き、少し後に再処理させることでマージ漏れを防ぐ
|
||||||
handler.postDelayed(procMergeStreamingMessage, 333L)
|
handler.postDelayed(procMergeStreamingMessage, 333L)
|
||||||
|
|
||||||
// ストリーミングされるデータは全てID順に並んでいるはず
|
// orderId順ソートを徹底する
|
||||||
tmpList.sortByDescending { it.getOrderId() }
|
tmpList.sortByDescending { it.getOrderId() }
|
||||||
|
|
||||||
val listNew = duplicateMap.filterDuplicate(tmpList)
|
// 既にカラム中にあるデータは除去する
|
||||||
if (listNew.isEmpty()) return
|
val listNew = duplicateMap.filterDuplicate(tmpList).notEmpty() ?: return
|
||||||
|
|
||||||
for (item in listNew) {
|
sendToSpeech(listNew)
|
||||||
if (enableSpeech && item is TootStatus) {
|
injectToPollingWorker(listNew)
|
||||||
appState.addSpeech(item.reblog ?: item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 通知カラムならストリーミング経由で届いたデータを通知ワーカーに伝達する
|
|
||||||
if (isNotificationColumn) {
|
|
||||||
val list = ArrayList<TootNotification>()
|
|
||||||
for (o in listNew) {
|
|
||||||
if (o is TootNotification) {
|
|
||||||
list.add(o)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (list.isNotEmpty()) {
|
|
||||||
PollingWorker.injectData(context, accessInfo, list)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 最新のIDをsince_idとして覚える(ソートはしない)
|
// 最新のIDをsince_idとして覚える(ソートはしない)
|
||||||
var newIdMax: EntityId? = null
|
var newIdMax: EntityId? = null
|
||||||
|
@ -154,17 +140,7 @@ fun Column.mergeStreamingMessage() {
|
||||||
// 画面復帰時の自動リフレッシュではギャップが残る可能性がある
|
// 画面復帰時の自動リフレッシュではギャップが残る可能性がある
|
||||||
if (bPutGap) {
|
if (bPutGap) {
|
||||||
bPutGap = false
|
bPutGap = false
|
||||||
try {
|
addGapAfterStreaming(listNew, newIdMin)
|
||||||
if (listData.size > 0 && newIdMin != null) {
|
|
||||||
val since = listData[0].getOrderId()
|
|
||||||
if (newIdMin > since) {
|
|
||||||
val gap = TootGap(newIdMin, since)
|
|
||||||
listNew.add(gap)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (ex: Throwable) {
|
|
||||||
Column.log.e(ex, "can't put gap.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val changeList = ArrayList<AdapterChange>()
|
val changeList = ArrayList<AdapterChange>()
|
||||||
|
@ -192,8 +168,49 @@ fun Column.mergeStreamingMessage() {
|
||||||
listData.addAll(0, listNew)
|
listData.addAll(0, listNew)
|
||||||
|
|
||||||
fireShowContent(reason = "mergeStreamingMessage", changeList = changeList)
|
fireShowContent(reason = "mergeStreamingMessage", changeList = changeList)
|
||||||
|
scrollAfterStreaming(added, holderSp, restoreIdx, restoreY)
|
||||||
|
updateMisskeyCapture()
|
||||||
|
}
|
||||||
|
|
||||||
if (holder != null) {
|
// 通知カラムならストリーミング経由で届いたデータを通知ワーカーに伝達する
|
||||||
|
private fun Column.injectToPollingWorker(listNew: ArrayList<TimelineItem>) {
|
||||||
|
if (!isNotificationColumn) return
|
||||||
|
listNew.mapNotNull { it as? TootNotification }.notEmpty()
|
||||||
|
?.let { PollingWorker.injectData(context, accessInfo, it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Column.sendToSpeech(listNew: ArrayList<TimelineItem>) {
|
||||||
|
if (!enableSpeech) return
|
||||||
|
listNew.mapNotNull { it as? TootStatus }
|
||||||
|
.forEach { appState.addSpeech(it.reblog ?: it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Column.addGapAfterStreaming(listNew: ArrayList<TimelineItem>, newIdMin: EntityId?) {
|
||||||
|
try {
|
||||||
|
if (listData.size > 0 && newIdMin != null) {
|
||||||
|
val since = listData[0].getOrderId()
|
||||||
|
if (newIdMin > since) {
|
||||||
|
val gap = TootGap(newIdMin, since)
|
||||||
|
listNew.add(gap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
Column.log.e(ex, "can't put gap.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Column.scrollAfterStreaming(added: Int, holderSp: ScrollPosition?, restoreIdx: Int, restoreY: Int) {
|
||||||
|
val holder = viewHolder
|
||||||
|
if (holder == null) {
|
||||||
|
val scrollSave = this.scrollSave
|
||||||
|
when {
|
||||||
|
// スクロール位置が先頭なら先頭のまま
|
||||||
|
scrollSave == null || scrollSave.isHead -> Unit
|
||||||
|
|
||||||
|
// 現在の要素が表示され続けるようにしたい
|
||||||
|
else -> scrollSave.adapterIndex += added
|
||||||
|
}
|
||||||
|
} else {
|
||||||
when {
|
when {
|
||||||
holderSp == null -> {
|
holderSp == null -> {
|
||||||
// スクロール位置が先頭なら先頭にする
|
// スクロール位置が先頭なら先頭にする
|
||||||
|
@ -218,19 +235,7 @@ fun Column.mergeStreamingMessage() {
|
||||||
holder.setListItemTop(restoreIdx + added, restoreY)
|
holder.setListItemTop(restoreIdx + added, restoreY)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
val scrollSave = this.scrollSave
|
|
||||||
when {
|
|
||||||
// スクロール位置が先頭なら先頭のまま
|
|
||||||
scrollSave == null || scrollSave.isHead -> {
|
|
||||||
}
|
|
||||||
|
|
||||||
// 現在の要素が表示され続けるようにしたい
|
|
||||||
else -> scrollSave.adapterIndex += added
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateMisskeyCapture()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Column.runOnMainLooperForStreamingEvent(proc: () -> Unit) {
|
fun Column.runOnMainLooperForStreamingEvent(proc: () -> Unit) {
|
||||||
|
|
|
@ -3,6 +3,7 @@ package jp.juggler.subwaytooter
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
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.notification.PollingWorker
|
import jp.juggler.subwaytooter.notification.PollingWorker
|
||||||
import jp.juggler.util.*
|
import jp.juggler.util.*
|
||||||
import java.lang.StringBuilder
|
import java.lang.StringBuilder
|
||||||
|
|
|
@ -3,6 +3,7 @@ package jp.juggler.subwaytooter
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
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.notification.PollingWorker
|
import jp.juggler.subwaytooter.notification.PollingWorker
|
||||||
import jp.juggler.subwaytooter.util.OpenSticker
|
import jp.juggler.subwaytooter.util.OpenSticker
|
||||||
import jp.juggler.util.*
|
import jp.juggler.util.*
|
||||||
|
|
|
@ -3,6 +3,7 @@ package jp.juggler.subwaytooter
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
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.util.ScrollPosition
|
import jp.juggler.subwaytooter.util.ScrollPosition
|
||||||
import jp.juggler.util.*
|
import jp.juggler.util.*
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import jp.juggler.subwaytooter.api.ApiPath
|
||||||
import jp.juggler.subwaytooter.api.TootApiClient
|
import jp.juggler.subwaytooter.api.TootApiClient
|
||||||
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.finder.*
|
||||||
import jp.juggler.subwaytooter.search.MspHelper.loadingMSP
|
import jp.juggler.subwaytooter.search.MspHelper.loadingMSP
|
||||||
import jp.juggler.subwaytooter.search.MspHelper.refreshMSP
|
import jp.juggler.subwaytooter.search.MspHelper.refreshMSP
|
||||||
import jp.juggler.subwaytooter.search.NotestockHelper.loadingNotestock
|
import jp.juggler.subwaytooter.search.NotestockHelper.loadingNotestock
|
||||||
|
|
|
@ -635,6 +635,7 @@ internal class DlgContextMenu(
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("ComplexMethod")
|
||||||
private fun ActMain.onClickUser(v: View, pos: Int, who: TootAccount, whoRef: TootAccountRef): Boolean {
|
private fun ActMain.onClickUser(v: View, pos: Int, who: TootAccount, whoRef: TootAccountRef): Boolean {
|
||||||
when (v.id) {
|
when (v.id) {
|
||||||
R.id.btnReportUser -> userReportForm(accessInfo, who)
|
R.id.btnReportUser -> userReportForm(accessInfo, who)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package jp.juggler.subwaytooter
|
package jp.juggler.subwaytooter
|
||||||
|
|
||||||
|
import android.text.Spannable
|
||||||
import android.text.SpannableStringBuilder
|
import android.text.SpannableStringBuilder
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
|
@ -51,34 +52,22 @@ fun ItemViewHolder.showStatus(status: TootStatus, colorBg: Int = 0) {
|
||||||
llStatus.visibility = View.VISIBLE
|
llStatus.visibility = View.VISIBLE
|
||||||
|
|
||||||
if (status.conversation_main) {
|
if (status.conversation_main) {
|
||||||
|
PrefI.ipConversationMainTootBgColor(activity.pref).notZero()
|
||||||
val conversationMainBgColor =
|
?: (activity.attrColor(R.attr.colorImageButtonAccent) and 0xffffff) or 0x20000000
|
||||||
PrefI.ipConversationMainTootBgColor(activity.pref).notZero()
|
|
||||||
?: (activity.attrColor(R.attr.colorImageButtonAccent) and 0xffffff) or 0x20000000
|
|
||||||
|
|
||||||
this.viewRoot.setBackgroundColor(conversationMainBgColor)
|
|
||||||
} else {
|
} else {
|
||||||
val c = colorBg.notZero()
|
colorBg.notZero() ?: when (status.bookmarked) {
|
||||||
|
true -> PrefI.ipEventBgColorBookmark(App1.pref)
|
||||||
?: when (status.bookmarked) {
|
false -> 0
|
||||||
true -> PrefI.ipEventBgColorBookmark(App1.pref)
|
}.notZero() ?: when (status.getBackgroundColorType(accessInfo)) {
|
||||||
false -> 0
|
TootVisibility.UnlistedHome -> ItemViewHolder.toot_color_unlisted
|
||||||
}.notZero()
|
TootVisibility.PrivateFollowers -> ItemViewHolder.toot_color_follower
|
||||||
|
TootVisibility.DirectSpecified -> ItemViewHolder.toot_color_direct_user
|
||||||
?: when (status.getBackgroundColorType(accessInfo)) {
|
TootVisibility.DirectPrivate -> ItemViewHolder.toot_color_direct_me
|
||||||
TootVisibility.UnlistedHome -> ItemViewHolder.toot_color_unlisted
|
// TODO add color setting for limited?
|
||||||
TootVisibility.PrivateFollowers -> ItemViewHolder.toot_color_follower
|
TootVisibility.Limited -> ItemViewHolder.toot_color_follower
|
||||||
TootVisibility.DirectSpecified -> ItemViewHolder.toot_color_direct_user
|
else -> 0
|
||||||
TootVisibility.DirectPrivate -> ItemViewHolder.toot_color_direct_me
|
}.notZero()
|
||||||
// TODO add color setting for limited?
|
}?.let { viewRoot.backgroundColor = it }
|
||||||
TootVisibility.Limited -> ItemViewHolder.toot_color_follower
|
|
||||||
else -> 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c != 0) {
|
|
||||||
this.viewRoot.backgroundColor = c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
showStatusTime(activity, tvTime, who = status.account, status = status)
|
showStatusTime(activity, tvTime, who = status.account, status = status)
|
||||||
|
|
||||||
|
@ -88,11 +77,6 @@ fun ItemViewHolder.showStatus(status: TootStatus, colorBg: Int = 0) {
|
||||||
|
|
||||||
setAcct(tvAcct, accessInfo, who)
|
setAcct(tvAcct, accessInfo, who)
|
||||||
|
|
||||||
// if(who == null) {
|
|
||||||
// tvName.text = "?"
|
|
||||||
// name_invalidator.register(null)
|
|
||||||
// ivThumbnail.setImageUrl(activity.pref, 16f, null, null)
|
|
||||||
// } else {
|
|
||||||
tvName.text = whoRef.decoded_display_name
|
tvName.text = whoRef.decoded_display_name
|
||||||
nameInvalidator.register(whoRef.decoded_display_name)
|
nameInvalidator.register(whoRef.decoded_display_name)
|
||||||
ivAvatar.setImageUrl(
|
ivAvatar.setImageUrl(
|
||||||
|
@ -101,34 +85,23 @@ fun ItemViewHolder.showStatus(status: TootStatus, colorBg: Int = 0) {
|
||||||
accessInfo.supplyBaseUrl(who.avatar_static),
|
accessInfo.supplyBaseUrl(who.avatar_static),
|
||||||
accessInfo.supplyBaseUrl(who.avatar)
|
accessInfo.supplyBaseUrl(who.avatar)
|
||||||
)
|
)
|
||||||
// }
|
|
||||||
|
|
||||||
showOpenSticker(who)
|
showOpenSticker(who)
|
||||||
|
|
||||||
var content = status.decoded_content
|
val modifiedContent = if (status.time_deleted_at > 0L) {
|
||||||
|
SpannableStringBuilder()
|
||||||
// ニコフレのアンケートの表示
|
.append('(')
|
||||||
val enquete = status.enquete
|
.append(
|
||||||
when {
|
activity.getString(
|
||||||
enquete == null -> {
|
R.string.deleted_at,
|
||||||
}
|
TootStatus.formatTime(activity, status.time_deleted_at, true)
|
||||||
|
)
|
||||||
enquete.pollType == TootPollsType.FriendsNico && enquete.type != TootPolls.TYPE_ENQUETE -> {
|
)
|
||||||
// フレニコの投票の結果表示は普通にテキストを表示するだけでよい
|
.append(')')
|
||||||
}
|
} else {
|
||||||
|
showPoll(status) ?: status.decoded_content
|
||||||
else -> {
|
|
||||||
|
|
||||||
// アンケートの本文を上書きする
|
|
||||||
val question = enquete.decoded_question
|
|
||||||
if (question.isNotBlank()) content = question
|
|
||||||
|
|
||||||
showEnqueteItems(status, enquete)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
showPreviewCard(status)
|
|
||||||
|
|
||||||
// if( status.decoded_tags == null ){
|
// if( status.decoded_tags == null ){
|
||||||
// tvTags.setVisibility( View.GONE );
|
// tvTags.setVisibility( View.GONE );
|
||||||
// }else{
|
// }else{
|
||||||
|
@ -143,27 +116,41 @@ fun ItemViewHolder.showStatus(status: TootStatus, colorBg: Int = 0) {
|
||||||
tvMentions.text = status.decoded_mentions
|
tvMentions.text = status.decoded_mentions
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status.time_deleted_at > 0L) {
|
tvContent.text = modifiedContent
|
||||||
val s = SpannableStringBuilder()
|
contentInvalidator.register(modifiedContent)
|
||||||
.append('(')
|
|
||||||
.append(
|
|
||||||
activity.getString(
|
|
||||||
R.string.deleted_at,
|
|
||||||
TootStatus.formatTime(activity, status.time_deleted_at, true)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.append(')')
|
|
||||||
content = s
|
|
||||||
}
|
|
||||||
|
|
||||||
tvContent.text = content
|
activity.checkAutoCW(status, modifiedContent)
|
||||||
contentInvalidator.register(content)
|
|
||||||
|
|
||||||
activity.checkAutoCW(status, content)
|
|
||||||
val r = status.auto_cw
|
val r = status.auto_cw
|
||||||
|
|
||||||
tvContent.minLines = r?.originalLineCount ?: -1
|
tvContent.minLines = r?.originalLineCount ?: -1
|
||||||
|
|
||||||
|
showPreviewCard(status)
|
||||||
|
showSpoilerTextAndContent(status)
|
||||||
|
showAttachments(status)
|
||||||
|
makeReactionsView(status)
|
||||||
|
buttonsForStatus?.bind(status, (item as? TootNotification))
|
||||||
|
showApplicationAndLanguage(status)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 投票の表示
|
||||||
|
// returns modified decoded_content or null
|
||||||
|
private fun ItemViewHolder.showPoll(status: TootStatus): Spannable? {
|
||||||
|
val enquete = status.enquete
|
||||||
|
return when {
|
||||||
|
enquete == null -> null
|
||||||
|
|
||||||
|
// フレニコの投票の結果表示は普通にテキストを表示するだけでよい
|
||||||
|
enquete.pollType == TootPollsType.FriendsNico && enquete.type != TootPolls.TYPE_ENQUETE -> null
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
showEnqueteItems(status, enquete)
|
||||||
|
// アンケートの本文を使ってcontentを上書きする
|
||||||
|
enquete.decoded_question.notBlank()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ItemViewHolder.showSpoilerTextAndContent(status: TootStatus) {
|
||||||
|
val r = status.auto_cw
|
||||||
val decodedSpoilerText = status.decoded_spoiler_text
|
val decodedSpoilerText = status.decoded_spoiler_text
|
||||||
when {
|
when {
|
||||||
decodedSpoilerText.isNotEmpty() -> {
|
decodedSpoilerText.isNotEmpty() -> {
|
||||||
|
@ -172,7 +159,7 @@ fun ItemViewHolder.showStatus(status: TootStatus, colorBg: Int = 0) {
|
||||||
tvContentWarning.text = status.decoded_spoiler_text
|
tvContentWarning.text = status.decoded_spoiler_text
|
||||||
spoilerInvalidator.register(status.decoded_spoiler_text)
|
spoilerInvalidator.register(status.decoded_spoiler_text)
|
||||||
val cwShown = ContentWarning.isShown(status, accessInfo.expand_cw)
|
val cwShown = ContentWarning.isShown(status, accessInfo.expand_cw)
|
||||||
showContent(cwShown)
|
setContentVisibility(cwShown)
|
||||||
}
|
}
|
||||||
|
|
||||||
r?.decodedSpoilerText != null -> {
|
r?.decodedSpoilerText != null -> {
|
||||||
|
@ -181,7 +168,7 @@ fun ItemViewHolder.showStatus(status: TootStatus, colorBg: Int = 0) {
|
||||||
tvContentWarning.text = r.decodedSpoilerText
|
tvContentWarning.text = r.decodedSpoilerText
|
||||||
spoilerInvalidator.register(r.decodedSpoilerText)
|
spoilerInvalidator.register(r.decodedSpoilerText)
|
||||||
val cwShown = ContentWarning.isShown(status, accessInfo.expand_cw)
|
val cwShown = ContentWarning.isShown(status, accessInfo.expand_cw)
|
||||||
showContent(cwShown)
|
setContentVisibility(cwShown)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
|
@ -190,55 +177,25 @@ fun ItemViewHolder.showStatus(status: TootStatus, colorBg: Int = 0) {
|
||||||
llContents.visibility = View.VISIBLE
|
llContents.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val mediaAttachments = status.media_attachments
|
private fun ItemViewHolder.setContentVisibility(shown: Boolean) {
|
||||||
if (mediaAttachments == null || mediaAttachments.isEmpty()) {
|
llContents.visibility = if (shown) View.VISIBLE else View.GONE
|
||||||
flMedia.visibility = View.GONE
|
btnContentWarning.setText(if (shown) R.string.hide else R.string.show)
|
||||||
llMedia.visibility = View.GONE
|
statusShowing?.let { status ->
|
||||||
btnShowMedia.visibility = View.GONE
|
val r = status.auto_cw
|
||||||
} else {
|
tvContent.minLines = r?.originalLineCount ?: -1
|
||||||
flMedia.visibility = View.VISIBLE
|
if (r?.decodedSpoilerText != null) {
|
||||||
|
// 自動CWの場合はContentWarningのテキストを切り替える
|
||||||
// hide sensitive media
|
tvContentWarning.text =
|
||||||
val defaultShown = when {
|
if (shown) activity.getString(R.string.auto_cw_prefix) else r.decodedSpoilerText
|
||||||
column.hideMediaDefault -> false
|
|
||||||
accessInfo.dont_hide_nsfw -> true
|
|
||||||
else -> !status.sensitive
|
|
||||||
}
|
}
|
||||||
val isShown = MediaShown.isShown(status, defaultShown)
|
|
||||||
|
|
||||||
btnShowMedia.visibility = if (!isShown) View.VISIBLE else View.GONE
|
|
||||||
llMedia.visibility = if (!isShown) View.GONE else View.VISIBLE
|
|
||||||
val sb = StringBuilder()
|
|
||||||
setMedia(mediaAttachments, sb, ivMedia1, 0)
|
|
||||||
setMedia(mediaAttachments, sb, ivMedia2, 1)
|
|
||||||
setMedia(mediaAttachments, sb, ivMedia3, 2)
|
|
||||||
setMedia(mediaAttachments, sb, ivMedia4, 3)
|
|
||||||
|
|
||||||
val m0 =
|
|
||||||
if (mediaAttachments.isEmpty()) null else mediaAttachments[0] as? TootAttachment
|
|
||||||
btnShowMedia.blurhash = m0?.blurhash
|
|
||||||
|
|
||||||
if (sb.isNotEmpty()) {
|
|
||||||
tvMediaDescription.visibility = View.VISIBLE
|
|
||||||
tvMediaDescription.text = sb
|
|
||||||
}
|
|
||||||
|
|
||||||
setIconDrawableId(
|
|
||||||
activity,
|
|
||||||
btnHideMedia,
|
|
||||||
R.drawable.ic_close,
|
|
||||||
color = contentColor,
|
|
||||||
alphaMultiplier = Styler.boostAlpha
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
makeReactionsView(status)
|
private fun ItemViewHolder.showApplicationAndLanguage(status: TootStatus) {
|
||||||
|
|
||||||
buttonsForStatus?.bind(status, (item as? TootNotification))
|
|
||||||
|
|
||||||
var sb: StringBuilder? = null
|
var sb: StringBuilder? = null
|
||||||
|
|
||||||
fun prepareSb(): StringBuilder =
|
fun prepareSb(): StringBuilder =
|
||||||
sb?.append(", ") ?: StringBuilder().also { sb = it }
|
sb?.append(", ") ?: StringBuilder().also { sb = it }
|
||||||
|
|
||||||
|
@ -259,7 +216,7 @@ fun ItemViewHolder.showStatus(status: TootStatus, colorBg: Int = 0) {
|
||||||
tvApplication.vg(sb != null)?.text = sb
|
tvApplication.vg(sb != null)?.text = sb
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ItemViewHolder.showOpenSticker(who: TootAccount) {
|
private fun ItemViewHolder.showOpenSticker(who: TootAccount) {
|
||||||
try {
|
try {
|
||||||
if (!Column.showOpenSticker) return
|
if (!Column.showOpenSticker) return
|
||||||
|
|
||||||
|
@ -306,17 +263,47 @@ fun ItemViewHolder.showOpenSticker(who: TootAccount) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ItemViewHolder.showContent(shown: Boolean) {
|
private fun ItemViewHolder.showAttachments(status: TootStatus) {
|
||||||
llContents.visibility = if (shown) View.VISIBLE else View.GONE
|
val mediaAttachments = status.media_attachments
|
||||||
btnContentWarning.setText(if (shown) R.string.hide else R.string.show)
|
if (mediaAttachments == null || mediaAttachments.isEmpty()) {
|
||||||
statusShowing?.let { status ->
|
flMedia.visibility = View.GONE
|
||||||
val r = status.auto_cw
|
llMedia.visibility = View.GONE
|
||||||
tvContent.minLines = r?.originalLineCount ?: -1
|
btnShowMedia.visibility = View.GONE
|
||||||
if (r?.decodedSpoilerText != null) {
|
} else {
|
||||||
// 自動CWの場合はContentWarningのテキストを切り替える
|
flMedia.visibility = View.VISIBLE
|
||||||
tvContentWarning.text =
|
|
||||||
if (shown) activity.getString(R.string.auto_cw_prefix) else r.decodedSpoilerText
|
// hide sensitive media
|
||||||
|
val defaultShown = when {
|
||||||
|
column.hideMediaDefault -> false
|
||||||
|
accessInfo.dont_hide_nsfw -> true
|
||||||
|
else -> !status.sensitive
|
||||||
}
|
}
|
||||||
|
val isShown = MediaShown.isShown(status, defaultShown)
|
||||||
|
|
||||||
|
btnShowMedia.visibility = if (!isShown) View.VISIBLE else View.GONE
|
||||||
|
llMedia.visibility = if (!isShown) View.GONE else View.VISIBLE
|
||||||
|
val sb = StringBuilder()
|
||||||
|
setMedia(mediaAttachments, sb, ivMedia1, 0)
|
||||||
|
setMedia(mediaAttachments, sb, ivMedia2, 1)
|
||||||
|
setMedia(mediaAttachments, sb, ivMedia3, 2)
|
||||||
|
setMedia(mediaAttachments, sb, ivMedia4, 3)
|
||||||
|
|
||||||
|
val m0 =
|
||||||
|
if (mediaAttachments.isEmpty()) null else mediaAttachments[0] as? TootAttachment
|
||||||
|
btnShowMedia.blurhash = m0?.blurhash
|
||||||
|
|
||||||
|
if (sb.isNotEmpty()) {
|
||||||
|
tvMediaDescription.visibility = View.VISIBLE
|
||||||
|
tvMediaDescription.text = sb
|
||||||
|
}
|
||||||
|
|
||||||
|
setIconDrawableId(
|
||||||
|
activity,
|
||||||
|
btnHideMedia,
|
||||||
|
R.drawable.ic_close,
|
||||||
|
color = contentColor,
|
||||||
|
alphaMultiplier = Styler.boostAlpha
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package jp.juggler.subwaytooter.action
|
package jp.juggler.subwaytooter.action
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import jp.juggler.subwaytooter.*
|
import jp.juggler.subwaytooter.*
|
||||||
import jp.juggler.subwaytooter.api.*
|
import jp.juggler.subwaytooter.api.*
|
||||||
|
@ -29,13 +30,17 @@ private class BoostImpl(
|
||||||
val visibility: TootVisibility? = null,
|
val visibility: TootVisibility? = null,
|
||||||
val callback: () -> Unit,
|
val callback: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
val parser = TootParser(activity, accessInfo)
|
||||||
|
var resultStatus: TootStatus? = null
|
||||||
|
var resultUnrenoteId: EntityId? = null
|
||||||
|
|
||||||
// Mastodonは非公開トゥートをブーストできるのは本人だけ
|
// Mastodonは非公開トゥートをブーストできるのは本人だけ
|
||||||
val isPrivateToot = accessInfo.isMastodon &&
|
private val isPrivateToot = accessInfo.isMastodon &&
|
||||||
statusArg.visibility == TootVisibility.PrivateFollowers
|
statusArg.visibility == TootVisibility.PrivateFollowers
|
||||||
|
|
||||||
var bConfirmed = false
|
private var bConfirmed = false
|
||||||
|
|
||||||
fun preCheck(): Boolean {
|
private fun preCheck(): Boolean {
|
||||||
|
|
||||||
// アカウントからステータスにブースト操作を行っているなら、何もしない
|
// アカウントからステータスにブースト操作を行っているなら、何もしない
|
||||||
if (activity.appState.isBusyBoost(accessInfo, statusArg)) {
|
if (activity.appState.isBusyBoost(accessInfo, statusArg)) {
|
||||||
|
@ -52,7 +57,7 @@ private class BoostImpl(
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun confirm(): Boolean {
|
private fun confirm(): Boolean {
|
||||||
if (bConfirmed) return true
|
if (bConfirmed) return true
|
||||||
DlgConfirm.open(
|
DlgConfirm.open(
|
||||||
activity,
|
activity,
|
||||||
|
@ -88,178 +93,162 @@ private class BoostImpl(
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun Context.syncStatus(client: TootApiClient) =
|
||||||
|
if (!crossAccountMode.isRemote) {
|
||||||
|
// 既に自タンスのステータスがある
|
||||||
|
statusArg
|
||||||
|
} else {
|
||||||
|
val (result, status) = client.syncStatus(accessInfo, statusArg)
|
||||||
|
when {
|
||||||
|
status == null -> errorApiResult(result)
|
||||||
|
status.reblogged -> errorApiResult(getString(R.string.already_boosted))
|
||||||
|
else -> status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ブースト結果をUIに反映させる
|
||||||
|
private fun after(result: TootApiResult?, newStatus: TootStatus?, unrenoteId: EntityId?) {
|
||||||
|
result ?: return // cancelled.
|
||||||
|
when {
|
||||||
|
// Misskeyでunrenoteに成功した
|
||||||
|
unrenoteId != null -> {
|
||||||
|
// 星を外したのにカウントが下がらないのは違和感あるので、表示をいじる
|
||||||
|
// 0未満にしない
|
||||||
|
val count = max(0, (statusArg.reblogs_count ?: 1) - 1)
|
||||||
|
for (column in activity.appState.columnList) {
|
||||||
|
column.findStatus(accessInfo.apDomain, statusArg.id) { account, status ->
|
||||||
|
// 同タンス別アカウントでもカウントは変化する
|
||||||
|
status.reblogs_count = count
|
||||||
|
// 同アカウントならreblogged状態を変化させる
|
||||||
|
if (accessInfo == account && status.myRenoteId == unrenoteId) {
|
||||||
|
status.myRenoteId = null
|
||||||
|
status.reblogged = false
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 処理に成功した
|
||||||
|
newStatus != null -> {
|
||||||
|
// カウント数は遅延があるみたいなので、恣意的に表示を変更する
|
||||||
|
// ブーストカウント数を加工する
|
||||||
|
val oldCount = statusArg.reblogs_count
|
||||||
|
val newCount = newStatus.reblogs_count
|
||||||
|
if (oldCount != null && newCount != null) {
|
||||||
|
if (bSet && newStatus.reblogged && newCount <= oldCount) {
|
||||||
|
// 星をつけたのにカウントが上がらないのは違和感あるので、表示をいじる
|
||||||
|
newStatus.reblogs_count = oldCount + 1
|
||||||
|
} else if (!bSet && !newStatus.reblogged && newCount >= oldCount) {
|
||||||
|
// 星を外したのにカウントが下がらないのは違和感あるので、表示をいじる
|
||||||
|
// 0未満にはしない
|
||||||
|
newStatus.reblogs_count = if (oldCount < 1) 0 else oldCount - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (column in activity.appState.columnList) {
|
||||||
|
column.findStatus(accessInfo.apDomain, newStatus.id) { account, status ->
|
||||||
|
// 同タンス別アカウントでもカウントは変化する
|
||||||
|
status.reblogs_count = newStatus.reblogs_count
|
||||||
|
|
||||||
|
if (accessInfo == account) {
|
||||||
|
// 同アカウントならreblog状態を変化させる
|
||||||
|
when {
|
||||||
|
accessInfo.isMastodon ->
|
||||||
|
status.reblogged = newStatus.reblogged
|
||||||
|
|
||||||
|
bSet && status.myRenoteId == null -> {
|
||||||
|
status.myRenoteId = newStatus.myRenoteId
|
||||||
|
status.reblogged = true
|
||||||
|
}
|
||||||
|
// Misskey のunrenote時はここを通らない
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> activity.showToast(true, result.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun boostApi(client: TootApiClient, targetStatus: TootStatus): TootApiResult? =
|
||||||
|
if (accessInfo.isMisskey) {
|
||||||
|
if (!bSet) {
|
||||||
|
val myRenoteId = targetStatus.myRenoteId ?: errorApiResult("missing renote id.")
|
||||||
|
|
||||||
|
client.request(
|
||||||
|
"/api/notes/delete",
|
||||||
|
accessInfo.putMisskeyApiToken().apply {
|
||||||
|
put("noteId", myRenoteId.toString())
|
||||||
|
put("renoteId", targetStatus.id.toString())
|
||||||
|
}.toPostRequestBuilder()
|
||||||
|
)?.also {
|
||||||
|
if (it.response?.code == 204) {
|
||||||
|
resultUnrenoteId = myRenoteId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
client.request(
|
||||||
|
"/api/notes/create",
|
||||||
|
accessInfo.putMisskeyApiToken().apply {
|
||||||
|
put("renoteId", targetStatus.id.toString())
|
||||||
|
}.toPostRequestBuilder()
|
||||||
|
)?.also { result ->
|
||||||
|
val jsonObject = result.jsonObject
|
||||||
|
if (jsonObject != null) {
|
||||||
|
val outerStatus = parser.status(jsonObject.jsonObject("createdNote") ?: jsonObject)
|
||||||
|
val innerStatus = outerStatus?.reblog ?: outerStatus
|
||||||
|
if (outerStatus != null && innerStatus != null && outerStatus != innerStatus) {
|
||||||
|
innerStatus.myRenoteId = outerStatus.id
|
||||||
|
innerStatus.reblogged = true
|
||||||
|
}
|
||||||
|
// renoteそのものではなくrenoteされた元noteが欲しい
|
||||||
|
resultStatus = innerStatus
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val b = JsonObject().apply {
|
||||||
|
if (visibility != null) put("visibility", visibility.strMastodon)
|
||||||
|
}.toPostRequestBuilder()
|
||||||
|
|
||||||
|
client.request(
|
||||||
|
"/api/v1/statuses/${targetStatus.id}/${if (bSet) "reblog" else "unreblog"}",
|
||||||
|
b
|
||||||
|
)?.also { result ->
|
||||||
|
// reblogはreblogを表すStatusを返す
|
||||||
|
// unreblogはreblogしたStatusを返す
|
||||||
|
val s = parser.status(result.jsonObject)
|
||||||
|
resultStatus = s?.reblog ?: s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun run() {
|
fun run() {
|
||||||
if (!preCheck()) return
|
if (!preCheck()) return
|
||||||
if (!confirm()) return
|
if (!confirm()) return
|
||||||
|
|
||||||
activity.appState.setBusyBoost(accessInfo, statusArg)
|
|
||||||
// ブースト表示を更新中にする
|
// ブースト表示を更新中にする
|
||||||
|
activity.appState.setBusyBoost(accessInfo, statusArg)
|
||||||
activity.showColumnMatchAccount(accessInfo)
|
activity.showColumnMatchAccount(accessInfo)
|
||||||
// misskeyは非公開トゥートをブーストできないっぽい
|
|
||||||
|
|
||||||
launchMain {
|
launchMain {
|
||||||
var resultStatus: TootStatus? = null
|
|
||||||
var resultUnrenoteId: EntityId? = null
|
|
||||||
val result = activity.runApiTask(accessInfo, progressStyle = ApiTask.PROGRESS_NONE) { client ->
|
val result = activity.runApiTask(accessInfo, progressStyle = ApiTask.PROGRESS_NONE) { client ->
|
||||||
|
try {
|
||||||
val parser = TootParser(this, accessInfo)
|
val targetStatus = syncStatus(client)
|
||||||
|
boostApi(client, targetStatus)
|
||||||
val targetStatus = if (crossAccountMode.isRemote) {
|
} catch (ex: TootApiResultException) {
|
||||||
val (result, status) = client.syncStatus(accessInfo, statusArg)
|
ex.result
|
||||||
if (status == null) return@runApiTask result
|
|
||||||
if (status.reblogged) {
|
|
||||||
return@runApiTask TootApiResult(getString(R.string.already_boosted))
|
|
||||||
}
|
|
||||||
status
|
|
||||||
} else {
|
|
||||||
// 既に自タンスのステータスがある
|
|
||||||
statusArg
|
|
||||||
}
|
|
||||||
|
|
||||||
if (accessInfo.isMisskey) {
|
|
||||||
if (!bSet) {
|
|
||||||
val myRenoteId = targetStatus.myRenoteId
|
|
||||||
?: return@runApiTask TootApiResult("missing renote id.")
|
|
||||||
|
|
||||||
client.request(
|
|
||||||
"/api/notes/delete",
|
|
||||||
accessInfo.putMisskeyApiToken().apply {
|
|
||||||
put("noteId", myRenoteId.toString())
|
|
||||||
put("renoteId", targetStatus.id.toString())
|
|
||||||
}
|
|
||||||
.toPostRequestBuilder()
|
|
||||||
)
|
|
||||||
?.also {
|
|
||||||
if (it.response?.code == 204) {
|
|
||||||
resultUnrenoteId = myRenoteId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
client.request(
|
|
||||||
"/api/notes/create",
|
|
||||||
accessInfo.putMisskeyApiToken().apply {
|
|
||||||
put("renoteId", targetStatus.id.toString())
|
|
||||||
}
|
|
||||||
.toPostRequestBuilder()
|
|
||||||
)
|
|
||||||
?.also { result ->
|
|
||||||
val jsonObject = result.jsonObject
|
|
||||||
if (jsonObject != null) {
|
|
||||||
val outerStatus = parser.status(
|
|
||||||
jsonObject.jsonObject("createdNote")
|
|
||||||
?: jsonObject
|
|
||||||
)
|
|
||||||
val innerStatus = outerStatus?.reblog ?: outerStatus
|
|
||||||
if (outerStatus != null && innerStatus != null && outerStatus != innerStatus) {
|
|
||||||
innerStatus.myRenoteId = outerStatus.id
|
|
||||||
innerStatus.reblogged = true
|
|
||||||
}
|
|
||||||
// renoteそのものではなくrenoteされた元noteが欲しい
|
|
||||||
resultStatus = innerStatus
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val b = JsonObject().apply {
|
|
||||||
if (visibility != null) put("visibility", visibility.strMastodon)
|
|
||||||
}.toPostRequestBuilder()
|
|
||||||
|
|
||||||
client.request(
|
|
||||||
"/api/v1/statuses/${targetStatus.id}/${if (bSet) "reblog" else "unreblog"}",
|
|
||||||
b
|
|
||||||
)?.also { result ->
|
|
||||||
// reblogはreblogを表すStatusを返す
|
|
||||||
// unreblogはreblogしたStatusを返す
|
|
||||||
val s = parser.status(result.jsonObject)
|
|
||||||
resultStatus = s?.reblog ?: s
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 更新中状態をリセット
|
||||||
activity.appState.resetBusyBoost(accessInfo, statusArg)
|
activity.appState.resetBusyBoost(accessInfo, statusArg)
|
||||||
|
// カラムデータの書き換え
|
||||||
if (result != null) {
|
after(result, resultStatus, resultUnrenoteId)
|
||||||
val unrenoteId = resultUnrenoteId
|
// result == null の場合でも更新中表示の解除が必要になる
|
||||||
val newStatus = resultStatus
|
|
||||||
when {
|
|
||||||
// Misskeyでunrenoteに成功した
|
|
||||||
unrenoteId != null -> {
|
|
||||||
|
|
||||||
// 星を外したのにカウントが下がらないのは違和感あるので、表示をいじる
|
|
||||||
// 0未満にはならない
|
|
||||||
val count = max(0, (statusArg.reblogs_count ?: 1) - 1)
|
|
||||||
|
|
||||||
for (column in activity.appState.columnList) {
|
|
||||||
column.findStatus(
|
|
||||||
accessInfo.apDomain,
|
|
||||||
statusArg.id
|
|
||||||
) { account, status ->
|
|
||||||
|
|
||||||
// 同タンス別アカウントでもカウントは変化する
|
|
||||||
status.reblogs_count = count
|
|
||||||
|
|
||||||
// 同アカウントならreblogged状態を変化させる
|
|
||||||
if (accessInfo == account && status.myRenoteId == unrenoteId) {
|
|
||||||
status.myRenoteId = null
|
|
||||||
status.reblogged = false
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
callback()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 処理に成功した
|
|
||||||
newStatus != null -> {
|
|
||||||
// カウント数は遅延があるみたいなので、恣意的に表示を変更する
|
|
||||||
// ブーストカウント数を加工する
|
|
||||||
val oldCount = statusArg.reblogs_count
|
|
||||||
val newCount = newStatus.reblogs_count
|
|
||||||
if (oldCount != null && newCount != null) {
|
|
||||||
if (bSet && newStatus.reblogged && newCount <= oldCount) {
|
|
||||||
// 星をつけたのにカウントが上がらないのは違和感あるので、表示をいじる
|
|
||||||
newStatus.reblogs_count = oldCount + 1
|
|
||||||
} else if (!bSet && !newStatus.reblogged && newCount >= oldCount) {
|
|
||||||
// 星を外したのにカウントが下がらないのは違和感あるので、表示をいじる
|
|
||||||
// 0未満にはならない
|
|
||||||
newStatus.reblogs_count = if (oldCount < 1) 0 else oldCount - 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (column in activity.appState.columnList) {
|
|
||||||
column.findStatus(
|
|
||||||
accessInfo.apDomain,
|
|
||||||
newStatus.id
|
|
||||||
) { account, status ->
|
|
||||||
|
|
||||||
// 同タンス別アカウントでもカウントは変化する
|
|
||||||
status.reblogs_count = newStatus.reblogs_count
|
|
||||||
|
|
||||||
if (accessInfo == account) {
|
|
||||||
|
|
||||||
// 同アカウントならreblog状態を変化させる
|
|
||||||
when {
|
|
||||||
accessInfo.isMastodon ->
|
|
||||||
status.reblogged = newStatus.reblogged
|
|
||||||
|
|
||||||
bSet && status.myRenoteId == null -> {
|
|
||||||
status.myRenoteId = newStatus.myRenoteId
|
|
||||||
status.reblogged = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Misskey のunrenote時はここを通らない
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
callback()
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> activity.showToast(true, result.error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 結果に関わらず、更新中状態から復帰させる
|
|
||||||
activity.showColumnMatchAccount(accessInfo)
|
activity.showColumnMatchAccount(accessInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package jp.juggler.subwaytooter.api
|
||||||
|
|
||||||
|
import java.lang.Exception
|
||||||
|
|
||||||
|
class TootApiResultException(val result: TootApiResult?) : Exception(result?.error ?: "cancelled.") {
|
||||||
|
constructor(error: String) : this(TootApiResult(error))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun errorApiResult(result: TootApiResult?):Nothing = throw TootApiResultException(result)
|
||||||
|
fun errorApiResult(error:String):Nothing = throw TootApiResultException(error)
|
|
@ -3,9 +3,10 @@ package jp.juggler.subwaytooter.api.entity
|
||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import jp.juggler.util.*
|
import jp.juggler.util.JsonObject
|
||||||
|
import jp.juggler.util.getStringOrNull
|
||||||
|
import jp.juggler.util.notZero
|
||||||
import kotlinx.serialization.KSerializer
|
import kotlinx.serialization.KSerializer
|
||||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||||
|
@ -109,9 +110,8 @@ object EntityIdSerializer : KSerializer<EntityId> {
|
||||||
PrimitiveSerialDescriptor("EntityId", PrimitiveKind.STRING)
|
PrimitiveSerialDescriptor("EntityId", PrimitiveKind.STRING)
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, value: EntityId) =
|
override fun serialize(encoder: Encoder, value: EntityId) =
|
||||||
encoder.encodeString(value.toString() )
|
encoder.encodeString(value.toString())
|
||||||
|
|
||||||
override fun deserialize(decoder: Decoder): EntityId =
|
override fun deserialize(decoder: Decoder): EntityId =
|
||||||
EntityId(decoder.decodeString())
|
EntityId(decoder.decodeString())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,15 +46,15 @@ class TootScheduled(parser: TootParser, val src: JsonObject) : TimelineItem() {
|
||||||
|
|
||||||
// 投稿画面の復元時に、IDだけでもないと困る
|
// 投稿画面の復元時に、IDだけでもないと困る
|
||||||
fun encodeSimple() = jsonObject {
|
fun encodeSimple() = jsonObject {
|
||||||
put("id",id.toString())
|
put("id", id.toString())
|
||||||
put("scheduled_at",scheduledAt)
|
put("scheduled_at", scheduledAt)
|
||||||
// SKIP: put("media_attachments",mediaAttachments?.map{ it.})
|
// SKIP: put("media_attachments",mediaAttachments?.map{ it.})
|
||||||
put("params", jsonObject {
|
put("params", jsonObject {
|
||||||
put("text",text)
|
put("text", text)
|
||||||
put("visibility",visibility.strMastodon)
|
put("visibility", visibility.strMastodon)
|
||||||
put("spoiler_text",spoilerText)
|
put("spoiler_text", spoilerText)
|
||||||
put("in_reply_to_id",inReplyToId)
|
put("in_reply_to_id", inReplyToId)
|
||||||
put("sensitive",sensitive)
|
put("sensitive", sensitive)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
package jp.juggler.subwaytooter.api.finder
|
||||||
|
|
||||||
|
import jp.juggler.subwaytooter.api.TootParser
|
||||||
|
import jp.juggler.subwaytooter.api.entity.*
|
||||||
|
import jp.juggler.subwaytooter.table.SavedAccount
|
||||||
|
import jp.juggler.util.JsonArray
|
||||||
|
import jp.juggler.util.JsonObject
|
||||||
|
|
||||||
|
val nullArrayFinder: (JsonObject) -> JsonArray? =
|
||||||
|
{ null }
|
||||||
|
|
||||||
|
val misskeyArrayFinderUsers = { it: JsonObject ->
|
||||||
|
it.jsonArray("users")
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// account list parser
|
||||||
|
|
||||||
|
val defaultAccountListParser: (parser: TootParser, jsonArray: JsonArray) -> List<TootAccountRef> =
|
||||||
|
{ parser, jsonArray -> parser.accountList(jsonArray) }
|
||||||
|
|
||||||
|
private fun misskeyUnwrapRelationAccount(parser: TootParser, srcList: JsonArray, key: String) =
|
||||||
|
srcList.objectList().mapNotNull {
|
||||||
|
when (val relationId = EntityId.mayNull(it.string("id"))) {
|
||||||
|
null -> null
|
||||||
|
else -> TootAccountRef.mayNull(parser, parser.account(it.jsonObject(key)))
|
||||||
|
?.apply { _orderId = relationId }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val misskey11FollowingParser: (TootParser, JsonArray) -> List<TootAccountRef> =
|
||||||
|
{ parser, jsonArray -> misskeyUnwrapRelationAccount(parser, jsonArray, "followee") }
|
||||||
|
|
||||||
|
val misskey11FollowersParser: (TootParser, JsonArray) -> List<TootAccountRef> =
|
||||||
|
{ parser, jsonArray -> misskeyUnwrapRelationAccount(parser, jsonArray, "follower") }
|
||||||
|
|
||||||
|
val misskeyCustomParserFollowRequest: (TootParser, JsonArray) -> List<TootAccountRef> =
|
||||||
|
{ parser, jsonArray -> misskeyUnwrapRelationAccount(parser, jsonArray, "follower") }
|
||||||
|
|
||||||
|
val misskeyCustomParserMutes: (TootParser, JsonArray) -> List<TootAccountRef> =
|
||||||
|
{ parser, jsonArray -> misskeyUnwrapRelationAccount(parser, jsonArray, "mutee") }
|
||||||
|
|
||||||
|
val misskeyCustomParserBlocks: (TootParser, JsonArray) -> List<TootAccountRef> =
|
||||||
|
{ parser, jsonArray -> misskeyUnwrapRelationAccount(parser, jsonArray, "blockee") }
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// status list parser
|
||||||
|
|
||||||
|
val defaultStatusListParser: (parser: TootParser, jsonArray: JsonArray) -> List<TootStatus> =
|
||||||
|
{ parser, jsonArray -> parser.statusList(jsonArray) }
|
||||||
|
|
||||||
|
val misskeyCustomParserFavorites: (TootParser, JsonArray) -> List<TootStatus> =
|
||||||
|
{ parser, jsonArray ->
|
||||||
|
jsonArray.objectList().mapNotNull {
|
||||||
|
when (val relationId = EntityId.mayNull(it.string("id"))) {
|
||||||
|
null -> null
|
||||||
|
else -> parser.status(it.jsonObject("note"))?.apply {
|
||||||
|
favourited = true
|
||||||
|
_orderId = relationId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// notification list parser
|
||||||
|
|
||||||
|
val defaultNotificationListParser: (parser: TootParser, jsonArray: JsonArray) -> List<TootNotification> =
|
||||||
|
{ parser, jsonArray -> parser.notificationList(jsonArray) }
|
||||||
|
|
||||||
|
val defaultDomainBlockListParser: (parser: TootParser, jsonArray: JsonArray) -> List<TootDomainBlock> =
|
||||||
|
{ _, jsonArray -> TootDomainBlock.parseList(jsonArray) }
|
||||||
|
|
||||||
|
val defaultReportListParser: (parser: TootParser, jsonArray: JsonArray) -> List<TootReport> =
|
||||||
|
{ _, jsonArray -> parseList(::TootReport, jsonArray) }
|
||||||
|
|
||||||
|
val defaultConversationSummaryListParser: (parser: TootParser, jsonArray: JsonArray) -> List<TootConversationSummary> =
|
||||||
|
{ parser, jsonArray -> parseList(::TootConversationSummary, parser, jsonArray) }
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
val mastodonFollowSuggestion2ListParser: (parser: TootParser, jsonArray: JsonArray) -> List<TootAccountRef> =
|
||||||
|
{ parser, jsonArray ->
|
||||||
|
TootAccountRef.wrapList(parser,
|
||||||
|
jsonArray.objectList().mapNotNull {
|
||||||
|
parser.account(it.jsonObject("account"))?.also { a ->
|
||||||
|
SuggestionSource.set(
|
||||||
|
(parser.linkHelper as? SavedAccount)?.db_id,
|
||||||
|
a.acct,
|
||||||
|
it.string("source")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,8 +1,6 @@
|
||||||
package jp.juggler.subwaytooter.emoji
|
package jp.juggler.subwaytooter.emoji
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Log
|
|
||||||
import java.io.EOFException
|
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
@ -21,135 +19,7 @@ object EmojiMap {
|
||||||
/////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
private fun readStream(appContext: Context, inStream: InputStream) {
|
private fun readStream(appContext: Context, inStream: InputStream) {
|
||||||
val assetManager = appContext.assets!!
|
EmojiMapLoader(appContext, this).readStream(inStream)
|
||||||
val resources = appContext.resources!!
|
|
||||||
val packageName = appContext.packageName!!
|
|
||||||
|
|
||||||
fun getDrawableId(name: String) =
|
|
||||||
resources.getIdentifier(name, "drawable", packageName).takeIf { it != 0 }
|
|
||||||
|
|
||||||
val categoryNameMap = HashMap<String, EmojiCategory>().apply {
|
|
||||||
EmojiCategory.values().forEach { put(it.name, it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// 素の数字とcopyright,registered, trademark は絵文字にしない
|
|
||||||
fun isIgnored(code: String): Boolean {
|
|
||||||
val c = code[0].code
|
|
||||||
return code.length == 1 && c <= 0xae
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addCode(emoji: UnicodeEmoji, code: String) {
|
|
||||||
if (isIgnored(code)) return
|
|
||||||
unicodeMap[code] = emoji
|
|
||||||
unicodeTrie.append(code, 0, emoji)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addName(emoji: UnicodeEmoji, name: String) {
|
|
||||||
shortNameMap[name] = emoji
|
|
||||||
shortNameList.add(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
val reComment = """\s*//.*""".toRegex()
|
|
||||||
val reLineHeader = """\A(\w+):""".toRegex()
|
|
||||||
val assetsSet = assetManager.list("")!!.toSet()
|
|
||||||
var lastEmoji: UnicodeEmoji? = null
|
|
||||||
var lastCategory: EmojiCategory? = null
|
|
||||||
var lno = 0
|
|
||||||
fun readEmojiDataLine(rawLine: String) {
|
|
||||||
++lno
|
|
||||||
var line = rawLine.replace(reComment, "").trim()
|
|
||||||
val head = reLineHeader.find(line)?.groupValues?.elementAtOrNull(1)
|
|
||||||
?: error("missing line header. line=$lno $line")
|
|
||||||
line = line.substring(head.length + 1)
|
|
||||||
try {
|
|
||||||
when (head) {
|
|
||||||
"svg" -> {
|
|
||||||
if (!assetsSet.contains(line)) error("missing assets.")
|
|
||||||
lastEmoji = UnicodeEmoji(assetsName = line)
|
|
||||||
}
|
|
||||||
"drawable" -> {
|
|
||||||
val drawableId = getDrawableId(line) ?: error("missing drawable.")
|
|
||||||
lastEmoji = UnicodeEmoji(drawableId = drawableId)
|
|
||||||
}
|
|
||||||
"un" -> {
|
|
||||||
val emoji = lastEmoji ?: error("missing lastEmoji.")
|
|
||||||
addCode(emoji, line)
|
|
||||||
emoji.unifiedCode = line
|
|
||||||
}
|
|
||||||
"u" -> {
|
|
||||||
val emoji = lastEmoji ?: error("missing lastEmoji.")
|
|
||||||
addCode(emoji, line)
|
|
||||||
}
|
|
||||||
"sn" -> {
|
|
||||||
val emoji = lastEmoji ?: error("missing lastEmoji.")
|
|
||||||
addName(emoji, line)
|
|
||||||
emoji.unifiedName = line
|
|
||||||
}
|
|
||||||
"s" -> {
|
|
||||||
val emoji = lastEmoji ?: error("missing lastEmoji.")
|
|
||||||
addName(emoji, line)
|
|
||||||
}
|
|
||||||
"t" -> {
|
|
||||||
val cols = line.split(",", limit = 3)
|
|
||||||
if (cols.size != 3) error("invalid tone spec. line=$lno $line")
|
|
||||||
val parent = unicodeMap[cols[0]]
|
|
||||||
?: error("missing tone parent. line=$lno $line")
|
|
||||||
val toneCode = cols[1].takeIf { it.isNotEmpty() }
|
|
||||||
?: error("missing tone code. line=$lno $line")
|
|
||||||
val child = unicodeMap[cols[2]]
|
|
||||||
?: error("missing tone child. line=$lno $line")
|
|
||||||
parent.toneChildren.add(Pair(toneCode, child))
|
|
||||||
child.toneParent = parent
|
|
||||||
}
|
|
||||||
|
|
||||||
"cn" -> {
|
|
||||||
lastCategory = categoryNameMap[line]
|
|
||||||
?: error("missing category name.")
|
|
||||||
}
|
|
||||||
"c" -> {
|
|
||||||
val category = lastCategory
|
|
||||||
?: error("missing lastCategory.")
|
|
||||||
val emoji = unicodeMap[line] ?: error("missing emoji.")
|
|
||||||
// if (emoji == null) {
|
|
||||||
// Log.w("SubwayTooter", "missing emoji. lno=$lno line=$rawLine")
|
|
||||||
// } else
|
|
||||||
if (!category.emojiList.contains(emoji)) {
|
|
||||||
category.emojiList.add(emoji)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> error("unknown header $head")
|
|
||||||
}
|
|
||||||
} catch (ex: Throwable) {
|
|
||||||
Log.e("SubwayTooter", "EmojiMap load error.", ex)
|
|
||||||
error("EmojiMap load error: ${ex.javaClass.simpleName} ${ex.message} lno=$lno line=$rawLine")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val lineFeed = 0x0a.toByte()
|
|
||||||
val buffer = ByteArray(4096)
|
|
||||||
var used = inStream.read(buffer, 0, buffer.size)
|
|
||||||
if (used <= 0) throw EOFException("unexpected EOF")
|
|
||||||
while (true) {
|
|
||||||
var lineStart = 0
|
|
||||||
while (lineStart < used) {
|
|
||||||
var i = lineStart
|
|
||||||
while (i < used && buffer[i] != lineFeed) ++i
|
|
||||||
if (i >= used) break
|
|
||||||
if (i > lineStart) {
|
|
||||||
val line = String(buffer, lineStart, i - lineStart, Charsets.UTF_8)
|
|
||||||
readEmojiDataLine(line)
|
|
||||||
}
|
|
||||||
lineStart = i + 1
|
|
||||||
}
|
|
||||||
buffer.copyInto(buffer, 0, lineStart, used)
|
|
||||||
used -= lineStart
|
|
||||||
val nRead = inStream.read(buffer, used, buffer.size - used)
|
|
||||||
if (nRead <= 0) {
|
|
||||||
if (used > 0) throw EOFException("unexpected EOF")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
used += nRead
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun load(appContext: Context) {
|
fun load(appContext: Context) {
|
||||||
|
|
|
@ -0,0 +1,167 @@
|
||||||
|
package jp.juggler.subwaytooter.emoji
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import jp.juggler.util.LogCategory
|
||||||
|
import jp.juggler.util.errorEx
|
||||||
|
import java.io.EOFException
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.util.HashMap
|
||||||
|
|
||||||
|
class EmojiMapLoader(
|
||||||
|
appContext: Context,
|
||||||
|
private val dst: EmojiMap,
|
||||||
|
) {
|
||||||
|
// このクラスは起動時に1回だけ使うため、companion objectに永続的に何か保持することはない
|
||||||
|
private val log = LogCategory("EmojiMapLoader")
|
||||||
|
private val reComment = """\s*//.*""".toRegex()
|
||||||
|
private val reLineHeader = """\A(\w+):""".toRegex()
|
||||||
|
|
||||||
|
private val packageName = appContext.packageName!!
|
||||||
|
private val assetsSet = appContext.assets.list("")!!.toSet()
|
||||||
|
private val resources = appContext.resources!!
|
||||||
|
|
||||||
|
private val categoryNameMap = HashMap<String, EmojiCategory>().apply {
|
||||||
|
EmojiCategory.values().forEach { put(it.name, it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private var lastEmoji: UnicodeEmoji? = null
|
||||||
|
private var lastCategory: EmojiCategory? = null
|
||||||
|
|
||||||
|
private fun getDrawableId(name: String) =
|
||||||
|
resources.getIdentifier(name, "drawable", packageName).takeIf { it != 0 }
|
||||||
|
|
||||||
|
// 素の数字とcopyright,registered, trademark は絵文字にしない
|
||||||
|
private fun isIgnored(code: String): Boolean {
|
||||||
|
val c = code[0].code
|
||||||
|
return code.length == 1 && c <= 0xae
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addCode(emoji: UnicodeEmoji, code: String) {
|
||||||
|
if (isIgnored(code)) return
|
||||||
|
dst.unicodeMap[code] = emoji
|
||||||
|
dst.unicodeTrie.append(code, 0, emoji)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addName(emoji: UnicodeEmoji, name: String) {
|
||||||
|
dst.shortNameMap[name] = emoji
|
||||||
|
dst.shortNameList.add(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readEmojiDataLine(lno: Int, rawLine: String) {
|
||||||
|
var line = rawLine.replace(reComment, "").trim()
|
||||||
|
val head = reLineHeader.find(line)?.groupValues?.elementAtOrNull(1)
|
||||||
|
?: error("missing line header. line=$lno $line")
|
||||||
|
line = line.substring(head.length + 1)
|
||||||
|
try {
|
||||||
|
when (head) {
|
||||||
|
"svg" -> {
|
||||||
|
if (!assetsSet.contains(line)) error("missing assets.")
|
||||||
|
lastEmoji = UnicodeEmoji(assetsName = line)
|
||||||
|
}
|
||||||
|
"drawable" -> {
|
||||||
|
val drawableId = getDrawableId(line) ?: error("missing drawable.")
|
||||||
|
lastEmoji = UnicodeEmoji(drawableId = drawableId)
|
||||||
|
}
|
||||||
|
"un" -> {
|
||||||
|
val emoji = lastEmoji ?: error("missing lastEmoji.")
|
||||||
|
addCode(emoji, line)
|
||||||
|
emoji.unifiedCode = line
|
||||||
|
}
|
||||||
|
"u" -> {
|
||||||
|
val emoji = lastEmoji ?: error("missing lastEmoji.")
|
||||||
|
addCode(emoji, line)
|
||||||
|
}
|
||||||
|
"sn" -> {
|
||||||
|
val emoji = lastEmoji ?: error("missing lastEmoji.")
|
||||||
|
addName(emoji, line)
|
||||||
|
emoji.unifiedName = line
|
||||||
|
}
|
||||||
|
"s" -> {
|
||||||
|
val emoji = lastEmoji ?: error("missing lastEmoji.")
|
||||||
|
addName(emoji, line)
|
||||||
|
}
|
||||||
|
"t" -> {
|
||||||
|
val cols = line.split(",", limit = 3)
|
||||||
|
if (cols.size != 3) error("invalid tone spec. line=$lno $line")
|
||||||
|
val parent = dst.unicodeMap[cols[0]]
|
||||||
|
?: error("missing tone parent. line=$lno $line")
|
||||||
|
val toneCode = cols[1].takeIf { it.isNotEmpty() }
|
||||||
|
?: error("missing tone code. line=$lno $line")
|
||||||
|
val child = dst.unicodeMap[cols[2]]
|
||||||
|
?: error("missing tone child. line=$lno $line")
|
||||||
|
parent.toneChildren.add(Pair(toneCode, child))
|
||||||
|
child.toneParent = parent
|
||||||
|
}
|
||||||
|
|
||||||
|
"cn" -> {
|
||||||
|
lastCategory = categoryNameMap[line]
|
||||||
|
?: error("missing category name.")
|
||||||
|
}
|
||||||
|
"c" -> {
|
||||||
|
val category = lastCategory
|
||||||
|
?: error("missing lastCategory.")
|
||||||
|
val emoji = dst.unicodeMap[line] ?: error("missing emoji.")
|
||||||
|
// if (emoji == null) {
|
||||||
|
// Log.w("SubwayTooter", "missing emoji. lno=$lno line=$rawLine")
|
||||||
|
// } else
|
||||||
|
if (!category.emojiList.contains(emoji)) {
|
||||||
|
category.emojiList.add(emoji)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> error("unknown header $head")
|
||||||
|
}
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
log.e(ex, "readEmojiDataLine: ${ex.javaClass.simpleName} ${ex.message} lno=$lno line=$rawLine")
|
||||||
|
// 行番号の情報をつけて投げ直す
|
||||||
|
errorEx(ex, "readEmojiDataLine: ${ex.javaClass.simpleName} ${ex.message} lno=$lno line=$rawLine")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ByteArray.indexOf(key: Byte, start: Int = 0): Int? {
|
||||||
|
var i = start
|
||||||
|
val end = this.size
|
||||||
|
while (i < end) {
|
||||||
|
if (this[i] == key) return i
|
||||||
|
++i
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun InputStream.eachLine(block: (Int, String) -> Unit) {
|
||||||
|
val lineFeed = 0x0a.toByte()
|
||||||
|
val buffer = ByteArray(4096)
|
||||||
|
// バッファに読む
|
||||||
|
var end = read(buffer, 0, buffer.size)
|
||||||
|
if (end <= 0) throw EOFException("unexpected EOF")
|
||||||
|
|
||||||
|
var lno = 0
|
||||||
|
while (true) {
|
||||||
|
var lineStart = 0
|
||||||
|
while (lineStart < end) {
|
||||||
|
// 行末記号を見つける
|
||||||
|
val feedPos = buffer.indexOf(lineFeed, lineStart) ?: break
|
||||||
|
++lno
|
||||||
|
if (feedPos > lineStart) {
|
||||||
|
// 1行分をUTF-8デコードして処理する
|
||||||
|
val line = String(buffer, lineStart, feedPos - lineStart, Charsets.UTF_8)
|
||||||
|
block(lno, line)
|
||||||
|
}
|
||||||
|
lineStart = feedPos + 1
|
||||||
|
}
|
||||||
|
// 最後の行末より後のデータをバッファ先頭に移動する
|
||||||
|
buffer.copyInto(buffer, 0, lineStart, end)
|
||||||
|
end -= lineStart
|
||||||
|
// ストリームから継ぎ足す
|
||||||
|
val nRead = read(buffer, end, buffer.size - end)
|
||||||
|
if (nRead <= 0) {
|
||||||
|
if (end > 0) throw EOFException("unexpected EOF")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
end += nRead
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun readStream(inStream: InputStream) {
|
||||||
|
inStream.eachLine { lno, line -> readEmojiDataLine(lno, line) }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
package jp.juggler.subwaytooter.notification
|
package jp.juggler.subwaytooter.notification
|
||||||
|
|
||||||
|
import android.annotation.TargetApi
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.service.notification.StatusBarNotification
|
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import jp.juggler.subwaytooter.ActCallback
|
import jp.juggler.subwaytooter.ActCallback
|
||||||
|
@ -463,215 +463,170 @@ class TaskRunner(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateNotification() {
|
fun updateNotification() {
|
||||||
|
|
||||||
val notificationTag = when (trackingName) {
|
val notificationTag = when (trackingName) {
|
||||||
"" -> "${account.db_id}/_"
|
"" -> "${account.db_id}/_"
|
||||||
else -> "${account.db_id}/$trackingName"
|
else -> "${account.db_id}/$trackingName"
|
||||||
}
|
}
|
||||||
|
|
||||||
val nt = NotificationTracking.load(account.acct.pretty, account.db_id, trackingName)
|
val nt = NotificationTracking.load(account.acct.pretty, account.db_id, trackingName)
|
||||||
val dataList = dstListData
|
when (val first = dstListData.firstOrNull()) {
|
||||||
val first = dataList.firstOrNull()
|
null -> {
|
||||||
if (first == null) {
|
log.d("showNotification[${account.acct.pretty}/$notificationTag] cancel notification.")
|
||||||
log.d("showNotification[${account.acct.pretty}/$notificationTag] cancel notification.")
|
removeNotification(notificationTag)
|
||||||
if (Build.VERSION.SDK_INT >= 23 && PrefB.bpDivideNotification(pref)) {
|
}
|
||||||
notificationManager.activeNotifications?.forEach {
|
else -> {
|
||||||
if (it != null &&
|
when {
|
||||||
it.id == PollingWorker.NOTIFICATION_ID &&
|
// 先頭にあるデータが同じなら、通知を更新しない
|
||||||
it.tag.startsWith("$notificationTag/")
|
// このマーカーは端末再起動時にリセットされるので、再起動後は通知が出るはず
|
||||||
) {
|
first.notification.time_created_at == nt.post_time && first.notification.id == nt.post_id ->
|
||||||
log.d("cancel: ${it.tag} context=${account.acct.pretty} $notificationTag")
|
log.d("showNotification[${account.acct.pretty}] id=${first.notification.id} is already shown.")
|
||||||
notificationManager.cancel(it.tag, PollingWorker.NOTIFICATION_ID)
|
|
||||||
|
Build.VERSION.SDK_INT >= 23 && PrefB.bpDivideNotification(pref) -> {
|
||||||
|
updateNotificationDivided(notificationTag, nt)
|
||||||
|
nt.updatePost(first.notification.id, first.notification.time_created_at)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
updateNotificationMerged(notificationTag, first)
|
||||||
|
nt.updatePost(first.notification.id, first.notification.time_created_at)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
notificationManager.cancel(notificationTag, PollingWorker.NOTIFICATION_ID)
|
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeNotification(notificationTag: String) {
|
||||||
|
if (Build.VERSION.SDK_INT >= 23 && PrefB.bpDivideNotification(pref)) {
|
||||||
|
notificationManager.activeNotifications?.filterNotNull()?.filter {
|
||||||
|
it.id == PollingWorker.NOTIFICATION_ID && it.tag.startsWith("$notificationTag/")
|
||||||
|
}?.forEach {
|
||||||
|
log.d("cancel: ${it.tag} context=${account.acct.pretty} $notificationTag")
|
||||||
|
notificationManager.cancel(it.tag, PollingWorker.NOTIFICATION_ID)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
notificationManager.cancel(notificationTag, PollingWorker.NOTIFICATION_ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(23)
|
||||||
|
private fun updateNotificationDivided(notificationTag: String, nt: NotificationTracking) {
|
||||||
|
log.d("updateNotificationDivided[${account.acct.pretty}] creating notification(1)")
|
||||||
|
|
||||||
|
val activeNotificationMap = notificationManager.activeNotifications?.filterNotNull()?.filter {
|
||||||
|
it.id == PollingWorker.NOTIFICATION_ID && it.tag.startsWith("$notificationTag/")
|
||||||
|
}?.map { Pair(it.tag, it) }?.toMutableMap() ?: mutableMapOf()
|
||||||
|
|
||||||
val lastPostTime = nt.post_time
|
val lastPostTime = nt.post_time
|
||||||
val lastPostId = nt.post_id
|
val lastPostId = nt.post_id
|
||||||
if (first.notification.time_created_at == lastPostTime &&
|
|
||||||
first.notification.id == lastPostId
|
|
||||||
) {
|
|
||||||
// 先頭にあるデータが同じなら、通知を更新しない
|
|
||||||
// このマーカーは端末再起動時にリセットされるので、再起動後は通知が出るはず
|
|
||||||
log.d("showNotification[${account.acct.pretty}] id=${first.notification.id} is already shown.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= 23 && PrefB.bpDivideNotification(pref)) {
|
for (item in dstListData.reversed()) {
|
||||||
val activeNotificationMap = HashMap<String, StatusBarNotification>().apply {
|
val itemTag = "$notificationTag/${item.notification.id}"
|
||||||
notificationManager.activeNotifications?.forEach {
|
|
||||||
if (it != null &&
|
if (lastPostId != null &&
|
||||||
it.id == PollingWorker.NOTIFICATION_ID &&
|
item.notification.time_created_at <= lastPostTime &&
|
||||||
it.tag.startsWith("$notificationTag/")
|
item.notification.id <= lastPostId
|
||||||
) {
|
) {
|
||||||
put(it.tag, it)
|
// 掲載済みデータより古い通知は再表示しない
|
||||||
}
|
log.d("ignore $itemTag ${item.notification.time_created_at} <= $lastPostTime && ${item.notification.id} <= $lastPostId")
|
||||||
}
|
continue
|
||||||
}
|
}
|
||||||
for (item in dstListData.reversed()) {
|
|
||||||
val itemTag = "$notificationTag/${item.notification.id}"
|
|
||||||
|
|
||||||
if (lastPostId != null &&
|
// ignore if already showing
|
||||||
item.notification.time_created_at <= lastPostTime &&
|
if (activeNotificationMap.remove(itemTag) != null) {
|
||||||
item.notification.id <= lastPostId
|
log.d("ignore $itemTag is in activeNotificationMap")
|
||||||
) {
|
continue
|
||||||
// 掲載済みデータより古い通知は再表示しない
|
|
||||||
log.d("ignore $itemTag ${item.notification.time_created_at} <= $lastPostTime && ${item.notification.id} <= $lastPostId")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignore if already showing
|
|
||||||
if (activeNotificationMap.remove(itemTag) != null) {
|
|
||||||
log.d("ignore $itemTag is in activeNotificationMap")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
createNotification(
|
|
||||||
itemTag,
|
|
||||||
notificationId = item.notification.id.toString()
|
|
||||||
) { builder ->
|
|
||||||
|
|
||||||
builder.setWhen(item.notification.time_created_at)
|
|
||||||
|
|
||||||
val summary = item.getNotificationLine()
|
|
||||||
builder.setContentTitle(summary)
|
|
||||||
val content = item.notification.status?.decoded_content?.notEmpty()
|
|
||||||
if (content != null) {
|
|
||||||
builder.setStyle(
|
|
||||||
NotificationCompat.BigTextStyle()
|
|
||||||
.setBigContentTitle(summary)
|
|
||||||
.setSummaryText(item.accessInfo.acct.pretty)
|
|
||||||
.bigText(content)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
builder.setContentText(item.accessInfo.acct.pretty)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT < 26) {
|
|
||||||
var iv = 0
|
|
||||||
|
|
||||||
if (PrefB.bpNotificationSound(pref)) {
|
|
||||||
|
|
||||||
var soundUri: Uri? = null
|
|
||||||
|
|
||||||
try {
|
|
||||||
val whoAcct = account.getFullAcct(item.notification.account)
|
|
||||||
soundUri = AcctColor.getNotificationSound(whoAcct).mayUri()
|
|
||||||
} catch (ex: Throwable) {
|
|
||||||
log.trace(ex)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (soundUri == null) {
|
|
||||||
soundUri = account.sound_uri.mayUri()
|
|
||||||
}
|
|
||||||
|
|
||||||
var bSoundSet = false
|
|
||||||
if (soundUri != null) {
|
|
||||||
try {
|
|
||||||
builder.setSound(soundUri)
|
|
||||||
bSoundSet = true
|
|
||||||
} catch (ex: Throwable) {
|
|
||||||
log.trace(ex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!bSoundSet) {
|
|
||||||
iv = iv or NotificationCompat.DEFAULT_SOUND
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PrefB.bpNotificationVibration(pref)) {
|
|
||||||
iv = iv or NotificationCompat.DEFAULT_VIBRATE
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PrefB.bpNotificationLED(pref)) {
|
|
||||||
iv = iv or NotificationCompat.DEFAULT_LIGHTS
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.setDefaults(iv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// リストにない通知は消さない。ある通知をユーザが指で削除した際に他の通知が残ってほしい場合がある
|
|
||||||
} else {
|
|
||||||
log.d("showNotification[${account.acct.pretty}] creating notification(1)")
|
|
||||||
createNotification(notificationTag) { builder ->
|
|
||||||
|
|
||||||
builder.setWhen(first.notification.time_created_at)
|
createNotification(itemTag, notificationId = item.notification.id.toString()) { builder ->
|
||||||
|
builder.setWhen(item.notification.time_created_at)
|
||||||
var a = first.getNotificationLine()
|
val summary = item.getNotificationLine()
|
||||||
|
builder.setContentTitle(summary)
|
||||||
if (dataList.size == 1) {
|
when (val content = item.notification.status?.decoded_content?.notEmpty()) {
|
||||||
builder.setContentTitle(a)
|
null -> builder.setContentText(item.accessInfo.acct.pretty)
|
||||||
builder.setContentText(account.acct.pretty)
|
else -> {
|
||||||
} else {
|
val style = NotificationCompat.BigTextStyle()
|
||||||
val header =
|
.setBigContentTitle(summary)
|
||||||
context.getString(R.string.notification_count, dataList.size)
|
.setSummaryText(item.accessInfo.acct.pretty)
|
||||||
builder.setContentTitle(header)
|
.bigText(content)
|
||||||
.setContentText(a)
|
builder.setStyle(style)
|
||||||
|
|
||||||
val style = NotificationCompat.InboxStyle()
|
|
||||||
.setBigContentTitle(header)
|
|
||||||
.setSummaryText(account.acct.pretty)
|
|
||||||
for (i in 0..4) {
|
|
||||||
if (i >= dataList.size) break
|
|
||||||
val item = dataList[i]
|
|
||||||
a = item.getNotificationLine()
|
|
||||||
style.addLine(a)
|
|
||||||
}
|
}
|
||||||
builder.setStyle(style)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT < 26) {
|
|
||||||
|
|
||||||
var iv = 0
|
|
||||||
|
|
||||||
if (PrefB.bpNotificationSound(pref)) {
|
|
||||||
|
|
||||||
var soundUri: Uri? = null
|
|
||||||
|
|
||||||
try {
|
|
||||||
val whoAcct =
|
|
||||||
account.getFullAcct(first.notification.account)
|
|
||||||
soundUri = AcctColor.getNotificationSound(whoAcct).mayUri()
|
|
||||||
} catch (ex: Throwable) {
|
|
||||||
log.trace(ex)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (soundUri == null) {
|
|
||||||
soundUri = account.sound_uri.mayUri()
|
|
||||||
}
|
|
||||||
|
|
||||||
var bSoundSet = false
|
|
||||||
if (soundUri != null) {
|
|
||||||
try {
|
|
||||||
builder.setSound(soundUri)
|
|
||||||
bSoundSet = true
|
|
||||||
} catch (ex: Throwable) {
|
|
||||||
log.trace(ex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!bSoundSet) {
|
|
||||||
iv = iv or NotificationCompat.DEFAULT_SOUND
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PrefB.bpNotificationVibration(pref)) {
|
|
||||||
iv = iv or NotificationCompat.DEFAULT_VIBRATE
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PrefB.bpNotificationLED(pref)) {
|
|
||||||
iv = iv or NotificationCompat.DEFAULT_LIGHTS
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.setDefaults(iv)
|
|
||||||
}
|
}
|
||||||
|
if (Build.VERSION.SDK_INT < 26) setNotificationSound25(builder, item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nt.updatePost(first.notification.id, first.notification.time_created_at)
|
// リストにない通知は消さない。ある通知をユーザが指で削除した際に他の通知が残ってほしい場合がある
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateNotificationMerged(
|
||||||
|
notificationTag: String,
|
||||||
|
first: NotificationData,
|
||||||
|
) {
|
||||||
|
log.d("updateNotificationMerged[${account.acct.pretty}] creating notification(1)")
|
||||||
|
createNotification(notificationTag) { builder ->
|
||||||
|
builder.setWhen(first.notification.time_created_at)
|
||||||
|
val a = first.getNotificationLine()
|
||||||
|
val dataList = dstListData
|
||||||
|
if (dataList.size == 1) {
|
||||||
|
builder.setContentTitle(a)
|
||||||
|
builder.setContentText(account.acct.pretty)
|
||||||
|
} else {
|
||||||
|
val header = context.getString(R.string.notification_count, dataList.size)
|
||||||
|
builder.setContentTitle(header).setContentText(a)
|
||||||
|
|
||||||
|
val style = NotificationCompat.InboxStyle()
|
||||||
|
.setBigContentTitle(header)
|
||||||
|
.setSummaryText(account.acct.pretty)
|
||||||
|
|
||||||
|
for (i in 0 until min(4, dataList.size)) {
|
||||||
|
style.addLine(dataList[i].getNotificationLine())
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.setStyle(style)
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT < 26) setNotificationSound25(builder, first)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Android 8 未満ではチャネルではなく通知に個別にスタイルを設定する
|
||||||
|
@TargetApi(25)
|
||||||
|
private fun setNotificationSound25(builder: NotificationCompat.Builder, item: NotificationData) {
|
||||||
|
var iv = 0
|
||||||
|
if (PrefB.bpNotificationSound(pref)) {
|
||||||
|
var soundUri: Uri? = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
val whoAcct = account.getFullAcct(item.notification.account)
|
||||||
|
soundUri = AcctColor.getNotificationSound(whoAcct).mayUri()
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
log.trace(ex)
|
||||||
|
}
|
||||||
|
if (soundUri == null) {
|
||||||
|
soundUri = account.sound_uri.mayUri()
|
||||||
|
}
|
||||||
|
|
||||||
|
var bSoundSet = false
|
||||||
|
if (soundUri != null) {
|
||||||
|
try {
|
||||||
|
builder.setSound(soundUri)
|
||||||
|
bSoundSet = true
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
log.trace(ex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!bSoundSet) {
|
||||||
|
iv = iv or NotificationCompat.DEFAULT_SOUND
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PrefB.bpNotificationVibration(pref)) {
|
||||||
|
iv = iv or NotificationCompat.DEFAULT_VIBRATE
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PrefB.bpNotificationLED(pref)) {
|
||||||
|
iv = iv or NotificationCompat.DEFAULT_LIGHTS
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.setDefaults(iv)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createNotification(
|
private fun createNotification(
|
||||||
|
@ -701,41 +656,35 @@ class TaskRunner(
|
||||||
"type" to trackingType.str,
|
"type" to trackingType.str,
|
||||||
"notificationId" to notificationId
|
"notificationId" to notificationId
|
||||||
).mapNotNull {
|
).mapNotNull {
|
||||||
val second = it.second
|
when (val second = it.second) {
|
||||||
if (second == null) {
|
null -> null
|
||||||
null
|
else -> "${it.first.encodePercent()}=${second.encodePercent()}"
|
||||||
} else {
|
|
||||||
"${it.first.encodePercent()}=${second.encodePercent()}"
|
|
||||||
}
|
}
|
||||||
}.joinToString("&")
|
}.joinToString("&")
|
||||||
|
|
||||||
setContentIntent(
|
val flag = PendingIntent.FLAG_UPDATE_CURRENT or
|
||||||
PendingIntent.getActivity(
|
(if (Build.VERSION.SDK_INT >= 23) PendingIntent.FLAG_IMMUTABLE else 0)
|
||||||
context,
|
|
||||||
257,
|
|
||||||
Intent(context, ActCallback::class.java).apply {
|
|
||||||
data =
|
|
||||||
"subwaytooter://notification_click/?$params".toUri()
|
|
||||||
|
|
||||||
// FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY を付与してはいけない
|
PendingIntent.getActivity(
|
||||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
context,
|
||||||
},
|
257,
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT or (if (Build.VERSION.SDK_INT >= 23) PendingIntent.FLAG_IMMUTABLE else 0)
|
Intent(context, ActCallback::class.java).apply {
|
||||||
)
|
data = "subwaytooter://notification_click/?$params".toUri()
|
||||||
)
|
// FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY を付与してはいけない
|
||||||
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
},
|
||||||
|
flag
|
||||||
|
)?.let { setContentIntent(it) }
|
||||||
|
|
||||||
setDeleteIntent(
|
PendingIntent.getBroadcast(
|
||||||
PendingIntent.getBroadcast(
|
context,
|
||||||
context,
|
257,
|
||||||
257,
|
Intent(context, EventReceiver::class.java).apply {
|
||||||
Intent(context, EventReceiver::class.java).apply {
|
action = EventReceiver.ACTION_NOTIFICATION_DELETE
|
||||||
action = EventReceiver.ACTION_NOTIFICATION_DELETE
|
data = "subwaytooter://notification_delete/?$params".toUri()
|
||||||
data =
|
},
|
||||||
"subwaytooter://notification_delete/?$params".toUri()
|
flag
|
||||||
},
|
)?.let { setDeleteIntent(it) }
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT or (if (Build.VERSION.SDK_INT >= 23) PendingIntent.FLAG_IMMUTABLE else 0)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
setAutoCancel(true)
|
setAutoCancel(true)
|
||||||
|
|
||||||
|
@ -752,16 +701,10 @@ class TaskRunner(
|
||||||
}
|
}
|
||||||
|
|
||||||
log.d("showNotification[${account.acct.pretty}] creating notification(3)")
|
log.d("showNotification[${account.acct.pretty}] creating notification(3)")
|
||||||
|
|
||||||
setContent(builder)
|
setContent(builder)
|
||||||
|
|
||||||
log.d("showNotification[${account.acct.pretty}] set notification...")
|
log.d("showNotification[${account.acct.pretty}] set notification...")
|
||||||
|
notificationManager.notify(notificationTag, PollingWorker.NOTIFICATION_ID, builder.build())
|
||||||
notificationManager.notify(
|
|
||||||
notificationTag,
|
|
||||||
PollingWorker.NOTIFICATION_ID,
|
|
||||||
builder.build()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,8 @@ import jp.juggler.subwaytooter.App1
|
||||||
import jp.juggler.subwaytooter.PrefB
|
import jp.juggler.subwaytooter.PrefB
|
||||||
import jp.juggler.subwaytooter.R
|
import jp.juggler.subwaytooter.R
|
||||||
import jp.juggler.subwaytooter.Styler
|
import jp.juggler.subwaytooter.Styler
|
||||||
import jp.juggler.subwaytooter.api.TootApiClient
|
import jp.juggler.subwaytooter.api.*
|
||||||
import jp.juggler.subwaytooter.api.TootApiResult
|
|
||||||
import jp.juggler.subwaytooter.api.TootParser
|
|
||||||
import jp.juggler.subwaytooter.api.entity.*
|
import jp.juggler.subwaytooter.api.entity.*
|
||||||
import jp.juggler.subwaytooter.api.runApiTask
|
|
||||||
import jp.juggler.subwaytooter.dialog.DlgConfirm
|
import jp.juggler.subwaytooter.dialog.DlgConfirm
|
||||||
import jp.juggler.subwaytooter.emoji.CustomEmoji
|
import jp.juggler.subwaytooter.emoji.CustomEmoji
|
||||||
import jp.juggler.subwaytooter.span.MyClickableSpan
|
import jp.juggler.subwaytooter.span.MyClickableSpan
|
||||||
|
@ -23,12 +20,10 @@ import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import java.lang.Exception
|
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
interface PostCompleteCallback {
|
interface PostCompleteCallback {
|
||||||
|
|
||||||
fun onPostComplete(targetAccount: SavedAccount, status: TootStatus)
|
fun onPostComplete(targetAccount: SavedAccount, status: TootStatus)
|
||||||
fun onScheduledPostComplete(targetAccount: SavedAccount)
|
fun onScheduledPostComplete(targetAccount: SavedAccount)
|
||||||
}
|
}
|
||||||
|
@ -247,10 +242,6 @@ class PostImpl(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TootApiResultException(val result: TootApiResult?) : Exception(result?.error ?: "cancelled.") {
|
|
||||||
constructor(error: String) : this(TootApiResult(error))
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun getWebVisibility(
|
private suspend fun getWebVisibility(
|
||||||
client: TootApiClient,
|
client: TootApiClient,
|
||||||
parser: TootParser,
|
parser: TootParser,
|
||||||
|
@ -261,10 +252,10 @@ class PostImpl(
|
||||||
val r2 = getCredential(client, parser)
|
val r2 = getCredential(client, parser)
|
||||||
|
|
||||||
val credentialTmp = resultCredentialTmp
|
val credentialTmp = resultCredentialTmp
|
||||||
?: throw TootApiResultException(r2)
|
?: errorApiResult(r2)
|
||||||
|
|
||||||
val privacy = credentialTmp.source?.privacy
|
val privacy = credentialTmp.source?.privacy
|
||||||
?: throw TootApiResultException(activity.getString(R.string.cant_get_web_setting_visibility))
|
?: errorApiResult(activity.getString(R.string.cant_get_web_setting_visibility))
|
||||||
|
|
||||||
return TootVisibility.parseMastodon(privacy)
|
return TootVisibility.parseMastodon(privacy)
|
||||||
// may null, not error
|
// may null, not error
|
||||||
|
@ -278,7 +269,7 @@ class PostImpl(
|
||||||
) {
|
) {
|
||||||
if (actual != extra || checkFun(instance)) return
|
if (actual != extra || checkFun(instance)) return
|
||||||
val strVisibility = Styler.getVisibilityString(activity, account.isMisskey, extra)
|
val strVisibility = Styler.getVisibilityString(activity, account.isMisskey, extra)
|
||||||
throw TootApiResultException(activity.getString(R.string.server_has_no_support_of_visibility, strVisibility))
|
errorApiResult(activity.getString(R.string.server_has_no_support_of_visibility, strVisibility))
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun checkVisibility(
|
private suspend fun checkVisibility(
|
||||||
|
@ -391,7 +382,7 @@ class PostImpl(
|
||||||
}
|
}
|
||||||
.toPostRequestBuilder()
|
.toPostRequestBuilder()
|
||||||
)
|
)
|
||||||
if (r == null || r.error != null) throw TootApiResultException(r)
|
if (r == null || r.error != null) errorApiResult(r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (array.isNotEmpty()) json["mediaIds"] = array
|
if (array.isNotEmpty()) json["mediaIds"] = array
|
||||||
|
@ -449,7 +440,7 @@ class PostImpl(
|
||||||
|
|
||||||
if (scheduledAt != 0L) {
|
if (scheduledAt != 0L) {
|
||||||
if (!instance.versionGE(TootInstance.VERSION_2_7_0_rc1)) {
|
if (!instance.versionGE(TootInstance.VERSION_2_7_0_rc1)) {
|
||||||
throw TootApiResultException(activity.getString(R.string.scheduled_status_requires_mastodon_2_7_0))
|
errorApiResult(activity.getString(R.string.scheduled_status_requires_mastodon_2_7_0))
|
||||||
}
|
}
|
||||||
// UTCの日時を渡す
|
// UTCの日時を渡す
|
||||||
val c = GregorianCalendar.getInstance(TimeZone.getTimeZone("UTC"))
|
val c = GregorianCalendar.getInstance(TimeZone.getTimeZone("UTC"))
|
||||||
|
|
|
@ -145,6 +145,7 @@ class ProgressResponseBody private constructor(
|
||||||
override fun source(): BufferedSource = wrappedSource
|
override fun source(): BufferedSource = wrappedSource
|
||||||
|
|
||||||
// To avoid double buffering, We have to make ForwardingBufferedSource.
|
// To avoid double buffering, We have to make ForwardingBufferedSource.
|
||||||
|
@Suppress("TooManyFunctions")
|
||||||
internal open class ForwardingBufferedSource(
|
internal open class ForwardingBufferedSource(
|
||||||
private val originalSource: BufferedSource,
|
private val originalSource: BufferedSource,
|
||||||
) : BufferedSource {
|
) : BufferedSource {
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package jp.juggler.util
|
package jp.juggler.util
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap
|
||||||
|
|
||||||
// same as x?.let{ dst.add(it) }
|
// same as x?.let{ dst.add(it) }
|
||||||
fun <T> T.addTo(dst: ArrayList<T>) = dst.add(this)
|
fun <T> T.addTo(dst: ArrayList<T>) = dst.add(this)
|
||||||
|
|
||||||
|
@ -11,3 +13,6 @@ fun <E : Map<*, *>> E?.notEmpty(): E? =
|
||||||
|
|
||||||
fun ByteArray?.notEmpty(): ByteArray? =
|
fun ByteArray?.notEmpty(): ByteArray? =
|
||||||
if (this?.isNotEmpty() == true) this else null
|
if (this?.isNotEmpty() == true) this else null
|
||||||
|
|
||||||
|
fun <K, V : Any?> Iterable<Pair<K, V>>.toMutableMap() =
|
||||||
|
LinkedHashMap<K, V>().also { map -> forEach { map[it.first] = it.second } }
|
||||||
|
|
Loading…
Reference in New Issue