mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-08-22 09:58:08 +00:00
Migrate 2 Fragments to Kotlin
This is a rebased version of !522, with the same key changes. * `PairingFragment` and `SystemVolumeFragment` are now written in Kotlin * A new `BaseFragment` defines a common 'binding' property for `AboutFragment`, `DeviceFragment`, `PairingFragment`, and `SystemVolumeFragment` * Calls in and out of `Intent`s and `Bundle`s for `Parcelable` values now go through `IntentCompat` and `BundleCompat`
This commit is contained in:
parent
08b1a9dce4
commit
d5c47a1c4c
@ -13,6 +13,4 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
|
|||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingHorizontal="@dimen/activity_horizontal_margin"
|
android:paddingHorizontal="@dimen/activity_horizontal_margin"
|
||||||
android:paddingVertical="@dimen/activity_vertical_margin">
|
android:paddingVertical="@dimen/activity_vertical_margin" />
|
||||||
|
|
||||||
</androidx.recyclerview.widget.RecyclerView>
|
|
||||||
|
@ -16,6 +16,7 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.net.Network
|
import android.net.Network
|
||||||
|
import android.os.Parcelable
|
||||||
import android.preference.PreferenceManager
|
import android.preference.PreferenceManager
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
@ -29,6 +30,8 @@ import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper
|
|||||||
import org.kde.kdeconnect.Helpers.ThreadHelper.execute
|
import org.kde.kdeconnect.Helpers.ThreadHelper.execute
|
||||||
import org.kde.kdeconnect.NetworkPacket
|
import org.kde.kdeconnect.NetworkPacket
|
||||||
import org.kde.kdeconnect.UserInterface.SettingsFragment
|
import org.kde.kdeconnect.UserInterface.SettingsFragment
|
||||||
|
import org.kde.kdeconnect.extensions.getParcelableArrayCompat
|
||||||
|
import org.kde.kdeconnect.extensions.getParcelableCompat
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStreamReader
|
import java.io.InputStreamReader
|
||||||
import java.io.Reader
|
import java.io.Reader
|
||||||
@ -74,7 +77,7 @@ class BluetoothLinkProvider(private val context: Context) : BaseLinkProvider() {
|
|||||||
if (!preferences.getBoolean(SettingsFragment.KEY_BLUETOOTH_ENABLED, false)) {
|
if (!preferences.getBoolean(SettingsFragment.KEY_BLUETOOTH_ENABLED, false)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (bluetoothAdapter == null || bluetoothAdapter.isEnabled == false) {
|
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Log.i("BluetoothLinkProvider", "onStart called")
|
Log.i("BluetoothLinkProvider", "onStart called")
|
||||||
@ -297,8 +300,8 @@ class BluetoothLinkProvider(private val context: Context) : BaseLinkProvider() {
|
|||||||
val action = intent.action
|
val action = intent.action
|
||||||
if (BluetoothDevice.ACTION_UUID == action) {
|
if (BluetoothDevice.ACTION_UUID == action) {
|
||||||
Log.i("BluetoothLinkProvider", "Action matches")
|
Log.i("BluetoothLinkProvider", "Action matches")
|
||||||
val device = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
|
val device = intent.getParcelableCompat<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
|
||||||
val activeUuids = intent.getParcelableArrayExtra(BluetoothDevice.EXTRA_UUID)
|
val activeUuids = intent.getParcelableArrayCompat<Parcelable>(BluetoothDevice.EXTRA_UUID)
|
||||||
if (sockets.containsKey(device)) {
|
if (sockets.containsKey(device)) {
|
||||||
Log.i("BluetoothLinkProvider", "sockets contains device")
|
Log.i("BluetoothLinkProvider", "sockets contains device")
|
||||||
return
|
return
|
||||||
|
@ -15,6 +15,7 @@ import android.os.Bundle;
|
|||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.content.IntentCompat;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
@ -85,7 +86,7 @@ public class FindMyPhoneSettingsFragment extends PluginSettingsFragment {
|
|||||||
@Override
|
@Override
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
if (requestCode == REQUEST_CODE_SELECT_RINGTONE && resultCode == Activity.RESULT_OK) {
|
if (requestCode == REQUEST_CODE_SELECT_RINGTONE && resultCode == Activity.RESULT_OK) {
|
||||||
Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
|
Uri uri = IntentCompat.getParcelableExtra(data, RingtoneManager.EXTRA_RINGTONE_PICKED_URI, Uri.class);
|
||||||
|
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
sharedPreferences.edit()
|
sharedPreferences.edit()
|
||||||
|
@ -10,6 +10,7 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import org.kde.kdeconnect.KdeConnect
|
import org.kde.kdeconnect.KdeConnect
|
||||||
|
import org.kde.kdeconnect.extensions.getParcelableCompat
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the mpris media notification's buttons are pressed
|
* Called when the mpris media notification's buttons are pressed
|
||||||
@ -20,7 +21,7 @@ class MprisMediaNotificationReceiver : BroadcastReceiver() {
|
|||||||
if (Intent.ACTION_MEDIA_BUTTON == intent.action) {
|
if (Intent.ACTION_MEDIA_BUTTON == intent.action) {
|
||||||
// Route these buttons to the media session, which will handle them
|
// Route these buttons to the media session, which will handle them
|
||||||
val mediaSession = MprisMediaSession.getMediaSession() ?: return
|
val mediaSession = MprisMediaSession.getMediaSession() ?: return
|
||||||
mediaSession.controller.dispatchMediaButtonEvent(intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT))
|
mediaSession.controller.dispatchMediaButtonEvent(intent.getParcelableCompat(Intent.EXTRA_KEY_EVENT))
|
||||||
} else {
|
} else {
|
||||||
// Second case: buttons on the notification, which we created ourselves
|
// Second case: buttons on the notification, which we created ourselves
|
||||||
// Get the correct device, the mpris plugin and the mpris player
|
// Get the correct device, the mpris plugin and the mpris player
|
||||||
|
@ -34,6 +34,7 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
import androidx.core.app.NotificationCompat;
|
import androidx.core.app.NotificationCompat;
|
||||||
|
import androidx.core.os.BundleCompat;
|
||||||
import androidx.fragment.app.DialogFragment;
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
|
||||||
import org.apache.commons.collections4.MultiValuedMap;
|
import org.apache.commons.collections4.MultiValuedMap;
|
||||||
@ -358,7 +359,7 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
|||||||
if (!notification.extras.containsKey(Notification.EXTRA_MESSAGES))
|
if (!notification.extras.containsKey(Notification.EXTRA_MESSAGES))
|
||||||
return new Pair<>(null, null);
|
return new Pair<>(null, null);
|
||||||
|
|
||||||
Parcelable[] ms = notification.extras.getParcelableArray(Notification.EXTRA_MESSAGES);
|
Parcelable[] ms = BundleCompat.getParcelableArray(notification.extras, Notification.EXTRA_MESSAGES, Parcelable.class);
|
||||||
|
|
||||||
if (ms == null)
|
if (ms == null)
|
||||||
return new Pair<>(null, null);
|
return new Pair<>(null, null);
|
||||||
|
@ -25,10 +25,12 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.WorkerThread;
|
import androidx.annotation.WorkerThread;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.core.content.IntentCompat;
|
||||||
import androidx.core.content.LocusIdCompat;
|
import androidx.core.content.LocusIdCompat;
|
||||||
import androidx.core.content.pm.ShortcutInfoCompat;
|
import androidx.core.content.pm.ShortcutInfoCompat;
|
||||||
import androidx.core.content.pm.ShortcutManagerCompat;
|
import androidx.core.content.pm.ShortcutManagerCompat;
|
||||||
import androidx.core.graphics.drawable.IconCompat;
|
import androidx.core.graphics.drawable.IconCompat;
|
||||||
|
import androidx.core.os.BundleCompat;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import org.apache.commons.lang3.ArrayUtils;
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
@ -355,10 +357,10 @@ public class SharePlugin extends Plugin {
|
|||||||
Log.i("SharePlugin", "Intent contains streams to share");
|
Log.i("SharePlugin", "Intent contains streams to share");
|
||||||
ArrayList<Uri> uriList;
|
ArrayList<Uri> uriList;
|
||||||
if (Intent.ACTION_SEND_MULTIPLE.equals(intent.getAction())) {
|
if (Intent.ACTION_SEND_MULTIPLE.equals(intent.getAction())) {
|
||||||
uriList = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
|
uriList = IntentCompat.getParcelableArrayListExtra(intent, Intent.EXTRA_STREAM, Uri.class);
|
||||||
} else {
|
} else {
|
||||||
uriList = new ArrayList<>();
|
uriList = new ArrayList<>();
|
||||||
uriList.add(extras.getParcelable(Intent.EXTRA_STREAM));
|
uriList.add(BundleCompat.getParcelable(extras, Intent.EXTRA_STREAM, Uri.class));
|
||||||
}
|
}
|
||||||
uriList.removeAll(Collections.singleton(null));
|
uriList.removeAll(Collections.singleton(null));
|
||||||
if (uriList.isEmpty()) {
|
if (uriList.isEmpty()) {
|
||||||
|
@ -1,179 +0,0 @@
|
|||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: 2018 Nicolas Fella <nicolas.fella@gmx.de>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.kde.kdeconnect.Plugins.SystemVolumePlugin;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.core.util.Consumer;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.ListAdapter;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import org.kde.kdeconnect.Helpers.VolumeHelperKt;
|
|
||||||
import org.kde.kdeconnect.KdeConnect;
|
|
||||||
import org.kde.kdeconnect.Plugins.MprisPlugin.MprisPlugin;
|
|
||||||
import org.kde.kdeconnect.Plugins.MprisPlugin.VolumeKeyListener;
|
|
||||||
import org.kde.kdeconnect_tp.R;
|
|
||||||
import org.kde.kdeconnect_tp.databinding.ListItemSystemvolumeBinding;
|
|
||||||
import org.kde.kdeconnect_tp.databinding.SystemVolumeFragmentBinding;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class SystemVolumeFragment
|
|
||||||
extends Fragment
|
|
||||||
implements Sink.UpdateListener, SystemVolumePlugin.SinkListener, VolumeKeyListener {
|
|
||||||
|
|
||||||
private SystemVolumePlugin plugin;
|
|
||||||
private RecyclerSinkAdapter recyclerAdapter;
|
|
||||||
private boolean tracking;
|
|
||||||
private final Consumer<Boolean> trackingConsumer = aBoolean -> tracking = aBoolean;
|
|
||||||
private SystemVolumeFragmentBinding systemVolumeFragmentBinding;
|
|
||||||
|
|
||||||
public static SystemVolumeFragment newInstance(String deviceId) {
|
|
||||||
SystemVolumeFragment systemvolumeFragment = new SystemVolumeFragment();
|
|
||||||
|
|
||||||
Bundle arguments = new Bundle();
|
|
||||||
arguments.putString(MprisPlugin.DEVICE_ID_KEY, deviceId);
|
|
||||||
|
|
||||||
systemvolumeFragment.setArguments(arguments);
|
|
||||||
|
|
||||||
return systemvolumeFragment;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(
|
|
||||||
@NonNull LayoutInflater inflater,
|
|
||||||
@Nullable ViewGroup container,
|
|
||||||
@Nullable Bundle savedInstanceState
|
|
||||||
) {
|
|
||||||
|
|
||||||
if (systemVolumeFragmentBinding == null) {
|
|
||||||
systemVolumeFragmentBinding = SystemVolumeFragmentBinding.inflate(inflater);
|
|
||||||
|
|
||||||
RecyclerView recyclerView = systemVolumeFragmentBinding.audioDevicesRecycler;
|
|
||||||
|
|
||||||
int gap = requireContext().getResources().getDimensionPixelSize(R.dimen.activity_vertical_margin);
|
|
||||||
recyclerView.addItemDecoration(new ItemGapDecoration(gap));
|
|
||||||
recyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
|
||||||
|
|
||||||
recyclerAdapter = new RecyclerSinkAdapter();
|
|
||||||
recyclerView.setAdapter(recyclerAdapter);
|
|
||||||
}
|
|
||||||
|
|
||||||
connectToPlugin(getDeviceId());
|
|
||||||
|
|
||||||
return systemVolumeFragmentBinding.getRoot();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroyView() {
|
|
||||||
disconnectFromPlugin(getDeviceId());
|
|
||||||
super.onDestroyView();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateSink(@NonNull final Sink sink) {
|
|
||||||
|
|
||||||
// Don't set progress while the slider is moved
|
|
||||||
if (!tracking) {
|
|
||||||
|
|
||||||
requireActivity().runOnUiThread(() -> recyclerAdapter.notifyDataSetChanged());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void connectToPlugin(final String deviceId) {
|
|
||||||
SystemVolumePlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, SystemVolumePlugin.class);
|
|
||||||
if (plugin == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.plugin = plugin;
|
|
||||||
plugin.addSinkListener(SystemVolumeFragment.this);
|
|
||||||
plugin.requestSinkList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void disconnectFromPlugin(final String deviceId) {
|
|
||||||
SystemVolumePlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, SystemVolumePlugin.class);
|
|
||||||
if (plugin == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
plugin.removeSinkListener(SystemVolumeFragment.this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sinksChanged() {
|
|
||||||
|
|
||||||
for (Sink sink : plugin.getSinks()) {
|
|
||||||
sink.addListener(SystemVolumeFragment.this);
|
|
||||||
}
|
|
||||||
|
|
||||||
requireActivity().runOnUiThread(() -> {
|
|
||||||
List<Sink> newSinks = new ArrayList<>(plugin.getSinks());
|
|
||||||
recyclerAdapter.submitList(newSinks);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onVolumeUp() {
|
|
||||||
updateDefaultSinkVolume(5);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onVolumeDown() {
|
|
||||||
updateDefaultSinkVolume(-5);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateDefaultSinkVolume(int percent) {
|
|
||||||
if (plugin == null) return;
|
|
||||||
|
|
||||||
Sink defaultSink = SystemVolumeUtilsKt.getDefaultSink(plugin);
|
|
||||||
if (defaultSink == null) return;
|
|
||||||
|
|
||||||
int newVolume = VolumeHelperKt.calculateNewVolume(
|
|
||||||
defaultSink.getVolume(),
|
|
||||||
defaultSink.getMaxVolume(),
|
|
||||||
percent
|
|
||||||
);
|
|
||||||
|
|
||||||
if (defaultSink.getVolume() == newVolume) return;
|
|
||||||
|
|
||||||
plugin.sendVolume(defaultSink.getName(), newVolume);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getDeviceId() {
|
|
||||||
return requireArguments().getString(MprisPlugin.DEVICE_ID_KEY);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class RecyclerSinkAdapter extends ListAdapter<Sink, SinkItemHolder> {
|
|
||||||
|
|
||||||
public RecyclerSinkAdapter() {
|
|
||||||
super(new SinkItemCallback());
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public SinkItemHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
|
||||||
|
|
||||||
LayoutInflater inflater = getLayoutInflater();
|
|
||||||
ListItemSystemvolumeBinding viewBinding = ListItemSystemvolumeBinding.inflate(inflater, parent, false);
|
|
||||||
|
|
||||||
return new SinkItemHolder(viewBinding, plugin, trackingConsumer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(@NonNull SinkItemHolder holder, int position) {
|
|
||||||
holder.bind(getItem(position));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,162 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2018 Nicolas Fella <nicolas.fella@gmx.de>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
*/
|
||||||
|
package org.kde.kdeconnect.Plugins.SystemVolumePlugin
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.util.Consumer
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
import org.kde.kdeconnect.Helpers.calculateNewVolume
|
||||||
|
import org.kde.kdeconnect.KdeConnect
|
||||||
|
import org.kde.kdeconnect.Plugins.MprisPlugin.MprisPlugin
|
||||||
|
import org.kde.kdeconnect.Plugins.MprisPlugin.VolumeKeyListener
|
||||||
|
import org.kde.kdeconnect.Plugins.SystemVolumePlugin.SystemVolumePlugin.SinkListener
|
||||||
|
import org.kde.kdeconnect.base.BaseFragment
|
||||||
|
import org.kde.kdeconnect.extensions.setupBottomPadding
|
||||||
|
import org.kde.kdeconnect_tp.R
|
||||||
|
import org.kde.kdeconnect_tp.databinding.ListItemSystemvolumeBinding
|
||||||
|
import org.kde.kdeconnect_tp.databinding.SystemVolumeFragmentBinding
|
||||||
|
|
||||||
|
class SystemVolumeFragment : BaseFragment<SystemVolumeFragmentBinding>(),
|
||||||
|
Sink.UpdateListener, SinkListener, VolumeKeyListener {
|
||||||
|
|
||||||
|
private lateinit var plugin: SystemVolumePlugin
|
||||||
|
private lateinit var recyclerAdapter: RecyclerSinkAdapter
|
||||||
|
private var tracking = false
|
||||||
|
private val trackingConsumer = Consumer { aBoolean: Boolean -> tracking = aBoolean }
|
||||||
|
|
||||||
|
override fun onInflateBinding(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): SystemVolumeFragmentBinding {
|
||||||
|
return SystemVolumeFragmentBinding.inflate(inflater, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
recyclerAdapter = RecyclerSinkAdapter()
|
||||||
|
binding.audioDevicesRecycler.apply {
|
||||||
|
layoutManager = LinearLayoutManager(requireContext())
|
||||||
|
addItemDecoration(ItemGapDecoration(resources.getDimensionPixelSize(R.dimen.activity_vertical_margin)))
|
||||||
|
adapter = recyclerAdapter
|
||||||
|
setupBottomPadding()
|
||||||
|
}
|
||||||
|
connectToPlugin(deviceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
disconnectFromPlugin(deviceId)
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
override fun updateSink(sink: Sink) {
|
||||||
|
// Don't set progress while the slider is moved
|
||||||
|
if (!tracking) {
|
||||||
|
requireActivity().runOnUiThread {
|
||||||
|
if (::recyclerAdapter.isInitialized) {
|
||||||
|
recyclerAdapter.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun connectToPlugin(deviceId: String?) {
|
||||||
|
val plugin = KdeConnect.getInstance().getDevicePlugin(
|
||||||
|
deviceId,
|
||||||
|
SystemVolumePlugin::class.java
|
||||||
|
)
|
||||||
|
if (plugin == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.plugin = plugin
|
||||||
|
plugin.addSinkListener(this@SystemVolumeFragment)
|
||||||
|
plugin.requestSinkList()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun disconnectFromPlugin(deviceId: String?) {
|
||||||
|
val plugin = KdeConnect.getInstance().getDevicePlugin(
|
||||||
|
deviceId,
|
||||||
|
SystemVolumePlugin::class.java
|
||||||
|
)
|
||||||
|
if (plugin == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
plugin.removeSinkListener(this@SystemVolumeFragment)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun sinksChanged() {
|
||||||
|
if (!::plugin.isInitialized || !::recyclerAdapter.isInitialized) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (sink in plugin.sinks) {
|
||||||
|
sink.addListener(this@SystemVolumeFragment)
|
||||||
|
}
|
||||||
|
requireActivity().runOnUiThread {
|
||||||
|
val newSinks: List<Sink> = ArrayList(plugin.sinks)
|
||||||
|
recyclerAdapter.submitList(newSinks)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onVolumeUp() {
|
||||||
|
updateDefaultSinkVolume(5)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onVolumeDown() {
|
||||||
|
updateDefaultSinkVolume(-5)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateDefaultSinkVolume(percent: Int) {
|
||||||
|
if (!::plugin.isInitialized) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val defaultSink = getDefaultSink(plugin) ?: return
|
||||||
|
|
||||||
|
val newVolume = calculateNewVolume(
|
||||||
|
defaultSink.volume,
|
||||||
|
defaultSink.maxVolume,
|
||||||
|
percent
|
||||||
|
)
|
||||||
|
|
||||||
|
if (defaultSink.volume == newVolume) return
|
||||||
|
|
||||||
|
plugin.sendVolume(defaultSink.name, newVolume)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val deviceId: String?
|
||||||
|
get() = arguments?.getString(MprisPlugin.DEVICE_ID_KEY)
|
||||||
|
|
||||||
|
private inner class RecyclerSinkAdapter : ListAdapter<Sink?, SinkItemHolder>(SinkItemCallback()) {
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SinkItemHolder {
|
||||||
|
val viewBinding = ListItemSystemvolumeBinding.inflate(layoutInflater, parent, false)
|
||||||
|
return SinkItemHolder(viewBinding, plugin, trackingConsumer)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: SinkItemHolder, position: Int) {
|
||||||
|
holder.bind(getItem(position))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun newInstance(deviceId: String?): SystemVolumeFragment {
|
||||||
|
val systemVolumeFragment = SystemVolumeFragment()
|
||||||
|
|
||||||
|
val arguments = Bundle()
|
||||||
|
arguments.putString(MprisPlugin.DEVICE_ID_KEY, deviceId)
|
||||||
|
|
||||||
|
systemVolumeFragment.arguments = arguments
|
||||||
|
|
||||||
|
return systemVolumeFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,50 +14,56 @@ import android.view.LayoutInflater
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import org.kde.kdeconnect.UserInterface.List.ListAdapter
|
import org.kde.kdeconnect.UserInterface.List.ListAdapter
|
||||||
import org.kde.kdeconnect.UserInterface.MainActivity
|
import org.kde.kdeconnect.base.BaseFragment
|
||||||
|
import org.kde.kdeconnect.extensions.getParcelableCompat
|
||||||
import org.kde.kdeconnect.extensions.setupBottomPadding
|
import org.kde.kdeconnect.extensions.setupBottomPadding
|
||||||
import org.kde.kdeconnect_tp.R
|
import org.kde.kdeconnect_tp.R
|
||||||
import org.kde.kdeconnect_tp.databinding.FragmentAboutBinding
|
import org.kde.kdeconnect_tp.databinding.FragmentAboutBinding
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
|
|
||||||
class AboutFragment : Fragment() {
|
class AboutFragment : BaseFragment<FragmentAboutBinding>() {
|
||||||
private var _binding: FragmentAboutBinding? = null
|
|
||||||
private val binding get() = _binding!!
|
|
||||||
private lateinit var aboutData: AboutData
|
|
||||||
private var tapCount = 0
|
|
||||||
private var firstTapMillis: Long? = null
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private const val KEY_ABOUT_DATA = "about_data"
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun newInstance(aboutData: AboutData): Fragment {
|
fun newInstance(aboutData: AboutData): Fragment {
|
||||||
val fragment = AboutFragment()
|
val fragment = AboutFragment()
|
||||||
|
|
||||||
val args = Bundle(1)
|
val args = Bundle(1)
|
||||||
args.putParcelable("ABOUT_DATA", aboutData)
|
args.putParcelable(KEY_ABOUT_DATA, aboutData)
|
||||||
fragment.arguments = args
|
fragment.arguments = args
|
||||||
|
|
||||||
return fragment
|
return fragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
private lateinit var aboutData: AboutData
|
||||||
if (activity != null) {
|
private var tapCount = 0
|
||||||
(requireActivity() as MainActivity).supportActionBar?.setTitle(R.string.about)
|
private var firstTapMillis: Long? = null
|
||||||
}
|
|
||||||
|
|
||||||
aboutData = requireArguments().getParcelable("ABOUT_DATA")!!
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
_binding = FragmentAboutBinding.inflate(inflater, container, false)
|
super.onCreate(savedInstanceState)
|
||||||
|
aboutData = arguments?.getParcelableCompat(KEY_ABOUT_DATA) ?: throw IllegalArgumentException("AboutData is null")
|
||||||
|
}
|
||||||
|
|
||||||
updateData()
|
override fun onInflateBinding(
|
||||||
return binding.root
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): FragmentAboutBinding {
|
||||||
|
return FragmentAboutBinding.inflate(inflater, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
(activity as? AppCompatActivity)?.supportActionBar?.setTitle(R.string.about)
|
||||||
binding.scrollView.setupBottomPadding()
|
binding.scrollView.setupBottomPadding()
|
||||||
|
updateData()
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
@ -65,8 +71,8 @@ class AboutFragment : Fragment() {
|
|||||||
// Update general info
|
// Update general info
|
||||||
|
|
||||||
binding.appName.text = aboutData.name
|
binding.appName.text = aboutData.name
|
||||||
binding.appIcon.setImageDrawable(this.context?.let { ContextCompat.getDrawable(it, aboutData.icon) })
|
binding.appIcon.setImageDrawable(context?.let { ContextCompat.getDrawable(it, aboutData.icon) })
|
||||||
binding.appVersion.text = this.context?.getString(R.string.version, aboutData.versionName)
|
binding.appVersion.text = context?.getString(R.string.version, aboutData.versionName)
|
||||||
|
|
||||||
// Setup Easter Egg onClickListener
|
// Setup Easter Egg onClickListener
|
||||||
|
|
||||||
@ -103,7 +109,7 @@ class AboutFragment : Fragment() {
|
|||||||
setupInfoButton(aboutData.websiteURL, binding.websiteButton)
|
setupInfoButton(aboutData.websiteURL, binding.websiteButton)
|
||||||
|
|
||||||
// Update authors
|
// Update authors
|
||||||
binding.authorsList.adapter = ListAdapter(this.requireContext(), aboutData.authors.map { AboutPersonEntryItem(it) }, false)
|
binding.authorsList.adapter = ListAdapter(requireContext(), aboutData.authors.map { AboutPersonEntryItem(it) }, false)
|
||||||
if (aboutData.authorsFooterText != null) {
|
if (aboutData.authorsFooterText != null) {
|
||||||
binding.authorsFooterText.text = context?.getString(aboutData.authorsFooterText!!)
|
binding.authorsFooterText.text = context?.getString(aboutData.authorsFooterText!!)
|
||||||
}
|
}
|
||||||
@ -119,8 +125,4 @@ class AboutFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
|
||||||
super.onDestroyView()
|
|
||||||
_binding = null
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -11,6 +11,8 @@ import android.util.Log
|
|||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
|
import android.view.MenuInflater
|
||||||
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
@ -30,7 +32,8 @@ import androidx.compose.ui.semantics.semantics
|
|||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.*
|
import androidx.compose.ui.unit.*
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.core.view.MenuProvider
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import org.kde.kdeconnect.BackgroundService
|
import org.kde.kdeconnect.BackgroundService
|
||||||
import org.kde.kdeconnect.Device
|
import org.kde.kdeconnect.Device
|
||||||
@ -44,44 +47,15 @@ import org.kde.kdeconnect.Plugins.Plugin
|
|||||||
import org.kde.kdeconnect.Plugins.PresenterPlugin.PresenterPlugin
|
import org.kde.kdeconnect.Plugins.PresenterPlugin.PresenterPlugin
|
||||||
import org.kde.kdeconnect.Plugins.RunCommandPlugin.RunCommandPlugin
|
import org.kde.kdeconnect.Plugins.RunCommandPlugin.RunCommandPlugin
|
||||||
import org.kde.kdeconnect.UserInterface.compose.KdeTheme
|
import org.kde.kdeconnect.UserInterface.compose.KdeTheme
|
||||||
|
import org.kde.kdeconnect.base.BaseFragment
|
||||||
import org.kde.kdeconnect.extensions.setupBottomPadding
|
import org.kde.kdeconnect.extensions.setupBottomPadding
|
||||||
import org.kde.kdeconnect_tp.R
|
import org.kde.kdeconnect_tp.R
|
||||||
import org.kde.kdeconnect_tp.databinding.ActivityDeviceBinding
|
import org.kde.kdeconnect_tp.databinding.ActivityDeviceBinding
|
||||||
import org.kde.kdeconnect_tp.databinding.ViewPairErrorBinding
|
|
||||||
import org.kde.kdeconnect_tp.databinding.ViewPairRequestBinding
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main view. Displays the current device and its plugins
|
* Main view. Displays the current device and its plugins
|
||||||
*/
|
*/
|
||||||
class DeviceFragment : Fragment() {
|
class DeviceFragment : BaseFragment<ActivityDeviceBinding>() {
|
||||||
val deviceId: String by lazy {
|
|
||||||
arguments?.getString(ARG_DEVICE_ID)
|
|
||||||
?: throw RuntimeException("You must instantiate a new DeviceFragment using DeviceFragment.newInstance()")
|
|
||||||
}
|
|
||||||
private var device: Device? = null
|
|
||||||
private val mActivity: MainActivity? by lazy { activity as MainActivity? }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Top-level ViewBinding for this fragment.
|
|
||||||
*/
|
|
||||||
private var deviceBinding: ActivityDeviceBinding? = null
|
|
||||||
private fun requireDeviceBinding() = deviceBinding ?: throw IllegalStateException("deviceBinding is not set")
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Not-yet-paired ViewBinding.
|
|
||||||
*
|
|
||||||
* Used to start and retry pairing.
|
|
||||||
*/
|
|
||||||
private var pairingBinding: ViewPairRequestBinding? = null
|
|
||||||
private fun requirePairingBinding() = pairingBinding ?: throw IllegalStateException("binding is not set")
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cannot-communicate ViewBinding.
|
|
||||||
*
|
|
||||||
* Used when the remote device is unreachable.
|
|
||||||
*/
|
|
||||||
private var errorBinding: ViewPairErrorBinding? = null
|
|
||||||
private fun requireErrorBinding() = errorBinding ?: throw IllegalStateException("errorBinding is not set")
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val ARG_DEVICE_ID = "deviceId"
|
private const val ARG_DEVICE_ID = "deviceId"
|
||||||
@ -97,35 +71,127 @@ class DeviceFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
val deviceId: String by lazy {
|
||||||
|
arguments?.getString(ARG_DEVICE_ID)
|
||||||
|
?: throw RuntimeException("You must instantiate a new DeviceFragment using DeviceFragment.newInstance()")
|
||||||
|
}
|
||||||
|
|
||||||
|
private var device: Device? = null
|
||||||
|
|
||||||
|
private val mActivity: MainActivity? by lazy { activity as MainActivity? }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Not-yet-paired ViewBinding.
|
||||||
|
*
|
||||||
|
* Used to start and retry pairing.
|
||||||
|
*/
|
||||||
|
private val pairingBinding get() = binding.pairRequest
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cannot-communicate ViewBinding.
|
||||||
|
*
|
||||||
|
* Used when the remote device is unreachable.
|
||||||
|
*/
|
||||||
|
private val errorBinding get() = binding.pairError
|
||||||
|
|
||||||
|
override fun onInflateBinding(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): ActivityDeviceBinding {
|
||||||
deviceBinding = ActivityDeviceBinding.inflate(inflater, container, false)
|
return ActivityDeviceBinding.inflate(inflater, container, false)
|
||||||
val deviceBinding = deviceBinding ?: return null
|
}
|
||||||
|
|
||||||
// Inner binding for the layout shown when we're not paired yet...
|
private val menuProvider = object : MenuProvider {
|
||||||
pairingBinding = deviceBinding.pairRequest
|
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||||
// ...and for when pairing doesn't (or can't) work
|
menu.clear()
|
||||||
errorBinding = deviceBinding.pairError
|
val device = device ?: return
|
||||||
|
|
||||||
device = KdeConnect.getInstance().getDevice(deviceId)
|
//Plugins button list
|
||||||
|
val plugins: Collection<Plugin> = device.loadedPlugins.values
|
||||||
requireErrorBinding().errorMessageContainer.setOnRefreshListener {
|
for (p in plugins) {
|
||||||
this.refreshDevicesAction()
|
if (p.displayInContextMenu()) {
|
||||||
|
menu.add(p.actionName).setOnMenuItemClickListener {
|
||||||
|
p.startMainActivity(mActivity!!)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val intent = Intent(mActivity, PluginSettingsActivity::class.java)
|
||||||
|
intent.putExtra("deviceId", deviceId)
|
||||||
|
menu.add(R.string.device_menu_plugins).setOnMenuItemClickListener {
|
||||||
|
startActivity(intent)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
if (device.isReachable) {
|
||||||
|
val builder = MaterialAlertDialogBuilder(requireContext())
|
||||||
|
builder.setTitle(requireContext().resources.getString(R.string.encryption_info_title))
|
||||||
|
builder.setPositiveButton(requireContext().resources.getString(R.string.ok)) { dialog, _ ->
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
builder.setMessage(
|
||||||
|
"${
|
||||||
|
requireContext().resources.getString(R.string.my_device_fingerprint)
|
||||||
|
} \n ${
|
||||||
|
SslHelper.getCertificateHash(SslHelper.certificate)
|
||||||
|
} \n\n ${
|
||||||
|
requireContext().resources.getString(R.string.remote_device_fingerprint)
|
||||||
|
} \n ${
|
||||||
|
SslHelper.getCertificateHash(device.certificate)
|
||||||
|
} \n\n ${
|
||||||
|
requireContext().resources.getString(R.string.protocol_version)
|
||||||
|
} ${
|
||||||
|
device.protocolVersion
|
||||||
|
}"
|
||||||
|
)
|
||||||
|
menu.add(R.string.encryption_info_title).setOnMenuItemClickListener {
|
||||||
|
builder.show()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (device.isPaired) {
|
||||||
|
menu.add(R.string.device_menu_unpair).setOnMenuItemClickListener {
|
||||||
|
device.apply {
|
||||||
|
// Remove listener so buttons don't show for an instant before changing the view
|
||||||
|
removePairingCallback(pairingCallback)
|
||||||
|
removePluginsChangedListener(pluginsChangedListener)
|
||||||
|
unpair()
|
||||||
|
}
|
||||||
|
mActivity?.onDeviceSelected(null)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (device.pairStatus == PairingHandler.PairState.Requested) {
|
||||||
|
menu.add(R.string.cancel_pairing).setOnMenuItemClickListener {
|
||||||
|
device.cancelPairing()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
requirePairingBinding().pairButton.setOnClickListener {
|
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
binding.deviceView.setupBottomPadding()
|
||||||
|
errorBinding.errorMessageContainer.setOnRefreshListener {
|
||||||
|
this.refreshDevicesAction()
|
||||||
|
}
|
||||||
|
pairingBinding.pairButton.setOnClickListener {
|
||||||
device?.requestPairing()
|
device?.requestPairing()
|
||||||
refreshUI()
|
refreshUI()
|
||||||
}
|
}
|
||||||
requirePairingBinding().acceptButton.setOnClickListener {
|
pairingBinding.acceptButton.setOnClickListener {
|
||||||
device?.apply {
|
device?.apply {
|
||||||
acceptPairing()
|
acceptPairing()
|
||||||
requirePairingBinding().pairingButtons.visibility = View.GONE
|
pairingBinding.pairingButtons.visibility = View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
requirePairingBinding().rejectButton.setOnClickListener {
|
pairingBinding.rejectButton.setOnClickListener {
|
||||||
device?.apply {
|
device?.apply {
|
||||||
// Remove listener so buttons don't show for an instant before changing the view
|
// Remove listener so buttons don't show for an instant before changing the view
|
||||||
removePluginsChangedListener(pluginsChangedListener)
|
removePluginsChangedListener(pluginsChangedListener)
|
||||||
@ -134,8 +200,7 @@ class DeviceFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
mActivity?.onDeviceSelected(null)
|
mActivity?.onDeviceSelected(null)
|
||||||
}
|
}
|
||||||
setHasOptionsMenu(true)
|
device = KdeConnect.getInstance().getDevice(deviceId)
|
||||||
|
|
||||||
device?.apply {
|
device?.apply {
|
||||||
mActivity?.supportActionBar?.title = name
|
mActivity?.supportActionBar?.title = name
|
||||||
addPairingCallback(pairingCallback)
|
addPairingCallback(pairingCallback)
|
||||||
@ -144,104 +209,31 @@ class DeviceFragment : Fragment() {
|
|||||||
Log.e(TAG, "Trying to display a device fragment but the device is not present")
|
Log.e(TAG, "Trying to display a device fragment but the device is not present")
|
||||||
mActivity?.onDeviceSelected(null)
|
mActivity?.onDeviceSelected(null)
|
||||||
}
|
}
|
||||||
|
mActivity?.addMenuProvider(menuProvider, viewLifecycleOwner)
|
||||||
refreshUI()
|
refreshUI()
|
||||||
|
|
||||||
return deviceBinding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
deviceBinding?.deviceView?.setupBottomPadding()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshDevicesAction() {
|
private fun refreshDevicesAction() {
|
||||||
BackgroundService.ForceRefreshConnections(requireContext())
|
BackgroundService.ForceRefreshConnections(requireContext())
|
||||||
requireErrorBinding().errorMessageContainer.isRefreshing = true
|
errorBinding.errorMessageContainer.isRefreshing = true
|
||||||
requireErrorBinding().errorMessageContainer.postDelayed({
|
errorBinding.errorMessageContainer.postDelayed({
|
||||||
errorBinding?.errorMessageContainer?.isRefreshing = false // check for null since the view might be destroyed by now
|
if (viewLifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) {
|
||||||
|
errorBinding.errorMessageContainer.isRefreshing = false // check for null since the view might be destroyed by now
|
||||||
|
}
|
||||||
}, 1500)
|
}, 1500)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val pluginsChangedListener = PluginsChangedListener { mActivity?.runOnUiThread { refreshUI() } }
|
private val pluginsChangedListener = PluginsChangedListener { mActivity?.runOnUiThread { refreshUI() } }
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
device?.apply {
|
device?.apply {
|
||||||
removePluginsChangedListener(pluginsChangedListener)
|
removePluginsChangedListener(pluginsChangedListener)
|
||||||
removePairingCallback(pairingCallback)
|
removePairingCallback(pairingCallback)
|
||||||
}
|
}
|
||||||
device = null
|
device = null
|
||||||
pairingBinding = null
|
|
||||||
errorBinding = null
|
|
||||||
deviceBinding = null
|
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPrepareOptionsMenu(menu: Menu) {
|
|
||||||
super.onPrepareOptionsMenu(menu)
|
|
||||||
menu.clear()
|
|
||||||
val device = device ?: return
|
|
||||||
|
|
||||||
//Plugins button list
|
|
||||||
val plugins: Collection<Plugin> = device.loadedPlugins.values
|
|
||||||
for (p in plugins) {
|
|
||||||
if (p.displayInContextMenu()) {
|
|
||||||
menu.add(p.actionName).setOnMenuItemClickListener {
|
|
||||||
p.startMainActivity(mActivity!!)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val intent = Intent(mActivity, PluginSettingsActivity::class.java)
|
|
||||||
intent.putExtra("deviceId", deviceId)
|
|
||||||
menu.add(R.string.device_menu_plugins).setOnMenuItemClickListener {
|
|
||||||
startActivity(intent)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
if (device.isReachable) {
|
|
||||||
val builder = MaterialAlertDialogBuilder(requireContext())
|
|
||||||
builder.setTitle(requireContext().resources.getString(R.string.encryption_info_title))
|
|
||||||
builder.setPositiveButton(requireContext().resources.getString(R.string.ok)) { dialog, _ ->
|
|
||||||
dialog.dismiss()
|
|
||||||
}
|
|
||||||
builder.setMessage(
|
|
||||||
"${
|
|
||||||
requireContext().resources.getString(R.string.my_device_fingerprint)
|
|
||||||
} \n ${
|
|
||||||
SslHelper.getCertificateHash(SslHelper.certificate)
|
|
||||||
} \n\n ${
|
|
||||||
requireContext().resources.getString(R.string.remote_device_fingerprint)
|
|
||||||
} \n ${
|
|
||||||
SslHelper.getCertificateHash(device.certificate)
|
|
||||||
} \n\n ${
|
|
||||||
requireContext().resources.getString(R.string.protocol_version)
|
|
||||||
} ${
|
|
||||||
device.protocolVersion
|
|
||||||
}"
|
|
||||||
)
|
|
||||||
menu.add(R.string.encryption_info_title).setOnMenuItemClickListener {
|
|
||||||
builder.show()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (device.isPaired) {
|
|
||||||
menu.add(R.string.device_menu_unpair).setOnMenuItemClickListener {
|
|
||||||
device.apply {
|
|
||||||
// Remove listener so buttons don't show for an instant before changing the view
|
|
||||||
removePairingCallback(pairingCallback)
|
|
||||||
removePluginsChangedListener(pluginsChangedListener)
|
|
||||||
unpair()
|
|
||||||
}
|
|
||||||
mActivity?.onDeviceSelected(null)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (device.pairStatus == PairingHandler.PairState.Requested) {
|
|
||||||
menu.add(R.string.cancel_pairing).setOnMenuItemClickListener {
|
|
||||||
device.cancelPairing()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
@ -270,13 +262,13 @@ class DeviceFragment : Fragment() {
|
|||||||
|
|
||||||
when (device.pairStatus) {
|
when (device.pairStatus) {
|
||||||
PairingHandler.PairState.NotPaired -> {
|
PairingHandler.PairState.NotPaired -> {
|
||||||
requireErrorBinding().errorMessageContainer.visibility = View.GONE
|
errorBinding.errorMessageContainer.visibility = View.GONE
|
||||||
requireDeviceBinding().deviceView.visibility = View.GONE
|
binding.deviceView.visibility = View.GONE
|
||||||
requirePairingBinding().pairingButtons.visibility = View.VISIBLE
|
pairingBinding.pairingButtons.visibility = View.VISIBLE
|
||||||
requirePairingBinding().pairVerification.visibility = View.GONE
|
pairingBinding.pairVerification.visibility = View.GONE
|
||||||
}
|
}
|
||||||
PairingHandler.PairState.Requested -> {
|
PairingHandler.PairState.Requested -> {
|
||||||
with(requirePairingBinding()) {
|
with(pairingBinding) {
|
||||||
pairButton.visibility = View.GONE
|
pairButton.visibility = View.GONE
|
||||||
pairMessage.text = getString(R.string.pair_requested)
|
pairMessage.text = getString(R.string.pair_requested)
|
||||||
pairProgress.visibility = View.VISIBLE
|
pairProgress.visibility = View.VISIBLE
|
||||||
@ -285,7 +277,7 @@ class DeviceFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
PairingHandler.PairState.RequestedByPeer -> {
|
PairingHandler.PairState.RequestedByPeer -> {
|
||||||
with (requirePairingBinding()) {
|
with (pairingBinding) {
|
||||||
pairMessage.setText(R.string.pair_requested)
|
pairMessage.setText(R.string.pair_requested)
|
||||||
pairVerification.visibility = View.VISIBLE
|
pairVerification.visibility = View.VISIBLE
|
||||||
pairingButtons.visibility = View.VISIBLE
|
pairingButtons.visibility = View.VISIBLE
|
||||||
@ -295,25 +287,25 @@ class DeviceFragment : Fragment() {
|
|||||||
pairVerification.text = device.verificationKey
|
pairVerification.text = device.verificationKey
|
||||||
pairVerification.visibility = View.VISIBLE
|
pairVerification.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
requireDeviceBinding().deviceView.visibility = View.GONE
|
binding.deviceView.visibility = View.GONE
|
||||||
}
|
}
|
||||||
PairingHandler.PairState.Paired -> {
|
PairingHandler.PairState.Paired -> {
|
||||||
requirePairingBinding().pairingButtons.visibility = View.GONE
|
pairingBinding.pairingButtons.visibility = View.GONE
|
||||||
if (device.isReachable) {
|
if (device.isReachable) {
|
||||||
val context = requireContext()
|
val context = requireContext()
|
||||||
val pluginsWithButtons = device.loadedPlugins.values.filter { it.displayAsButton(context) }
|
val pluginsWithButtons = device.loadedPlugins.values.filter { it.displayAsButton(context) }
|
||||||
val pluginsNeedPermissions = device.pluginsWithoutPermissions.values.filter { device.isPluginEnabled(it.pluginKey) }
|
val pluginsNeedPermissions = device.pluginsWithoutPermissions.values.filter { device.isPluginEnabled(it.pluginKey) }
|
||||||
val pluginsNeedOptionalPermissions = device.pluginsWithoutOptionalPermissions.values.filter { device.isPluginEnabled(it.pluginKey) }
|
val pluginsNeedOptionalPermissions = device.pluginsWithoutOptionalPermissions.values.filter { device.isPluginEnabled(it.pluginKey) }
|
||||||
requireErrorBinding().errorMessageContainer.visibility = View.GONE
|
errorBinding.errorMessageContainer.visibility = View.GONE
|
||||||
requireDeviceBinding().deviceView.visibility = View.VISIBLE
|
binding.deviceView.visibility = View.VISIBLE
|
||||||
requireDeviceBinding().deviceViewCompose.apply {
|
binding.deviceViewCompose.apply {
|
||||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||||
setContent { KdeTheme(context) { PluginList(pluginsWithButtons, pluginsNeedPermissions, pluginsNeedOptionalPermissions) } }
|
setContent { KdeTheme(context) { PluginList(pluginsWithButtons, pluginsNeedPermissions, pluginsNeedOptionalPermissions) } }
|
||||||
}
|
}
|
||||||
displayBatteryInfoIfPossible()
|
displayBatteryInfoIfPossible()
|
||||||
} else {
|
} else {
|
||||||
requireErrorBinding().errorMessageContainer.visibility = View.VISIBLE
|
errorBinding.errorMessageContainer.visibility = View.VISIBLE
|
||||||
requireDeviceBinding().deviceView.visibility = View.GONE
|
binding.deviceView.visibility = View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -326,13 +318,13 @@ class DeviceFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun pairingSuccessful() {
|
override fun pairingSuccessful() {
|
||||||
requirePairingBinding().pairMessage.announceForAccessibility(getString(R.string.pair_succeeded))
|
pairingBinding.pairMessage.announceForAccessibility(getString(R.string.pair_succeeded))
|
||||||
mActivity?.runOnUiThread { refreshUI() }
|
mActivity?.runOnUiThread { refreshUI() }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun pairingFailed(error: String) {
|
override fun pairingFailed(error: String) {
|
||||||
mActivity?.runOnUiThread {
|
mActivity?.runOnUiThread {
|
||||||
with(requirePairingBinding()) {
|
with(pairingBinding) {
|
||||||
pairMessage.text = error
|
pairMessage.text = error
|
||||||
pairProgress.visibility = View.GONE
|
pairProgress.visibility = View.GONE
|
||||||
pairButton.visibility = View.VISIBLE
|
pairButton.visibility = View.VISIBLE
|
||||||
@ -344,7 +336,7 @@ class DeviceFragment : Fragment() {
|
|||||||
|
|
||||||
override fun unpaired() {
|
override fun unpaired() {
|
||||||
mActivity?.runOnUiThread {
|
mActivity?.runOnUiThread {
|
||||||
with(requirePairingBinding()) {
|
with(pairingBinding) {
|
||||||
pairMessage.setText(R.string.device_not_paired)
|
pairMessage.setText(R.string.device_not_paired)
|
||||||
pairProgress.visibility = View.GONE
|
pairProgress.visibility = View.GONE
|
||||||
pairButton.visibility = View.VISIBLE
|
pairButton.visibility = View.VISIBLE
|
||||||
@ -501,5 +493,4 @@ class DeviceFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,336 +0,0 @@
|
|||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: 2014 Albert Vaca Cintora <albertvaka@gmail.com>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.kde.kdeconnect.UserInterface;
|
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.provider.Settings;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.core.app.ActivityCompat;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
|
|
||||||
import org.kde.kdeconnect.BackgroundService;
|
|
||||||
import org.kde.kdeconnect.Device;
|
|
||||||
import org.kde.kdeconnect.Helpers.TrustedNetworkHelper;
|
|
||||||
import org.kde.kdeconnect.Helpers.WindowHelper;
|
|
||||||
import org.kde.kdeconnect.KdeConnect;
|
|
||||||
import org.kde.kdeconnect.UserInterface.List.ListAdapter;
|
|
||||||
import org.kde.kdeconnect.UserInterface.List.PairingDeviceItem;
|
|
||||||
import org.kde.kdeconnect.UserInterface.List.SectionItem;
|
|
||||||
import org.kde.kdeconnect_tp.R;
|
|
||||||
import org.kde.kdeconnect_tp.databinding.DevicesListBinding;
|
|
||||||
import org.kde.kdeconnect_tp.databinding.PairingExplanationDuplicateNamesBinding;
|
|
||||||
import org.kde.kdeconnect_tp.databinding.PairingExplanationNotTrustedBinding;
|
|
||||||
import org.kde.kdeconnect_tp.databinding.PairingExplanationTextBinding;
|
|
||||||
import org.kde.kdeconnect_tp.databinding.PairingExplanationTextNoNotificationsBinding;
|
|
||||||
import org.kde.kdeconnect_tp.databinding.PairingExplanationTextNoWifiBinding;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashSet;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The view that the user will see when there are no devices paired, or when you choose "add a new device" from the sidebar.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class PairingFragment extends Fragment implements PairingDeviceItem.Callback {
|
|
||||||
|
|
||||||
private static final int RESULT_PAIRING_SUCCESFUL = Activity.RESULT_FIRST_USER;
|
|
||||||
|
|
||||||
private DevicesListBinding devicesListBinding;
|
|
||||||
private PairingExplanationNotTrustedBinding pairingExplanationNotTrustedBinding;
|
|
||||||
private PairingExplanationTextBinding pairingExplanationTextBinding;
|
|
||||||
private PairingExplanationTextNoWifiBinding pairingExplanationTextNoWifiBinding;
|
|
||||||
private PairingExplanationDuplicateNamesBinding pairingExplanationDuplicateNamesBinding;
|
|
||||||
private PairingExplanationTextNoNotificationsBinding pairingExplanationTextNoNotificationsBinding;
|
|
||||||
|
|
||||||
private MainActivity mActivity;
|
|
||||||
|
|
||||||
private boolean listRefreshCalledThisFrame = false;
|
|
||||||
|
|
||||||
private TextView headerText;
|
|
||||||
private TextView noWifiHeader;
|
|
||||||
private TextView duplicateNamesHeader;
|
|
||||||
private TextView noNotificationsHeader;
|
|
||||||
private TextView notTrustedText;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
|
||||||
Bundle savedInstanceState) {
|
|
||||||
mActivity.getSupportActionBar().setTitle(R.string.pairing_title);
|
|
||||||
|
|
||||||
setHasOptionsMenu(true);
|
|
||||||
|
|
||||||
devicesListBinding = DevicesListBinding.inflate(inflater, container, false);
|
|
||||||
|
|
||||||
pairingExplanationNotTrustedBinding = PairingExplanationNotTrustedBinding.inflate(inflater);
|
|
||||||
notTrustedText = pairingExplanationNotTrustedBinding.getRoot();
|
|
||||||
notTrustedText.setOnClickListener(null);
|
|
||||||
notTrustedText.setOnLongClickListener(null);
|
|
||||||
|
|
||||||
pairingExplanationTextBinding = PairingExplanationTextBinding.inflate(inflater);
|
|
||||||
headerText = pairingExplanationTextBinding.getRoot();
|
|
||||||
headerText.setOnClickListener(null);
|
|
||||||
headerText.setOnLongClickListener(null);
|
|
||||||
|
|
||||||
pairingExplanationTextNoWifiBinding = PairingExplanationTextNoWifiBinding.inflate(inflater);
|
|
||||||
noWifiHeader = pairingExplanationTextNoWifiBinding.getRoot();
|
|
||||||
noWifiHeader.setOnClickListener(view -> startActivity(new Intent(Settings.ACTION_WIFI_SETTINGS)));
|
|
||||||
|
|
||||||
pairingExplanationDuplicateNamesBinding = PairingExplanationDuplicateNamesBinding.inflate(inflater);
|
|
||||||
duplicateNamesHeader = pairingExplanationDuplicateNamesBinding.getRoot();
|
|
||||||
|
|
||||||
pairingExplanationTextNoNotificationsBinding = PairingExplanationTextNoNotificationsBinding.inflate(inflater);
|
|
||||||
noNotificationsHeader = pairingExplanationTextNoNotificationsBinding.getRoot();
|
|
||||||
noNotificationsHeader.setOnClickListener(view -> ActivityCompat.requestPermissions(requireActivity(), new String[]{Manifest.permission.POST_NOTIFICATIONS}, MainActivity.RESULT_NOTIFICATIONS_ENABLED));
|
|
||||||
noNotificationsHeader.setOnLongClickListener(view -> {
|
|
||||||
Intent intent = new Intent();
|
|
||||||
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
|
||||||
Uri uri = Uri.fromParts("package", requireContext().getPackageName(), null);
|
|
||||||
intent.setData(uri);
|
|
||||||
startActivity(intent);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
devicesListBinding.devicesList.addHeaderView(headerText);
|
|
||||||
devicesListBinding.refreshListLayout.setOnRefreshListener(this::refreshDevicesAction);
|
|
||||||
|
|
||||||
return devicesListBinding.getRoot();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
|
||||||
super.onViewCreated(view, savedInstanceState);
|
|
||||||
|
|
||||||
// Configure focus order for Accessibility, for touchpads, and for TV remotes
|
|
||||||
// (allow focus of items in the device list)
|
|
||||||
devicesListBinding.devicesList.setItemsCanFocus(true);
|
|
||||||
WindowHelper.setupBottomPadding(devicesListBinding.devicesList);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroyView() {
|
|
||||||
super.onDestroyView();
|
|
||||||
devicesListBinding = null;
|
|
||||||
pairingExplanationNotTrustedBinding = null;
|
|
||||||
pairingExplanationTextBinding = null;
|
|
||||||
pairingExplanationTextNoWifiBinding = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(@NonNull Context context) {
|
|
||||||
super.onAttach(context);
|
|
||||||
mActivity = ((MainActivity) getActivity());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void refreshDevicesAction() {
|
|
||||||
BackgroundService.ForceRefreshConnections(requireContext());
|
|
||||||
|
|
||||||
devicesListBinding.refreshListLayout.setRefreshing(true);
|
|
||||||
devicesListBinding.refreshListLayout.postDelayed(() -> {
|
|
||||||
if (devicesListBinding != null) { // the view might be destroyed by now
|
|
||||||
devicesListBinding.refreshListLayout.setRefreshing(false);
|
|
||||||
}
|
|
||||||
}, 1500);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateDeviceList() {
|
|
||||||
if (!isAdded()) {
|
|
||||||
//Fragment is not attached to an activity. We will crash if we try to do anything here.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (listRefreshCalledThisFrame) {
|
|
||||||
// This makes sure we don't try to call list.getFirstVisiblePosition()
|
|
||||||
// twice per frame, because the second time the list hasn't been drawn
|
|
||||||
// yet and it would always return 0.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
listRefreshCalledThisFrame = true;
|
|
||||||
|
|
||||||
devicesListBinding.devicesList.removeHeaderView(duplicateNamesHeader);
|
|
||||||
|
|
||||||
//Check if we're on Wi-Fi/Local network. If we still see a device, don't do anything special
|
|
||||||
BackgroundService service = BackgroundService.getInstance();
|
|
||||||
if (service == null) {
|
|
||||||
updateConnectivityInfoHeader(true);
|
|
||||||
} else {
|
|
||||||
service.isConnectedToNonCellularNetwork().observe(this, this::updateConnectivityInfoHeader);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
final ArrayList<ListAdapter.Item> items = new ArrayList<>();
|
|
||||||
|
|
||||||
SectionItem connectedSection;
|
|
||||||
Resources res = getResources();
|
|
||||||
|
|
||||||
Collection<Device> devices = KdeConnect.getInstance().getDevices().values();
|
|
||||||
|
|
||||||
HashSet<String> seenNames = new HashSet<>();
|
|
||||||
for (Device device : devices) {
|
|
||||||
if (seenNames.contains(device.getName())) {
|
|
||||||
devicesListBinding.devicesList.addHeaderView(duplicateNamesHeader);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
seenNames.add(device.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
connectedSection = new SectionItem(res.getString(R.string.category_connected_devices));
|
|
||||||
items.add(connectedSection);
|
|
||||||
|
|
||||||
for (Device device : devices) {
|
|
||||||
if (device.isReachable() && device.isPaired()) {
|
|
||||||
items.add(new PairingDeviceItem(device, PairingFragment.this));
|
|
||||||
connectedSection.isEmpty = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (connectedSection.isEmpty) {
|
|
||||||
items.remove(items.size() - 1); //Remove connected devices section if empty
|
|
||||||
}
|
|
||||||
|
|
||||||
SectionItem availableSection = new SectionItem(res.getString(R.string.category_not_paired_devices));
|
|
||||||
items.add(availableSection);
|
|
||||||
for (Device device : devices) {
|
|
||||||
if (device.isReachable() && !device.isPaired()) {
|
|
||||||
items.add(new PairingDeviceItem(device, PairingFragment.this));
|
|
||||||
availableSection.isEmpty = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (availableSection.isEmpty && !connectedSection.isEmpty) {
|
|
||||||
items.remove(items.size() - 1); //Remove remembered devices section if empty
|
|
||||||
}
|
|
||||||
|
|
||||||
SectionItem rememberedSection = new SectionItem(res.getString(R.string.category_remembered_devices));
|
|
||||||
items.add(rememberedSection);
|
|
||||||
for (Device device : devices) {
|
|
||||||
if (!device.isReachable() && device.isPaired()) {
|
|
||||||
items.add(new PairingDeviceItem(device, PairingFragment.this));
|
|
||||||
rememberedSection.isEmpty = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (rememberedSection.isEmpty) {
|
|
||||||
items.remove(items.size() - 1); //Remove remembered devices section if empty
|
|
||||||
}
|
|
||||||
|
|
||||||
//Store current scroll
|
|
||||||
int index = devicesListBinding.devicesList.getFirstVisiblePosition();
|
|
||||||
View v = devicesListBinding.devicesList.getChildAt(0);
|
|
||||||
int top = (v == null) ? 0 : (v.getTop() - devicesListBinding.devicesList.getPaddingTop());
|
|
||||||
|
|
||||||
devicesListBinding.devicesList.setAdapter(new ListAdapter(mActivity, items));
|
|
||||||
|
|
||||||
//Restore scroll
|
|
||||||
devicesListBinding.devicesList.setSelectionFromTop(index, top);
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
//Ignore: The activity was closed while we were trying to update it
|
|
||||||
} finally {
|
|
||||||
listRefreshCalledThisFrame = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateConnectivityInfoHeader(boolean isConnectedToNonCellularNetwork) {
|
|
||||||
Collection<Device> devices = KdeConnect.getInstance().getDevices().values();
|
|
||||||
boolean someDevicesReachable = false;
|
|
||||||
for (Device device : devices) {
|
|
||||||
if (device.isReachable()) {
|
|
||||||
someDevicesReachable = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean hasNotificationsPermission = ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED;
|
|
||||||
|
|
||||||
devicesListBinding.devicesList.removeHeaderView(headerText);
|
|
||||||
devicesListBinding.devicesList.removeHeaderView(noWifiHeader);
|
|
||||||
devicesListBinding.devicesList.removeHeaderView(notTrustedText);
|
|
||||||
devicesListBinding.devicesList.removeHeaderView(noNotificationsHeader);
|
|
||||||
|
|
||||||
if (someDevicesReachable || isConnectedToNonCellularNetwork) {
|
|
||||||
if (!hasNotificationsPermission) {
|
|
||||||
devicesListBinding.devicesList.addHeaderView(noNotificationsHeader);
|
|
||||||
} else if (TrustedNetworkHelper.isTrustedNetwork(getContext())) {
|
|
||||||
devicesListBinding.devicesList.addHeaderView(headerText);
|
|
||||||
} else {
|
|
||||||
devicesListBinding.devicesList.addHeaderView(notTrustedText);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
devicesListBinding.devicesList.addHeaderView(noWifiHeader);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
KdeConnect.getInstance().addDeviceListChangedCallback("PairingFragment", () -> mActivity.runOnUiThread(this::updateDeviceList));
|
|
||||||
BackgroundService.ForceRefreshConnections(requireContext()); // force a network re-discover
|
|
||||||
updateDeviceList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStop() {
|
|
||||||
KdeConnect.getInstance().removeDeviceListChangedCallback("PairingFragment");
|
|
||||||
super.onStop();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void pairingClicked(Device device) {
|
|
||||||
mActivity.onDeviceSelected(device.getDeviceId(), !device.isPaired() || !device.isReachable());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
||||||
switch (requestCode) {
|
|
||||||
case RESULT_PAIRING_SUCCESFUL:
|
|
||||||
if (resultCode == 1) {
|
|
||||||
String deviceId = data.getStringExtra("deviceId");
|
|
||||||
mActivity.onDeviceSelected(deviceId);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater inflater) {
|
|
||||||
inflater.inflate(R.menu.pairing, menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
|
||||||
int id = item.getItemId();
|
|
||||||
if (id == R.id.menu_refresh) {
|
|
||||||
refreshDevicesAction();
|
|
||||||
return true;
|
|
||||||
} else if (id == R.id.menu_custom_device_list) {
|
|
||||||
startActivity(new Intent(mActivity, CustomDevicesActivity.class));
|
|
||||||
return true;
|
|
||||||
} else if (id == R.id.menu_trusted_networks) {
|
|
||||||
startActivity(new Intent(mActivity, TrustedNetworksActivity.class));
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
331
src/org/kde/kdeconnect/UserInterface/PairingFragment.kt
Normal file
331
src/org/kde/kdeconnect/UserInterface/PairingFragment.kt
Normal file
@ -0,0 +1,331 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2014 Albert Vaca Cintora <albertvaka@gmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
*/
|
||||||
|
package org.kde.kdeconnect.UserInterface
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.provider.Settings
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuInflater
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.view.MenuProvider
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import org.kde.kdeconnect.BackgroundService.Companion.ForceRefreshConnections
|
||||||
|
import org.kde.kdeconnect.BackgroundService.Companion.instance
|
||||||
|
import org.kde.kdeconnect.Device
|
||||||
|
import org.kde.kdeconnect.Helpers.TrustedNetworkHelper.Companion.isTrustedNetwork
|
||||||
|
import org.kde.kdeconnect.KdeConnect
|
||||||
|
import org.kde.kdeconnect.UserInterface.List.ListAdapter
|
||||||
|
import org.kde.kdeconnect.UserInterface.List.PairingDeviceItem
|
||||||
|
import org.kde.kdeconnect.UserInterface.List.SectionItem
|
||||||
|
import org.kde.kdeconnect.base.BaseFragment
|
||||||
|
import org.kde.kdeconnect.extensions.setupBottomPadding
|
||||||
|
import org.kde.kdeconnect_tp.R
|
||||||
|
import org.kde.kdeconnect_tp.databinding.DevicesListBinding
|
||||||
|
import org.kde.kdeconnect_tp.databinding.PairingExplanationDuplicateNamesBinding
|
||||||
|
import org.kde.kdeconnect_tp.databinding.PairingExplanationNotTrustedBinding
|
||||||
|
import org.kde.kdeconnect_tp.databinding.PairingExplanationTextBinding
|
||||||
|
import org.kde.kdeconnect_tp.databinding.PairingExplanationTextNoNotificationsBinding
|
||||||
|
import org.kde.kdeconnect_tp.databinding.PairingExplanationTextNoWifiBinding
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The view that the user will see when there are no devices paired, or when you choose "add a new device" from the sidebar.
|
||||||
|
*/
|
||||||
|
class PairingFragment : BaseFragment<DevicesListBinding>(), PairingDeviceItem.Callback {
|
||||||
|
|
||||||
|
private var _textBinding: PairingExplanationTextBinding? = null
|
||||||
|
private var _duplicateNamesBinding: PairingExplanationDuplicateNamesBinding? = null
|
||||||
|
private var _textNoWifiBinding: PairingExplanationTextNoWifiBinding? = null
|
||||||
|
private var _textNoNotificationsBinding: PairingExplanationTextNoNotificationsBinding? = null
|
||||||
|
private var _textNotTrustedBinding: PairingExplanationNotTrustedBinding? = null
|
||||||
|
|
||||||
|
private val headerText: TextView get() = _textBinding!!.root
|
||||||
|
private val noWifiHeader: TextView get() = _textNoWifiBinding!!.root
|
||||||
|
private val duplicateNamesHeader: TextView get() = _duplicateNamesBinding!!.root
|
||||||
|
private val noNotificationsHeader: TextView get() = _textNoNotificationsBinding!!.root
|
||||||
|
private val notTrustedText: TextView get() = _textNotTrustedBinding!!.root
|
||||||
|
|
||||||
|
private var listRefreshCalledThisFrame = false
|
||||||
|
|
||||||
|
private val mainActivity by lazy { activity as MainActivity }
|
||||||
|
|
||||||
|
private val menuProvider = object : MenuProvider {
|
||||||
|
|
||||||
|
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||||
|
menuInflater.inflate(R.menu.pairing, menu)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
|
||||||
|
return when (menuItem.itemId) {
|
||||||
|
R.id.menu_refresh -> {
|
||||||
|
refreshDevicesAction()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
R.id.menu_custom_device_list -> {
|
||||||
|
startActivity(Intent(mainActivity, CustomDevicesActivity::class.java))
|
||||||
|
true
|
||||||
|
}
|
||||||
|
R.id.menu_trusted_networks -> {
|
||||||
|
startActivity(Intent(mainActivity, TrustedNetworksActivity::class.java))
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onInflateBinding(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): DevicesListBinding {
|
||||||
|
_textBinding = PairingExplanationTextBinding.inflate(inflater)
|
||||||
|
_duplicateNamesBinding = PairingExplanationDuplicateNamesBinding.inflate(inflater)
|
||||||
|
_textNoWifiBinding = PairingExplanationTextNoWifiBinding.inflate(inflater)
|
||||||
|
_textNoNotificationsBinding = PairingExplanationTextNoNotificationsBinding.inflate(inflater)
|
||||||
|
_textNotTrustedBinding = PairingExplanationNotTrustedBinding.inflate(inflater)
|
||||||
|
return DevicesListBinding.inflate(inflater, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
// Configure focus order for Accessibility, for touchpads, and for TV remotes
|
||||||
|
// (allow focus of items in the device list)
|
||||||
|
binding.devicesList.itemsCanFocus = true
|
||||||
|
binding.devicesList.setupBottomPadding()
|
||||||
|
|
||||||
|
mainActivity.supportActionBar?.setTitle(R.string.pairing_title)
|
||||||
|
mainActivity.addMenuProvider(menuProvider, viewLifecycleOwner, Lifecycle.State.RESUMED)
|
||||||
|
|
||||||
|
notTrustedText.setOnClickListener(null)
|
||||||
|
notTrustedText.setOnLongClickListener(null)
|
||||||
|
|
||||||
|
headerText.setOnClickListener(null)
|
||||||
|
headerText.setOnLongClickListener(null)
|
||||||
|
|
||||||
|
|
||||||
|
noWifiHeader.setOnClickListener {
|
||||||
|
startActivity(Intent(Settings.ACTION_WIFI_SETTINGS))
|
||||||
|
}
|
||||||
|
|
||||||
|
noNotificationsHeader.setOnClickListener {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
ActivityCompat.requestPermissions(
|
||||||
|
requireActivity(),
|
||||||
|
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
|
||||||
|
MainActivity.RESULT_NOTIFICATIONS_ENABLED
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
noNotificationsHeader.setOnLongClickListener {
|
||||||
|
val intent = Intent()
|
||||||
|
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
||||||
|
val uri = Uri.fromParts("package", requireContext().packageName, null)
|
||||||
|
intent.setData(uri)
|
||||||
|
startActivity(intent)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.devicesList.addHeaderView(headerText)
|
||||||
|
binding.refreshListLayout.setOnRefreshListener { this.refreshDevicesAction() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_textBinding = null
|
||||||
|
_textNoWifiBinding = null
|
||||||
|
_textNoNotificationsBinding = null
|
||||||
|
_textNotTrustedBinding = null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun refreshDevicesAction() {
|
||||||
|
ForceRefreshConnections(requireContext())
|
||||||
|
|
||||||
|
binding.refreshListLayout.isRefreshing = true
|
||||||
|
binding.refreshListLayout.postDelayed({
|
||||||
|
if (isResumed && !isDetached) { // the view might be destroyed by now
|
||||||
|
binding.refreshListLayout.isRefreshing = false
|
||||||
|
}
|
||||||
|
}, 1500)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateDeviceList() {
|
||||||
|
if (!isAdded) {
|
||||||
|
//Fragment is not attached to an activity. We will crash if we try to do anything here.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listRefreshCalledThisFrame) {
|
||||||
|
// This makes sure we don't try to call list.getFirstVisiblePosition()
|
||||||
|
// twice per frame, because the second time the list hasn't been drawn
|
||||||
|
// yet and it would always return 0.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
listRefreshCalledThisFrame = true
|
||||||
|
|
||||||
|
binding.devicesList.removeHeaderView(duplicateNamesHeader)
|
||||||
|
|
||||||
|
//Check if we're on Wi-Fi/Local network. If we still see a device, don't do anything special
|
||||||
|
val service = instance
|
||||||
|
if (service == null) {
|
||||||
|
updateConnectivityInfoHeader(true)
|
||||||
|
} else {
|
||||||
|
service.isConnectedToNonCellularNetwork.observe(this, ::updateConnectivityInfoHeader)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val items = ArrayList<ListAdapter.Item>()
|
||||||
|
|
||||||
|
val connectedSection: SectionItem
|
||||||
|
val res = resources
|
||||||
|
|
||||||
|
val moreDevices = KdeConnect.getInstance().devices.values
|
||||||
|
|
||||||
|
val seenNames = hashSetOf<String>()
|
||||||
|
for (device in moreDevices) {
|
||||||
|
if (seenNames.contains(device.name)) {
|
||||||
|
binding.devicesList.addHeaderView(duplicateNamesHeader)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
seenNames.add(device.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedSection = SectionItem(res.getString(R.string.category_connected_devices))
|
||||||
|
items.add(connectedSection)
|
||||||
|
|
||||||
|
val devices: Collection<Device> = KdeConnect.getInstance().devices.values
|
||||||
|
for (device in devices) {
|
||||||
|
if (device.isReachable && device.isPaired) {
|
||||||
|
items.add(PairingDeviceItem(device, this@PairingFragment))
|
||||||
|
connectedSection.isEmpty = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (connectedSection.isEmpty) {
|
||||||
|
items.removeAt(items.size - 1) //Remove connected devices section if empty
|
||||||
|
}
|
||||||
|
|
||||||
|
val availableSection = SectionItem(res.getString(R.string.category_not_paired_devices))
|
||||||
|
items.add(availableSection)
|
||||||
|
for (device in devices) {
|
||||||
|
if (device.isReachable && !device.isPaired) {
|
||||||
|
items.add(PairingDeviceItem(device, this@PairingFragment))
|
||||||
|
availableSection.isEmpty = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (availableSection.isEmpty && !connectedSection.isEmpty) {
|
||||||
|
items.removeAt(items.size - 1) //Remove remembered devices section if empty
|
||||||
|
}
|
||||||
|
|
||||||
|
val rememberedSection = SectionItem(res.getString(R.string.category_remembered_devices))
|
||||||
|
items.add(rememberedSection)
|
||||||
|
for (device in devices) {
|
||||||
|
if (!device.isReachable && device.isPaired) {
|
||||||
|
items.add(PairingDeviceItem(device, this@PairingFragment))
|
||||||
|
rememberedSection.isEmpty = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rememberedSection.isEmpty) {
|
||||||
|
items.removeAt(items.size - 1) //Remove remembered devices section if empty
|
||||||
|
}
|
||||||
|
|
||||||
|
//Store current scroll
|
||||||
|
val index = binding.devicesList.firstVisiblePosition
|
||||||
|
val v = binding.devicesList.getChildAt(0)
|
||||||
|
val top = if ((v == null)) 0 else (v.top - binding.devicesList.paddingTop)
|
||||||
|
|
||||||
|
binding.devicesList.adapter = ListAdapter(mainActivity, items)
|
||||||
|
|
||||||
|
//Restore scroll
|
||||||
|
binding.devicesList.setSelectionFromTop(index, top)
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
//Ignore: The activity was closed while we were trying to update it
|
||||||
|
} finally {
|
||||||
|
listRefreshCalledThisFrame = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateConnectivityInfoHeader(isConnectedToNonCellularNetwork: Boolean) {
|
||||||
|
val devices: Collection<Device> = KdeConnect.getInstance().devices.values
|
||||||
|
var someDevicesReachable = false
|
||||||
|
for (device in devices) {
|
||||||
|
if (device.isReachable) {
|
||||||
|
someDevicesReachable = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val hasNotificationsPermission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
ContextCompat.checkSelfPermission(
|
||||||
|
requireContext(),
|
||||||
|
Manifest.permission.POST_NOTIFICATIONS
|
||||||
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.devicesList.removeHeaderView(headerText)
|
||||||
|
binding.devicesList.removeHeaderView(noWifiHeader)
|
||||||
|
binding.devicesList.removeHeaderView(notTrustedText)
|
||||||
|
binding.devicesList.removeHeaderView(noNotificationsHeader)
|
||||||
|
|
||||||
|
if (someDevicesReachable || isConnectedToNonCellularNetwork) {
|
||||||
|
if (!hasNotificationsPermission) {
|
||||||
|
binding.devicesList.addHeaderView(noNotificationsHeader)
|
||||||
|
} else if (isTrustedNetwork(requireContext())) {
|
||||||
|
binding.devicesList.addHeaderView(headerText)
|
||||||
|
} else {
|
||||||
|
binding.devicesList.addHeaderView(notTrustedText)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding.devicesList.addHeaderView(noWifiHeader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
KdeConnect.getInstance().addDeviceListChangedCallback("PairingFragment") {
|
||||||
|
mainActivity.runOnUiThread { this.updateDeviceList() }
|
||||||
|
}
|
||||||
|
ForceRefreshConnections(mainActivity) // force a network re-discover
|
||||||
|
updateDeviceList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
KdeConnect.getInstance().removeDeviceListChangedCallback("PairingFragment")
|
||||||
|
super.onStop()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pairingClicked(device: Device) {
|
||||||
|
mainActivity.onDeviceSelected(device.deviceId, !device.isPaired || !device.isReachable)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
when (requestCode) {
|
||||||
|
RESULT_PAIRING_SUCCESFUL -> if (resultCode == 1) {
|
||||||
|
val deviceId = data?.getStringExtra("deviceId")
|
||||||
|
mainActivity.onDeviceSelected(deviceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val RESULT_PAIRING_SUCCESFUL = Activity.RESULT_FIRST_USER
|
||||||
|
}
|
||||||
|
}
|
36
src/org/kde/kdeconnect/base/BaseFragment.kt
Normal file
36
src/org/kde/kdeconnect/base/BaseFragment.kt
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 Mash Kyrielight <fiepi@live.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
*/
|
||||||
|
package org.kde.kdeconnect.base
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.viewbinding.ViewBinding
|
||||||
|
|
||||||
|
abstract class BaseFragment<VB: ViewBinding> : Fragment() {
|
||||||
|
|
||||||
|
private var _binding: VB? = null
|
||||||
|
|
||||||
|
protected val binding get() = _binding!!
|
||||||
|
|
||||||
|
abstract fun onInflateBinding(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): VB
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
_binding = onInflateBinding(inflater, container, savedInstanceState)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
}
|
23
src/org/kde/kdeconnect/extensions/Bundle.kt
Normal file
23
src/org/kde/kdeconnect/extensions/Bundle.kt
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 Mash Kyrielight <fiepi@live.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
*/
|
||||||
|
package org.kde.kdeconnect.extensions
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.core.os.BundleCompat
|
||||||
|
|
||||||
|
|
||||||
|
inline fun <reified T> Bundle.getParcelableCompat(key: String): T? {
|
||||||
|
return BundleCompat.getParcelable(this, key, T::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T> Bundle.getParcelableArrayListCompat(key: String): ArrayList<T>? {
|
||||||
|
return BundleCompat.getParcelableArrayList(this, key, T::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T: Parcelable> Bundle.getParcelableArrayCompat(key: String): Array<Parcelable>? {
|
||||||
|
return BundleCompat.getParcelableArray(this, key, T::class.java)
|
||||||
|
}
|
23
src/org/kde/kdeconnect/extensions/Intent.kt
Normal file
23
src/org/kde/kdeconnect/extensions/Intent.kt
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 Mash Kyrielight <fiepi@live.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
*/
|
||||||
|
package org.kde.kdeconnect.extensions
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.core.content.IntentCompat
|
||||||
|
|
||||||
|
|
||||||
|
inline fun <reified T> Intent.getParcelableCompat(key: String): T? {
|
||||||
|
return IntentCompat.getParcelableExtra(this, key, T::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T> Intent.getParcelableArrayListCompat(key: String): ArrayList<T>? {
|
||||||
|
return IntentCompat.getParcelableArrayListExtra(this, key, T::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T: Parcelable> Intent.getParcelableArrayCompat(key: String): Array<Parcelable>? {
|
||||||
|
return IntentCompat.getParcelableArrayExtra(this, key, T::class.java)
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user