Added new casting dialog.

This commit is contained in:
Koen J 2025-04-29 15:22:06 +02:00
commit ee7b89ec6e
9 changed files with 375 additions and 340 deletions

View file

@ -69,7 +69,6 @@ class StateCasting {
private var _started = false; private var _started = false;
var devices: HashMap<String, CastingDevice> = hashMapOf(); var devices: HashMap<String, CastingDevice> = hashMapOf();
var rememberedDevices: ArrayList<CastingDevice> = arrayListOf();
val onDeviceAdded = Event1<CastingDevice>(); val onDeviceAdded = Event1<CastingDevice>();
val onDeviceChanged = Event1<CastingDevice>(); val onDeviceChanged = Event1<CastingDevice>();
val onDeviceRemoved = Event1<CastingDevice>(); val onDeviceRemoved = Event1<CastingDevice>();
@ -156,9 +155,6 @@ class StateCasting {
Logger.i(TAG, "CastingService starting..."); Logger.i(TAG, "CastingService starting...");
rememberedDevices.clear();
rememberedDevices.addAll(_storage.deviceInfos.map { deviceFromCastingDeviceInfo(it) });
_castServer.start(); _castServer.start();
enableDeveloper(true); enableDeveloper(true);
@ -370,9 +366,6 @@ class StateCasting {
invokeInMainScopeIfRequired { onActiveDeviceTimeChanged.emit(it) }; invokeInMainScopeIfRequired { onActiveDeviceTimeChanged.emit(it) };
}; };
addRememberedDevice(device);
Logger.i(TAG, "Device added to active discovery. Active discovery now contains ${_storage.getDevicesCount()} devices.")
try { try {
device.start(); device.start();
} catch (e: Throwable) { } catch (e: Throwable) {
@ -394,21 +387,22 @@ class StateCasting {
return addRememberedDevice(device); return addRememberedDevice(device);
} }
fun addRememberedDevice(device: CastingDevice): CastingDeviceInfo { fun getRememberedCastingDevices(): List<CastingDevice> {
val deviceInfo = device.getDeviceInfo() return _storage.getDevices().map { deviceFromCastingDeviceInfo(it) }
val foundInfo = _storage.addDevice(deviceInfo)
if (foundInfo == deviceInfo) {
rememberedDevices.add(device);
return foundInfo;
} }
return foundInfo; fun getRememberedCastingDeviceNames(): List<String> {
return _storage.getDeviceNames()
}
fun addRememberedDevice(device: CastingDevice): CastingDeviceInfo {
val deviceInfo = device.getDeviceInfo()
return _storage.addDevice(deviceInfo)
} }
fun removeRememberedDevice(device: CastingDevice) { fun removeRememberedDevice(device: CastingDevice) {
val name = device.name ?: return; val name = device.name ?: return
_storage.removeDevice(name); _storage.removeDevice(name)
rememberedDevices.remove(device);
} }
private fun invokeInMainScopeIfRequired(action: () -> Unit){ private fun invokeInMainScopeIfRequired(action: () -> Unit){

View file

@ -9,7 +9,9 @@ import android.view.View
import android.widget.Button import android.widget.Button
import android.widget.ImageButton import android.widget.ImageButton
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.futo.platformplayer.R import com.futo.platformplayer.R
@ -21,22 +23,21 @@ import com.futo.platformplayer.casting.StateCasting
import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.states.StateApp
import com.futo.platformplayer.views.adapters.DeviceAdapter import com.futo.platformplayer.views.adapters.DeviceAdapter
import com.futo.platformplayer.views.adapters.DeviceAdapterEntry
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class ConnectCastingDialog(context: Context?) : AlertDialog(context) { class ConnectCastingDialog(context: Context?) : AlertDialog(context) {
private lateinit var _imageLoader: ImageView; private lateinit var _imageLoader: ImageView;
private lateinit var _buttonClose: Button; private lateinit var _buttonClose: Button;
private lateinit var _buttonAdd: ImageButton; private lateinit var _buttonAdd: LinearLayout;
private lateinit var _buttonScanQR: ImageButton; private lateinit var _buttonScanQR: LinearLayout;
private lateinit var _textNoDevicesFound: TextView; private lateinit var _textNoDevicesFound: TextView;
private lateinit var _textNoDevicesRemembered: TextView;
private lateinit var _recyclerDevices: RecyclerView; private lateinit var _recyclerDevices: RecyclerView;
private lateinit var _recyclerRememberedDevices: RecyclerView;
private lateinit var _adapter: DeviceAdapter; private lateinit var _adapter: DeviceAdapter;
private lateinit var _rememberedAdapter: DeviceAdapter; private val _devices: MutableSet<String> = mutableSetOf()
private val _devices: ArrayList<CastingDevice> = arrayListOf(); private val _rememberedDevices: MutableSet<String> = mutableSetOf()
private val _rememberedDevices: ArrayList<CastingDevice> = arrayListOf(); private val _unifiedDevices: MutableList<DeviceAdapterEntry> = mutableListOf()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -45,42 +46,40 @@ class ConnectCastingDialog(context: Context?) : AlertDialog(context) {
_imageLoader = findViewById(R.id.image_loader); _imageLoader = findViewById(R.id.image_loader);
_buttonClose = findViewById(R.id.button_close); _buttonClose = findViewById(R.id.button_close);
_buttonAdd = findViewById(R.id.button_add); _buttonAdd = findViewById(R.id.button_add);
_buttonScanQR = findViewById(R.id.button_scan_qr); _buttonScanQR = findViewById(R.id.button_qr);
_recyclerDevices = findViewById(R.id.recycler_devices); _recyclerDevices = findViewById(R.id.recycler_devices);
_recyclerRememberedDevices = findViewById(R.id.recycler_remembered_devices);
_textNoDevicesFound = findViewById(R.id.text_no_devices_found); _textNoDevicesFound = findViewById(R.id.text_no_devices_found);
_textNoDevicesRemembered = findViewById(R.id.text_no_devices_remembered);
_adapter = DeviceAdapter(_devices, false); _adapter = DeviceAdapter(_unifiedDevices)
_recyclerDevices.adapter = _adapter; _recyclerDevices.adapter = _adapter;
_recyclerDevices.layoutManager = LinearLayoutManager(context); _recyclerDevices.layoutManager = LinearLayoutManager(context);
_rememberedAdapter = DeviceAdapter(_rememberedDevices, true); _adapter.onPin.subscribe { d ->
_rememberedAdapter.onRemove.subscribe { d -> val isRemembered = _rememberedDevices.contains(d.name)
if (StateCasting.instance.activeDevice == d) { val newIsRemembered = !isRemembered
d.stopCasting(); if (newIsRemembered) {
StateCasting.instance.addRememberedDevice(d)
val name = d.name
if (name != null) {
_rememberedDevices.add(name)
}
} else {
StateCasting.instance.removeRememberedDevice(d)
_rememberedDevices.remove(d.name)
}
updateUnifiedList()
} }
StateCasting.instance.removeRememberedDevice(d); //TODO: Integrate remembered into the main list
val index = _rememberedDevices.indexOf(d); //TODO: Add green indicator to indicate a device is oneline
if (index != -1) { //TODO: Add pinning
_rememberedDevices.removeAt(index); //TODO: Implement QR code as an option in add manually
_rememberedAdapter.notifyItemRemoved(index); //TODO: Remove start button
}
_textNoDevicesRemembered.visibility = if (_rememberedDevices.isEmpty()) View.VISIBLE else View.GONE;
_recyclerRememberedDevices.visibility = if (_rememberedDevices.isNotEmpty()) View.VISIBLE else View.GONE;
};
_rememberedAdapter.onConnect.subscribe { _ ->
dismiss()
//UIDialogs.showCastingDialog(context)
}
_adapter.onConnect.subscribe { _ -> _adapter.onConnect.subscribe { _ ->
dismiss() dismiss()
//UIDialogs.showCastingDialog(context) //UIDialogs.showCastingDialog(context)
} }
_recyclerRememberedDevices.adapter = _rememberedAdapter;
_recyclerRememberedDevices.layoutManager = LinearLayoutManager(context);
_buttonClose.setOnClickListener { dismiss(); }; _buttonClose.setOnClickListener { dismiss(); };
_buttonAdd.setOnClickListener { _buttonAdd.setOnClickListener {
@ -105,77 +104,112 @@ class ConnectCastingDialog(context: Context?) : AlertDialog(context) {
Logger.i(TAG, "Dialog shown."); Logger.i(TAG, "Dialog shown.");
StateCasting.instance.startDiscovering() StateCasting.instance.startDiscovering()
(_imageLoader.drawable as Animatable?)?.start(); (_imageLoader.drawable as Animatable?)?.start();
_devices.clear(); synchronized(StateCasting.instance.devices) {
synchronized (StateCasting.instance.devices) { _devices.addAll(StateCasting.instance.devices.values.mapNotNull { it.name })
_devices.addAll(StateCasting.instance.devices.values);
} }
_rememberedDevices.clear(); _rememberedDevices.addAll(StateCasting.instance.getRememberedCastingDeviceNames())
synchronized (StateCasting.instance.rememberedDevices) { updateUnifiedList()
_rememberedDevices.addAll(StateCasting.instance.rememberedDevices);
}
_textNoDevicesFound.visibility = if (_devices.isEmpty()) View.VISIBLE else View.GONE;
_recyclerDevices.visibility = if (_devices.isNotEmpty()) View.VISIBLE else View.GONE;
_textNoDevicesRemembered.visibility = if (_rememberedDevices.isEmpty()) View.VISIBLE else View.GONE;
_recyclerRememberedDevices.visibility = if (_rememberedDevices.isNotEmpty()) View.VISIBLE else View.GONE;
StateCasting.instance.onDeviceAdded.subscribe(this) { d -> StateCasting.instance.onDeviceAdded.subscribe(this) { d ->
_devices.add(d); val name = d.name
_adapter.notifyItemInserted(_devices.size - 1); if (name != null)
_textNoDevicesFound.visibility = View.GONE; _devices.add(name)
_recyclerDevices.visibility = View.VISIBLE; updateUnifiedList()
}; }
StateCasting.instance.onDeviceChanged.subscribe(this) { d -> StateCasting.instance.onDeviceChanged.subscribe(this) { d ->
val index = _devices.indexOf(d); val index = _unifiedDevices.indexOfFirst { it.castingDevice.name == d.name }
if (index == -1) { if (index != -1) {
return@subscribe; _unifiedDevices[index] = DeviceAdapterEntry(d, _unifiedDevices[index].isPinnedDevice, _unifiedDevices[index].isOnlineDevice)
_adapter.notifyItemChanged(index)
}
} }
_devices[index] = d;
_adapter.notifyItemChanged(index);
};
StateCasting.instance.onDeviceRemoved.subscribe(this) { d -> StateCasting.instance.onDeviceRemoved.subscribe(this) { d ->
val index = _devices.indexOf(d); _devices.remove(d.name)
if (index == -1) { updateUnifiedList()
return@subscribe;
} }
_devices.removeAt(index);
_adapter.notifyItemRemoved(index);
_textNoDevicesFound.visibility = if (_devices.isEmpty()) View.VISIBLE else View.GONE;
_recyclerDevices.visibility = if (_devices.isNotEmpty()) View.VISIBLE else View.GONE;
};
StateCasting.instance.onActiveDeviceConnectionStateChanged.subscribe(this) { _, connectionState -> StateCasting.instance.onActiveDeviceConnectionStateChanged.subscribe(this) { _, connectionState ->
if (connectionState != CastConnectionState.CONNECTED) { if (connectionState == CastConnectionState.CONNECTED) {
return@subscribe; StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) {
dismiss()
}
}
} }
StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) { _textNoDevicesFound.visibility = if (_devices.isEmpty()) View.VISIBLE else View.GONE;
dismiss(); _recyclerDevices.visibility = if (_devices.isNotEmpty()) View.VISIBLE else View.GONE;
};
};
_adapter.notifyDataSetChanged();
_rememberedAdapter.notifyDataSetChanged();
} }
override fun dismiss() { override fun dismiss() {
super.dismiss(); super.dismiss()
(_imageLoader.drawable as Animatable?)?.stop()
(_imageLoader.drawable as Animatable?)?.stop();
StateCasting.instance.stopDiscovering() StateCasting.instance.stopDiscovering()
StateCasting.instance.onDeviceAdded.remove(this); StateCasting.instance.onDeviceAdded.remove(this)
StateCasting.instance.onDeviceChanged.remove(this); StateCasting.instance.onDeviceChanged.remove(this)
StateCasting.instance.onDeviceRemoved.remove(this); StateCasting.instance.onDeviceRemoved.remove(this)
StateCasting.instance.onActiveDeviceConnectionStateChanged.remove(this); StateCasting.instance.onActiveDeviceConnectionStateChanged.remove(this)
}
private fun updateUnifiedList() {
val oldList = ArrayList(_unifiedDevices)
val newList = buildUnifiedList()
val diffResult = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
override fun getOldListSize() = oldList.size
override fun getNewListSize() = newList.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldList[oldItemPosition]
val newItem = newList[newItemPosition]
return oldItem.castingDevice.name == newItem.castingDevice.name
&& oldItem.castingDevice.isReady == newItem.castingDevice.isReady
&& oldItem.isOnlineDevice == newItem.isOnlineDevice
&& oldItem.isPinnedDevice == newItem.isPinnedDevice
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldList[oldItemPosition]
val newItem = newList[newItemPosition]
return oldItem.castingDevice.name == newItem.castingDevice.name
&& oldItem.castingDevice.isReady == newItem.castingDevice.isReady
&& oldItem.isOnlineDevice == newItem.isOnlineDevice
&& oldItem.isPinnedDevice == newItem.isPinnedDevice
}
})
_unifiedDevices.clear()
_unifiedDevices.addAll(newList)
diffResult.dispatchUpdatesTo(_adapter)
_textNoDevicesFound.visibility = if (_unifiedDevices.isEmpty()) View.VISIBLE else View.GONE
_recyclerDevices.visibility = if (_unifiedDevices.isNotEmpty()) View.VISIBLE else View.GONE
}
private fun buildUnifiedList(): List<DeviceAdapterEntry> {
val onlineDevices = StateCasting.instance.devices.values.associateBy { it.name }
val rememberedDevices = StateCasting.instance.getRememberedCastingDevices().associateBy { it.name }
val unifiedList = mutableListOf<DeviceAdapterEntry>()
val intersectionNames = _devices.intersect(_rememberedDevices)
for (name in intersectionNames) {
onlineDevices[name]?.let { unifiedList.add(DeviceAdapterEntry(it, true, true)) }
}
val onlineOnlyNames = _devices - _rememberedDevices
for (name in onlineOnlyNames) {
onlineDevices[name]?.let { unifiedList.add(DeviceAdapterEntry(it, false, true)) }
}
val rememberedOnlyNames = _rememberedDevices - _devices
for (name in rememberedOnlyNames) {
rememberedDevices[name]?.let { unifiedList.add(DeviceAdapterEntry(it, true, false)) }
}
return unifiedList
} }
companion object { companion object {

View file

@ -19,6 +19,11 @@ class CastingDeviceInfoStorage : FragmentedStorageFileJson() {
return deviceInfos.toList(); return deviceInfos.toList();
} }
@Synchronized
fun getDeviceNames() : List<String> {
return deviceInfos.map { it.name }.toList();
}
@Synchronized @Synchronized
fun addDevice(castingDeviceInfo: CastingDeviceInfo): CastingDeviceInfo { fun addDevice(castingDeviceInfo: CastingDeviceInfo): CastingDeviceInfo {
val foundDeviceInfo = deviceInfos.firstOrNull { d -> d.name == castingDeviceInfo.name } val foundDeviceInfo = deviceInfos.firstOrNull { d -> d.name == castingDeviceInfo.name }

View file

@ -7,16 +7,16 @@ import com.futo.platformplayer.R
import com.futo.platformplayer.casting.CastingDevice import com.futo.platformplayer.casting.CastingDevice
import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.constructs.Event1
class DeviceAdapter : RecyclerView.Adapter<DeviceViewHolder> { data class DeviceAdapterEntry(val castingDevice: CastingDevice, val isPinnedDevice: Boolean, val isOnlineDevice: Boolean)
private val _devices: ArrayList<CastingDevice>;
private val _isRememberedDevice: Boolean;
var onRemove = Event1<CastingDevice>(); class DeviceAdapter : RecyclerView.Adapter<DeviceViewHolder> {
private val _devices: List<DeviceAdapterEntry>;
var onPin = Event1<CastingDevice>();
var onConnect = Event1<CastingDevice>(); var onConnect = Event1<CastingDevice>();
constructor(devices: ArrayList<CastingDevice>, isRememberedDevice: Boolean) : super() { constructor(devices: List<DeviceAdapterEntry>) : super() {
_devices = devices; _devices = devices;
_isRememberedDevice = isRememberedDevice;
} }
override fun getItemCount() = _devices.size; override fun getItemCount() = _devices.size;
@ -24,13 +24,13 @@ class DeviceAdapter : RecyclerView.Adapter<DeviceViewHolder> {
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): DeviceViewHolder { override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): DeviceViewHolder {
val view = LayoutInflater.from(viewGroup.context).inflate(R.layout.list_device, viewGroup, false); val view = LayoutInflater.from(viewGroup.context).inflate(R.layout.list_device, viewGroup, false);
val holder = DeviceViewHolder(view); val holder = DeviceViewHolder(view);
holder.setIsRememberedDevice(_isRememberedDevice); holder.onPin.subscribe { d -> onPin.emit(d); };
holder.onRemove.subscribe { d -> onRemove.emit(d); };
holder.onConnect.subscribe { d -> onConnect.emit(d); } holder.onConnect.subscribe { d -> onConnect.emit(d); }
return holder; return holder;
} }
override fun onBindViewHolder(viewHolder: DeviceViewHolder, position: Int) { override fun onBindViewHolder(viewHolder: DeviceViewHolder, position: Int) {
viewHolder.bind(_devices[position]); val p = _devices[position];
viewHolder.bind(p.castingDevice, p.isOnlineDevice, p.isPinnedDevice);
} }
} }

View file

@ -2,9 +2,11 @@ package com.futo.platformplayer.views.adapters
import android.graphics.drawable.Animatable import android.graphics.drawable.Animatable
import android.view.View import android.view.View
import android.widget.FrameLayout
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.RecyclerView.ViewHolder import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.futo.platformplayer.R import com.futo.platformplayer.R
import com.futo.platformplayer.casting.AirPlayCastingDevice import com.futo.platformplayer.casting.AirPlayCastingDevice
@ -14,70 +16,62 @@ import com.futo.platformplayer.casting.ChromecastCastingDevice
import com.futo.platformplayer.casting.FCastCastingDevice import com.futo.platformplayer.casting.FCastCastingDevice
import com.futo.platformplayer.casting.StateCasting import com.futo.platformplayer.casting.StateCasting
import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.constructs.Event2
import androidx.core.view.isVisible
class DeviceViewHolder : ViewHolder { class DeviceViewHolder : ViewHolder {
private val _layoutDevice: FrameLayout;
private val _imageDevice: ImageView; private val _imageDevice: ImageView;
private val _textName: TextView; private val _textName: TextView;
private val _textType: TextView; private val _textType: TextView;
private val _textNotReady: TextView; private val _textNotReady: TextView;
private val _buttonDisconnect: LinearLayout;
private val _buttonConnect: LinearLayout;
private val _buttonRemove: LinearLayout;
private val _imageLoader: ImageView; private val _imageLoader: ImageView;
private val _imageOnline: ImageView;
private val _root: ConstraintLayout;
private var _animatableLoader: Animatable? = null; private var _animatableLoader: Animatable? = null;
private var _isRememberedDevice: Boolean = false; private var _imagePin: ImageView;
var device: CastingDevice? = null var device: CastingDevice? = null
private set private set
var onRemove = Event1<CastingDevice>(); var onPin = Event1<CastingDevice>();
val onConnect = Event1<CastingDevice>(); val onConnect = Event1<CastingDevice>();
constructor(view: View) : super(view) { constructor(view: View) : super(view) {
_root = view.findViewById(R.id.layout_root);
_layoutDevice = view.findViewById(R.id.layout_device);
_imageDevice = view.findViewById(R.id.image_device); _imageDevice = view.findViewById(R.id.image_device);
_textName = view.findViewById(R.id.text_name); _textName = view.findViewById(R.id.text_name);
_textType = view.findViewById(R.id.text_type); _textType = view.findViewById(R.id.text_type);
_textNotReady = view.findViewById(R.id.text_not_ready); _textNotReady = view.findViewById(R.id.text_not_ready);
_buttonDisconnect = view.findViewById(R.id.button_disconnect);
_buttonConnect = view.findViewById(R.id.button_connect);
_buttonRemove = view.findViewById(R.id.button_remove);
_imageLoader = view.findViewById(R.id.image_loader); _imageLoader = view.findViewById(R.id.image_loader);
_imageOnline = view.findViewById(R.id.image_online);
_imagePin = view.findViewById(R.id.image_pin);
val d = _imageLoader.drawable; val d = _imageLoader.drawable;
if (d is Animatable) { if (d is Animatable) {
_animatableLoader = d; _animatableLoader = d;
} }
_buttonDisconnect.setOnClickListener { val connect = {
StateCasting.instance.activeDevice?.stopCasting(); device?.let { dev ->
updateButton();
};
_buttonConnect.setOnClickListener {
val dev = device ?: return@setOnClickListener;
StateCasting.instance.activeDevice?.stopCasting(); StateCasting.instance.activeDevice?.stopCasting();
StateCasting.instance.connectDevice(dev); StateCasting.instance.connectDevice(dev);
onConnect.emit(dev); onConnect.emit(dev);
}; }
}
_buttonRemove.setOnClickListener { _textName.setOnClickListener { connect() };
_textType.setOnClickListener { connect() };
_layoutDevice.setOnClickListener { connect() };
_imagePin.setOnClickListener {
val dev = device ?: return@setOnClickListener; val dev = device ?: return@setOnClickListener;
onRemove.emit(dev); onPin.emit(dev);
}; }
StateCasting.instance.onActiveDeviceConnectionStateChanged.subscribe(this) { _, _ ->
updateButton();
} }
setIsRememberedDevice(false); fun bind(d: CastingDevice, isOnlineDevice: Boolean, isPinnedDevice: Boolean) {
}
fun setIsRememberedDevice(isRememberedDevice: Boolean) {
_isRememberedDevice = isRememberedDevice;
_buttonRemove.visibility = if (isRememberedDevice) View.VISIBLE else View.GONE;
}
fun bind(d: CastingDevice) {
if (d is ChromecastCastingDevice) { if (d is ChromecastCastingDevice) {
_imageDevice.setImageResource(R.drawable.ic_chromecast); _imageDevice.setImageResource(R.drawable.ic_chromecast);
_textType.text = "Chromecast"; _textType.text = "Chromecast";
@ -90,54 +84,47 @@ class DeviceViewHolder : ViewHolder {
} }
_textName.text = d.name; _textName.text = d.name;
device = d; _imageOnline.visibility = if (isOnlineDevice) View.VISIBLE else View.GONE
updateButton();
}
private fun updateButton() {
val d = device ?: return;
if (!d.isReady) { if (!d.isReady) {
_buttonConnect.visibility = View.GONE;
_buttonDisconnect.visibility = View.GONE;
_imageLoader.visibility = View.GONE; _imageLoader.visibility = View.GONE;
_textNotReady.visibility = View.VISIBLE; _textNotReady.visibility = View.VISIBLE;
return; _imagePin.visibility = View.GONE;
} } else {
_textNotReady.visibility = View.GONE; _textNotReady.visibility = View.GONE;
val dev = StateCasting.instance.activeDevice; val dev = StateCasting.instance.activeDevice;
if (dev == d) { if (dev == d) {
if (dev.connectionState == CastConnectionState.CONNECTED) { if (dev.connectionState == CastConnectionState.CONNECTED) {
_buttonConnect.visibility = View.GONE;
_buttonDisconnect.visibility = View.VISIBLE;
_imageLoader.visibility = View.GONE; _imageLoader.visibility = View.GONE;
_textNotReady.visibility = View.GONE; _textNotReady.visibility = View.GONE;
_imagePin.visibility = View.VISIBLE;
} else { } else {
_buttonConnect.visibility = View.GONE;
_buttonDisconnect.visibility = View.VISIBLE;
_imageLoader.visibility = View.VISIBLE; _imageLoader.visibility = View.VISIBLE;
_textNotReady.visibility = View.GONE; _textNotReady.visibility = View.GONE;
_imagePin.visibility = View.VISIBLE;
} }
} else { } else {
if (d.isReady) { if (d.isReady) {
_buttonConnect.visibility = View.VISIBLE;
_buttonDisconnect.visibility = View.GONE;
_imageLoader.visibility = View.GONE; _imageLoader.visibility = View.GONE;
_textNotReady.visibility = View.GONE; _textNotReady.visibility = View.GONE;
_imagePin.visibility = View.VISIBLE;
} else { } else {
_buttonConnect.visibility = View.GONE;
_buttonDisconnect.visibility = View.GONE;
_imageLoader.visibility = View.GONE; _imageLoader.visibility = View.GONE;
_textNotReady.visibility = View.VISIBLE; _textNotReady.visibility = View.VISIBLE;
_imagePin.visibility = View.VISIBLE;
} }
} }
if (_imageLoader.visibility == View.VISIBLE) { _imagePin.setImageResource(if (isPinnedDevice) R.drawable.keep_24px else R.drawable.ic_pin)
if (_imageLoader.isVisible) {
_animatableLoader?.start(); _animatableLoader?.start();
} else { } else {
_animatableLoader?.stop(); _animatableLoader?.stop();
} }
} }
device = d;
}
} }

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@android:color/white"
android:pathData="M600,496.92L663.08,560L663.08,600L500,600L500,800L480,820L460,800L460,600L296.92,600L296.92,560L360,496.92L360,200L320,200L320,160L640,160L640,200L600,200L600,496.92Z"/>
</vector>

View file

@ -11,7 +11,13 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal"> android:orientation="horizontal"
android:layout_marginTop="12dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView <TextView
android:id="@+id/text_devices" android:id="@+id/text_devices"
@ -23,13 +29,30 @@
android:textColor="@color/white" android:textColor="@color/white"
android:fontFamily="@font/inter_regular" /> android:fontFamily="@font/inter_regular" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/available_devices"
android:layout_marginStart="20dp"
android:textSize="11dp"
android:textColor="@color/gray_ac"
android:fontFamily="@font/inter_medium" />
<ImageView <ImageView
android:id="@+id/image_loader" android:id="@+id/image_loader"
android:layout_width="22dp" android:layout_width="18dp"
android:layout_height="22dp" android:layout_height="18dp"
android:scaleType="fitCenter" android:scaleType="fitCenter"
app:srcCompat="@drawable/ic_loader_animated" app:srcCompat="@drawable/ic_loader_animated"
android:layout_marginStart="5dp"/> android:layout_marginStart="5dp"/>
</LinearLayout>
</LinearLayout>
<Space android:layout_width="0dp" <Space android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -38,7 +61,7 @@
<Button <Button
android:id="@+id/button_close" android:id="@+id/button_close"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:text="@string/close" android:text="@string/close"
android:textSize="14dp" android:textSize="14dp"
android:fontFamily="@font/inter_regular" android:fontFamily="@font/inter_regular"
@ -67,79 +90,102 @@
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_devices" android:id="@+id/recycler_devices"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="100dp" android:layout_height="200dp"
android:layout_marginStart="20dp" android:layout_marginStart="20dp"
android:layout_marginEnd="20dp" /> android:layout_marginEnd="20dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="20dp"/>
</LinearLayout> </LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="1px"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:background="@color/gray_ac" />
<TextView
android:id="@+id/text_remembered_devices"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/unable_to_see_the_Device_youre_looking_for_try_add_the_device_manually"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:textSize="9dp"
android:ellipsize="end"
android:textColor="@color/gray_c3"
android:maxLines="3"
android:fontFamily="@font/inter_light"
android:layout_marginTop="12dp"/>
<LinearLayout <LinearLayout
android:id="@+id/layout_remembered_devices_header" android:id="@+id/layout_remembered_devices_header"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal"> android:orientation="horizontal"
android:layout_marginTop="12dp"
<TextView android:layout_marginBottom="12dp"
android:id="@+id/text_remembered_devices"
android:layout_width="0dp"
android:layout_weight="3"
android:layout_height="wrap_content"
android:text="@string/remembered_devices"
android:layout_marginStart="20dp" android:layout_marginStart="20dp"
android:layout_marginEnd="20dp" android:layout_marginEnd="20dp">
android:textSize="14dp"
android:ellipsize="end"
android:textColor="@color/white"
android:maxLines="1"
android:fontFamily="@font/inter_regular" />
<ImageButton
android:id="@+id/button_scan_qr"
android:layout_width="40dp"
android:layout_height="40dp"
android:contentDescription="@string/cd_button_scan_qr"
android:scaleType="centerCrop"
app:srcCompat="@drawable/ic_qr"
app:tint="@color/primary" />
<Space android:layout_width="0dp" <Space android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="1" /> android:layout_weight="1" />
<ImageButton <LinearLayout
android:id="@+id/button_add" android:id="@+id/button_add"
android:layout_width="40dp" android:layout_width="wrap_content"
android:layout_height="40dp" android:layout_height="wrap_content"
android:contentDescription="@string/cd_button_add" android:orientation="horizontal"
android:scaleType="centerCrop" android:background="@drawable/background_border_2e_round_6dp"
android:layout_marginEnd="20dp"
android:gravity="center">
<ImageView
android:layout_width="22dp"
android:layout_height="22dp"
app:srcCompat="@drawable/ic_add" app:srcCompat="@drawable/ic_add"
app:tint="@color/primary" android:layout_marginStart="8dp"/>
android:layout_marginEnd="20dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/add_manually"
android:textSize="12dp"
android:textColor="@color/white"
android:fontFamily="@font/inter_medium"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:paddingStart="4dp"
android:paddingEnd="12dp" />
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/layout_remembered_devices" android:id="@+id/button_qr"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/text_no_devices_remembered"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="10dp" android:orientation="horizontal"
android:text="@string/there_are_no_remembered_devices" android:background="@drawable/background_border_2e_round_6dp"
android:layout_marginTop="10dp" android:gravity="center">
android:layout_marginBottom="20dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:textColor="@color/gray_e0" />
<androidx.recyclerview.widget.RecyclerView <ImageView
android:id="@+id/recycler_remembered_devices" android:layout_width="22dp"
android:layout_width="match_parent" android:layout_height="22dp"
android:layout_height="100dp" app:srcCompat="@drawable/ic_qr"
android:layout_marginStart="20dp" android:layout_marginStart="8dp"/>
android:layout_marginEnd="20dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/scan_qr"
android:textSize="12dp"
android:textColor="@color/white"
android:fontFamily="@font/inter_medium"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:paddingStart="4dp"
android:paddingEnd="12dp" />
</LinearLayout>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View file

@ -4,18 +4,34 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="35dp" android:layout_height="35dp"
android:clickable="true"> android:clickable="true"
android:id="@+id/layout_root">
<FrameLayout
android:id="@+id/layout_device"
android:layout_width="25dp"
android:layout_height="25dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<ImageView <ImageView
android:id="@+id/image_device" android:id="@+id/image_device"
android:layout_width="25dp" android:layout_width="match_parent"
android:layout_height="25dp" android:layout_height="match_parent"
android:contentDescription="@string/cd_image_device" android:contentDescription="@string/cd_image_device"
app:srcCompat="@drawable/ic_chromecast" app:srcCompat="@drawable/ic_chromecast"
android:scaleType="fitCenter" android:scaleType="fitCenter" />
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" <ImageView
app:layout_constraintBottom_toBottomOf="parent"/> android:id="@+id/image_online"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_gravity="end|top"
android:contentDescription="@string/cd_image_device"
app:srcCompat="@drawable/ic_online"
android:scaleType="fitCenter" />
</FrameLayout>
<TextView <TextView
android:id="@+id/text_name" android:id="@+id/text_name"
@ -31,8 +47,8 @@
android:layout_marginStart="10dp" android:layout_marginStart="10dp"
android:layout_marginEnd="10dp" android:layout_marginEnd="10dp"
android:includeFontPadding="false" android:includeFontPadding="false"
app:layout_constraintTop_toTopOf="@id/image_device" app:layout_constraintTop_toTopOf="@id/layout_device"
app:layout_constraintLeft_toRightOf="@id/image_device" app:layout_constraintLeft_toRightOf="@id/layout_device"
app:layout_constraintRight_toLeftOf="@id/layout_button" /> app:layout_constraintRight_toLeftOf="@id/layout_button" />
<TextView <TextView
@ -43,12 +59,12 @@
tools:text="Chromecast" tools:text="Chromecast"
android:textSize="10dp" android:textSize="10dp"
android:fontFamily="@font/inter_extra_light" android:fontFamily="@font/inter_extra_light"
android:textColor="@color/white" android:textColor="@color/gray_ac"
android:includeFontPadding="false" android:includeFontPadding="false"
android:layout_marginStart="10dp" android:layout_marginStart="10dp"
android:layout_marginEnd="10dp" android:layout_marginEnd="10dp"
app:layout_constraintTop_toBottomOf="@id/text_name" app:layout_constraintTop_toBottomOf="@id/text_name"
app:layout_constraintLeft_toRightOf="@id/image_device" app:layout_constraintLeft_toRightOf="@id/layout_device"
app:layout_constraintRight_toLeftOf="@id/layout_button" /> app:layout_constraintRight_toLeftOf="@id/layout_button" />
<LinearLayout <LinearLayout
@ -68,74 +84,15 @@
app:srcCompat="@drawable/ic_loader_animated" app:srcCompat="@drawable/ic_loader_animated"
android:layout_marginEnd="8dp"/> android:layout_marginEnd="8dp"/>
<LinearLayout <ImageView
android:id="@+id/image_pin"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="25dp"
android:orientation="horizontal" android:contentDescription="@string/cd_image_loader"
app:layout_constraintRight_toRightOf="parent" app:srcCompat="@drawable/ic_pin"
app:layout_constraintTop_toTopOf="parent" android:layout_marginEnd="8dp"
app:layout_constraintBottom_toBottomOf="parent"> android:scaleType="fitEnd"
android:paddingStart="10dp" />
<LinearLayout
android:id="@+id/button_remove"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/background_button_accent"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:layout_marginEnd="7dp"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12dp"
android:textColor="@color/white"
android:fontFamily="@font/inter_light"
android:text="@string/remove" />
</LinearLayout>
<LinearLayout
android:id="@+id/button_disconnect"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/background_button_accent"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:paddingTop="4dp"
android:paddingBottom="4dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12dp"
android:textColor="@color/white"
android:fontFamily="@font/inter_light"
android:text="@string/stop" />
</LinearLayout>
<LinearLayout
android:id="@+id/button_connect"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/background_button_primary"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12dp"
android:textColor="@color/white"
android:fontFamily="@font/inter_light"
android:text="@string/start" />
</LinearLayout>
</LinearLayout>
<TextView <TextView
android:id="@+id/text_not_ready" android:id="@+id/text_not_ready"

View file

@ -194,7 +194,9 @@
<string name="ip">IP</string> <string name="ip">IP</string>
<string name="port">Port</string> <string name="port">Port</string>
<string name="discovered_devices">Discovered Devices</string> <string name="discovered_devices">Discovered Devices</string>
<string name="available_devices">Available devices</string>
<string name="remembered_devices">Remembered Devices</string> <string name="remembered_devices">Remembered Devices</string>
<string name="unable_to_see_the_Device_youre_looking_for_try_add_the_device_manually">Unable to see the device you\'re looking for? Try to add the device manually.</string>
<string name="there_are_no_remembered_devices">There are no remembered devices</string> <string name="there_are_no_remembered_devices">There are no remembered devices</string>
<string name="connected_to">Connected to</string> <string name="connected_to">Connected to</string>
<string name="volume">Volume</string> <string name="volume">Volume</string>
@ -204,6 +206,7 @@
<string name="previous">Previous</string> <string name="previous">Previous</string>
<string name="next">Next</string> <string name="next">Next</string>
<string name="comment">Comment</string> <string name="comment">Comment</string>
<string name="add_manually">Add manually</string>
<string name="not_empty_close">Comment is not empty, close anyway?</string> <string name="not_empty_close">Comment is not empty, close anyway?</string>
<string name="str_import">Import</string> <string name="str_import">Import</string>
<string name="my_playlist_name">My Playlist Name</string> <string name="my_playlist_name">My Playlist Name</string>