(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.view.View
import android.view.ViewGroup
import android.widget.AdapterView
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 android.widget.*
import com.jrummyapps.android.colorpicker.ColorPickerDialog
import com.jrummyapps.android.colorpicker.ColorPickerDialogListener
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.SavedAccount
import jp.juggler.subwaytooter.util.*
import org.json.JSONObject
import java.io.FileInputStream
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.handleGetContentResult
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.text.NumberFormat
import java.util.*
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
@ -868,11 +856,11 @@ class ActAppSetting : AppCompatActivity()
c = footer_button_fg_color
if(c == 0) {
Styler.setIconDefaultColor(this, ivFooterToot, R.attr.ic_edit)
Styler.setIconDefaultColor(this, ivFooterMenu, R.attr.ic_hamburger)
Styler.setIconAttr(this, ivFooterToot, R.attr.ic_edit)
Styler.setIconAttr(this, ivFooterMenu, R.attr.ic_hamburger)
} else {
Styler.setIconCustomColor(this, ivFooterToot, c, R.attr.ic_edit)
Styler.setIconCustomColor(this, ivFooterMenu, c, R.attr.ic_hamburger)
Styler.setIconAttr(this, ivFooterToot, R.attr.ic_edit,color=c)
Styler.setIconAttr(this, ivFooterMenu, R.attr.ic_hamburger,color=c)
}
c = footer_tab_bg_color

View File

@ -414,18 +414,18 @@ class ActColumnCustomize : AppCompatActivity(), View.OnClickListener, ColorPicke
android.R.attr.textColorPrimary
)
)
Styler.setIconDefaultColor(
Styler.setIconAttr(
this,
ivColumnHeader,
column.getIconAttrId(column.column_type)
)
} else {
tvColumnName.setTextColor(c)
Styler.setIconCustomColor(
Styler.setIconAttr(
this,
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
, args = arrayOf("", false)
)
R.id.nav_add_mutes -> Action_Account.timeline(
this
, defaultInsertPosition
, Column.TYPE_MUTES
, bAllowPseudo = false
)
R.id.nav_add_blocks -> Action_Account.timeline(
this
, defaultInsertPosition
, Column.TYPE_BLOCKS
, bAllowPseudo = false
, bAllowMisskey = false
)
R.id.nav_keyword_filter -> Action_Account.timeline(
this
, defaultInsertPosition
@ -1421,9 +1423,9 @@ class ActMain : AppCompatActivity()
c = column.header_fg_color
if(c == 0) {
Styler.setIconDefaultColor(this, ivIcon, column.getIconAttrId(column.column_type))
Styler.setIconAttr(this, ivIcon, column.getIconAttrId(column.column_type))
} 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
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)
Styler.setIconAttr(this, btnToot, R.attr.ic_edit)
Styler.setIconAttr(this, btnMenu, R.attr.ic_hamburger)
Styler.setIconAttr(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)
Styler.setIconAttr(this, btnToot, R.attr.ic_edit,c)
Styler.setIconAttr(this, btnMenu, R.attr.ic_hamburger,c)
Styler.setIconAttr(this, btnQuickToot, R.attr.btn_post,c)
}
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/28 v279 31 => 32 UserRelation,UserRelationMisskey にendorsedを追加
// 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(
LogData,

View File

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

View File

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

View File

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

View File

@ -4,6 +4,8 @@ import android.app.DownloadManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import jp.juggler.subwaytooter.util.getIntOrNull
import jp.juggler.subwaytooter.util.getStringOrNull
import jp.juggler.subwaytooter.util.showToast
@ -22,12 +24,10 @@ class DownloadReceiver : BroadcastReceiver() {
val query = DownloadManager.Query().setFilterById(id)
downloadManager.query(query)?.use { cursor ->
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にアイコンを設定する
fun setIconDefaultColor(context : Context, imageView : ImageView, iconAttrId : Int) {
imageView.setImageResource(getAttributeResourceId(context, iconAttrId))
}
// ImageViewにアイコンを設定する。色を変えてしまう
fun setIconCustomColor(
private fun setIconDrawableId(
context : Context,
imageView : ImageView,
color : Int,
iconAttrId : Int
drawableId : Int,
color:Int? = null
) {
val d = getAttributeDrawable(context, iconAttrId)
d.mutate() // 色指定が他のアイコンに影響しないようにする
d.setColorFilter(color, PorterDuff.Mode.SRC_ATOP)
imageView.setImageDrawable(d)
if( color == null) {
// ImageViewにアイコンを設定する。デフォルトの色
imageView.setImageResource(drawableId)
}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 {
@ -162,15 +171,34 @@ object Styler {
) {
// 被フォロー状態
if(! relation.followed_by) {
ivDot.visibility = View.GONE
} else {
ivDot.visibility = View.VISIBLE
setIconDefaultColor(context, ivDot, R.attr.ic_followed_by)
when {
// 被フォローリクエスト状態の時に followed_by が 真と偽の両方がありえるようなので
// Relationshipだけを見ても被フォローリクエスト状態は分からないっぽい
// 仕方ないので馬鹿正直に「 followed_byが真ならバッジをつける」しかできない
relation.blocked_by -> {
ivDot.visibility = View.VISIBLE
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)
setIconCustomColor(context, ibFollow, color, icon_attr)
setIconAttr(context, ibFollow, icon_attr, color =Styler.getAttributeColor(context, color_attr) )
ibFollow.contentDescription = contentDescription
}

View File

@ -122,17 +122,20 @@ fun makeAccountListNonPseudo(
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
val now = System.currentTimeMillis()
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 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
}
@ -141,7 +144,8 @@ internal fun loadRelation1Mastodon(
client : TootApiClient,
access_info : SavedAccount,
who : TootAccount
):RelationResult{val rr = RelationResult()
) : RelationResult {
val rr = RelationResult()
rr.result = client.request("/api/v1/accounts/relationships?id=${who.id}")
val r2 = rr.result
val jsonArray = r2?.jsonArray
@ -154,7 +158,6 @@ internal fun loadRelation1Mastodon(
return rr
}
// 別アカ操作と別タンスの関係
const val NOT_CROSS_ACCOUNT = 1
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.SavedAccount
import jp.juggler.subwaytooter.table.UserRelation
import jp.juggler.subwaytooter.table.UserRelationMisskey
import jp.juggler.subwaytooter.util.*
import okhttp3.Request
import okhttp3.RequestBody
@ -37,25 +38,31 @@ object Action_User {
var relation : UserRelation? = null
override fun background(client : TootApiClient) : TootApiResult? {
if(access_info.isMisskey){
if(access_info.isMisskey) {
val params = access_info.putMisskeyApiToken(JSONObject())
.put("userId",who.id.toString())
.put("userId", who.id.toString())
val result = client.request(when(bMute){
true-> "/api/mute/create"
else->"/api/mute/delete"
},params.toPostRequestBuilder())
if( result?.jsonObject != null ){
val result = client.request(
when(bMute) {
true -> "/api/mute/create"
else -> "/api/mute/delete"
}, params.toPostRequestBuilder()
)
if(result?.jsonObject != null) {
// 204 no content
// 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
saveUserRelationMisskey(access_info,who.id,TootParser(activity,access_info))
saveUserRelationMisskey(
access_info,
who.id,
TootParser(activity, access_info)
)
this.relation = ur
}
return result
}else{
} else {
val request_builder = Request.Builder().post(
if(! bMute)
RequestBody.create(TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED, "")
@ -126,7 +133,10 @@ object Action_User {
// ユーザをブロック/ブロック解除する
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)) {
showToast(activity, false, R.string.it_is_you)
@ -138,23 +148,52 @@ object Action_User {
var relation : UserRelation? = null
override fun background(client : TootApiClient) : TootApiResult? {
val request_builder = Request.Builder().post(
RequestBody.create(
TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED, "" // 空データ
if(access_info.isMisskey) {
val params = access_info.putMisskeyApiToken()
.put("userId", who.id)
val result = client.request(
if(bBlock)
"/api/blocking/create"
else
"/api/blocking/delete",
params.toPostRequestBuilder()
)
)
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))
if(result?.jsonObject != null) {
// ユーザ情報があるがリレーションが含まれないっぽい
// update user relation
val ur = UserRelation.load(access_info.db_id, who.id)
ur.blocking = bBlock
saveUserRelationMisskey(
access_info,
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?) {
@ -170,24 +209,38 @@ object Action_User {
return
}
for(column in App1.getAppState(activity).column_list) {
if(relation.blocking) {
if(column.column_type == Column.TYPE_PROFILE) {
// プロフページのトゥートはブロックしてても見れる
continue
} else {
column.removeAccountInTimeline(access_info, who.id)
when {
//ブロック解除したら「ブロックしたユーザ」カラムのリストから消える
! relation.blocking -> column.removeUser(
access_info,
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(
activity,
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 {
@ -206,7 +259,12 @@ object Action_User {
who : TootAccount
) {
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)
else -> activity.addColumn(pos, access_info, Column.TYPE_PROFILE, who.id)
}
@ -223,7 +281,7 @@ object Action_User {
var who_local : TootAccount? = null
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
return result
}
@ -422,7 +480,8 @@ object Action_User {
client.request("/api/v1/accounts/" + who.id + "/follow", request_builder)
val jsonObject = result?.jsonObject
if(jsonObject != null) {
relation =saveUserRelation(access_info,parseItem(::TootRelationShip, jsonObject))
relation =
saveUserRelation(access_info, parseItem(::TootRelationShip, jsonObject))
}
return result
}
@ -482,23 +541,23 @@ object Action_User {
activity : ActMain,
access_info : SavedAccount,
who : TootAccount,
bConfirmed :Boolean = false
bConfirmed : Boolean = false
) {
if(!bConfirmed){
if(! bConfirmed) {
val name = who.decodeDisplayName(activity)
AlertDialog.Builder(activity)
.setMessage( name.intoStringResource(activity,R.string.delete_succeeded_confirm))
.setNegativeButton(R.string.cancel,null)
.setPositiveButton(R.string.ok){ _ , _ ->
deleteSuggestion(activity,access_info,who,bConfirmed=true)
.setMessage(name.intoStringResource(activity, R.string.delete_succeeded_confirm))
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.ok) { _, _ ->
deleteSuggestion(activity, access_info, who, bConfirmed = true)
}
.show()
return
}
TootTaskRunner(activity).run(access_info, object : TootTask {
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?) {
@ -507,16 +566,16 @@ object Action_User {
// error
val error = result.error
if( error != null ){
showToast(activity,true,result.error)
if(error != null) {
showToast(activity, true, result.error)
return
}
showToast(activity,false,R.string.delete_succeeded)
showToast(activity, false, R.string.delete_succeeded)
// update suggestion column
for( column in activity.app_state.column_list){
column.removeUser(access_info,Column.TYPE_FOLLOW_SUGGESTION,who.id)
for(column in activity.app_state.column_list) {
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) {
class Field(
val name :String,
val value :String,
val verified_at: Long // 0L if not verified
val name : String,
val value : String,
val verified_at : Long // 0L if not verified
)
companion object {
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)
internal val reAccountUrl :Pattern =
internal val reAccountUrl : Pattern =
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)
return if(m.find()){
return if(m.find()) {
val host = m.group(1)
val user = m.group(2).unescapeUri()
val instance = m.groupOrNull(3)?.unescapeUri()
if( instance?.isNotEmpty() == true){
if(instance?.isNotEmpty() == true) {
"$user@$instance"
}else{
} else {
"$user@$host"
}
}else{
} else {
null
}
}
@ -79,7 +79,7 @@ open class TootAccount(parser : TootParser, src : JSONObject) {
try {
// たぶんどんなURLでもauthorityの部分にホスト名が来るだろう(慢心)
val host = Uri.parse(url).authority
if( host?.isNotEmpty() == true){
if(host?.isNotEmpty() == true) {
return host.toLowerCase()
}
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 value = item.parseString("value") ?: continue
val svVerifiedAt = item.parseString("verified_at")
val verifiedAt = when(svVerifiedAt){
val verifiedAt = when(svVerifiedAt) {
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()) {
null
@ -113,7 +113,6 @@ open class TootAccount(parser : TootParser, src : JSONObject) {
}
}
//URL of the user's profile page (can be remote)
// https://mastodon.juggler.jp/@tateisu
// 疑似アカウントでは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 {
@ -335,7 +343,7 @@ open class TootAccount(parser : TootParser, src : JSONObject) {
}
ServiceType.MSP -> {
this.id = EntityId.mayDefault(src.parseLong("id") )
this.id = EntityId.mayDefault(src.parseLong("id"))
// MSPはLTLの情報しか持ってないのでacctは常にホスト名部分を持たない
this.host = findHostFromUrl(null, null, url)
@ -400,9 +408,8 @@ open class TootAccount(parser : TootParser, src : JSONObject) {
).decodeEmoji(sv)
}
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
init {
// Misskey はこのEntityを使わない
this.id = EntityId.mayDefault( src.parseLong("id") )
var ov = src.opt("following")

View File

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

View File

@ -2,12 +2,11 @@ package jp.juggler.subwaytooter.table
import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase
import org.json.JSONObject
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.getString
import jp.juggler.subwaytooter.util.toJsonObject
import org.json.JSONObject
object ClientInfo :TableCompanion {
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)
.use { cursor ->
if(cursor.moveToFirst()) {
return cursor.getString(cursor.getColumnIndex(COL_RESULT)).toJsonObject()
return cursor.getString(COL_RESULT).toJsonObject()
}
}
} 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.TootStatus
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.getInt
object ContentWarning : TableCompanion {
private val log = LogCategory("ContentWarning")
@ -66,7 +67,7 @@ object ContentWarning : TableCompanion {
null
).use { cursor ->
if(cursor.moveToFirst()) {
val iv = cursor.getInt(cursor.getColumnIndex(COL_SHOWN))
val iv = cursor.getInt(COL_SHOWN)
return 0 != iv
}

View File

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

View File

@ -131,14 +131,12 @@ class HighlightWord {
}
constructor(cursor : Cursor) {
this.id = cursor.getLong(cursor.getColumnIndex(COL_ID))
this.name = cursor.getString(cursor.getColumnIndex(COL_NAME))
this.color_bg = cursor.getInt(cursor.getColumnIndex(COL_COLOR_BG))
this.color_fg = cursor.getInt(cursor.getColumnIndex(COL_COLOR_FG))
this.sound_type = cursor.getInt(cursor.getColumnIndex(COL_SOUND_TYPE))
val colIdx_sound_uri = cursor.getColumnIndex(COL_SOUND_URI)
this.sound_uri =
if(cursor.isNull(colIdx_sound_uri)) null else cursor.getString(colIdx_sound_uri)
this.id = cursor.getLong(COL_ID)
this.name = cursor.getString(COL_NAME)
this.color_bg = cursor.getInt(COL_COLOR_BG)
this.color_fg = cursor.getInt(COL_COLOR_FG)
this.sound_type = cursor.getInt(COL_SOUND_TYPE)
this.sound_uri = cursor.getStringOrNull(COL_SOUND_URI)
}
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.TootStatus
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.getInt
object MediaShown : TableCompanion {
private val log = LogCategory("MediaShown")
@ -65,7 +66,7 @@ object MediaShown : TableCompanion {
null
).use { cursor ->
if(cursor.moveToFirst()) {
return 0 != cursor.getInt(cursor.getColumnIndex(COL_SHOWN))
return 0 != cursor.getInt(COL_SHOWN)
}
}
} catch(ex : Throwable) {

View File

@ -6,6 +6,7 @@ import android.database.sqlite.SQLiteDatabase
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.api.entity.TootStatus
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.getInt
object MediaShownMisskey : TableCompanion {
private val log = LogCategory("MediaShownMisskey")
@ -58,7 +59,7 @@ object MediaShownMisskey : TableCompanion {
null
).use { cursor ->
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.util.LogCategory
import jp.juggler.subwaytooter.util.getStringOrNull
import jp.juggler.subwaytooter.util.getLong
class NotificationTracking {
@ -160,13 +161,13 @@ class NotificationTracking {
null
)?.use { cursor ->
if(cursor.moveToFirst()) {
dst.id = cursor.getLong(cursor.getColumnIndex(COL_ID))
dst.last_load = cursor.getLong(cursor.getColumnIndex(COL_LAST_LOAD))
dst.id = cursor.getLong(COL_ID)
dst.last_load = cursor.getLong(COL_LAST_LOAD)
dst.nid_show = EntityId.from(cursor, COL_NID_SHOW)
dst.nid_read = EntityId.from(cursor, COL_NID_READ)
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)

View File

@ -94,13 +94,13 @@ class SavedAccount(
}
constructor(context : Context, cursor : Cursor) : this(
cursor.getLong(cursor.getColumnIndex(COL_ID)), // db_id
cursor.getString(cursor.getColumnIndex(COL_USER)), // acct
cursor.getString(cursor.getColumnIndex(COL_HOST)) // host
, _isMisskey = cursor.getInt(cursor.getColumnIndex(COL_IS_MISSKEY)).i2b()
cursor.getLong(COL_ID), // db_id
cursor.getString(COL_USER), // acct
cursor.getString(COL_HOST) // host
, _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) {
// 疑似アカウント
this.loginAccount = null
@ -113,60 +113,45 @@ class SavedAccount(
if(loginAccount == null) {
log.e(
"missing loginAccount for %s",
cursor.getString(cursor.getColumnIndex(COL_ACCOUNT))
cursor.getString(COL_ACCOUNT)
)
}
this.loginAccount = loginAccount
}
val colIdx_visibility = cursor.getColumnIndex(COL_VISIBILITY)
val sv = if(cursor.isNull(colIdx_visibility)) null else cursor.getString(colIdx_visibility)
val sv = cursor.getStringOrNull(COL_VISIBILITY)
this.visibility = TootVisibility.parseSavedVisibility(sv) ?: TootVisibility.Public
this.confirm_boost = cursor.getInt(cursor.getColumnIndex(COL_CONFIRM_BOOST)).i2b()
this.confirm_favourite = cursor.getInt(cursor.getColumnIndex(COL_CONFIRM_FAVOURITE)).i2b()
this.confirm_unboost = cursor.getInt(cursor.getColumnIndex(COL_CONFIRM_UNBOOST)).i2b()
this.confirm_unfavourite =
cursor.getInt(cursor.getColumnIndex(COL_CONFIRM_UNFAVOURITE)).i2b()
this.confirm_boost = cursor.getBoolean(COL_CONFIRM_BOOST)
this.confirm_favourite = cursor.getBoolean(COL_CONFIRM_FAVOURITE)
this.confirm_unboost = cursor.getBoolean(COL_CONFIRM_UNBOOST)
this.confirm_unfavourite = cursor.getBoolean(COL_CONFIRM_UNFAVOURITE)
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.dont_show_timeout = cursor.getInt(cursor.getColumnIndex(COL_DONT_SHOW_TIMEOUT)).i2b()
this.notification_mention = cursor.getBoolean(COL_NOTIFICATION_MENTION)
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 =
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_tag = cursor.getStringOrNull(COL_NOTIFICATION_TAG)
this.notification_reaction =
cursor.getInt(cursor.getColumnIndex(COL_NOTIFICATION_REACTION)).i2b()
this.register_key = cursor.getStringOrNull(COL_REGISTER_KEY)
this.notification_vote =
cursor.getInt(cursor.getColumnIndex(COL_NOTIFICATION_VOTE)).i2b()
this.register_time = cursor.getLong(COL_REGISTER_TIME)
this.confirm_follow = cursor.getInt(cursor.getColumnIndex(COL_CONFIRM_FOLLOW)).i2b()
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()
this.token_info = cursor.getString(COL_TOKEN).toJsonObject()
val idx_notification_tag = cursor.getColumnIndex(COL_NOTIFICATION_TAG)
this.notification_tag =
if(cursor.isNull(idx_notification_tag)) null else cursor.getString(idx_notification_tag)
this.sound_uri = cursor.getString(COL_SOUND_URI)
val idx_register_key = cursor.getColumnIndex(COL_REGISTER_KEY)
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)) ?: ""
this.default_text = cursor.getStringOrNull(COL_DEFAULT_TEXT) ?: ""
}
@ -739,7 +724,7 @@ class SavedAccount(
} catch(ex : Throwable) {
log.trace(ex)
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
@ -910,7 +895,7 @@ class SavedAccount(
val misskeyApiToken : String?
get() = token_info?.parseString(TootApiClient.KEY_API_KEY_MISSKEY)
fun putMisskeyApiToken(params : JSONObject =JSONObject()) : JSONObject {
fun putMisskeyApiToken(params : JSONObject = JSONObject()) : JSONObject {
val apiKey = misskeyApiToken
if(apiKey?.isNotEmpty() == true) params.put("i", apiKey)
return params

View File

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

View File

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

View File

@ -3,22 +3,24 @@ package jp.juggler.subwaytooter.table
import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase
import android.support.v4.util.LruCache
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.api.entity.EntityId
import jp.juggler.subwaytooter.api.entity.EntityIdString
import jp.juggler.subwaytooter.api.entity.TootAccount
import jp.juggler.subwaytooter.api.entity.TootRelationShip
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.getInt
import org.json.JSONObject
class UserRelation {
var following : 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 requested : Boolean = false // 認証ユーザからのフォローは申請中である
var requested_by : Boolean = false // 相手から認証ユーザへのフォローリクエスト申請中(Misskeyのみ。Mastodonでは常にfalse)
var following_reblogs : Int = 0 // このユーザからのブーストをTLに表示する
var endorsed : Boolean = false // ユーザをプロフィールで紹介する
@ -221,14 +223,13 @@ class UserRelation {
.use { cursor ->
if(cursor.moveToNext()) {
val dst = UserRelation()
dst.following = cursor.getInt(cursor.getColumnIndex(COL_FOLLOWING)).i2b()
dst.followed_by =cursor.getInt(cursor.getColumnIndex(COL_FOLLOWED_BY)).i2b()
dst.blocking = cursor.getInt(cursor.getColumnIndex(COL_BLOCKING)).i2b()
dst.muting = cursor.getInt(cursor.getColumnIndex(COL_MUTING)).i2b()
dst.requested = cursor.getInt(cursor.getColumnIndex(COL_REQUESTED)).i2b()
dst.following_reblogs =
cursor.getInt(cursor.getColumnIndex(COL_FOLLOWING_REBLOGS))
dst.endorsed = cursor.getInt(cursor.getColumnIndex(COL_ENDORSED)).i2b()
dst.following = cursor.getBoolean(COL_FOLLOWING)
dst.followed_by =cursor.getBoolean(COL_FOLLOWED_BY)
dst.blocking = cursor.getBoolean(COL_BLOCKING)
dst.muting = cursor.getBoolean(COL_MUTING)
dst.requested = cursor.getBoolean(COL_REQUESTED)
dst.following_reblogs = cursor.getInt(COL_FOLLOWING_REBLOGS)
dst.endorsed = cursor.getBoolean(COL_ENDORSED)
return dst
}
}
@ -240,13 +241,21 @@ class UserRelation {
}
// Misskey用
fun parseMisskeyUser(src : JSONObject) = UserRelation().apply {
following = src.optBoolean("isFollowing")
followed_by = src.optBoolean("isFollowed")
muting = src.optBoolean("isMuted")
blocking = false
endorsed = false
requested = src.optBoolean("hasPendingFollowRequestFromYou")
fun parseMisskeyUser(src : JSONObject) :UserRelation? {
// リレーションを返さない場合がある
src.opt("isFollowing") ?: return null
return UserRelation().apply {
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.api.entity.EntityId
import jp.juggler.subwaytooter.util.LogCategory
import jp.juggler.subwaytooter.util.getInt
object UserRelationMisskey : TableCompanion {
@ -21,6 +22,8 @@ object UserRelationMisskey : TableCompanion {
private const val COL_REQUESTED = "requested"
private const val COL_FOLLOWING_REBLOGS = "following_reblogs"
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) {
log.d("onDBCreate!")
@ -38,6 +41,8 @@ object UserRelationMisskey : TableCompanion {
,$COL_REQUESTED integer not null
,$COL_FOLLOWING_REBLOGS integer not null
,$COL_ENDORSED integer default 0
,$COL_BLOCKED_BY integer default 0
,$COL_REQUESTED_BY integer default 0
)"""
)
db.execSQL(
@ -60,9 +65,24 @@ object UserRelationMisskey : TableCompanion {
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 {
val cv = ContentValues()
cv.put(COL_TIME_SAVE, now)
@ -75,6 +95,8 @@ object UserRelationMisskey : TableCompanion {
cv.put(COL_REQUESTED, src.requested.b2i())
cv.put(COL_FOLLOWING_REBLOGS, src.following_reblogs)
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)
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_FOLLOWING_REBLOGS, src.following_reblogs)
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)
}
bOK = true
@ -151,15 +175,15 @@ object UserRelationMisskey : TableCompanion {
.use { cursor ->
if(cursor.moveToNext()) {
val dst = UserRelation()
dst.following = cursor.getInt(cursor.getColumnIndex(COL_FOLLOWING)).i2b()
dst.followed_by =
cursor.getInt(cursor.getColumnIndex(COL_FOLLOWED_BY)).i2b()
dst.blocking = cursor.getInt(cursor.getColumnIndex(COL_BLOCKING)).i2b()
dst.muting = cursor.getInt(cursor.getColumnIndex(COL_MUTING)).i2b()
dst.requested = cursor.getInt(cursor.getColumnIndex(COL_REQUESTED)).i2b()
dst.following_reblogs =
cursor.getInt(cursor.getColumnIndex(COL_FOLLOWING_REBLOGS))
dst.endorsed = cursor.getInt(cursor.getColumnIndex(COL_ENDORSED)).i2b()
dst.following = cursor.getBoolean(COL_FOLLOWING)
dst.followed_by = cursor.getBoolean(COL_FOLLOWED_BY)
dst.blocking = cursor.getBoolean(COL_BLOCKING)
dst.muting = cursor.getBoolean(COL_MUTING)
dst.requested = cursor.getBoolean(COL_REQUESTED)
dst.following_reblogs = cursor.getInt(COL_FOLLOWING_REBLOGS)
dst.endorsed =cursor.getBoolean(COL_ENDORSED)
dst.blocked_by = cursor.getBoolean(COL_BLOCKED_BY)
dst.requested_by = cursor.getBoolean(COL_REQUESTED_BY)
return dst
}
}

View File

@ -9,8 +9,7 @@ import android.os.Environment
import android.os.storage.StorageManager
import android.webkit.MimeTypeMap
import java.io.File
import java.util.ArrayList
import java.util.HashMap
import java.util.*
object StorageUtils{
@ -145,7 +144,7 @@ object StorageUtils{
val type = cursor.getType(i)
if(type != Cursor.FIELD_TYPE_STRING) continue
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)
}
}

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))
}
fun Cursor.getStringOrNull(colName : String) : String? {
val colIdx = getColumnIndex(colName)
return if(isNull(colIdx)) {
null
} else {
getString(colIdx)
}
}
fun Cursor.getInt(key:String) =
getInt(getColumnIndex(key))
fun Cursor.getIntOrNull(idx:Int) =
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 {
val errorName = "no_name"
@ -921,12 +942,7 @@ fun getDocumentName(contentResolver:ContentResolver,uri : Uri) : String {
return if(! cursor.moveToFirst()) {
errorName
} else {
val colIdx = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
if(cursor.isNull(colIdx)) {
errorName
} else {
cursor.getString(colIdx)
}
cursor.getStringOrNull(OpenableColumns.DISPLAY_NAME) ?: 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( "../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);