2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-09-01 06:35:09 +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"
xmlns:tools="http://schemas.android.com/tools"
package="org.kde.kdeconnect_tp"
android:versionCode="12400"
android:versionName="1.24.0">
android:versionCode="12401"
android:versionName="1.24.1">
<supports-screens
android:anyDensity="true"

View File

@@ -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"
@@ -136,19 +136,19 @@ ext {
}
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.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.recyclerview:recyclerview:1.3.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.documentfile:documentfile:1.0.1'
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.0"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.0'
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'
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 'com.google.android.material:material:1.8.0'
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:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="@integer/activity_device_orientation"
tools:context="org.kde.kdeconnect.UserInterface.DeviceFragment">
android:layout_height="match_parent">
<!-- Layout shown when device is reachable but not yet paired -->
<include
android:id="@+id/pair_request"
layout="@layout/view_pair_request"
tools:visibility="gone"/>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<!-- Layout shown when we can't pair with device or device is not reachable -->
<include
android:id="@+id/pair_error"
layout="@layout/view_pair_error"
tools:visibility="gone"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:context="org.kde.kdeconnect.UserInterface.DeviceFragment">
<!-- Layouts shown when device is paired and reachable -->
<GridView
android:id="@+id/plugins_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
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" />
<!-- Layout shown when device is reachable but not yet paired -->
<include
android:id="@+id/pair_request"
layout="@layout/view_pair_request"
tools:visibility="gone"/>
<ListView
android:id="@+id/buttons_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="@integer/buttons_list_weight"
android:divider="@null"
android:dividerHeight="0dp"
tools:context=".DeviceActivity"
tools:listitem="@layout/list_item_with_icon_entry"
tools:layout_height="300dp" />
</LinearLayout>
<!-- Layout shown when we can't pair with device or device is not reachable -->
<include
android:id="@+id/pair_error"
layout="@layout/view_pair_error"
tools:visibility="gone"/>
<!-- Layouts shown when device is paired and reachable -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/plugins_list"
android:layout_width="match_parent"
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"
xmlns:app="http://schemas.android.com/apk/res-auto"
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"
<FrameLayout 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"
tools:context="org.kde.kdeconnect.UserInterface.MainActivity">
android:layout_height="match_parent">
<include layout="@layout/toolbar" android:id="@+id/toolbar_layout" />
<FrameLayout
android:id="@+id/container"
<androidx.drawerlayout.widget.DrawerLayout
android:id="@+id/drawer_layout"
android:layout_width="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
android:id="@+id/navigation_drawer"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="@layout/nav_header" />
<include layout="@layout/toolbar" android:id="@+id/toolbar_layout"/>
</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"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="16dp"
android:paddingTop="28dp"
android:paddingRight="16dp"
android:paddingBottom="8dp" />
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:padding="@dimen/view_default_padding"
tools:background="@android:color/darker_gray"
tools:text="@tools:sample/lorem"/>

View File

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

View File

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

View File

@@ -1,11 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<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_album_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>
<!--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>
<!--used in mpris_now_playing-->

View File

@@ -2,6 +2,7 @@
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_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="fab_margin">16dp</dimen>
<dimen name="fab_elevation">6dp</dimen>

View File

@@ -209,7 +209,7 @@ public class SslHelper {
trustManagerFactory.init(keyStore);
// 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) {
tlsContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), RandomHelper.secureRandom);
} else {

View File

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

View File

@@ -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)

View File

@@ -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 {

View File

@@ -37,12 +37,10 @@ class AboutData(var name: String, var icon: Int, var versionName: String, var bu
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

View File

@@ -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.PluginAdapter
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.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<ListAdapter.Item> = ArrayList()
private val permissionListItems: ArrayList<ListAdapter.Item> = ArrayList()
private val pluginListItems: ArrayList<PluginItem> = ArrayList()
private val permissionListItems: ArrayList<PluginItem> = ArrayList()
/**
* Top-level ViewBinding for this fragment.
@@ -64,8 +61,8 @@ class DeviceFragment : Fragment() {
*
* Used to start and retry pairing.
*/
private var binding: ViewPairRequestBinding? = null
private fun requireBinding() = binding ?: throw IllegalStateException("binding is not set")
private var pairingBinding: ViewPairRequestBinding? = null
private fun requirePairingBinding() = pairingBinding ?: throw IllegalStateException("binding is not set")
/**
* Cannot-communicate ViewBinding.
@@ -97,7 +94,7 @@ class DeviceFragment : Fragment() {
val deviceBinding = deviceBinding ?: return null
// 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
errorBinding = deviceBinding.pairError
@@ -105,21 +102,23 @@ class DeviceFragment : Fragment() {
device = it.getDevice(deviceId)
}
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
requirePairingBinding().pairButton.setOnClickListener {
with(requirePairingBinding()) {
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 {
requirePairingBinding().acceptButton.setOnClickListener {
device?.apply {
acceptPairing()
requireBinding().pairingButtons.visibility = View.GONE
requirePairingBinding().pairingButtons.visibility = View.GONE
}
}
requireBinding().rejectButton.setOnClickListener {
requirePairingBinding().rejectButton.setOnClickListener {
device?.apply {
//Remove listener so buttons don't show for a while before changing the view
removePluginsChangedListener(pluginsChangedListener)
@@ -141,6 +140,10 @@ class DeviceFragment : Fragment() {
refreshUI()
}
requireDeviceBinding().pluginsList.layoutManager =
GridLayoutManager(requireContext(), resources.getInteger(R.integer.plugins_columns))
requireDeviceBinding().permissionsList.layoutManager = LinearLayoutManager(requireContext())
return deviceBinding.root
}
@@ -152,7 +155,7 @@ class DeviceFragment : Fragment() {
device.removePairingCallback(pairingCallback)
}
super.onDestroyView()
binding = null
pairingBinding = null
errorBinding = null
deviceBinding = null
}
@@ -165,12 +168,11 @@ class DeviceFragment : Fragment() {
//Plugins button list
val plugins: Collection<Plugin> = device.loadedPlugins.values
for (p in plugins) {
if (!p.displayInContextMenu()) {
continue
}
menu.add(p.actionName).setOnMenuItemClickListener {
p.startMainActivity(mActivity)
true
if (p.displayInContextMenu()) {
menu.add(p.actionName).setOnMenuItemClickListener {
p.startMainActivity(mActivity)
true
}
}
}
val intent = Intent(mActivity, PluginSettingsActivity::class.java)
@@ -241,60 +243,80 @@ class DeviceFragment : Fragment() {
mActivity?.runOnUiThread(object : Runnable {
override fun run() {
if (device.isPairRequestedByPeer) {
requireBinding().pairMessage.setText(R.string.pair_requested)
requireBinding().pairVerification.visibility = View.VISIBLE
requireBinding().pairVerification.text =
SslHelper.getVerificationKey(SslHelper.certificate, device.certificate)
requireBinding().pairingButtons.visibility = View.VISIBLE
requireBinding().pairProgress.visibility = View.GONE
requireBinding().pairButton.visibility = View.GONE
requireBinding().pairRequestButtons.visibility = View.VISIBLE
with (requirePairingBinding()) {
pairMessage.setText(R.string.pair_requested)
pairVerification.visibility = View.VISIBLE
pairVerification.text = SslHelper.getVerificationKey(SslHelper.certificate, device.certificate)
pairingButtons.visibility = View.VISIBLE
pairProgress.visibility = View.GONE
pairButton.visibility = View.GONE
pairRequestButtons.visibility = View.VISIBLE
}
with (requireDeviceBinding()) {
permissionsList.visibility = View.GONE
pluginsList.visibility = View.GONE
}
} else {
val paired = device.isPaired
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) {
requireErrorBinding().errorMessageContainer.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().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 {
if (paired && reachable) {
//Plugins button list
val plugins: Collection<Plugin> = 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) })
if (p.hasMainActivity(context) && !p.displayInContextMenu()) {
pluginListItems.add(
PluginItem(requireContext(), p, { 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().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()
}
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
@@ -318,9 +340,9 @@ class DeviceFragment : Fragment() {
override fun pairingFailed(error: String) {
mActivity?.runOnUiThread {
with(requireBinding()) {
with(requirePairingBinding()) {
pairMessage.text = error
pairVerification.text = ""
pairVerification.text = null
pairVerification.visibility = View.GONE
pairProgress.visibility = View.GONE
pairButton.visibility = View.VISIBLE
@@ -332,7 +354,7 @@ class DeviceFragment : Fragment() {
override fun unpaired() {
mActivity?.runOnUiThread {
with(requireBinding()) {
with(requirePairingBinding()) {
pairMessage.setText(R.string.device_not_paired)
pairVerification.visibility = View.GONE
pairProgress.visibility = View.GONE
@@ -346,17 +368,24 @@ class DeviceFragment : Fragment() {
private fun createPermissionsList(
plugins: ConcurrentHashMap<String, Plugin>,
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(
PluginItem(
context = requireContext(),
header = requireContext().getString(headerText),
textStyleRes = R.style.TextAppearance_Material3_BodyMedium,
)
)
for (plugin in plugins.values) {
if (!device.isPluginEnabled(plugin.pluginKey)) {
continue
if (device.isPluginEnabled(plugin.pluginKey)) {
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) {
@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)

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