2018-01-04 19:52:25 +01:00
|
|
|
package jp.juggler.subwaytooter
|
|
|
|
|
|
|
|
import android.annotation.SuppressLint
|
|
|
|
import android.app.Activity
|
|
|
|
import android.app.Dialog
|
|
|
|
import android.content.Intent
|
|
|
|
import android.content.SharedPreferences
|
|
|
|
import android.content.pm.PackageManager
|
|
|
|
import android.graphics.Typeface
|
|
|
|
import android.net.Uri
|
|
|
|
import android.os.AsyncTask
|
|
|
|
import android.os.Build
|
|
|
|
import android.os.Bundle
|
|
|
|
import android.os.Handler
|
|
|
|
import android.support.v4.view.ViewCompat
|
|
|
|
import android.support.v4.view.ViewPager
|
|
|
|
import android.support.v7.app.AlertDialog
|
|
|
|
import android.support.v7.widget.LinearLayoutManager
|
|
|
|
import android.support.v7.widget.RecyclerView
|
|
|
|
import android.text.InputType
|
|
|
|
import android.text.Spannable
|
|
|
|
import android.text.SpannableStringBuilder
|
|
|
|
import android.util.JsonReader
|
|
|
|
import android.view.Gravity
|
|
|
|
import android.view.View
|
|
|
|
import android.support.design.widget.NavigationView
|
|
|
|
import android.support.v4.view.GravityCompat
|
|
|
|
import android.support.v4.widget.DrawerLayout
|
|
|
|
import android.support.v7.app.AppCompatActivity
|
|
|
|
import android.view.Menu
|
|
|
|
import android.view.MenuItem
|
|
|
|
import android.view.Window
|
|
|
|
import android.view.WindowManager
|
|
|
|
import android.view.inputmethod.EditorInfo
|
|
|
|
import android.widget.HorizontalScrollView
|
|
|
|
import android.widget.ImageButton
|
|
|
|
import android.widget.ImageView
|
|
|
|
import android.widget.LinearLayout
|
|
|
|
import android.widget.TextView
|
|
|
|
import jp.juggler.subwaytooter.action.*
|
|
|
|
|
|
|
|
import org.apache.commons.io.IOUtils
|
|
|
|
|
|
|
|
import java.io.File
|
|
|
|
import java.io.FileInputStream
|
|
|
|
import java.io.FileOutputStream
|
|
|
|
import java.io.InputStreamReader
|
|
|
|
import java.lang.ref.WeakReference
|
|
|
|
import java.util.ArrayList
|
|
|
|
import java.util.HashSet
|
|
|
|
import java.util.regex.Pattern
|
|
|
|
|
|
|
|
import jp.juggler.subwaytooter.api.TootApiClient
|
|
|
|
import jp.juggler.subwaytooter.api.TootApiResult
|
|
|
|
import jp.juggler.subwaytooter.api.TootTask
|
|
|
|
import jp.juggler.subwaytooter.api.TootTaskRunner
|
|
|
|
import jp.juggler.subwaytooter.api.entity.TootAccount
|
|
|
|
import jp.juggler.subwaytooter.api.entity.TootStatus
|
|
|
|
import jp.juggler.subwaytooter.dialog.AccountPicker
|
|
|
|
import jp.juggler.subwaytooter.dialog.DlgTextInput
|
|
|
|
import jp.juggler.subwaytooter.table.AcctColor
|
|
|
|
import jp.juggler.subwaytooter.table.SavedAccount
|
|
|
|
import jp.juggler.subwaytooter.dialog.ActionsDialog
|
2018-01-13 07:38:32 +01:00
|
|
|
import jp.juggler.subwaytooter.dialog.ProgressDialogEx
|
2018-01-04 19:52:25 +01:00
|
|
|
import jp.juggler.subwaytooter.view.ColumnStripLinearLayout
|
|
|
|
import jp.juggler.subwaytooter.view.GravitySnapHelper
|
|
|
|
import jp.juggler.subwaytooter.view.MyEditText
|
|
|
|
|
|
|
|
import jp.juggler.subwaytooter.span.MyClickableSpan
|
|
|
|
import jp.juggler.subwaytooter.span.MyClickableSpanClickCallback
|
|
|
|
import jp.juggler.subwaytooter.util.*
|
|
|
|
|
|
|
|
class ActMain : AppCompatActivity()
|
|
|
|
, NavigationView.OnNavigationItemSelectedListener
|
|
|
|
, View.OnClickListener
|
|
|
|
, ViewPager.OnPageChangeListener
|
|
|
|
, Column.Callback
|
2018-01-10 16:47:35 +01:00
|
|
|
, DrawerLayout.DrawerListener {
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
companion object {
|
|
|
|
|
|
|
|
val log = LogCategory("ActMain")
|
|
|
|
|
|
|
|
// リザルト
|
|
|
|
const val RESULT_APP_DATA_IMPORT = Activity.RESULT_FIRST_USER
|
|
|
|
|
|
|
|
// リクエスト
|
|
|
|
const val REQUEST_CODE_COLUMN_LIST = 1
|
|
|
|
const val REQUEST_CODE_ACCOUNT_SETTING = 2
|
|
|
|
const val REQUEST_APP_ABOUT = 3
|
|
|
|
const val REQUEST_CODE_NICKNAME = 4
|
|
|
|
const val REQUEST_CODE_POST = 5
|
|
|
|
const val REQUEST_CODE_COLUMN_COLOR = 6
|
|
|
|
const val REQUEST_CODE_APP_SETTING = 7
|
|
|
|
const val REQUEST_CODE_TEXT = 8
|
|
|
|
|
|
|
|
const val COLUMN_WIDTH_MIN_DP = 300
|
|
|
|
|
|
|
|
const val STATE_CURRENT_PAGE = "current_page"
|
|
|
|
|
|
|
|
internal var sent_intent2 : Intent? = null
|
|
|
|
|
|
|
|
@Suppress("HasPlatformType")
|
|
|
|
val reUrlHashTag = Pattern.compile("\\Ahttps://([^/]+)/tags/([^?#・\\s\\-+.,:;/]+)(?:\\z|[?#])")
|
|
|
|
|
|
|
|
@Suppress("HasPlatformType")
|
|
|
|
val reUserPage = Pattern.compile("\\Ahttps://([^/]+)/@([A-Za-z0-9_]+)(?:\\z|[?#])")
|
|
|
|
|
|
|
|
@Suppress("HasPlatformType")
|
|
|
|
val reStatusPage = Pattern.compile("\\Ahttps://([^/]+)/@([A-Za-z0-9_]+)/(\\d+)(?:\\z|[?#])")
|
|
|
|
}
|
|
|
|
|
|
|
|
// @Override
|
|
|
|
// protected void attachBaseContext(Context newBase) {
|
|
|
|
// super.attachBaseContext( CalligraphyContextWrapper.wrap(newBase));
|
|
|
|
// }
|
|
|
|
|
|
|
|
var density : Float = 0.toFloat()
|
|
|
|
var acct_pad_lr : Int = 0
|
|
|
|
|
|
|
|
lateinit var pref : SharedPreferences
|
|
|
|
lateinit var handler : Handler
|
|
|
|
lateinit var app_state : AppState
|
|
|
|
|
|
|
|
// onActivityResultで設定されてonResumeで消化される
|
|
|
|
// 状態保存の必要なし
|
|
|
|
private var posted_acct : String? = null
|
|
|
|
private var posted_status_id : Long = 0
|
|
|
|
|
|
|
|
var timeline_font_size_sp = Float.NaN
|
|
|
|
var acct_font_size_sp = Float.NaN
|
|
|
|
|
|
|
|
internal var bStart : Boolean = false
|
|
|
|
|
|
|
|
// 画面上のUI操作で生成されて
|
|
|
|
// onPause,onPageDestroy 等のタイミングで閉じられる
|
|
|
|
// 状態保存の必要なし
|
|
|
|
var listItemPopup : StatusButtonsPopup? = null
|
|
|
|
|
|
|
|
private lateinit var llEmpty : View
|
2018-01-10 16:47:35 +01:00
|
|
|
internal lateinit var drawer : DrawerLayout
|
2018-01-04 19:52:25 +01:00
|
|
|
private lateinit var llColumnStrip : ColumnStripLinearLayout
|
|
|
|
private lateinit var svColumnStrip : HorizontalScrollView
|
|
|
|
private lateinit var btnMenu : ImageButton
|
|
|
|
private lateinit var btnToot : ImageButton
|
|
|
|
private lateinit var vFooterDivider1 : View
|
|
|
|
private lateinit var vFooterDivider2 : View
|
|
|
|
|
|
|
|
var timeline_font : Typeface? = null
|
|
|
|
var timeline_font_bold : Typeface? = null
|
|
|
|
|
|
|
|
var dont_crop_media_thumbnail : Boolean = false
|
|
|
|
var shortAcctLocalUser : Boolean = false
|
|
|
|
var avatarIconSize : Int = 0
|
|
|
|
|
|
|
|
private lateinit var llQuickTootBar : View
|
|
|
|
private lateinit var etQuickToot : MyEditText
|
|
|
|
private lateinit var btnQuickToot : ImageButton
|
|
|
|
lateinit var post_helper : PostHelper
|
|
|
|
|
|
|
|
class PhoneEnv {
|
|
|
|
internal lateinit var pager : ViewPager
|
|
|
|
internal lateinit var pager_adapter : ColumnPagerAdapter
|
|
|
|
}
|
|
|
|
class TabletEnv {
|
|
|
|
internal lateinit var tablet_pager : RecyclerView
|
|
|
|
internal lateinit var tablet_pager_adapter : TabletColumnPagerAdapter
|
|
|
|
internal lateinit var tablet_layout_manager : LinearLayoutManager
|
|
|
|
internal lateinit var tablet_snap_helper : GravitySnapHelper
|
|
|
|
}
|
|
|
|
private var phoneEnv : PhoneEnv? = null
|
|
|
|
private var tabletEnv : TabletEnv? = null
|
|
|
|
|
2018-01-10 16:47:35 +01:00
|
|
|
// スマホモードとタブレットモードでコードを切り替える
|
2018-01-04 19:52:25 +01:00
|
|
|
private inline fun <R> phoneTab(lambdaPhone : (PhoneEnv) -> R, lambdaTablet : (TabletEnv) -> R) : R {
|
2018-01-10 16:47:35 +01:00
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
val pe = phoneEnv
|
|
|
|
if(pe != null) return lambdaPhone(pe)
|
2018-01-10 16:47:35 +01:00
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
val te = tabletEnv
|
|
|
|
if(te != null) return lambdaTablet(te)
|
2018-01-10 16:47:35 +01:00
|
|
|
|
|
|
|
throw RuntimeException("missing phoneEnv or tabletEnv")
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
|
2018-01-10 16:47:35 +01:00
|
|
|
// スマホモードならラムダを実行する。タブレットモードならnullを返す
|
|
|
|
private inline fun <R> phoneOnly(lambdaPhone : (PhoneEnv) -> R) : R? {
|
2018-01-04 19:52:25 +01:00
|
|
|
val pe = phoneEnv
|
2018-01-10 16:47:35 +01:00
|
|
|
return if(pe != null) lambdaPhone(pe) else null
|
|
|
|
}
|
|
|
|
|
|
|
|
// タブレットモードならラムダを実行する。スマホモードならnullを返す
|
|
|
|
@Suppress("unused")
|
|
|
|
private inline fun <R> tabOnly(lambdaTablet : (TabletEnv) -> R) : R? {
|
|
|
|
val te = tabletEnv
|
|
|
|
return if(te != null) lambdaTablet(te) else null
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
private val link_click_listener : MyClickableSpanClickCallback = { viewClicked, span ->
|
|
|
|
|
|
|
|
var view = viewClicked
|
|
|
|
var column : Column? = null
|
|
|
|
|
|
|
|
while(true) {
|
|
|
|
val tag = view.tag
|
|
|
|
if(tag is ItemViewHolder) {
|
|
|
|
column = tag.column
|
|
|
|
break
|
|
|
|
} else if(tag is HeaderViewHolderProfile) {
|
|
|
|
column = tag.column
|
|
|
|
break
|
|
|
|
} else if(tag is TabletColumnViewHolder) {
|
|
|
|
column = tag.columnViewHolder.column
|
|
|
|
break
|
|
|
|
} else {
|
|
|
|
val parent = view.parent
|
|
|
|
if(parent is View) {
|
|
|
|
view = parent
|
|
|
|
} else {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
val pos = nextPosition(column)
|
|
|
|
val access_info = column?.access_info
|
|
|
|
|
|
|
|
var tag_list : ArrayList<String>? = null
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
val cs = (viewClicked as TextView).text
|
|
|
|
if(cs is Spannable) {
|
|
|
|
for(s in cs.getSpans(0, cs.length, MyClickableSpan::class.java)) {
|
|
|
|
val m = reUrlHashTag.matcher(s.url)
|
|
|
|
if(m.find()) {
|
|
|
|
val s_tag = if(s.text.startsWith("#")) s.text else "#" + Uri.decode(m.group(2))
|
|
|
|
if(tag_list == null) tag_list = ArrayList()
|
|
|
|
tag_list.add(s_tag)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch(ex : Throwable) {
|
|
|
|
log.trace(ex)
|
|
|
|
}
|
|
|
|
|
2018-01-10 16:47:35 +01:00
|
|
|
ChromeTabOpener(this@ActMain, pos, span.url, accessInfo = access_info, tagList = tag_list).open()
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////
|
|
|
|
// column list
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
2018-01-10 16:47:35 +01:00
|
|
|
val follow_complete_callback : EmptyCallback = {
|
|
|
|
Utils.showToast(this@ActMain, false, R.string.follow_succeeded)
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
|
2018-01-10 16:47:35 +01:00
|
|
|
val unfollow_complete_callback : EmptyCallback = {
|
2018-01-04 19:52:25 +01:00
|
|
|
Utils.showToast(this@ActMain, false, R.string.unfollow_succeeded)
|
|
|
|
}
|
|
|
|
|
2018-01-10 16:47:35 +01:00
|
|
|
val favourite_complete_callback : EmptyCallback = {
|
2018-01-04 19:52:25 +01:00
|
|
|
Utils.showToast(this@ActMain, false, R.string.favourite_succeeded)
|
|
|
|
}
|
|
|
|
|
2018-01-10 16:47:35 +01:00
|
|
|
val unfavourite_complete_callback : EmptyCallback = {
|
2018-01-04 19:52:25 +01:00
|
|
|
Utils.showToast(this@ActMain, false, R.string.unfavourite_succeeded)
|
|
|
|
}
|
|
|
|
|
2018-01-10 16:47:35 +01:00
|
|
|
val boost_complete_callback : EmptyCallback = {
|
2018-01-04 19:52:25 +01:00
|
|
|
Utils.showToast(this@ActMain, false, R.string.boost_succeeded)
|
|
|
|
}
|
|
|
|
|
2018-01-10 16:47:35 +01:00
|
|
|
val unboost_complete_callback : EmptyCallback = {
|
2018-01-04 19:52:25 +01:00
|
|
|
Utils.showToast(this@ActMain, false, R.string.unboost_succeeded)
|
|
|
|
}
|
|
|
|
|
2018-01-10 16:47:35 +01:00
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
private var nScreenColumn : Int = 0
|
|
|
|
private var nColumnWidth : Int = 0
|
|
|
|
|
|
|
|
// 相対時刻の表記を定期的に更新する
|
|
|
|
private val proc_updateRelativeTime = object : Runnable {
|
|
|
|
override fun run() {
|
|
|
|
handler.removeCallbacks(this)
|
|
|
|
if(! bStart) return
|
|
|
|
for(c in app_state.column_list) {
|
|
|
|
c.fireShowContent()
|
|
|
|
}
|
|
|
|
if(pref.getBoolean(Pref.KEY_RELATIVE_TIMESTAMP, false)) {
|
|
|
|
handler.postDelayed(this, 10000L)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private var nAutoCwCellWidth = 0
|
|
|
|
private var nAutoCwLines = 0
|
|
|
|
|
|
|
|
// 簡易投稿入力のテキストを取得
|
|
|
|
val quickTootText : String
|
|
|
|
get() = etQuickToot.text.toString()
|
|
|
|
|
|
|
|
// デフォルトの投稿先アカウントのdb_idを返す
|
|
|
|
val currentPostTargetId : Long
|
2018-01-10 16:47:35 +01:00
|
|
|
get() = phoneTab(
|
|
|
|
{ pe ->
|
|
|
|
val c = pe.pager_adapter.getColumn(pe.pager.currentItem)
|
|
|
|
if(c != null && ! c.access_info.isPseudo) {
|
|
|
|
return c.access_info.db_id
|
|
|
|
}
|
|
|
|
return - 1L
|
|
|
|
},
|
|
|
|
{ _ ->
|
|
|
|
val db_id = App1.pref.getLong(Pref.KEY_TABLET_TOOT_DEFAULT_ACCOUNT, - 1L)
|
|
|
|
val a = SavedAccount.loadAccount(this@ActMain, db_id)
|
|
|
|
return a?.db_id ?: - 1L
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
2018-01-10 16:47:35 +01:00
|
|
|
)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
// スマホモードなら現在のカラムを、タブレットモードなら-1Lを返す
|
|
|
|
// (カラム一覧画面のデフォルト選択位置に使われる)
|
|
|
|
val currentColumn : Int
|
2018-01-10 16:47:35 +01:00
|
|
|
get() = phoneTab(
|
|
|
|
{ pe -> pe.pager.currentItem },
|
|
|
|
{ _ -> - 1 }
|
|
|
|
)
|
|
|
|
|
|
|
|
// 新しいカラムをどこに挿入するか
|
|
|
|
val defaultInsertPosition : Int
|
|
|
|
get() = phoneTab(
|
|
|
|
{ pe -> pe.pager.currentItem + 1 },
|
|
|
|
{ _ -> Integer.MAX_VALUE }
|
|
|
|
)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
private fun validateFloat(fv : Float) : Float {
|
|
|
|
return if(fv.isNaN()) fv else if(fv < 1f) 1f else fv
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onCreate(savedInstanceState : Bundle?) {
|
|
|
|
log.d("onCreate")
|
|
|
|
super.onCreate(savedInstanceState)
|
|
|
|
App1.setActivityTheme(this, true)
|
|
|
|
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
|
|
|
|
|
|
|
handler = Handler()
|
|
|
|
app_state = App1.getAppState(this)
|
|
|
|
pref = App1.pref
|
|
|
|
|
|
|
|
this.density = app_state.density
|
|
|
|
this.acct_pad_lr = (0.5f + 4f * density).toInt()
|
|
|
|
|
|
|
|
timeline_font_size_sp = validateFloat(pref.getFloat(Pref.KEY_TIMELINE_FONT_SIZE, Float.NaN))
|
|
|
|
acct_font_size_sp = validateFloat(pref.getFloat(Pref.KEY_ACCT_FONT_SIZE, Float.NaN))
|
|
|
|
|
|
|
|
initUI()
|
|
|
|
|
|
|
|
updateColumnStrip()
|
|
|
|
|
|
|
|
if(! app_state.column_list.isEmpty()) {
|
|
|
|
|
|
|
|
// 前回最後に表示していたカラムの位置にスクロールする
|
|
|
|
val column_pos = pref.getInt(Pref.KEY_LAST_COLUMN_POS, - 1)
|
|
|
|
if(column_pos >= 0 && column_pos < app_state.column_list.size) {
|
|
|
|
scrollToColumn(column_pos, true)
|
|
|
|
}
|
|
|
|
|
|
|
|
// 表示位置に合わせたイベントを発行
|
|
|
|
phoneTab(
|
|
|
|
{ env -> onPageSelected(env.pager.currentItem) },
|
|
|
|
{ env -> resizeColumnWidth(env) }
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
PollingWorker.queueUpdateNotification(this)
|
|
|
|
|
|
|
|
if(savedInstanceState != null) {
|
|
|
|
sent_intent2?.let { handleSentIntent(it) }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onDestroy() {
|
|
|
|
log.d("onDestroy")
|
|
|
|
super.onDestroy()
|
|
|
|
post_helper.onDestroy()
|
|
|
|
|
|
|
|
// このアクティビティに関連する ColumnViewHolder への参照を全カラムから除去する
|
|
|
|
for(c in app_state.column_list) {
|
|
|
|
c.removeColumnViewHolderByActivity(this)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onSaveInstanceState(outState : Bundle?) {
|
|
|
|
log.d("onSaveInstanceState")
|
|
|
|
super.onSaveInstanceState(outState)
|
2018-01-10 16:47:35 +01:00
|
|
|
outState ?: return
|
|
|
|
|
|
|
|
phoneTab(
|
|
|
|
{ env -> outState.putInt(STATE_CURRENT_PAGE, env.pager.currentItem)},
|
|
|
|
{ env ->
|
2018-01-04 19:52:25 +01:00
|
|
|
val ve = env.tablet_layout_manager.findLastVisibleItemPosition()
|
|
|
|
if(ve != RecyclerView.NO_POSITION) {
|
|
|
|
outState.putInt(STATE_CURRENT_PAGE, ve)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onRestoreInstanceState(savedInstanceState : Bundle) {
|
|
|
|
log.d("onRestoreInstanceState")
|
|
|
|
super.onRestoreInstanceState(savedInstanceState)
|
|
|
|
val pos = savedInstanceState.getInt(STATE_CURRENT_PAGE)
|
|
|
|
if(pos > 0 && pos < app_state.column_list.size) {
|
2018-01-10 16:47:35 +01:00
|
|
|
phoneTab(
|
|
|
|
{ env -> env.pager.currentItem = pos },
|
|
|
|
{ env -> env.tablet_layout_manager
|
|
|
|
.smoothScrollToPosition(env.tablet_pager, null, pos) }
|
|
|
|
)
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
}
|
2018-01-10 16:47:35 +01:00
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
override val isActivityStart : Boolean
|
|
|
|
get() = bStart
|
|
|
|
|
|
|
|
override fun onStart() {
|
|
|
|
super.onStart()
|
|
|
|
|
|
|
|
bStart = true
|
|
|
|
log.d("onStart")
|
|
|
|
|
|
|
|
// アカウント設定から戻ってきたら、カラムを消す必要があるかもしれない
|
|
|
|
run {
|
|
|
|
val new_order = ArrayList<Int>()
|
|
|
|
var i = 0
|
|
|
|
val ie = app_state.column_list.size
|
|
|
|
while(i < ie) {
|
|
|
|
val column = app_state.column_list[i]
|
|
|
|
|
|
|
|
if(! column.access_info.isNA) {
|
|
|
|
val sa = SavedAccount.loadAccount(this@ActMain, column.access_info.db_id)
|
|
|
|
if(sa == null) {
|
|
|
|
++ i
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
new_order.add(i)
|
|
|
|
++ i
|
|
|
|
}
|
|
|
|
|
|
|
|
if(new_order.size != app_state.column_list.size) {
|
|
|
|
setOrder(new_order)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 各カラムのアカウント設定を読み直す
|
|
|
|
reloadAccountSetting()
|
|
|
|
|
|
|
|
// 投稿直後ならカラムの再取得を行う
|
|
|
|
refreshAfterPost()
|
|
|
|
|
|
|
|
// 画面復帰時に再取得やストリーミング開始を行う
|
|
|
|
for(column in app_state.column_list) {
|
|
|
|
column.onStart(this)
|
|
|
|
}
|
|
|
|
|
|
|
|
// カラムの表示範囲インジケータを更新
|
|
|
|
updateColumnStripSelection(- 1, - 1f)
|
|
|
|
|
|
|
|
// 相対時刻表示
|
|
|
|
proc_updateRelativeTime.run()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onStop() {
|
|
|
|
|
|
|
|
log.d("onStop")
|
|
|
|
|
|
|
|
bStart = false
|
|
|
|
|
|
|
|
handler.removeCallbacks(proc_updateRelativeTime)
|
|
|
|
|
|
|
|
post_helper.closeAcctPopup()
|
|
|
|
|
|
|
|
closeListItemPopup()
|
|
|
|
|
|
|
|
app_state.stream_reader.stopAll()
|
|
|
|
|
|
|
|
super.onStop()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onResume() {
|
|
|
|
super.onResume()
|
|
|
|
log.d("onResume")
|
|
|
|
|
|
|
|
MyClickableSpan.link_callback = WeakReference(link_click_listener)
|
|
|
|
|
|
|
|
if(pref.getBoolean(Pref.KEY_DONT_SCREEN_OFF, false)) {
|
|
|
|
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
|
|
|
} else {
|
|
|
|
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
|
|
|
}
|
|
|
|
|
|
|
|
// 外部から受け取ったUriの処理
|
|
|
|
val uri = ActCallback.last_uri.getAndSet(null)
|
|
|
|
if(uri != null) {
|
|
|
|
handleIntentUri(uri)
|
|
|
|
}
|
|
|
|
|
|
|
|
// 外部から受け取ったUriの処理
|
|
|
|
val intent = ActCallback.sent_intent.getAndSet(null)
|
|
|
|
if(intent != null) {
|
|
|
|
handleSentIntent(intent)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onPause() {
|
|
|
|
log.d("onPause")
|
|
|
|
|
|
|
|
// 最後に表示していたカラムの位置
|
2018-01-10 16:47:35 +01:00
|
|
|
val last_pos = phoneTab(
|
|
|
|
{ env -> env.pager.currentItem },
|
|
|
|
{ env -> env.tablet_layout_manager.findFirstVisibleItemPosition() })
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
pref.edit().putInt(Pref.KEY_LAST_COLUMN_POS, last_pos).apply()
|
|
|
|
|
|
|
|
super.onPause()
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun refreshAfterPost() {
|
2018-01-10 16:47:35 +01:00
|
|
|
if(posted_acct?.isNotEmpty() == true) {
|
2018-01-04 19:52:25 +01:00
|
|
|
val refresh_after_toot = pref.getInt(Pref.KEY_REFRESH_AFTER_TOOT, 0)
|
|
|
|
if(refresh_after_toot != Pref.RAT_DONT_REFRESH) {
|
|
|
|
for(column in app_state.column_list) {
|
|
|
|
val a = column.access_info
|
2018-01-10 16:47:35 +01:00
|
|
|
if(a.acct == posted_acct) {
|
|
|
|
column.startRefreshForPost(posted_status_id, refresh_after_toot)
|
|
|
|
}
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
posted_acct = null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun handleSentIntent(intent : Intent) {
|
|
|
|
sent_intent2 = intent
|
2018-01-13 07:15:52 +01:00
|
|
|
AccountPicker.pick(
|
|
|
|
this,
|
|
|
|
bAllowPseudo = false,
|
|
|
|
bAuto = true,
|
|
|
|
message = getString(R.string.account_picker_toot)
|
|
|
|
,dismiss_callback ={ sent_intent2 = null }
|
|
|
|
){ ai ->
|
2018-01-10 16:47:35 +01:00
|
|
|
sent_intent2 = null
|
|
|
|
ActPost.open(this@ActMain, REQUEST_CODE_POST, ai.db_id, intent)
|
|
|
|
}
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
fun closeListItemPopup() {
|
2018-01-10 16:47:35 +01:00
|
|
|
try {
|
|
|
|
listItemPopup?.dismiss()
|
|
|
|
} catch(ignored : Throwable) {
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
2018-01-10 16:47:35 +01:00
|
|
|
listItemPopup = null
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
override fun onClick(v : View) {
|
|
|
|
when(v.id) {
|
2018-01-10 16:47:35 +01:00
|
|
|
R.id.btnMenu -> if(! drawer .isDrawerOpen(Gravity.START)) {
|
|
|
|
drawer .openDrawer(Gravity.START)
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
R.id.btnToot -> Action_Account.openPost(this@ActMain)
|
|
|
|
|
|
|
|
R.id.btnQuickToot -> performQuickPost(null)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun performQuickPost(account : SavedAccount?) {
|
|
|
|
if(account == null) {
|
|
|
|
phoneTab({ env ->
|
|
|
|
// スマホモードなら表示中のカラムがあればそれで
|
|
|
|
val c = app_state.column_list[env.pager.currentItem]
|
|
|
|
// 表示中のカラムは疑似アカウントかもしれない
|
|
|
|
if(! c.access_info.isPseudo) {
|
|
|
|
performQuickPost(c.access_info)
|
|
|
|
} else {
|
|
|
|
// アカウント選択してやり直し
|
2018-01-13 07:15:52 +01:00
|
|
|
AccountPicker.pick(
|
|
|
|
this,
|
|
|
|
bAllowPseudo = false,
|
|
|
|
bAuto = true,
|
|
|
|
message = getString(R.string.account_picker_toot)
|
|
|
|
) { ai -> performQuickPost(ai) }
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
}, { _ ->
|
|
|
|
// アカウント選択してやり直し
|
2018-01-13 07:15:52 +01:00
|
|
|
AccountPicker.pick(
|
|
|
|
this,
|
|
|
|
bAllowPseudo = false,
|
|
|
|
bAuto =true,
|
|
|
|
message =getString(R.string.account_picker_toot)
|
|
|
|
) { ai -> performQuickPost(ai) }
|
2018-01-04 19:52:25 +01:00
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
post_helper.content = etQuickToot.text.toString().trim { it <= ' ' }
|
|
|
|
post_helper.spoiler_text = null
|
|
|
|
post_helper.visibility = account.visibility
|
|
|
|
post_helper.bNSFW = false
|
|
|
|
post_helper.in_reply_to_id = - 1L
|
|
|
|
post_helper.attachment_list = null
|
|
|
|
|
|
|
|
Utils.hideKeyboard(this, etQuickToot)
|
|
|
|
post_helper.post(
|
|
|
|
account
|
|
|
|
, false
|
|
|
|
, false
|
|
|
|
) { target_account, status ->
|
|
|
|
etQuickToot.setText("")
|
|
|
|
posted_acct = target_account.acct
|
|
|
|
posted_status_id = status.id
|
|
|
|
refreshAfterPost()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onPageScrolled(position : Int, positionOffset : Float, positionOffsetPixels : Int) {
|
|
|
|
updateColumnStripSelection(position, positionOffset)
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onPageSelected(position : Int) {
|
|
|
|
handler.post {
|
|
|
|
if(position >= 0 && position < app_state.column_list.size) {
|
|
|
|
val column = app_state.column_list[position]
|
|
|
|
if(! column.bFirstInitialized) {
|
|
|
|
column.startLoading()
|
|
|
|
}
|
|
|
|
scrollColumnStrip(position)
|
|
|
|
post_helper.setInstance(if(column.access_info.isNA) null else column.access_info.host)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onPageScrollStateChanged(state : Int) {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun isOrderChanged(new_order : ArrayList<Int>) : Boolean {
|
|
|
|
if(new_order.size != app_state.column_list.size) return true
|
|
|
|
var i = 0
|
|
|
|
val ie = new_order.size
|
|
|
|
while(i < ie) {
|
|
|
|
if(new_order[i] != i) return true
|
|
|
|
++ i
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onActivityResult(requestCode : Int, resultCode : Int, data : Intent?) {
|
|
|
|
log.d("onActivityResult")
|
|
|
|
if(resultCode == Activity.RESULT_OK) {
|
|
|
|
if(requestCode == REQUEST_CODE_COLUMN_LIST) {
|
|
|
|
if(data != null) {
|
|
|
|
val order = data.getIntegerArrayListExtra(ActColumnList.EXTRA_ORDER)
|
|
|
|
if(order != null && isOrderChanged(order)) {
|
|
|
|
setOrder(order)
|
|
|
|
}
|
|
|
|
|
|
|
|
if(! app_state.column_list.isEmpty()) {
|
|
|
|
val select = data.getIntExtra(ActColumnList.EXTRA_SELECTION, - 1)
|
|
|
|
if(0 <= select && select < app_state.column_list.size) {
|
|
|
|
scrollToColumn(select, false)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if(requestCode == REQUEST_APP_ABOUT) {
|
|
|
|
if(data != null) {
|
|
|
|
val search = data.getStringExtra(ActAbout.EXTRA_SEARCH)
|
2018-01-10 16:47:35 +01:00
|
|
|
if(search?.isNotEmpty() == true) {
|
2018-01-04 19:52:25 +01:00
|
|
|
Action_Account.timeline(this@ActMain, defaultInsertPosition, true, Column.TYPE_SEARCH, search, true)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
} else if(requestCode == REQUEST_CODE_NICKNAME) {
|
|
|
|
|
|
|
|
updateColumnStrip()
|
|
|
|
|
|
|
|
for(column in app_state.column_list) {
|
|
|
|
column.fireShowColumnHeader()
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if(requestCode == REQUEST_CODE_POST) {
|
|
|
|
if(data != null) {
|
|
|
|
etQuickToot.setText("")
|
|
|
|
posted_acct = data.getStringExtra(ActPost.EXTRA_POSTED_ACCT)
|
|
|
|
posted_status_id = data.getLongExtra(ActPost.EXTRA_POSTED_STATUS_ID, 0L)
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if(requestCode == REQUEST_CODE_COLUMN_COLOR) {
|
|
|
|
if(data != null) {
|
|
|
|
app_state.saveColumnList()
|
|
|
|
val idx = data.getIntExtra(ActColumnCustomize.EXTRA_COLUMN_INDEX, 0)
|
|
|
|
if(idx >= 0 && idx < app_state.column_list.size) {
|
|
|
|
app_state.column_list[idx].fireColumnColor()
|
|
|
|
app_state.column_list[idx].fireShowContent()
|
|
|
|
}
|
|
|
|
updateColumnStrip()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(requestCode == REQUEST_CODE_ACCOUNT_SETTING) {
|
|
|
|
updateColumnStrip()
|
|
|
|
|
|
|
|
for(column in app_state.column_list) {
|
|
|
|
column.fireShowColumnHeader()
|
|
|
|
}
|
|
|
|
|
|
|
|
if(resultCode == Activity.RESULT_OK && data != null) {
|
|
|
|
startAccessTokenUpdate(data)
|
|
|
|
} else if(resultCode == ActAccountSetting.RESULT_INPUT_ACCESS_TOKEN && data != null) {
|
|
|
|
val db_id = data.getLongExtra(ActAccountSetting.EXTRA_DB_ID, - 1L)
|
|
|
|
checkAccessToken2(db_id)
|
|
|
|
}
|
|
|
|
} else if(requestCode == REQUEST_CODE_APP_SETTING) {
|
|
|
|
showFooterColor()
|
|
|
|
|
|
|
|
if(resultCode == RESULT_APP_DATA_IMPORT) {
|
|
|
|
if(data != null) {
|
|
|
|
importAppData(data.data)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if(requestCode == REQUEST_CODE_TEXT) {
|
|
|
|
if(resultCode == ActText.RESULT_SEARCH_MSP) {
|
2018-01-10 16:47:35 +01:00
|
|
|
val text = data ?.getStringExtra(Intent.EXTRA_TEXT)
|
|
|
|
addColumn(defaultInsertPosition, SavedAccount.na, Column.TYPE_SEARCH_MSP, text ?:"")
|
2018-01-04 19:52:25 +01:00
|
|
|
} else if(resultCode == ActText.RESULT_SEARCH_TS) {
|
2018-01-10 16:47:35 +01:00
|
|
|
val text = data ?.getStringExtra(Intent.EXTRA_TEXT)
|
|
|
|
addColumn(defaultInsertPosition, SavedAccount.na, Column.TYPE_SEARCH_TS, text?:"")
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
super.onActivityResult(requestCode, resultCode, data)
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onBackPressed() {
|
|
|
|
|
|
|
|
// メニューが開いていたら閉じる
|
|
|
|
val drawer = findViewById<DrawerLayout>(R.id.drawer_layout)
|
|
|
|
if(drawer.isDrawerOpen(GravityCompat.START)) {
|
|
|
|
drawer.closeDrawer(GravityCompat.START)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// カラムが0個ならアプリを終了する
|
|
|
|
if(app_state.column_list.isEmpty()) {
|
|
|
|
this@ActMain.finish()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// カラム設定が開いているならカラム設定を閉じる
|
|
|
|
if(closeColumnSetting()) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// カラムが1個以上ある場合は設定に合わせて挙動を変える
|
|
|
|
when(pref.getInt(Pref.KEY_BACK_BUTTON_ACTION, 0)) {
|
|
|
|
ActAppSetting.BACK_ASK_ALWAYS -> {
|
|
|
|
val dialog = ActionsDialog()
|
|
|
|
|
|
|
|
val add_column_closer = { current_column : Column ->
|
|
|
|
if(! current_column.dont_close) {
|
|
|
|
dialog.addAction(getString(R.string.close_column)) { closeColumn(true, current_column) }
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
phoneTab({ env ->
|
|
|
|
app_state.column_list[env.pager.currentItem].let(add_column_closer)
|
|
|
|
}, { env ->
|
|
|
|
val vs = env.tablet_layout_manager.findFirstVisibleItemPosition()
|
|
|
|
val ve = env.tablet_layout_manager.findLastVisibleItemPosition()
|
|
|
|
if(vs == ve && vs != RecyclerView.NO_POSITION) {
|
|
|
|
app_state.column_list[vs].let(add_column_closer)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
dialog.addAction(getString(R.string.open_column_list)) { Action_App.columnList(this@ActMain) }
|
|
|
|
|
|
|
|
dialog.addAction(getString(R.string.app_exit)) { this@ActMain.finish() }
|
|
|
|
dialog.show(this, null)
|
|
|
|
}
|
|
|
|
|
|
|
|
ActAppSetting.BACK_CLOSE_COLUMN -> {
|
|
|
|
|
|
|
|
val closer = { column : Column ->
|
|
|
|
if(column.dont_close
|
|
|
|
&& pref.getBoolean(Pref.KEY_EXIT_APP_WHEN_CLOSE_PROTECTED_COLUMN, false)
|
|
|
|
&& pref.getBoolean(Pref.KEY_DONT_CONFIRM_BEFORE_CLOSE_COLUMN, false)
|
|
|
|
) {
|
|
|
|
this@ActMain.finish()
|
|
|
|
} else {
|
|
|
|
closeColumn(false, column)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
phoneTab({ env ->
|
|
|
|
env.pager_adapter.getColumn(env.pager.currentItem)?.let(closer)
|
|
|
|
}, { env ->
|
|
|
|
val vs = env.tablet_layout_manager.findFirstVisibleItemPosition()
|
|
|
|
val ve = env.tablet_layout_manager.findLastVisibleItemPosition()
|
|
|
|
if(vs == ve && vs != RecyclerView.NO_POSITION) {
|
|
|
|
app_state.column_list[vs].let(closer)
|
|
|
|
} else {
|
|
|
|
Utils.showToast(this, false, getString(R.string.cant_close_column_by_back_button_when_multiple_column_shown))
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
ActAppSetting.BACK_EXIT_APP -> this@ActMain.finish()
|
|
|
|
|
|
|
|
ActAppSetting.BACK_OPEN_COLUMN_LIST -> Action_App.columnList(this@ActMain)
|
2018-01-10 16:47:35 +01:00
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
else -> {
|
|
|
|
val dialog = ActionsDialog()
|
|
|
|
val add_close_column = { current_column : Column ->
|
|
|
|
if(! current_column.dont_close) {
|
|
|
|
dialog.addAction(getString(R.string.close_column)) { closeColumn(true, current_column) }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
phoneTab({ env ->
|
|
|
|
app_state.column_list[env.pager.currentItem].let(add_close_column)
|
|
|
|
}, { env ->
|
|
|
|
val vs = env.tablet_layout_manager.findFirstVisibleItemPosition()
|
|
|
|
val ve = env.tablet_layout_manager.findLastVisibleItemPosition()
|
|
|
|
if(vs == ve && vs != RecyclerView.NO_POSITION) {
|
|
|
|
app_state.column_list[vs].let(add_close_column)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
dialog.addAction(getString(R.string.open_column_list)) { Action_App.columnList(this@ActMain) }
|
|
|
|
dialog.addAction(getString(R.string.app_exit)) { this@ActMain.finish() }
|
|
|
|
dialog.show(this, null)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onCreateOptionsMenu(menu : Menu) : Boolean {
|
|
|
|
// Inflate the menu; this adds items to the action bar if it is present.
|
|
|
|
menuInflater.inflate(R.menu.act_main, menu)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onOptionsItemSelected(item : MenuItem) : Boolean {
|
|
|
|
// Handle action bar item clicks here. The action bar will
|
|
|
|
// automatically handle clicks on the Home/Up button, so long
|
|
|
|
// as you specify a parent activity in AndroidManifest.xml.
|
|
|
|
val id = item.itemId
|
|
|
|
|
|
|
|
|
|
|
|
return if(id == R.id.action_settings) {
|
|
|
|
true
|
|
|
|
} else super.onOptionsItemSelected(item)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle navigation view item clicks here.
|
|
|
|
override fun onNavigationItemSelected(item : MenuItem) : Boolean {
|
|
|
|
val id = item.itemId
|
|
|
|
|
|
|
|
|
|
|
|
when(id) {
|
|
|
|
// アカウント
|
|
|
|
R.id.nav_account_add -> Action_Account.add(this)
|
|
|
|
R.id.nav_account_setting -> Action_Account.setting(this)
|
|
|
|
|
|
|
|
// カラム
|
|
|
|
R.id.nav_column_list -> Action_App.columnList(this)
|
|
|
|
R.id.nav_add_tl_home -> Action_Account.timeline(this, defaultInsertPosition, false, Column.TYPE_HOME)
|
|
|
|
R.id.nav_add_tl_local -> Action_Account.timeline(this, defaultInsertPosition, true, Column.TYPE_LOCAL)
|
|
|
|
R.id.nav_add_tl_federate -> Action_Account.timeline(this, defaultInsertPosition, true, Column.TYPE_FEDERATE)
|
|
|
|
R.id.nav_add_favourites -> Action_Account.timeline(this, defaultInsertPosition, false, Column.TYPE_FAVOURITES)
|
|
|
|
R.id.nav_add_statuses -> Action_Account.timeline(this, defaultInsertPosition, false, Column.TYPE_PROFILE)
|
|
|
|
R.id.nav_add_notifications -> Action_Account.timeline(this, defaultInsertPosition, false, Column.TYPE_NOTIFICATIONS)
|
|
|
|
R.id.nav_add_tl_search -> Action_Account.timeline(this, defaultInsertPosition, false, Column.TYPE_SEARCH, "", false)
|
|
|
|
R.id.nav_add_mutes -> Action_Account.timeline(this, defaultInsertPosition, false, Column.TYPE_MUTES)
|
|
|
|
R.id.nav_add_blocks -> Action_Account.timeline(this, defaultInsertPosition, false, Column.TYPE_BLOCKS)
|
|
|
|
R.id.nav_add_domain_blocks -> Action_Account.timeline(this, defaultInsertPosition, false, Column.TYPE_DOMAIN_BLOCKS)
|
|
|
|
R.id.nav_add_list -> Action_Account.timeline(this, defaultInsertPosition, false, Column.TYPE_LIST_LIST)
|
|
|
|
R.id.nav_follow_requests -> Action_Account.timeline(this, defaultInsertPosition, false, Column.TYPE_FOLLOW_REQUESTS)
|
|
|
|
|
|
|
|
// トゥート検索
|
|
|
|
R.id.mastodon_search_portal -> addColumn(defaultInsertPosition, SavedAccount.na, Column.TYPE_SEARCH_MSP, "")
|
|
|
|
R.id.tootsearch -> addColumn(defaultInsertPosition, SavedAccount.na, Column.TYPE_SEARCH_TS, "")
|
|
|
|
|
|
|
|
// 設定
|
|
|
|
R.id.nav_app_setting -> ActAppSetting.open(this, REQUEST_CODE_APP_SETTING)
|
|
|
|
R.id.nav_muted_app -> startActivity(Intent(this, ActMutedApp::class.java))
|
|
|
|
R.id.nav_muted_word -> startActivity(Intent(this, ActMutedWord::class.java))
|
|
|
|
R.id.nav_highlight_word -> startActivity(Intent(this, ActHighlightWordList::class.java))
|
|
|
|
R.id.nav_app_about -> startActivityForResult(Intent(this, ActAbout::class.java), ActMain.REQUEST_APP_ABOUT)
|
|
|
|
R.id.nav_oss_license -> startActivity(Intent(this, ActOSSLicense::class.java))
|
|
|
|
R.id.nav_app_exit -> finish()
|
|
|
|
}
|
|
|
|
|
|
|
|
val drawer = findViewById<DrawerLayout>(R.id.drawer_layout)
|
|
|
|
drawer.closeDrawer(GravityCompat.START)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
internal fun initUI() {
|
|
|
|
setContentView(R.layout.act_main)
|
|
|
|
|
|
|
|
dont_crop_media_thumbnail = pref.getBoolean(Pref.KEY_DONT_CROP_MEDIA_THUMBNAIL, false)
|
|
|
|
|
|
|
|
var sv = pref.getString(Pref.KEY_TIMELINE_FONT, null)
|
2018-01-10 16:47:35 +01:00
|
|
|
if(sv?.isNotEmpty() == true) {
|
2018-01-04 19:52:25 +01:00
|
|
|
try {
|
|
|
|
timeline_font = Typeface.createFromFile(sv)
|
|
|
|
} catch(ex : Throwable) {
|
|
|
|
log.trace(ex)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
sv = pref.getString(Pref.KEY_TIMELINE_FONT_BOLD, null)
|
2018-01-10 16:47:35 +01:00
|
|
|
if(sv?.isNotEmpty() == true) {
|
2018-01-04 19:52:25 +01:00
|
|
|
try {
|
|
|
|
timeline_font_bold = Typeface.createFromFile(sv)
|
|
|
|
} catch(ex : Throwable) {
|
|
|
|
log.trace(ex)
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if(timeline_font != null) {
|
|
|
|
try {
|
|
|
|
timeline_font_bold = Typeface.create(timeline_font, Typeface.BOLD)
|
|
|
|
} catch(ex : Throwable) {
|
|
|
|
log.trace(ex)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
shortAcctLocalUser = pref.getBoolean(Pref.KEY_SHORT_ACCT_LOCAL_USER, false)
|
|
|
|
|
|
|
|
run {
|
|
|
|
var icon_size_dp = 48f
|
|
|
|
try {
|
|
|
|
sv = pref.getString(Pref.KEY_AVATAR_ICON_SIZE, null)
|
2018-01-10 16:47:35 +01:00
|
|
|
val fv = if(sv == null || sv.isEmpty()) Float.NaN else sv.toFloat()
|
|
|
|
if(fv.isNaN() || fv.isInfinite() || fv < 1f) {
|
2018-01-04 19:52:25 +01:00
|
|
|
// error or bad range
|
|
|
|
} else {
|
|
|
|
icon_size_dp = fv
|
|
|
|
}
|
|
|
|
} catch(ex : Throwable) {
|
|
|
|
log.trace(ex)
|
|
|
|
}
|
|
|
|
|
|
|
|
avatarIconSize = (0.5f + icon_size_dp * density).toInt()
|
|
|
|
}
|
|
|
|
|
|
|
|
llEmpty = findViewById(R.id.llEmpty)
|
|
|
|
|
|
|
|
// // toolbar
|
|
|
|
// Toolbar toolbar = (Toolbar) findViewById( R.id.toolbar );
|
|
|
|
// setSupportActionBar( toolbar );
|
|
|
|
|
|
|
|
// navigation drawer
|
|
|
|
drawer = findViewById(R.id.drawer_layout)
|
|
|
|
// ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
|
|
|
|
// this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close );
|
2018-01-10 16:47:35 +01:00
|
|
|
drawer .addDrawerListener(this)
|
2018-01-04 19:52:25 +01:00
|
|
|
// toggle.syncState();
|
|
|
|
|
|
|
|
val navigationView = findViewById<NavigationView>(R.id.nav_view)
|
|
|
|
navigationView.setNavigationItemSelectedListener(this)
|
|
|
|
|
|
|
|
btnMenu = findViewById(R.id.btnMenu)
|
|
|
|
btnToot = findViewById(R.id.btnToot)
|
|
|
|
vFooterDivider1 = findViewById(R.id.vFooterDivider1)
|
|
|
|
vFooterDivider2 = findViewById(R.id.vFooterDivider2)
|
|
|
|
llColumnStrip = findViewById(R.id.llColumnStrip)
|
|
|
|
svColumnStrip = findViewById(R.id.svColumnStrip)
|
|
|
|
llQuickTootBar = findViewById(R.id.llQuickTootBar)
|
|
|
|
etQuickToot = findViewById(R.id.etQuickToot)
|
|
|
|
btnQuickToot = findViewById(R.id.btnQuickToot)
|
|
|
|
|
|
|
|
if(! pref.getBoolean(Pref.KEY_QUICK_TOOT_BAR, false)) {
|
|
|
|
llQuickTootBar.visibility = View.GONE
|
|
|
|
}
|
|
|
|
|
|
|
|
btnToot.setOnClickListener(this)
|
|
|
|
btnMenu.setOnClickListener(this)
|
|
|
|
btnQuickToot.setOnClickListener(this)
|
|
|
|
|
|
|
|
if(pref.getBoolean(Pref.KEY_DONT_USE_ACTION_BUTTON, false)) {
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
svColumnStrip.isHorizontalFadingEdgeEnabled = true
|
|
|
|
|
|
|
|
post_helper = PostHelper(this, pref, app_state.handler)
|
|
|
|
|
|
|
|
val dm = resources.displayMetrics
|
|
|
|
|
|
|
|
val density = dm.density
|
|
|
|
|
|
|
|
var media_thumb_height = 64
|
|
|
|
sv = pref.getString(Pref.KEY_MEDIA_THUMB_HEIGHT, "")
|
2018-01-10 16:47:35 +01:00
|
|
|
if(sv?.isNotEmpty() == true) {
|
2018-01-04 19:52:25 +01:00
|
|
|
try {
|
|
|
|
val iv = Integer.parseInt(sv)
|
|
|
|
if(iv >= 32) {
|
|
|
|
media_thumb_height = iv
|
|
|
|
}
|
|
|
|
} catch(ex : Throwable) {
|
|
|
|
log.trace(ex)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
app_state.media_thumb_height = (0.5f + media_thumb_height * density).toInt()
|
|
|
|
|
|
|
|
var column_w_min_dp = COLUMN_WIDTH_MIN_DP
|
|
|
|
sv = pref.getString(Pref.KEY_COLUMN_WIDTH, "")
|
2018-01-10 16:47:35 +01:00
|
|
|
if(sv?.isNotEmpty() == true) {
|
2018-01-04 19:52:25 +01:00
|
|
|
try {
|
|
|
|
val iv = Integer.parseInt(sv)
|
|
|
|
if(iv >= 100) {
|
|
|
|
column_w_min_dp = iv
|
|
|
|
}
|
|
|
|
} catch(ex : Throwable) {
|
|
|
|
log.trace(ex)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
val column_w_min = (0.5f + column_w_min_dp * density).toInt()
|
|
|
|
|
|
|
|
val sw = dm.widthPixels
|
|
|
|
|
|
|
|
if(pref.getBoolean(Pref.KEY_DISABLE_TABLET_MODE, false) || sw < column_w_min * 2) {
|
|
|
|
// SmartPhone mode
|
|
|
|
findViewById<View>(R.id.rvPager).visibility = View.GONE
|
|
|
|
phoneEnv = PhoneEnv()
|
|
|
|
} else {
|
|
|
|
// Tablet mode
|
|
|
|
findViewById<View>(R.id.viewPager).visibility = View.GONE
|
|
|
|
tabletEnv = TabletEnv()
|
|
|
|
}
|
|
|
|
|
|
|
|
phoneTab({ env ->
|
|
|
|
env.pager = findViewById(R.id.viewPager)
|
|
|
|
env.pager_adapter = ColumnPagerAdapter(this)
|
|
|
|
env.pager.adapter = env.pager_adapter
|
|
|
|
env.pager.addOnPageChangeListener(this)
|
|
|
|
|
|
|
|
resizeAutoCW(sw)
|
|
|
|
|
|
|
|
}, { env ->
|
|
|
|
env.tablet_pager = findViewById(R.id.rvPager)
|
|
|
|
env.tablet_pager_adapter = TabletColumnPagerAdapter(this)
|
|
|
|
env.tablet_layout_manager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
|
|
|
|
env.tablet_pager.adapter = env.tablet_pager_adapter
|
|
|
|
env.tablet_pager.layoutManager = env.tablet_layout_manager
|
|
|
|
env.tablet_pager.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
|
|
|
override fun onScrollStateChanged(recyclerView : RecyclerView?, newState : Int) {
|
|
|
|
super.onScrollStateChanged(recyclerView, newState)
|
|
|
|
val vs = env.tablet_layout_manager.findFirstVisibleItemPosition()
|
|
|
|
val ve = env.tablet_layout_manager.findLastVisibleItemPosition()
|
|
|
|
// 端に近い方に合わせる
|
|
|
|
val distance_left = Math.abs(vs)
|
|
|
|
val distance_right = Math.abs(app_state.column_list.size - 1 - ve)
|
|
|
|
if(distance_left < distance_right) {
|
|
|
|
scrollColumnStrip(vs)
|
|
|
|
} else {
|
|
|
|
scrollColumnStrip(ve)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onScrolled(recyclerView : RecyclerView?, dx : Int, dy : Int) {
|
|
|
|
super.onScrolled(recyclerView, dx, dy)
|
|
|
|
updateColumnStripSelection(- 1, - 1f)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
env.tablet_snap_helper = GravitySnapHelper(Gravity.START)
|
|
|
|
env.tablet_snap_helper.attachToRecyclerView(env.tablet_pager)
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
showFooterColor()
|
|
|
|
|
|
|
|
post_helper.attachEditText(findViewById(R.id.llFormRoot), etQuickToot, true, object : PostHelper.Callback2 {
|
|
|
|
override fun onTextUpdate() {}
|
|
|
|
|
|
|
|
override fun canOpenPopup() : Boolean {
|
2018-01-10 16:47:35 +01:00
|
|
|
return ! drawer.isDrawerOpen(Gravity.START)
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
internal fun updateColumnStrip() {
|
|
|
|
llEmpty.visibility = if(app_state.column_list.isEmpty()) View.VISIBLE else View.GONE
|
|
|
|
|
|
|
|
llColumnStrip.removeAllViews()
|
2018-01-10 16:47:35 +01:00
|
|
|
for( i in 0 until app_state.column_list.size){
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
val column = app_state.column_list[i]
|
|
|
|
|
|
|
|
val viewRoot = layoutInflater.inflate(R.layout.lv_column_strip, llColumnStrip, false)
|
|
|
|
val ivIcon = viewRoot.findViewById<ImageView>(R.id.ivIcon)
|
|
|
|
|
|
|
|
viewRoot.tag = i
|
|
|
|
viewRoot.setOnClickListener { v -> scrollToColumn(v.tag as Int, false) }
|
|
|
|
viewRoot.contentDescription = column.getColumnName(true)
|
|
|
|
//
|
|
|
|
|
|
|
|
var c = column.header_bg_color
|
|
|
|
if(c == 0) {
|
|
|
|
viewRoot.setBackgroundResource(R.drawable.btn_bg_ddd)
|
|
|
|
} else {
|
|
|
|
ViewCompat.setBackground(viewRoot, Styler.getAdaptiveRippleDrawable(
|
|
|
|
c,
|
|
|
|
if(column.header_fg_color != 0)
|
|
|
|
column.header_fg_color
|
|
|
|
else
|
|
|
|
Styler.getAttributeColor(this, R.attr.colorRippleEffect)
|
|
|
|
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
|
|
|
c = column.header_fg_color
|
|
|
|
if(c == 0) {
|
|
|
|
Styler.setIconDefaultColor(this, ivIcon, column.getIconAttrId(column.column_type))
|
|
|
|
} else {
|
|
|
|
Styler.setIconCustomColor(this, ivIcon, c, column.getIconAttrId(column.column_type))
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
val ac = AcctColor.load(column.access_info.acct)
|
|
|
|
if(AcctColor.hasColorForeground(ac)) {
|
|
|
|
val vAcctColor = viewRoot.findViewById<View>(R.id.vAcctColor)
|
|
|
|
vAcctColor.setBackgroundColor(ac.color_fg)
|
|
|
|
}
|
|
|
|
//
|
|
|
|
llColumnStrip.addView(viewRoot)
|
|
|
|
}
|
|
|
|
svColumnStrip.requestLayout()
|
|
|
|
updateColumnStripSelection(- 1, - 1f)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun updateColumnStripSelection(position : Int, positionOffset : Float) {
|
|
|
|
handler.post(Runnable {
|
|
|
|
if(isFinishing) return@Runnable
|
|
|
|
|
|
|
|
if(app_state.column_list.isEmpty()) {
|
2018-01-13 16:24:51 +01:00
|
|
|
llColumnStrip.setVisibleRange(- 1, - 1, 0f)
|
2018-01-04 19:52:25 +01:00
|
|
|
} else {
|
|
|
|
phoneTab({ env ->
|
|
|
|
if(position >= 0) {
|
2018-01-13 16:24:51 +01:00
|
|
|
llColumnStrip.setVisibleRange(position, position, positionOffset)
|
2018-01-04 19:52:25 +01:00
|
|
|
} else {
|
|
|
|
val c = env.pager.currentItem
|
2018-01-13 16:24:51 +01:00
|
|
|
llColumnStrip.setVisibleRange(c, c, 0f)
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
}, { env ->
|
|
|
|
val first = env.tablet_layout_manager.findFirstVisibleItemPosition()
|
|
|
|
var last = env.tablet_layout_manager.findLastVisibleItemPosition()
|
|
|
|
|
|
|
|
if(last - first > nScreenColumn - 1) {
|
|
|
|
last = first + nScreenColumn - 1
|
|
|
|
}
|
|
|
|
var slide_ratio = 0f
|
|
|
|
if(first != RecyclerView.NO_POSITION && nColumnWidth > 0) {
|
|
|
|
val child = env.tablet_layout_manager.findViewByPosition(first)
|
|
|
|
slide_ratio = Math.abs(child.left / nColumnWidth.toFloat())
|
|
|
|
}
|
|
|
|
|
2018-01-13 16:24:51 +01:00
|
|
|
llColumnStrip.setVisibleRange(first, last, slide_ratio)
|
2018-01-04 19:52:25 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun scrollColumnStrip(select : Int) {
|
|
|
|
val child_count = llColumnStrip.childCount
|
|
|
|
if(select < 0 || select >= child_count) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
val icon = llColumnStrip.getChildAt(select)
|
|
|
|
|
|
|
|
val sv_width = (llColumnStrip.parent as View).width
|
|
|
|
val ll_width = llColumnStrip.width
|
|
|
|
val icon_width = icon.width
|
|
|
|
val icon_left = icon.left
|
|
|
|
|
|
|
|
if(sv_width == 0 || ll_width == 0 || icon_width == 0) {
|
|
|
|
handler.postDelayed({ scrollColumnStrip(select) }, 20L)
|
|
|
|
}
|
|
|
|
|
|
|
|
val sx = icon_left + icon_width / 2 - sv_width / 2
|
|
|
|
svColumnStrip.smoothScrollTo(sx, 0)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
fun startAccessTokenUpdate(data : Intent) {
|
|
|
|
val uri = data.data ?: return
|
2018-01-10 16:47:35 +01:00
|
|
|
// ブラウザで開く
|
2018-01-04 19:52:25 +01:00
|
|
|
try {
|
|
|
|
val intent = Intent(Intent.ACTION_VIEW, uri)
|
|
|
|
startActivity(intent)
|
|
|
|
} catch(ex : Throwable) {
|
|
|
|
log.trace(ex)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// ActOAuthCallbackで受け取ったUriを処理する
|
|
|
|
private fun handleIntentUri(uri : Uri) {
|
|
|
|
|
|
|
|
if("subwaytooter" == uri.scheme) {
|
|
|
|
try {
|
|
|
|
handleOAuth2CallbackUri(uri)
|
|
|
|
} catch(ex : Throwable) {
|
|
|
|
log.trace(ex)
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
val url = uri.toString()
|
|
|
|
|
|
|
|
var m = reStatusPage.matcher(url)
|
|
|
|
if(m.find()) {
|
|
|
|
try {
|
|
|
|
// https://mastodon.juggler.jp/@SubwayTooter/(status_id)
|
|
|
|
val host = m.group(1)
|
|
|
|
val status_id = m.group(3).toLong(10)
|
|
|
|
// ステータスをアプリ内で開く
|
|
|
|
Action_Toot.conversationOtherInstance(this@ActMain, defaultInsertPosition, uri.toString(), status_id, host, status_id)
|
|
|
|
} catch(ex : Throwable) {
|
|
|
|
Utils.showToast(this, ex, "can't parse status id.")
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
m = reUserPage.matcher(url)
|
|
|
|
if(m.find()) {
|
|
|
|
// https://mastodon.juggler.jp/@SubwayTooter
|
|
|
|
// ユーザページをアプリ内で開く
|
|
|
|
Action_User.profile(
|
|
|
|
this@ActMain, defaultInsertPosition, null, uri.toString(), m.group(1), Uri.decode(m.group(2))
|
|
|
|
)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// このアプリでは処理できないURLだった
|
|
|
|
// 外部ブラウザを開きなおそうとすると無限ループの恐れがある
|
|
|
|
// アプリケーションチューザーを表示する
|
|
|
|
|
|
|
|
val error_message = getString(R.string.cant_handle_uri_of, url)
|
|
|
|
|
|
|
|
try {
|
|
|
|
val query_flag = if(Build.VERSION.SDK_INT >= 23) {
|
|
|
|
// Android 6.0以降
|
|
|
|
// MATCH_DEFAULT_ONLY だと標準の設定に指定されたアプリがあるとソレしか出てこない
|
|
|
|
// MATCH_ALL を指定すると 以前と同じ挙動になる
|
|
|
|
PackageManager.MATCH_ALL
|
|
|
|
} else {
|
|
|
|
// Android 5.xまでは MATCH_DEFAULT_ONLY でマッチするすべてのアプリを取得できる
|
|
|
|
PackageManager.MATCH_DEFAULT_ONLY
|
|
|
|
}
|
|
|
|
|
|
|
|
// queryIntentActivities に渡すURLは実在しないホストのものにする
|
|
|
|
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://dummy.subwaytooter.club/"))
|
|
|
|
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
|
|
|
val resolveInfoList = packageManager.queryIntentActivities(intent, query_flag)
|
|
|
|
if(resolveInfoList.isEmpty()) {
|
|
|
|
throw RuntimeException("resolveInfoList is empty.")
|
|
|
|
}
|
|
|
|
|
|
|
|
// このアプリ以外の選択肢を集める
|
|
|
|
val my_name = packageName
|
|
|
|
val choice_list = ArrayList<Intent>()
|
|
|
|
for(ri in resolveInfoList) {
|
|
|
|
|
|
|
|
// 選択肢からこのアプリを除外
|
|
|
|
if(my_name == ri.activityInfo.packageName) continue
|
|
|
|
|
|
|
|
// 選択肢のIntentは目的のUriで作成する
|
|
|
|
val choice = Intent(Intent.ACTION_VIEW, uri)
|
|
|
|
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
|
|
|
choice.`package` = ri.activityInfo.packageName
|
|
|
|
choice.setClassName(ri.activityInfo.packageName, ri.activityInfo.name)
|
|
|
|
choice_list.add(choice)
|
|
|
|
}
|
|
|
|
|
|
|
|
if(choice_list.isEmpty()) {
|
|
|
|
throw RuntimeException("choice_list is empty.")
|
|
|
|
}
|
|
|
|
// 指定した選択肢でチューザーを作成して開く
|
|
|
|
val chooser = Intent.createChooser(choice_list.removeAt(0), error_message)
|
|
|
|
chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, choice_list.toTypedArray())
|
|
|
|
startActivity(chooser)
|
|
|
|
return
|
|
|
|
} catch(ex : Throwable) {
|
|
|
|
log.trace(ex)
|
|
|
|
}
|
|
|
|
|
|
|
|
AlertDialog.Builder(this)
|
|
|
|
.setCancelable(true)
|
|
|
|
.setMessage(error_message)
|
|
|
|
.setPositiveButton(R.string.close, null)
|
|
|
|
.show()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun handleOAuth2CallbackUri(uri : Uri) {
|
|
|
|
|
|
|
|
// 通知タップ
|
|
|
|
// subwaytooter://notification_click/?db_id=(db_id)
|
|
|
|
val dataIdString = uri.getQueryParameter("db_id")
|
2018-01-10 16:47:35 +01:00
|
|
|
if(dataIdString != null) {
|
2018-01-04 19:52:25 +01:00
|
|
|
try {
|
2018-01-10 16:47:35 +01:00
|
|
|
val dataId = dataIdString.toLong(10)
|
2018-01-04 19:52:25 +01:00
|
|
|
val account = SavedAccount.loadAccount(this@ActMain, dataId)
|
|
|
|
if(account != null) {
|
|
|
|
val column = addColumn(defaultInsertPosition, account, Column.TYPE_NOTIFICATIONS)
|
|
|
|
// 通知を読み直す
|
|
|
|
if(! column.bInitialLoading) {
|
|
|
|
column.startLoading()
|
|
|
|
}
|
|
|
|
|
|
|
|
PollingWorker.queueNotificationClicked(this, dataId)
|
|
|
|
|
|
|
|
}
|
|
|
|
} catch(ex : Throwable) {
|
|
|
|
log.trace(ex)
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// OAuth2 認証コールバック
|
|
|
|
// subwaytooter://oauth/?...
|
2018-01-10 16:47:35 +01:00
|
|
|
TootTaskRunner(this@ActMain).run(object : TootTask {
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
internal var ta : TootAccount? = null
|
|
|
|
internal var sa : SavedAccount? = null
|
|
|
|
internal var host : String? = null
|
|
|
|
|
|
|
|
override fun background(client : TootApiClient) : TootApiResult? {
|
|
|
|
|
|
|
|
// エラー時
|
|
|
|
// subwaytooter://oauth
|
|
|
|
// ?error=access_denied
|
|
|
|
// &error_description=%E3%83%AA%E3%82%BD%E3%83%BC%E3%82%B9%E3%81%AE%E6%89%80%E6%9C%89%E8%80%85%E3%81%BE%E3%81%9F%E3%81%AF%E8%AA%8D%E8%A8%BC%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E3%81%8C%E8%A6%81%E6%B1%82%E3%82%92%E6%8B%92%E5%90%A6%E3%81%97%E3%81%BE%E3%81%97%E3%81%9F%E3%80%82
|
|
|
|
// &state=db%3A3
|
|
|
|
val error = uri.getQueryParameter("error_description")
|
2018-01-10 16:47:35 +01:00
|
|
|
if(error?.isNotEmpty() == true) {
|
2018-01-04 19:52:25 +01:00
|
|
|
return TootApiResult(error)
|
|
|
|
}
|
|
|
|
|
|
|
|
// subwaytooter://oauth
|
|
|
|
// ?code=113cc036e078ac500d3d0d3ad345cd8181456ab087abc67270d40f40a4e9e3c2
|
|
|
|
// &state=host%3Amastodon.juggler.jp
|
|
|
|
|
|
|
|
val code = uri.getQueryParameter("code")
|
2018-01-10 16:47:35 +01:00
|
|
|
if(code?.isEmpty() != false) {
|
2018-01-04 19:52:25 +01:00
|
|
|
return TootApiResult("missing code in callback url.")
|
|
|
|
}
|
|
|
|
|
|
|
|
val sv = uri.getQueryParameter("state")
|
2018-01-10 16:47:35 +01:00
|
|
|
if(sv?.isEmpty() != false) {
|
2018-01-04 19:52:25 +01:00
|
|
|
return TootApiResult("missing state in callback url.")
|
|
|
|
}
|
|
|
|
|
|
|
|
if(sv.startsWith("db:")) {
|
|
|
|
try {
|
|
|
|
val dataId = sv.substring(3).toLong(10)
|
|
|
|
val sa = SavedAccount.loadAccount(this@ActMain, dataId)
|
|
|
|
?: return TootApiResult("missing account db_id=" + dataId)
|
|
|
|
this.sa = sa
|
2018-01-12 10:01:25 +01:00
|
|
|
client.account = sa
|
2018-01-04 19:52:25 +01:00
|
|
|
} catch(ex : Throwable) {
|
|
|
|
log.trace(ex)
|
|
|
|
return TootApiResult(Utils.formatError(ex, "invalid state"))
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if(sv.startsWith("host:")) {
|
|
|
|
val host = sv.substring(5)
|
2018-01-12 10:01:25 +01:00
|
|
|
client.instance =host
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if(client.instance == null) {
|
|
|
|
return TootApiResult("missing instance in callback url.")
|
|
|
|
}
|
|
|
|
|
|
|
|
this.host = client.instance
|
|
|
|
val client_name = Pref.pref(this@ActMain).getString(Pref.KEY_CLIENT_NAME, "")
|
|
|
|
|
2018-01-13 07:15:52 +01:00
|
|
|
val result = client.authentication2(client_name, code)
|
2018-01-04 19:52:25 +01:00
|
|
|
val obj = result?.jsonObject
|
|
|
|
if(obj != null) {
|
|
|
|
// ダミーのLinkClickContext
|
2018-01-10 16:47:35 +01:00
|
|
|
val lcc = object : LinkClickContext {}
|
2018-01-04 19:52:25 +01:00
|
|
|
this.ta = TootAccount.parse(this@ActMain, lcc, obj)
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun handleResult(result : TootApiResult?) {
|
|
|
|
afterAccountVerify(result, ta, sa, host)
|
|
|
|
}
|
|
|
|
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
internal fun afterAccountVerify(result : TootApiResult?, ta : TootAccount?, sa : SavedAccount?, host : String?) : Boolean {
|
|
|
|
|
|
|
|
val jsonObject = result?.jsonObject
|
2018-01-13 07:15:52 +01:00
|
|
|
val token_info = result?.tokenInfo
|
2018-01-04 19:52:25 +01:00
|
|
|
val error = result?.error
|
|
|
|
|
|
|
|
if(result == null) {
|
|
|
|
// cancelled.
|
|
|
|
|
|
|
|
} else if(error != null) {
|
|
|
|
Utils.showToast(this@ActMain, true, result.error)
|
|
|
|
|
|
|
|
} else if(token_info == null) {
|
|
|
|
Utils.showToast(this@ActMain, true, "can't get access token.")
|
|
|
|
|
|
|
|
} else if(jsonObject == null) {
|
|
|
|
Utils.showToast(this@ActMain, true, "can't parse json response.")
|
|
|
|
|
|
|
|
} else if(ta == null) {
|
|
|
|
// 自分のユーザネームを取れなかった
|
|
|
|
// …普通はエラーメッセージが設定されてるはずだが
|
|
|
|
Utils.showToast(this@ActMain, true, "can't verify user credential.")
|
|
|
|
|
|
|
|
} else if(sa != null) {
|
|
|
|
// アクセストークン更新時
|
|
|
|
|
|
|
|
// インスタンスは同じだと思うが、ユーザ名が異なる可能性がある
|
|
|
|
if(sa.username != ta.username) {
|
|
|
|
Utils.showToast(this@ActMain, true, R.string.user_name_not_match)
|
|
|
|
} else {
|
|
|
|
Utils.showToast(this@ActMain, false, R.string.access_token_updated_for, sa.acct)
|
|
|
|
|
|
|
|
// DBの情報を更新する
|
|
|
|
sa.updateTokenInfo(token_info)
|
|
|
|
|
|
|
|
// 各カラムの持つアカウント情報をリロードする
|
|
|
|
reloadAccountSetting()
|
|
|
|
|
|
|
|
// 自動でリロードする
|
|
|
|
for(it in app_state.column_list) {
|
|
|
|
if(it.access_info.acct == sa.acct) {
|
|
|
|
it.startLoading()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 通知の更新が必要かもしれない
|
|
|
|
PollingWorker.queueUpdateNotification(this@ActMain)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
} else if(host != null) {
|
|
|
|
// アカウント追加時
|
|
|
|
val user = ta.username + "@" + host
|
|
|
|
val row_id = SavedAccount.insert(host, user, jsonObject, token_info)
|
|
|
|
val account = SavedAccount.loadAccount(this@ActMain, row_id)
|
|
|
|
if(account != null) {
|
|
|
|
var bModified = false
|
2018-01-10 16:47:35 +01:00
|
|
|
if(account.loginAccount?.locked == true) {
|
2018-01-04 19:52:25 +01:00
|
|
|
bModified = true
|
|
|
|
account.visibility = TootStatus.VISIBILITY_PRIVATE
|
|
|
|
}
|
|
|
|
val source = ta.source
|
|
|
|
if(source != null) {
|
|
|
|
try {
|
|
|
|
val privacy = source.privacy
|
|
|
|
TootStatus.parseVisibility(privacy) // 失敗すると例外
|
|
|
|
bModified = true
|
|
|
|
account.visibility = privacy
|
2018-01-10 16:47:35 +01:00
|
|
|
} catch(ignored : Throwable) {
|
2018-01-04 19:52:25 +01:00
|
|
|
// privacyの値がおかしい
|
|
|
|
}
|
2018-01-10 16:47:35 +01:00
|
|
|
|
|
|
|
// XXX ta.source.sensitive パラメータを読んで「添付画像をデフォルトでNSFWにする」を実現する
|
2018-01-04 19:52:25 +01:00
|
|
|
// 現在、アカウント設定にはこの項目はない( 「NSFWな添付メディアを隠さない」はあるが全く別の効果)
|
|
|
|
}
|
|
|
|
|
|
|
|
if(bModified) {
|
|
|
|
account.saveSetting()
|
|
|
|
}
|
|
|
|
Utils.showToast(this@ActMain, false, R.string.account_confirmed)
|
|
|
|
|
|
|
|
// 通知の更新が必要かもしれない
|
|
|
|
PollingWorker.queueUpdateNotification(this@ActMain)
|
|
|
|
|
|
|
|
// 適当にカラムを追加する
|
|
|
|
val count = SavedAccount.count
|
|
|
|
if(count > 1) {
|
|
|
|
addColumn(defaultInsertPosition, account, Column.TYPE_HOME)
|
|
|
|
} else {
|
|
|
|
addColumn(defaultInsertPosition, account, Column.TYPE_HOME)
|
|
|
|
addColumn(defaultInsertPosition, account, Column.TYPE_NOTIFICATIONS)
|
|
|
|
addColumn(defaultInsertPosition, account, Column.TYPE_LOCAL)
|
|
|
|
addColumn(defaultInsertPosition, account, Column.TYPE_FEDERATE)
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// アクセストークンを手動で入力した場合
|
|
|
|
fun checkAccessToken(
|
|
|
|
dialog_host : Dialog?, dialog_token : Dialog?, host : String, access_token : String, sa : SavedAccount?
|
|
|
|
) {
|
|
|
|
|
2018-01-10 16:47:35 +01:00
|
|
|
TootTaskRunner(this@ActMain).run(host, object : TootTask {
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
internal var ta : TootAccount? = null
|
|
|
|
|
|
|
|
override fun background(client : TootApiClient) : TootApiResult? {
|
2018-01-13 07:15:52 +01:00
|
|
|
val result = client.getUserCredential(access_token)
|
2018-01-04 19:52:25 +01:00
|
|
|
val obj = result?.jsonObject
|
|
|
|
if(obj != null) {
|
|
|
|
// taは使い捨てなので、生成に使うLinkClickContextはダミーで問題ない
|
2018-01-10 16:47:35 +01:00
|
|
|
val lcc = object : LinkClickContext {}
|
2018-01-04 19:52:25 +01:00
|
|
|
this.ta = TootAccount.parse(this@ActMain, lcc, obj)
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun handleResult(result : TootApiResult?) {
|
|
|
|
|
|
|
|
if(afterAccountVerify(result, ta, sa, host)) {
|
|
|
|
try {
|
|
|
|
dialog_host?.dismiss()
|
|
|
|
} catch(ignored : Throwable) {
|
|
|
|
// IllegalArgumentException がたまに出る
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
dialog_token?.dismiss()
|
|
|
|
} catch(ignored : Throwable) {
|
|
|
|
// IllegalArgumentException がたまに出る
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// アクセストークンの手動入力(更新)
|
|
|
|
private fun checkAccessToken2(db_id : Long) {
|
|
|
|
|
|
|
|
val sa = SavedAccount.loadAccount(this, db_id) ?: return
|
|
|
|
|
|
|
|
DlgTextInput.show(this, getString(R.string.access_token), null, object : DlgTextInput.Callback {
|
|
|
|
override fun onOK(dialog : Dialog, text : String) {
|
|
|
|
checkAccessToken(null, dialog, sa.host, text, sa)
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onEmptyError() {
|
|
|
|
Utils.showToast(this@ActMain, true, R.string.token_not_specified)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun reloadAccountSetting() {
|
|
|
|
val done_list = ArrayList<SavedAccount>()
|
|
|
|
for(column in app_state.column_list) {
|
|
|
|
val a = column.access_info
|
|
|
|
if(done_list.contains(a)) continue
|
|
|
|
done_list.add(a)
|
|
|
|
if(! a.isNA) a.reloadSetting(this@ActMain)
|
|
|
|
column.fireShowColumnHeader()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fun reloadAccountSetting(account : SavedAccount) {
|
|
|
|
val done_list = ArrayList<SavedAccount>()
|
|
|
|
for(column in app_state.column_list) {
|
|
|
|
val a = column.access_info
|
2018-01-10 16:47:35 +01:00
|
|
|
if(a.acct != account.acct) continue
|
2018-01-04 19:52:25 +01:00
|
|
|
if(done_list.contains(a)) continue
|
|
|
|
done_list.add(a)
|
|
|
|
if(! a.isNA) a.reloadSetting(this@ActMain)
|
|
|
|
column.fireShowColumnHeader()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-10 16:47:35 +01:00
|
|
|
fun closeColumn(bConfirm : Boolean, column : Column) {
|
|
|
|
|
|
|
|
if(column.dont_close) {
|
2018-01-04 19:52:25 +01:00
|
|
|
Utils.showToast(this, false, R.string.column_has_dont_close_option)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if(! bConfirm && ! pref.getBoolean(Pref.KEY_DONT_CONFIRM_BEFORE_CLOSE_COLUMN, false)) {
|
|
|
|
AlertDialog.Builder(this)
|
|
|
|
.setMessage(R.string.confirm_close_column)
|
|
|
|
.setNegativeButton(R.string.cancel, null)
|
|
|
|
.setPositiveButton(R.string.ok) { _, _ -> closeColumn(true, column) }
|
|
|
|
.show()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
val page_delete = app_state.column_list.indexOf(column)
|
|
|
|
|
|
|
|
phoneTab({ env ->
|
|
|
|
val page_showing = env.pager.currentItem
|
|
|
|
|
|
|
|
removeColumn(column)
|
|
|
|
|
|
|
|
if(! app_state.column_list.isEmpty() && page_delete > 0 && page_showing == page_delete) {
|
|
|
|
val idx = page_delete - 1
|
|
|
|
scrollToColumn(idx, false)
|
|
|
|
val c = app_state.column_list[idx]
|
|
|
|
if(! c.bFirstInitialized) {
|
|
|
|
c.startLoading()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}, { _ ->
|
|
|
|
removeColumn(column)
|
|
|
|
|
|
|
|
if(! app_state.column_list.isEmpty() && page_delete > 0) {
|
|
|
|
val idx = page_delete - 1
|
|
|
|
scrollToColumn(idx, false)
|
|
|
|
val c = app_state.column_list[idx]
|
|
|
|
if(! c.bFirstInitialized) {
|
|
|
|
c.startLoading()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////
|
|
|
|
// カラム追加系
|
|
|
|
|
|
|
|
fun addColumn(indexArg : Int, ai : SavedAccount, type : Int, vararg params : Any) : Column {
|
|
|
|
var index = indexArg
|
|
|
|
// 既に同じカラムがあればそこに移動する
|
|
|
|
for(column in app_state.column_list) {
|
|
|
|
if(column.isSameSpec(ai, type, params)) {
|
|
|
|
index = app_state.column_list.indexOf(column)
|
|
|
|
scrollToColumn(index, false)
|
|
|
|
return column
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
val col = Column(app_state, ai, this, type, *params)
|
|
|
|
index = addColumn(col, index)
|
|
|
|
scrollToColumn(index, false)
|
|
|
|
if(! col.bFirstInitialized) {
|
|
|
|
col.startLoading()
|
|
|
|
}
|
|
|
|
return col
|
|
|
|
}
|
|
|
|
|
|
|
|
fun openChromeTab(opener : ChromeTabOpener) {
|
|
|
|
|
|
|
|
try {
|
|
|
|
log.d("openChromeTab url=%s", opener.url)
|
|
|
|
|
|
|
|
val accessInto = opener.accessInfo
|
|
|
|
if(opener.allowIntercept && accessInto != null) {
|
|
|
|
|
|
|
|
// ハッシュタグはいきなり開くのではなくメニューがある
|
|
|
|
var m = reUrlHashTag.matcher(opener.url)
|
|
|
|
if(m.find()) {
|
|
|
|
// https://mastodon.juggler.jp/tags/%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E3%82%BF%E3%82%B0
|
|
|
|
val host = m.group(1)
|
|
|
|
val tag_without_sharp = Uri.decode(m.group(2))
|
|
|
|
Action_HashTag.dialog(
|
|
|
|
this@ActMain, opener.pos, opener.url, host, tag_without_sharp, opener.tagList
|
|
|
|
)
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// ステータスページをアプリから開く
|
|
|
|
m = reStatusPage.matcher(opener.url)
|
|
|
|
if(m.find()) {
|
|
|
|
try {
|
|
|
|
// https://mastodon.juggler.jp/@SubwayTooter/(status_id)
|
|
|
|
val host = m.group(1)
|
2018-01-10 16:47:35 +01:00
|
|
|
val status_id = m.group(3).toLong(10)
|
2018-01-04 19:52:25 +01:00
|
|
|
if(accessInto.isNA || ! host.equals(accessInto.host, ignoreCase = true)) {
|
|
|
|
Action_Toot.conversationOtherInstance(this@ActMain, opener.pos, opener.url, status_id, host, status_id)
|
|
|
|
} else {
|
|
|
|
Action_Toot.conversationLocal(this@ActMain, opener.pos, accessInto, status_id)
|
|
|
|
}
|
|
|
|
} catch(ex : Throwable) {
|
|
|
|
Utils.showToast(this, ex, "can't parse status id.")
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// https://mastodon.juggler.jp/@SubwayTooter
|
|
|
|
m = reUserPage.matcher(opener.url)
|
|
|
|
if(m.find()) {
|
|
|
|
// ユーザページをアプリ内で開く
|
|
|
|
Action_User.profile(
|
|
|
|
this@ActMain, opener.pos, accessInto, opener.url, m.group(1), Uri.decode(m.group(2))
|
|
|
|
)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
App1.openCustomTab(this, opener.url)
|
|
|
|
|
|
|
|
} catch(ex : Throwable) {
|
|
|
|
// log.trace( ex );
|
|
|
|
log.e(ex, "openChromeTab failed. url=%s", opener.url)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
fun showColumnMatchAccount(account : SavedAccount) {
|
|
|
|
for(column in app_state.column_list) {
|
|
|
|
if(account.acct == column.access_info.acct) {
|
|
|
|
column.fireShowContent()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun showFooterColor() {
|
|
|
|
val footer_button_bg_color = pref.getInt(Pref.KEY_FOOTER_BUTTON_BG_COLOR, 0)
|
|
|
|
val footer_button_fg_color = pref.getInt(Pref.KEY_FOOTER_BUTTON_FG_COLOR, 0)
|
|
|
|
val footer_tab_bg_color = pref.getInt(Pref.KEY_FOOTER_TAB_BG_COLOR, 0)
|
|
|
|
val footer_tab_divider_color = pref.getInt(Pref.KEY_FOOTER_TAB_DIVIDER_COLOR, 0)
|
|
|
|
val footer_tab_indicator_color = pref.getInt(Pref.KEY_FOOTER_TAB_INDICATOR_COLOR, 0)
|
|
|
|
var c = footer_button_bg_color
|
|
|
|
if(c == 0) {
|
|
|
|
btnMenu.setBackgroundResource(R.drawable.btn_bg_ddd)
|
|
|
|
btnToot.setBackgroundResource(R.drawable.btn_bg_ddd)
|
|
|
|
btnQuickToot.setBackgroundResource(R.drawable.btn_bg_ddd)
|
|
|
|
} else {
|
|
|
|
val fg = if(footer_button_fg_color != 0)
|
|
|
|
footer_button_fg_color
|
|
|
|
else
|
|
|
|
Styler.getAttributeColor(this, R.attr.colorRippleEffect)
|
|
|
|
ViewCompat.setBackground(btnToot, Styler.getAdaptiveRippleDrawable(c, fg))
|
|
|
|
ViewCompat.setBackground(btnMenu, Styler.getAdaptiveRippleDrawable(c, fg))
|
|
|
|
ViewCompat.setBackground(btnQuickToot, Styler.getAdaptiveRippleDrawable(c, fg))
|
|
|
|
}
|
|
|
|
|
|
|
|
c = footer_button_fg_color
|
|
|
|
if(c == 0) {
|
|
|
|
Styler.setIconDefaultColor(this, btnToot, R.attr.ic_edit)
|
|
|
|
Styler.setIconDefaultColor(this, btnMenu, R.attr.ic_hamburger)
|
|
|
|
Styler.setIconDefaultColor(this, btnQuickToot, R.attr.btn_post)
|
|
|
|
} else {
|
|
|
|
Styler.setIconCustomColor(this, btnToot, c, R.attr.ic_edit)
|
|
|
|
Styler.setIconCustomColor(this, btnMenu, c, R.attr.ic_hamburger)
|
|
|
|
Styler.setIconCustomColor(this, btnQuickToot, c, R.attr.btn_post)
|
|
|
|
}
|
|
|
|
|
|
|
|
c = footer_tab_bg_color
|
|
|
|
if(c == 0) {
|
|
|
|
svColumnStrip.setBackgroundColor(Styler.getAttributeColor(this, R.attr.colorColumnStripBackground))
|
|
|
|
llQuickTootBar.setBackgroundColor(Styler.getAttributeColor(this, R.attr.colorColumnStripBackground))
|
|
|
|
} else {
|
|
|
|
svColumnStrip.setBackgroundColor(c)
|
|
|
|
svColumnStrip.setBackgroundColor(Styler.getAttributeColor(this, R.attr.colorColumnStripBackground))
|
|
|
|
}
|
|
|
|
|
|
|
|
c = footer_tab_divider_color
|
|
|
|
if(c == 0) {
|
|
|
|
vFooterDivider1.setBackgroundColor(Styler.getAttributeColor(this, R.attr.colorImageButton))
|
|
|
|
vFooterDivider2.setBackgroundColor(Styler.getAttributeColor(this, R.attr.colorImageButton))
|
|
|
|
} else {
|
|
|
|
vFooterDivider1.setBackgroundColor(c)
|
|
|
|
vFooterDivider2.setBackgroundColor(c)
|
|
|
|
}
|
|
|
|
|
2018-01-13 16:24:51 +01:00
|
|
|
llColumnStrip.indicatorColor = footer_tab_indicator_color
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////
|
|
|
|
// タブレット対応で必要になった関数など
|
|
|
|
|
|
|
|
private fun closeColumnSetting() : Boolean {
|
|
|
|
phoneTab({ env ->
|
|
|
|
val vh = env.pager_adapter.getColumnViewHolder(env.pager.currentItem)
|
|
|
|
if(vh.isColumnSettingShown) {
|
|
|
|
vh.closeColumnSetting()
|
|
|
|
return@closeColumnSetting true
|
|
|
|
}
|
|
|
|
}, { env ->
|
2018-01-10 16:47:35 +01:00
|
|
|
for(i in 0 until env.tablet_layout_manager.childCount) {
|
2018-01-04 19:52:25 +01:00
|
|
|
val v = env.tablet_layout_manager.getChildAt(i)
|
|
|
|
val columnViewHolder = (env.tablet_pager.getChildViewHolder(v) as? TabletColumnViewHolder)?.columnViewHolder
|
2018-01-10 16:47:35 +01:00
|
|
|
if(columnViewHolder?.isColumnSettingShown == true) {
|
2018-01-04 19:52:25 +01:00
|
|
|
columnViewHolder.closeColumnSetting()
|
|
|
|
return@closeColumnSetting true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
fun nextPosition(column : Column?) : Int {
|
|
|
|
if(column != null) {
|
|
|
|
val pos = app_state.column_list.indexOf(column)
|
|
|
|
if(pos != - 1) return pos + 1
|
|
|
|
}
|
|
|
|
return defaultInsertPosition
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun addColumn(column : Column, indexArg : Int) : Int {
|
|
|
|
var index = indexArg
|
|
|
|
val size = app_state.column_list.size
|
|
|
|
if(index > size) index = size
|
|
|
|
|
2018-01-10 16:47:35 +01:00
|
|
|
phoneOnly { env-> env.pager.adapter = null}
|
|
|
|
|
|
|
|
app_state.column_list.add(index, column)
|
|
|
|
|
|
|
|
phoneTab(
|
|
|
|
{ env -> env.pager.adapter = env.pager_adapter },
|
|
|
|
{ env -> resizeColumnWidth(env) }
|
|
|
|
)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
app_state.saveColumnList()
|
|
|
|
updateColumnStrip()
|
|
|
|
|
|
|
|
return index
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun removeColumn(column : Column) {
|
|
|
|
val idx_column = app_state.column_list.indexOf(column)
|
|
|
|
if(idx_column == - 1) return
|
|
|
|
|
2018-01-10 16:47:35 +01:00
|
|
|
phoneOnly{env->env.pager.adapter = null}
|
|
|
|
|
|
|
|
app_state.column_list.removeAt(idx_column).dispose()
|
|
|
|
|
|
|
|
phoneTab(
|
|
|
|
{ env -> env.pager.adapter = env.pager_adapter},
|
|
|
|
{ env -> resizeColumnWidth(env) }
|
|
|
|
)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
app_state.saveColumnList()
|
|
|
|
updateColumnStrip()
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun setOrder(new_order : ArrayList<Int>) {
|
2018-01-10 16:47:35 +01:00
|
|
|
|
|
|
|
phoneOnly { env -> env.pager.adapter = null }
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
val tmp_list = ArrayList<Column>()
|
|
|
|
val used_set = HashSet<Int>()
|
|
|
|
|
|
|
|
for(i in new_order) {
|
|
|
|
used_set.add(i)
|
|
|
|
tmp_list.add(app_state.column_list[i])
|
|
|
|
}
|
|
|
|
|
|
|
|
var i = 0
|
|
|
|
val ie = app_state.column_list.size
|
|
|
|
while(i < ie) {
|
|
|
|
if(used_set.contains(i)) {
|
|
|
|
++ i
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
app_state.column_list[i].dispose()
|
|
|
|
++ i
|
|
|
|
}
|
|
|
|
app_state.column_list.clear()
|
|
|
|
app_state.column_list.addAll(tmp_list)
|
|
|
|
|
|
|
|
phoneTab(
|
|
|
|
{ env -> env.pager.adapter = env.pager_adapter },
|
|
|
|
{ env -> resizeColumnWidth(env) }
|
|
|
|
)
|
|
|
|
|
|
|
|
app_state.saveColumnList()
|
|
|
|
updateColumnStrip()
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun resizeColumnWidth(env : TabletEnv) {
|
|
|
|
|
|
|
|
var column_w_min_dp = COLUMN_WIDTH_MIN_DP
|
|
|
|
val sv = pref.getString(Pref.KEY_COLUMN_WIDTH, "")
|
2018-01-10 16:47:35 +01:00
|
|
|
if(sv?.isNotEmpty() == true) {
|
2018-01-04 19:52:25 +01:00
|
|
|
try {
|
|
|
|
val iv = Integer.parseInt(sv)
|
|
|
|
if(iv >= 100) {
|
|
|
|
column_w_min_dp = iv
|
|
|
|
}
|
|
|
|
} catch(ex : Throwable) {
|
|
|
|
log.trace(ex)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
val dm = resources.displayMetrics
|
|
|
|
|
|
|
|
val sw = dm.widthPixels
|
|
|
|
|
|
|
|
val density = dm.density
|
|
|
|
var column_w_min = (0.5f + column_w_min_dp * density).toInt()
|
|
|
|
if(column_w_min < 1) column_w_min = 1
|
|
|
|
|
|
|
|
if(sw < column_w_min * 2) {
|
|
|
|
|
|
|
|
// 最小幅で2つ表示できないのなら1カラム表示
|
2018-01-10 16:47:35 +01:00
|
|
|
env.tablet_pager_adapter.columnWidth = sw
|
2018-01-04 19:52:25 +01:00
|
|
|
resizeAutoCW(sw)
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// カラム最小幅から計算した表示カラム数
|
|
|
|
nScreenColumn = sw / column_w_min
|
|
|
|
if(nScreenColumn < 1) nScreenColumn = 1
|
|
|
|
|
|
|
|
// データのカラム数より大きくならないようにする
|
|
|
|
// (でも最小は1)
|
|
|
|
val column_count = app_state.column_list.size
|
|
|
|
if(column_count > 0 && column_count < nScreenColumn) {
|
|
|
|
nScreenColumn = column_count
|
|
|
|
}
|
|
|
|
|
|
|
|
// 表示カラム数から計算したカラム幅
|
|
|
|
var column_w = sw / nScreenColumn
|
|
|
|
|
|
|
|
// 最小カラム幅の1.5倍よりは大きくならないようにする
|
|
|
|
val column_w_max = (0.5f + column_w_min * 1.5f).toInt()
|
|
|
|
if(column_w > column_w_max) {
|
|
|
|
column_w = column_w_max
|
|
|
|
}
|
|
|
|
resizeAutoCW(column_w)
|
|
|
|
|
|
|
|
nColumnWidth = column_w
|
|
|
|
env.tablet_pager_adapter.columnWidth = column_w
|
|
|
|
env.tablet_snap_helper.columnWidth = column_w
|
|
|
|
}
|
|
|
|
|
|
|
|
// 並べ直す
|
|
|
|
env.tablet_pager_adapter.notifyDataSetChanged()
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun scrollToColumn(index : Int, bAlign : Boolean) {
|
|
|
|
scrollColumnStrip(index)
|
|
|
|
|
2018-01-10 16:47:35 +01:00
|
|
|
phoneTab(
|
|
|
|
{ env -> env.pager.setCurrentItem(index, true) },
|
|
|
|
{ env ->
|
|
|
|
if(! bAlign) {
|
|
|
|
// 指定したカラムが画面内に表示されるように動いてくれるようだ
|
|
|
|
env.tablet_pager.smoothScrollToPosition(index)
|
|
|
|
} else {
|
|
|
|
// 指定位置が表示範囲の左端にくるようにスクロール
|
|
|
|
env.tablet_pager.scrollToPosition(index)
|
|
|
|
}
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
2018-01-10 16:47:35 +01:00
|
|
|
)
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
private fun importAppData(uri : Uri) {
|
|
|
|
|
|
|
|
// remove all columns
|
|
|
|
run {
|
|
|
|
phoneOnly { env -> env.pager.adapter = null }
|
|
|
|
|
|
|
|
for(c in app_state.column_list) {
|
|
|
|
c.dispose()
|
|
|
|
}
|
|
|
|
app_state.column_list.clear()
|
|
|
|
|
|
|
|
phoneTab(
|
|
|
|
{ env -> env.pager.adapter = env.pager_adapter },
|
|
|
|
{ env -> resizeColumnWidth(env) }
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
updateColumnStrip()
|
|
|
|
}
|
|
|
|
|
|
|
|
@Suppress("DEPRECATION")
|
2018-01-13 07:38:32 +01:00
|
|
|
val progress = ProgressDialogEx(this)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
2018-01-10 16:47:35 +01:00
|
|
|
val task = @SuppressLint("StaticFieldLeak") object : AsyncTask<Void, String, ArrayList<Column>?>() {
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
internal fun setProgressMessage(sv : String) {
|
|
|
|
Utils.runOnMainThread { progress.setMessage(sv) }
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun doInBackground(vararg params : Void) : ArrayList<Column>? {
|
|
|
|
try {
|
|
|
|
setProgressMessage("import data to local storage...")
|
|
|
|
|
|
|
|
val cacheDir = cacheDir
|
|
|
|
|
|
|
|
cacheDir.mkdir()
|
|
|
|
val file = File(cacheDir, "SubwayTooter." + android.os.Process.myPid() + "." + android.os.Process.myTid() + ".json")
|
|
|
|
|
|
|
|
// ローカルファイルにコピーする
|
|
|
|
val source = contentResolver.openInputStream(uri)
|
|
|
|
if(source == null) {
|
|
|
|
Utils.showToast(this@ActMain, true, "openInputStream failed.")
|
|
|
|
return null
|
2018-01-10 16:47:35 +01:00
|
|
|
} else {
|
|
|
|
source.use { inStream ->
|
|
|
|
FileOutputStream(file).use { outStream ->
|
2018-01-04 19:52:25 +01:00
|
|
|
IOUtils.copy(inStream, outStream)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 通知サービスを止める
|
2018-01-13 07:15:52 +01:00
|
|
|
setProgressMessage("syncing notification poller…")
|
2018-01-04 19:52:25 +01:00
|
|
|
PollingWorker.queueAppDataImportBefore(this@ActMain)
|
|
|
|
while(PollingWorker.mBusyAppDataImportBefore.get()) {
|
2018-01-13 07:15:52 +01:00
|
|
|
Thread.sleep(1000L)
|
|
|
|
log.d("syncing polling task...")
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// JSONを読みだす
|
|
|
|
setProgressMessage("reading app data...")
|
2018-01-10 16:47:35 +01:00
|
|
|
InputStreamReader(FileInputStream(file), "UTF-8").use { inStream ->
|
2018-01-04 19:52:25 +01:00
|
|
|
return AppDataExporter.decodeAppData(this@ActMain, JsonReader(inStream))
|
|
|
|
}
|
|
|
|
} catch(ex : Throwable) {
|
|
|
|
log.trace(ex)
|
|
|
|
Utils.showToast(this@ActMain, ex, "importAppData failed.")
|
|
|
|
}
|
|
|
|
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
2018-01-10 16:47:35 +01:00
|
|
|
override fun onCancelled(result : ArrayList<Column>?) {
|
2018-01-04 19:52:25 +01:00
|
|
|
onPostExecute(result)
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onPostExecute(result : ArrayList<Column>?) {
|
|
|
|
|
|
|
|
try {
|
|
|
|
progress.dismiss()
|
|
|
|
} catch(ignored : Throwable) {
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
|
|
|
} catch(ignored : Throwable) {
|
|
|
|
}
|
|
|
|
|
2018-01-13 07:15:52 +01:00
|
|
|
try {
|
|
|
|
if(isCancelled || result == null) {
|
|
|
|
// cancelled.
|
|
|
|
return
|
|
|
|
}
|
2018-01-04 19:52:25 +01:00
|
|
|
|
2018-01-13 07:15:52 +01:00
|
|
|
run {
|
|
|
|
|
|
|
|
phoneOnly { env -> env.pager.adapter = null }
|
|
|
|
|
|
|
|
app_state.column_list.clear()
|
|
|
|
app_state.column_list.addAll(result)
|
|
|
|
app_state.saveColumnList()
|
|
|
|
|
|
|
|
phoneTab(
|
|
|
|
{ env -> env.pager.adapter = env.pager_adapter },
|
|
|
|
{ env -> resizeColumnWidth(env) }
|
|
|
|
)
|
|
|
|
updateColumnStrip()
|
|
|
|
}
|
|
|
|
}finally{
|
|
|
|
// 通知サービスをリスタート
|
|
|
|
PollingWorker.queueAppDataImportAfter(this@ActMain)
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
|
|
|
} catch(ignored : Throwable) {
|
|
|
|
}
|
|
|
|
|
|
|
|
progress.isIndeterminate = true
|
|
|
|
progress.setCancelable(false)
|
|
|
|
progress.setOnCancelListener { task.cancel(true) }
|
|
|
|
progress.show()
|
|
|
|
task.executeOnExecutor(App1.task_executor)
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onDrawerSlide(drawerView : View, slideOffset : Float) {
|
|
|
|
post_helper.closeAcctPopup()
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onDrawerOpened(drawerView : View) {
|
|
|
|
post_helper.closeAcctPopup()
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onDrawerClosed(drawerView : View) {
|
|
|
|
post_helper.closeAcctPopup()
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onDrawerStateChanged(newState : Int) {
|
|
|
|
post_helper.closeAcctPopup()
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun resizeAutoCW(column_w : Int) {
|
|
|
|
val sv = pref.getString(Pref.KEY_AUTO_CW_LINES, "")
|
|
|
|
nAutoCwLines = Utils.parse_int(sv, - 1)
|
|
|
|
if(nAutoCwLines > 0) {
|
|
|
|
val lv_pad = (0.5f + 12 * density).toInt()
|
|
|
|
val icon_width = avatarIconSize
|
|
|
|
val icon_end = (0.5f + 4 * density).toInt()
|
|
|
|
nAutoCwCellWidth = column_w - lv_pad * 2 - icon_width - icon_end
|
|
|
|
}
|
|
|
|
// この後各カラムは再描画される
|
|
|
|
}
|
|
|
|
|
|
|
|
fun checkAutoCW(status : TootStatus, text : CharSequence) {
|
|
|
|
if(nAutoCwCellWidth <= 0) {
|
|
|
|
// 設定が無効
|
|
|
|
status.auto_cw = null
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var auto_cw = status.auto_cw
|
|
|
|
if(auto_cw != null && auto_cw.refActivity?.get() === this@ActMain && auto_cw.cell_width == nAutoCwCellWidth) {
|
|
|
|
// 以前に計算した値がまだ使える
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-01-10 16:47:35 +01:00
|
|
|
if(auto_cw == null) {
|
2018-01-04 19:52:25 +01:00
|
|
|
auto_cw = TootStatus.AutoCW()
|
|
|
|
status.auto_cw = auto_cw
|
|
|
|
}
|
|
|
|
|
|
|
|
// 計算時の条件(文字フォント、文字サイズ、カラム幅)を覚えておいて、再利用時に同じか確認する
|
|
|
|
auto_cw.refActivity = WeakReference(this@ActMain)
|
|
|
|
auto_cw.cell_width = nAutoCwCellWidth
|
|
|
|
auto_cw.decoded_spoiler_text = null
|
|
|
|
|
|
|
|
// テキストをレイアウトして行数を測定
|
|
|
|
|
|
|
|
val lp = LinearLayout.LayoutParams(nAutoCwCellWidth, LinearLayout.LayoutParams.WRAP_CONTENT)
|
|
|
|
val tv = TextView(this)
|
|
|
|
tv.layoutParams = lp
|
2018-01-10 16:47:35 +01:00
|
|
|
if(timeline_font_size_sp.isNaN()) {
|
2018-01-04 19:52:25 +01:00
|
|
|
tv.textSize = timeline_font_size_sp
|
|
|
|
}
|
|
|
|
if(timeline_font != null) {
|
|
|
|
tv.typeface = timeline_font
|
|
|
|
}
|
|
|
|
tv.text = text
|
|
|
|
tv.measure(
|
|
|
|
View.MeasureSpec.makeMeasureSpec(nAutoCwCellWidth, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
|
|
|
|
)
|
|
|
|
val l = tv.layout
|
|
|
|
if(l != null) {
|
|
|
|
auto_cw.originalLineCount = l.lineCount
|
|
|
|
val line_count = auto_cw.originalLineCount
|
|
|
|
|
|
|
|
if(nAutoCwLines > 0
|
|
|
|
&& line_count > nAutoCwLines
|
2018-01-10 16:47:35 +01:00
|
|
|
&& status.spoiler_text?.isEmpty() != false
|
|
|
|
) {
|
2018-01-04 19:52:25 +01:00
|
|
|
val sb = SpannableStringBuilder()
|
|
|
|
sb.append(getString(R.string.auto_cw_prefix))
|
|
|
|
sb.append(text, 0, l.getLineEnd(nAutoCwLines - 1))
|
|
|
|
var last = sb.length
|
|
|
|
while(last > 0) {
|
|
|
|
val c = sb[last - 1]
|
|
|
|
if(c == '\n' || Character.isWhitespace(c)) {
|
|
|
|
-- last
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if(last < sb.length) {
|
|
|
|
sb.delete(last, sb.length)
|
|
|
|
}
|
|
|
|
sb.append('…')
|
|
|
|
auto_cw.decoded_spoiler_text = sb
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|