Skip to content

Commit 147cdcc

Browse files
authored
Merge pull request #12 from PSDev/feature/widget_profile_edit
Refactor widget profile edit UI
2 parents 25740d5 + f035507 commit 147cdcc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+821
-292
lines changed

PRIVACY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ This app does not collect any Personally Identifiable Information (PII).
55
It uses Firebase Analytics, but only if the user explicitly opted in during first start or when activating in the app
66
settings.
77
Additionally it uses Firebase Performance and Firebase Crashlytics to observe and report performance and stability
8-
problems aswell as Firebase Remote Config to allow for dynamic configuration of certain experimental features.
8+
problems as well as Firebase Remote Config to allow for dynamic configuration of certain experimental features.
99

1010
You can see the privacy policy for Firebase at https://firebase.google.com/support/privacy

README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,18 @@ The original implementation has been rewritten in Kotlin.
1212
* Support for multiple widgets with different configurations
1313
* Support for filtering apps by signature
1414
* Support for Dark Mode
15+
* Support for previewing filters
1516

1617
You can [join the public beta on Google Play](https://play.google.com/apps/testing/de.psdev.devdrawer)
1718

18-
[Download DevDrawer in the Google Play Store](https://play.google.com/store/apps/details?id=de.psdev.devdrawer)
19+
[Download DevDrawer in the Google Play Store](https://play.google.com/store/apps/details?id=de.psdev.devdrawer)
20+
21+
## Third Party
22+
23+
### Material Design Icons
24+
25+
URL: https://github.com/Templarian/MaterialDesign
26+
License: Apache License 2.0
27+
Files:
28+
* ic_certificate.xml
29+
* ic_regex.xml

app/build.gradle

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
plugins {
22
id 'com.android.application'
3-
id 'com.github.triplet.play' version '3.0.0'
3+
id 'com.github.triplet.play' version '3.1.0'
44
}
55

66
apply plugin: 'kotlin-android'
@@ -44,7 +44,6 @@ android {
4444
jvmTarget = "1.8"
4545
freeCompilerArgs += [
4646
"-Xinline-classes",
47-
"-progressive",
4847
"-Xopt-in=kotlin.RequiresOptIn",
4948
"-Xopt-in=kotlin.ExperimentalStdlibApi",
5049
"-Xopt-in=kotlin.time.ExperimentalTime",

app/src/main/java/de/psdev/devdrawer/BaseFragment.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@ abstract class BaseFragment<T : ViewBinding> : Fragment() {
1919
lateinit var trackingService: TrackingService
2020

2121
private var _binding: T? = null
22-
23-
// This property is only valid between onCreateView and
24-
// onDestroyView.
22+
// This property is only valid between onCreateView and onDestroyView.
2523
protected val binding get() = _binding!!
2624

2725
protected var toolbarTitle: CharSequence

app/src/main/java/de/psdev/devdrawer/appwidget/AppInfo.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import android.content.pm.ApplicationInfo
55
import android.content.pm.PackageInfo
66
import android.content.pm.PackageManager
77
import android.graphics.drawable.Drawable
8+
import androidx.recyclerview.widget.DiffUtil
89
import mu.KotlinLogging
910
import okio.HashingSink
1011
import okio.blackholeSink
@@ -19,7 +20,16 @@ data class AppInfo(
1920
val firstInstalledTime: Long,
2021
val lastUpdateTime: Long,
2122
val signatureSha256: String
22-
)
23+
) {
24+
companion object {
25+
val DIFF_CALLBACK = object : DiffUtil.ItemCallback<AppInfo>() {
26+
override fun areItemsTheSame(oldItem: AppInfo, newItem: AppInfo): Boolean =
27+
oldItem.packageName == newItem.packageName
28+
29+
override fun areContentsTheSame(oldItem: AppInfo, newItem: AppInfo): Boolean = oldItem == newItem
30+
}
31+
}
32+
}
2333

2434
fun PackageHashInfo.toAppInfo(context: Context): AppInfo? = try {
2535
val packageManager = context.packageManager

app/src/main/java/de/psdev/devdrawer/database/PackageFilterDao.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import androidx.room.Query
55
import kotlinx.coroutines.flow.Flow
66

77
@Dao
8-
abstract class PackageFilterDao: BaseDao<PackageFilter>() {
8+
abstract class PackageFilterDao : BaseDao<PackageFilter>() {
9+
10+
@Query("SELECT * FROM filters WHERE id = :id")
11+
abstract fun findById(id: String): PackageFilter?
912

1013
@Query("SELECT * FROM filters WHERE profile_id = :profileId")
1114
abstract suspend fun findAllByProfile(profileId: String): List<PackageFilter>
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package de.psdev.devdrawer.profiles
2+
3+
import android.os.Bundle
4+
import android.text.Editable
5+
import android.text.TextWatcher
6+
import android.view.LayoutInflater
7+
import android.view.View
8+
import android.view.ViewGroup
9+
import androidx.fragment.app.DialogFragment
10+
import androidx.lifecycle.lifecycleScope
11+
import androidx.navigation.fragment.navArgs
12+
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
13+
import dagger.hilt.android.AndroidEntryPoint
14+
import de.psdev.devdrawer.R
15+
import de.psdev.devdrawer.adapters.PartialMatchAdapter
16+
import de.psdev.devdrawer.database.DevDrawerDatabase
17+
import de.psdev.devdrawer.database.PackageFilter
18+
import de.psdev.devdrawer.databinding.AddPackageFilterBottomSheetDialogFragmentBinding
19+
import de.psdev.devdrawer.receivers.UpdateReceiver
20+
import de.psdev.devdrawer.utils.getExistingPackages
21+
import mu.KLogging
22+
import javax.inject.Inject
23+
24+
@AndroidEntryPoint
25+
class AddPackageFilterBottomSheetDialogFragment : BottomSheetDialogFragment(), TextWatcher {
26+
companion object : KLogging()
27+
28+
@Inject
29+
lateinit var devDrawerDatabase: DevDrawerDatabase
30+
31+
private var _binding: AddPackageFilterBottomSheetDialogFragmentBinding? = null
32+
private val binding get() = _binding!!
33+
34+
private val navArgs: AddPackageFilterBottomSheetDialogFragmentArgs by navArgs()
35+
36+
private val appPackages: List<String> by lazy { requireActivity().packageManager.getExistingPackages() }
37+
private val packageNameCompletionAdapter: PartialMatchAdapter by lazy {
38+
PartialMatchAdapter(
39+
requireActivity(),
40+
navArgs.widgetProfileId,
41+
appPackages,
42+
devDrawerDatabase
43+
)
44+
}
45+
46+
override fun onCreate(savedInstanceState: Bundle?) {
47+
setStyle(DialogFragment.STYLE_NORMAL, R.style.AppBottomSheetDialogTheme)
48+
super.onCreate(savedInstanceState)
49+
}
50+
51+
override fun onCreateView(
52+
inflater: LayoutInflater,
53+
container: ViewGroup?,
54+
savedInstanceState: Bundle?
55+
): View = AddPackageFilterBottomSheetDialogFragmentBinding.inflate(inflater).also {
56+
_binding = it
57+
}.root
58+
59+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
60+
super.onViewCreated(view, savedInstanceState)
61+
with(binding) {
62+
editPackageName.setAdapter(packageNameCompletionAdapter)
63+
editPackageName.addTextChangedListener(this@AddPackageFilterBottomSheetDialogFragment)
64+
65+
btnAdd.setOnClickListener {
66+
val filter = editPackageName.text.toString()
67+
if (filter.isNotEmpty()) {
68+
lifecycleScope.launchWhenResumed {
69+
val filters = devDrawerDatabase.packageFilterDao()
70+
.findAllByProfile(navArgs.widgetProfileId)
71+
if (filters.none { it.filter == filter }) {
72+
val packageFilter = PackageFilter(
73+
filter = filter,
74+
profileId = navArgs.widgetProfileId
75+
)
76+
devDrawerDatabase.packageFilterDao().insert(packageFilter)
77+
editPackageName.text.clear()
78+
UpdateReceiver.send(requireContext())
79+
dismiss()
80+
} else {
81+
inputLayoutPackage.error = "Filter already exists"
82+
}
83+
}
84+
}
85+
}
86+
}
87+
}
88+
89+
// ==========================================================================================================================
90+
// TextWatcher
91+
// ==========================================================================================================================
92+
93+
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i2: Int, i3: Int) = Unit
94+
95+
override fun onTextChanged(charSequence: CharSequence, i: Int, i2: Int, i3: Int) = Unit
96+
97+
override fun afterTextChanged(editable: Editable) {
98+
packageNameCompletionAdapter.filter.filter(editable.toString())
99+
binding.inputLayoutPackage.error = null
100+
}
101+
}

app/src/main/java/de/psdev/devdrawer/profiles/AppAdapter.kt

Lines changed: 0 additions & 29 deletions
This file was deleted.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package de.psdev.devdrawer.profiles
2+
3+
import android.view.ViewGroup
4+
import androidx.recyclerview.widget.ListAdapter
5+
import androidx.recyclerview.widget.RecyclerView
6+
import de.psdev.devdrawer.appwidget.AppInfo
7+
import de.psdev.devdrawer.databinding.ListItemAppBinding
8+
import de.psdev.devdrawer.utils.layoutInflater
9+
10+
class AppListAdapter(
11+
private val onAppClickListener: AppInfoActionListener
12+
) : ListAdapter<AppInfo, AppListAdapter.AppInfoViewHolder>(AppInfo.DIFF_CALLBACK) {
13+
14+
// ==========================================================================================================================
15+
// ListAdapter
16+
// ==========================================================================================================================
17+
18+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppInfoViewHolder = AppInfoViewHolder(
19+
binding = ListItemAppBinding.inflate(parent.layoutInflater, parent, false),
20+
onClickListener = onAppClickListener
21+
)
22+
23+
override fun onBindViewHolder(holder: AppInfoViewHolder, position: Int) {
24+
val appInfo = getItem(position)
25+
holder.bindTo(appInfo)
26+
}
27+
28+
public override fun getItem(position: Int): AppInfo = super.getItem(position)
29+
30+
class AppInfoViewHolder(
31+
private val binding: ListItemAppBinding,
32+
private val onClickListener: AppInfoActionListener
33+
) : RecyclerView.ViewHolder(binding.root) {
34+
fun bindTo(appInfo: AppInfo) {
35+
binding.icon.setImageDrawable(appInfo.appIcon)
36+
binding.text1.text = appInfo.name
37+
binding.root.setOnClickListener {
38+
onClickListener(appInfo)
39+
}
40+
}
41+
}
42+
43+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package de.psdev.devdrawer.profiles
2+
3+
import android.content.pm.PackageManager
4+
import android.os.Bundle
5+
import android.view.LayoutInflater
6+
import android.view.View
7+
import android.view.ViewGroup
8+
import androidx.core.view.isVisible
9+
import androidx.lifecycle.lifecycleScope
10+
import androidx.navigation.fragment.navArgs
11+
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
12+
import com.google.firebase.ktx.Firebase
13+
import com.google.firebase.perf.ktx.performance
14+
import dagger.hilt.android.AndroidEntryPoint
15+
import de.psdev.devdrawer.appwidget.toAppInfo
16+
import de.psdev.devdrawer.appwidget.toPackageHashInfo
17+
import de.psdev.devdrawer.database.DevDrawerDatabase
18+
import de.psdev.devdrawer.database.FilterType
19+
import de.psdev.devdrawer.database.PackageFilter
20+
import de.psdev.devdrawer.databinding.AppSignatureChooserBottomSheetDialogFragmentBinding
21+
import de.psdev.devdrawer.receivers.UpdateReceiver
22+
import de.psdev.devdrawer.utils.awaitSubmit
23+
import de.psdev.devdrawer.utils.trace
24+
import kotlinx.coroutines.Dispatchers
25+
import kotlinx.coroutines.launch
26+
import kotlinx.coroutines.withContext
27+
import mu.KLogging
28+
import javax.inject.Inject
29+
30+
@AndroidEntryPoint
31+
class AppSignatureChooserBottomSheetDialogFragment : BottomSheetDialogFragment() {
32+
companion object : KLogging()
33+
34+
@Inject
35+
lateinit var devDrawerDatabase: DevDrawerDatabase
36+
37+
private var _binding: AppSignatureChooserBottomSheetDialogFragmentBinding? = null
38+
private val binding get() = _binding!!
39+
40+
private val navArgs: AppSignatureChooserBottomSheetDialogFragmentArgs by navArgs()
41+
42+
private val onAppClickListener: AppInfoActionListener = { appInfo ->
43+
lifecycleScope.launch {
44+
val packageFilter = PackageFilter(
45+
filter = appInfo.signatureSha256,
46+
type = FilterType.SIGNATURE,
47+
description = appInfo.name,
48+
profileId = navArgs.widgetProfileId
49+
)
50+
devDrawerDatabase.packageFilterDao().insert(packageFilter)
51+
UpdateReceiver.send(requireContext())
52+
dismiss()
53+
}
54+
}
55+
private val appAdapter = AppListAdapter(onAppClickListener)
56+
57+
override fun onCreateView(
58+
inflater: LayoutInflater,
59+
container: ViewGroup?,
60+
savedInstanceState: Bundle?
61+
): View = AppSignatureChooserBottomSheetDialogFragmentBinding.inflate(inflater).also {
62+
_binding = it
63+
}.root
64+
65+
@Suppress("DEPRECATION")
66+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
67+
super.onViewCreated(view, savedInstanceState)
68+
with(binding) {
69+
btnClose.setOnClickListener { dismiss() }
70+
recyclerApps.adapter = appAdapter
71+
lifecycleScope.launchWhenResumed {
72+
withContext(Dispatchers.IO) {
73+
val filters = devDrawerDatabase.packageFilterDao().findAllByProfile(navArgs.widgetProfileId)
74+
val context = requireContext()
75+
val packageManager = context.packageManager
76+
val installedPackages = Firebase.performance.trace("widget_profile_packages") {
77+
packageManager.getInstalledPackages(PackageManager.GET_SIGNATURES)
78+
.asSequence()
79+
.map { it.toPackageHashInfo() }
80+
.distinctBy { it.signatureHashSha256 }
81+
.filter { hashInfo -> filters.none { it.type == FilterType.SIGNATURE && it.filter == hashInfo.signatureHashSha256 } }
82+
.mapNotNull { it.toAppInfo(context) }
83+
.sortedBy { it.name }
84+
.toList()
85+
}
86+
logger.warn { "Installed packages: $installedPackages" }
87+
withContext(Dispatchers.Main) {
88+
appAdapter.awaitSubmit(installedPackages)
89+
progress.hide()
90+
recyclerApps.isVisible = true
91+
}
92+
}
93+
}
94+
}
95+
}
96+
97+
override fun onDestroyView() {
98+
binding.recyclerApps.adapter = null
99+
super.onDestroyView()
100+
}
101+
}

0 commit comments

Comments
 (0)