-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathTeamsFragment.kt
350 lines (310 loc) · 14.8 KB
/
TeamsFragment.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
package com.majeur.psclient.ui
import android.app.Activity
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.graphics.drawable.BitmapDrawable
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar
import com.majeur.psclient.databinding.FragmentTeamsBinding
import com.majeur.psclient.databinding.ListCategoryTeamBinding
import com.majeur.psclient.databinding.ListItemTeamBinding
import com.majeur.psclient.io.AssetLoader
import com.majeur.psclient.io.TeamsStore
import com.majeur.psclient.model.common.BattleFormat
import com.majeur.psclient.model.common.Team
import com.majeur.psclient.model.common.toId
import com.majeur.psclient.ui.teambuilder.TeamBuilderActivity
import com.majeur.psclient.util.recyclerview.DividerItemDecoration
import com.majeur.psclient.util.recyclerview.ItemTouchHelperCallbacks
import com.majeur.psclient.util.recyclerview.OnItemClickListener
import com.majeur.psclient.util.smogon.SmogonTeamBuilder
import com.majeur.psclient.util.toId
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import java.io.Serializable
import java.util.*
class TeamsFragment : BaseFragment(), OnItemClickListener {
val teams: List<Team.Group> get() = groups.toList()
private lateinit var teamsStore: TeamsStore
private lateinit var assetLoader: AssetLoader
private lateinit var listAdapter: TeamListAdapter
private val groups = mutableListOf<Team.Group>()
private val fallbackFormat = BattleFormat.FORMAT_OTHER
private var _binding: FragmentTeamsBinding? = null
private val binding get() = _binding!!
private val clipboardManager
get() = requireActivity().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
private val battleFormats
get() = service?.getSharedData<List<BattleFormat.Category>>("formats")
override fun onAttach(context: Context) {
super.onAttach(context)
teamsStore = TeamsStore(context)
assetLoader = mainActivity.assetLoader
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
fragmentScope.launch {
val storedGroups = teamsStore.get()
groups.clear()
groups.addAll(storedGroups.sortedWith(Comparator<Team.Group> { g1, g2 ->
BattleFormat.compare(battleFormats, g1.format, g2.format)
}))
groups.forEach { g -> g.teams.sort() }
if (this@TeamsFragment::listAdapter.isInitialized) {
listAdapter.notifyDataSetChanged()
}
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
_binding = FragmentTeamsBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.teamList.apply {
listAdapter = TeamListAdapter(this@TeamsFragment)
adapter = listAdapter
addItemDecoration(object : DividerItemDecoration(view.context) {
override fun shouldDrawDivider(parent: RecyclerView, child: View) =
parent.findContainingViewHolder(child) is TeamsFragment.TeamListAdapter.CategoryHolder
})
addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
binding.apply {
if (dy > 0) {
importFab.hide()
buildFab.hide()
} else {
importFab.show()
buildFab.show()
}
}
}
})
ItemTouchHelper(object : ItemTouchHelperCallbacks(context, allowDeletion = true) {
override fun onRemoveItem(position: Int) {
val team = listAdapter.getItem(position) as Team
removeTeam(team)
Snackbar.make(binding.root, "${team.label} removed", Snackbar.LENGTH_LONG)
.setAction("Undo") {
addOrUpdateTeam(team)
}.show()
}
}).attachToRecyclerView(this)
}
binding.buildFab.setOnClickListener {
startTeamBuilderActivity()
}
binding.importFab.setOnClickListener {
if (childFragmentManager.findFragmentByTag(ImportTeamDialog.FRAGMENT_TAG) == null)
ImportTeamDialog().show(childFragmentManager, ImportTeamDialog.FRAGMENT_TAG)
}
}
fun promptImportFromPokepaste(teamId: String) {
if (childFragmentManager.findFragmentByTag(ImportTeamDialog.FRAGMENT_TAG) == null) {
ImportTeamDialog().apply {
arguments = bundleOf(ImportTeamDialog.ARG_PP_ID to teamId)
}.show(childFragmentManager, ImportTeamDialog.FRAGMENT_TAG)
}
}
fun makeSnackbar(message: String) {
Snackbar.make(binding.root, message, Snackbar.LENGTH_LONG).show()
}
override fun onItemClick(itemView: View, holder: RecyclerView.ViewHolder, position: Int) {
val team = listAdapter.getItem(position) as Team
startTeamBuilderActivity(team)
}
private fun startTeamBuilderActivity(team: Team? = null) {
val intent = Intent(context, TeamBuilderActivity::class.java)
val battleFormats = battleFormats
if (battleFormats != null)
intent.putExtra(TeamBuilderActivity.INTENT_EXTRA_FORMATS, battleFormats as Serializable)
if (team != null)
intent.putExtra(TeamBuilderActivity.INTENT_EXTRA_TEAM, team)
startActivityForResult(intent, TeamBuilderActivity.INTENT_REQUEST_CODE)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == TeamBuilderActivity.INTENT_REQUEST_CODE && resultCode == Activity.RESULT_OK && data != null) {
val team = data.getSerializableExtra(TeamBuilderActivity.INTENT_EXTRA_TEAM) as Team
addOrUpdateTeam(team)
}
}
fun onBattleFormatsChanged() {
groups.sortWith(Comparator<Team.Group> { g1, g2 ->
BattleFormat.compare(battleFormats, g1.format, g2.format)
})
listAdapter.notifyDataSetChanged() // Update and sorts formats labels
}
fun onTeamsImported(teams: List<Team>) {
for (team in teams) addOrUpdateTeam(team, persistTeams = false)
persistUserTeams()
makeSnackbar("Successfully imported ${teams.size} team(s)")
}
private fun addOrUpdateTeam(newTeam: Team, persistTeams: Boolean = true) {
if (newTeam.format == null) newTeam.format = fallbackFormat.toId()
var teamAdded = false
for (group in groups) {
val oldTeam = group.teams.firstOrNull { it.uniqueId == newTeam.uniqueId }
if (oldTeam != null) { // Its an update
if (oldTeam.format?.toId() == newTeam.format?.toId()) { // Format has not changed so we just replace item
val adapterPosition = listAdapter.getItemPosition(oldTeam)
val indexInGroup = group.teams.indexOf(oldTeam)
group.teams[indexInGroup] = newTeam
listAdapter.notifyItemChanged(adapterPosition)
teamAdded = true
if (oldTeam.label != newTeam.label) { // Label changed, move team to correct position
val newIndex = group.teams.sorted().indexOf(newTeam)
group.teams.add(newIndex, group.teams.removeAt(indexInGroup))
listAdapter.notifyItemMoved(adapterPosition, listAdapter.getItemPosition(newTeam))
}
} else { // Format has changed so we need to remove team from its previous group
var adapterPosition = listAdapter.getItemPosition(oldTeam)
group.teams.remove(oldTeam)
listAdapter.notifyItemRemoved(adapterPosition)
if (group.teams.isEmpty()) {
adapterPosition = listAdapter.getItemPosition(group)
groups.remove(group)
listAdapter.notifyItemRemoved(adapterPosition)
}
}
break
}
if (group.format.toId() == newTeam.format?.toId()) {
val index = group.teams.plus(newTeam).sorted().indexOf(newTeam)
group.teams.add(index, newTeam)
val adapterPosition = listAdapter.getItemPosition(newTeam)
listAdapter.notifyItemInserted(adapterPosition)
teamAdded = true
}
}
if (!teamAdded) { // No group matched our team format
val newGroup = Team.Group(newTeam.format!!.toId())
val index = groups.plus(newGroup).sortedWith(Comparator<Team.Group> { g1, g2 ->
BattleFormat.compare(battleFormats, g1.format, g2.format)
}).indexOf(newGroup)
groups.add(index, newGroup)
var adapterPosition = listAdapter.getItemPosition(newGroup)
listAdapter.notifyItemInserted(adapterPosition)
newGroup.teams.add(newTeam)
adapterPosition = listAdapter.getItemPosition(newTeam)
listAdapter.notifyItemInserted(adapterPosition)
}
homeFragment.onTeamsChanged()
if (persistTeams) persistUserTeams()
}
private fun removeTeam(team: Team) {
for (group in groups) {
val matchingTeam = group.teams.firstOrNull { it.uniqueId == team.uniqueId } ?: continue
var adapterPosition = listAdapter.getItemPosition(matchingTeam)
group.teams.remove(matchingTeam)
listAdapter.notifyItemRemoved(adapterPosition)
if (group.teams.isEmpty()) {
adapterPosition = listAdapter.getItemPosition(group)
groups.remove(group)
listAdapter.notifyItemRemoved(adapterPosition)
}
break
}
homeFragment.onTeamsChanged()
persistUserTeams()
}
private fun resolveFormatName(formatId: String): String {
battleFormats?.let {
return BattleFormat.resolveName(it, formatId)
}
return formatId
}
private fun persistUserTeams() {
fragmentScope.launch {
val success = teamsStore.store(groups)
if (!success) Snackbar.make(binding.root, "Error while saving teams", Snackbar.LENGTH_LONG).show()
}
}
private fun exportTeamToClipboard(team: Team) {
fragmentScope.launch {
val result = SmogonTeamBuilder.buildTeams(assetLoader, listOf(team))
clipboardManager.setPrimaryClip(ClipData.newPlainText("Exported Teams", result))
makeSnackbar("${team.label} copied to clipboard")
}
}
private inner class TeamListAdapter(
private val itemClickListener: OnItemClickListener
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val VIEW_TYPE_CATEGORY = 0
private val VIEW_TYPE_ITEM = 1
inner class CategoryHolder(val binding: ListCategoryTeamBinding) : RecyclerView.ViewHolder(binding.root)
inner class ItemHolder(val binding: ListItemTeamBinding, var job: Job? = null) : RecyclerView.ViewHolder(binding.root), View.OnClickListener, View.OnLongClickListener {
val pokemonViews = binding.run {
listOf(imageViewPokemon1, imageViewPokemon2, imageViewPokemon3,
imageViewPokemon4, imageViewPokemon5, imageViewPokemon6)
}
init {
binding.copyButton.setOnClickListener(this)
binding.root.setOnClickListener(this)
binding.root.setOnLongClickListener(this)
}
override fun onClick(v: View?) {
if (v == binding.copyButton) {
exportTeamToClipboard(getItem(adapterPosition) as Team)
} else {
itemClickListener.onItemClick(itemView, this, adapterPosition)
}
}
override fun onLongClick(v: View?): Boolean {
makeSnackbar("Swipe to the left to remove a team from the list")
return true
}
}
override fun getItemCount(): Int {
var count = 0
groups.forEach { g -> count += 1 + g.teams.size }
return count
}
fun getItem(position: Int): Any? {
var count = -1
groups.forEach { g -> if (++count == position) return g else g.teams.forEach { if (++count == position) return it } }
return null
}
fun getItemPosition(item: Any): Int {
var count = -1
groups.forEach { g -> ++count; if (g == item) return count else g.teams.forEach { ++count; if (it == item) return count } }
return -1
}
override fun getItemViewType(position: Int) = if (getItem(position) is Team) VIEW_TYPE_ITEM else VIEW_TYPE_CATEGORY
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = when (viewType) {
VIEW_TYPE_CATEGORY -> CategoryHolder(ListCategoryTeamBinding.inflate(layoutInflater, parent, false))
else -> ItemHolder(ListItemTeamBinding.inflate(layoutInflater, parent, false))
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is CategoryHolder) {
val group = getItem(position) as Team.Group
holder.binding.text1.text = resolveFormatName(group.format)
} else if (holder is ItemHolder) {
val team = getItem(position) as Team
holder.binding.textViewTitle.text = team.label
holder.pokemonViews.forEach { it.setImageDrawable(null) }
holder.job?.cancel()
if (team.pokemons.isNotEmpty()) {
holder.job = fragmentScope.launch {
assetLoader.dexIcons(*team.pokemons.map { it.species.toId() }.toTypedArray()).forEachIndexed { index, bitmap ->
val drawable = BitmapDrawable(resources, bitmap)
holder.pokemonViews[index].setImageDrawable(drawable)
}
}
}
}
}
}
}