Finish Registered Hosts List on Android

This commit is contained in:
Florian Märkl 2019-10-26 19:28:39 +02:00
commit c6d84d0b22
No known key found for this signature in database
GPG key ID: 125BC8A5A6A1E857
10 changed files with 279 additions and 15 deletions

View file

@ -65,6 +65,9 @@ interface RegisteredHostDao
@Query("DELETE FROM registered_host WHERE ps4_mac == :mac")
fun deleteByMac(mac: MacAddress): Completable
@Delete
fun delete(host: RegisteredHost): Completable
@Query("SELECT COUNT(*) FROM registered_host")
fun count(): Flowable<Int>

View file

@ -0,0 +1,66 @@
/*
* This file is part of Chiaki.
*
* Chiaki is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Chiaki is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Chiaki. If not, see <https://www.gnu.org/licenses/>.
*/
package com.metallic.chiaki.settings
import android.content.Context
import android.graphics.Canvas
import android.graphics.Rect
import androidx.core.graphics.withClip
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.metallic.chiaki.R
abstract class ItemTouchSwipeCallback(context: Context): ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT)
{
private val backgroundDrawable = context.getDrawable(R.color.item_delete_background)
private val icon = context.getDrawable(R.drawable.ic_delete_row)
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder) = false
override fun onChildDraw(canvas: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean)
{
val itemView = viewHolder.itemView
val itemHeight = itemView.bottom - itemView.top
val bounds = Rect(
itemView.right + dX.toInt(),
itemView.top,
itemView.right,
itemView.bottom
)
backgroundDrawable?.bounds = bounds
backgroundDrawable?.draw(canvas)
val icon = icon
if(icon != null)
{
val iconMargin = (itemHeight - icon.intrinsicHeight) / 2
val iconTop = itemView.top + iconMargin
val iconLeft = itemView.right - iconMargin - icon.intrinsicWidth
val iconRight = itemView.right - iconMargin
val iconBottom = iconTop + icon.intrinsicHeight
canvas.withClip(bounds) {
icon.setBounds(iconLeft, iconTop, iconRight, iconBottom)
icon.draw(canvas)
}
}
super.onChildDraw(canvas, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
}
}

View file

@ -0,0 +1,50 @@
/*
* This file is part of Chiaki.
*
* Chiaki is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Chiaki is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Chiaki. If not, see <https://www.gnu.org/licenses/>.
*/
package com.metallic.chiaki.settings
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.metallic.chiaki.R
import com.metallic.chiaki.common.RegisteredHost
import com.metallic.chiaki.common.ext.inflate
import kotlinx.android.synthetic.main.item_registered_host.view.*
class SettingsRegisteredHostsAdapter: RecyclerView.Adapter<SettingsRegisteredHostsAdapter.ViewHolder>()
{
class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView)
var hosts: List<RegisteredHost> = listOf()
set(value)
{
field = value
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(parent.inflate(R.layout.item_registered_host))
override fun getItemCount() = hosts.size
override fun onBindViewHolder(holder: ViewHolder, position: Int)
{
val view = holder.itemView
val host = hosts[position]
view.nameTextView.text = host.ps4Nickname
view.summaryTextView.text = host.ps4Mac.toString()
}
}

View file

@ -17,40 +17,72 @@
package com.metallic.chiaki.settings
import android.app.ActivityOptions
import android.content.Intent
import android.content.res.Resources
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatDialogFragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.metallic.chiaki.R
import com.metallic.chiaki.common.ext.putRevealExtra
import com.metallic.chiaki.common.ext.viewModelFactory
import com.metallic.chiaki.common.getDatabase
import com.metallic.chiaki.regist.RegistActivity
import kotlinx.android.synthetic.main.fragment_settings_registered_hosts.*
class SettingsRegisteredHostsFragment: PreferenceFragmentCompat(), TitleFragment
class SettingsRegisteredHostsFragment: AppCompatDialogFragment(), TitleFragment
{
private lateinit var viewModel: SettingsRegisteredHostsViewModel
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?)
{
val context = preferenceManager.context
preferenceScreen = preferenceManager.createPreferenceScreen(context)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
inflater.inflate(R.layout.fragment_settings_registered_hosts, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?)
{
viewModel = ViewModelProviders
.of(this, viewModelFactory { SettingsRegisteredHostsViewModel(getDatabase(context!!)) })
.get(SettingsRegisteredHostsViewModel::class.java)
viewModel.registeredHosts.observe(this, Observer {
preferenceScreen.removeAll()
it.forEach { host ->
val pref = Preference(context)
pref.title = host.ps4Nickname
pref.summary = host.ps4Mac.toString()
pref.setIcon(R.drawable.ic_console_simple)
preferenceScreen.addPreference(pref)
val adapter = SettingsRegisteredHostsAdapter()
hostsRecyclerView.layoutManager = LinearLayoutManager(context)
hostsRecyclerView.adapter = adapter
val itemTouchSwipeCallback = object : ItemTouchSwipeCallback(context!!)
{
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int)
{
val pos = viewHolder.adapterPosition
val host = viewModel.registeredHosts.value?.getOrNull(pos) ?: return
MaterialAlertDialogBuilder(viewHolder.itemView.context)
.setMessage(getString(R.string.alert_message_delete_registered_host, host.ps4Nickname, host.ps4Mac.toString()))
.setPositiveButton(R.string.alert_action_delete_registered_host) { _, _ ->
viewModel.deleteHost(host)
}
.setNegativeButton(R.string.alert_action_keep_registered_host) { _, _ ->
adapter.notifyItemChanged(pos) // to reset the swipe
}
.create()
.show()
}
}
ItemTouchHelper(itemTouchSwipeCallback).attachToRecyclerView(hostsRecyclerView)
viewModel.registeredHosts.observe(this, Observer {
adapter.hosts = it
})
floatingActionButton.setOnClickListener {
Intent(context, RegistActivity::class.java).also {
it.putRevealExtra(floatingActionButton, rootLayout)
startActivity(it, ActivityOptions.makeSceneTransitionAnimation(activity).toBundle())
}
}
}
override fun getTitle(resources: Resources): String = resources.getString(R.string.preferences_registered_hosts_title)

View file

@ -19,11 +19,32 @@ package com.metallic.chiaki.settings
import androidx.lifecycle.ViewModel
import com.metallic.chiaki.common.AppDatabase
import com.metallic.chiaki.common.RegisteredHost
import com.metallic.chiaki.common.ext.toLiveData
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.addTo
import io.reactivex.schedulers.Schedulers
class SettingsRegisteredHostsViewModel(val database: AppDatabase): ViewModel()
{
private val disposable = CompositeDisposable()
val registeredHosts by lazy {
database.registeredHostDao().getAll().toLiveData()
}
fun deleteHost(host: RegisteredHost)
{
database.registeredHostDao()
.delete(host)
.subscribeOn(Schedulers.io())
.subscribe()
.addTo(disposable)
}
override fun onCleared()
{
super.onCleared()
disposable.dispose()
}
}

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#ffffff"
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
</vector>

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/rootLayout"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/hostsRecyclerView"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/floatingActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/selector_add_fab"
android:layout_margin="16dp"
app:fabSize="normal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools"
android:minHeight="?android:attr/listPreferredItemHeight"
android:gravity="center_vertical"
android:paddingEnd="?android:attr/scrollbarSize"
android:background="?android:attr/selectableItemBackground"
android:paddingTop="8dp"
android:paddingBottom="8dp">
<ImageView
android:id="@+id/iconImageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxWidth="48dp"
android:maxHeight="48dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginStart="16dp"
android:src="@drawable/ic_console_simple"/>
<TextView
android:id="@+id/nameTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?android:attr/textColorPrimary"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
app:layout_constraintStart_toEndOf="@id/iconImageView"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginStart="32dp"
tools:text="Name" />
<TextView
android:id="@+id/summaryTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@android:id/title"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorSecondary"
android:maxLines="4"
app:layout_constraintStart_toStartOf="@id/nameTextView"
app:layout_constraintTop_toBottomOf="@id/nameTextView"
app:layout_constraintBottom_toBottomOf="parent"
tools:text="Summary"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -12,4 +12,6 @@
<color name="control_primary">#22ffffff</color>
<color name="control_pressed">#88ffffff</color>
<color name="item_delete_background">#B71C1C</color>
</resources>

View file

@ -58,6 +58,9 @@
<string name="preferences_bitrate_title">Bitrate</string>
<string name="preferences_log_verbose_title">Verbose Logging</string>
<string name="preferences_log_verbose_summary">Warning: This logs a LOT! Don\'t enable for regular use.</string>
<string name="alert_message_delete_registered_host">Are you sure you want to delete the registered console %s with ID %s?</string>
<string name="alert_action_delete_registered_host">Delete</string>
<string name="alert_action_keep_registered_host">Keep</string>
<!-- Don't localize these -->
<string name="preferences_discovery_enabled_key">discovery_enabled</string>