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:orientation="vertical"
|
||||
android:paddingHorizontal="@dimen/activity_horizontal_margin"
|
||||
android:paddingVertical="@dimen/activity_vertical_margin">
|
||||
|
||||
</androidx.recyclerview.widget.RecyclerView>
|
||||
android:paddingVertical="@dimen/activity_vertical_margin" />
|
||||
|
@ -16,6 +16,7 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.net.Network
|
||||
import android.os.Parcelable
|
||||
import android.preference.PreferenceManager
|
||||
import android.util.Base64
|
||||
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.NetworkPacket
|
||||
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.InputStreamReader
|
||||
import java.io.Reader
|
||||
@ -74,7 +77,7 @@ class BluetoothLinkProvider(private val context: Context) : BaseLinkProvider() {
|
||||
if (!preferences.getBoolean(SettingsFragment.KEY_BLUETOOTH_ENABLED, false)) {
|
||||
return
|
||||
}
|
||||
if (bluetoothAdapter == null || bluetoothAdapter.isEnabled == false) {
|
||||
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled) {
|
||||
return
|
||||
}
|
||||
Log.i("BluetoothLinkProvider", "onStart called")
|
||||
@ -297,8 +300,8 @@ class BluetoothLinkProvider(private val context: Context) : BaseLinkProvider() {
|
||||
val action = intent.action
|
||||
if (BluetoothDevice.ACTION_UUID == action) {
|
||||
Log.i("BluetoothLinkProvider", "Action matches")
|
||||
val device = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
|
||||
val activeUuids = intent.getParcelableArrayExtra(BluetoothDevice.EXTRA_UUID)
|
||||
val device = intent.getParcelableCompat<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
|
||||
val activeUuids = intent.getParcelableArrayCompat<Parcelable>(BluetoothDevice.EXTRA_UUID)
|
||||
if (sockets.containsKey(device)) {
|
||||
Log.i("BluetoothLinkProvider", "sockets contains device")
|
||||
return
|
||||
|
@ -15,6 +15,7 @@ import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.IntentCompat;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
@ -85,7 +86,7 @@ public class FindMyPhoneSettingsFragment extends PluginSettingsFragment {
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
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) {
|
||||
sharedPreferences.edit()
|
||||
|
@ -10,6 +10,7 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import org.kde.kdeconnect.KdeConnect
|
||||
import org.kde.kdeconnect.extensions.getParcelableCompat
|
||||
|
||||
/**
|
||||
* Called when the mpris media notification's buttons are pressed
|
||||
@ -20,7 +21,7 @@ class MprisMediaNotificationReceiver : BroadcastReceiver() {
|
||||
if (Intent.ACTION_MEDIA_BUTTON == intent.action) {
|
||||
// Route these buttons to the media session, which will handle them
|
||||
val mediaSession = MprisMediaSession.getMediaSession() ?: return
|
||||
mediaSession.controller.dispatchMediaButtonEvent(intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT))
|
||||
mediaSession.controller.dispatchMediaButtonEvent(intent.getParcelableCompat(Intent.EXTRA_KEY_EVENT))
|
||||
} else {
|
||||
// Second case: buttons on the notification, which we created ourselves
|
||||
// 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.RequiresApi;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.os.BundleCompat;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
import org.apache.commons.collections4.MultiValuedMap;
|
||||
@ -358,7 +359,7 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
if (!notification.extras.containsKey(Notification.EXTRA_MESSAGES))
|
||||
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)
|
||||
return new Pair<>(null, null);
|
||||
|
@ -25,10 +25,12 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.content.IntentCompat;
|
||||
import androidx.core.content.LocusIdCompat;
|
||||
import androidx.core.content.pm.ShortcutInfoCompat;
|
||||
import androidx.core.content.pm.ShortcutManagerCompat;
|
||||
import androidx.core.graphics.drawable.IconCompat;
|
||||
import androidx.core.os.BundleCompat;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
@ -355,10 +357,10 @@ public class SharePlugin extends Plugin {
|
||||
Log.i("SharePlugin", "Intent contains streams to share");
|
||||
ArrayList<Uri> uriList;
|
||||
if (Intent.ACTION_SEND_MULTIPLE.equals(intent.getAction())) {
|
||||
uriList = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
|
||||
uriList = IntentCompat.getParcelableArrayListExtra(intent, Intent.EXTRA_STREAM, Uri.class);
|
||||
} else {
|
||||
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));
|
||||
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.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
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_tp.R
|
||||
import org.kde.kdeconnect_tp.databinding.FragmentAboutBinding
|
||||
import androidx.core.net.toUri
|
||||
|
||||
class AboutFragment : Fragment() {
|
||||
private var _binding: FragmentAboutBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
private lateinit var aboutData: AboutData
|
||||
private var tapCount = 0
|
||||
private var firstTapMillis: Long? = null
|
||||
class AboutFragment : BaseFragment<FragmentAboutBinding>() {
|
||||
|
||||
companion object {
|
||||
private const val KEY_ABOUT_DATA = "about_data"
|
||||
|
||||
@JvmStatic
|
||||
fun newInstance(aboutData: AboutData): Fragment {
|
||||
val fragment = AboutFragment()
|
||||
|
||||
val args = Bundle(1)
|
||||
args.putParcelable("ABOUT_DATA", aboutData)
|
||||
args.putParcelable(KEY_ABOUT_DATA, aboutData)
|
||||
fragment.arguments = args
|
||||
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
if (activity != null) {
|
||||
(requireActivity() as MainActivity).supportActionBar?.setTitle(R.string.about)
|
||||
private lateinit var aboutData: AboutData
|
||||
private var tapCount = 0
|
||||
private var firstTapMillis: Long? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
aboutData = arguments?.getParcelableCompat(KEY_ABOUT_DATA) ?: throw IllegalArgumentException("AboutData is null")
|
||||
}
|
||||
|
||||
aboutData = requireArguments().getParcelable("ABOUT_DATA")!!
|
||||
_binding = FragmentAboutBinding.inflate(inflater, container, false)
|
||||
|
||||
updateData()
|
||||
return binding.root
|
||||
override fun onInflateBinding(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): FragmentAboutBinding {
|
||||
return FragmentAboutBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
(activity as? AppCompatActivity)?.supportActionBar?.setTitle(R.string.about)
|
||||
binding.scrollView.setupBottomPadding()
|
||||
updateData()
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
@ -65,8 +71,8 @@ class AboutFragment : Fragment() {
|
||||
// Update general info
|
||||
|
||||
binding.appName.text = aboutData.name
|
||||
binding.appIcon.setImageDrawable(this.context?.let { ContextCompat.getDrawable(it, aboutData.icon) })
|
||||
binding.appVersion.text = this.context?.getString(R.string.version, aboutData.versionName)
|
||||
binding.appIcon.setImageDrawable(context?.let { ContextCompat.getDrawable(it, aboutData.icon) })
|
||||
binding.appVersion.text = context?.getString(R.string.version, aboutData.versionName)
|
||||
|
||||
// Setup Easter Egg onClickListener
|
||||
|
||||
@ -103,7 +109,7 @@ class AboutFragment : Fragment() {
|
||||
setupInfoButton(aboutData.websiteURL, binding.websiteButton)
|
||||
|
||||
// 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) {
|
||||
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.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
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.tooling.preview.Preview
|
||||
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 org.kde.kdeconnect.BackgroundService
|
||||
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.RunCommandPlugin.RunCommandPlugin
|
||||
import org.kde.kdeconnect.UserInterface.compose.KdeTheme
|
||||
import org.kde.kdeconnect.base.BaseFragment
|
||||
import org.kde.kdeconnect.extensions.setupBottomPadding
|
||||
import org.kde.kdeconnect_tp.R
|
||||
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
|
||||
*/
|
||||
class DeviceFragment : Fragment() {
|
||||
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")
|
||||
class DeviceFragment : BaseFragment<ActivityDeviceBinding>() {
|
||||
|
||||
companion object {
|
||||
private const val ARG_DEVICE_ID = "deviceId"
|
||||
@ -97,87 +71,40 @@ 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?
|
||||
): View? {
|
||||
deviceBinding = ActivityDeviceBinding.inflate(inflater, container, false)
|
||||
val deviceBinding = deviceBinding ?: return null
|
||||
|
||||
// Inner binding for the layout shown when we're not paired yet...
|
||||
pairingBinding = deviceBinding.pairRequest
|
||||
// ...and for when pairing doesn't (or can't) work
|
||||
errorBinding = deviceBinding.pairError
|
||||
|
||||
device = KdeConnect.getInstance().getDevice(deviceId)
|
||||
|
||||
requireErrorBinding().errorMessageContainer.setOnRefreshListener {
|
||||
this.refreshDevicesAction()
|
||||
): ActivityDeviceBinding {
|
||||
return ActivityDeviceBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
requirePairingBinding().pairButton.setOnClickListener {
|
||||
device?.requestPairing()
|
||||
refreshUI()
|
||||
}
|
||||
requirePairingBinding().acceptButton.setOnClickListener {
|
||||
device?.apply {
|
||||
acceptPairing()
|
||||
requirePairingBinding().pairingButtons.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
requirePairingBinding().rejectButton.setOnClickListener {
|
||||
device?.apply {
|
||||
// Remove listener so buttons don't show for an instant before changing the view
|
||||
removePluginsChangedListener(pluginsChangedListener)
|
||||
removePairingCallback(pairingCallback)
|
||||
cancelPairing()
|
||||
}
|
||||
mActivity?.onDeviceSelected(null)
|
||||
}
|
||||
setHasOptionsMenu(true)
|
||||
|
||||
device?.apply {
|
||||
mActivity?.supportActionBar?.title = name
|
||||
addPairingCallback(pairingCallback)
|
||||
addPluginsChangedListener(pluginsChangedListener)
|
||||
} ?: run { // device is null
|
||||
Log.e(TAG, "Trying to display a device fragment but the device is not present")
|
||||
mActivity?.onDeviceSelected(null)
|
||||
}
|
||||
|
||||
refreshUI()
|
||||
|
||||
return deviceBinding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
deviceBinding?.deviceView?.setupBottomPadding()
|
||||
}
|
||||
|
||||
private fun refreshDevicesAction() {
|
||||
BackgroundService.ForceRefreshConnections(requireContext())
|
||||
requireErrorBinding().errorMessageContainer.isRefreshing = true
|
||||
requireErrorBinding().errorMessageContainer.postDelayed({
|
||||
errorBinding?.errorMessageContainer?.isRefreshing = false // check for null since the view might be destroyed by now
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
private val pluginsChangedListener = PluginsChangedListener { mActivity?.runOnUiThread { refreshUI() } }
|
||||
override fun onDestroyView() {
|
||||
device?.apply {
|
||||
removePluginsChangedListener(pluginsChangedListener)
|
||||
removePairingCallback(pairingCallback)
|
||||
}
|
||||
device = null
|
||||
pairingBinding = null
|
||||
errorBinding = null
|
||||
deviceBinding = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||
super.onPrepareOptionsMenu(menu)
|
||||
private val menuProvider = object : MenuProvider {
|
||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||
menu.clear()
|
||||
val device = device ?: return
|
||||
|
||||
@ -243,6 +170,71 @@ class DeviceFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
refreshUI()
|
||||
}
|
||||
pairingBinding.acceptButton.setOnClickListener {
|
||||
device?.apply {
|
||||
acceptPairing()
|
||||
pairingBinding.pairingButtons.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
pairingBinding.rejectButton.setOnClickListener {
|
||||
device?.apply {
|
||||
// Remove listener so buttons don't show for an instant before changing the view
|
||||
removePluginsChangedListener(pluginsChangedListener)
|
||||
removePairingCallback(pairingCallback)
|
||||
cancelPairing()
|
||||
}
|
||||
mActivity?.onDeviceSelected(null)
|
||||
}
|
||||
device = KdeConnect.getInstance().getDevice(deviceId)
|
||||
device?.apply {
|
||||
mActivity?.supportActionBar?.title = name
|
||||
addPairingCallback(pairingCallback)
|
||||
addPluginsChangedListener(pluginsChangedListener)
|
||||
} ?: run { // device is null
|
||||
Log.e(TAG, "Trying to display a device fragment but the device is not present")
|
||||
mActivity?.onDeviceSelected(null)
|
||||
}
|
||||
mActivity?.addMenuProvider(menuProvider, viewLifecycleOwner)
|
||||
refreshUI()
|
||||
}
|
||||
|
||||
private fun refreshDevicesAction() {
|
||||
BackgroundService.ForceRefreshConnections(requireContext())
|
||||
errorBinding.errorMessageContainer.isRefreshing = true
|
||||
errorBinding.errorMessageContainer.postDelayed({
|
||||
if (viewLifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) {
|
||||
errorBinding.errorMessageContainer.isRefreshing = false // check for null since the view might be destroyed by now
|
||||
}
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
private val pluginsChangedListener = PluginsChangedListener { mActivity?.runOnUiThread { refreshUI() } }
|
||||
|
||||
override fun onDestroyView() {
|
||||
device?.apply {
|
||||
removePluginsChangedListener(pluginsChangedListener)
|
||||
removePairingCallback(pairingCallback)
|
||||
}
|
||||
device = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
with(requireView()) {
|
||||
@ -270,13 +262,13 @@ class DeviceFragment : Fragment() {
|
||||
|
||||
when (device.pairStatus) {
|
||||
PairingHandler.PairState.NotPaired -> {
|
||||
requireErrorBinding().errorMessageContainer.visibility = View.GONE
|
||||
requireDeviceBinding().deviceView.visibility = View.GONE
|
||||
requirePairingBinding().pairingButtons.visibility = View.VISIBLE
|
||||
requirePairingBinding().pairVerification.visibility = View.GONE
|
||||
errorBinding.errorMessageContainer.visibility = View.GONE
|
||||
binding.deviceView.visibility = View.GONE
|
||||
pairingBinding.pairingButtons.visibility = View.VISIBLE
|
||||
pairingBinding.pairVerification.visibility = View.GONE
|
||||
}
|
||||
PairingHandler.PairState.Requested -> {
|
||||
with(requirePairingBinding()) {
|
||||
with(pairingBinding) {
|
||||
pairButton.visibility = View.GONE
|
||||
pairMessage.text = getString(R.string.pair_requested)
|
||||
pairProgress.visibility = View.VISIBLE
|
||||
@ -285,7 +277,7 @@ class DeviceFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
PairingHandler.PairState.RequestedByPeer -> {
|
||||
with (requirePairingBinding()) {
|
||||
with (pairingBinding) {
|
||||
pairMessage.setText(R.string.pair_requested)
|
||||
pairVerification.visibility = View.VISIBLE
|
||||
pairingButtons.visibility = View.VISIBLE
|
||||
@ -295,25 +287,25 @@ class DeviceFragment : Fragment() {
|
||||
pairVerification.text = device.verificationKey
|
||||
pairVerification.visibility = View.VISIBLE
|
||||
}
|
||||
requireDeviceBinding().deviceView.visibility = View.GONE
|
||||
binding.deviceView.visibility = View.GONE
|
||||
}
|
||||
PairingHandler.PairState.Paired -> {
|
||||
requirePairingBinding().pairingButtons.visibility = View.GONE
|
||||
pairingBinding.pairingButtons.visibility = View.GONE
|
||||
if (device.isReachable) {
|
||||
val context = requireContext()
|
||||
val pluginsWithButtons = device.loadedPlugins.values.filter { it.displayAsButton(context) }
|
||||
val pluginsNeedPermissions = device.pluginsWithoutPermissions.values.filter { device.isPluginEnabled(it.pluginKey) }
|
||||
val pluginsNeedOptionalPermissions = device.pluginsWithoutOptionalPermissions.values.filter { device.isPluginEnabled(it.pluginKey) }
|
||||
requireErrorBinding().errorMessageContainer.visibility = View.GONE
|
||||
requireDeviceBinding().deviceView.visibility = View.VISIBLE
|
||||
requireDeviceBinding().deviceViewCompose.apply {
|
||||
errorBinding.errorMessageContainer.visibility = View.GONE
|
||||
binding.deviceView.visibility = View.VISIBLE
|
||||
binding.deviceViewCompose.apply {
|
||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||
setContent { KdeTheme(context) { PluginList(pluginsWithButtons, pluginsNeedPermissions, pluginsNeedOptionalPermissions) } }
|
||||
}
|
||||
displayBatteryInfoIfPossible()
|
||||
} else {
|
||||
requireErrorBinding().errorMessageContainer.visibility = View.VISIBLE
|
||||
requireDeviceBinding().deviceView.visibility = View.GONE
|
||||
errorBinding.errorMessageContainer.visibility = View.VISIBLE
|
||||
binding.deviceView.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -326,13 +318,13 @@ class DeviceFragment : Fragment() {
|
||||
}
|
||||
|
||||
override fun pairingSuccessful() {
|
||||
requirePairingBinding().pairMessage.announceForAccessibility(getString(R.string.pair_succeeded))
|
||||
pairingBinding.pairMessage.announceForAccessibility(getString(R.string.pair_succeeded))
|
||||
mActivity?.runOnUiThread { refreshUI() }
|
||||
}
|
||||
|
||||
override fun pairingFailed(error: String) {
|
||||
mActivity?.runOnUiThread {
|
||||
with(requirePairingBinding()) {
|
||||
with(pairingBinding) {
|
||||
pairMessage.text = error
|
||||
pairProgress.visibility = View.GONE
|
||||
pairButton.visibility = View.VISIBLE
|
||||
@ -344,7 +336,7 @@ class DeviceFragment : Fragment() {
|
||||
|
||||
override fun unpaired() {
|
||||
mActivity?.runOnUiThread {
|
||||
with(requirePairingBinding()) {
|
||||
with(pairingBinding) {
|
||||
pairMessage.setText(R.string.device_not_paired)
|
||||
pairProgress.visibility = View.GONE
|
||||
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