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.graphics.Typeface
|
||||
import android.os.*
|
||||
import android.text.InputType
|
||||
import android.text.Spannable
|
||||
import android.view.*
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.*
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.GravityCompat
|
||||
|
@ -40,8 +38,7 @@ class ActMain : AppCompatActivity(),
|
|||
MyClickableSpanHandler {
|
||||
|
||||
companion object {
|
||||
|
||||
val log = LogCategory("ActMain")
|
||||
private val log = LogCategory("ActMain")
|
||||
|
||||
// リザルト
|
||||
const val RESULT_APP_DATA_IMPORT = Activity.RESULT_FIRST_USER
|
||||
|
@ -122,9 +119,7 @@ class ActMain : AppCompatActivity(),
|
|||
|
||||
var quickTootVisibility: TootVisibility = TootVisibility.AccountSetting
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// 変更しない変数(lateinit)
|
||||
|
||||
lateinit var llFormRoot: LinearLayout
|
||||
lateinit var llQuickTootBar: LinearLayout
|
||||
lateinit var etQuickToot: MyEditText
|
||||
lateinit var btnQuickToot: ImageButton
|
||||
|
@ -879,106 +874,11 @@ class ActMain : AppCompatActivity(),
|
|||
return rv
|
||||
}
|
||||
|
||||
internal fun initUI() {
|
||||
setContentView(R.layout.act_main)
|
||||
App1.initEdgeToEdge(this)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// lateinitなビュー変数を初期化する
|
||||
fun findViews() {
|
||||
llFormRoot = findViewById(R.id.llFormRoot)
|
||||
llEmpty = findViewById(R.id.llEmpty)
|
||||
|
||||
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)
|
||||
btnToot = findViewById(R.id.btnToot)
|
||||
vFooterDivider1 = findViewById(R.id.vFooterDivider1)
|
||||
|
@ -990,128 +890,58 @@ class ActMain : AppCompatActivity(),
|
|||
btnQuickToot = findViewById(R.id.btnQuickToot)
|
||||
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)
|
||||
btnMenu.setOnClickListener(this)
|
||||
btnQuickToot.setOnClickListener(this)
|
||||
btnQuickTootMenu.setOnClickListener(this)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
internal fun initUI() {
|
||||
setContentView(R.layout.act_main)
|
||||
App1.initEdgeToEdge(this)
|
||||
|
||||
quickTootVisibility =
|
||||
TootVisibility.parseSavedVisibility(PrefS.spQuickTootVisibility(pref))
|
||||
?: quickTootVisibility
|
||||
|
||||
Column.reloadDefaultColor(this, pref)
|
||||
|
||||
reloadFonts()
|
||||
reloadIconSize()
|
||||
reloadRoundRatio()
|
||||
reloadBoostAlpha()
|
||||
|
||||
findViews()
|
||||
|
||||
drawer.addDrawerListener(this)
|
||||
drawer.setExclusionSize(stripIconSize)
|
||||
|
||||
SideMenuAdapter(this, handler, findViewById(R.id.nav_view), drawer)
|
||||
|
||||
llFormRoot.setPadding(0, 0, 0, screenBottomPadding)
|
||||
|
||||
justifyWindowContentPortrait()
|
||||
|
||||
initUIQuickToot()
|
||||
|
||||
svColumnStrip.isHorizontalFadingEdgeEnabled = true
|
||||
|
||||
completionHelper = CompletionHelper(this, pref, appState.handler)
|
||||
|
||||
val dm = resources.displayMetrics
|
||||
|
||||
val density = dm.density
|
||||
|
||||
var mediaThumbHeightDp = 64
|
||||
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()
|
||||
|
||||
reloadMediaHeight()
|
||||
val columnWMin = loadColumnMin(density)
|
||||
val sw = dm.widthPixels
|
||||
|
||||
// スマホモードとタブレットモードの切り替え
|
||||
if (PrefB.bpDisableTabletMode(pref) || sw < columnWMin * 2) {
|
||||
// SmartPhone mode
|
||||
phoneViews = PhoneViews(this)
|
||||
} else {
|
||||
// Tablet mode
|
||||
tabletViews = TabletViews(this)
|
||||
}
|
||||
|
||||
val tmpPhonePager: MyViewPager = findViewById(R.id.viewPager)
|
||||
val tmpTabletPager: RecyclerView = findViewById(R.id.rvPager)
|
||||
|
||||
phoneTab({ env ->
|
||||
tmpTabletPager.visibility = View.GONE
|
||||
env.initUI(tmpPhonePager)
|
||||
|
@ -1119,23 +949,8 @@ class ActMain : AppCompatActivity(),
|
|||
}, { env ->
|
||||
tmpPhonePager.visibility = View.GONE
|
||||
env.initUI(tmpTabletPager)
|
||||
|
||||
})
|
||||
|
||||
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.postedStatusId = null
|
||||
}
|
||||
|
||||
|
|
|
@ -1,20 +1,14 @@
|
|||
package jp.juggler.subwaytooter
|
||||
|
||||
import android.content.Intent
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.RawRes
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatus
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.util.*
|
||||
import java.lang.ref.WeakReference
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.min
|
||||
|
||||
|
||||
fun ActMain.resizeAutoCW(columnW: Int) {
|
||||
val sv = PrefS.spAutoCWLines(pref)
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
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.TootVisibility
|
||||
import jp.juggler.subwaytooter.dialog.pickAccount
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.subwaytooter.util.CompletionHelper
|
||||
import jp.juggler.subwaytooter.util.PostCompleteCallback
|
||||
import jp.juggler.subwaytooter.util.PostImpl
|
||||
import jp.juggler.util.hideKeyboard
|
||||
|
@ -14,6 +20,49 @@ import org.jetbrains.anko.imageResource
|
|||
val ActMain.quickTootText: String
|
||||
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() {
|
||||
btnQuickTootMenu.imageResource =
|
||||
when (val resId = Styler.getVisibilityIconId(false, quickTootVisibility)) {
|
||||
|
|
|
@ -1,17 +1,181 @@
|
|||
package jp.juggler.subwaytooter
|
||||
|
||||
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.span.MyClickableSpan
|
||||
import jp.juggler.subwaytooter.util.CustomShare
|
||||
import jp.juggler.subwaytooter.view.ListDivider
|
||||
import jp.juggler.subwaytooter.view.TabletColumnDivider
|
||||
import jp.juggler.util.attrColor
|
||||
import jp.juggler.util.getAdaptiveRippleDrawableRound
|
||||
import jp.juggler.util.notZero
|
||||
import jp.juggler.util.*
|
||||
import org.jetbrains.anko.backgroundDrawable
|
||||
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時に呼ばれる
|
||||
fun ActMain.reloadTimeZone() {
|
||||
try {
|
||||
|
@ -22,7 +186,7 @@ fun ActMain.reloadTimeZone(){
|
|||
}
|
||||
TootStatus.date_format.timeZone = tz
|
||||
} catch (ex: Throwable) {
|
||||
ActMain.log.e(ex, "getTimeZone failed.")
|
||||
log.e(ex, "getTimeZone failed.")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ import jp.juggler.subwaytooter.view.MyNetworkImageView
|
|||
import jp.juggler.util.*
|
||||
import kotlinx.coroutines.Job
|
||||
import okhttp3.Request
|
||||
import okhttp3.internal.closeQuietly
|
||||
import ru.gildor.coroutines.okhttp.await
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
@ -40,7 +41,6 @@ class ActPost : AppCompatActivity(),
|
|||
MyClickableSpanHandler, AttachmentPicker.Callback {
|
||||
|
||||
companion object {
|
||||
|
||||
internal val log = LogCategory("ActPost")
|
||||
|
||||
var refActPost: WeakReference<ActPost>? = null
|
||||
|
@ -69,8 +69,8 @@ class ActPost : AppCompatActivity(),
|
|||
/////////////////////////////////////////////////
|
||||
|
||||
fun createIntent(
|
||||
|
||||
activity: Activity,
|
||||
|
||||
accountDbId: Long,
|
||||
|
||||
multiWindowMode: Boolean,
|
||||
|
@ -127,9 +127,12 @@ class ActPost : AppCompatActivity(),
|
|||
val request = Request.Builder().url(url).build()
|
||||
val call = App1.ok_http_client.newCall(request)
|
||||
val response = call.await()
|
||||
try {
|
||||
if (response.isSuccessful) return true
|
||||
|
||||
log.e(TootApiClient.formatResponse(response, "check_exist failed."))
|
||||
} finally {
|
||||
response.closeQuietly()
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
log.trace(ex)
|
||||
}
|
||||
|
@ -165,7 +168,7 @@ class ActPost : AppCompatActivity(),
|
|||
|
||||
lateinit var cbQuote: CheckBox
|
||||
|
||||
lateinit var spEnquete: Spinner
|
||||
lateinit var spPollType: Spinner
|
||||
lateinit var llEnquete: View
|
||||
lateinit var etChoices: List<MyEditText>
|
||||
|
||||
|
@ -197,8 +200,6 @@ class ActPost : AppCompatActivity(),
|
|||
|
||||
///////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
var states = ActPostStates()
|
||||
|
||||
internal var account: SavedAccount? = null
|
||||
|
@ -220,18 +221,6 @@ class ActPost : AppCompatActivity(),
|
|||
|
||||
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 =
|
||||
ViewTreeObserver.OnScrollChangedListener { completionHelper.onScrollChanged() }
|
||||
|
||||
|
@ -292,24 +281,65 @@ class ActPost : AppCompatActivity(),
|
|||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||
return when {
|
||||
super.onKeyDown(keyCode, event) -> true
|
||||
event == null -> false
|
||||
else -> event.isCtrlPressed
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
if (isMultiWindowPost) ActMain.refActMain?.get()?.closeList?.add(WeakReference(this))
|
||||
App1.setActivityTheme(this, noActionBar = true)
|
||||
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 {
|
||||
val rv = super.onKeyUp(keyCode, event)
|
||||
if (event?.isCtrlPressed == true) {
|
||||
ActMain.log.d("onKeyUp code=$keyCode rv=$rv")
|
||||
when (keyCode) {
|
||||
KeyEvent.KEYCODE_T -> btnPost.performClick()
|
||||
override fun onDestroy() {
|
||||
completionHelper.onDestroy()
|
||||
attachmentUploader.onActivityDestroy()
|
||||
super.onDestroy()
|
||||
}
|
||||
return true
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
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) {
|
||||
|
@ -335,93 +365,31 @@ class ActPost : AppCompatActivity(),
|
|||
}
|
||||
}
|
||||
|
||||
// unused? for REQUEST_CODE_ATTACHMENT
|
||||
// fun handleAttachmentResult(ar: ActivityResult?) {
|
||||
// if (ar?.resultCode == RESULT_OK) {
|
||||
// ar.data?.handleGetContentResult(contentResolver)?.let { checkAttachments(it) }
|
||||
// }
|
||||
// }
|
||||
|
||||
override fun onBackPressed() {
|
||||
saveDraft()
|
||||
super.onBackPressed()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
refActPost = WeakReference(this)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
|
||||
// 編集中にホーム画面を押したり他アプリに移動する場合は下書きを保存する
|
||||
// やや過剰な気がするが、自アプリに戻ってくるときにランチャーからアイコンタップされると
|
||||
// メイン画面より上にあるアクティビティはすべて消されてしまうので
|
||||
// このタイミングで保存するしかない
|
||||
if (!isPostComplete) {
|
||||
saveDraft()
|
||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||
return when {
|
||||
super.onKeyDown(keyCode, event) -> true
|
||||
event == null -> false
|
||||
else -> event.isCtrlPressed
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
if (isMultiWindowPost) ActMain.refActMain?.get()?.closeList?.add(WeakReference(this))
|
||||
|
||||
App1.setActivityTheme(this, noActionBar = 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)
|
||||
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
|
||||
val rv = super.onKeyUp(keyCode, event)
|
||||
if (event?.isCtrlPressed == true) {
|
||||
ActMain.log.d("onKeyUp code=$keyCode rv=$rv")
|
||||
when (keyCode) {
|
||||
KeyEvent.KEYCODE_T -> btnPost.performClick()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
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()
|
||||
return rv
|
||||
}
|
||||
|
||||
override fun onMyClickableSpanClicked(viewClicked: View, span: MyClickableSpan) {
|
||||
openBrowser(span.linkInfo.url)
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<String>,
|
||||
grantResults: IntArray,
|
||||
) {
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
||||
attachmentPicker.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
}
|
||||
|
@ -439,12 +407,10 @@ class ActPost : AppCompatActivity(),
|
|||
}
|
||||
|
||||
fun initUI() {
|
||||
density = resources.displayMetrics.density
|
||||
|
||||
setContentView(R.layout.act_post)
|
||||
App1.initEdgeToEdge(this)
|
||||
|
||||
if (PrefB.bpPostButtonBarTop(this)) {
|
||||
if (PrefB.bpPostButtonBarTop(pref)) {
|
||||
val bar = findViewById<View>(R.id.llFooterBar)
|
||||
val parent = bar.parent as ViewGroup
|
||||
parent.removeView(bar)
|
||||
|
@ -480,7 +446,7 @@ class ActPost : AppCompatActivity(),
|
|||
|
||||
cbQuote = findViewById(R.id.cbQuote)
|
||||
|
||||
spEnquete = findViewById<Spinner>(R.id.spEnquete).apply {
|
||||
spPollType = findViewById<Spinner>(R.id.spEnquete).apply {
|
||||
this.adapter = ArrayAdapter(
|
||||
this@ActPost,
|
||||
android.R.layout.simple_spinner_item,
|
||||
|
@ -499,17 +465,13 @@ class ActPost : AppCompatActivity(),
|
|||
updateTextCount()
|
||||
}
|
||||
|
||||
override fun onItemSelected(
|
||||
parent: AdapterView<*>?,
|
||||
view: View?,
|
||||
position: Int,
|
||||
id: Long,
|
||||
) {
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
showPoll()
|
||||
updateTextCount()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
llEnquete = findViewById(R.id.llEnquete)
|
||||
llExpire = findViewById(R.id.llExpire)
|
||||
cbHideTotals = findViewById(R.id.cbHideTotals)
|
||||
|
@ -542,7 +504,6 @@ class ActPost : AppCompatActivity(),
|
|||
ibSchedule = findViewById(R.id.ibSchedule)
|
||||
ibScheduleReset = findViewById(R.id.ibScheduleReset)
|
||||
|
||||
|
||||
arrayOf(
|
||||
ibSchedule,
|
||||
ibScheduleReset,
|
||||
|
@ -559,9 +520,7 @@ class ActPost : AppCompatActivity(),
|
|||
|
||||
ivMedia.forEach { it.setOnClickListener(this) }
|
||||
|
||||
cbContentWarning.setOnCheckedChangeListener { _, _ ->
|
||||
showContentWarningEnabled()
|
||||
}
|
||||
cbContentWarning.setOnCheckedChangeListener { _, _ -> showContentWarningEnabled() }
|
||||
|
||||
completionHelper = CompletionHelper(this, pref, appState.handler)
|
||||
completionHelper.attachEditText(formRoot, etContent, false, object : CompletionHelper.Callback2 {
|
||||
|
@ -569,12 +528,23 @@ class ActPost : AppCompatActivity(),
|
|||
updateTextCount()
|
||||
}
|
||||
|
||||
override fun canOpenPopup(): Boolean {
|
||||
return true
|
||||
}
|
||||
override fun canOpenPopup(): Boolean = 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)
|
||||
|
||||
for (et in etChoices) {
|
||||
et.addTextChangedListener(textWatcher)
|
||||
}
|
||||
|
|
|
@ -38,7 +38,6 @@ fun ActPost.decodeAttachments(sv: String) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
fun ActPost.showMediaAttachment() {
|
||||
if (isFinishing) return
|
||||
llAttachment.vg(attachmentList.isNotEmpty())
|
||||
|
|
|
@ -74,7 +74,7 @@ fun ActPost.updateTextCount() {
|
|||
}
|
||||
}
|
||||
|
||||
when (spEnquete.selectedItemPosition) {
|
||||
when (spPollType.selectedItemPosition) {
|
||||
1 -> checkEnqueteLength()
|
||||
|
||||
2 -> {
|
||||
|
|
|
@ -102,7 +102,7 @@ fun ActPost.resetText() {
|
|||
attachmentList.clear()
|
||||
cbQuote.isChecked = false
|
||||
etContent.setText("")
|
||||
spEnquete.setSelection(0, false)
|
||||
spPollType.setSelection(0, false)
|
||||
etChoices.forEach { it.setText("") }
|
||||
accountList = SavedAccount.loadAccountList(this)
|
||||
SavedAccount.sort(accountList)
|
||||
|
@ -132,7 +132,6 @@ fun ActPost.afterUpdateText() {
|
|||
updateTextCount()
|
||||
}
|
||||
|
||||
|
||||
// 初期化時と投稿完了時とリセット確認後に呼ばれる
|
||||
fun ActPost.updateText(
|
||||
intent: Intent,
|
||||
|
@ -294,7 +293,7 @@ fun ActPost.performPost() {
|
|||
var pollExpireSeconds = 0
|
||||
var pollHideTotals = false
|
||||
var pollMultipleChoice = false
|
||||
when (spEnquete.selectedItemPosition) {
|
||||
when (spPollType.selectedItemPosition) {
|
||||
1 -> {
|
||||
pollType = TootPollsType.Mastodon
|
||||
pollItems = pollChoiceList()
|
||||
|
|
|
@ -5,7 +5,7 @@ import jp.juggler.util.notEmpty
|
|||
import jp.juggler.util.vg
|
||||
|
||||
fun ActPost.showPoll() {
|
||||
val i = spEnquete.selectedItemPosition
|
||||
val i = spPollType.selectedItemPosition
|
||||
llEnquete.vg(i != 0)
|
||||
llExpire.vg(i == 1)
|
||||
cbHideTotals.vg(i == 1)
|
||||
|
@ -14,7 +14,7 @@ fun ActPost.showPoll() {
|
|||
|
||||
// 投票が有効で何か入力済みなら真
|
||||
fun ActPost.hasPoll(): Boolean {
|
||||
if( spEnquete.selectedItemPosition <= 0) return false
|
||||
if (spPollType.selectedItemPosition <= 0) return false
|
||||
return etChoices.any { it.text.toString().isNotBlank() }
|
||||
}
|
||||
|
||||
|
@ -30,4 +30,3 @@ fun ActPost.pollExpireSeconds(): Int {
|
|||
val m = etExpireMinutes.text.toString().trim().toDoubleOrNull().finiteOrZero()
|
||||
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_QUOTE = "quotedRenote" // 歴史的な理由で名前がMisskey用になってる
|
||||
|
||||
|
||||
fun ActPost.saveDraft() {
|
||||
val content = etContent.text.toString()
|
||||
val contentWarning =
|
||||
if (cbContentWarning.isChecked) etContentWarning.text.toString() else ""
|
||||
|
||||
val isEnquete = spEnquete.selectedItemPosition > 0
|
||||
val isEnquete = spPollType.selectedItemPosition > 0
|
||||
|
||||
val strChoice = arrayOf(
|
||||
if (isEnquete) etChoices[0].text.toString() else "",
|
||||
|
@ -88,7 +87,7 @@ fun ActPost.saveDraft() {
|
|||
// deprecated. but still used in old draft.
|
||||
// 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_HIDE_TOTALS] = cbHideTotals.isChecked
|
||||
|
@ -236,11 +235,11 @@ fun ActPost.restoreDraft(draft: JsonObject) {
|
|||
|
||||
val sv = draft.string(DRAFT_POLL_TYPE)
|
||||
if (sv != null) {
|
||||
spEnquete.setSelection(sv.toPollTypeIndex())
|
||||
spPollType.setSelection(sv.toPollTypeIndex())
|
||||
} else {
|
||||
// old draft
|
||||
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)
|
||||
|
@ -369,7 +368,7 @@ fun ActPost.initializeFromRedraftStatus(account: SavedAccount, jsonText: String)
|
|||
}
|
||||
|
||||
else -> {
|
||||
spEnquete.setSelection(
|
||||
spPollType.setSelection(
|
||||
if (srcEnquete.pollType == TootPollsType.FriendsNico) {
|
||||
2
|
||||
} else {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package jp.juggler.subwaytooter
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import jp.juggler.subwaytooter.api.TootParser
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
package jp.juggler.subwaytooter
|
||||
|
||||
import android.view.View
|
||||
import jp.juggler.subwaytooter.api.entity.TootVisibility
|
||||
|
||||
fun ActPost.showContentWarningEnabled() {
|
||||
etContentWarning.visibility = if (cbContentWarning.isChecked) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
|
|
|
@ -4,11 +4,10 @@ import android.content.Context
|
|||
import android.os.Environment
|
||||
import android.util.LruCache
|
||||
import androidx.annotation.RawRes
|
||||
import jp.juggler.subwaytooter.api.ApiPath.READ_LIMIT
|
||||
import jp.juggler.subwaytooter.Column.Companion.log
|
||||
import jp.juggler.subwaytooter.api.*
|
||||
import jp.juggler.subwaytooter.api.ApiPath.READ_LIMIT
|
||||
import jp.juggler.subwaytooter.api.entity.*
|
||||
import jp.juggler.subwaytooter.table.SavedAccount
|
||||
import jp.juggler.util.*
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
@ -826,94 +825,6 @@ fun Column.makeProfileStatusesUrl(profileId: EntityId?): String {
|
|||
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"
|
||||
|
|
|
@ -7,6 +7,7 @@ import jp.juggler.subwaytooter.notification.PollingWorker
|
|||
import jp.juggler.subwaytooter.streaming.StreamManager
|
||||
import jp.juggler.subwaytooter.streaming.StreamStatus
|
||||
import jp.juggler.subwaytooter.util.ScrollPosition
|
||||
import jp.juggler.util.notEmpty
|
||||
import jp.juggler.util.runOnMainLooper
|
||||
import kotlin.math.max
|
||||
|
||||
|
@ -73,37 +74,22 @@ fun Column.mergeStreamingMessage() {
|
|||
|
||||
lastShowStreamData.set(now)
|
||||
|
||||
// read items while queue is not empty
|
||||
val tmpList = ArrayList<TimelineItem>()
|
||||
while (true) tmpList.add(streamDataQueue.poll() ?: break)
|
||||
if (tmpList.isEmpty()) return
|
||||
.apply { while (true) add(streamDataQueue.poll() ?: break) }.notEmpty()
|
||||
?: return
|
||||
|
||||
// キューから読めた件数が0の場合を除き、少し後に再処理させることでマージ漏れを防ぐ
|
||||
handler.postDelayed(procMergeStreamingMessage, 333L)
|
||||
|
||||
// ストリーミングされるデータは全てID順に並んでいるはず
|
||||
// orderId順ソートを徹底する
|
||||
tmpList.sortByDescending { it.getOrderId() }
|
||||
|
||||
val listNew = duplicateMap.filterDuplicate(tmpList)
|
||||
if (listNew.isEmpty()) return
|
||||
// 既にカラム中にあるデータは除去する
|
||||
val listNew = duplicateMap.filterDuplicate(tmpList).notEmpty() ?: return
|
||||
|
||||
for (item in listNew) {
|
||||
if (enableSpeech && item is TootStatus) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
sendToSpeech(listNew)
|
||||
injectToPollingWorker(listNew)
|
||||
|
||||
// 最新のIDをsince_idとして覚える(ソートはしない)
|
||||
var newIdMax: EntityId? = null
|
||||
|
@ -154,17 +140,7 @@ fun Column.mergeStreamingMessage() {
|
|||
// 画面復帰時の自動リフレッシュではギャップが残る可能性がある
|
||||
if (bPutGap) {
|
||||
bPutGap = false
|
||||
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.")
|
||||
}
|
||||
addGapAfterStreaming(listNew, newIdMin)
|
||||
}
|
||||
|
||||
val changeList = ArrayList<AdapterChange>()
|
||||
|
@ -192,8 +168,49 @@ fun Column.mergeStreamingMessage() {
|
|||
listData.addAll(0, listNew)
|
||||
|
||||
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 {
|
||||
holderSp == null -> {
|
||||
// スクロール位置が先頭なら先頭にする
|
||||
|
@ -218,19 +235,7 @@ fun Column.mergeStreamingMessage() {
|
|||
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) {
|
||||
|
|
|
@ -3,6 +3,7 @@ package jp.juggler.subwaytooter
|
|||
import android.os.SystemClock
|
||||
import jp.juggler.subwaytooter.api.*
|
||||
import jp.juggler.subwaytooter.api.entity.*
|
||||
import jp.juggler.subwaytooter.api.finder.*
|
||||
import jp.juggler.subwaytooter.notification.PollingWorker
|
||||
import jp.juggler.util.*
|
||||
import java.lang.StringBuilder
|
||||
|
|
|
@ -3,6 +3,7 @@ package jp.juggler.subwaytooter
|
|||
import android.os.SystemClock
|
||||
import jp.juggler.subwaytooter.api.*
|
||||
import jp.juggler.subwaytooter.api.entity.*
|
||||
import jp.juggler.subwaytooter.api.finder.*
|
||||
import jp.juggler.subwaytooter.notification.PollingWorker
|
||||
import jp.juggler.subwaytooter.util.OpenSticker
|
||||
import jp.juggler.util.*
|
||||
|
|
|
@ -3,6 +3,7 @@ package jp.juggler.subwaytooter
|
|||
import android.os.SystemClock
|
||||
import jp.juggler.subwaytooter.api.*
|
||||
import jp.juggler.subwaytooter.api.entity.*
|
||||
import jp.juggler.subwaytooter.api.finder.*
|
||||
import jp.juggler.subwaytooter.util.ScrollPosition
|
||||
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.TootApiResult
|
||||
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.refreshMSP
|
||||
import jp.juggler.subwaytooter.search.NotestockHelper.loadingNotestock
|
||||
|
|
|
@ -635,6 +635,7 @@ internal class DlgContextMenu(
|
|||
return true
|
||||
}
|
||||
|
||||
@Suppress("ComplexMethod")
|
||||
private fun ActMain.onClickUser(v: View, pos: Int, who: TootAccount, whoRef: TootAccountRef): Boolean {
|
||||
when (v.id) {
|
||||
R.id.btnReportUser -> userReportForm(accessInfo, who)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package jp.juggler.subwaytooter
|
||||
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
|
@ -51,21 +52,13 @@ fun ItemViewHolder.showStatus(status: TootStatus, colorBg: Int = 0) {
|
|||
llStatus.visibility = View.VISIBLE
|
||||
|
||||
if (status.conversation_main) {
|
||||
|
||||
val conversationMainBgColor =
|
||||
PrefI.ipConversationMainTootBgColor(activity.pref).notZero()
|
||||
?: (activity.attrColor(R.attr.colorImageButtonAccent) and 0xffffff) or 0x20000000
|
||||
|
||||
this.viewRoot.setBackgroundColor(conversationMainBgColor)
|
||||
} else {
|
||||
val c = colorBg.notZero()
|
||||
|
||||
?: when (status.bookmarked) {
|
||||
colorBg.notZero() ?: when (status.bookmarked) {
|
||||
true -> PrefI.ipEventBgColorBookmark(App1.pref)
|
||||
false -> 0
|
||||
}.notZero()
|
||||
|
||||
?: when (status.getBackgroundColorType(accessInfo)) {
|
||||
}.notZero() ?: when (status.getBackgroundColorType(accessInfo)) {
|
||||
TootVisibility.UnlistedHome -> ItemViewHolder.toot_color_unlisted
|
||||
TootVisibility.PrivateFollowers -> ItemViewHolder.toot_color_follower
|
||||
TootVisibility.DirectSpecified -> ItemViewHolder.toot_color_direct_user
|
||||
|
@ -73,12 +66,8 @@ fun ItemViewHolder.showStatus(status: TootStatus, colorBg: Int = 0) {
|
|||
// TODO add color setting for limited?
|
||||
TootVisibility.Limited -> ItemViewHolder.toot_color_follower
|
||||
else -> 0
|
||||
}
|
||||
|
||||
if (c != 0) {
|
||||
this.viewRoot.backgroundColor = c
|
||||
}
|
||||
}
|
||||
}.notZero()
|
||||
}?.let { viewRoot.backgroundColor = it }
|
||||
|
||||
showStatusTime(activity, tvTime, who = status.account, status = status)
|
||||
|
||||
|
@ -88,11 +77,6 @@ fun ItemViewHolder.showStatus(status: TootStatus, colorBg: Int = 0) {
|
|||
|
||||
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
|
||||
nameInvalidator.register(whoRef.decoded_display_name)
|
||||
ivAvatar.setImageUrl(
|
||||
|
@ -101,34 +85,23 @@ fun ItemViewHolder.showStatus(status: TootStatus, colorBg: Int = 0) {
|
|||
accessInfo.supplyBaseUrl(who.avatar_static),
|
||||
accessInfo.supplyBaseUrl(who.avatar)
|
||||
)
|
||||
// }
|
||||
|
||||
showOpenSticker(who)
|
||||
|
||||
var content = status.decoded_content
|
||||
|
||||
// ニコフレのアンケートの表示
|
||||
val enquete = status.enquete
|
||||
when {
|
||||
enquete == null -> {
|
||||
val modifiedContent = if (status.time_deleted_at > 0L) {
|
||||
SpannableStringBuilder()
|
||||
.append('(')
|
||||
.append(
|
||||
activity.getString(
|
||||
R.string.deleted_at,
|
||||
TootStatus.formatTime(activity, status.time_deleted_at, true)
|
||||
)
|
||||
)
|
||||
.append(')')
|
||||
} else {
|
||||
showPoll(status) ?: status.decoded_content
|
||||
}
|
||||
|
||||
enquete.pollType == TootPollsType.FriendsNico && enquete.type != TootPolls.TYPE_ENQUETE -> {
|
||||
// フレニコの投票の結果表示は普通にテキストを表示するだけでよい
|
||||
}
|
||||
|
||||
else -> {
|
||||
|
||||
// アンケートの本文を上書きする
|
||||
val question = enquete.decoded_question
|
||||
if (question.isNotBlank()) content = question
|
||||
|
||||
showEnqueteItems(status, enquete)
|
||||
}
|
||||
}
|
||||
|
||||
showPreviewCard(status)
|
||||
|
||||
// if( status.decoded_tags == null ){
|
||||
// tvTags.setVisibility( View.GONE );
|
||||
// }else{
|
||||
|
@ -143,27 +116,41 @@ fun ItemViewHolder.showStatus(status: TootStatus, colorBg: Int = 0) {
|
|||
tvMentions.text = status.decoded_mentions
|
||||
}
|
||||
|
||||
if (status.time_deleted_at > 0L) {
|
||||
val s = SpannableStringBuilder()
|
||||
.append('(')
|
||||
.append(
|
||||
activity.getString(
|
||||
R.string.deleted_at,
|
||||
TootStatus.formatTime(activity, status.time_deleted_at, true)
|
||||
)
|
||||
)
|
||||
.append(')')
|
||||
content = s
|
||||
}
|
||||
tvContent.text = modifiedContent
|
||||
contentInvalidator.register(modifiedContent)
|
||||
|
||||
tvContent.text = content
|
||||
contentInvalidator.register(content)
|
||||
|
||||
activity.checkAutoCW(status, content)
|
||||
activity.checkAutoCW(status, modifiedContent)
|
||||
val r = status.auto_cw
|
||||
|
||||
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
|
||||
when {
|
||||
decodedSpoilerText.isNotEmpty() -> {
|
||||
|
@ -172,7 +159,7 @@ fun ItemViewHolder.showStatus(status: TootStatus, colorBg: Int = 0) {
|
|||
tvContentWarning.text = status.decoded_spoiler_text
|
||||
spoilerInvalidator.register(status.decoded_spoiler_text)
|
||||
val cwShown = ContentWarning.isShown(status, accessInfo.expand_cw)
|
||||
showContent(cwShown)
|
||||
setContentVisibility(cwShown)
|
||||
}
|
||||
|
||||
r?.decodedSpoilerText != null -> {
|
||||
|
@ -181,7 +168,7 @@ fun ItemViewHolder.showStatus(status: TootStatus, colorBg: Int = 0) {
|
|||
tvContentWarning.text = r.decodedSpoilerText
|
||||
spoilerInvalidator.register(r.decodedSpoilerText)
|
||||
val cwShown = ContentWarning.isShown(status, accessInfo.expand_cw)
|
||||
showContent(cwShown)
|
||||
setContentVisibility(cwShown)
|
||||
}
|
||||
|
||||
else -> {
|
||||
|
@ -190,55 +177,25 @@ fun ItemViewHolder.showStatus(status: TootStatus, colorBg: Int = 0) {
|
|||
llContents.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
val mediaAttachments = status.media_attachments
|
||||
if (mediaAttachments == null || mediaAttachments.isEmpty()) {
|
||||
flMedia.visibility = View.GONE
|
||||
llMedia.visibility = View.GONE
|
||||
btnShowMedia.visibility = View.GONE
|
||||
} else {
|
||||
flMedia.visibility = View.VISIBLE
|
||||
|
||||
// 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
|
||||
)
|
||||
private fun ItemViewHolder.setContentVisibility(shown: Boolean) {
|
||||
llContents.visibility = if (shown) View.VISIBLE else View.GONE
|
||||
btnContentWarning.setText(if (shown) R.string.hide else R.string.show)
|
||||
statusShowing?.let { status ->
|
||||
val r = status.auto_cw
|
||||
tvContent.minLines = r?.originalLineCount ?: -1
|
||||
if (r?.decodedSpoilerText != null) {
|
||||
// 自動CWの場合はContentWarningのテキストを切り替える
|
||||
tvContentWarning.text =
|
||||
if (shown) activity.getString(R.string.auto_cw_prefix) else r.decodedSpoilerText
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
makeReactionsView(status)
|
||||
|
||||
buttonsForStatus?.bind(status, (item as? TootNotification))
|
||||
private fun ItemViewHolder.showApplicationAndLanguage(status: TootStatus) {
|
||||
|
||||
var sb: StringBuilder? = null
|
||||
|
||||
fun prepareSb(): StringBuilder =
|
||||
sb?.append(", ") ?: StringBuilder().also { sb = it }
|
||||
|
||||
|
@ -259,7 +216,7 @@ fun ItemViewHolder.showStatus(status: TootStatus, colorBg: Int = 0) {
|
|||
tvApplication.vg(sb != null)?.text = sb
|
||||
}
|
||||
|
||||
fun ItemViewHolder.showOpenSticker(who: TootAccount) {
|
||||
private fun ItemViewHolder.showOpenSticker(who: TootAccount) {
|
||||
try {
|
||||
if (!Column.showOpenSticker) return
|
||||
|
||||
|
@ -306,17 +263,47 @@ fun ItemViewHolder.showOpenSticker(who: TootAccount) {
|
|||
}
|
||||
}
|
||||
|
||||
fun ItemViewHolder.showContent(shown: Boolean) {
|
||||
llContents.visibility = if (shown) View.VISIBLE else View.GONE
|
||||
btnContentWarning.setText(if (shown) R.string.hide else R.string.show)
|
||||
statusShowing?.let { status ->
|
||||
val r = status.auto_cw
|
||||
tvContent.minLines = r?.originalLineCount ?: -1
|
||||
if (r?.decodedSpoilerText != null) {
|
||||
// 自動CWの場合はContentWarningのテキストを切り替える
|
||||
tvContentWarning.text =
|
||||
if (shown) activity.getString(R.string.auto_cw_prefix) else r.decodedSpoilerText
|
||||
private fun ItemViewHolder.showAttachments(status: TootStatus) {
|
||||
val mediaAttachments = status.media_attachments
|
||||
if (mediaAttachments == null || mediaAttachments.isEmpty()) {
|
||||
flMedia.visibility = View.GONE
|
||||
llMedia.visibility = View.GONE
|
||||
btnShowMedia.visibility = View.GONE
|
||||
} else {
|
||||
flMedia.visibility = View.VISIBLE
|
||||
|
||||
// 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
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import jp.juggler.subwaytooter.*
|
||||
import jp.juggler.subwaytooter.api.*
|
||||
|
@ -29,13 +30,17 @@ private class BoostImpl(
|
|||
val visibility: TootVisibility? = null,
|
||||
val callback: () -> Unit,
|
||||
) {
|
||||
val parser = TootParser(activity, accessInfo)
|
||||
var resultStatus: TootStatus? = null
|
||||
var resultUnrenoteId: EntityId? = null
|
||||
|
||||
// Mastodonは非公開トゥートをブーストできるのは本人だけ
|
||||
val isPrivateToot = accessInfo.isMastodon &&
|
||||
private val isPrivateToot = accessInfo.isMastodon &&
|
||||
statusArg.visibility == TootVisibility.PrivateFollowers
|
||||
|
||||
var bConfirmed = false
|
||||
private var bConfirmed = false
|
||||
|
||||
fun preCheck(): Boolean {
|
||||
private fun preCheck(): Boolean {
|
||||
|
||||
// アカウントからステータスにブースト操作を行っているなら、何もしない
|
||||
if (activity.appState.isBusyBoost(accessInfo, statusArg)) {
|
||||
|
@ -52,7 +57,7 @@ private class BoostImpl(
|
|||
return true
|
||||
}
|
||||
|
||||
fun confirm(): Boolean {
|
||||
private fun confirm(): Boolean {
|
||||
if (bConfirmed) return true
|
||||
DlgConfirm.open(
|
||||
activity,
|
||||
|
@ -88,48 +93,100 @@ private class BoostImpl(
|
|||
return false
|
||||
}
|
||||
|
||||
fun run() {
|
||||
if (!preCheck()) return
|
||||
if (!confirm()) return
|
||||
|
||||
activity.appState.setBusyBoost(accessInfo, statusArg)
|
||||
// ブースト表示を更新中にする
|
||||
activity.showColumnMatchAccount(accessInfo)
|
||||
// misskeyは非公開トゥートをブーストできないっぽい
|
||||
|
||||
launchMain {
|
||||
var resultStatus: TootStatus? = null
|
||||
var resultUnrenoteId: EntityId? = null
|
||||
val result = activity.runApiTask(accessInfo, progressStyle = ApiTask.PROGRESS_NONE) { client ->
|
||||
|
||||
val parser = TootParser(this, accessInfo)
|
||||
|
||||
val targetStatus = if (crossAccountMode.isRemote) {
|
||||
val (result, status) = client.syncStatus(accessInfo, statusArg)
|
||||
if (status == null) return@runApiTask result
|
||||
if (status.reblogged) {
|
||||
return@runApiTask TootApiResult(getString(R.string.already_boosted))
|
||||
}
|
||||
status
|
||||
} else {
|
||||
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
|
||||
?: return@runApiTask TootApiResult("missing renote id.")
|
||||
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 {
|
||||
}.toPostRequestBuilder()
|
||||
)?.also {
|
||||
if (it.response?.code == 204) {
|
||||
resultUnrenoteId = myRenoteId
|
||||
}
|
||||
|
@ -139,16 +196,11 @@ private class BoostImpl(
|
|||
"/api/notes/create",
|
||||
accessInfo.putMisskeyApiToken().apply {
|
||||
put("renoteId", targetStatus.id.toString())
|
||||
}
|
||||
.toPostRequestBuilder()
|
||||
)
|
||||
?.also { result ->
|
||||
}.toPostRequestBuilder()
|
||||
)?.also { result ->
|
||||
val jsonObject = result.jsonObject
|
||||
if (jsonObject != null) {
|
||||
val outerStatus = parser.status(
|
||||
jsonObject.jsonObject("createdNote")
|
||||
?: jsonObject
|
||||
)
|
||||
val outerStatus = parser.status(jsonObject.jsonObject("createdNote") ?: jsonObject)
|
||||
val innerStatus = outerStatus?.reblog ?: outerStatus
|
||||
if (outerStatus != null && innerStatus != null && outerStatus != innerStatus) {
|
||||
innerStatus.myRenoteId = outerStatus.id
|
||||
|
@ -174,92 +226,29 @@ private class BoostImpl(
|
|||
resultStatus = s?.reblog ?: s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun run() {
|
||||
if (!preCheck()) return
|
||||
if (!confirm()) return
|
||||
|
||||
// ブースト表示を更新中にする
|
||||
activity.appState.setBusyBoost(accessInfo, statusArg)
|
||||
activity.showColumnMatchAccount(accessInfo)
|
||||
|
||||
launchMain {
|
||||
val result = activity.runApiTask(accessInfo, progressStyle = ApiTask.PROGRESS_NONE) { client ->
|
||||
try {
|
||||
val targetStatus = syncStatus(client)
|
||||
boostApi(client, targetStatus)
|
||||
} catch (ex: TootApiResultException) {
|
||||
ex.result
|
||||
}
|
||||
}
|
||||
// 更新中状態をリセット
|
||||
activity.appState.resetBusyBoost(accessInfo, statusArg)
|
||||
|
||||
if (result != null) {
|
||||
val unrenoteId = resultUnrenoteId
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// 結果に関わらず、更新中状態から復帰させる
|
||||
// カラムデータの書き換え
|
||||
after(result, resultStatus, resultUnrenoteId)
|
||||
// result == null の場合でも更新中表示の解除が必要になる
|
||||
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.Intent
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
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.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
|
@ -114,4 +115,3 @@ object EntityIdSerializer : KSerializer<EntityId> {
|
|||
override fun deserialize(decoder: Decoder): EntityId =
|
||||
EntityId(decoder.decodeString())
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import java.io.EOFException
|
||||
import java.io.InputStream
|
||||
import java.util.*
|
||||
|
||||
|
@ -21,135 +19,7 @@ object EmojiMap {
|
|||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
private fun readStream(appContext: Context, inStream: InputStream) {
|
||||
val assetManager = appContext.assets!!
|
||||
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
|
||||
}
|
||||
EmojiMapLoader(appContext, this).readStream(inStream)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.service.notification.StatusBarNotification
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import jp.juggler.subwaytooter.ActCallback
|
||||
|
@ -463,55 +463,62 @@ class TaskRunner(
|
|||
}
|
||||
|
||||
fun updateNotification() {
|
||||
|
||||
val notificationTag = when (trackingName) {
|
||||
"" -> "${account.db_id}/_"
|
||||
else -> "${account.db_id}/$trackingName"
|
||||
}
|
||||
|
||||
val nt = NotificationTracking.load(account.acct.pretty, account.db_id, trackingName)
|
||||
val dataList = dstListData
|
||||
val first = dataList.firstOrNull()
|
||||
if (first == null) {
|
||||
when (val first = dstListData.firstOrNull()) {
|
||||
null -> {
|
||||
log.d("showNotification[${account.acct.pretty}/$notificationTag] cancel notification.")
|
||||
removeNotification(notificationTag)
|
||||
}
|
||||
else -> {
|
||||
when {
|
||||
// 先頭にあるデータが同じなら、通知を更新しない
|
||||
// このマーカーは端末再起動時にリセットされるので、再起動後は通知が出るはず
|
||||
first.notification.time_created_at == nt.post_time && first.notification.id == nt.post_id ->
|
||||
log.d("showNotification[${account.acct.pretty}] id=${first.notification.id} is already shown.")
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeNotification(notificationTag: String) {
|
||||
if (Build.VERSION.SDK_INT >= 23 && PrefB.bpDivideNotification(pref)) {
|
||||
notificationManager.activeNotifications?.forEach {
|
||||
if (it != null &&
|
||||
it.id == PollingWorker.NOTIFICATION_ID &&
|
||||
it.tag.startsWith("$notificationTag/")
|
||||
) {
|
||||
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)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@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 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)) {
|
||||
val activeNotificationMap = HashMap<String, StatusBarNotification>().apply {
|
||||
notificationManager.activeNotifications?.forEach {
|
||||
if (it != null &&
|
||||
it.id == PollingWorker.NOTIFICATION_ID &&
|
||||
it.tag.startsWith("$notificationTag/")
|
||||
) {
|
||||
put(it.tag, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
for (item in dstListData.reversed()) {
|
||||
val itemTag = "$notificationTag/${item.notification.id}"
|
||||
|
||||
|
@ -530,32 +537,61 @@ class TaskRunner(
|
|||
continue
|
||||
}
|
||||
|
||||
createNotification(
|
||||
itemTag,
|
||||
notificationId = item.notification.id.toString()
|
||||
) { builder ->
|
||||
|
||||
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()
|
||||
when (val content = item.notification.status?.decoded_content?.notEmpty()) {
|
||||
null -> builder.setContentText(item.accessInfo.acct.pretty)
|
||||
else -> {
|
||||
val style = NotificationCompat.BigTextStyle()
|
||||
.setBigContentTitle(summary)
|
||||
.setSummaryText(item.accessInfo.acct.pretty)
|
||||
.bigText(content)
|
||||
)
|
||||
} else {
|
||||
builder.setContentText(item.accessInfo.acct.pretty)
|
||||
builder.setStyle(style)
|
||||
}
|
||||
}
|
||||
if (Build.VERSION.SDK_INT < 26) setNotificationSound25(builder, item)
|
||||
}
|
||||
}
|
||||
// リストにない通知は消さない。ある通知をユーザが指で削除した際に他の通知が残ってほしい場合がある
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT < 26) {
|
||||
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 {
|
||||
|
@ -564,7 +600,6 @@ class TaskRunner(
|
|||
} catch (ex: Throwable) {
|
||||
log.trace(ex)
|
||||
}
|
||||
|
||||
if (soundUri == null) {
|
||||
soundUri = account.sound_uri.mayUri()
|
||||
}
|
||||
|
@ -593,86 +628,6 @@ class TaskRunner(
|
|||
|
||||
builder.setDefaults(iv)
|
||||
}
|
||||
}
|
||||
}
|
||||
// リストにない通知は消さない。ある通知をユーザが指で削除した際に他の通知が残ってほしい場合がある
|
||||
} else {
|
||||
log.d("showNotification[${account.acct.pretty}] creating notification(1)")
|
||||
createNotification(notificationTag) { builder ->
|
||||
|
||||
builder.setWhen(first.notification.time_created_at)
|
||||
|
||||
var a = first.getNotificationLine()
|
||||
|
||||
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..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)
|
||||
}
|
||||
}
|
||||
}
|
||||
nt.updatePost(first.notification.id, first.notification.time_created_at)
|
||||
}
|
||||
|
||||
private fun createNotification(
|
||||
notificationTag: String,
|
||||
|
@ -701,41 +656,35 @@ class TaskRunner(
|
|||
"type" to trackingType.str,
|
||||
"notificationId" to notificationId
|
||||
).mapNotNull {
|
||||
val second = it.second
|
||||
if (second == null) {
|
||||
null
|
||||
} else {
|
||||
"${it.first.encodePercent()}=${second.encodePercent()}"
|
||||
when (val second = it.second) {
|
||||
null -> null
|
||||
else -> "${it.first.encodePercent()}=${second.encodePercent()}"
|
||||
}
|
||||
}.joinToString("&")
|
||||
|
||||
setContentIntent(
|
||||
val flag = PendingIntent.FLAG_UPDATE_CURRENT or
|
||||
(if (Build.VERSION.SDK_INT >= 23) PendingIntent.FLAG_IMMUTABLE else 0)
|
||||
|
||||
PendingIntent.getActivity(
|
||||
context,
|
||||
257,
|
||||
Intent(context, ActCallback::class.java).apply {
|
||||
data =
|
||||
"subwaytooter://notification_click/?$params".toUri()
|
||||
|
||||
data = "subwaytooter://notification_click/?$params".toUri()
|
||||
// FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY を付与してはいけない
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
},
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or (if (Build.VERSION.SDK_INT >= 23) PendingIntent.FLAG_IMMUTABLE else 0)
|
||||
)
|
||||
)
|
||||
flag
|
||||
)?.let { setContentIntent(it) }
|
||||
|
||||
setDeleteIntent(
|
||||
PendingIntent.getBroadcast(
|
||||
context,
|
||||
257,
|
||||
Intent(context, EventReceiver::class.java).apply {
|
||||
action = EventReceiver.ACTION_NOTIFICATION_DELETE
|
||||
data =
|
||||
"subwaytooter://notification_delete/?$params".toUri()
|
||||
data = "subwaytooter://notification_delete/?$params".toUri()
|
||||
},
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or (if (Build.VERSION.SDK_INT >= 23) PendingIntent.FLAG_IMMUTABLE else 0)
|
||||
)
|
||||
)
|
||||
flag
|
||||
)?.let { setDeleteIntent(it) }
|
||||
|
||||
setAutoCancel(true)
|
||||
|
||||
|
@ -752,16 +701,10 @@ class TaskRunner(
|
|||
}
|
||||
|
||||
log.d("showNotification[${account.acct.pretty}] creating notification(3)")
|
||||
|
||||
setContent(builder)
|
||||
|
||||
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.R
|
||||
import jp.juggler.subwaytooter.Styler
|
||||
import jp.juggler.subwaytooter.api.TootApiClient
|
||||
import jp.juggler.subwaytooter.api.TootApiResult
|
||||
import jp.juggler.subwaytooter.api.TootParser
|
||||
import jp.juggler.subwaytooter.api.*
|
||||
import jp.juggler.subwaytooter.api.entity.*
|
||||
import jp.juggler.subwaytooter.api.runApiTask
|
||||
import jp.juggler.subwaytooter.dialog.DlgConfirm
|
||||
import jp.juggler.subwaytooter.emoji.CustomEmoji
|
||||
import jp.juggler.subwaytooter.span.MyClickableSpan
|
||||
|
@ -23,12 +20,10 @@ import kotlinx.coroutines.Job
|
|||
import kotlinx.coroutines.delay
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import java.lang.Exception
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.*
|
||||
|
||||
interface PostCompleteCallback {
|
||||
|
||||
fun onPostComplete(targetAccount: SavedAccount, status: TootStatus)
|
||||
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(
|
||||
client: TootApiClient,
|
||||
parser: TootParser,
|
||||
|
@ -261,10 +252,10 @@ class PostImpl(
|
|||
val r2 = getCredential(client, parser)
|
||||
|
||||
val credentialTmp = resultCredentialTmp
|
||||
?: throw TootApiResultException(r2)
|
||||
?: errorApiResult(r2)
|
||||
|
||||
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)
|
||||
// may null, not error
|
||||
|
@ -278,7 +269,7 @@ class PostImpl(
|
|||
) {
|
||||
if (actual != extra || checkFun(instance)) return
|
||||
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(
|
||||
|
@ -391,7 +382,7 @@ class PostImpl(
|
|||
}
|
||||
.toPostRequestBuilder()
|
||||
)
|
||||
if (r == null || r.error != null) throw TootApiResultException(r)
|
||||
if (r == null || r.error != null) errorApiResult(r)
|
||||
}
|
||||
}
|
||||
if (array.isNotEmpty()) json["mediaIds"] = array
|
||||
|
@ -449,7 +440,7 @@ class PostImpl(
|
|||
|
||||
if (scheduledAt != 0L) {
|
||||
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の日時を渡す
|
||||
val c = GregorianCalendar.getInstance(TimeZone.getTimeZone("UTC"))
|
||||
|
|
|
@ -145,6 +145,7 @@ class ProgressResponseBody private constructor(
|
|||
override fun source(): BufferedSource = wrappedSource
|
||||
|
||||
// To avoid double buffering, We have to make ForwardingBufferedSource.
|
||||
@Suppress("TooManyFunctions")
|
||||
internal open class ForwardingBufferedSource(
|
||||
private val originalSource: BufferedSource,
|
||||
) : BufferedSource {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package jp.juggler.util
|
||||
|
||||
import java.util.LinkedHashMap
|
||||
|
||||
// same as x?.let{ dst.add(it) }
|
||||
fun <T> T.addTo(dst: ArrayList<T>) = dst.add(this)
|
||||
|
||||
|
@ -11,3 +13,6 @@ fun <E : Map<*, *>> E?.notEmpty(): E? =
|
|||
|
||||
fun ByteArray?.notEmpty(): ByteArray? =
|
||||
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