diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 00000000..13566b81
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/icon.png b/.idea/icon.png
new file mode 100644
index 00000000..c74b4c11
Binary files /dev/null and b/.idea/icon.png differ
diff --git a/build.gradle b/build.gradle
index 64e732b8..50ab691d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -4,7 +4,7 @@ import com.android.build.gradle.api.ApplicationVariant
import com.github.jk1.license.render.TextReportRenderer
buildscript {
- ext.kotlin_version = '1.8.0'
+ ext.kotlin_version = '1.8.10'
dependencies {
classpath 'com.android.tools.build:gradle:7.4.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
diff --git a/res/layout-w820dp/activity_main.xml b/res/layout-w820dp/activity_main.xml
new file mode 100644
index 00000000..59cc39b5
--- /dev/null
+++ b/res/layout-w820dp/activity_main.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/activity_device.xml b/res/layout/activity_device.xml
index 97e04aaf..10290a18 100644
--- a/res/layout/activity_device.xml
+++ b/res/layout/activity_device.xml
@@ -1,44 +1,51 @@
-
+ android:layout_height="match_parent">
-
-
+
-
-
+
-
-
+
+
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/activity_main.xml b/res/layout/activity_main.xml
index fc23d86b..6502e5ab 100644
--- a/res/layout/activity_main.xml
+++ b/res/layout/activity_main.xml
@@ -1,32 +1,37 @@
-
-
-
+ android:layout_height="match_parent">
-
-
-
+ android:fitsSystemWindows="true">
-
+
-
+
-
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/list_item_plugin_header.xml b/res/layout/list_item_plugin_header.xml
index 95025c0c..ac9aa48e 100644
--- a/res/layout/list_item_plugin_header.xml
+++ b/res/layout/list_item_plugin_header.xml
@@ -1,9 +1,11 @@
+ android:maxWidth="400dp"
+ android:layout_height="wrap_content"
+ android:background="?selectableItemBackground"
+ android:paddingHorizontal="@dimen/activity_horizontal_margin"
+ android:paddingVertical="@dimen/activity_vertical_margin"
+ tools:background="@android:color/darker_gray"
+ tools:text="@tools:sample/lorem"/>
diff --git a/res/layout/list_plugin_entry.xml b/res/layout/list_plugin_entry.xml
index 37ac4b9b..ec50633c 100644
--- a/res/layout/list_plugin_entry.xml
+++ b/res/layout/list_plugin_entry.xml
@@ -2,8 +2,9 @@
@@ -17,7 +18,7 @@
android:minHeight="?android:attr/listPreferredItemHeight"
android:orientation="vertical">
-
-
+ android:text="@string/request_pairing" />
+ android:visibility="gone"
+ android:paddingVertical="4dp"
+ tools:visibility="visible">
-
+ android:text="@string/pairing_accept" />
-
+
+
+ android:text="@string/pairing_reject" />
\ No newline at end of file
diff --git a/res/values-land/consts.xml b/res/values-land/consts.xml
index 7abe2d3a..1c6307b1 100644
--- a/res/values-land/consts.xml
+++ b/res/values-land/consts.xml
@@ -1,11 +1,6 @@
- @integer/orientation_horizontal
-
- 4
- 6
-
@integer/orientation_horizontal
1
1
diff --git a/res/values-w820dp/consts.xml b/res/values-w600dp/consts.xml
similarity index 100%
rename from res/values-w820dp/consts.xml
rename to res/values-w600dp/consts.xml
diff --git a/res/values/consts.xml b/res/values/consts.xml
index 67dcda44..5401c764 100644
--- a/res/values/consts.xml
+++ b/res/values/consts.xml
@@ -10,9 +10,6 @@
- -2
- @integer/orientation_vertical
- @null
- @null
2
diff --git a/src/org/kde/kdeconnect/Plugins/RemoteKeyboardPlugin/RemoteKeyboardService.java b/src/org/kde/kdeconnect/Plugins/RemoteKeyboardPlugin/RemoteKeyboardService.java
index 95b1d011..d6171593 100644
--- a/src/org/kde/kdeconnect/Plugins/RemoteKeyboardPlugin/RemoteKeyboardService.java
+++ b/src/org/kde/kdeconnect/Plugins/RemoteKeyboardPlugin/RemoteKeyboardService.java
@@ -162,7 +162,7 @@ public class RemoteKeyboardService
}
} else { // != 1 instance of plugin -> show main activity view
Intent intent = new Intent(this, MainActivity.class);
- intent.putExtra("forceOverview", true);
+ intent.putExtra(MainActivity.FLAG_FORCE_OVERVIEW, true);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
if (instances.size() < 1)
diff --git a/src/org/kde/kdeconnect/Plugins/StubTextPlugin.kt b/src/org/kde/kdeconnect/Plugins/StubTextPlugin.kt
new file mode 100644
index 00000000..a3d3b1f7
--- /dev/null
+++ b/src/org/kde/kdeconnect/Plugins/StubTextPlugin.kt
@@ -0,0 +1,15 @@
+package org.kde.kdeconnect.Plugins
+
+class StubTextPlugin(private val description: String) : Plugin() {
+ override fun getDisplayName() = description
+
+ override fun getDescription() = description
+
+ override fun getSupportedPacketTypes(): Array {
+ throw UnsupportedOperationException("StubTextPlugin is used only with displayName and description")
+ }
+
+ override fun getOutgoingPacketTypes(): Array {
+ throw UnsupportedOperationException("StubTextPlugin is used only with displayName and description")
+ }
+}
\ No newline at end of file
diff --git a/src/org/kde/kdeconnect/Plugins/SystemVolumePlugin/SystemVolumeProvider.kt b/src/org/kde/kdeconnect/Plugins/SystemVolumePlugin/SystemVolumeProvider.kt
index e967e513..75bb2527 100644
--- a/src/org/kde/kdeconnect/Plugins/SystemVolumePlugin/SystemVolumeProvider.kt
+++ b/src/org/kde/kdeconnect/Plugins/SystemVolumePlugin/SystemVolumeProvider.kt
@@ -32,13 +32,11 @@ internal class SystemVolumeProvider private constructor(plugin: SystemVolumePlug
@JvmStatic
fun fromPlugin(systemVolumePlugin: SystemVolumePlugin): SystemVolumeProvider {
- if (currentProvider == null) {
- currentProvider = SystemVolumeProvider(systemVolumePlugin)
- }
+ val currentProvider = currentProvider ?: SystemVolumeProvider(systemVolumePlugin)
- currentProvider!!.update(systemVolumePlugin)
+ currentProvider.update(systemVolumePlugin)
- return currentProvider!!
+ return currentProvider
}
private fun scale(value: Int, maxValue: Int, maxScaled: Int): Int {
diff --git a/src/org/kde/kdeconnect/UserInterface/About/AboutData.kt b/src/org/kde/kdeconnect/UserInterface/About/AboutData.kt
index fd9595a8..3faca689 100644
--- a/src/org/kde/kdeconnect/UserInterface/About/AboutData.kt
+++ b/src/org/kde/kdeconnect/UserInterface/About/AboutData.kt
@@ -41,12 +41,10 @@ class AboutData(var name: String, var description: Int, var icon: Int, var versi
parcel.writeString(sourceCodeURL)
parcel.writeString(donateURL)
- if (authorsFooterText == null) {
- parcel.writeByte(0x00)
- } else {
+ authorsFooterText?.let {
parcel.writeByte(0x01)
- parcel.writeInt(authorsFooterText!!)
- }
+ parcel.writeInt(it)
+ } ?: parcel.writeByte(0x00)
}
override fun describeContents(): Int = 0
diff --git a/src/org/kde/kdeconnect/UserInterface/DeviceFragment.kt b/src/org/kde/kdeconnect/UserInterface/DeviceFragment.kt
index 2bb87dbf..516bcc32 100644
--- a/src/org/kde/kdeconnect/UserInterface/DeviceFragment.kt
+++ b/src/org/kde/kdeconnect/UserInterface/DeviceFragment.kt
@@ -6,7 +6,6 @@
package org.kde.kdeconnect.UserInterface
import android.content.Intent
-import android.content.res.Configuration
import android.os.Bundle
import android.util.Log
import android.view.KeyEvent
@@ -14,10 +13,10 @@ import android.view.LayoutInflater
import android.view.Menu
import android.view.View
import android.view.ViewGroup
-import android.widget.LinearLayout
import androidx.annotation.StringRes
-import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
+import androidx.recyclerview.widget.GridLayoutManager
+import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.kde.kdeconnect.BackgroundService
import org.kde.kdeconnect.Device
@@ -26,10 +25,8 @@ import org.kde.kdeconnect.Device.PluginsChangedListener
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper
import org.kde.kdeconnect.Plugins.BatteryPlugin.BatteryPlugin
import org.kde.kdeconnect.Plugins.Plugin
-import org.kde.kdeconnect.UserInterface.List.FailedPluginListItem
-import org.kde.kdeconnect.UserInterface.List.ListAdapter
-import org.kde.kdeconnect.UserInterface.List.PluginItem
-import org.kde.kdeconnect.UserInterface.List.PluginListHeaderItem
+import org.kde.kdeconnect.Plugins.StubTextPlugin
+import org.kde.kdeconnect.UserInterface.List.PluginAdapter
import org.kde.kdeconnect_tp.R
import org.kde.kdeconnect_tp.databinding.ActivityDeviceBinding
import org.kde.kdeconnect_tp.databinding.ViewPairErrorBinding
@@ -48,8 +45,8 @@ class DeviceFragment : Fragment() {
private val mActivity: MainActivity? by lazy { activity as MainActivity? }
//TODO use LinkedHashMap and delete irrelevant records when plugins changed
- private val pluginListItems: ArrayList = ArrayList()
- private val permissionListItems: ArrayList = ArrayList()
+ private val pluginListItems: ArrayList Unit)?>> = ArrayList()
+ private val permissionListItems: ArrayList Unit)?>> = ArrayList()
/**
* Top-level ViewBinding for this fragment.
@@ -106,11 +103,13 @@ class DeviceFragment : Fragment() {
}
requireBinding().pairButton.setOnClickListener {
- requireBinding().pairButton.visibility = View.GONE
- requireBinding().pairMessage.text = null
- requireBinding().pairVerification.visibility = View.VISIBLE
- requireBinding().pairVerification.text = SslHelper.getVerificationKey(SslHelper.certificate, device?.certificate)
- requireBinding().pairProgress.visibility = View.VISIBLE
+ with(requireBinding()) {
+ pairButton.visibility = View.GONE
+ pairMessage.text = null
+ pairVerification.visibility = View.VISIBLE
+ pairVerification.text = SslHelper.getVerificationKey(SslHelper.certificate, device?.certificate)
+ pairProgress.visibility = View.VISIBLE
+ }
device?.requestPairing()
}
requireBinding().acceptButton.setOnClickListener {
@@ -141,6 +140,10 @@ class DeviceFragment : Fragment() {
refreshUI()
}
+ requireDeviceBinding().pluginsList.layoutManager =
+ GridLayoutManager(requireContext(), resources.getInteger(R.integer.plugins_columns))
+ requireDeviceBinding().buttonsList.layoutManager = LinearLayoutManager(requireContext())
+
return deviceBinding.root
}
@@ -264,37 +267,41 @@ class DeviceFragment : Fragment() {
if (paired && reachable) {
//Plugins button list
val plugins: Collection = device.loadedPlugins.values
+
+ //TODO look for LinkedHashMap mention above
pluginListItems.clear()
permissionListItems.clear()
+
+ //Fill enabled plugins ArrayList
for (p in plugins) {
if (!p.hasMainActivity(context) || p.displayInContextMenu()) continue
- pluginListItems.add(PluginItem(p) { p.startMainActivity(mActivity) })
+ pluginListItems.add(p to { p.startMainActivity(mActivity) })
}
+
+ //Fill permissionListItems with permissions plugins
createPermissionsList(
device.pluginsWithoutPermissions,
R.string.plugins_need_permission
- ) { plugin: Plugin ->
- val dialog = plugin.permissionExplanationDialog
- dialog?.show(childFragmentManager, null)
+ ) { p: Plugin ->
+ p.permissionExplanationDialog?.show(childFragmentManager, null)
}
createPermissionsList(
device.pluginsWithoutOptionalPermissions,
R.string.plugins_need_optional_permission
- ) { plugin: Plugin ->
- val dialog: DialogFragment? = plugin.optionalPermissionExplanationDialog
- dialog?.show(childFragmentManager, null)
+ ) { p: Plugin ->
+ p.optionalPermissionExplanationDialog?.show(childFragmentManager, null)
}
+ requireDeviceBinding().buttonsList.adapter =
+ PluginAdapter(permissionListItems, R.layout.list_item_plugin_header)
+ requireDeviceBinding().pluginsList.adapter =
+ PluginAdapter(pluginListItems, R.layout.list_plugin_entry)
+
+ requireDeviceBinding().pluginsList.adapter?.notifyDataSetChanged()
+
displayBatteryInfoIfPossible()
}
- requireDeviceBinding().pluginsList.adapter = ListAdapter(mActivity, pluginListItems)
- //don't do unnecessary work when all permissions granted and remove view for landscape orientation
- if (permissionListItems.isEmpty()) {
- requireDeviceBinding().buttonsList.visibility = View.GONE
- } else {
- requireDeviceBinding().buttonsList.adapter = ListAdapter(mActivity, permissionListItems)
- requireDeviceBinding().buttonsList.visibility = View.VISIBLE
- }
+
mActivity?.invalidateOptionsMenu()
} catch (e: IllegalStateException) {
//Ignore: The activity was closed while we were trying to update it
@@ -320,7 +327,7 @@ class DeviceFragment : Fragment() {
mActivity?.runOnUiThread {
with(requireBinding()) {
pairMessage.text = error
- pairVerification.text = ""
+ pairVerification.text = null
pairVerification.visibility = View.GONE
pairProgress.visibility = View.GONE
pairButton.visibility = View.VISIBLE
@@ -346,17 +353,16 @@ class DeviceFragment : Fragment() {
private fun createPermissionsList(
plugins: ConcurrentHashMap,
- headerText: Int,
- action: FailedPluginListItem.Action
+ @StringRes headerText: Int,
+ action: (Plugin) -> Unit,
) {
if (plugins.isEmpty()) return
val device = device ?: return
- permissionListItems.add(PluginListHeaderItem(headerText))
+ permissionListItems.add(StubTextPlugin(requireContext().getString(headerText)) to null)
for (plugin in plugins.values) {
- if (!device.isPluginEnabled(plugin.pluginKey)) {
- continue
+ if (device.isPluginEnabled(plugin.pluginKey)) {
+ permissionListItems.add(plugin to { action(plugin) })
}
- permissionListItems.add(FailedPluginListItem(plugin, action))
}
}
@@ -375,12 +381,10 @@ class DeviceFragment : Fragment() {
if (info != null) {
@StringRes
- val resId: Int = if (info.isCharging) {
- R.string.battery_status_charging_format
- } else if (BatteryPlugin.isLowBattery(info)) {
- R.string.battery_status_low_format
- } else {
- R.string.battery_status_format
+ val resId = when {
+ info.isCharging -> R.string.battery_status_charging_format
+ BatteryPlugin.isLowBattery(info) -> R.string.battery_status_low_format
+ else -> R.string.battery_status_format
}
mActivity?.supportActionBar?.subtitle = mActivity?.getString(resId, info.currentCharge)
diff --git a/src/org/kde/kdeconnect/UserInterface/List/PluginAdapter.kt b/src/org/kde/kdeconnect/UserInterface/List/PluginAdapter.kt
new file mode 100644
index 00000000..1c0a72cb
--- /dev/null
+++ b/src/org/kde/kdeconnect/UserInterface/List/PluginAdapter.kt
@@ -0,0 +1,55 @@
+package org.kde.kdeconnect.UserInterface.List
+
+import android.annotation.TargetApi
+import android.os.Build
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import org.kde.kdeconnect.Plugins.Plugin
+import org.kde.kdeconnect.Plugins.StubTextPlugin
+import org.kde.kdeconnect_tp.R
+
+/**
+ * Adapter for showing enabled plugins and permission requests
+ * can be used with following layouts:
+ * list_plugin_entry - card view with text and icon
+ * list_item_plugin_header - plain TextView
+ * Any other TextView layout
+ */
+class PluginAdapter(
+ private val pluginList: ArrayList Unit)?>>,
+ private val layout: Int,
+) : RecyclerView.Adapter() {
+
+ override fun onCreateViewHolder(viewGroup: ViewGroup, type: Int) =
+ PluginViewHolder(LayoutInflater.from(viewGroup.context).inflate(layout, viewGroup, false))
+
+ override fun getItemCount() = pluginList.size
+
+ @TargetApi(Build.VERSION_CODES.M)
+ override fun onBindViewHolder(holder: PluginViewHolder, position: Int) {
+ pluginList[position].let { (plugin, action) ->
+ holder.pluginTitle.text = plugin.displayName
+ holder.pluginIcon?.setImageDrawable(plugin.icon)
+
+ //Set regular text for unclickable StubTextPlugin and bold for supposedly clickable TextView items
+ when {
+ plugin is StubTextPlugin ->
+ holder.pluginTitle.setTextAppearance(R.style.TextAppearance_Material3_BodyMedium)
+ holder.itemView is TextView ->
+ holder.pluginTitle.setTextAppearance(R.style.TextAppearance_Material3_LabelLarge)
+ }
+
+ action?.let { holder.itemView.setOnClickListener { action.invoke() } }
+ }
+ }
+
+ class PluginViewHolder(view: View) : RecyclerView.ViewHolder(view) {
+ val pluginTitle: TextView = view.findViewById(R.id.list_item_entry_title) ?: view as TextView
+ val pluginIcon: ImageView? = view.findViewById(R.id.list_item_entry_icon)
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/kde/kdeconnect/UserInterface/MainActivity.java b/src/org/kde/kdeconnect/UserInterface/MainActivity.java
deleted file mode 100644
index 9a5d1cfb..00000000
--- a/src/org/kde/kdeconnect/UserInterface/MainActivity.java
+++ /dev/null
@@ -1,424 +0,0 @@
-package org.kde.kdeconnect.UserInterface;
-
-import android.Manifest;
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.SubMenu;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.ActionBar;
-import androidx.appcompat.app.ActionBarDrawerToggle;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.core.view.GravityCompat;
-import androidx.drawerlayout.widget.DrawerLayout;
-import androidx.fragment.app.Fragment;
-import androidx.preference.PreferenceManager;
-
-import com.google.android.material.navigation.NavigationView;
-
-import org.apache.commons.lang3.ArrayUtils;
-import org.kde.kdeconnect.BackgroundService;
-import org.kde.kdeconnect.Device;
-import org.kde.kdeconnect.Helpers.DeviceHelper;
-import org.kde.kdeconnect.Plugins.SharePlugin.ShareSettingsFragment;
-import org.kde.kdeconnect.UserInterface.About.AboutFragment;
-import org.kde.kdeconnect.UserInterface.About.ApplicationAboutDataKt;
-import org.kde.kdeconnect_tp.R;
-import org.kde.kdeconnect_tp.databinding.ActivityMainBinding;
-
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Objects;
-
-public class MainActivity extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
-
- private static final int MENU_ENTRY_ADD_DEVICE = 1; //0 means no-selection
- private static final int MENU_ENTRY_SETTINGS = 2;
- private static final int MENU_ENTRY_ABOUT = 3;
- private static final int MENU_ENTRY_DEVICE_FIRST_ID = 1000; //All subsequent ids are devices in the menu
- private static final int MENU_ENTRY_DEVICE_UNKNOWN = 9999; //It's still a device, but we don't know which one yet
- private static final int STORAGE_LOCATION_CONFIGURED = 2020;
-
- private static final String STATE_SELECTED_MENU_ENTRY = "selected_entry"; //Saved only in onSaveInstanceState
- private static final String STATE_SELECTED_DEVICE = "selected_device"; //Saved persistently in preferences
-
- public static final int RESULT_NEEDS_RELOAD = Activity.RESULT_FIRST_USER;
-
- public static final String PAIR_REQUEST_STATUS = "pair_req_status";
- public static final String PAIRING_ACCEPTED = "accepted";
- public static final String PAIRING_REJECTED = "rejected";
- public static final String PAIRING_PENDING = "pending";
-
- public static final String EXTRA_DEVICE_ID = "deviceId";
-
- private NavigationView mNavigationView;
- private DrawerLayout mDrawerLayout;
- private TextView mNavViewDeviceName;
-
- private String mCurrentDevice;
- private int mCurrentMenuEntry;
-
- private SharedPreferences preferences;
-
- private final HashMap