(Misskey)ユーザのブロックと解除。被ブロック、被フォロー申請の表示。ただしブロックしたユーザの一覧を表示することはできない(APIがない)。

This commit is contained in:
tateisu 2018-10-31 04:29:00 +09:00
parent 64d3aa4739
commit 486a91d996
29 changed files with 488 additions and 353 deletions

View File

@ -19,34 +19,22 @@ import android.text.TextWatcher
import android.util.JsonWriter import android.util.JsonWriter
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.AdapterView import android.widget.*
import android.widget.ArrayAdapter
import android.widget.BaseAdapter
import android.widget.CompoundButton
import android.widget.EditText
import android.widget.ImageView
import android.widget.Spinner
import android.widget.TextView
import com.jrummyapps.android.colorpicker.ColorPickerDialog import com.jrummyapps.android.colorpicker.ColorPickerDialog
import com.jrummyapps.android.colorpicker.ColorPickerDialogListener import com.jrummyapps.android.colorpicker.ColorPickerDialogListener
import jp.juggler.subwaytooter.dialog.ProgressDialogEx import jp.juggler.subwaytooter.dialog.ProgressDialogEx
import org.apache.commons.io.IOUtils
import org.apache.commons.io.output.FileWriterWithEncoding
import java.io.File
import java.io.FileOutputStream
import java.text.NumberFormat
import java.util.ArrayList
import java.util.Locale
import jp.juggler.subwaytooter.table.AcctColor import jp.juggler.subwaytooter.table.AcctColor
import jp.juggler.subwaytooter.table.SavedAccount import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.* import jp.juggler.subwaytooter.util.LogCategory
import org.json.JSONObject import jp.juggler.subwaytooter.util.handleGetContentResult
import java.io.FileInputStream import jp.juggler.subwaytooter.util.intentOpenDocument
import jp.juggler.subwaytooter.util.showToast
import org.apache.commons.io.IOUtils
import java.io.File
import java.io.FileOutputStream
import java.io.OutputStreamWriter import java.io.OutputStreamWriter
import java.text.NumberFormat
import java.util.*
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream import java.util.zip.ZipOutputStream
@ -868,11 +856,11 @@ class ActAppSetting : AppCompatActivity()
c = footer_button_fg_color c = footer_button_fg_color
if(c == 0) { if(c == 0) {
Styler.setIconDefaultColor(this, ivFooterToot, R.attr.ic_edit) Styler.setIconAttr(this, ivFooterToot, R.attr.ic_edit)
Styler.setIconDefaultColor(this, ivFooterMenu, R.attr.ic_hamburger) Styler.setIconAttr(this, ivFooterMenu, R.attr.ic_hamburger)
} else { } else {
Styler.setIconCustomColor(this, ivFooterToot, c, R.attr.ic_edit) Styler.setIconAttr(this, ivFooterToot, R.attr.ic_edit,color=c)
Styler.setIconCustomColor(this, ivFooterMenu, c, R.attr.ic_hamburger) Styler.setIconAttr(this, ivFooterMenu, R.attr.ic_hamburger,color=c)
} }
c = footer_tab_bg_color c = footer_tab_bg_color

View File

@ -414,18 +414,18 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
android.R.attr.textColorPrimary android.R.attr.textColorPrimary
) )
) )
Styler.setIconDefaultColor( Styler.setIconAttr(
this, this,
ivColumnHeader, ivColumnHeader,
column.getIconAttrId(column.column_type) column.getIconAttrId(column.column_type)
) )
} else { } else {
tvColumnName.setTextColor(c) tvColumnName.setTextColor(c)
Styler.setIconCustomColor( Styler.setIconAttr(
this, this,
ivColumnHeader, ivColumnHeader,
c, column.getIconAttrId(column.column_type),
column.getIconAttrId(column.column_type) color = c
) )
} }

View File

@ -1046,19 +1046,21 @@ class ActMain : AppCompatActivity()
, bAllowPseudo = false , bAllowPseudo = false
, args = arrayOf("", false) , args = arrayOf("", false)
) )
R.id.nav_add_mutes -> Action_Account.timeline( R.id.nav_add_mutes -> Action_Account.timeline(
this this
, defaultInsertPosition , defaultInsertPosition
, Column.TYPE_MUTES , Column.TYPE_MUTES
, bAllowPseudo = false , bAllowPseudo = false
) )
R.id.nav_add_blocks -> Action_Account.timeline( R.id.nav_add_blocks -> Action_Account.timeline(
this this
, defaultInsertPosition , defaultInsertPosition
, Column.TYPE_BLOCKS , Column.TYPE_BLOCKS
, bAllowPseudo = false , bAllowPseudo = false
, bAllowMisskey = false
) )
R.id.nav_keyword_filter -> Action_Account.timeline( R.id.nav_keyword_filter -> Action_Account.timeline(
this this
, defaultInsertPosition , defaultInsertPosition
@ -1421,9 +1423,9 @@ class ActMain : AppCompatActivity()
c = column.header_fg_color c = column.header_fg_color
if(c == 0) { if(c == 0) {
Styler.setIconDefaultColor(this, ivIcon, column.getIconAttrId(column.column_type)) Styler.setIconAttr(this, ivIcon, column.getIconAttrId(column.column_type))
} else { } else {
Styler.setIconCustomColor(this, ivIcon, c, column.getIconAttrId(column.column_type)) Styler.setIconAttr(this, ivIcon, column.getIconAttrId(column.column_type),c)
} }
// //
@ -2266,13 +2268,13 @@ class ActMain : AppCompatActivity()
c = footer_button_fg_color c = footer_button_fg_color
if(c == 0) { if(c == 0) {
Styler.setIconDefaultColor(this, btnToot, R.attr.ic_edit) Styler.setIconAttr(this, btnToot, R.attr.ic_edit)
Styler.setIconDefaultColor(this, btnMenu, R.attr.ic_hamburger) Styler.setIconAttr(this, btnMenu, R.attr.ic_hamburger)
Styler.setIconDefaultColor(this, btnQuickToot, R.attr.btn_post) Styler.setIconAttr(this, btnQuickToot, R.attr.btn_post)
} else { } else {
Styler.setIconCustomColor(this, btnToot, c, R.attr.ic_edit) Styler.setIconAttr(this, btnToot, R.attr.ic_edit,c)
Styler.setIconCustomColor(this, btnMenu, c, R.attr.ic_hamburger) Styler.setIconAttr(this, btnMenu, R.attr.ic_hamburger,c)
Styler.setIconCustomColor(this, btnQuickToot, c, R.attr.btn_post) Styler.setIconAttr(this, btnQuickToot, R.attr.btn_post,c)
} }
c = footer_tab_bg_color c = footer_tab_bg_color

View File

@ -108,7 +108,10 @@ class App1 : Application() {
// 2018/8/19 v268 30 => 31 (29)で失敗しておかしくなったContentWarningとMediaShownを作り直す // 2018/8/19 v268 30 => 31 (29)で失敗しておかしくなったContentWarningとMediaShownを作り直す
// 2018/8/28 v279 31 => 32 UserRelation,UserRelationMisskey にendorsedを追加 // 2018/8/28 v279 31 => 32 UserRelation,UserRelationMisskey にendorsedを追加
// 2018/8/28 v280 32 => 33 NotificationTracking テーブルの作り直し。SavedAccountに通知二種類を追加 // 2018/8/28 v280 32 => 33 NotificationTracking テーブルの作り直し。SavedAccountに通知二種類を追加
internal const val DB_VERSION = 33 // 2018/10/31 v296 33 => 34 UserRelationMisskey に blocked_by を追加
// 2018/10/31 v296 34 => 35 UserRelationMisskey に requested_by を追加
internal const val DB_VERSION = 35
private val tableList = arrayOf( private val tableList = arrayOf(
LogData, LogData,

View File

@ -769,8 +769,8 @@ class Column(
} }
} }
private fun JSONObject.putIfTrue(key:String,value:Boolean){ private fun JSONObject.putIfTrue(key : String, value : Boolean) {
if(value) put( key,true) if(value) put(key, true)
} }
@Throws(JSONException::class) @Throws(JSONException::class)
@ -1556,41 +1556,43 @@ class Column(
internal fun loadProfileAccount( internal fun loadProfileAccount(
client : TootApiClient, client : TootApiClient,
parser : TootParser,
bForceReload : Boolean bForceReload : Boolean
) : TootApiResult? { ) : TootApiResult? {
return if(bForceReload || this.who_account == null) {
return if(this.who_account != null && ! bForceReload) {
// リロード不要なら何もしない
null
} else if(isMisskey) {
val params = access_info.putMisskeyApiToken(JSONObject())
.put("userId", profile_id)
if(isMisskey) { val result = client.request(PATH_MISSKEY_PROFILE, params.toPostRequestBuilder())
val params = access_info.putMisskeyApiToken(JSONObject())
.put("userId", profile_id) // ユーザリレーションの取り扱いのため、別のparserを作ってはいけない
val result = client.request(PATH_MISSKEY_PROFILE, params.toPostRequestBuilder()) parser.misskeyDecodeProfilePin = true
val parser = TootParser( try {
context,
access_info,
misskeyDecodeProfilePin = true
)
val a = TootAccountRef.mayNull(parser, parser.account(result?.jsonObject)) val a = TootAccountRef.mayNull(parser, parser.account(result?.jsonObject))
if(a != null) { if(a != null) {
this.who_account = a this.who_account = a
client.publishApiProgress("") // カラムヘッダの再表示 client.publishApiProgress("") // カラムヘッダの再表示
} }
result } finally {
parser.misskeyDecodeProfilePin = false
} else {
val result = client.request(String.format(Locale.JAPAN, PATH_ACCOUNT, profile_id))
val parser = TootParser(context, access_info)
val a = TootAccountRef.mayNull(parser, parser.account(result?.jsonObject))
if(a != null) {
this.who_account = a
client.publishApiProgress("") // カラムヘッダの再表示
}
result
} }
result
} else { } else {
null val result = client.request(String.format(Locale.JAPAN, PATH_ACCOUNT, profile_id))
val a = TootAccountRef.mayNull(parser, parser.account(result?.jsonObject))
if(a != null) {
this.who_account = a
client.publishApiProgress("") // カラムヘッダの再表示
}
result
} }
} }
internal fun loadListInfo(client : TootApiClient, bForceReload : Boolean) { internal fun loadListInfo(client : TootApiClient, bForceReload : Boolean) {
@ -1756,7 +1758,7 @@ class Column(
// DMカラム更新時に新APIの利用に成功したなら真 // DMカラム更新時に新APIの利用に成功したなら真
internal var useConversationSummarys = false internal var useConversationSummarys = false
// DMカラムのストリーミングイベントで新形式のイベントを利用できたなら真 // DMカラムのストリーミングイベントで新形式のイベントを利用できたなら真
internal var useConversationSummaryStreaming = false internal var useConversationSummaryStreaming = false
@ -2432,12 +2434,12 @@ class Column(
TYPE_DIRECT_MESSAGES -> { TYPE_DIRECT_MESSAGES -> {
useConversationSummarys = false useConversationSummarys = false
if(! use_old_api){ if(! use_old_api) {
// try 2.6.0 new API https://github.com/tootsuite/mastodon/pull/8832 // try 2.6.0 new API https://github.com/tootsuite/mastodon/pull/8832
val resultCS = getConversationSummary(client, PATH_DIRECT_MESSAGES2) val resultCS = getConversationSummary(client, PATH_DIRECT_MESSAGES2)
when{ when {
// cancelled // cancelled
resultCS == null -> return null resultCS == null -> return null
@ -2475,7 +2477,7 @@ class Column(
TYPE_PROFILE -> { TYPE_PROFILE -> {
val who_result = loadProfileAccount(client, true) val who_result = loadProfileAccount(client, parser, true)
if(client.isApiCancelled || who_account == null) return who_result if(client.isApiCancelled || who_account == null) return who_result
@ -2594,7 +2596,11 @@ class Column(
} }
TYPE_KEYWORD_FILTER -> return parseFilterList(client, PATH_FILTERS) TYPE_KEYWORD_FILTER -> return parseFilterList(client, PATH_FILTERS)
TYPE_BLOCKS -> return parseAccountList(client, PATH_BLOCKS) TYPE_BLOCKS -> return if(isMisskey){
TootApiResult("Misskey has no API to get block list")
}else{
parseAccountList(client, PATH_BLOCKS)
}
TYPE_DOMAIN_BLOCKS -> return parseDomainList(client, PATH_DOMAIN_BLOCK) TYPE_DOMAIN_BLOCKS -> return parseDomainList(client, PATH_DOMAIN_BLOCK)
@ -2825,7 +2831,7 @@ class Column(
// カードを取得する // カードを取得する
if(! Pref.bpDontRetrievePreviewCard(context)) { if(! Pref.bpDontRetrievePreviewCard(context)) {
this.list_tmp?.forEach { o -> this.list_tmp?.forEach { o ->
if( o is TootStatus && o.card==null ) if(o is TootStatus && o.card == null)
o.card = parseItem( o.card = parseItem(
::TootCard, ::TootCard,
client.request("/api/v1/statuses/" + o.id + "/card")?.jsonObject client.request("/api/v1/statuses/" + o.id + "/card")?.jsonObject
@ -3893,7 +3899,7 @@ class Column(
aroundMin : Boolean = false, aroundMin : Boolean = false,
misskeyParams : JSONObject? = null, misskeyParams : JSONObject? = null,
misskeyCustomParser : (parser : TootParser, jsonArray : JSONArray) -> ArrayList<TootConversationSummary> = misskeyCustomParser : (parser : TootParser, jsonArray : JSONArray) -> ArrayList<TootConversationSummary> =
{ parser, jsonArray -> parseList(::TootConversationSummary,parser,jsonArray) } { parser, jsonArray -> parseList(::TootConversationSummary, parser, jsonArray) }
) : TootApiResult? { ) : TootApiResult? {
val isMisskey = access_info.isMisskey val isMisskey = access_info.isMisskey
@ -4397,7 +4403,6 @@ class Column(
return firstResult return firstResult
} }
var filterUpdated = false var filterUpdated = false
override fun doInBackground(vararg unused : Void) : TootApiResult? { override fun doInBackground(vararg unused : Void) : TootApiResult? {
@ -4431,7 +4436,7 @@ class Column(
TYPE_DIRECT_MESSAGES -> if(useConversationSummarys) { TYPE_DIRECT_MESSAGES -> if(useConversationSummarys) {
// try 2.6.0 new API https://github.com/tootsuite/mastodon/pull/8832 // try 2.6.0 new API https://github.com/tootsuite/mastodon/pull/8832
getConversationSummaryList(client, PATH_DIRECT_MESSAGES2) getConversationSummaryList(client, PATH_DIRECT_MESSAGES2)
}else { } else {
// fallback to old api // fallback to old api
getStatusList(client, PATH_DIRECT_MESSAGES) getStatusList(client, PATH_DIRECT_MESSAGES)
} }
@ -4518,7 +4523,7 @@ class Column(
) )
TYPE_PROFILE -> { TYPE_PROFILE -> {
loadProfileAccount(client, false) loadProfileAccount(client, parser, false)
when(profile_tab) { when(profile_tab) {
@ -4800,7 +4805,7 @@ class Column(
} }
} }
replaceConversationSummary(changeList,list_new,list_data) replaceConversationSummary(changeList, list_new, list_data)
// 投稿後のリフレッシュなら当該投稿の位置を探す // 投稿後のリフレッシュなら当該投稿の位置を探す
var status_index = - 1 var status_index = - 1
@ -5352,14 +5357,13 @@ class Column(
return result return result
} }
fun getConversationSummaryList( fun getConversationSummaryList(
client : TootApiClient, client : TootApiClient,
path_base : String, path_base : String,
misskeyParams : JSONObject? = null misskeyParams : JSONObject? = null
, ,
misskeyCustomParser : (parser : TootParser, jsonArray : JSONArray) -> ArrayList<TootConversationSummary> = misskeyCustomParser : (parser : TootParser, jsonArray : JSONArray) -> ArrayList<TootConversationSummary> =
{ parser, jsonArray -> parseList(::TootConversationSummary,parser,jsonArray) } { parser, jsonArray -> parseList(::TootConversationSummary, parser, jsonArray) }
) : TootApiResult? { ) : TootApiResult? {
val isMisskey = access_info.isMisskey val isMisskey = access_info.isMisskey
@ -5647,14 +5651,13 @@ class Column(
} }
} }
TYPE_DIRECT_MESSAGES -> if(useConversationSummarys) { TYPE_DIRECT_MESSAGES -> if(useConversationSummarys) {
// try 2.6.0 new API https://github.com/tootsuite/mastodon/pull/8832 // try 2.6.0 new API https://github.com/tootsuite/mastodon/pull/8832
getConversationSummaryList(client, PATH_DIRECT_MESSAGES2) getConversationSummaryList(client, PATH_DIRECT_MESSAGES2)
}else { } else {
// fallback to old api // fallback to old api
getStatusList(client, PATH_DIRECT_MESSAGES) getStatusList(client, PATH_DIRECT_MESSAGES)
} }
else -> getStatusList(client, makeHomeTlUrl()) else -> getStatusList(client, makeHomeTlUrl())
} }
@ -5700,14 +5703,14 @@ class Column(
fireShowContent(reason = "gap list_tmp is null", changeList = ArrayList()) fireShowContent(reason = "gap list_tmp is null", changeList = ArrayList())
return return
} }
val list_new = duplicate_map.filterDuplicate(list_tmp) val list_new = duplicate_map.filterDuplicate(list_tmp)
// 0個でもギャップを消すために以下の処理を続ける // 0個でもギャップを消すために以下の処理を続ける
val added = list_new.size // may 0 val added = list_new.size // may 0
val changeList = ArrayList<AdapterChange>() val changeList = ArrayList<AdapterChange>()
replaceConversationSummary(changeList,list_new,list_data) replaceConversationSummary(changeList, list_new, list_data)
val position = list_data.indexOf(gap) val position = list_data.indexOf(gap)
if(position == - 1) { if(position == - 1) {
@ -5732,7 +5735,7 @@ class Column(
} }
} }
} }
list_data.removeAt(position) list_data.removeAt(position)
list_data.addAll(position, list_new) list_data.addAll(position, list_new)
@ -6021,32 +6024,31 @@ class Column(
} }
} }
override fun onTimelineItem(item : TimelineItem) { override fun onTimelineItem(item : TimelineItem) {
if(is_dispose.get()) return if(is_dispose.get()) return
if(item is TootConversationSummary) { if(item is TootConversationSummary) {
if(column_type != TYPE_DIRECT_MESSAGES) return if(column_type != TYPE_DIRECT_MESSAGES) return
if(isFiltered(item.last_status)) return if(isFiltered(item.last_status)) return
if( use_old_api ){ if(use_old_api) {
useConversationSummaryStreaming = false useConversationSummaryStreaming = false
return return
}else{ } else {
useConversationSummaryStreaming = true useConversationSummaryStreaming = true
} }
}else if(item is TootNotification) { } else if(item is TootNotification) {
if(column_type != TYPE_NOTIFICATIONS) return if(column_type != TYPE_NOTIFICATIONS) return
if(isFiltered(item)) return if(isFiltered(item)) return
} else if(item is TootStatus) { } else if(item is TootStatus) {
if(column_type == TYPE_NOTIFICATIONS) return if(column_type == TYPE_NOTIFICATIONS) return
// マストドン2.6.0形式のDMカラム用イベントを利用したならば、その直後に発生する普通の投稿イベントを無視する // マストドン2.6.0形式のDMカラム用イベントを利用したならば、その直後に発生する普通の投稿イベントを無視する
if( useConversationSummaryStreaming ) return if(useConversationSummaryStreaming) return
if(column_type == TYPE_LOCAL && ! isMisskey && item.account.acct.indexOf('@') != - 1) return if(column_type == TYPE_LOCAL && ! isMisskey && item.account.acct.indexOf('@') != - 1) return
if(isFiltered(item)) return if(isFiltered(item)) return
} }
stream_data_queue.add(item) stream_data_queue.add(item)
val handler = App1.getAppState(context).handler val handler = App1.getAppState(context).handler
@ -6171,8 +6173,6 @@ class Column(
} }
} }
// 最新のIDをsince_idとして覚える(ソートはしない) // 最新のIDをsince_idとして覚える(ソートはしない)
var new_id_max : EntityId? = null var new_id_max : EntityId? = null
var new_id_min : EntityId? = null var new_id_min : EntityId? = null
@ -6243,12 +6243,12 @@ class Column(
val added = list_new.size val added = list_new.size
val changeList = ArrayList<AdapterChange>() val changeList = ArrayList<AdapterChange>()
replaceConversationSummary(changeList,list_new,list_data) replaceConversationSummary(changeList, list_new, list_data)
loop@ for(o in list_new) { loop@ for(o in list_new) {
when(o) { when(o) {
is TootStatus -> { is TootStatus -> {
val highlight_sound = o.highlight_sound val highlight_sound = o.highlight_sound
if(highlight_sound != null) { if(highlight_sound != null) {
@ -6258,7 +6258,7 @@ class Column(
} }
} }
} }
changeList.add(AdapterChange(AdapterChangeType.RangeInsert, 0, added)) changeList.add(AdapterChange(AdapterChangeType.RangeInsert, 0, added))
list_data.addAll(0, list_new) list_data.addAll(0, list_new)
@ -6299,21 +6299,21 @@ class Column(
} }
private fun replaceConversationSummary( private fun replaceConversationSummary(
changeList :ArrayList<AdapterChange>, changeList : ArrayList<AdapterChange>,
list_new: ArrayList<TimelineItem>, list_new : ArrayList<TimelineItem>,
list_data: BucketList<TimelineItem> list_data : BucketList<TimelineItem>
){ ) {
val removeSet = HashSet<EntityId>() val removeSet = HashSet<EntityId>()
loop@ for(o in list_new) { loop@ for(o in list_new) {
if( o is TootConversationSummary ) { if(o is TootConversationSummary) {
removeSet.add( o.getOrderId() ) removeSet.add(o.getOrderId())
} }
} }
if( list_data .isNotEmpty() && removeSet.isNotEmpty() ){ if(list_data.isNotEmpty() && removeSet.isNotEmpty()) {
for( i in list_data.size-1 downTo 0 ){ for(i in list_data.size - 1 downTo 0) {
val o = list_data[i] val o = list_data[i]
if( o is TootConversationSummary && removeSet.contains(o.id) ){ if(o is TootConversationSummary && removeSet.contains(o.id)) {
changeList.add(AdapterChange(AdapterChangeType.RangeRemove, i, 1)) changeList.add(AdapterChange(AdapterChangeType.RangeRemove, i, 1))
list_data.removeAt(i) list_data.removeAt(i)
log.d("remove old TootConversationSummary") log.d("remove old TootConversationSummary")

View File

@ -624,27 +624,27 @@ class ColumnViewHolder(
android.R.attr.textColorPrimary android.R.attr.textColorPrimary
) )
) )
Styler.setIconDefaultColor( Styler.setIconAttr(
activity, activity,
ivColumnIcon, ivColumnIcon,
column.getIconAttrId(column.column_type) column.getIconAttrId(column.column_type)
) )
Styler.setIconDefaultColor(activity, btnColumnSetting, R.attr.ic_tune) Styler.setIconAttr(activity, btnColumnSetting, R.attr.ic_tune)
Styler.setIconDefaultColor(activity, btnColumnReload, R.attr.btn_refresh) Styler.setIconAttr(activity, btnColumnReload, R.attr.btn_refresh)
Styler.setIconDefaultColor(activity, btnColumnClose, R.attr.btn_close) Styler.setIconAttr(activity, btnColumnClose, R.attr.btn_close)
} else { } else {
tvColumnIndex.setTextColor(c) tvColumnIndex.setTextColor(c)
tvColumnStatus.setTextColor(c) tvColumnStatus.setTextColor(c)
tvColumnName.setTextColor(c) tvColumnName.setTextColor(c)
Styler.setIconCustomColor( Styler.setIconAttr(
activity, activity,
ivColumnIcon, ivColumnIcon,
c, column.getIconAttrId(column.column_type),
column.getIconAttrId(column.column_type) c
) )
Styler.setIconCustomColor(activity, btnColumnSetting, c, R.attr.ic_tune) Styler.setIconAttr(activity, btnColumnSetting, R.attr.ic_tune,c)
Styler.setIconCustomColor(activity, btnColumnReload, c, R.attr.btn_refresh) Styler.setIconAttr(activity, btnColumnReload, R.attr.btn_refresh,c)
Styler.setIconCustomColor(activity, btnColumnClose, c, R.attr.btn_close) Styler.setIconAttr(activity, btnColumnClose, R.attr.btn_close,c)
} }
c = column.column_bg_color c = column.column_bg_color

View File

@ -496,7 +496,12 @@ internal class DlgContextMenu(
R.id.btnBlock -> R.id.btnBlock ->
if(relation.blocking) { if(relation.blocking) {
Action_User.block(activity, access_info, who, false) Action_User.block(
activity,
access_info,
who,
false
)
} else { } else {
AlertDialog.Builder(activity) AlertDialog.Builder(activity)
.setMessage( .setMessage(

View File

@ -4,6 +4,8 @@ import android.app.DownloadManager
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import jp.juggler.subwaytooter.util.getIntOrNull
import jp.juggler.subwaytooter.util.getStringOrNull
import jp.juggler.subwaytooter.util.showToast import jp.juggler.subwaytooter.util.showToast
@ -22,12 +24,10 @@ class DownloadReceiver : BroadcastReceiver() {
val query = DownloadManager.Query().setFilterById(id) val query = DownloadManager.Query().setFilterById(id)
downloadManager.query(query)?.use { cursor -> downloadManager.query(query)?.use { cursor ->
if(cursor.moveToFirst()) { if(cursor.moveToFirst()) {
val idx_status = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)
val idx_title = cursor.getColumnIndex(DownloadManager.COLUMN_TITLE)
val title = cursor.getString(idx_title) val title = cursor.getStringOrNull(DownloadManager.COLUMN_TITLE)
if(DownloadManager.STATUS_SUCCESSFUL == cursor.getInt(idx_status)) { if(DownloadManager.STATUS_SUCCESSFUL == cursor.getIntOrNull(DownloadManager.COLUMN_STATUS) ) {
/* /*
ダウンロード完了通知がシステムからのものと重複することがある ダウンロード完了通知がシステムからのものと重複することがある

View File

@ -58,22 +58,31 @@ object Styler {
) )
} }
// ImageViewにアイコンを設定する private fun setIconDrawableId(
fun setIconDefaultColor(context : Context, imageView : ImageView, iconAttrId : Int) {
imageView.setImageResource(getAttributeResourceId(context, iconAttrId))
}
// ImageViewにアイコンを設定する。色を変えてしまう
fun setIconCustomColor(
context : Context, context : Context,
imageView : ImageView, imageView : ImageView,
color : Int, drawableId : Int,
iconAttrId : Int color:Int? = null
) { ) {
val d = getAttributeDrawable(context, iconAttrId) if( color == null) {
d.mutate() // 色指定が他のアイコンに影響しないようにする // ImageViewにアイコンを設定する。デフォルトの色
d.setColorFilter(color, PorterDuff.Mode.SRC_ATOP) imageView.setImageResource(drawableId)
imageView.setImageDrawable(d) }else{
// ImageViewにアイコンを設定する。色を変えてしまう
val d = ContextCompat.getDrawable(context,drawableId) ?: return
d.mutate() // 色指定が他のアイコンに影響しないようにする
d.setColorFilter(color, PorterDuff.Mode.SRC_ATOP)
imageView.setImageDrawable(d)
}
}
fun setIconAttr(
context : Context,
imageView : ImageView,
iconAttrId : Int,
color:Int? = null
) {
setIconDrawableId(context,imageView,getAttributeResourceId(context, iconAttrId),color)
} }
fun getVisibilityIconAttr(isMisskeyData:Boolean ,visibility : TootVisibility):Int { fun getVisibilityIconAttr(isMisskeyData:Boolean ,visibility : TootVisibility):Int {
@ -162,15 +171,34 @@ object Styler {
) { ) {
// 被フォロー状態 // 被フォロー状態
if(! relation.followed_by) { when {
ivDot.visibility = View.GONE
} else {
ivDot.visibility = View.VISIBLE
setIconDefaultColor(context, ivDot, R.attr.ic_followed_by)
// 被フォローリクエスト状態の時に followed_by が 真と偽の両方がありえるようなので relation.blocked_by -> {
// Relationshipだけを見ても被フォローリクエスト状態は分からないっぽい ivDot.visibility = View.VISIBLE
// 仕方ないので馬鹿正直に「 followed_byが真ならバッジをつける」しかできない setIconDrawableId(context, ivDot, R.drawable.ic_blocked_by,
color=Styler.getAttributeColor(context, R.attr.colorRegexFilterError)
)
}
relation.requested_by -> {
ivDot.visibility = View.VISIBLE
setIconDrawableId(context, ivDot, R.drawable.ic_requested_by,
color=Styler.getAttributeColor(context, R.attr.colorRegexFilterError)
)
}
relation.followed_by-> {
ivDot.visibility = View.VISIBLE
setIconAttr(context, ivDot, R.attr.ic_followed_by)
// 被フォローリクエスト状態の時に followed_by が 真と偽の両方がありえるようなので
// Relationshipだけを見ても被フォローリクエスト状態は分からないっぽい
// 仕方ないので馬鹿正直に「 followed_byが真ならバッジをつける」しかできない
}
else -> {
ivDot.visibility = View.GONE
}
} }
// フォローボタン // フォローボタン
@ -211,8 +239,7 @@ object Styler {
} }
} }
val color = Styler.getAttributeColor(context, color_attr) setIconAttr(context, ibFollow, icon_attr, color =Styler.getAttributeColor(context, color_attr) )
setIconCustomColor(context, ibFollow, color, icon_attr)
ibFollow.contentDescription = contentDescription ibFollow.contentDescription = contentDescription
} }

View File

@ -122,17 +122,20 @@ fun makeAccountListNonPseudo(
return list_same_host return list_same_host
} }
internal fun saveUserRelation(access_info : SavedAccount,src : TootRelationShip?) : UserRelation? { internal fun saveUserRelation(access_info : SavedAccount, src : TootRelationShip?) : UserRelation? {
src ?: return null src ?: return null
val now = System.currentTimeMillis() val now = System.currentTimeMillis()
return UserRelation.save1(now, access_info.db_id, src) return UserRelation.save1(now, access_info.db_id, src)
} }
internal fun saveUserRelationMisskey(access_info : SavedAccount,whoId:EntityId,parser:TootParser) : UserRelation? {
internal fun saveUserRelationMisskey(
access_info : SavedAccount,
whoId : EntityId,
parser : TootParser
) : UserRelation? {
val now = System.currentTimeMillis() val now = System.currentTimeMillis()
val relation = parser.getMisskeyUserRelation(whoId) val relation = parser.getMisskeyUserRelation(whoId)
if( relation != null){ UserRelationMisskey.save1(now, access_info.db_id, whoId.toString(), relation)
UserRelationMisskey.save1(now, access_info.db_id, whoId.toString(),relation)
}
return relation return relation
} }
@ -141,7 +144,8 @@ internal fun loadRelation1Mastodon(
client : TootApiClient, client : TootApiClient,
access_info : SavedAccount, access_info : SavedAccount,
who : TootAccount who : TootAccount
):RelationResult{val rr = RelationResult() ) : RelationResult {
val rr = RelationResult()
rr.result = client.request("/api/v1/accounts/relationships?id=${who.id}") rr.result = client.request("/api/v1/accounts/relationships?id=${who.id}")
val r2 = rr.result val r2 = rr.result
val jsonArray = r2?.jsonArray val jsonArray = r2?.jsonArray
@ -154,7 +158,6 @@ internal fun loadRelation1Mastodon(
return rr return rr
} }
// 別アカ操作と別タンスの関係 // 別アカ操作と別タンスの関係
const val NOT_CROSS_ACCOUNT = 1 const val NOT_CROSS_ACCOUNT = 1
const val CROSS_ACCOUNT_SAME_INSTANCE = 2 const val CROSS_ACCOUNT_SAME_INSTANCE = 2

View File

@ -12,6 +12,7 @@ import jp.juggler.subwaytooter.dialog.ReportForm
import jp.juggler.subwaytooter.table.AcctColor import jp.juggler.subwaytooter.table.AcctColor
import jp.juggler.subwaytooter.table.SavedAccount import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.table.UserRelation import jp.juggler.subwaytooter.table.UserRelation
import jp.juggler.subwaytooter.table.UserRelationMisskey
import jp.juggler.subwaytooter.util.* import jp.juggler.subwaytooter.util.*
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody import okhttp3.RequestBody
@ -37,25 +38,31 @@ object Action_User {
var relation : UserRelation? = null var relation : UserRelation? = null
override fun background(client : TootApiClient) : TootApiResult? { override fun background(client : TootApiClient) : TootApiResult? {
if(access_info.isMisskey){ if(access_info.isMisskey) {
val params = access_info.putMisskeyApiToken(JSONObject()) val params = access_info.putMisskeyApiToken(JSONObject())
.put("userId",who.id.toString()) .put("userId", who.id.toString())
val result = client.request(when(bMute){ val result = client.request(
true-> "/api/mute/create" when(bMute) {
else->"/api/mute/delete" true -> "/api/mute/create"
},params.toPostRequestBuilder()) else -> "/api/mute/delete"
if( result?.jsonObject != null ){ }, params.toPostRequestBuilder()
)
if(result?.jsonObject != null) {
// 204 no content // 204 no content
// update user relation // update user relation
val ur = UserRelation.load(access_info.db_id,who.id) val ur = UserRelation.load(access_info.db_id, who.id)
ur.muting = bMute ur.muting = bMute
saveUserRelationMisskey(access_info,who.id,TootParser(activity,access_info)) saveUserRelationMisskey(
access_info,
who.id,
TootParser(activity, access_info)
)
this.relation = ur this.relation = ur
} }
return result return result
}else{ } else {
val request_builder = Request.Builder().post( val request_builder = Request.Builder().post(
if(! bMute) if(! bMute)
RequestBody.create(TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED, "") RequestBody.create(TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED, "")
@ -126,7 +133,10 @@ object Action_User {
// ユーザをブロック/ブロック解除する // ユーザをブロック/ブロック解除する
fun block( fun block(
activity : ActMain, access_info : SavedAccount, who : TootAccount, bBlock : Boolean activity : ActMain,
access_info : SavedAccount,
who : TootAccount,
bBlock : Boolean
) { ) {
if(access_info.isMe(who)) { if(access_info.isMe(who)) {
showToast(activity, false, R.string.it_is_you) showToast(activity, false, R.string.it_is_you)
@ -138,23 +148,52 @@ object Action_User {
var relation : UserRelation? = null var relation : UserRelation? = null
override fun background(client : TootApiClient) : TootApiResult? { override fun background(client : TootApiClient) : TootApiResult? {
val request_builder = Request.Builder().post( if(access_info.isMisskey) {
RequestBody.create( val params = access_info.putMisskeyApiToken()
TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED, "" // 空データ .put("userId", who.id)
val result = client.request(
if(bBlock)
"/api/blocking/create"
else
"/api/blocking/delete",
params.toPostRequestBuilder()
) )
)
if(result?.jsonObject != null) {
val result = client.request( // ユーザ情報があるがリレーションが含まれないっぽい
"/api/v1/accounts/" + who.id + if(bBlock) "/block" else "/unblock",
request_builder // update user relation
) val ur = UserRelation.load(access_info.db_id, who.id)
val jsonObject = result?.jsonObject ur.blocking = bBlock
if(jsonObject != null) { saveUserRelationMisskey(
relation = access_info,
saveUserRelation(access_info, parseItem(::TootRelationShip, jsonObject)) who.id,
TootParser(activity, access_info)
)
this.relation = ur
}
return result
} else {
val request_builder = Request.Builder().post(
RequestBody.create(
TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED, "" // 空データ
)
)
val result = client.request(
"/api/v1/accounts/" + who.id + if(bBlock) "/block" else "/unblock",
request_builder
)
val jsonObject = result?.jsonObject
if(jsonObject != null) {
relation =
saveUserRelation(access_info, parseItem(::TootRelationShip, jsonObject))
}
return result
} }
return result
} }
override fun handleResult(result : TootApiResult?) { override fun handleResult(result : TootApiResult?) {
@ -170,24 +209,38 @@ object Action_User {
return return
} }
for(column in App1.getAppState(activity).column_list) { for(column in App1.getAppState(activity).column_list) {
if(relation.blocking) { when {
if(column.column_type == Column.TYPE_PROFILE) {
// プロフページのトゥートはブロックしてても見れる //ブロック解除したら「ブロックしたユーザ」カラムのリストから消える
continue ! relation.blocking -> column.removeUser(
} else { access_info,
column.removeAccountInTimeline(access_info, who.id) Column.TYPE_BLOCKS,
who.id
)
// Misskeyのブロックはフォロー解除とフォロー拒否だけなので
// カラム中の投稿を消すなどの効果はない
access_info.isMisskey -> {
} }
} else {
//「ブロックしたユーザ」カラムのリストから消える // プロフページのトゥートはブロックしてても見れる
column.removeUser(access_info, Column.TYPE_BLOCKS, who.id) column.column_type == Column.TYPE_PROFILE -> {
}
// MastodonではブロックしたらTLからそのアカウントの投稿が消える
else -> column.removeAccountInTimeline(access_info, who.id)
} }
} }
showToast( showToast(
activity, activity,
false, false,
if(relation.blocking) R.string.block_succeeded else R.string.unblock_succeeded if(relation.blocking)
R.string.block_succeeded
else
R.string.unblock_succeeded
) )
} else { } else {
@ -206,7 +259,12 @@ object Action_User {
who : TootAccount who : TootAccount
) { ) {
when { when {
access_info.isMisskey -> activity.addColumn(pos, access_info, Column.TYPE_PROFILE, who.id) access_info.isMisskey -> activity.addColumn(
pos,
access_info,
Column.TYPE_PROFILE,
who.id
)
access_info.isPseudo -> profileFromAnotherAccount(activity, pos, access_info, who) access_info.isPseudo -> profileFromAnotherAccount(activity, pos, access_info, who)
else -> activity.addColumn(pos, access_info, Column.TYPE_PROFILE, who.id) else -> activity.addColumn(pos, access_info, Column.TYPE_PROFILE, who.id)
} }
@ -223,7 +281,7 @@ object Action_User {
var who_local : TootAccount? = null var who_local : TootAccount? = null
override fun background(client : TootApiClient) : TootApiResult? { override fun background(client : TootApiClient) : TootApiResult? {
val result = client.syncAccountByUrl(access_info,who_url) val result = client.syncAccountByUrl(access_info, who_url)
who_local = result?.data as? TootAccount who_local = result?.data as? TootAccount
return result return result
} }
@ -422,7 +480,8 @@ object Action_User {
client.request("/api/v1/accounts/" + who.id + "/follow", request_builder) client.request("/api/v1/accounts/" + who.id + "/follow", request_builder)
val jsonObject = result?.jsonObject val jsonObject = result?.jsonObject
if(jsonObject != null) { if(jsonObject != null) {
relation =saveUserRelation(access_info,parseItem(::TootRelationShip, jsonObject)) relation =
saveUserRelation(access_info, parseItem(::TootRelationShip, jsonObject))
} }
return result return result
} }
@ -482,23 +541,23 @@ object Action_User {
activity : ActMain, activity : ActMain,
access_info : SavedAccount, access_info : SavedAccount,
who : TootAccount, who : TootAccount,
bConfirmed :Boolean = false bConfirmed : Boolean = false
) { ) {
if(!bConfirmed){ if(! bConfirmed) {
val name = who.decodeDisplayName(activity) val name = who.decodeDisplayName(activity)
AlertDialog.Builder(activity) AlertDialog.Builder(activity)
.setMessage( name.intoStringResource(activity,R.string.delete_succeeded_confirm)) .setMessage(name.intoStringResource(activity, R.string.delete_succeeded_confirm))
.setNegativeButton(R.string.cancel,null) .setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.ok){ _ , _ -> .setPositiveButton(R.string.ok) { _, _ ->
deleteSuggestion(activity,access_info,who,bConfirmed=true) deleteSuggestion(activity, access_info, who, bConfirmed = true)
} }
.show() .show()
return return
} }
TootTaskRunner(activity).run(access_info, object : TootTask { TootTaskRunner(activity).run(access_info, object : TootTask {
override fun background(client : TootApiClient) : TootApiResult? { override fun background(client : TootApiClient) : TootApiResult? {
return client.request("/api/v1/suggestions/${who.id}",Request.Builder().delete()) return client.request("/api/v1/suggestions/${who.id}", Request.Builder().delete())
} }
override fun handleResult(result : TootApiResult?) { override fun handleResult(result : TootApiResult?) {
@ -507,16 +566,16 @@ object Action_User {
// error // error
val error = result.error val error = result.error
if( error != null ){ if(error != null) {
showToast(activity,true,result.error) showToast(activity, true, result.error)
return return
} }
showToast(activity,false,R.string.delete_succeeded) showToast(activity, false, R.string.delete_succeeded)
// update suggestion column // update suggestion column
for( column in activity.app_state.column_list){ for(column in activity.app_state.column_list) {
column.removeUser(access_info,Column.TYPE_FOLLOW_SUGGESTION,who.id) column.removeUser(access_info, Column.TYPE_FOLLOW_SUGGESTION, who.id)
} }
} }
}) })

View File

@ -16,32 +16,32 @@ import java.util.regex.Pattern
open class TootAccount(parser : TootParser, src : JSONObject) { open class TootAccount(parser : TootParser, src : JSONObject) {
class Field( class Field(
val name :String, val name : String,
val value :String, val value : String,
val verified_at: Long // 0L if not verified val verified_at : Long // 0L if not verified
) )
companion object { companion object {
private val log = LogCategory("TootAccount") private val log = LogCategory("TootAccount")
internal val reWhitespace:Pattern = Pattern.compile("[\\s\\t\\x0d\\x0a]+") internal val reWhitespace : Pattern = Pattern.compile("[\\s\\t\\x0d\\x0a]+")
// host, user ,(instance) // host, user ,(instance)
internal val reAccountUrl :Pattern = internal val reAccountUrl : Pattern =
Pattern.compile("\\Ahttps://([A-Za-z0-9._-]+)/@([A-Za-z0-9_]+(?:@[A-Za-z0-9._-]+)?)(?:\\z|[?#])") Pattern.compile("\\Ahttps://([A-Za-z0-9._-]+)/@([A-Za-z0-9_]+(?:@[A-Za-z0-9._-]+)?)(?:\\z|[?#])")
fun getAcctFromUrl(url:String):String?{ fun getAcctFromUrl(url : String) : String? {
val m = reAccountUrl.matcher(url) val m = reAccountUrl.matcher(url)
return if(m.find()){ return if(m.find()) {
val host = m.group(1) val host = m.group(1)
val user = m.group(2).unescapeUri() val user = m.group(2).unescapeUri()
val instance = m.groupOrNull(3)?.unescapeUri() val instance = m.groupOrNull(3)?.unescapeUri()
if( instance?.isNotEmpty() == true){ if(instance?.isNotEmpty() == true) {
"$user@$instance" "$user@$instance"
}else{ } else {
"$user@$host" "$user@$host"
} }
}else{ } else {
null null
} }
} }
@ -79,7 +79,7 @@ open class TootAccount(parser : TootParser, src : JSONObject) {
try { try {
// たぶんどんなURLでもauthorityの部分にホスト名が来るだろう(慢心) // たぶんどんなURLでもauthorityの部分にホスト名が来るだろう(慢心)
val host = Uri.parse(url).authority val host = Uri.parse(url).authority
if( host?.isNotEmpty() == true){ if(host?.isNotEmpty() == true) {
return host.toLowerCase() return host.toLowerCase()
} }
log.e("findHostFromUrl: can't parse host from URL $url") log.e("findHostFromUrl: can't parse host from URL $url")
@ -99,11 +99,11 @@ open class TootAccount(parser : TootParser, src : JSONObject) {
val name = item.parseString("name") ?: continue val name = item.parseString("name") ?: continue
val value = item.parseString("value") ?: continue val value = item.parseString("value") ?: continue
val svVerifiedAt = item.parseString("verified_at") val svVerifiedAt = item.parseString("verified_at")
val verifiedAt = when(svVerifiedAt){ val verifiedAt = when(svVerifiedAt) {
null -> 0L null -> 0L
else-> TootStatus.parseTime(svVerifiedAt) else -> TootStatus.parseTime(svVerifiedAt)
} }
dst.add( Field(name,value,verifiedAt)) dst.add(Field(name, value, verifiedAt))
} }
return if(dst.isEmpty()) { return if(dst.isEmpty()) {
null null
@ -113,7 +113,6 @@ open class TootAccount(parser : TootParser, src : JSONObject) {
} }
} }
//URL of the user's profile page (can be remote) //URL of the user's profile page (can be remote)
// https://mastodon.juggler.jp/@tateisu // https://mastodon.juggler.jp/@tateisu
// 疑似アカウントではnullになります // 疑似アカウントではnullになります
@ -250,7 +249,16 @@ open class TootAccount(parser : TootParser, src : JSONObject) {
} }
parser.misskeyUserRelationMap[id]=UserRelation.parseMisskeyUser(src) // プロフカラムで ユーザのプロフ(A)とアカウントTL(B)を順に取得すると
// (A)ではisBlockingに情報が入っているが、(B)では情報が入っていない
// 対策として(A)でリレーションを取得済みのユーザは(B)のタイミングではリレーションを読み捨てる
val map = parser.misskeyUserRelationMap
if(map[id] == null) {
val relation = UserRelation.parseMisskeyUser(src)
if( relation != null) {
map[id] = relation
}
}
} else { } else {
@ -335,7 +343,7 @@ open class TootAccount(parser : TootParser, src : JSONObject) {
} }
ServiceType.MSP -> { ServiceType.MSP -> {
this.id = EntityId.mayDefault(src.parseLong("id") ) this.id = EntityId.mayDefault(src.parseLong("id"))
// MSPはLTLの情報しか持ってないのでacctは常にホスト名部分を持たない // MSPはLTLの情報しか持ってないのでacctは常にホスト名部分を持たない
this.host = findHostFromUrl(null, null, url) this.host = findHostFromUrl(null, null, url)
@ -400,9 +408,8 @@ open class TootAccount(parser : TootParser, src : JSONObject) {
).decodeEmoji(sv) ).decodeEmoji(sv)
} }
var _orderId : EntityId? = null var _orderId : EntityId? = null
fun getOrderId() :EntityId = _orderId ?: id fun getOrderId() : EntityId = _orderId ?: id
} }

View File

@ -35,6 +35,9 @@ class TootRelationShip(src : JSONObject) {
val endorsed : Boolean val endorsed : Boolean
init { init {
// Misskey はこのEntityを使わない
this.id = EntityId.mayDefault( src.parseLong("id") ) this.id = EntityId.mayDefault( src.parseLong("id") )
var ov = src.opt("following") var ov = src.opt("following")

View File

@ -11,6 +11,8 @@ import android.text.Spannable
import android.text.SpannableStringBuilder import android.text.SpannableStringBuilder
import android.text.style.BackgroundColorSpan import android.text.style.BackgroundColorSpan
import android.text.style.ForegroundColorSpan import android.text.style.ForegroundColorSpan
import jp.juggler.subwaytooter.util.getIntOrNull
import jp.juggler.subwaytooter.util.getStringOrNull
import jp.juggler.subwaytooter.util.sanitizeBDI import jp.juggler.subwaytooter.util.sanitizeBDI
import java.util.Locale import java.util.Locale
@ -80,7 +82,7 @@ class AcctColor {
private const val CHAR_REPLACE : Char = 0x328A.toChar() private const val CHAR_REPLACE : Char = 0x328A.toChar()
private const val load_where = COL_ACCT + "=?" private const val load_where = "$COL_ACCT=?"
private val load_where_arg = object : ThreadLocal<Array<String?>>() { private val load_where_arg = object : ThreadLocal<Array<String?>>() {
override fun initialValue() : Array<String?> { override fun initialValue() : Array<String?> {
@ -138,22 +140,13 @@ class AcctColor {
App1.database.query(table, null, load_where, where_arg, null, null, null) App1.database.query(table, null, load_where, where_arg, null, null, null)
.use { cursor -> .use { cursor ->
if(cursor.moveToNext()) { if(cursor.moveToNext()) {
var idx : Int
val ac = AcctColor(acct) val ac = AcctColor(acct)
idx = cursor.getColumnIndex(COL_COLOR_FG) ac.color_fg = cursor.getIntOrNull(COL_COLOR_FG) ?: 0
ac.color_fg = if(cursor.isNull(idx)) 0 else cursor.getInt(idx) ac.color_bg = cursor.getIntOrNull(COL_COLOR_BG) ?: 0
ac.nickname = cursor.getStringOrNull(COL_NICKNAME)
idx = cursor.getColumnIndex(COL_COLOR_BG) ac.notification_sound = cursor.getStringOrNull(COL_NOTIFICATION_SOUND)
ac.color_bg = if(cursor.isNull(idx)) 0 else cursor.getInt(idx)
idx = cursor.getColumnIndex(COL_NICKNAME)
ac.nickname = if(cursor.isNull(idx)) null else cursor.getString(idx)
idx = cursor.getColumnIndex(COL_NOTIFICATION_SOUND)
ac.notification_sound =
if(cursor.isNull(idx)) null else cursor.getString(idx)
mMemoryCache.put(acct, ac) mMemoryCache.put(acct, ac)
return ac return ac

View File

@ -2,12 +2,11 @@ package jp.juggler.subwaytooter.table
import android.content.ContentValues import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import org.json.JSONObject
import jp.juggler.subwaytooter.App1 import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.util.LogCategory import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.getString
import jp.juggler.subwaytooter.util.toJsonObject import jp.juggler.subwaytooter.util.toJsonObject
import org.json.JSONObject
object ClientInfo :TableCompanion { object ClientInfo :TableCompanion {
private val log = LogCategory("ClientInfo") private val log = LogCategory("ClientInfo")
@ -42,7 +41,7 @@ object ClientInfo :TableCompanion {
App1.database.query(table, null, "h=? and cn=?", arrayOf(instance, client_name), null, null, null) App1.database.query(table, null, "h=? and cn=?", arrayOf(instance, client_name), null, null, null)
.use { cursor -> .use { cursor ->
if(cursor.moveToFirst()) { if(cursor.moveToFirst()) {
return cursor.getString(cursor.getColumnIndex(COL_RESULT)).toJsonObject() return cursor.getString(COL_RESULT).toJsonObject()
} }
} }
} catch(ex : Throwable) { } catch(ex : Throwable) {

View File

@ -7,6 +7,7 @@ import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.api.entity.EntityIdString import jp.juggler.subwaytooter.api.entity.EntityIdString
import jp.juggler.subwaytooter.api.entity.TootStatus import jp.juggler.subwaytooter.api.entity.TootStatus
import jp.juggler.subwaytooter.util.LogCategory import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.getInt
object ContentWarning : TableCompanion { object ContentWarning : TableCompanion {
private val log = LogCategory("ContentWarning") private val log = LogCategory("ContentWarning")
@ -66,7 +67,7 @@ object ContentWarning : TableCompanion {
null null
).use { cursor -> ).use { cursor ->
if(cursor.moveToFirst()) { if(cursor.moveToFirst()) {
val iv = cursor.getInt(cursor.getColumnIndex(COL_SHOWN)) val iv = cursor.getInt(COL_SHOWN)
return 0 != iv return 0 != iv
} }

View File

@ -6,6 +6,7 @@ import android.database.sqlite.SQLiteDatabase
import jp.juggler.subwaytooter.App1 import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.api.entity.TootStatus import jp.juggler.subwaytooter.api.entity.TootStatus
import jp.juggler.subwaytooter.util.LogCategory import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.getInt
object ContentWarningMisskey : TableCompanion { object ContentWarningMisskey : TableCompanion {
private val log = LogCategory("ContentWarningMisskey") private val log = LogCategory("ContentWarningMisskey")
@ -58,7 +59,7 @@ object ContentWarningMisskey : TableCompanion {
null null
).use { cursor -> ).use { cursor ->
if(cursor.moveToFirst()) { if(cursor.moveToFirst()) {
val iv = cursor.getInt(cursor.getColumnIndex(COL_SHOWN)) val iv = cursor.getInt(COL_SHOWN)
return 0 != iv return 0 != iv
} }

View File

@ -131,14 +131,12 @@ class HighlightWord {
} }
constructor(cursor : Cursor) { constructor(cursor : Cursor) {
this.id = cursor.getLong(cursor.getColumnIndex(COL_ID)) this.id = cursor.getLong(COL_ID)
this.name = cursor.getString(cursor.getColumnIndex(COL_NAME)) this.name = cursor.getString(COL_NAME)
this.color_bg = cursor.getInt(cursor.getColumnIndex(COL_COLOR_BG)) this.color_bg = cursor.getInt(COL_COLOR_BG)
this.color_fg = cursor.getInt(cursor.getColumnIndex(COL_COLOR_FG)) this.color_fg = cursor.getInt(COL_COLOR_FG)
this.sound_type = cursor.getInt(cursor.getColumnIndex(COL_SOUND_TYPE)) this.sound_type = cursor.getInt(COL_SOUND_TYPE)
val colIdx_sound_uri = cursor.getColumnIndex(COL_SOUND_URI) this.sound_uri = cursor.getStringOrNull(COL_SOUND_URI)
this.sound_uri =
if(cursor.isNull(colIdx_sound_uri)) null else cursor.getString(colIdx_sound_uri)
} }
fun save() { fun save() {

View File

@ -7,6 +7,7 @@ import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.api.entity.EntityIdString import jp.juggler.subwaytooter.api.entity.EntityIdString
import jp.juggler.subwaytooter.api.entity.TootStatus import jp.juggler.subwaytooter.api.entity.TootStatus
import jp.juggler.subwaytooter.util.LogCategory import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.getInt
object MediaShown : TableCompanion { object MediaShown : TableCompanion {
private val log = LogCategory("MediaShown") private val log = LogCategory("MediaShown")
@ -65,7 +66,7 @@ object MediaShown : TableCompanion {
null null
).use { cursor -> ).use { cursor ->
if(cursor.moveToFirst()) { if(cursor.moveToFirst()) {
return 0 != cursor.getInt(cursor.getColumnIndex(COL_SHOWN)) return 0 != cursor.getInt(COL_SHOWN)
} }
} }
} catch(ex : Throwable) { } catch(ex : Throwable) {

View File

@ -6,6 +6,7 @@ import android.database.sqlite.SQLiteDatabase
import jp.juggler.subwaytooter.App1 import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.api.entity.TootStatus import jp.juggler.subwaytooter.api.entity.TootStatus
import jp.juggler.subwaytooter.util.LogCategory import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.getInt
object MediaShownMisskey : TableCompanion { object MediaShownMisskey : TableCompanion {
private val log = LogCategory("MediaShownMisskey") private val log = LogCategory("MediaShownMisskey")
@ -58,7 +59,7 @@ object MediaShownMisskey : TableCompanion {
null null
).use { cursor -> ).use { cursor ->
if(cursor.moveToFirst()) { if(cursor.moveToFirst()) {
return 0 != cursor.getInt(cursor.getColumnIndex(COL_SHOWN)) return 0 != cursor.getInt(COL_SHOWN)
} }
} }

View File

@ -9,6 +9,7 @@ import jp.juggler.subwaytooter.api.entity.EntityId
import jp.juggler.subwaytooter.api.entity.putMayNull import jp.juggler.subwaytooter.api.entity.putMayNull
import jp.juggler.subwaytooter.util.LogCategory import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.getStringOrNull import jp.juggler.subwaytooter.util.getStringOrNull
import jp.juggler.subwaytooter.util.getLong
class NotificationTracking { class NotificationTracking {
@ -160,13 +161,13 @@ class NotificationTracking {
null null
)?.use { cursor -> )?.use { cursor ->
if(cursor.moveToFirst()) { if(cursor.moveToFirst()) {
dst.id = cursor.getLong(cursor.getColumnIndex(COL_ID)) dst.id = cursor.getLong(COL_ID)
dst.last_load = cursor.getLong(cursor.getColumnIndex(COL_LAST_LOAD)) dst.last_load = cursor.getLong(COL_LAST_LOAD)
dst.nid_show = EntityId.from(cursor, COL_NID_SHOW) dst.nid_show = EntityId.from(cursor, COL_NID_SHOW)
dst.nid_read = EntityId.from(cursor, COL_NID_READ) dst.nid_read = EntityId.from(cursor, COL_NID_READ)
dst.post_id = EntityId.from(cursor, COL_POST_ID) dst.post_id = EntityId.from(cursor, COL_POST_ID)
dst.post_time = cursor.getLong(cursor.getColumnIndex(COL_POST_TIME)) dst.post_time = cursor.getLong(COL_POST_TIME)
dst.last_data = cursor.getStringOrNull(COL_LAST_DATA) dst.last_data = cursor.getStringOrNull(COL_LAST_DATA)

View File

@ -94,13 +94,13 @@ class SavedAccount(
} }
constructor(context : Context, cursor : Cursor) : this( constructor(context : Context, cursor : Cursor) : this(
cursor.getLong(cursor.getColumnIndex(COL_ID)), // db_id cursor.getLong(COL_ID), // db_id
cursor.getString(cursor.getColumnIndex(COL_USER)), // acct cursor.getString(COL_USER), // acct
cursor.getString(cursor.getColumnIndex(COL_HOST)) // host cursor.getString(COL_HOST) // host
, _isMisskey = cursor.getInt(cursor.getColumnIndex(COL_IS_MISSKEY)).i2b() , _isMisskey = cursor.getBoolean(COL_IS_MISSKEY)
) { ) {
val jsonAccount = cursor.getString(cursor.getColumnIndex(COL_ACCOUNT)).toJsonObject() val jsonAccount = cursor.getString(COL_ACCOUNT).toJsonObject()
if(jsonAccount.opt("id") == null) { if(jsonAccount.opt("id") == null) {
// 疑似アカウント // 疑似アカウント
this.loginAccount = null this.loginAccount = null
@ -113,60 +113,45 @@ class SavedAccount(
if(loginAccount == null) { if(loginAccount == null) {
log.e( log.e(
"missing loginAccount for %s", "missing loginAccount for %s",
cursor.getString(cursor.getColumnIndex(COL_ACCOUNT)) cursor.getString(COL_ACCOUNT)
) )
} }
this.loginAccount = loginAccount this.loginAccount = loginAccount
} }
val colIdx_visibility = cursor.getColumnIndex(COL_VISIBILITY) val sv = cursor.getStringOrNull(COL_VISIBILITY)
val sv = if(cursor.isNull(colIdx_visibility)) null else cursor.getString(colIdx_visibility)
this.visibility = TootVisibility.parseSavedVisibility(sv) ?: TootVisibility.Public this.visibility = TootVisibility.parseSavedVisibility(sv) ?: TootVisibility.Public
this.confirm_boost = cursor.getInt(cursor.getColumnIndex(COL_CONFIRM_BOOST)).i2b() this.confirm_boost = cursor.getBoolean(COL_CONFIRM_BOOST)
this.confirm_favourite = cursor.getInt(cursor.getColumnIndex(COL_CONFIRM_FAVOURITE)).i2b() this.confirm_favourite = cursor.getBoolean(COL_CONFIRM_FAVOURITE)
this.confirm_unboost = cursor.getInt(cursor.getColumnIndex(COL_CONFIRM_UNBOOST)).i2b() this.confirm_unboost = cursor.getBoolean(COL_CONFIRM_UNBOOST)
this.confirm_unfavourite = this.confirm_unfavourite = cursor.getBoolean(COL_CONFIRM_UNFAVOURITE)
cursor.getInt(cursor.getColumnIndex(COL_CONFIRM_UNFAVOURITE)).i2b() this.confirm_follow = cursor.getBoolean(COL_CONFIRM_FOLLOW)
this.confirm_follow_locked = cursor.getBoolean(COL_CONFIRM_FOLLOW_LOCKED)
this.confirm_unfollow = cursor.getBoolean(COL_CONFIRM_UNFOLLOW)
this.confirm_post = cursor.getBoolean(COL_CONFIRM_POST)
this.dont_hide_nsfw = cursor.getInt(cursor.getColumnIndex(COL_DONT_HIDE_NSFW)).i2b() this.notification_mention = cursor.getBoolean(COL_NOTIFICATION_MENTION)
this.dont_show_timeout = cursor.getInt(cursor.getColumnIndex(COL_DONT_SHOW_TIMEOUT)).i2b() this.notification_boost = cursor.getBoolean(COL_NOTIFICATION_BOOST)
this.notification_favourite = cursor.getBoolean(COL_NOTIFICATION_FAVOURITE)
this.notification_follow = cursor.getBoolean(COL_NOTIFICATION_FOLLOW)
this.notification_reaction = cursor.getBoolean(COL_NOTIFICATION_REACTION)
this.notification_vote = cursor.getBoolean(COL_NOTIFICATION_VOTE)
this.dont_hide_nsfw = cursor.getBoolean(COL_DONT_HIDE_NSFW)
this.dont_show_timeout = cursor.getBoolean(COL_DONT_SHOW_TIMEOUT)
this.notification_mention = this.notification_tag = cursor.getStringOrNull(COL_NOTIFICATION_TAG)
cursor.getInt(cursor.getColumnIndex(COL_NOTIFICATION_MENTION)).i2b()
this.notification_boost = cursor.getInt(cursor.getColumnIndex(COL_NOTIFICATION_BOOST)).i2b()
this.notification_favourite =
cursor.getInt(cursor.getColumnIndex(COL_NOTIFICATION_FAVOURITE)).i2b()
this.notification_follow =
cursor.getInt(cursor.getColumnIndex(COL_NOTIFICATION_FOLLOW)).i2b()
this.notification_reaction = this.register_key = cursor.getStringOrNull(COL_REGISTER_KEY)
cursor.getInt(cursor.getColumnIndex(COL_NOTIFICATION_REACTION)).i2b()
this.notification_vote = this.register_time = cursor.getLong(COL_REGISTER_TIME)
cursor.getInt(cursor.getColumnIndex(COL_NOTIFICATION_VOTE)).i2b()
this.confirm_follow = cursor.getInt(cursor.getColumnIndex(COL_CONFIRM_FOLLOW)).i2b() this.token_info = cursor.getString(COL_TOKEN).toJsonObject()
this.confirm_follow_locked =
cursor.getInt(cursor.getColumnIndex(COL_CONFIRM_FOLLOW_LOCKED)).i2b()
this.confirm_unfollow = cursor.getInt(cursor.getColumnIndex(COL_CONFIRM_UNFOLLOW)).i2b()
this.confirm_post = cursor.getInt(cursor.getColumnIndex(COL_CONFIRM_POST)).i2b()
val idx_notification_tag = cursor.getColumnIndex(COL_NOTIFICATION_TAG) this.sound_uri = cursor.getString(COL_SOUND_URI)
this.notification_tag =
if(cursor.isNull(idx_notification_tag)) null else cursor.getString(idx_notification_tag)
val idx_register_key = cursor.getColumnIndex(COL_REGISTER_KEY) this.default_text = cursor.getStringOrNull(COL_DEFAULT_TEXT) ?: ""
this.register_key =
if(cursor.isNull(idx_register_key)) null else cursor.getString(idx_register_key)
this.register_time = cursor.getLong(cursor.getColumnIndex(COL_REGISTER_TIME))
this.token_info = cursor.getString(cursor.getColumnIndex(COL_TOKEN)).toJsonObject()
this.sound_uri = cursor.getString(cursor.getColumnIndex(COL_SOUND_URI))
this.default_text = cursor.getString(cursor.getColumnIndex(COL_DEFAULT_TEXT)) ?: ""
} }
@ -739,7 +724,7 @@ class SavedAccount(
} catch(ex : Throwable) { } catch(ex : Throwable) {
log.trace(ex) log.trace(ex)
log.e(ex, "loadAccountList failed.") log.e(ex, "loadAccountList failed.")
showToast(context,true,ex.withCaption("(SubwayTooter) broken in-app database?")) showToast(context, true, ex.withCaption("(SubwayTooter) broken in-app database?"))
} }
return result return result
@ -910,7 +895,7 @@ class SavedAccount(
val misskeyApiToken : String? val misskeyApiToken : String?
get() = token_info?.parseString(TootApiClient.KEY_API_KEY_MISSKEY) get() = token_info?.parseString(TootApiClient.KEY_API_KEY_MISSKEY)
fun putMisskeyApiToken(params : JSONObject =JSONObject()) : JSONObject { fun putMisskeyApiToken(params : JSONObject = JSONObject()) : JSONObject {
val apiKey = misskeyApiToken val apiKey = misskeyApiToken
if(apiKey?.isNotEmpty() == true) params.put("i", apiKey) if(apiKey?.isNotEmpty() == true) params.put("i", apiKey)
return params return params

View File

@ -6,6 +6,7 @@ import android.provider.BaseColumns
import jp.juggler.subwaytooter.App1 import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.util.LogCategory import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.getString
object SubscriptionServerKey : TableCompanion { object SubscriptionServerKey : TableCompanion {
@ -58,8 +59,7 @@ object SubscriptionServerKey : TableCompanion {
null null
)?.use { cursor -> )?.use { cursor ->
if(cursor.moveToNext()) { if(cursor.moveToNext()) {
val idx = cursor.getColumnIndex(COL_SERVER_KEY) return cursor.getString(COL_SERVER_KEY)
return cursor.getString(idx)
} }
} }
} catch(ex : Throwable) { } catch(ex : Throwable) {

View File

@ -1,5 +1,6 @@
package jp.juggler.subwaytooter.table package jp.juggler.subwaytooter.table
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
// SQLite にBooleanをそのまま保存することはできないのでInt型との変換が必要になる // SQLite にBooleanをそのまま保存することはできないのでInt型との変換が必要になる
@ -11,6 +12,12 @@ fun Boolean.b2i() = if(this) 1 else 0
fun Int.i2b() = this!=0 fun Int.i2b() = this!=0
fun Cursor.getBoolean(keyIdx:Int) =
getInt(keyIdx).i2b()
fun Cursor.getBoolean(key:String) =
getBoolean(getColumnIndex(key))
interface TableCompanion{ interface TableCompanion{
fun onDBCreate(db : SQLiteDatabase) fun onDBCreate(db : SQLiteDatabase)

View File

@ -3,22 +3,24 @@ package jp.juggler.subwaytooter.table
import android.content.ContentValues import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import android.support.v4.util.LruCache import android.support.v4.util.LruCache
import jp.juggler.subwaytooter.App1 import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.api.entity.EntityId import jp.juggler.subwaytooter.api.entity.EntityId
import jp.juggler.subwaytooter.api.entity.EntityIdString import jp.juggler.subwaytooter.api.entity.EntityIdString
import jp.juggler.subwaytooter.api.entity.TootAccount import jp.juggler.subwaytooter.api.entity.TootAccount
import jp.juggler.subwaytooter.api.entity.TootRelationShip import jp.juggler.subwaytooter.api.entity.TootRelationShip
import jp.juggler.subwaytooter.util.LogCategory import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.getInt
import org.json.JSONObject import org.json.JSONObject
class UserRelation { class UserRelation {
var following : Boolean = false // 認証ユーザからのフォロー状態にある var following : Boolean = false // 認証ユーザからのフォロー状態にある
var followed_by : Boolean = false // 認証ユーザは被フォロー状態にある var followed_by : Boolean = false // 認証ユーザは被フォロー状態にある
var blocking : Boolean = false var blocking : Boolean = false // 認証ユーザからブロックした
var blocked_by : Boolean = false // 認証ユーザからブロックされた(Misskeyのみ。Mastodonでは常にfalse)
var muting : Boolean = false var muting : Boolean = false
var requested : Boolean = false // 認証ユーザからのフォローは申請中である var requested : Boolean = false // 認証ユーザからのフォローは申請中である
var requested_by : Boolean = false // 相手から認証ユーザへのフォローリクエスト申請中(Misskeyのみ。Mastodonでは常にfalse)
var following_reblogs : Int = 0 // このユーザからのブーストをTLに表示する var following_reblogs : Int = 0 // このユーザからのブーストをTLに表示する
var endorsed : Boolean = false // ユーザをプロフィールで紹介する var endorsed : Boolean = false // ユーザをプロフィールで紹介する
@ -221,14 +223,13 @@ class UserRelation {
.use { cursor -> .use { cursor ->
if(cursor.moveToNext()) { if(cursor.moveToNext()) {
val dst = UserRelation() val dst = UserRelation()
dst.following = cursor.getInt(cursor.getColumnIndex(COL_FOLLOWING)).i2b() dst.following = cursor.getBoolean(COL_FOLLOWING)
dst.followed_by =cursor.getInt(cursor.getColumnIndex(COL_FOLLOWED_BY)).i2b() dst.followed_by =cursor.getBoolean(COL_FOLLOWED_BY)
dst.blocking = cursor.getInt(cursor.getColumnIndex(COL_BLOCKING)).i2b() dst.blocking = cursor.getBoolean(COL_BLOCKING)
dst.muting = cursor.getInt(cursor.getColumnIndex(COL_MUTING)).i2b() dst.muting = cursor.getBoolean(COL_MUTING)
dst.requested = cursor.getInt(cursor.getColumnIndex(COL_REQUESTED)).i2b() dst.requested = cursor.getBoolean(COL_REQUESTED)
dst.following_reblogs = dst.following_reblogs = cursor.getInt(COL_FOLLOWING_REBLOGS)
cursor.getInt(cursor.getColumnIndex(COL_FOLLOWING_REBLOGS)) dst.endorsed = cursor.getBoolean(COL_ENDORSED)
dst.endorsed = cursor.getInt(cursor.getColumnIndex(COL_ENDORSED)).i2b()
return dst return dst
} }
} }
@ -240,13 +241,21 @@ class UserRelation {
} }
// Misskey用 // Misskey用
fun parseMisskeyUser(src : JSONObject) = UserRelation().apply { fun parseMisskeyUser(src : JSONObject) :UserRelation? {
following = src.optBoolean("isFollowing")
followed_by = src.optBoolean("isFollowed") // リレーションを返さない場合がある
muting = src.optBoolean("isMuted") src.opt("isFollowing") ?: return null
blocking = false
endorsed = false return UserRelation().apply {
requested = src.optBoolean("hasPendingFollowRequestFromYou") following = src.optBoolean("isFollowing")
followed_by = src.optBoolean("isFollowed")
muting = src.optBoolean("isMuted")
blocking = src.optBoolean("isBlocking")
blocked_by = src.optBoolean("isBlocked")
endorsed = false
requested = src.optBoolean("hasPendingFollowRequestFromYou")
requested_by = src.optBoolean("hasPendingFollowRequestToYou")
}
} }
} }

View File

@ -5,6 +5,7 @@ import android.database.sqlite.SQLiteDatabase
import jp.juggler.subwaytooter.App1 import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.api.entity.EntityId import jp.juggler.subwaytooter.api.entity.EntityId
import jp.juggler.subwaytooter.util.LogCategory import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.getInt
object UserRelationMisskey : TableCompanion { object UserRelationMisskey : TableCompanion {
@ -21,6 +22,8 @@ object UserRelationMisskey : TableCompanion {
private const val COL_REQUESTED = "requested" private const val COL_REQUESTED = "requested"
private const val COL_FOLLOWING_REBLOGS = "following_reblogs" private const val COL_FOLLOWING_REBLOGS = "following_reblogs"
private const val COL_ENDORSED = "endorsed" private const val COL_ENDORSED = "endorsed"
private const val COL_BLOCKED_BY = "blocked_by"
private const val COL_REQUESTED_BY = "requested_by"
override fun onDBCreate(db : SQLiteDatabase) { override fun onDBCreate(db : SQLiteDatabase) {
log.d("onDBCreate!") log.d("onDBCreate!")
@ -38,6 +41,8 @@ object UserRelationMisskey : TableCompanion {
,$COL_REQUESTED integer not null ,$COL_REQUESTED integer not null
,$COL_FOLLOWING_REBLOGS integer not null ,$COL_FOLLOWING_REBLOGS integer not null
,$COL_ENDORSED integer default 0 ,$COL_ENDORSED integer default 0
,$COL_BLOCKED_BY integer default 0
,$COL_REQUESTED_BY integer default 0
)""" )"""
) )
db.execSQL( db.execSQL(
@ -60,9 +65,24 @@ object UserRelationMisskey : TableCompanion {
log.trace(ex) log.trace(ex)
} }
} }
if(oldVersion < 34 && newVersion >= 34) {
try {
db.execSQL("alter table $table add column $COL_BLOCKED_BY integer default 0")
} catch(ex : Throwable) {
log.trace(ex)
}
}
if(oldVersion < 35 && newVersion >= 35) {
try {
db.execSQL("alter table $table add column $COL_REQUESTED_BY integer default 0")
} catch(ex : Throwable) {
log.trace(ex)
}
}
} }
fun save1(now : Long, db_id : Long, whoId : String, src : UserRelation) { fun save1(now : Long, db_id : Long, whoId : String, src : UserRelation?) {
src?:return
try { try {
val cv = ContentValues() val cv = ContentValues()
cv.put(COL_TIME_SAVE, now) cv.put(COL_TIME_SAVE, now)
@ -75,6 +95,8 @@ object UserRelationMisskey : TableCompanion {
cv.put(COL_REQUESTED, src.requested.b2i()) cv.put(COL_REQUESTED, src.requested.b2i())
cv.put(COL_FOLLOWING_REBLOGS, src.following_reblogs) cv.put(COL_FOLLOWING_REBLOGS, src.following_reblogs)
cv.put(COL_ENDORSED, src.endorsed.b2i()) cv.put(COL_ENDORSED, src.endorsed.b2i())
cv.put(COL_BLOCKED_BY,src.blocked_by.b2i())
cv.put(COL_REQUESTED_BY,src.requested_by.b2i())
App1.database.replace(table, null, cv) App1.database.replace(table, null, cv)
val key = String.format("%s:%s", db_id, whoId) val key = String.format("%s:%s", db_id, whoId)
@ -113,6 +135,8 @@ object UserRelationMisskey : TableCompanion {
cv.put(COL_REQUESTED, src.requested.b2i()) cv.put(COL_REQUESTED, src.requested.b2i())
cv.put(COL_FOLLOWING_REBLOGS, src.following_reblogs) cv.put(COL_FOLLOWING_REBLOGS, src.following_reblogs)
cv.put(COL_ENDORSED, src.endorsed.b2i()) cv.put(COL_ENDORSED, src.endorsed.b2i())
cv.put(COL_BLOCKED_BY,src.blocked_by.b2i())
cv.put(COL_REQUESTED_BY,src.requested_by.b2i())
db.replace(table, null, cv) db.replace(table, null, cv)
} }
bOK = true bOK = true
@ -151,15 +175,15 @@ object UserRelationMisskey : TableCompanion {
.use { cursor -> .use { cursor ->
if(cursor.moveToNext()) { if(cursor.moveToNext()) {
val dst = UserRelation() val dst = UserRelation()
dst.following = cursor.getInt(cursor.getColumnIndex(COL_FOLLOWING)).i2b() dst.following = cursor.getBoolean(COL_FOLLOWING)
dst.followed_by = dst.followed_by = cursor.getBoolean(COL_FOLLOWED_BY)
cursor.getInt(cursor.getColumnIndex(COL_FOLLOWED_BY)).i2b() dst.blocking = cursor.getBoolean(COL_BLOCKING)
dst.blocking = cursor.getInt(cursor.getColumnIndex(COL_BLOCKING)).i2b() dst.muting = cursor.getBoolean(COL_MUTING)
dst.muting = cursor.getInt(cursor.getColumnIndex(COL_MUTING)).i2b() dst.requested = cursor.getBoolean(COL_REQUESTED)
dst.requested = cursor.getInt(cursor.getColumnIndex(COL_REQUESTED)).i2b() dst.following_reblogs = cursor.getInt(COL_FOLLOWING_REBLOGS)
dst.following_reblogs = dst.endorsed =cursor.getBoolean(COL_ENDORSED)
cursor.getInt(cursor.getColumnIndex(COL_FOLLOWING_REBLOGS)) dst.blocked_by = cursor.getBoolean(COL_BLOCKED_BY)
dst.endorsed = cursor.getInt(cursor.getColumnIndex(COL_ENDORSED)).i2b() dst.requested_by = cursor.getBoolean(COL_REQUESTED_BY)
return dst return dst
} }
} }

View File

@ -9,8 +9,7 @@ import android.os.Environment
import android.os.storage.StorageManager import android.os.storage.StorageManager
import android.webkit.MimeTypeMap import android.webkit.MimeTypeMap
import java.io.File import java.io.File
import java.util.ArrayList import java.util.*
import java.util.HashMap
object StorageUtils{ object StorageUtils{
@ -145,7 +144,7 @@ object StorageUtils{
val type = cursor.getType(i) val type = cursor.getType(i)
if(type != Cursor.FIELD_TYPE_STRING) continue if(type != Cursor.FIELD_TYPE_STRING) continue
val name = cursor.getColumnName(i) val name = cursor.getColumnName(i)
val value = if(cursor.isNull(i)) null else cursor.getString(i) val value = cursor.getStringOrNull(i)
if(value != null && value.isNotEmpty() && "filePath" == name) return File(value) if(value != null && value.isNotEmpty() && "filePath" == name) return File(value)
} }
} }

View File

@ -905,14 +905,35 @@ fun showToast(context : Context, ex : Throwable, string_id : Int, vararg args :
Utils.showToastImpl(context, true, ex.withCaption(context.resources, string_id, *args)) Utils.showToastImpl(context, true, ex.withCaption(context.resources, string_id, *args))
} }
fun Cursor.getStringOrNull(colName : String) : String? {
val colIdx = getColumnIndex(colName) fun Cursor.getInt(key:String) =
return if(isNull(colIdx)) { getInt(getColumnIndex(key))
null
} else { fun Cursor.getIntOrNull(idx:Int) =
getString(colIdx) if(isNull(idx)) null else getInt(idx)
}
} fun Cursor.getIntOrNull(key:String) =
getIntOrNull(getColumnIndex(key))
fun Cursor.getLong(key:String) =
getLong(getColumnIndex(key))
//fun Cursor.getLongOrNull(idx:Int) =
// if(isNull(idx)) null else getLong(idx)
//fun Cursor.getLongOrNull(key:String) =
// getLongOrNull(getColumnIndex(key))
fun Cursor.getString(key:String) :String =
getString(getColumnIndex(key))
fun Cursor.getStringOrNull(keyIdx:Int) =
if(isNull(keyIdx)) null else getString(keyIdx)
fun Cursor.getStringOrNull(key:String) =
getStringOrNull(getColumnIndex(key))
fun getDocumentName(contentResolver:ContentResolver,uri : Uri) : String { fun getDocumentName(contentResolver:ContentResolver,uri : Uri) : String {
val errorName = "no_name" val errorName = "no_name"
@ -921,12 +942,7 @@ fun getDocumentName(contentResolver:ContentResolver,uri : Uri) : String {
return if(! cursor.moveToFirst()) { return if(! cursor.moveToFirst()) {
errorName errorName
} else { } else {
val colIdx = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) cursor.getStringOrNull(OpenableColumns.DISPLAY_NAME) ?: errorName
if(cursor.isNull(colIdx)) {
errorName
} else {
cursor.getString(colIdx)
}
} }
} }
?: errorName ?: errorName

View File

@ -141,5 +141,8 @@ my $res_dir = "app/src/main/res";
# resize_scales( "_ArtWork/ic_medal.png" ,$res_dir,"drawable","ic_authorized",0,24); # resize_scales( "_ArtWork/ic_medal.png" ,$res_dir,"drawable","ic_authorized",0,24);
resize_scales( "../extra-SubwayTooter/_ArtWork/ic_unread.png" ,$res_dir,"drawable","ic_unread",0,24); #resize_scales( "../extra-SubwayTooter/_ArtWork/ic_unread.png" ,$res_dir,"drawable","ic_unread",0,24);
resize_scales( "../extra-SubwayTooter/_ArtWork/ic_blocked_by.png" ,$res_dir,"drawable","ic_blocked_by",0,32);
resize_scales( "../extra-SubwayTooter/_ArtWork/ic_requested_by.png" ,$res_dir,"drawable","ic_requested_by",0,32);