2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-09-02 23:25:10 +00:00

Compare commits

...

8 Commits

Author SHA1 Message Date
Albert Vaca Cintora
61190189ec Release 1.24.1 2023-04-13 19:10:54 +02:00
Albert Vaca Cintora
4c6cda711f Fix "find my phone" notification being dismissable
Dismissing the notification caused the alarm to keep playing without a
way to stop it.

BUG: 446349
2023-04-13 19:10:12 +02:00
Albert Vaca Cintora
64b32003cc Fix cards still visibile if device got unpaired with the app open 2023-04-13 18:55:49 +02:00
Albert Vaca Cintora
39fb60b81b Rename binding to pairingBinding 2023-04-13 18:55:28 +02:00
Albert Vaca Cintora
431312fcd0 Bump dependencies 2023-04-13 18:37:27 +02:00
Albert Vaca Cintora
6c8c6dd63e Fix compatibility with API 20 2023-04-13 17:56:13 +02:00
Dmitry Yudin
8aeefded7c Main activity responsive layout 2023-04-13 17:56:13 +02:00
Albert Vaca Cintora
e2dbc39e3a Do not force TLS v1
Stop specifying the TLS version we want and let the system chose

Co-authored-by: Daniel Tang <danielzgtg.opensource@gmail.com>
2023-04-13 11:07:29 +00:00
23 changed files with 343 additions and 282 deletions

View File

@@ -2,8 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="org.kde.kdeconnect_tp" package="org.kde.kdeconnect_tp"
android:versionCode="12400" android:versionCode="12401"
android:versionName="1.24.0"> android:versionName="1.24.1">
<supports-screens <supports-screens
android:anyDensity="true" android:anyDensity="true"

View File

@@ -4,7 +4,7 @@ import com.android.build.gradle.api.ApplicationVariant
import com.github.jk1.license.render.TextReportRenderer import com.github.jk1.license.render.TextReportRenderer
buildscript { buildscript {
ext.kotlin_version = '1.8.0' ext.kotlin_version = '1.8.10'
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:7.4.2' classpath 'com.android.tools.build:gradle:7.4.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
@@ -136,19 +136,19 @@ ext {
} }
dependencies { dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.2' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
implementation 'androidx.media:media:1.6.0' implementation 'androidx.media:media:1.6.0'
implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.core:core-ktx:1.9.0' implementation 'androidx.core:core-ktx:1.10.0'
implementation 'androidx.preference:preference-ktx:1.2.0' implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'androidx.recyclerview:recyclerview:1.3.0' implementation 'androidx.recyclerview:recyclerview:1.3.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.documentfile:documentfile:1.0.1' implementation 'androidx.documentfile:documentfile:1.0.1'
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.0" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.0' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
implementation 'androidx.lifecycle:lifecycle-common-java8:2.6.0' implementation 'androidx.lifecycle:lifecycle-common-java8:2.6.1'
implementation 'androidx.gridlayout:gridlayout:1.0.0' implementation 'androidx.gridlayout:gridlayout:1.0.0'
implementation 'com.google.android.material:material:1.8.0' implementation 'com.google.android.material:material:1.8.0'
implementation 'com.jakewharton:disklrucache:2.0.2' //For caching album art bitmaps implementation 'com.jakewharton:disklrucache:2.0.2' //For caching album art bitmaps

View File

@@ -0,0 +1,30 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<com.google.android.material.navigation.NavigationView
android:id="@+id/navigation_drawer"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="@layout/nav_header" />
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/coordinatorLayout"
android:layout_height="match_parent"
android:layout_width="match_parent"
tools:context="org.kde.kdeconnect.UserInterface.MainActivity">
<include layout="@layout/toolbar" android:id="@+id/toolbar_layout" />
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</LinearLayout>

View File

@@ -1,44 +1,51 @@
<LinearLayout <FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:orientation="@integer/activity_device_orientation"
tools:context="org.kde.kdeconnect.UserInterface.DeviceFragment">
<!-- Layout shown when device is reachable but not yet paired --> <androidx.core.widget.NestedScrollView
<include android:layout_width="match_parent"
android:id="@+id/pair_request" android:layout_height="match_parent"
layout="@layout/view_pair_request" android:fillViewport="true">
tools:visibility="gone"/>
<!-- Layout shown when we can't pair with device or device is not reachable --> <LinearLayout
<include android:layout_width="match_parent"
android:id="@+id/pair_error" android:layout_height="wrap_content"
layout="@layout/view_pair_error" android:orientation="vertical"
tools:visibility="gone"/> tools:context="org.kde.kdeconnect.UserInterface.DeviceFragment">
<!-- Layouts shown when device is paired and reachable --> <!-- Layout shown when device is reachable but not yet paired -->
<GridView <include
android:id="@+id/plugins_list" android:id="@+id/pair_request"
android:layout_width="wrap_content" layout="@layout/view_pair_request"
android:layout_height="wrap_content" tools:visibility="gone"/>
android:layout_weight="@integer/plugins_list_weight"
android:numColumns="@integer/plugins_columns"
android:horizontalSpacing="8dp"
android:verticalSpacing="8dp"
android:layout_margin="@dimen/activity_vertical_margin"
tools:listitem="@layout/list_plugin_entry"
tools:layout_height="300dp" />
<ListView <!-- Layout shown when we can't pair with device or device is not reachable -->
android:id="@+id/buttons_list" <include
android:layout_width="match_parent" android:id="@+id/pair_error"
android:layout_height="wrap_content" layout="@layout/view_pair_error"
android:layout_weight="@integer/buttons_list_weight" tools:visibility="gone"/>
android:divider="@null"
android:dividerHeight="0dp" <!-- Layouts shown when device is paired and reachable -->
tools:context=".DeviceActivity" <androidx.recyclerview.widget.RecyclerView
tools:listitem="@layout/list_item_with_icon_entry" android:id="@+id/plugins_list"
tools:layout_height="300dp" /> android:layout_width="match_parent"
</LinearLayout> android:layout_height="wrap_content"
android:layout_margin="12dp"
android:nestedScrollingEnabled="false"
tools:listitem="@layout/list_plugin_entry"
tools:layout_height="300dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/permissions_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="false"
tools:context=".DeviceActivity"
tools:listitem="@layout/list_item_plugin_header"
tools:layout_height="300dp" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</FrameLayout>

View File

@@ -1,32 +1,37 @@
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"> <!-- fitSystemWindows to make the drawer slide below the Lollipop transparent status bar -->
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/coordinatorLayout"
android:layout_height="match_parent"
android:layout_width="match_parent" android:layout_width="match_parent"
tools:context="org.kde.kdeconnect.UserInterface.MainActivity"> android:layout_height="match_parent">
<include layout="@layout/toolbar" android:id="@+id/toolbar_layout" /> <androidx.drawerlayout.widget.DrawerLayout
android:id="@+id/drawer_layout"
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/> android:fitsSystemWindows="true"> <!-- fitSystemWindows to make the drawer slide below the Lollipop transparent status bar -->
</androidx.coordinatorlayout.widget.CoordinatorLayout> <androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/coordinatorLayout"
android:layout_height="match_parent"
android:layout_width="match_parent"
tools:context="org.kde.kdeconnect.UserInterface.MainActivity">
<com.google.android.material.navigation.NavigationView <include layout="@layout/toolbar" android:id="@+id/toolbar_layout"/>
android:id="@+id/navigation_drawer"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="@layout/nav_header" />
</androidx.drawerlayout.widget.DrawerLayout> <FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.navigation.NavigationView
android:id="@+id/navigation_drawer"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="@layout/nav_header"/>
</androidx.drawerlayout.widget.DrawerLayout>
</FrameLayout>

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android" <TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:paddingLeft="16dp" android:padding="@dimen/view_default_padding"
android:paddingTop="28dp" tools:background="@android:color/darker_gray"
android:paddingRight="16dp" tools:text="@tools:sample/lorem"/>
android:paddingBottom="8dp" />

View File

@@ -2,10 +2,11 @@
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android" <com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="4dp"
style="@style/KdeConnectCardStyle.Filled" style="@style/KdeConnectCardStyle.Filled"
app:contentPadding="12dp" app:contentPadding="@dimen/view_default_padding"
tools:layout_width="240dp"> tools:layout_width="240dp">
<LinearLayout <LinearLayout
@@ -17,7 +18,7 @@
android:minHeight="?android:attr/listPreferredItemHeight" android:minHeight="?android:attr/listPreferredItemHeight"
android:orientation="vertical"> android:orientation="vertical">
<androidx.appcompat.widget.AppCompatImageView <ImageView
android:id="@+id/list_item_entry_icon" android:id="@+id/list_item_entry_icon"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"

View File

@@ -33,17 +33,14 @@
android:drawablePadding="5dp" android:drawablePadding="5dp"
android:layout_marginBottom="8dip" android:layout_marginBottom="8dip"
android:visibility="gone" android:visibility="gone"
android:text=""
android:textAppearance="?android:attr/textAppearanceMedium" android:textAppearance="?android:attr/textAppearanceMedium"
app:drawableStartCompat="@drawable/ic_key" /> app:drawableStartCompat="@drawable/ic_key" />
<Button <com.google.android.material.button.MaterialButton
android:id="@+id/pair_button" android:id="@+id/pair_button"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@drawable/button_round" android:text="@string/request_pairing" />
android:text="@string/request_pairing"
android:textColor="@android:color/white" />
<LinearLayout <LinearLayout
@@ -51,27 +48,27 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:visibility="gone"> android:visibility="gone"
android:paddingVertical="4dp"
tools:visibility="visible">
<Button <com.google.android.material.button.MaterialButton
android:id="@+id/accept_button" android:id="@+id/accept_button"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="4dip"
android:layout_weight="1" android:layout_weight="1"
android:background="@drawable/button_round" android:text="@string/pairing_accept" />
android:text="@string/pairing_accept"
android:textColor="@android:color/white" />
<Button <android.widget.Space
android:layout_width="8dp"
android:layout_height="8dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/reject_button" android:id="@+id/reject_button"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="4dip"
android:layout_weight="1" android:layout_weight="1"
android:background="@drawable/button_round" android:text="@string/pairing_reject" />
android:text="@string/pairing_reject"
android:textColor="@android:color/white" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View File

@@ -1,11 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<integer name="activity_device_orientation">@integer/orientation_horizontal</integer>
<integer name="plugins_list_weight">4</integer>
<integer name="buttons_list_weight">6</integer>
<integer name="mpris_now_playing_orientation">@integer/orientation_horizontal</integer> <integer name="mpris_now_playing_orientation">@integer/orientation_horizontal</integer>
<integer name="mpris_now_playing_album_weight">1</integer> <integer name="mpris_now_playing_album_weight">1</integer>
<integer name="mpris_now_playing_controls_weight">1</integer> <integer name="mpris_now_playing_controls_weight">1</integer>

View File

@@ -10,9 +10,6 @@
<item name="layout_wrap_content" type="dimen">-2</item> <item name="layout_wrap_content" type="dimen">-2</item>
<!--used in activity_device--> <!--used in activity_device-->
<integer name="activity_device_orientation">@integer/orientation_vertical</integer>
<integer name="plugins_list_weight">@null</integer>
<integer name="buttons_list_weight">@null</integer>
<integer name="plugins_columns">2</integer> <integer name="plugins_columns">2</integer>
<!--used in mpris_now_playing--> <!--used in mpris_now_playing-->

View File

@@ -2,6 +2,7 @@
<!-- Default screen margins, per the Android Design guidelines. --> <!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen> <dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen> <dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="view_default_padding">16dp</dimen>
<dimen name="key_height">48dip</dimen> <dimen name="key_height">48dip</dimen>
<dimen name="fab_margin">16dp</dimen> <dimen name="fab_margin">16dp</dimen>
<dimen name="fab_elevation">6dp</dimen> <dimen name="fab_elevation">6dp</dimen>

View File

@@ -209,7 +209,7 @@ public class SslHelper {
trustManagerFactory.init(keyStore); trustManagerFactory.init(keyStore);
// Setup custom trust manager if device not trusted // Setup custom trust manager if device not trusted
SSLContext tlsContext = SSLContext.getInstance("TLSv1"); //Newer TLS versions are only supported on API 16+ SSLContext tlsContext = SSLContext.getInstance("TLS");
if (isDeviceTrusted) { if (isDeviceTrusted) {
tlsContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), RandomHelper.secureRandom); tlsContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), RandomHelper.secureRandom);
} else { } else {

View File

@@ -151,6 +151,7 @@ public class FindMyPhonePlugin extends Plugin {
.setFullScreenIntent(pendingIntent, true) .setFullScreenIntent(pendingIntent, true)
.setPriority(NotificationCompat.PRIORITY_HIGH) .setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true) .setAutoCancel(true)
.setOngoing(true)
.setContentTitle(context.getString(R.string.findmyphone_found)); .setContentTitle(context.getString(R.string.findmyphone_found));
notification.setGroup("BackgroundService"); notification.setGroup("BackgroundService");

View File

@@ -162,7 +162,7 @@ public class RemoteKeyboardService
} }
} else { // != 1 instance of plugin -> show main activity view } else { // != 1 instance of plugin -> show main activity view
Intent intent = new Intent(this, MainActivity.class); 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); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent); startActivity(intent);
if (instances.size() < 1) if (instances.size() < 1)

View File

@@ -32,13 +32,11 @@ internal class SystemVolumeProvider private constructor(plugin: SystemVolumePlug
@JvmStatic @JvmStatic
fun fromPlugin(systemVolumePlugin: SystemVolumePlugin): SystemVolumeProvider { fun fromPlugin(systemVolumePlugin: SystemVolumePlugin): SystemVolumeProvider {
if (currentProvider == null) { val currentProvider = currentProvider ?: SystemVolumeProvider(systemVolumePlugin)
currentProvider = SystemVolumeProvider(systemVolumePlugin)
}
currentProvider!!.update(systemVolumePlugin) currentProvider.update(systemVolumePlugin)
return currentProvider!! return currentProvider
} }
private fun scale(value: Int, maxValue: Int, maxScaled: Int): Int { private fun scale(value: Int, maxValue: Int, maxScaled: Int): Int {

View File

@@ -37,12 +37,10 @@ class AboutData(var name: String, var icon: Int, var versionName: String, var bu
parcel.writeString(sourceCodeURL) parcel.writeString(sourceCodeURL)
parcel.writeString(donateURL) parcel.writeString(donateURL)
if (authorsFooterText == null) { authorsFooterText?.let {
parcel.writeByte(0x00)
} else {
parcel.writeByte(0x01) parcel.writeByte(0x01)
parcel.writeInt(authorsFooterText!!) parcel.writeInt(it)
} } ?: parcel.writeByte(0x00)
} }
override fun describeContents(): Int = 0 override fun describeContents(): Int = 0

View File

@@ -6,7 +6,6 @@
package org.kde.kdeconnect.UserInterface package org.kde.kdeconnect.UserInterface
import android.content.Intent import android.content.Intent
import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.KeyEvent import android.view.KeyEvent
@@ -14,10 +13,10 @@ import android.view.LayoutInflater
import android.view.Menu import android.view.Menu
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
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
@@ -26,10 +25,8 @@ import org.kde.kdeconnect.Device.PluginsChangedListener
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper
import org.kde.kdeconnect.Plugins.BatteryPlugin.BatteryPlugin import org.kde.kdeconnect.Plugins.BatteryPlugin.BatteryPlugin
import org.kde.kdeconnect.Plugins.Plugin import org.kde.kdeconnect.Plugins.Plugin
import org.kde.kdeconnect.UserInterface.List.FailedPluginListItem import org.kde.kdeconnect.UserInterface.List.PluginAdapter
import org.kde.kdeconnect.UserInterface.List.ListAdapter
import org.kde.kdeconnect.UserInterface.List.PluginItem import org.kde.kdeconnect.UserInterface.List.PluginItem
import org.kde.kdeconnect.UserInterface.List.PluginListHeaderItem
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.ViewPairErrorBinding
@@ -48,8 +45,8 @@ class DeviceFragment : Fragment() {
private val mActivity: MainActivity? by lazy { activity as MainActivity? } private val mActivity: MainActivity? by lazy { activity as MainActivity? }
//TODO use LinkedHashMap and delete irrelevant records when plugins changed //TODO use LinkedHashMap and delete irrelevant records when plugins changed
private val pluginListItems: ArrayList<ListAdapter.Item> = ArrayList() private val pluginListItems: ArrayList<PluginItem> = ArrayList()
private val permissionListItems: ArrayList<ListAdapter.Item> = ArrayList() private val permissionListItems: ArrayList<PluginItem> = ArrayList()
/** /**
* Top-level ViewBinding for this fragment. * Top-level ViewBinding for this fragment.
@@ -64,8 +61,8 @@ class DeviceFragment : Fragment() {
* *
* Used to start and retry pairing. * Used to start and retry pairing.
*/ */
private var binding: ViewPairRequestBinding? = null private var pairingBinding: ViewPairRequestBinding? = null
private fun requireBinding() = binding ?: throw IllegalStateException("binding is not set") private fun requirePairingBinding() = pairingBinding ?: throw IllegalStateException("binding is not set")
/** /**
* Cannot-communicate ViewBinding. * Cannot-communicate ViewBinding.
@@ -97,7 +94,7 @@ class DeviceFragment : Fragment() {
val deviceBinding = deviceBinding ?: return null val deviceBinding = deviceBinding ?: return null
// Inner binding for the layout shown when we're not paired yet... // Inner binding for the layout shown when we're not paired yet...
binding = deviceBinding.pairRequest pairingBinding = deviceBinding.pairRequest
// ...and for when pairing doesn't (or can't) work // ...and for when pairing doesn't (or can't) work
errorBinding = deviceBinding.pairError errorBinding = deviceBinding.pairError
@@ -105,21 +102,23 @@ class DeviceFragment : Fragment() {
device = it.getDevice(deviceId) device = it.getDevice(deviceId)
} }
requireBinding().pairButton.setOnClickListener { requirePairingBinding().pairButton.setOnClickListener {
requireBinding().pairButton.visibility = View.GONE with(requirePairingBinding()) {
requireBinding().pairMessage.text = null pairButton.visibility = View.GONE
requireBinding().pairVerification.visibility = View.VISIBLE pairMessage.text = null
requireBinding().pairVerification.text = SslHelper.getVerificationKey(SslHelper.certificate, device?.certificate) pairVerification.visibility = View.VISIBLE
requireBinding().pairProgress.visibility = View.VISIBLE pairVerification.text = SslHelper.getVerificationKey(SslHelper.certificate, device?.certificate)
pairProgress.visibility = View.VISIBLE
}
device?.requestPairing() device?.requestPairing()
} }
requireBinding().acceptButton.setOnClickListener { requirePairingBinding().acceptButton.setOnClickListener {
device?.apply { device?.apply {
acceptPairing() acceptPairing()
requireBinding().pairingButtons.visibility = View.GONE requirePairingBinding().pairingButtons.visibility = View.GONE
} }
} }
requireBinding().rejectButton.setOnClickListener { requirePairingBinding().rejectButton.setOnClickListener {
device?.apply { device?.apply {
//Remove listener so buttons don't show for a while before changing the view //Remove listener so buttons don't show for a while before changing the view
removePluginsChangedListener(pluginsChangedListener) removePluginsChangedListener(pluginsChangedListener)
@@ -141,6 +140,10 @@ class DeviceFragment : Fragment() {
refreshUI() refreshUI()
} }
requireDeviceBinding().pluginsList.layoutManager =
GridLayoutManager(requireContext(), resources.getInteger(R.integer.plugins_columns))
requireDeviceBinding().permissionsList.layoutManager = LinearLayoutManager(requireContext())
return deviceBinding.root return deviceBinding.root
} }
@@ -152,7 +155,7 @@ class DeviceFragment : Fragment() {
device.removePairingCallback(pairingCallback) device.removePairingCallback(pairingCallback)
} }
super.onDestroyView() super.onDestroyView()
binding = null pairingBinding = null
errorBinding = null errorBinding = null
deviceBinding = null deviceBinding = null
} }
@@ -165,12 +168,11 @@ class DeviceFragment : Fragment() {
//Plugins button list //Plugins button list
val plugins: Collection<Plugin> = device.loadedPlugins.values val plugins: Collection<Plugin> = device.loadedPlugins.values
for (p in plugins) { for (p in plugins) {
if (!p.displayInContextMenu()) { if (p.displayInContextMenu()) {
continue menu.add(p.actionName).setOnMenuItemClickListener {
} p.startMainActivity(mActivity)
menu.add(p.actionName).setOnMenuItemClickListener { true
p.startMainActivity(mActivity) }
true
} }
} }
val intent = Intent(mActivity, PluginSettingsActivity::class.java) val intent = Intent(mActivity, PluginSettingsActivity::class.java)
@@ -241,60 +243,80 @@ class DeviceFragment : Fragment() {
mActivity?.runOnUiThread(object : Runnable { mActivity?.runOnUiThread(object : Runnable {
override fun run() { override fun run() {
if (device.isPairRequestedByPeer) { if (device.isPairRequestedByPeer) {
requireBinding().pairMessage.setText(R.string.pair_requested) with (requirePairingBinding()) {
requireBinding().pairVerification.visibility = View.VISIBLE pairMessage.setText(R.string.pair_requested)
requireBinding().pairVerification.text = pairVerification.visibility = View.VISIBLE
SslHelper.getVerificationKey(SslHelper.certificate, device.certificate) pairVerification.text = SslHelper.getVerificationKey(SslHelper.certificate, device.certificate)
requireBinding().pairingButtons.visibility = View.VISIBLE pairingButtons.visibility = View.VISIBLE
requireBinding().pairProgress.visibility = View.GONE pairProgress.visibility = View.GONE
requireBinding().pairButton.visibility = View.GONE pairButton.visibility = View.GONE
requireBinding().pairRequestButtons.visibility = View.VISIBLE pairRequestButtons.visibility = View.VISIBLE
}
with (requireDeviceBinding()) {
permissionsList.visibility = View.GONE
pluginsList.visibility = View.GONE
}
} else { } else {
val paired = device.isPaired val paired = device.isPaired
val reachable = device.isReachable val reachable = device.isReachable
requireBinding().pairingButtons.visibility = if (paired) View.GONE else View.VISIBLE requirePairingBinding().pairingButtons.visibility = if (paired) View.GONE else View.VISIBLE
if (paired && !reachable) { if (paired && !reachable) {
requireErrorBinding().errorMessageContainer.visibility = View.VISIBLE requireErrorBinding().errorMessageContainer.visibility = View.VISIBLE
requireErrorBinding().notReachableMessage.visibility = View.VISIBLE requireErrorBinding().notReachableMessage.visibility = View.VISIBLE
} else { requireDeviceBinding().permissionsList.visibility = View.GONE
requireDeviceBinding().pluginsList.visibility = View.GONE
} else if (paired) {
requireErrorBinding().errorMessageContainer.visibility = View.GONE requireErrorBinding().errorMessageContainer.visibility = View.GONE
requireErrorBinding().notReachableMessage.visibility = View.GONE requireErrorBinding().notReachableMessage.visibility = View.GONE
requireDeviceBinding().permissionsList.visibility = View.VISIBLE
requireDeviceBinding().pluginsList.visibility = View.VISIBLE
} else {
requireDeviceBinding().permissionsList.visibility = View.GONE
requireDeviceBinding().pluginsList.visibility = View.GONE
} }
try { try {
if (paired && reachable) { if (paired && reachable) {
//Plugins button list //Plugins button list
val plugins: Collection<Plugin> = device.loadedPlugins.values val plugins: Collection<Plugin> = device.loadedPlugins.values
//TODO look for LinkedHashMap mention above
pluginListItems.clear() pluginListItems.clear()
permissionListItems.clear() permissionListItems.clear()
//Fill enabled plugins ArrayList
for (p in plugins) { for (p in plugins) {
if (!p.hasMainActivity(context) || p.displayInContextMenu()) continue if (p.hasMainActivity(context) && !p.displayInContextMenu()) {
pluginListItems.add(PluginItem(p) { p.startMainActivity(mActivity) }) pluginListItems.add(
PluginItem(requireContext(), p, { p.startMainActivity(mActivity) })
)
}
} }
//Fill permissionListItems with permissions plugins
createPermissionsList( createPermissionsList(
device.pluginsWithoutPermissions, device.pluginsWithoutPermissions,
R.string.plugins_need_permission R.string.plugins_need_permission
) { plugin: Plugin -> ) { p: Plugin ->
val dialog = plugin.permissionExplanationDialog p.permissionExplanationDialog?.show(childFragmentManager, null)
dialog?.show(childFragmentManager, null)
} }
createPermissionsList( createPermissionsList(
device.pluginsWithoutOptionalPermissions, device.pluginsWithoutOptionalPermissions,
R.string.plugins_need_optional_permission R.string.plugins_need_optional_permission
) { plugin: Plugin -> ) { p: Plugin ->
val dialog: DialogFragment? = plugin.optionalPermissionExplanationDialog p.optionalPermissionExplanationDialog?.show(childFragmentManager, null)
dialog?.show(childFragmentManager, null)
} }
requireDeviceBinding().permissionsList.adapter =
PluginAdapter(permissionListItems, R.layout.list_item_plugin_header)
requireDeviceBinding().pluginsList.adapter =
PluginAdapter(pluginListItems, R.layout.list_plugin_entry)
requireDeviceBinding().permissionsList.adapter?.notifyDataSetChanged()
requireDeviceBinding().pluginsList.adapter?.notifyDataSetChanged()
displayBatteryInfoIfPossible() 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() mActivity?.invalidateOptionsMenu()
} catch (e: IllegalStateException) { } catch (e: IllegalStateException) {
//Ignore: The activity was closed while we were trying to update it //Ignore: The activity was closed while we were trying to update it
@@ -318,9 +340,9 @@ class DeviceFragment : Fragment() {
override fun pairingFailed(error: String) { override fun pairingFailed(error: String) {
mActivity?.runOnUiThread { mActivity?.runOnUiThread {
with(requireBinding()) { with(requirePairingBinding()) {
pairMessage.text = error pairMessage.text = error
pairVerification.text = "" pairVerification.text = null
pairVerification.visibility = View.GONE pairVerification.visibility = View.GONE
pairProgress.visibility = View.GONE pairProgress.visibility = View.GONE
pairButton.visibility = View.VISIBLE pairButton.visibility = View.VISIBLE
@@ -332,7 +354,7 @@ class DeviceFragment : Fragment() {
override fun unpaired() { override fun unpaired() {
mActivity?.runOnUiThread { mActivity?.runOnUiThread {
with(requireBinding()) { with(requirePairingBinding()) {
pairMessage.setText(R.string.device_not_paired) pairMessage.setText(R.string.device_not_paired)
pairVerification.visibility = View.GONE pairVerification.visibility = View.GONE
pairProgress.visibility = View.GONE pairProgress.visibility = View.GONE
@@ -346,17 +368,24 @@ class DeviceFragment : Fragment() {
private fun createPermissionsList( private fun createPermissionsList(
plugins: ConcurrentHashMap<String, Plugin>, plugins: ConcurrentHashMap<String, Plugin>,
headerText: Int, @StringRes headerText: Int,
action: FailedPluginListItem.Action action: (Plugin) -> Unit,
) { ) {
if (plugins.isEmpty()) return if (plugins.isEmpty()) return
val device = device ?: return val device = device ?: return
permissionListItems.add(PluginListHeaderItem(headerText)) permissionListItems.add(
PluginItem(
context = requireContext(),
header = requireContext().getString(headerText),
textStyleRes = R.style.TextAppearance_Material3_BodyMedium,
)
)
for (plugin in plugins.values) { for (plugin in plugins.values) {
if (!device.isPluginEnabled(plugin.pluginKey)) { if (device.isPluginEnabled(plugin.pluginKey)) {
continue permissionListItems.add(
PluginItem(requireContext(), plugin, action, R.style.TextAppearance_Material3_LabelLarge)
)
} }
permissionListItems.add(FailedPluginListItem(plugin, action))
} }
} }
@@ -375,12 +404,10 @@ class DeviceFragment : Fragment() {
if (info != null) { if (info != null) {
@StringRes @StringRes
val resId: Int = if (info.isCharging) { val resId = when {
R.string.battery_status_charging_format info.isCharging -> R.string.battery_status_charging_format
} else if (BatteryPlugin.isLowBattery(info)) { BatteryPlugin.isLowBattery(info) -> R.string.battery_status_low_format
R.string.battery_status_low_format else -> R.string.battery_status_format
} else {
R.string.battery_status_format
} }
mActivity?.supportActionBar?.subtitle = mActivity?.getString(resId, info.currentCharge) mActivity?.supportActionBar?.subtitle = mActivity?.getString(resId, info.currentCharge)

View File

@@ -1,19 +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.UserInterface.List;
import org.kde.kdeconnect.Plugins.Plugin;
public class FailedPluginListItem extends SmallEntryItem {
public interface Action {
void action(Plugin plugin);
}
public FailedPluginListItem(Plugin plugin, Action action) {
super(plugin.getDisplayName(), (view) -> action.action(plugin));
}
}

View File

@@ -0,0 +1,53 @@
package org.kde.kdeconnect.UserInterface.List
import android.content.Context
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_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<PluginItem>,
private val layoutRes: Int,
) : RecyclerView.Adapter<PluginAdapter.PluginViewHolder>() {
private lateinit var context: Context
override fun onCreateViewHolder(viewGroup: ViewGroup, type: Int) : PluginViewHolder {
context = viewGroup.context
return PluginViewHolder(
LayoutInflater.from(context).inflate(layoutRes, viewGroup, false)
)
}
override fun getItemCount() = pluginList.size
override fun onBindViewHolder(holder: PluginViewHolder, position: Int) {
pluginList[position].let { plugin ->
holder.pluginTitle.text = plugin.header
holder.pluginIcon?.setImageDrawable(plugin.icon)
// Remove context when we require API 23+
plugin.textStyleRes?.let { holder.pluginTitle.setTextAppearance(context, it) }
plugin.action?.let { action -> 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)
}
}

View File

@@ -1,31 +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.List;
import android.view.LayoutInflater;
import android.view.View;
import androidx.annotation.NonNull;
import org.kde.kdeconnect.Plugins.Plugin;
public class PluginItem extends EntryItemWithIcon {
private final View.OnClickListener clickListener;
public PluginItem(Plugin p, View.OnClickListener clickListener) {
super(p.getActionName(), p.getIcon());
this.clickListener = clickListener;
}
@NonNull
@Override
public View inflateView(@NonNull LayoutInflater layoutInflater) {
final View root = super.inflateView(layoutInflater);
root.setOnClickListener(clickListener);
return root;
}
}

View File

@@ -0,0 +1,29 @@
package org.kde.kdeconnect.UserInterface.List
import android.content.Context
import android.graphics.drawable.Drawable
import org.kde.kdeconnect.Plugins.Plugin
class PluginItem(
val context: Context,
val header: String,
val textStyleRes: Int? = null,
) {
var action: (() -> Unit)? = null
var icon: Drawable? = null
constructor(
context: Context,
plugin: Plugin,
action: (Plugin) -> Unit,
textStyleRes: Int? = null,
) : this(
context = context,
header = plugin.displayName,
textStyleRes = textStyleRes,
) {
this.action = { action(plugin) }
this.icon = plugin.icon
}
}

View File

@@ -1,33 +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.List;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import org.kde.kdeconnect_tp.databinding.ListItemPluginHeaderBinding;
public class PluginListHeaderItem implements ListAdapter.Item {
private final int text;
public PluginListHeaderItem(int text) {
this.text = text;
}
@NonNull
@Override
public View inflateView(@NonNull LayoutInflater layoutInflater) {
TextView textView = ListItemPluginHeaderBinding.inflate(layoutInflater).getRoot();
textView.setText(text);
textView.setOnClickListener(null);
textView.setOnLongClickListener(null);
return textView;
}
}

View File

@@ -63,6 +63,7 @@ public class MainActivity extends AppCompatActivity implements SharedPreferences
public static final String PAIRING_PENDING = "pending"; public static final String PAIRING_PENDING = "pending";
public static final String EXTRA_DEVICE_ID = "deviceId"; public static final String EXTRA_DEVICE_ID = "deviceId";
public static final String FLAG_FORCE_OVERVIEW = "forceOverview";
private NavigationView mNavigationView; private NavigationView mNavigationView;
private DrawerLayout mDrawerLayout; private DrawerLayout mDrawerLayout;
@@ -95,22 +96,24 @@ public class MainActivity extends AppCompatActivity implements SharedPreferences
ActionBar actionBar = getSupportActionBar(); ActionBar actionBar = getSupportActionBar();
ActionBarDrawerToggle mDrawerToggle = new ActionBarDrawerToggle(this, /* host Activity */ if (mDrawerLayout != null) {
mDrawerLayout, /* DrawerLayout object */ ActionBarDrawerToggle mDrawerToggle = new ActionBarDrawerToggle(this, /* host Activity */
R.string.open, /* "open drawer" description */ mDrawerLayout, /* DrawerLayout object */
R.string.close /* "close drawer" description */ R.string.open, /* "open drawer" description */
); R.string.close /* "close drawer" description */
);
mDrawerLayout.addDrawerListener(mDrawerToggle); mDrawerLayout.addDrawerListener(mDrawerToggle);
mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START); mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
if (actionBar != null) { if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true); actionBar.setDisplayHomeAsUpEnabled(true);
}
mDrawerToggle.setDrawerIndicatorEnabled(true);
mDrawerToggle.syncState();
} }
mDrawerToggle.setDrawerIndicatorEnabled(true);
mDrawerToggle.syncState();
preferences = getSharedPreferences("stored_menu_selection", Context.MODE_PRIVATE); preferences = getSharedPreferences("stored_menu_selection", Context.MODE_PRIVATE);
// Note: The preference changed listener should be registered before getting the name, because getting // Note: The preference changed listener should be registered before getting the name, because getting
@@ -144,14 +147,16 @@ public class MainActivity extends AppCompatActivity implements SharedPreferences
break; break;
} }
mDrawerLayout.closeDrawer(mNavigationView); if (mDrawerLayout != null) {
mDrawerLayout.closeDrawer(mNavigationView);
}
return true; return true;
}); });
// Decide which menu entry should be selected at start // Decide which menu entry should be selected at start
String savedDevice; String savedDevice;
int savedMenuEntry; int savedMenuEntry;
if (getIntent().hasExtra("forceOverview")) { if (getIntent().hasExtra(FLAG_FORCE_OVERVIEW)) {
Log.i("MainActivity", "Requested to start main overview"); Log.i("MainActivity", "Requested to start main overview");
savedDevice = null; savedDevice = null;
savedMenuEntry = MENU_ENTRY_ADD_DEVICE; savedMenuEntry = MENU_ENTRY_ADD_DEVICE;
@@ -248,7 +253,7 @@ public class MainActivity extends AppCompatActivity implements SharedPreferences
@Override @Override
public void onBackPressed() { public void onBackPressed() {
if (mDrawerLayout.isDrawerOpen(mNavigationView)) { if (mDrawerLayout != null && mDrawerLayout.isDrawerOpen(mNavigationView)) {
mDrawerLayout.closeDrawer(mNavigationView); mDrawerLayout.closeDrawer(mNavigationView);
} else if (mCurrentMenuEntry == MENU_ENTRY_SETTINGS || mCurrentMenuEntry == MENU_ENTRY_ABOUT) { } else if (mCurrentMenuEntry == MENU_ENTRY_SETTINGS || mCurrentMenuEntry == MENU_ENTRY_ABOUT) {
mCurrentMenuEntry = MENU_ENTRY_ADD_DEVICE; mCurrentMenuEntry = MENU_ENTRY_ADD_DEVICE;
@@ -261,7 +266,7 @@ public class MainActivity extends AppCompatActivity implements SharedPreferences
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) { if (mDrawerLayout != null && item.getItemId() == android.R.id.home) {
mDrawerLayout.openDrawer(mNavigationView); mDrawerLayout.openDrawer(mNavigationView);
return true; return true;
} else { } else {