SubwayTooter-Android-App/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgListMember.kt

424 lines
12 KiB
Kotlin

package jp.juggler.subwaytooter.dialog
import android.annotation.SuppressLint
import android.app.Dialog
import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.view.WindowManager
import android.widget.*
import jp.juggler.subwaytooter.ActMain
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.Styler
import jp.juggler.subwaytooter.action.Action_List
import jp.juggler.subwaytooter.action.Action_ListMember
import jp.juggler.subwaytooter.action.makeAccountListNonPseudo
import jp.juggler.subwaytooter.api.*
import jp.juggler.subwaytooter.api.entity.EntityId
import jp.juggler.subwaytooter.api.entity.TootAccount
import jp.juggler.subwaytooter.api.entity.TootList
import jp.juggler.subwaytooter.api.entity.parseList
import jp.juggler.subwaytooter.table.AcctColor
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.NetworkEmojiInvalidator
import jp.juggler.subwaytooter.view.MyListView
import jp.juggler.subwaytooter.view.MyNetworkImageView
import jp.juggler.util.*
import org.jetbrains.anko.textColor
import java.util.*
@SuppressLint("InflateParams")
class DlgListMember(
private val activity : ActMain,
who : TootAccount,
_list_owner : SavedAccount
) : View.OnClickListener {
private val dialog : Dialog
private val btnListOwner : Button
private val btnCreateList : Button
private val account_list : ArrayList<SavedAccount>
private val target_user_full_acct : String
private var list_owner : SavedAccount? = null
private var local_who : TootAccount? = null
private val adapter : MyListAdapter
init {
this.account_list = makeAccountListNonPseudo(activity, null)
this.target_user_full_acct = _list_owner.getFullAcct(who)
this.list_owner = if(_list_owner.isPseudo) {
null
} else {
_list_owner
}
val view = activity.layoutInflater.inflate(R.layout.dlg_list_member, null, false)
val ivUser = view.findViewById<MyNetworkImageView>(R.id.ivUser)
val tvUserName = view.findViewById<TextView>(R.id.tvUserName)
val tvUserAcct = view.findViewById<TextView>(R.id.tvUserAcct)
btnListOwner = view.findViewById(R.id.btnListOwner)
btnCreateList = view.findViewById(R.id.btnCreateList)
val listView = view.findViewById<MyListView>(R.id.listView)
this.adapter = MyListAdapter()
listView.adapter = adapter
btnCreateList.setOnClickListener(this)
btnListOwner.setOnClickListener(this)
view.findViewById<View>(R.id.btnClose).setOnClickListener(this)
ivUser.setImageUrl(
App1.pref,
Styler.calcIconRound(ivUser.layoutParams),
who.avatar_static,
who.avatar
)
val user_name_invalidator = NetworkEmojiInvalidator(activity.handler, tvUserName)
val name = who.decodeDisplayName(activity)
tvUserName.text = name
user_name_invalidator.register(name)
tvUserAcct.text = target_user_full_acct
setListOwner(list_owner)
dialog = Dialog(activity).apply {
window?.apply {
setFlags(0, Window.FEATURE_NO_TITLE)
setLayout(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT
)
}
setTitle(R.string.your_lists)
setContentView(view)
}
}
fun show() = dialog.apply {
window?.setLayout(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT
)
show()
}
override fun onClick(v : View) {
when(v.id) {
R.id.btnClose -> dialog.dismissSafe()
R.id.btnListOwner -> {
AccountPicker.pick(
activity,
bAllowPseudo = false,
bAuto = false,
accountListArg = account_list
) { ai ->
setListOwner(ai)
}
}
R.id.btnCreateList -> openListCreator()
}
}
// リストオーナボタンの文字列を更新する
// リスト一覧を取得する
private fun setListOwner(a : SavedAccount?) {
this.list_owner = a
if(a == null) {
btnListOwner.setText(R.string.not_selected)
btnListOwner.setTextColor(getAttributeColor(activity, android.R.attr.textColorPrimary))
btnListOwner.setBackgroundResource(R.drawable.btn_bg_transparent)
//
} else {
val ac = AcctColor.load(a.acctAscii)
val nickname = if(AcctColor.hasNickname(ac)) ac.nickname else a.acctPretty
btnListOwner.text = nickname
if(AcctColor.hasColorBackground(ac)) {
btnListOwner.setBackgroundColor(ac.color_bg)
} else {
btnListOwner.setBackgroundResource(R.drawable.btn_bg_transparent)
}
btnListOwner.textColor = ac.color_fg.notZero()
?: getAttributeColor(activity, android.R.attr.textColorPrimary)
}
loadLists()
}
// リストの一覧とターゲットユーザの登録状況を取得する
private fun loadLists() {
val list_owner = this.list_owner
if(list_owner == null) {
showList(null)
return
}
TootTaskRunner(activity).run(list_owner, object : TootTask {
var new_list : ArrayList<TootList>? = null
override fun background(client : TootApiClient) : TootApiResult? {
// 現在の登録状況を知るため、対象ユーザの自タンスでのアカウントIDを取得する
// ドメインブロックなどの影響で同期できない場合があるが、
// 一覧そのものは取得できるのでこの段階ではエラーにはしない
val (r1, ar) = client.syncAccountByAcct(list_owner, target_user_full_acct)
r1 ?: return null // cancelled.
val local_who = ar?.get() // may null
if(local_who == null) showToast(activity, true, r1.error)
this@DlgListMember.local_who = local_who
return if(list_owner.isMisskey) {
// 今のmisskeyではリスト全スキャンしないとユーザの登録状況が分からない
client.request(
"/api/users/lists/list",
list_owner
.putMisskeyApiToken()
.toPostRequestBuilder()
)?.also { result ->
this.new_list = parseList(
::TootList,
TootParser(activity, list_owner),
result.jsonArray ?: return@also
).apply {
if(local_who != null) {
forEach { list ->
list.isRegistered =
null != list.userIds?.find { it == local_who.id }
}
}
}
}
} else {
val set_registered = HashSet<EntityId>()
// メンバーを指定してリスト登録状況を取得
if(local_who != null) client.request(
"/api/v1/accounts/${local_who.id}/lists"
)?.also { result ->
val jsonArray = result.jsonArray ?: return result
parseList(
::TootList,
TootParser(activity, list_owner),
jsonArray
).forEach {
set_registered.add(it.id)
}
}
// リスト一覧を取得
client.request("/api/v1/lists")?.also { result ->
this.new_list = parseList(
::TootList,
TootParser(activity, list_owner),
result.jsonArray ?: return@also
).apply {
sort()
forEach {
it.isRegistered = set_registered.contains(it.id)
}
}
}
}
}
override fun handleResult(result : TootApiResult?) {
showList(new_list)
result ?: return // cancelled.
val error = result.error
if(error?.isNotEmpty() == true) {
showToast(activity, true, result.error)
}
}
})
}
private fun showList(_list : ArrayList<TootList>?) {
btnCreateList.isEnabled = _list != null
adapter.item_list.clear()
when {
_list == null -> adapter.item_list.add(ErrorItem(activity.getString(R.string.cant_access_list)))
_list.isEmpty() -> adapter.item_list.add(ErrorItem(activity.getString(R.string.list_not_created)))
else -> adapter.item_list.addAll(_list)
}
adapter.notifyDataSetChanged()
}
private fun openListCreator() {
DlgTextInput.show(
activity,
activity.getString(R.string.list_create),
null,
object : DlgTextInput.Callback {
override fun onEmptyError() {
showToast(activity, false, R.string.list_name_empty)
}
override fun onOK(dialog : Dialog, text : String) {
val list_owner = this@DlgListMember.list_owner
if(list_owner == null) {
showToast(activity, false, "list owner is not selected.")
return
}
Action_List.create(
activity,
list_owner,
text,
object : Action_List.CreateCallback {
override fun onCreated(list : TootList) {
dialog.dismissSafe()
loadLists()
}
})
}
})
}
internal class ErrorItem(val message : String)
private inner class MyListAdapter : BaseAdapter() {
internal val item_list = ArrayList<Any>()
override fun getCount() : Int = item_list.size
override fun getItem(position : Int) : Any? =
if(position >= 0 && position < item_list.size) item_list[position] else null
override fun getItemId(position : Int) : Long =
0L
override fun getViewTypeCount() : Int = 2
override fun getItemViewType(position : Int) : Int =
when(getItem(position)) {
is TootList -> 0
else -> 1
}
override fun getView(position : Int, viewOld : View?, parent : ViewGroup) : View =
when(val o = getItem(position)) {
is TootList ->
if(viewOld != null) {
viewOld.apply { (tag as VH_List).bind(o) }
} else {
val view : View = activity.layoutInflater.inflate(
R.layout.lv_list_member_list,
parent,
false
)
view.apply { tag = VH_List(this).bind(o) }
}
is ErrorItem ->
if(viewOld != null) {
viewOld.apply { (tag as VH_Error).bind(o) }
} else {
val view : View = activity.layoutInflater.inflate(
R.layout.lv_list_member_error,
parent,
false
)
view.apply { tag = VH_Error(view).bind(o) }
}
else -> activity.layoutInflater.inflate(
R.layout.lv_list_member_error,
parent,
false
)
}
}
internal inner class VH_List(view : View) : CompoundButton.OnCheckedChangeListener,
Action_ListMember.Callback {
private val cbItem : CheckBox
private var bBusy : Boolean = false
var item : TootList? = null
init {
this.cbItem = view.findViewById(R.id.cbItem)
cbItem.setOnCheckedChangeListener(this)
}
fun bind(item : TootList) = this.apply {
bBusy = true
this.item = item
cbItem.text = item.title
cbItem.isChecked = item.isRegistered
bBusy = false
}
override fun onCheckedChanged(view : CompoundButton, isChecked : Boolean) {
// ユーザ操作以外で変更されたなら何もしない
if(bBusy) return
val list_owner = this@DlgListMember.list_owner
if(list_owner == null) {
showToast(activity, false, "list owner is not selected")
revokeCheckedChanged(isChecked)
return
}
val local_who = this@DlgListMember.local_who
if(local_who == null) {
showToast(activity, false, "target user is not synchronized")
revokeCheckedChanged(isChecked)
return
}
// 状態をサーバに伝える
val item = this.item ?: return
if(isChecked) {
Action_ListMember.add(activity, list_owner, item.id, local_who, callback = this)
} else {
Action_ListMember.delete(activity, list_owner, item.id, local_who, this)
}
}
override fun onListMemberUpdated(willRegistered : Boolean, bSuccess : Boolean) {
if(! bSuccess) revokeCheckedChanged(willRegistered)
}
private fun revokeCheckedChanged(willRegistered : Boolean) {
item?.isRegistered = ! willRegistered
adapter.notifyDataSetChanged()
}
}
internal inner class VH_Error(view : View) {
private val tvError : TextView = view.findViewById(R.id.tvError)
fun bind(o : ErrorItem) = this.apply {
tvError.text = o.message
}
}
}