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

398 lines
14 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.R
import jp.juggler.subwaytooter.Styler
import jp.juggler.subwaytooter.action.*
import jp.juggler.subwaytooter.api.*
import jp.juggler.subwaytooter.api.entity.*
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,
listOwnerArg: SavedAccount,
) : View.OnClickListener {
private val dialog: Dialog
private val btnListOwner: Button
private val btnCreateList: Button
private val accountList: MutableList<SavedAccount>
private val targetUserFullAcct: Acct
private var listOwner: SavedAccount? = null
private var whoLocal: TootAccount? = null
private val adapter: MyListAdapter
init {
this.accountList = activity.accountListNonPseudo(null)
this.targetUserFullAcct = listOwnerArg.getFullAcct(who)
this.listOwner = if (listOwnerArg.isPseudo) {
null
} else {
listOwnerArg
}
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(
Styler.calcIconRound(ivUser.layoutParams),
who.avatar_static,
who.avatar
)
val userNameInvalidator = NetworkEmojiInvalidator(activity.handler, tvUserName)
val name = who.decodeDisplayName(activity)
tvUserName.text = name
userNameInvalidator.register(name)
tvUserAcct.text = targetUserFullAcct.pretty
setListOwner(listOwner)
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 ->
launchMain {
activity.pickAccount(
bAllowPseudo = false,
bAuto = false,
accountListArg = accountList
)?.let { setListOwner(it) }
}
R.id.btnCreateList -> openListCreator()
}
}
// リストオーナボタンの文字列を更新する
// リスト一覧を取得する
private fun setListOwner(a: SavedAccount?) {
this.listOwner = a
if (a == null) {
btnListOwner.setText(R.string.not_selected)
btnListOwner.setTextColor(activity.attrColor(android.R.attr.textColorPrimary))
btnListOwner.setBackgroundResource(R.drawable.btn_bg_transparent_round6dp)
//
} else {
val ac = AcctColor.load(a)
btnListOwner.text = ac.nickname
if (AcctColor.hasColorBackground(ac)) {
btnListOwner.setBackgroundColor(ac.color_bg)
} else {
btnListOwner.setBackgroundResource(R.drawable.btn_bg_transparent_round6dp)
}
btnListOwner.textColor = ac.color_fg.notZero()
?: activity.attrColor(android.R.attr.textColorPrimary)
}
loadLists()
}
// リストの一覧とターゲットユーザの登録状況を取得する
private fun loadLists() {
val listOwner = this.listOwner
if (listOwner == null) {
showList(null)
return
}
launchMain {
var resultList: ArrayList<TootList>? = null
val result = activity.runApiTask(listOwner) { client ->
// 現在の登録状況を知るため、対象ユーザの自タンスでのアカウントIDを取得する
// ドメインブロックなどの影響で同期できない場合があるが、
// 一覧そのものは取得できるのでこの段階ではエラーにはしない
val (r1, ar) = client.syncAccountByAcct(listOwner, targetUserFullAcct)
r1 ?: return@runApiTask null // cancelled.
val whoLocal = ar?.get() // may null
if (whoLocal == null) activity.showToast(true, r1.error)
this@DlgListMember.whoLocal = whoLocal
if (listOwner.isMisskey) {
// 今のmisskeyではリスト全スキャンしないとユーザの登録状況が分からない
client.request(
"/api/users/lists/list",
listOwner
.putMisskeyApiToken()
.toPostRequestBuilder()
)?.also { result ->
resultList = parseList(
::TootList,
TootParser(activity, listOwner),
result.jsonArray ?: return@also
).apply {
if (whoLocal != null) {
forEach { list ->
list.isRegistered =
list.userIds?.any { it == whoLocal.id } ?: false
}
}
}
}
} else {
val registeredSet = HashSet<EntityId>()
// メンバーを指定してリスト登録状況を取得
if (whoLocal != null) client.request(
"/api/v1/accounts/${whoLocal.id}/lists"
)?.also { result ->
val jsonArray = result.jsonArray
?: return@runApiTask result
parseList(
::TootList,
TootParser(activity, listOwner),
jsonArray
).forEach {
registeredSet.add(it.id)
}
}
// リスト一覧を取得
client.request("/api/v1/lists")?.also { result ->
resultList = parseList(
::TootList,
TootParser(activity, listOwner),
result.jsonArray ?: return@also
).apply {
sort()
forEach {
it.isRegistered = registeredSet.contains(it.id)
}
}
}
}
}
result?.error?.let { activity.showToast(true, it) }
showList(resultList)
}
}
private fun showList(list: ArrayList<TootList>?) {
btnCreateList.isEnabledAlpha = list != null
adapter.itemList.clear()
when {
list == null -> adapter.itemList.add(ErrorItem(activity.getString(R.string.cant_access_list)))
list.isEmpty() -> adapter.itemList.add(ErrorItem(activity.getString(R.string.list_not_created)))
else -> adapter.itemList.addAll(list)
}
adapter.notifyDataSetChanged()
}
private fun openListCreator() {
DlgTextInput.show(
activity,
activity.getString(R.string.list_create),
null,
callback = object : DlgTextInput.Callback {
override fun onEmptyError() {
activity.showToast(false, R.string.list_name_empty)
}
override fun onOK(dialog: Dialog, text: String) {
val list_owner = this@DlgListMember.listOwner
if (list_owner == null) {
activity.showToast(false, "list owner is not selected.")
return
}
activity.listCreate(list_owner, text) {
dialog.dismissSafe()
loadLists()
}
}
})
}
internal class ErrorItem(val message: String)
private inner class MyListAdapter : BaseAdapter() {
val itemList = ArrayList<Any>()
override fun getCount(): Int = itemList.size
override fun getItem(position: Int): Any? =
if (position >= 0 && position < itemList.size) itemList[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
)
}
}
@Suppress("ClassNaming")
internal inner class VH_List(view: View) : CompoundButton.OnCheckedChangeListener {
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 listOwner = this@DlgListMember.listOwner
if (listOwner == null) {
activity.showToast(false, "list owner is not selected")
revokeCheckedChanged(isChecked)
return
}
val whoLocal = this@DlgListMember.whoLocal
if (whoLocal == null) {
activity.showToast(false, "target user is not synchronized")
revokeCheckedChanged(isChecked)
return
}
// 状態をサーバに伝える
val item = this.item ?: return
if (isChecked) {
activity.listMemberAdd(listOwner, item.id, whoLocal) { willRegistered, bSuccess ->
if (!bSuccess) revokeCheckedChanged(willRegistered)
}
} else {
activity.listMemberDelete(
listOwner,
item.id,
whoLocal
) { willRegistered, bSuccess ->
if (!bSuccess) revokeCheckedChanged(willRegistered)
}
}
}
private fun revokeCheckedChanged(willRegistered: Boolean) {
item?.isRegistered = !willRegistered
adapter.notifyDataSetChanged()
}
}
@Suppress("ClassNaming")
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
}
}
}