This commit is contained in:
lunaticbum 2024-08-22 11:54:56 +09:00
parent 3cdda32c99
commit bdb6a57a55
6 changed files with 294 additions and 157 deletions

View File

@ -28,6 +28,8 @@ import android.content.pm.ResolveInfo
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.provider.ContactsContract import android.provider.ContactsContract
import android.util.Log import android.util.Log
import android.view.Gravity import android.view.Gravity
@ -148,6 +150,12 @@ internal class AppDrawer : Fragment() {
binding.searchTranslate.setOnClickListener { binding.searchTranslate.setOnClickListener {
openSearchApps("https://translate.google.com/?hl=ko&sl=ko&tl=en&text=${getInputText()}&op=translate","com.android.chrome") openSearchApps("https://translate.google.com/?hl=ko&sl=ko&tl=en&text=${getInputText()}&op=translate","com.android.chrome")
} }
binding.searchCoupang.setOnClickListener {
openSearchApps("coupang://search?q=${getInputText()}","com.coupang.mobile")
}
setLayout() setLayout()
return binding.root return binding.root
@ -223,13 +231,65 @@ internal class AppDrawer : Fragment() {
// false -> openSearch() // false -> openSearch()
// } // }
// } // }
binding.searchInput.setOnKeyListener { v, keyCode, event ->
BLog.LOGE("v == ${v}, keyCode == ${keyCode}, event == ${event}")
if (keyCode==66) { checkResult() }
return@setOnKeyListener true
}
binding.searchInput.doOnTextChanged { inputText, _, _, _ -> binding.searchInput.doOnTextChanged { inputText, _, _, _ ->
binding.searchInput.text?.let { binding.searchInput.setSelection(it.length) } binding.searchInput.text?.let { binding.searchInput.setSelection(it.length) }
filterAppsList(inputText.toString()) filterAppsList(inputText.toString())
} }
} }
fun checkResult() {
if (lastSearchString.length > 0 && packageList.size == 1) {
var dialog = AlertDialog.Builder(requireContext())
dialog.setTitle("앱 실행 확인")
dialog.setMessage("${lastSearchString} 검색 결과 '${packageList[0].appName}' 준비됨")
dialog.setCancelable(false)
dialog.setNegativeButton("취소") { s,d->
s.dismiss()
}
dialog.setPositiveButton("실행") { s, d ->
startActivity(packageManager?.getLaunchIntentForPackage(packageList[0].packageName))
s.dismiss()
}
dialog.show()
} else if (packageList.size == 0 && contactList.size == 1) {
var dialog = AlertDialog.Builder(requireContext())
dialog.setTitle("연락처 실행 확인")
dialog.setMessage("${lastSearchString} 검색 결과 '${contactList[0].name}' 준비됨")
dialog.setCancelable(false)
dialog.setNegativeButton("취소") { s,d->
s.dismiss()
}
dialog.setPositiveButton("실행") { s, d ->
ContactMenu().show(childFragmentManager, contactList[0].id.toString())
s.dismiss()
}
dialog.show()
} else {
var dialog = AlertDialog.Builder(requireContext())
dialog.setTitle("검색 실행 확인")
dialog.setMessage("${lastSearchString} 검색 준비됨")
dialog.setCancelable(true)
dialog.setNegativeButton("네이버 지도 검색") { s,d->
s.dismiss()
binding.searchNmap.performClick()
}
dialog.setNeutralButton("쿠팡 검색") { s,d->
s.dismiss()
binding.searchCoupang.performClick()
}
dialog.setPositiveButton("구글 검색") { s, d ->
s.dismiss()
binding.searchGoogle.performClick()
}
dialog.show()
}
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
@ -246,7 +306,18 @@ internal class AppDrawer : Fragment() {
contactAdapter?.updateData(contactList) contactAdapter?.updateData(contactList)
/* pop up the keyboard */ /* pop up the keyboard */
if (settingsPrefs!!.getBoolean(KEY_KEYBOARD_SEARCH, false)) openSearch() openSearch()
chechHandler.postDelayed(cancelSearch, 3000L)
}
val chechHandler = Handler(Looper.getMainLooper())
val cancelSearch = Runnable { timerCheck() }
private fun timerCheck() {
lActivity?.onBackPressed()
} }
override fun onPause() { override fun onPause() {
@ -271,62 +342,30 @@ internal class AppDrawer : Fragment() {
/* update app list with app and package name */ /* update app list with app and package name */
fun fetchApps() { fun fetchApps() {
if (oringinPackageList.size > 0) { packageList.clear()
packageList.clear() oringinPackageList.clear()
for(pkg in oringinPackageList) { GlobalScope.launch {
packageList.add(pkg) packageInfoList = (if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
} packageManager?.queryIntentActivities(
} else { Intent(Intent.ACTION_MAIN, null).addCategory(Intent.CATEGORY_LAUNCHER),
packageList.clear() PackageManager.ResolveInfoFlags.of(0)
oringinPackageList.clear() )
GlobalScope.launch { } else {
packageInfoList = (if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { (packageManager?.queryIntentActivities(
packageManager?.queryIntentActivities( Intent(Intent.ACTION_MAIN, null).addCategory(Intent.CATEGORY_LAUNCHER), 0
Intent(Intent.ACTION_MAIN, null).addCategory(Intent.CATEGORY_LAUNCHER), ))
PackageManager.ResolveInfoFlags.of(0) })?.apply {
) removeIf { it.activityInfo.packageName.equals(BuildConfig.APPLICATION_ID) }
} else { forEach { oringinPackageList.add(Packages(it.activityInfo.packageName, normalize(appName(it)), getCategory(it.activityInfo.applicationInfo.category))) }
(packageManager?.queryIntentActivities( oringinPackageList.sortBy { it.appName }
Intent(Intent.ACTION_MAIN, null).addCategory(Intent.CATEGORY_LAUNCHER), 0 packageList.addAll(
)) oringinPackageList
})?.apply { )
removeIf { it.activityInfo.packageName.equals(BuildConfig.APPLICATION_ID) } binding.appsList.post { if (packageList.size > 0) {
appsAdapter?.updateData(packageList)
forEach { } }
oringinPackageList.add( }!!
Packages(
it.activityInfo.packageName,
appName(it),
getCategory(it.activityInfo.applicationInfo.category)
)
)
packageList.add(
Packages(
it.activityInfo.packageName,
appName(it),
getCategory(it.activityInfo.applicationInfo.category)
)
)
}
}!!
}
/* add package and app names to the list */
// var edit = appNamesPrefs?.edit()
// for (resolver in packageInfoList) {
// packageList.add(Packages(resolver.activityInfo.packageName, appName(resolver)))
// }
} }
// when {
// packageList.size < 1 -> return
// else -> {
if (packageList.size > 0) {
MainScope().launch {
appsAdapter?.updateData(packageList)
}
}
// }
} }
private fun getAlphabetItems() { private fun getAlphabetItems() {
@ -357,8 +396,9 @@ internal class AppDrawer : Fragment() {
var lastSearchStringLength = 0 var lastSearchStringLength = 0
var lastSearchString : String = "" var lastSearchString : String = ""
private fun filterAppsList(searchString: String) { private fun filterAppsList(searchString: String) {
chechHandler.removeCallbacks(cancelSearch)
/* check each app name and add if it matches the search string */ /* check each app name and add if it matches the search string */
if (lastSearchStringLength > 0 && (lastSearchStringLength != searchString.length || lastSearchString.equals(searchString) == false)) { if (searchString.length > 0 && (lastSearchStringLength != searchString.length || lastSearchString.equals(searchString) == false)) {
BLog.LOGE("START FILTER") BLog.LOGE("START FILTER")
packageList.clear() packageList.clear()
for (pkg in oringinPackageList) { for (pkg in oringinPackageList) {
@ -367,51 +407,41 @@ internal class AppDrawer : Fragment() {
packageList.add(pkg) packageList.add(pkg)
} }
} }
packageList.sortBy { it.appName }
BLog.LOGE("MIDDLE FILTER") BLog.LOGE("MIDDLE FILTER")
if (searchString.length > 2 && packageList.size == 1 && settingsPrefs!!.getBoolean(
KEY_QUICK_LAUNCH,
true
)
) {
var dialog = AlertDialog.Builder(requireContext())
dialog.setTitle("앱 실행 확인")
dialog.setMessage("${searchString} 검색 결과 '${packageList[0].appName}' 준비됨")
dialog.setCancelable(false)
dialog.setNegativeButton("취소") { s,d->
binding.searchInput.setText("")
s.dismiss()
}
dialog.setPositiveButton("실행") { s, d ->
startActivity(packageManager?.getLaunchIntentForPackage(packageList[0].packageName))
s.dismiss()
binding.searchInput.setText("")
}
dialog.show()
} else {
appsAdapter?.updateData(packageList) appsAdapter?.updateData(packageList)
}
contactList.clear() contactList.clear()
for (item in originContactList) { for (item in originContactList) {
if (item.name.contains(searchString) || item.phoneNumber.contains(searchString)) { if (item.name.contains(searchString) || item.phoneNumber.contains(searchString)) {
contactList.add(item) contactList.add(item)
} }
} }
contactList.sortBy { it.name }
contactAdapter?.updateData(contactList) contactAdapter?.updateData(contactList)
BLog.LOGE("END FILTER") BLog.LOGE("END FILTER")
} else if(lastSearchStringLength == 0){ } else {
contactList.clear() afterClearSearch()
for (item in originContactList) {
contactList.add(item)
}
packageList.clear()
for (resolver in oringinPackageList) {
packageList.add(resolver)
}
appsAdapter?.updateData(packageList)
contactAdapter?.updateData(contactList)
} }
lastSearchString = searchString lastSearchString = searchString
lastSearchStringLength = searchString.length lastSearchStringLength = searchString.length
chechHandler.postDelayed(cancelSearch, 3000L)
}
private fun afterClearSearch() {
contactList.clear()
packageList.clear()
for (item in originContactList) {
contactList.add(item)
}
for (resolver in oringinPackageList) {
packageList.add(resolver)
}
packageList.sortBy { it.appName }
contactList.sortBy { it.name }
appsAdapter?.updateData(packageList)
contactAdapter?.updateData(contactList)
} }
private fun normalize(str: String): String { private fun normalize(str: String): String {

View File

@ -70,7 +70,7 @@ internal class ContactAdapter (
holder.view.root.apply { holder.view.root.apply {
/* on click - open app */ /* on click - open app */
setOnClickListener { setOnClickListener {
context.startActivity(Intent(Intent.ACTION_DIAL, Uri.parse("tel://${item.phoneNumber}"))) ContactMenu().show(fragmentManager, item.id.toString())
} }
/* on long click - open app menu */ /* on long click - open app menu */

View File

@ -58,6 +58,7 @@ import rasel.lunar.launcher.apps.AppDrawer.Companion.appNamesPrefs
import rasel.lunar.launcher.databinding.ActivityBrowserDialogBinding import rasel.lunar.launcher.databinding.ActivityBrowserDialogBinding
import rasel.lunar.launcher.databinding.AppInfoDialogBinding import rasel.lunar.launcher.databinding.AppInfoDialogBinding
import rasel.lunar.launcher.databinding.AppMenuBinding import rasel.lunar.launcher.databinding.AppMenuBinding
import rasel.lunar.launcher.databinding.ContactMenuBinding
import rasel.lunar.launcher.helpers.Constants.Companion.KEY_APP_NO_ import rasel.lunar.launcher.helpers.Constants.Companion.KEY_APP_NO_
import rasel.lunar.launcher.helpers.Constants.Companion.MAX_FAVORITE_APPS import rasel.lunar.launcher.helpers.Constants.Companion.MAX_FAVORITE_APPS
import rasel.lunar.launcher.helpers.Constants.Companion.PREFS_FAVORITE_APPS import rasel.lunar.launcher.helpers.Constants.Companion.PREFS_FAVORITE_APPS
@ -74,14 +75,15 @@ import java.util.*
internal class ContactMenu : BottomSheetDialogFragment() { internal class ContactMenu : BottomSheetDialogFragment() {
private lateinit var binding: AppMenuBinding private lateinit var binding: ContactMenuBinding
private lateinit var packageName: String private lateinit var packageName: String
private lateinit var packageManager: PackageManager private lateinit var packageManager: PackageManager
private lateinit var appInfo: ApplicationInfo private lateinit var appInfo: ApplicationInfo
private lateinit var defAppName: String private lateinit var defAppName: String
var contactName : String = ""
var contactPhoneNumber : String = ""
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = AppMenuBinding.inflate(inflater, container, false) binding = ContactMenuBinding.inflate(inflater, container, false)
/* get package name from fragment's tag */ /* get package name from fragment's tag */
packageName = tag.toString() packageName = tag.toString()
@ -96,14 +98,12 @@ internal class ContactMenu : BottomSheetDialogFragment() {
val cursor = resolver.query(phoneUri, projection, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = " + packageName, null , null) val cursor = resolver.query(phoneUri, projection, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = " + packageName, null , null)
if (cursor != null) { if (cursor != null) {
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
// val idx =cursor.getColumnIndex(projection[0])
val nameIndex = cursor.getColumnIndex(projection[0]) val nameIndex = cursor.getColumnIndex(projection[0])
val numberIndex = cursor.getColumnIndex(projection[1]) val numberIndex = cursor.getColumnIndex(projection[1])
// var contactId = cursor.getInt(idx) contactName = cursor.getString(nameIndex)
val name = cursor.getString(nameIndex)
var number = cursor.getString(numberIndex) var number = cursor.getString(numberIndex)
number = number.replace("-", "") contactPhoneNumber = number.replace("-", "")
BLog.LOGE("GetContact", "이름 : $name 번호 : $number ") BLog.LOGE("GetContact", "이름 : $contactName 번호 : $contactPhoneNumber ")
} }
} }
// 데이터 계열은 반드시 닫아줘야 한다. // 데이터 계열은 반드시 닫아줘야 한다.
@ -124,8 +124,8 @@ internal class ContactMenu : BottomSheetDialogFragment() {
// copyToClipboard(requireContext(), packageName) // copyToClipboard(requireContext(), packageName)
// } // }
// //
// appName() appName()
// binding.detailedInfo.setOnClickListener { detailedInfo() } binding.detailedInfo.setOnClickListener { detailedInfo() }
// binding.activityBrowser.setOnClickListener { activityBrowser() } // binding.activityBrowser.setOnClickListener { activityBrowser() }
// binding.appStore.setOnClickListener { appStore() } // binding.appStore.setOnClickListener { appStore() }
// binding.appFreeform.setOnClickListener { freeform() } // binding.appFreeform.setOnClickListener { freeform() }
@ -165,7 +165,7 @@ internal class ContactMenu : BottomSheetDialogFragment() {
/* listen on clicks */ /* listen on clicks */
binding.favGroup.addOnButtonCheckedListener { _: MaterialButtonToggleGroup?, binding.favGroup.addOnButtonCheckedListener { _: MaterialButtonToggleGroup?,
checkedId: Int, isChecked: Boolean -> checkedId: Int, isChecked: Boolean ->
try { try {
if (checkedId == button.id) { if (checkedId == button.id) {
if (isChecked) { if (isChecked) {
@ -186,66 +186,17 @@ internal class ContactMenu : BottomSheetDialogFragment() {
} }
private fun appName() { private fun appName() {
binding.appName.setOnFocusChangeListener { _, hasFocus -> binding.appName.text = contactName
if (hasFocus) binding.appName.minWidth = resources.getDimensionPixelOffset(R.dimen.twoSeventySix) binding.phoneNumber.text = contactPhoneNumber
else {
(requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager)
.hideSoftInputFromWindow(binding.appName.windowToken, 0)
binding.appName.apply {
minWidth = resources.getDimensionPixelOffset(R.dimen.zero)
if (text!!.isBlank()) setText(defAppName)
else setText(text!!.trim())
if (text.toString() == defAppName) appNamesPrefs?.edit()!!.remove(packageName).apply()
else appNamesPrefs?.edit()!!.putString(packageName, text.toString()).apply()
(requireParentFragment() as AppDrawer).fetchApps()
}
}
}
binding.appName.setOnKeyListener { _, keyCode, event ->
if (event.action == KeyEvent.ACTION_DOWN) {
if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_BACK) {
binding.appName.clearFocus()
return@setOnKeyListener true
}
}
false
}
} }
/* detailed info dialog */ /* detailed info dialog */
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
private fun detailedInfo() { private fun detailedInfo() {
val dialogBinding = AppInfoDialogBinding.inflate(lActivity!!.layoutInflater) var intent = Intent(Intent.ACTION_VIEW);
MaterialAlertDialogBuilder(lActivity!!) intent.setData(Uri.parse(ContactsContract.Contacts.CONTENT_URI.toString() + "/" + packageName));
.setView(dialogBinding.root) startActivity(intent);
.setPositiveButton(android.R.string.cancel, null)
.show()
/* show app name */
dialogBinding.appName.text = packageManager.getApplicationLabel(appInfo)
/* get package info */
val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
packageManager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0))
} else {
packageManager.getPackageInfo(packageName, 0)
}
/* show infos */
dialogBinding.mixed.text =
"${resources.getString(R.string.version)}: ${packageInfo.versionName} (${PackageInfoCompat.getLongVersionCode(packageInfo).toInt()})\n" +
"${resources.getString(R.string.sdk)}: ${appInfo.minSdkVersion} ~ ${appInfo.targetSdkVersion}\n" +
"${resources.getString(R.string.uid)}: ${appInfo.uid}\n" +
"${resources.getString(R.string.first_install)}: ${dateTimeFormat(packageInfo.firstInstallTime)}\n" +
"${resources.getString(R.string.last_update)}: ${dateTimeFormat(packageInfo.lastUpdateTime)}"
/* show permissions */
dialogBinding.permissions.text = permissionsList
} }
/* activity browser dialog */ /* activity browser dialog */

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -95,11 +95,23 @@
style="@style/SearchIcons" style="@style/SearchIcons"
android:id="@+id/search_translate"/> android:id="@+id/search_translate"/>
</LinearLayout> </LinearLayout>
<LinearLayout
android:id="@+id/quickSearch2"
android:orientation="horizontal"
app:layout_constraintTop_toBottomOf="@id/quickSearch"
android:layout_width="match_parent"
android:layout_height="50dp">
<rasel.lunar.launcher.view.CircleImageView
style="@style/SearchIcons"
android:src="@drawable/coupang"
android:id="@+id/search_coupang"/>
</LinearLayout>
<TextView <TextView
android:paddingLeft="15dp" android:paddingLeft="15dp"
android:paddingRight="15dp" android:paddingRight="15dp"
android:id="@+id/title_contact" android:id="@+id/title_contact"
app:layout_constraintTop_toBottomOf="@id/quickSearch" app:layout_constraintTop_toBottomOf="@id/quickSearch2"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
android:text="연락처" android:text="연락처"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@ -0,0 +1,144 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:padding="@dimen/twelve"
android:clickable="true"
android:focusableInTouchMode="true">
<TextView
android:id="@+id/appName"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_height="wrap_content"
android:minWidth="@dimen/zero"
android:gravity="center"
android:padding="@dimen/eight"
android:inputType="textNoSuggestions"
/>
<TextView
android:id="@+id/phoneNumber"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/eight"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/appName" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/activityBrowser"
style="@style/Widget.Material3.ExtendedFloatingActionButton.Surface"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/eight"
android:contentDescription="@null"
android:src="@drawable/ic_activity"
android:tooltipText="@string/activity_browser"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/detailedInfo"
app:layout_constraintTop_toBottomOf="@+id/phoneNumber" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/detailedInfo"
style="@style/Widget.Material3.ExtendedFloatingActionButton.Surface"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/eight"
android:layout_marginEnd="@dimen/eight"
android:contentDescription="@null"
android:src="@drawable/ic_info"
android:tooltipText="@string/detailed_info"
app:layout_constraintEnd_toStartOf="@id/activityBrowser"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/phoneNumber" />
<com.google.android.material.button.MaterialButtonToggleGroup
android:id="@+id/favGroup"
android:layout_width="@dimen/zero"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/eight"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/detailedInfo"
app:singleSelection="true" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/appInfo"
style="@style/Widget.Material3.ExtendedFloatingActionButton.Surface"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/eight"
android:layout_marginEnd="@dimen/eight"
android:contentDescription="@null"
android:src="@drawable/ic_info2"
android:tooltipText="@string/app_info"
app:layout_constraintEnd_toStartOf="@id/appFreeform"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/favGroup" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/appFreeform"
style="@style/Widget.Material3.ExtendedFloatingActionButton.Surface"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/eight"
android:layout_marginEnd="@dimen/eight"
android:contentDescription="@null"
android:src="@drawable/ic_pip"
android:tooltipText="@string/freeform"
app:layout_constraintEnd_toStartOf="@id/appStore"
app:layout_constraintStart_toEndOf="@id/appInfo"
app:layout_constraintTop_toBottomOf="@+id/favGroup" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/appStore"
style="@style/Widget.Material3.ExtendedFloatingActionButton.Surface"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/eight"
android:layout_marginEnd="@dimen/eight"
android:contentDescription="@null"
android:src="@drawable/ic_store"
android:tooltipText="@string/app_store"
app:layout_constraintEnd_toStartOf="@id/appShare"
app:layout_constraintStart_toEndOf="@id/appFreeform"
app:layout_constraintTop_toBottomOf="@+id/favGroup" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/appShare"
style="@style/Widget.Material3.ExtendedFloatingActionButton.Surface"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/eight"
android:layout_marginEnd="@dimen/eight"
android:contentDescription="@null"
android:src="@drawable/ic_share"
android:tooltipText="@string/share"
app:layout_constraintEnd_toStartOf="@id/appUninstall"
app:layout_constraintStart_toEndOf="@id/appStore"
app:layout_constraintTop_toBottomOf="@+id/favGroup" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/appUninstall"
style="@style/Widget.Material3.ExtendedFloatingActionButton.Surface"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/eight"
android:contentDescription="@null"
android:src="@drawable/ic_delete"
android:tooltipText="@string/uninstall"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/appShare"
app:layout_constraintTop_toBottomOf="@+id/favGroup"
app:tint="@android:color/holo_red_light" />
</androidx.constraintlayout.widget.ConstraintLayout>