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

View file

@ -9,7 +9,9 @@ import android.view.View
import android.widget.Button
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
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.states.StateApp
import com.futo.platformplayer.views.adapters.DeviceAdapter
import com.futo.platformplayer.views.adapters.DeviceAdapterEntry
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class ConnectCastingDialog(context: Context?) : AlertDialog(context) {
private lateinit var _imageLoader: ImageView;
private lateinit var _buttonClose: Button;
private lateinit var _buttonAdd: ImageButton;
private lateinit var _buttonScanQR: ImageButton;
private lateinit var _buttonAdd: LinearLayout;
private lateinit var _buttonScanQR: LinearLayout;
private lateinit var _textNoDevicesFound: TextView;
private lateinit var _textNoDevicesRemembered: TextView;
private lateinit var _recyclerDevices: RecyclerView;
private lateinit var _recyclerRememberedDevices: RecyclerView;
private lateinit var _adapter: DeviceAdapter;
private lateinit var _rememberedAdapter: DeviceAdapter;
private val _devices: ArrayList<CastingDevice> = arrayListOf();
private val _rememberedDevices: ArrayList<CastingDevice> = arrayListOf();
private val _devices: MutableSet<String> = mutableSetOf()
private val _rememberedDevices: MutableSet<String> = mutableSetOf()
private val _unifiedDevices: MutableList<DeviceAdapterEntry> = mutableListOf()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState);
@ -45,42 +46,40 @@ class ConnectCastingDialog(context: Context?) : AlertDialog(context) {
_imageLoader = findViewById(R.id.image_loader);
_buttonClose = findViewById(R.id.button_close);
_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);
_recyclerRememberedDevices = findViewById(R.id.recycler_remembered_devices);
_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.layoutManager = LinearLayoutManager(context);
_rememberedAdapter = DeviceAdapter(_rememberedDevices, true);
_rememberedAdapter.onRemove.subscribe { d ->
if (StateCasting.instance.activeDevice == d) {
d.stopCasting();
_adapter.onPin.subscribe { d ->
val isRemembered = _rememberedDevices.contains(d.name)
val newIsRemembered = !isRemembered
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);
val index = _rememberedDevices.indexOf(d);
if (index != -1) {
_rememberedDevices.removeAt(index);
_rememberedAdapter.notifyItemRemoved(index);
}
//TODO: Integrate remembered into the main list
//TODO: Add green indicator to indicate a device is oneline
//TODO: Add pinning
//TODO: Implement QR code as an option in add manually
//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 { _ ->
dismiss()
//UIDialogs.showCastingDialog(context)
}
_recyclerRememberedDevices.adapter = _rememberedAdapter;
_recyclerRememberedDevices.layoutManager = LinearLayoutManager(context);
_buttonClose.setOnClickListener { dismiss(); };
_buttonAdd.setOnClickListener {
@ -105,77 +104,112 @@ class ConnectCastingDialog(context: Context?) : AlertDialog(context) {
Logger.i(TAG, "Dialog shown.");
StateCasting.instance.startDiscovering()
(_imageLoader.drawable as Animatable?)?.start();
_devices.clear();
synchronized (StateCasting.instance.devices) {
_devices.addAll(StateCasting.instance.devices.values);
synchronized(StateCasting.instance.devices) {
_devices.addAll(StateCasting.instance.devices.values.mapNotNull { it.name })
}
_rememberedDevices.clear();
synchronized (StateCasting.instance.rememberedDevices) {
_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;
_rememberedDevices.addAll(StateCasting.instance.getRememberedCastingDeviceNames())
updateUnifiedList()
StateCasting.instance.onDeviceAdded.subscribe(this) { d ->
_devices.add(d);
_adapter.notifyItemInserted(_devices.size - 1);
_textNoDevicesFound.visibility = View.GONE;
_recyclerDevices.visibility = View.VISIBLE;
};
val name = d.name
if (name != null)
_devices.add(name)
updateUnifiedList()
}
StateCasting.instance.onDeviceChanged.subscribe(this) { d ->
val index = _devices.indexOf(d);
if (index == -1) {
return@subscribe;
val index = _unifiedDevices.indexOfFirst { it.castingDevice.name == d.name }
if (index != -1) {
_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 ->
val index = _devices.indexOf(d);
if (index == -1) {
return@subscribe;
_devices.remove(d.name)
updateUnifiedList()
}
_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 ->
if (connectionState != CastConnectionState.CONNECTED) {
return@subscribe;
if (connectionState == CastConnectionState.CONNECTED) {
StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) {
dismiss()
}
}
}
StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) {
dismiss();
};
};
_adapter.notifyDataSetChanged();
_rememberedAdapter.notifyDataSetChanged();
_textNoDevicesFound.visibility = if (_devices.isEmpty()) View.VISIBLE else View.GONE;
_recyclerDevices.visibility = if (_devices.isNotEmpty()) View.VISIBLE else View.GONE;
}
override fun dismiss() {
super.dismiss();
(_imageLoader.drawable as Animatable?)?.stop();
super.dismiss()
(_imageLoader.drawable as Animatable?)?.stop()
StateCasting.instance.stopDiscovering()
StateCasting.instance.onDeviceAdded.remove(this);
StateCasting.instance.onDeviceChanged.remove(this);
StateCasting.instance.onDeviceRemoved.remove(this);
StateCasting.instance.onActiveDeviceConnectionStateChanged.remove(this);
StateCasting.instance.onDeviceAdded.remove(this)
StateCasting.instance.onDeviceChanged.remove(this)
StateCasting.instance.onDeviceRemoved.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 {

View file

@ -19,6 +19,11 @@ class CastingDeviceInfoStorage : FragmentedStorageFileJson() {
return deviceInfos.toList();
}
@Synchronized
fun getDeviceNames() : List<String> {
return deviceInfos.map { it.name }.toList();
}
@Synchronized
fun addDevice(castingDeviceInfo: CastingDeviceInfo): CastingDeviceInfo {
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.constructs.Event1
class DeviceAdapter : RecyclerView.Adapter<DeviceViewHolder> {
private val _devices: ArrayList<CastingDevice>;
private val _isRememberedDevice: Boolean;
data class DeviceAdapterEntry(val castingDevice: CastingDevice, val isPinnedDevice: Boolean, val isOnlineDevice: Boolean)
var onRemove = Event1<CastingDevice>();
class DeviceAdapter : RecyclerView.Adapter<DeviceViewHolder> {
private val _devices: List<DeviceAdapterEntry>;
var onPin = Event1<CastingDevice>();
var onConnect = Event1<CastingDevice>();
constructor(devices: ArrayList<CastingDevice>, isRememberedDevice: Boolean) : super() {
constructor(devices: List<DeviceAdapterEntry>) : super() {
_devices = devices;
_isRememberedDevice = isRememberedDevice;
}
override fun getItemCount() = _devices.size;
@ -24,13 +24,13 @@ class DeviceAdapter : RecyclerView.Adapter<DeviceViewHolder> {
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): DeviceViewHolder {
val view = LayoutInflater.from(viewGroup.context).inflate(R.layout.list_device, viewGroup, false);
val holder = DeviceViewHolder(view);
holder.setIsRememberedDevice(_isRememberedDevice);
holder.onRemove.subscribe { d -> onRemove.emit(d); };
holder.onPin.subscribe { d -> onPin.emit(d); };
holder.onConnect.subscribe { d -> onConnect.emit(d); }
return holder;
}
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.view.View
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.futo.platformplayer.R
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.StateCasting
import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.constructs.Event2
import androidx.core.view.isVisible
class DeviceViewHolder : ViewHolder {
private val _layoutDevice: FrameLayout;
private val _imageDevice: ImageView;
private val _textName: TextView;
private val _textType: TextView;
private val _textNotReady: TextView;
private val _buttonDisconnect: LinearLayout;
private val _buttonConnect: LinearLayout;
private val _buttonRemove: LinearLayout;
private val _imageLoader: ImageView;
private val _imageOnline: ImageView;
private val _root: ConstraintLayout;
private var _animatableLoader: Animatable? = null;
private var _isRememberedDevice: Boolean = false;
private var _imagePin: ImageView;
var device: CastingDevice? = null
private set
var onRemove = Event1<CastingDevice>();
var onPin = Event1<CastingDevice>();
val onConnect = Event1<CastingDevice>();
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);
_textName = view.findViewById(R.id.text_name);
_textType = view.findViewById(R.id.text_type);
_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);
_imageOnline = view.findViewById(R.id.image_online);
_imagePin = view.findViewById(R.id.image_pin);
val d = _imageLoader.drawable;
if (d is Animatable) {
_animatableLoader = d;
}
_buttonDisconnect.setOnClickListener {
StateCasting.instance.activeDevice?.stopCasting();
updateButton();
};
_buttonConnect.setOnClickListener {
val dev = device ?: return@setOnClickListener;
val connect = {
device?.let { dev ->
StateCasting.instance.activeDevice?.stopCasting();
StateCasting.instance.connectDevice(dev);
onConnect.emit(dev);
};
}
}
_buttonRemove.setOnClickListener {
_textName.setOnClickListener { connect() };
_textType.setOnClickListener { connect() };
_layoutDevice.setOnClickListener { connect() };
_imagePin.setOnClickListener {
val dev = device ?: return@setOnClickListener;
onRemove.emit(dev);
};
StateCasting.instance.onActiveDeviceConnectionStateChanged.subscribe(this) { _, _ ->
updateButton();
onPin.emit(dev);
}
}
setIsRememberedDevice(false);
}
fun setIsRememberedDevice(isRememberedDevice: Boolean) {
_isRememberedDevice = isRememberedDevice;
_buttonRemove.visibility = if (isRememberedDevice) View.VISIBLE else View.GONE;
}
fun bind(d: CastingDevice) {
fun bind(d: CastingDevice, isOnlineDevice: Boolean, isPinnedDevice: Boolean) {
if (d is ChromecastCastingDevice) {
_imageDevice.setImageResource(R.drawable.ic_chromecast);
_textType.text = "Chromecast";
@ -90,54 +84,47 @@ class DeviceViewHolder : ViewHolder {
}
_textName.text = d.name;
device = d;
updateButton();
}
private fun updateButton() {
val d = device ?: return;
_imageOnline.visibility = if (isOnlineDevice) View.VISIBLE else View.GONE
if (!d.isReady) {
_buttonConnect.visibility = View.GONE;
_buttonDisconnect.visibility = View.GONE;
_imageLoader.visibility = View.GONE;
_textNotReady.visibility = View.VISIBLE;
return;
}
_imagePin.visibility = View.GONE;
} else {
_textNotReady.visibility = View.GONE;
val dev = StateCasting.instance.activeDevice;
if (dev == d) {
if (dev.connectionState == CastConnectionState.CONNECTED) {
_buttonConnect.visibility = View.GONE;
_buttonDisconnect.visibility = View.VISIBLE;
_imageLoader.visibility = View.GONE;
_textNotReady.visibility = View.GONE;
_imagePin.visibility = View.VISIBLE;
} else {
_buttonConnect.visibility = View.GONE;
_buttonDisconnect.visibility = View.VISIBLE;
_imageLoader.visibility = View.VISIBLE;
_textNotReady.visibility = View.GONE;
_imagePin.visibility = View.VISIBLE;
}
} else {
if (d.isReady) {
_buttonConnect.visibility = View.VISIBLE;
_buttonDisconnect.visibility = View.GONE;
_imageLoader.visibility = View.GONE;
_textNotReady.visibility = View.GONE;
_imagePin.visibility = View.VISIBLE;
} else {
_buttonConnect.visibility = View.GONE;
_buttonDisconnect.visibility = View.GONE;
_imageLoader.visibility = View.GONE;
_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();
} else {
_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_height="wrap_content"
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
android:id="@+id/text_devices"
@ -23,13 +29,30 @@
android:textColor="@color/white"
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
android:id="@+id/image_loader"
android:layout_width="22dp"
android:layout_height="22dp"
android:layout_width="18dp"
android:layout_height="18dp"
android:scaleType="fitCenter"
app:srcCompat="@drawable/ic_loader_animated"
android:layout_marginStart="5dp"/>
</LinearLayout>
</LinearLayout>
<Space android:layout_width="0dp"
android:layout_height="match_parent"
@ -38,7 +61,7 @@
<Button
android:id="@+id/button_close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:text="@string/close"
android:textSize="14dp"
android:fontFamily="@font/inter_regular"
@ -67,79 +90,102 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_devices"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_height="200dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp" />
android:layout_marginEnd="20dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="20dp"/>
</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
android:id="@+id/layout_remembered_devices_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
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:orientation="horizontal"
android:layout_marginTop="12dp"
android:layout_marginBottom="12dp"
android:layout_marginStart="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" />
android:layout_marginEnd="20dp">
<Space android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<ImageButton
<LinearLayout
android:id="@+id/button_add"
android:layout_width="40dp"
android:layout_height="40dp"
android:contentDescription="@string/cd_button_add"
android:scaleType="centerCrop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
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:tint="@color/primary"
android:layout_marginEnd="20dp"/>
android:layout_marginStart="8dp"/>
<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
android:id="@+id/layout_remembered_devices"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/text_no_devices_remembered"
android:id="@+id/button_qr"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="10dp"
android:text="@string/there_are_no_remembered_devices"
android:layout_marginTop="10dp"
android:layout_marginBottom="20dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:textColor="@color/gray_e0" />
android:orientation="horizontal"
android:background="@drawable/background_border_2e_round_6dp"
android:gravity="center">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_remembered_devices"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp" />
<ImageView
android:layout_width="22dp"
android:layout_height="22dp"
app:srcCompat="@drawable/ic_qr"
android:layout_marginStart="8dp"/>
<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>

View file

@ -4,18 +4,34 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
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
android:id="@+id/image_device"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/cd_image_device"
app:srcCompat="@drawable/ic_chromecast"
android:scaleType="fitCenter"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
android:scaleType="fitCenter" />
<ImageView
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
android:id="@+id/text_name"
@ -31,8 +47,8 @@
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:includeFontPadding="false"
app:layout_constraintTop_toTopOf="@id/image_device"
app:layout_constraintLeft_toRightOf="@id/image_device"
app:layout_constraintTop_toTopOf="@id/layout_device"
app:layout_constraintLeft_toRightOf="@id/layout_device"
app:layout_constraintRight_toLeftOf="@id/layout_button" />
<TextView
@ -43,12 +59,12 @@
tools:text="Chromecast"
android:textSize="10dp"
android:fontFamily="@font/inter_extra_light"
android:textColor="@color/white"
android:textColor="@color/gray_ac"
android:includeFontPadding="false"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
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" />
<LinearLayout
@ -68,74 +84,15 @@
app:srcCompat="@drawable/ic_loader_animated"
android:layout_marginEnd="8dp"/>
<LinearLayout
<ImageView
android:id="@+id/image_pin"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<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>
android:layout_height="25dp"
android:contentDescription="@string/cd_image_loader"
app:srcCompat="@drawable/ic_pin"
android:layout_marginEnd="8dp"
android:scaleType="fitEnd"
android:paddingStart="10dp" />
<TextView
android:id="@+id/text_not_ready"

View file

@ -194,7 +194,9 @@
<string name="ip">IP</string>
<string name="port">Port</string>
<string name="discovered_devices">Discovered Devices</string>
<string name="available_devices">Available 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="connected_to">Connected to</string>
<string name="volume">Volume</string>
@ -204,6 +206,7 @@
<string name="previous">Previous</string>
<string name="next">Next</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="str_import">Import</string>
<string name="my_playlist_name">My Playlist Name</string>