(Mastodon 3.2)プロフカラムで覚え書き(Personal notes)を記録/表示できる。覚書は記録した本人だけが閲覧できる。
This commit is contained in:
parent
1f64df766e
commit
bab7bfdfd8
|
@ -250,7 +250,7 @@ class ActHighlightWordList : AppCompatActivity(), View.OnClickListener {
|
|||
}
|
||||
|
||||
private fun create() {
|
||||
DlgTextInput.show(this, getString(R.string.new_item), "", object : DlgTextInput.Callback {
|
||||
DlgTextInput.show(this, getString(R.string.new_item), "", callback = object : DlgTextInput.Callback {
|
||||
override fun onEmptyError() {
|
||||
showToast(this@ActHighlightWordList, true, R.string.word_empty)
|
||||
}
|
||||
|
|
|
@ -2178,7 +2178,7 @@ class ActMain : AsyncActivity()
|
|||
this,
|
||||
getString(R.string.access_token_or_api_token),
|
||||
null,
|
||||
object : DlgTextInput.Callback {
|
||||
callback = object : DlgTextInput.Callback {
|
||||
override fun onOK(dialog : Dialog, text : String) {
|
||||
checkAccessToken(null, dialog, sa.host, text, sa)
|
||||
}
|
||||
|
|
|
@ -1757,7 +1757,7 @@ class ActPost : AsyncActivity(),
|
|||
this,
|
||||
getString(R.string.attachment_description),
|
||||
a.description,
|
||||
object : DlgTextInput.Callback {
|
||||
callback = object : DlgTextInput.Callback {
|
||||
override fun onOK(dialog : Dialog, text : String) {
|
||||
setAttachmentDescription(pa, dialog, text)
|
||||
}
|
||||
|
|
|
@ -134,8 +134,9 @@ class App1 : Application() {
|
|||
// 2019/12/18 44=> 45 SavedAccount テーブルに項目追加。
|
||||
// 2019/12/18 44=> 46 SavedAccount テーブルに項目追加。
|
||||
// 2020/6/8 46 => 54 別ブランチで色々してた。このブランチには影響ないが onDowngrade()を実装してないので上げてしまう
|
||||
// 2020/7/19 54=>55 UserRelation テーブルに項目追加。
|
||||
|
||||
internal const val DB_VERSION = 54
|
||||
internal const val DB_VERSION = 55
|
||||
|
||||
private val tableList = arrayOf(
|
||||
LogData,
|
||||
|
|
|
@ -1706,35 +1706,31 @@ class Column(
|
|||
// リロード不要なら何もしない
|
||||
null
|
||||
} else if(isMisskey) {
|
||||
val result = client.request(
|
||||
client.request(
|
||||
PATH_MISSKEY_PROFILE,
|
||||
access_info.putMisskeyApiToken().apply {
|
||||
put("userId", profile_id)
|
||||
}.toPostRequestBuilder()
|
||||
)
|
||||
|
||||
// ユーザリレーションの取り扱いのため、別のparserを作ってはいけない
|
||||
parser.misskeyDecodeProfilePin = true
|
||||
try {
|
||||
val a = TootAccountRef.mayNull(parser, parser.account(result?.jsonObject))
|
||||
if(a != null) {
|
||||
)?.also{ result1 ->
|
||||
// ユーザリレーションの取り扱いのため、別のparserを作ってはいけない
|
||||
parser.misskeyDecodeProfilePin = true
|
||||
try {
|
||||
TootAccountRef.mayNull(parser, parser.account(result1.jsonObject))?.also{ a->
|
||||
this.who_account = a
|
||||
client.publishApiProgress("") // カラムヘッダの再表示
|
||||
}
|
||||
} finally {
|
||||
parser.misskeyDecodeProfilePin = false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
client.request(String.format(Locale.JAPAN, PATH_ACCOUNT, profile_id))?.also{result1 ->
|
||||
TootAccountRef.mayNull(parser, parser.account(result1.jsonObject))?.also{ a->
|
||||
this.who_account = a
|
||||
client.publishApiProgress("") // カラムヘッダの再表示
|
||||
|
||||
}
|
||||
} finally {
|
||||
parser.misskeyDecodeProfilePin = false
|
||||
}
|
||||
|
||||
result
|
||||
|
||||
} else {
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1781,7 +1777,8 @@ class Column(
|
|||
// parser内部にアカウントIDとRelationのマップが生成されるので、それをデータベースに記録する
|
||||
run {
|
||||
val now = System.currentTimeMillis()
|
||||
val who_list = parser.misskeyUserRelationMap.entries.toMutableList()
|
||||
val who_list =
|
||||
parser.misskeyUserRelationMap.entries.toMutableList()
|
||||
var start = 0
|
||||
val end = who_list.size
|
||||
while(start < end) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package jp.juggler.subwaytooter
|
||||
|
||||
import android.app.Dialog
|
||||
import android.graphics.Color
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.Spanned
|
||||
|
@ -9,10 +10,11 @@ import android.widget.*
|
|||
import jp.juggler.emoji.EmojiMap
|
||||
import jp.juggler.subwaytooter.action.Action_Follow
|
||||
import jp.juggler.subwaytooter.action.Action_User
|
||||
import jp.juggler.subwaytooter.api.MisskeyAccountDetailMap
|
||||
import jp.juggler.subwaytooter.api.*
|
||||
import jp.juggler.subwaytooter.api.entity.TootAccount
|
||||
import jp.juggler.subwaytooter.api.entity.TootAccountRef
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatus
|
||||
import jp.juggler.subwaytooter.dialog.DlgTextInput
|
||||
import jp.juggler.subwaytooter.span.EmojiImageSpan
|
||||
import jp.juggler.subwaytooter.span.createSpan
|
||||
import jp.juggler.subwaytooter.table.AcctColor
|
||||
|
@ -68,6 +70,9 @@ internal class ViewHolderHeaderProfile(
|
|||
private val density : Float
|
||||
private val btnMore : ImageButton
|
||||
|
||||
private val tvPersonalNotes : TextView
|
||||
private val btnPersonalNotesEdit : ImageButton
|
||||
|
||||
init {
|
||||
ivBackground = viewRoot.findViewById(R.id.ivBackground)
|
||||
llProfile = viewRoot.findViewById(R.id.llProfile)
|
||||
|
@ -95,18 +100,28 @@ internal class ViewHolderHeaderProfile(
|
|||
ivMovedBy = viewRoot.findViewById(R.id.ivMovedBy)
|
||||
llFields = viewRoot.findViewById(R.id.llFields)
|
||||
|
||||
tvPersonalNotes = viewRoot.findViewById(R.id.tvPersonalNotes)
|
||||
btnPersonalNotesEdit = viewRoot.findViewById(R.id.btnPersonalNotesEdit)
|
||||
|
||||
|
||||
density = tvDisplayName.resources.displayMetrics.density
|
||||
|
||||
ivBackground.setOnClickListener(this)
|
||||
btnFollowing.setOnClickListener(this)
|
||||
btnFollowers.setOnClickListener(this)
|
||||
btnStatusCount.setOnClickListener(this)
|
||||
btnMore.setOnClickListener(this)
|
||||
btnFollow.setOnClickListener(this)
|
||||
tvRemoteProfileWarning.setOnClickListener(this)
|
||||
|
||||
btnMoved.setOnClickListener(this)
|
||||
llMoved.setOnClickListener(this)
|
||||
for(v in arrayOf(
|
||||
ivBackground,
|
||||
btnFollowing,
|
||||
btnFollowers,
|
||||
btnStatusCount,
|
||||
btnMore,
|
||||
btnFollow,
|
||||
tvRemoteProfileWarning,
|
||||
btnPersonalNotesEdit,
|
||||
|
||||
btnMoved,
|
||||
llMoved,
|
||||
btnPersonalNotesEdit
|
||||
)) {
|
||||
v.setOnClickListener(this)
|
||||
}
|
||||
|
||||
btnMoved.setOnLongClickListener(this)
|
||||
btnFollow.setOnLongClickListener(this)
|
||||
|
@ -132,6 +147,8 @@ internal class ViewHolderHeaderProfile(
|
|||
|
||||
private var contentColor = 0
|
||||
|
||||
private var relation : UserRelation? = null
|
||||
|
||||
override fun bindData(column : Column) {
|
||||
super.bindData(column)
|
||||
|
||||
|
@ -141,6 +158,7 @@ internal class ViewHolderHeaderProfile(
|
|||
if(! f.isNaN()) {
|
||||
tvMovedName.textSize = f
|
||||
tvMoved.textSize = f
|
||||
tvPersonalNotes.textSize = f
|
||||
}
|
||||
|
||||
f = activity.acct_font_size_sp
|
||||
|
@ -159,6 +177,7 @@ internal class ViewHolderHeaderProfile(
|
|||
val contentColor = column.getContentColor()
|
||||
this.contentColor = contentColor
|
||||
|
||||
tvPersonalNotes.textColor = contentColor
|
||||
tvMoved.textColor = contentColor
|
||||
tvMovedName.textColor = contentColor
|
||||
tvDisplayName.textColor = contentColor
|
||||
|
@ -175,7 +194,15 @@ internal class ViewHolderHeaderProfile(
|
|||
color = contentColor,
|
||||
alphaMultiplier = Styler.boost_alpha
|
||||
)
|
||||
|
||||
|
||||
setIconDrawableId(
|
||||
activity,
|
||||
btnPersonalNotesEdit,
|
||||
R.drawable.ic_edit,
|
||||
color = contentColor,
|
||||
alphaMultiplier = Styler.boost_alpha
|
||||
)
|
||||
|
||||
val acctColor = column.getAcctColor()
|
||||
tvCreated.textColor = acctColor
|
||||
tvMovedAcct.textColor = acctColor
|
||||
|
@ -200,6 +227,7 @@ internal class ViewHolderHeaderProfile(
|
|||
llFields.removeAllViews()
|
||||
|
||||
if(who == null) {
|
||||
relation = null
|
||||
tvCreated.text = ""
|
||||
tvLastStatusAt.vg(false)
|
||||
ivBackground.setImageDrawable(null)
|
||||
|
@ -345,6 +373,8 @@ internal class ViewHolderHeaderProfile(
|
|||
}
|
||||
|
||||
val relation = UserRelation.load(access_info.db_id, who.id)
|
||||
this.relation = relation
|
||||
|
||||
Styler.setFollowIcon(
|
||||
activity,
|
||||
btnFollow,
|
||||
|
@ -355,6 +385,8 @@ internal class ViewHolderHeaderProfile(
|
|||
alphaMultiplier = Styler.boost_alpha
|
||||
)
|
||||
|
||||
tvPersonalNotes.text = relation.note ?: ""
|
||||
|
||||
showMoved(who, who.movedRef)
|
||||
|
||||
val fields = whoDetail?.fields ?: who.fields
|
||||
|
@ -550,6 +582,53 @@ internal class ViewHolderHeaderProfile(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
R.id.btnPersonalNotesEdit -> whoRef?.let{ whoRef->
|
||||
val who = whoRef.get()
|
||||
val relation = this.relation
|
||||
val lastColumn = column
|
||||
DlgTextInput.show(
|
||||
activity,
|
||||
AcctColor.getStringWithNickname(activity,R.string.personal_notes_of,who.acct),
|
||||
relation?.note ?: "",
|
||||
allowEmpty = true,
|
||||
callback = object: DlgTextInput.Callback{
|
||||
override fun onEmptyError() {
|
||||
}
|
||||
|
||||
override fun onOK(dialog : Dialog, text : String) {
|
||||
TootTaskRunner(activity).run(column.access_info,object:TootTask{
|
||||
override fun background(client : TootApiClient) : TootApiResult? {
|
||||
|
||||
if(access_info.isPseudo)
|
||||
return TootApiResult("Personal notes is not supported on pseudo account.")
|
||||
|
||||
if(access_info.isMisskey)
|
||||
return TootApiResult("Personal notes is not supported on Misskey account.")
|
||||
|
||||
return client.request("/api/v1/accounts/${who.id}/note",
|
||||
jsonObject {
|
||||
put("comment",text)
|
||||
}.toPostRequestBuilder()
|
||||
)
|
||||
}
|
||||
|
||||
override fun handleResult(result : TootApiResult?) {
|
||||
if(result==null) return
|
||||
if(result.error!=null)
|
||||
showToast(activity,true,result.error)
|
||||
else{
|
||||
relation?.note = text
|
||||
dialog.dismissSafe()
|
||||
if(lastColumn==column) bindData(column)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -93,7 +93,7 @@ object Action_Account {
|
|||
activity,
|
||||
activity.getString(R.string.access_token_or_api_token),
|
||||
null,
|
||||
object : DlgTextInput.Callback {
|
||||
callback = object : DlgTextInput.Callback {
|
||||
|
||||
@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
|
||||
override fun onOK(
|
||||
|
|
|
@ -140,7 +140,7 @@ object Action_List {
|
|||
activity,
|
||||
activity.getString(R.string.rename),
|
||||
item.title,
|
||||
object : DlgTextInput.Callback {
|
||||
callback = object : DlgTextInput.Callback {
|
||||
override fun onEmptyError() {
|
||||
showToast(activity, false, R.string.list_name_empty)
|
||||
}
|
||||
|
|
|
@ -39,6 +39,9 @@ class TootRelationShip(parser:TootParser,src : JsonObject) {
|
|||
|
||||
// misskey用
|
||||
val requested_by : Boolean
|
||||
|
||||
// (Mastodon 3.2)
|
||||
var note : String? = null
|
||||
|
||||
init {
|
||||
|
||||
|
@ -91,6 +94,7 @@ class TootRelationShip(parser:TootParser,src : JsonObject) {
|
|||
this.muting = src.optBoolean("muting")
|
||||
this.requested = src.optBoolean("requested")
|
||||
this.endorsed = src.optBoolean("endorsed")
|
||||
this.note = src.optString( "note")
|
||||
|
||||
// https://github.com/tootsuite/mastodon/commit/9745de883b198375ba23f7fde879f6d75ce2df0f
|
||||
// Mastodon 2.8.0から
|
||||
|
|
|
@ -265,7 +265,7 @@ class DlgListMember(
|
|||
activity,
|
||||
activity.getString(R.string.list_create),
|
||||
null,
|
||||
object : DlgTextInput.Callback {
|
||||
callback = object : DlgTextInput.Callback {
|
||||
|
||||
override fun onEmptyError() {
|
||||
showToast(activity, false, R.string.list_name_empty)
|
||||
|
|
|
@ -24,6 +24,7 @@ object DlgTextInput {
|
|||
activity : Activity,
|
||||
caption : CharSequence,
|
||||
initial_text : CharSequence?,
|
||||
allowEmpty : Boolean = false,
|
||||
callback : Callback
|
||||
) {
|
||||
val view = activity.layoutInflater.inflate(R.layout.dlg_text_input, null, false)
|
||||
|
@ -51,7 +52,7 @@ object DlgTextInput {
|
|||
btnOk.setOnClickListener {
|
||||
val token = etInput.text.toString().trim { it <= ' ' }
|
||||
|
||||
if( token.isEmpty() ) {
|
||||
if( token.isEmpty() && !allowEmpty ) {
|
||||
callback.onEmptyError()
|
||||
} else {
|
||||
callback.onOK(dialog, token)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package jp.juggler.subwaytooter.table
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.database.Cursor
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
|
||||
|
@ -21,3 +22,6 @@ interface TableCompanion {
|
|||
fun onDBCreate(db : SQLiteDatabase)
|
||||
fun onDBUpgrade(db : SQLiteDatabase, oldVersion : Int, newVersion : Int)
|
||||
}
|
||||
|
||||
fun ContentValues.putOrNull(key : String, value : String?) =
|
||||
if(value == null) putNull(key) else put(key, value)
|
||||
|
|
|
@ -13,6 +13,7 @@ import jp.juggler.subwaytooter.api.entity.TootRelationShip
|
|||
import jp.juggler.util.JsonObject
|
||||
import jp.juggler.util.LogCategory
|
||||
import jp.juggler.util.getInt
|
||||
import jp.juggler.util.getStringOrNull
|
||||
|
||||
class UserRelation {
|
||||
|
||||
|
@ -26,6 +27,8 @@ class UserRelation {
|
|||
var following_reblogs : Int = 0 // このユーザからのブーストをTLに表示する
|
||||
var endorsed : Boolean = false // ユーザをプロフィールで紹介する
|
||||
|
||||
var note : String? = null
|
||||
|
||||
// 認証ユーザからのフォロー状態
|
||||
fun getFollowing(who : TootAccount?) : Boolean {
|
||||
return if(requested && ! following && who != null && ! who.locked) true else following
|
||||
|
@ -62,6 +65,7 @@ class UserRelation {
|
|||
private const val COL_ENDORSED = "endorsed"
|
||||
private const val COL_BLOCKED_BY = "blocked_by"
|
||||
private const val COL_REQUESTED_BY = "requested_by"
|
||||
private const val COL_NOTE = "note"
|
||||
|
||||
private const val DB_ID_PSEUDO = - 2L
|
||||
|
||||
|
@ -83,6 +87,7 @@ class UserRelation {
|
|||
,$COL_ENDORSED integer default 0
|
||||
,$COL_BLOCKED_BY integer default 0
|
||||
,$COL_REQUESTED_BY integer default 0
|
||||
,$COL_NOTE text default null
|
||||
)"""
|
||||
)
|
||||
db.execSQL(
|
||||
|
@ -119,6 +124,13 @@ class UserRelation {
|
|||
log.trace(ex)
|
||||
}
|
||||
}
|
||||
if(oldVersion < 55 && newVersion >= 55) {
|
||||
try {
|
||||
db.execSQL("alter table $table add column $COL_NOTE text default null")
|
||||
} catch(ex : Throwable) {
|
||||
log.trace(ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteOld(now : Long) {
|
||||
|
@ -155,6 +167,7 @@ class UserRelation {
|
|||
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())
|
||||
cv.putOrNull(COL_NOTE, src.note)
|
||||
App1.database.replaceOrThrow(table, null, cv)
|
||||
|
||||
val key = String.format("%s:%s", db_id, whoId)
|
||||
|
@ -184,6 +197,7 @@ class UserRelation {
|
|||
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())
|
||||
cv.putOrNull(COL_NOTE, src.note)
|
||||
App1.database.replaceOrThrow(table, null, cv)
|
||||
val key = String.format("%s:%s", db_id, id)
|
||||
mMemoryCache.remove(key)
|
||||
|
@ -216,6 +230,7 @@ class UserRelation {
|
|||
cv.put(COL_REQUESTED, src.requested.b2i())
|
||||
cv.put(COL_FOLLOWING_REBLOGS, src.showing_reblogs)
|
||||
cv.put(COL_ENDORSED, src.endorsed.b2i())
|
||||
cv.putOrNull(COL_NOTE, src.note)
|
||||
db.replaceOrThrow(table, null, cv)
|
||||
|
||||
}
|
||||
|
@ -267,6 +282,7 @@ class UserRelation {
|
|||
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())
|
||||
cv.putOrNull(COL_NOTE, src.note)
|
||||
db.replaceOrThrow(table, null, cv)
|
||||
}
|
||||
bOK = true
|
||||
|
@ -307,6 +323,7 @@ class UserRelation {
|
|||
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())
|
||||
cv.putOrNull(COL_NOTE, src.note)
|
||||
db.replace(table, null, cv)
|
||||
}
|
||||
bOK = true
|
||||
|
@ -365,6 +382,7 @@ class UserRelation {
|
|||
dst.endorsed = cursor.getBoolean(COL_ENDORSED)
|
||||
dst.blocked_by = cursor.getBoolean(COL_BLOCKED_BY)
|
||||
dst.requested_by = cursor.getBoolean(COL_REQUESTED_BY)
|
||||
dst.note = cursor.getStringOrNull(COL_NOTE)
|
||||
return dst
|
||||
}
|
||||
}
|
||||
|
|
|
@ -220,6 +220,39 @@
|
|||
tools:text="xxxx-xx-xx xx:xx:xx"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:gravity="start"
|
||||
android:text="@string/personal_notes"
|
||||
/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="start|top"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
<TextView
|
||||
android:id="@+id/tvPersonalNotes"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="start"
|
||||
android:padding="12dp"
|
||||
android:text="@string/personal_notes"
|
||||
/>
|
||||
<ImageButton
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:background="@drawable/btn_bg_transparent_round6dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:id="@+id/btnPersonalNotesEdit"
|
||||
android:src="@drawable/ic_edit"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvRemoteProfileWarning"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
@ -1028,4 +1028,6 @@
|
|||
<string name="verified_link_background_color">認証済みリンクの背景色 (アプリ再起動が必要)</string>
|
||||
<string name="verified_link_foreground_color">認証済みリンクの文字色 (アプリ再起動が必要)</string>
|
||||
<string name="screen_bottom_padding">画面下部の余白(単位:dp。デフォルト:0。アプリ再起動が必要)</string>
|
||||
<string name="personal_notes">覚え書き</string>
|
||||
<string name="personal_notes_of">%1$sに関する覚え書き</string>
|
||||
</resources>
|
||||
|
|
|
@ -1035,4 +1035,6 @@
|
|||
<string name="verified_link_background_color">Verified link background color (app restart required)</string>
|
||||
<string name="verified_link_foreground_color">Verified link foreground color (app restart required)</string>
|
||||
<string name="screen_bottom_padding">Screen bottom padding (Unit: dp. Default:0. App restart required)</string>
|
||||
<string name="personal_notes">Personal notes</string>
|
||||
<string name="personal_notes_of">Personal notes of %1$s</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in New Issue