This commit is contained in:
lunaticbum 2024-08-14 16:06:50 +09:00
parent b8c08da378
commit 4e26fab27c
24 changed files with 1578 additions and 932 deletions

View File

@ -16,6 +16,7 @@
<uses-permission android:name="android.permission.WRITE_SETTINGS"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="com.android.alarm.permission.SET_ALARM"/>
<uses-permission android:name="android.permission.READ_CONTACTS" />
<!-- api 33+ -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
@ -38,6 +39,8 @@
android:excludeFromRecents="true"
android:clearTaskOnLaunch="true"
android:stateNotNeeded="true"
android:largeHeap="true"
android:hardwareAccelerated="true"
android:screenOrientation="nosensor"
android:windowSoftInputMode="adjustResize"
android:requestLegacyExternalStorage="true">

View File

@ -19,15 +19,17 @@
package rasel.lunar.launcher.apps
import android.annotation.SuppressLint
import android.content.ContentUris
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.graphics.Rect
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings.Global
import android.provider.ContactsContract
import android.util.Log
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
@ -36,34 +38,24 @@ import android.view.View.VISIBLE
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import androidx.appcompat.app.AlertDialog
import androidx.core.view.updateLayoutParams
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.textview.MaterialTextView
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import rasel.lunar.launcher.BuildConfig
import rasel.lunar.launcher.LauncherActivity.Companion.lActivity
import rasel.lunar.launcher.R
import rasel.lunar.launcher.databinding.AppDrawerBinding
import rasel.lunar.launcher.helpers.Constants.Companion.DEFAULT_GRID_COLUMNS
import rasel.lunar.launcher.helpers.Constants.Companion.DEFAULT_SCROLLBAR_HEIGHT
import rasel.lunar.launcher.helpers.Constants.Companion.KEY_APPS_COUNT
import rasel.lunar.launcher.helpers.Constants.Companion.KEY_APPS_LAYOUT
import rasel.lunar.launcher.helpers.Constants.Companion.KEY_DRAW_ALIGN
import rasel.lunar.launcher.helpers.Constants.Companion.KEY_GRID_COLUMNS
import rasel.lunar.launcher.helpers.Constants.Companion.KEY_KEYBOARD_SEARCH
import rasel.lunar.launcher.helpers.Constants.Companion.KEY_QUICK_LAUNCH
import rasel.lunar.launcher.helpers.Constants.Companion.KEY_SCROLLBAR_HEIGHT
import rasel.lunar.launcher.helpers.Constants.Companion.KEY_STATUS_BAR
import rasel.lunar.launcher.helpers.Constants.Companion.PREFS_APP_NAMES
import rasel.lunar.launcher.helpers.Constants.Companion.PREFS_SETTINGS
import rasel.lunar.launcher.utils.BLog
import java.text.Normalizer
import java.util.*
import java.util.regex.Pattern
@ -77,6 +69,7 @@ internal class AppDrawer : Fragment() {
companion object {
private var packageManager: PackageManager? = null
private var appsAdapter: AppsAdapter? = null
private var contactAdapter : ContactAdapter? = null
private var packageInfoList: MutableList<ResolveInfo> = mutableListOf()
private var packageList = mutableListOf<Packages>()
@ -84,11 +77,11 @@ internal class AppDrawer : Fragment() {
// private val alphabetPattern = Pattern.compile("[A-Z]")
@JvmStatic var settingsPrefs: SharedPreferences? = null
@JvmStatic var appNamesPrefs: SharedPreferences? = null
// @JvmStatic var alphabetList = mutableListOf<String>()
@JvmStatic var letterPreview: MaterialTextView? = null
private fun appName(resolver: ResolveInfo): String {
if(appNamesPrefs?.contains(resolver.activityInfo.packageName) != null && appNamesPrefs?.getString(resolver.activityInfo.packageName,"")?.length ?: 0 > 0) {
BLog.LOGE("it.activityInfo.packageName >>>> ${resolver.activityInfo.packageName} == name : ${appNamesPrefs?.getString(resolver.activityInfo.packageName,"") ?: ""}")
return appNamesPrefs?.getString(resolver.activityInfo.packageName,"") ?: ""
} else {
return resolver.loadLabel(packageManager).toString().apply {
@ -99,6 +92,7 @@ internal class AppDrawer : Fragment() {
}
}
fun getInputText() = binding.searchInput.text.toString()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = AppDrawerBinding.inflate(inflater, container, false)
@ -108,36 +102,106 @@ internal class AppDrawer : Fragment() {
layoutType = settingsPrefs!!.getInt(KEY_APPS_LAYOUT, 0)
packageManager = lActivity?.packageManager
appsAdapter = AppsAdapter(layoutType, packageManager!!, childFragmentManager, binding.appsCount)
letterPreview = binding.appsCount
contactAdapter = ContactAdapter(packageManager!!, childFragmentManager)
binding.appsCount.visibility = if (settingsPrefs!!.getBoolean(KEY_APPS_COUNT, true)) VISIBLE else GONE
binding.searchNmap.setOnClickListener {
openSearchApps("nmap://search?query=${getInputText()}&appname=${BuildConfig.APPLICATION_ID}","com.nhn.android.nmap")
}
binding.searchGoogleMap.setOnClickListener {
openSearchApps("geo:0,0?q=${getInputText()}","com.google.android.apps.maps")
}
binding.searchGoogle.setOnClickListener {
openSearchApps("https://www.google.com/search?q=${getInputText()}","com.android.chrome")
}
binding.searchTmap.setOnClickListener {
openSearchApps("tmap://search?name=${getInputText()}","com.skt.tmap.ku")
}
binding.searchNaver.setOnClickListener {
openSearchApps("https://search.naver.com/search.naver?where=nexearch&query=${getInputText()}", "com.nhn.android.search")
}
binding.searchDuckduckgo.setOnClickListener {
openSearchApps("https://duckduckgo.com/?t=h_&q=${getInputText()}","com.duckduckgo.mobile.android")
}
binding.searchNamuwiki.setOnClickListener {
openSearchApps("https://namu.wiki/Search?q=${getInputText()}")
}
setLayout()
return binding.root
}
val originContactList = arrayListOf<SimpleContact>()
val contactList = arrayListOf<SimpleContact>()
private fun GetContact() {
if (originContactList.size > 0) {
} else {
contactList.clear()
originContactList.clear()
val resolver = lActivity!!.contentResolver
val phoneUri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI
val projection = arrayOf(
ContactsContract.CommonDataKinds.Phone.CONTACT_ID,
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.NUMBER,
)
val cursor = resolver.query(phoneUri, projection, null, null, null)
if (cursor != null) {
while (cursor.moveToNext()) {
val idx =cursor.getColumnIndex(projection[0])
val nameIndex = cursor.getColumnIndex(projection[1])
val numberIndex = cursor.getColumnIndex(projection[2])
var contactId = cursor.getString(idx)
val name = cursor.getString(nameIndex)
var number = cursor.getString(numberIndex)
number = number.replace("-", "")
if (name?.length ?: 0 > 0 && number?.length ?: 0 > 0) {
contactList.add(SimpleContact(contactId,name, number))
originContactList.add(SimpleContact(contactId,name, number))
}
Log.d("GetContact", "이름 : $name 번호 : $number ")
}
}
// 데이터 계열은 반드시 닫아줘야 한다.
cursor!!.close()
}
}
fun openSearchApps(schemeString : String, pakage : String? = null) {
val gmmIntentUri = Uri.parse(schemeString)
val mapIntent = Intent(Intent.ACTION_VIEW, gmmIntentUri)
pakage?.let {
mapIntent.setPackage(pakage)
}
startActivity(mapIntent)
}
@SuppressLint("ClickableViewAccessibility")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.reset.setOnClickListener { onResume() }
binding.reset.setOnClickListener { filterAppsList("") }
binding.moveDown.setOnClickListener {
binding.appsList.smoothScrollToPosition(packageList.size - 1)
}
// binding.moveDown.setOnClickListener {
// binding.appsList.smoothScrollToPosition(packageList.size - 1)
// }
//
// binding.moveUp.setOnClickListener {
// binding.appsList.smoothScrollToPosition(0)
// }
binding.moveUp.setOnClickListener {
binding.appsList.smoothScrollToPosition(0)
}
binding.search.setOnClickListener {
when (isSearchShown) {
true -> closeSearch()
false -> openSearch()
}
}
// binding.search.setOnClickListener {
// when (isSearchShown) {
// true -> closeSearch()
// false -> openSearch()
// }
// }
binding.searchInput.doOnTextChanged { inputText, _, _, _ ->
binding.searchInput.text?.let { binding.searchInput.setSelection(it.length) }
@ -148,6 +212,7 @@ internal class AppDrawer : Fragment() {
override fun onResume() {
super.onResume()
fetchApps()
GetContact()
setKeyboardPadding()
binding.appsCount.visibility = if (settingsPrefs!!.getBoolean(KEY_APPS_COUNT, true)) VISIBLE else GONE
@ -156,6 +221,8 @@ internal class AppDrawer : Fragment() {
appsAdapter?.updateGravity(settingsPrefs!!.getInt(KEY_DRAW_ALIGN, Gravity.CENTER))
}
contactAdapter?.updateData(contactList)
/* pop up the keyboard */
if (settingsPrefs!!.getBoolean(KEY_KEYBOARD_SEARCH, false)) openSearch()
}
@ -166,16 +233,18 @@ internal class AppDrawer : Fragment() {
}
private fun setLayout() {
when (layoutType) {
0, 1 -> {
binding.appsList.layoutManager = LinearLayoutManager(requireContext())
appsAdapter!!.updateGravity(settingsPrefs!!.getInt(KEY_DRAW_ALIGN, Gravity.CENTER))
}
2 -> binding.appsList.layoutManager = GridLayoutManager(requireContext(), Math.min(settingsPrefs!!.getInt(KEY_GRID_COLUMNS, DEFAULT_GRID_COLUMNS), 4))
}
// when (layoutType) {
// 0, 1 -> {
// binding.appsList.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL,false)
// appsAdapter!!.updateGravity(settingsPrefs!!.getInt(KEY_DRAW_ALIGN, Gravity.CENTER))
// }
// 2 -> binding.appsList.layoutManager = GridLayoutManager(requireContext(), Math.min(settingsPrefs!!.getInt(KEY_GRID_COLUMNS, DEFAULT_GRID_COLUMNS), 4), GridLayoutManager.HORIZONTAL,false)
// }
binding.appsList.layoutManager = GridLayoutManager(requireContext(), 2, GridLayoutManager.HORIZONTAL,false)
binding.contactList.layoutManager = GridLayoutManager(requireContext(), 2, GridLayoutManager.HORIZONTAL,false)
/* initialize apps list adapter */
binding.appsList.adapter = appsAdapter
binding.contactList.adapter = contactAdapter
}
/* update app list with app and package name */
@ -281,13 +350,25 @@ internal class AppDrawer : Fragment() {
} else {
appsAdapter?.updateData(packageList)
}
contactList.clear()
for (item in originContactList) {
if (item.name.contains(searchString) || item.phoneNumber.contains(searchString)) {
contactList.add(item)
}
}
contactAdapter?.updateData(contactList)
BLog.LOGE("END FILTER")
} else if(lastSearchStringLength == 0){
contactList.clear()
for (item in originContactList) {
contactList.add(item)
}
packageList.clear()
for (resolver in packageInfoList) {
packageList.add(Packages(resolver.activityInfo.packageName, appName(resolver)))
}
appsAdapter?.updateData(packageList)
contactAdapter?.updateData(contactList)
}
lastSearchString = searchString
lastSearchStringLength = searchString.length
@ -302,7 +383,6 @@ internal class AppDrawer : Fragment() {
private fun openSearch() {
isSearchShown = true
binding.search.setImageResource(R.drawable.ic_close)
binding.searchInput.apply {
visibility = VISIBLE
requestFocus()
@ -316,7 +396,6 @@ internal class AppDrawer : Fragment() {
/* clear search string, hide keyboard and search box */
private fun closeSearch() {
isSearchShown = false
binding.search.setImageResource(R.drawable.ic_search)
binding.searchInput.apply {
text?.clear()
visibility = GONE
@ -328,28 +407,29 @@ internal class AppDrawer : Fragment() {
}
private fun setKeyboardPadding() {
binding.root.viewTreeObserver.addOnGlobalLayoutListener {
val rect = Rect()
binding.root.getWindowVisibleDisplayFrame(rect)
val screenHeight = binding.root.height
val keyboardHeight = screenHeight - (rect.bottom - rect.top)
when {
keyboardHeight > screenHeight * 0.15 -> {
if (!isKeyboardShowing &&
!settingsPrefs!!.getBoolean(KEY_STATUS_BAR, false)) {
isKeyboardShowing = true
binding.root.setPadding(0, 0, 0, keyboardHeight)
}
}
else -> {
if (isKeyboardShowing) {
isKeyboardShowing = false
binding.root.setPadding(0, 0, 0, 0)
}
}
}
}
// binding.root.viewTreeObserver.addOnGlobalLayoutListener {
// val rect = Rect()
// binding.root.getWindowVisibleDisplayFrame(rect)
// val screenHeight = binding.root.height
// val keyboardHeight = screenHeight - (rect.bottom - rect.top)
//
// when {
// keyboardHeight > screenHeight * 0.15 -> {
// if (!isKeyboardShowing &&
// !settingsPrefs!!.getBoolean(KEY_STATUS_BAR, false)) {
// isKeyboardShowing = true
// binding.root.setPadding(0, 0, 0, keyboardHeight)
// }
// }
// else -> {
// if (isKeyboardShowing) {
// isKeyboardShowing = false
// binding.root.setPadding(0, 0, 0, 0)
// }
// }
// }
// }
}
}

View File

@ -25,6 +25,7 @@ import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.view.updatePadding
import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.DiffUtil
@ -43,7 +44,7 @@ internal class AppsAdapter(
private val layoutType: Int,
private val packageManager: PackageManager,
private val fragmentManager: FragmentManager,
private val appsCount: MaterialTextView) : RecyclerView.Adapter<AppsAdapter.AppsViewHolder>() {
private val appsCount: TextView) : RecyclerView.Adapter<AppsAdapter.AppsViewHolder>() {
private var oldList = mutableListOf<Packages>()
private var appGravity: Int = Gravity.CENTER
@ -57,57 +58,19 @@ internal class AppsAdapter(
override fun onBindViewHolder(holder: AppsViewHolder, i: Int) {
val item = oldList[i]
val fourDp = dpToPx(lActivity!!, R.dimen.four)
val eightDp = dpToPx(lActivity!!, R.dimen.eight)
val twelveDp = dpToPx(lActivity!!, R.dimen.twelve)
val sixteenDp = dpToPx(lActivity!!, R.dimen.sixteen)
holder.view.apply {
childTextview.text = item.appName
appIconTwo.visibility = View.VISIBLE
when (layoutType) {
0 -> {
appIcon.visibility = View.GONE
appIconTwo.visibility = View.GONE
childTextview.apply {
gravity = appGravity
setTextSize(TypedValue.COMPLEX_UNIT_PX, lActivity!!.resources.getDimension(R.dimen.twentyTwo))
}
root.setPadding(sixteenDp, fourDp, sixteenDp, fourDp)
}
1 -> {
appIcon.visibility = View.GONE
appIconTwo.visibility = View.VISIBLE
MainScope().async {
getDrawableIconForPackage(item.packageName, packageManager.getApplicationIcon(item.packageName)) {
appIconTwo.post { appIconTwo.setImageDrawable(it) }
} }
childTextview.apply {
gravity = appGravity or Gravity.CENTER_VERTICAL
setTextSize(TypedValue.COMPLEX_UNIT_PX, lActivity!!.resources.getDimension(R.dimen.twenty))
updatePadding(left = twelveDp)
}
root.setPadding(sixteenDp, eightDp, sixteenDp, eightDp)
}
2 -> {
appIconTwo.visibility = View.GONE
appIcon.visibility = View.VISIBLE
MainScope().async {
getDrawableIconForPackage(
item.packageName,
packageManager.getApplicationIcon(item.packageName)
) {
appIcon.post { appIcon.setImageDrawable(it) }
}
}
childTextview.apply {
gravity = Gravity.CENTER
setTextSize(TypedValue.COMPLEX_UNIT_PX, lActivity!!.resources.getDimension(R.dimen.twelve))
}
root.setPadding(eightDp, eightDp, eightDp, eightDp)
}
MainScope().async {
getDrawableIconForPackage(item.packageName, packageManager.getApplicationIcon(item.packageName)) {
appIconTwo.post { appIconTwo.setImageDrawable(it) }
} }
childTextview.apply {
gravity = Gravity.CENTER
setTextSize(TypedValue.COMPLEX_UNIT_PX, lActivity!!.resources.getDimension(R.dimen.twelve))
}
}

View File

@ -0,0 +1,131 @@
/*
* Lunar Launcher
* Copyright (C) 2022 Md Rasel Hossain
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package rasel.lunar.launcher.apps
import android.annotation.SuppressLint
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.util.TypedValue
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.view.updatePadding
import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.textview.MaterialTextView
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.async
import rasel.lunar.launcher.LauncherActivity.Companion.lActivity
import rasel.lunar.launcher.R
import rasel.lunar.launcher.apps.IconPackManager.Companion.getDrawableIconForPackage
import rasel.lunar.launcher.databinding.AppsChildBinding
import rasel.lunar.launcher.databinding.ContactItemBinding
import rasel.lunar.launcher.helpers.UniUtils.Companion.dpToPx
import rasel.lunar.launcher.utils.BLog
internal class ContactAdapter (
private val packageManager: PackageManager,
private val fragmentManager: FragmentManager) : RecyclerView.Adapter<ContactAdapter.ContactViewHolder>() {
private var oldList = mutableListOf<SimpleContact>()
private var appGravity: Int = Gravity.CENTER
companion object {
@JvmStatic var appsSize: Int? = null
}
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): ContactViewHolder =
ContactViewHolder(ContactItemBinding.inflate(LayoutInflater.from(viewGroup.context), viewGroup, false))
override fun onBindViewHolder(holder: ContactViewHolder, i: Int) {
val item = oldList[i]
holder.view.apply {
name.text = item.name
number.text= item.phoneNumber
}
holder.view.root.apply {
/* on click - open app */
setOnClickListener {
context.startActivity(Intent(Intent.ACTION_CALL, Uri.parse("tel://${item.phoneNumber}")))
}
/* on long click - open app menu */
setOnLongClickListener {
BLog.LOGE("item.id.toString() >> ${item.id.toString()}")
ContactMenu().show(fragmentManager, item.id.toString())
true
}
}
}
override fun getItemCount(): Int = oldList.size
inner class ContactViewHolder(var view: ContactItemBinding) : RecyclerView.ViewHolder(view.root)
/* update app list */
fun updateData(newList: List<SimpleContact>) {
val diffUtilResult = DiffUtil.calculateDiff(ContactDiffUtil(oldList, newList))
oldList.clear()
oldList.addAll(newList)
diffUtilResult.dispatchUpdatesTo(this)
newList.size.let {
appsSize = it
}
}
/* update text gravity (alignment) */
@SuppressLint("RtlHardcoded", "NotifyDataSetChanged")
fun updateGravity(gravity: Int){
/* the first check is to avoid calling notifyDataSetChanged() everytime */
if (gravity != appGravity &&
(gravity == Gravity.LEFT || gravity == Gravity.CENTER || gravity == Gravity.RIGHT)) {
appGravity = gravity
notifyDataSetChanged()
}
}
fun hideItem(idx: Int) {
}
}
data class SimpleContact(val id : String,val name : String ,val phoneNumber : String)
internal class ContactDiffUtil(
private val oldList: List<SimpleContact>, private val newList: List<SimpleContact>
) : DiffUtil.Callback() {
override fun getOldListSize(): Int = oldList.size
override fun getNewListSize(): Int = newList.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
oldList[oldItemPosition].phoneNumber == newList[newItemPosition].phoneNumber
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
oldList[oldItemPosition] == newList[newItemPosition]
}

View File

@ -0,0 +1,438 @@
/*
* Lunar Launcher
* Copyright (C) 2022 Md Rasel Hossain
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package rasel.lunar.launcher.apps
import android.annotation.SuppressLint
import android.app.ActivityOptions
import android.content.ActivityNotFoundException
import android.content.ComponentName
import android.content.ContentUris
import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.res.ColorStateList
import android.graphics.Rect
import android.icu.text.SimpleDateFormat
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.ContactsContract
import android.provider.Settings
import android.util.Log
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.Toast
import androidx.appcompat.widget.LinearLayoutCompat
import androidx.core.content.FileProvider
import androidx.core.content.pm.PackageInfoCompat
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.button.MaterialButton
import com.google.android.material.button.MaterialButtonToggleGroup
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import rasel.lunar.launcher.LauncherActivity.Companion.lActivity
import rasel.lunar.launcher.R
import rasel.lunar.launcher.apps.AppDrawer.Companion.appNamesPrefs
import rasel.lunar.launcher.databinding.ActivityBrowserDialogBinding
import rasel.lunar.launcher.databinding.AppInfoDialogBinding
import rasel.lunar.launcher.databinding.AppMenuBinding
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.PREFS_FAVORITE_APPS
import rasel.lunar.launcher.helpers.UniUtils.Companion.copyToClipboard
import rasel.lunar.launcher.helpers.UniUtils.Companion.screenHeight
import rasel.lunar.launcher.helpers.UniUtils.Companion.screenWidth
import rasel.lunar.launcher.utils.BLog
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import java.util.*
internal class ContactMenu : BottomSheetDialogFragment() {
private lateinit var binding: AppMenuBinding
private lateinit var packageName: String
private lateinit var packageManager: PackageManager
private lateinit var appInfo: ApplicationInfo
private lateinit var defAppName: String
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = AppMenuBinding.inflate(inflater, container, false)
/* get package name from fragment's tag */
packageName = tag.toString()
val resolver = lActivity!!.contentResolver
val phoneUri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI
val projection = arrayOf(
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.NUMBER,
)
BLog.LOGE("GetContact", "packageName ${packageName}")
val cursor = resolver.query(phoneUri, projection, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = " + packageName, null , null)
if (cursor != null) {
while (cursor.moveToNext()) {
// val idx =cursor.getColumnIndex(projection[0])
val nameIndex = cursor.getColumnIndex(projection[0])
val numberIndex = cursor.getColumnIndex(projection[1])
// var contactId = cursor.getInt(idx)
val name = cursor.getString(nameIndex)
var number = cursor.getString(numberIndex)
number = number.replace("-", "")
BLog.LOGE("GetContact", "이름 : $name 번호 : $number ")
}
}
// 데이터 계열은 반드시 닫아줘야 한다.
cursor!!.close()
/* get application info */
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
(requireDialog() as BottomSheetDialog).dismissWithAnimation = true
/* copy package name */
// binding.appPackage.setOnClickListener {
// copyToClipboard(requireContext(), packageName)
// }
//
// appName()
// binding.detailedInfo.setOnClickListener { detailedInfo() }
// binding.activityBrowser.setOnClickListener { activityBrowser() }
// binding.appStore.setOnClickListener { appStore() }
// binding.appFreeform.setOnClickListener { freeform() }
// binding.appInfo.setOnClickListener { appInfo() }
// binding.appShare.setOnClickListener { share() }
// binding.appUninstall.setOnClickListener { uninstall() }
}
/* manage initial preview and clicks for favorite apps */
@SuppressLint("PrivateResource")
private fun favoriteApps() {
val sharedPreferences = requireContext().getSharedPreferences(PREFS_FAVORITE_APPS, 0)
val enabledStroke =
ColorStateList.valueOf(requireContext().getColor(com.google.android.material.R.color.material_on_surface_stroke))
val disabledStroke =
ColorStateList.valueOf(requireContext().getColor(com.google.android.material.R.color.m3_chip_stroke_color))
for (position in 1..MAX_FAVORITE_APPS) {
val button = outlinedButton
val savedPackageName = sharedPreferences.getString(KEY_APP_NO_ + position, "")
/* set previews */
if (packageName == savedPackageName) button.isChecked = true
if (savedPackageName?.isNotEmpty() == true) button.strokeColor = enabledStroke
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
packageManager.getPackageInfo(savedPackageName!!, PackageManager.PackageInfoFlags.of(0))
else
packageManager.getPackageInfo(savedPackageName!!, 0)
} catch (e: PackageManager.NameNotFoundException) {
requireContext().getSharedPreferences(PREFS_FAVORITE_APPS, 0)
.edit().remove(KEY_APP_NO_ + position).apply()
button.strokeColor = disabledStroke
e.printStackTrace()
}
/* listen on clicks */
binding.favGroup.addOnButtonCheckedListener { _: MaterialButtonToggleGroup?,
checkedId: Int, isChecked: Boolean ->
try {
if (checkedId == button.id) {
if (isChecked) {
requireContext().getSharedPreferences(PREFS_FAVORITE_APPS, 0)
.edit().putString(KEY_APP_NO_ + position, packageName).apply()
button.strokeColor = enabledStroke
} else {
requireContext().getSharedPreferences(PREFS_FAVORITE_APPS, 0)
.edit().remove(KEY_APP_NO_ + position).apply()
button.strokeColor = disabledStroke
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}
private fun appName() {
binding.appName.setOnFocusChangeListener { _, hasFocus ->
if (hasFocus) binding.appName.minWidth = resources.getDimensionPixelOffset(R.dimen.twoSeventySix)
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 */
@SuppressLint("SetTextI18n")
private fun detailedInfo() {
val dialogBinding = AppInfoDialogBinding.inflate(lActivity!!.layoutInflater)
MaterialAlertDialogBuilder(lActivity!!)
.setView(dialogBinding.root)
.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 */
private fun activityBrowser() {
val dialogBinding = ActivityBrowserDialogBinding.inflate(lActivity!!.layoutInflater)
val dialogBuilder = MaterialAlertDialogBuilder(lActivity!!)
.setView(dialogBinding.root)
.setPositiveButton(android.R.string.cancel, null)
.show()
/* show app name */
dialogBinding.appName.text = packageManager.getApplicationLabel(appInfo)
/* get activity info */
val activityInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
packageManager.getPackageInfo(
packageName, PackageManager.PackageInfoFlags.of(PackageManager.GET_ACTIVITIES.toLong())
)
} else {
packageManager.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES)
}
/* show activity list */
val activityAdapter: ArrayAdapter<String> =
ArrayAdapter(requireContext(), R.layout.list_item, R.id.itemText, ArrayList())
if (activityInfo.activities.isNotEmpty()) {
for (activity in activityInfo.activities) {
activityAdapter.add(
activity.toString().split(" ").toTypedArray()[1].replace("}", "")
)
}
dialogBinding.activityList.adapter = activityAdapter
}
/* listen item clicks */
dialogBinding.activityList.onItemClickListener =
AdapterView.OnItemClickListener { _: AdapterView<*>?, _: View?, i: Int, _: Long ->
try {
/* open activity */
val intent = Intent()
intent.component = ComponentName(packageName, activityAdapter.getItem(i).toString())
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
requireContext().startActivity(intent)
} catch (exception: Exception) {
/* couldn't open activity */
exception.printStackTrace()
val exceptionShort = (exception.toString().split(": ").toTypedArray())[0]
Toast.makeText(requireContext(),
"${resources.getString(R.string.unable_to_launch)} -\n$exceptionShort", Toast.LENGTH_LONG).show()
}
dialogBuilder.dismiss()
}
}
/* open app's page in app store/market */
private fun appStore() {
try {
val storeIntent = Intent(Intent.ACTION_VIEW)
storeIntent.data = Uri.parse("market://details?id=$packageName")
storeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
requireContext().startActivity(storeIntent)
} catch (activityNotFoundException: ActivityNotFoundException) {
/* no app store found exception */
Toast.makeText(requireContext(), requireContext().getString(R.string.null_app_store_message),
Toast.LENGTH_SHORT).show()
activityNotFoundException.printStackTrace()
}
this.dismiss()
}
/* launch app as a freeform window */
private fun freeform() {
val freeformIntent = requireContext().packageManager.getLaunchIntentForPackage(packageName)
freeformIntent!!.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT or
Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
val rect = Rect(0, screenHeight / 2, screenWidth, screenHeight)
var activityOptions = activityOptions
activityOptions = activityOptions.setLaunchBounds(rect)
requireContext().startActivity(freeformIntent, activityOptions.toBundle())
this.dismiss()
}
/* open android's app info screen */
private fun appInfo() {
val infoIntent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
infoIntent.data = Uri.parse("package:$packageName")
infoIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
requireContext().startActivity(infoIntent)
this.dismiss()
}
private fun share() {
try {
// Create a temporary file to copy the APK
val apkLabel = packageManager.getApplicationLabel(appInfo).toString().lowercase().replace(" ", "_")
val tempApkFile = File(requireContext().externalCacheDir, "$apkLabel.apk")
// Copy the APK file
FileInputStream(File(appInfo.sourceDir)).use { `in` ->
FileOutputStream(tempApkFile).use { out ->
val buffer = ByteArray(1024)
var length: Int
while (`in`.read(buffer).also { length = it } > 0) {
out.write(buffer, 0, length)
}
}
}
// Generate a content URI using FileProvider
val contentUri =
FileProvider.getUriForFile(requireContext(), "${requireContext().packageName}.fileprovider", tempApkFile)
//requireContext().grantUriPermission(receivers.package.name, contentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
// Create a Share Intent
Intent(Intent.ACTION_SEND).apply {
type = "application/vnd.android.package-archive"
putExtra(Intent.EXTRA_STREAM, contentUri)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}.let {
// Start the chooser activity
startActivity(Intent.createChooser(it, getString(R.string.share_apk_message)))
}
}
catch (e: PackageManager.NameNotFoundException) { e.printStackTrace() }
catch (e: IOException) { e.printStackTrace() }
this.dismiss()
}
/* uninstall the app */
private fun uninstall() {
val uninstallIntent = Intent(Intent.ACTION_DELETE)
uninstallIntent.data = Uri.parse("package:$packageName")
uninstallIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
requireContext().startActivity(uninstallIntent)
this.dismiss()
}
/* create and add an outlined button to the toggle group */
private val outlinedButton: MaterialButton get() {
val style = com.google.android.material.R.attr.materialButtonOutlinedStyle
val button = MaterialButton(requireContext(), null, style)
button.layoutParams = LinearLayoutCompat.LayoutParams(
LinearLayoutCompat.LayoutParams.WRAP_CONTENT,
LinearLayoutCompat.LayoutParams.WRAP_CONTENT, 1F
)
binding.favGroup.addView(button)
return button
}
/* long value to local date-time format */
private fun dateTimeFormat(long: Long) : String = SimpleDateFormat.getDateTimeInstance().format(Date(long))
/* get and arrange all the permissions for an application */
private val permissionsList : String get() {
val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
packageManager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(PackageManager.GET_PERMISSIONS.toLong()))
} else {
packageManager.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS)
}
return if (packageInfo.requestedPermissions.isNotEmpty()) {
val stringBuilder = StringBuilder()
packageInfo.requestedPermissions.indices.forEach { i: Int ->
if (i != packageInfo.requestedPermissions.size - 1)
stringBuilder.append("${packageInfo.requestedPermissions[i]}\n\n")
/* don't add any new line after the last entry */
else
stringBuilder.append(packageInfo.requestedPermissions[i])
}
stringBuilder.toString()
} else {
""
}
}
/* get activity options for launching app in freeform mode */
private val activityOptions: ActivityOptions get() {
val activityOptions = ActivityOptions.makeBasic()
try {
val method =
ActivityOptions::class.java.getMethod("setLaunchWindowingMode", Int::class.javaPrimitiveType)
method.invoke(activityOptions, 5)
} catch (exception: Exception) {
exception.printStackTrace()
}
return activityOptions
}
}

View File

@ -1,705 +0,0 @@
package rasel.lunar.launcher.apps
import android.os.SystemClock
import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.view.View.OnTouchListener
import kotlin.math.abs
import kotlin.math.pow
import kotlin.math.sqrt
class GestureAnalyser @JvmOverloads constructor(
swipeSlopeIntolerance: Int = 3,
doubleTapMaxDelayMillis: Int = 500,
doubleTapMaxDownMillis: Int = 100
) {
private val initialX = DoubleArray(5)
private val initialY = DoubleArray(5)
private val finalX = DoubleArray(5)
private val finalY = DoubleArray(5)
private val currentX = DoubleArray(5)
private val currentY = DoubleArray(5)
private val delX = DoubleArray(5)
private val delY = DoubleArray(5)
private var numFingers = 0
private var initialT: Long = 0
private var finalT: Long = 0
private var currentT: Long = 0
private var prevInitialT: Long = 0
private var prevFinalT: Long = 0
private var swipeSlopeIntolerance = 3
private val doubleTapMaxDelayMillis: Long
private val doubleTapMaxDownMillis: Long
init {
this.swipeSlopeIntolerance = swipeSlopeIntolerance
this.doubleTapMaxDownMillis = doubleTapMaxDownMillis.toLong()
this.doubleTapMaxDelayMillis = doubleTapMaxDelayMillis.toLong()
}
fun trackGesture(ev: MotionEvent) {
val n = ev.pointerCount
for (i in 0 until n) {
initialX[i] = ev.getX(i).toDouble()
initialY[i] = ev.getY(i).toDouble()
}
numFingers = n
initialT = SystemClock.uptimeMillis()
}
fun untrackGesture() {
numFingers = 0
prevFinalT = SystemClock.uptimeMillis()
prevInitialT = initialT
}
fun getGesture(ev: MotionEvent): GestureType {
var averageDistance = 0.0
for (i in 0 until numFingers) {
finalX[i] = ev.getX(i).toDouble()
finalY[i] = ev.getY(i).toDouble()
delX[i] = finalX[i] - initialX[i]
delY[i] = finalY[i] - initialY[i]
averageDistance += sqrt(
(finalX[i] - initialX[i]).pow(2.0) + (finalY[i] - initialY[i]).pow(
2.0
)
)
}
averageDistance /= numFingers.toDouble()
finalT = SystemClock.uptimeMillis()
val gt = GestureType()
gt.gestureFlag = calcGesture()
gt.gestureDuration = finalT - initialT
gt.gestureDistance = averageDistance
return gt
}
fun getOngoingGesture(ev: MotionEvent): Int {
for (i in 0 until numFingers) {
currentX[i] = ev.getX(i).toDouble()
currentY[i] = ev.getY(i).toDouble()
delX[i] = finalX[i] - initialX[i]
delY[i] = finalY[i] - initialY[i]
}
currentT = SystemClock.uptimeMillis()
return calcGesture()
}
private fun calcGesture(): Int {
if (isDoubleTap) {
return DOUBLE_TAP_1
}
if (numFingers == 1) {
if ((-(delY[0])) > (swipeSlopeIntolerance * (abs(
delX[0]
)))
) {
return SWIPE_1_UP
}
if (((delY[0])) > (swipeSlopeIntolerance * (abs(
delX[0]
)))
) {
return SWIPE_1_DOWN
}
if ((-(delX[0])) > (swipeSlopeIntolerance * (abs(
delY[0]
)))
) {
return SWIPE_1_LEFT
}
if (((delX[0])) > (swipeSlopeIntolerance * (abs(
delY[0]
)))
) {
return SWIPE_1_RIGHT
}
}
if (numFingers == 2) {
if (((-delY[0]) > (swipeSlopeIntolerance * abs(
delX[0]
))) && ((-delY[1]) > (swipeSlopeIntolerance * abs(
delX[1]
)))
) {
return SWIPE_2_UP
}
if (((delY[0]) > (swipeSlopeIntolerance * abs(
delX[0]
))) && ((delY[1]) > (swipeSlopeIntolerance * abs(
delX[1]
)))
) {
return SWIPE_2_DOWN
}
if (((-delX[0]) > (swipeSlopeIntolerance * abs(
delY[0]
))) && ((-delX[1]) > (swipeSlopeIntolerance * abs(
delY[1]
)))
) {
return SWIPE_2_LEFT
}
if (((delX[0]) > (swipeSlopeIntolerance * abs(
delY[0]
))) && ((delX[1]) > (swipeSlopeIntolerance * abs(
delY[1]
)))
) {
return SWIPE_2_RIGHT
}
if (finalFingDist(0, 1) > 2 * (initialFingDist(0, 1))) {
return UNPINCH_2
}
if (finalFingDist(0, 1) < 0.5 * (initialFingDist(0, 1))) {
return PINCH_2
}
}
if (numFingers == 3) {
if (((-delY[0]) > (swipeSlopeIntolerance * abs(
delX[0]
)))
&& ((-delY[1]) > (swipeSlopeIntolerance * abs(
delX[1]
)))
&& ((-delY[2]) > (swipeSlopeIntolerance * abs(
delX[2]
)))
) {
return SWIPE_3_UP
}
if (((delY[0]) > (swipeSlopeIntolerance * abs(
delX[0]
)))
&& ((delY[1]) > (swipeSlopeIntolerance * abs(
delX[1]
)))
&& ((delY[2]) > (swipeSlopeIntolerance * abs(
delX[2]
)))
) {
return SWIPE_3_DOWN
}
if (((-delX[0]) > (swipeSlopeIntolerance * abs(
delY[0]
)))
&& ((-delX[1]) > (swipeSlopeIntolerance * abs(
delY[1]
)))
&& ((-delX[2]) > (swipeSlopeIntolerance * abs(
delY[2]
)))
) {
return SWIPE_3_LEFT
}
if (((delX[0]) > (swipeSlopeIntolerance * abs(
delY[0]
)))
&& ((delX[1]) > (swipeSlopeIntolerance * abs(
delY[1]
)))
&& ((delX[2]) > (swipeSlopeIntolerance * abs(
delY[2]
)))
) {
return SWIPE_3_RIGHT
}
if ((finalFingDist(0, 1) > 1.75 * (initialFingDist(0, 1)))
&& (finalFingDist(1, 2) > 1.75 * (initialFingDist(1, 2)))
&& (finalFingDist(2, 0) > 1.75 * (initialFingDist(2, 0)))
) {
return UNPINCH_3
}
if ((finalFingDist(0, 1) < 0.66 * (initialFingDist(0, 1)))
&& (finalFingDist(1, 2) < 0.66 * (initialFingDist(1, 2)))
&& (finalFingDist(2, 0) < 0.66 * (initialFingDist(2, 0)))
) {
return PINCH_3
}
}
if (numFingers == 4) {
if (((-delY[0]) > (swipeSlopeIntolerance * abs(
delX[0]
)))
&& ((-delY[1]) > (swipeSlopeIntolerance * abs(
delX[1]
)))
&& ((-delY[2]) > (swipeSlopeIntolerance * abs(
delX[2]
)))
&& ((-delY[3]) > (swipeSlopeIntolerance * abs(
delX[3]
)))
) {
return SWIPE_4_UP
}
if (((delY[0]) > (swipeSlopeIntolerance * abs(
delX[0]
)))
&& ((delY[1]) > (swipeSlopeIntolerance * abs(
delX[1]
)))
&& ((delY[2]) > (swipeSlopeIntolerance * abs(
delX[2]
)))
&& ((delY[3]) > (swipeSlopeIntolerance * abs(
delX[3]
)))
) {
return SWIPE_4_DOWN
}
if (((-delX[0]) > (swipeSlopeIntolerance * abs(
delY[0]
)))
&& ((-delX[1]) > (swipeSlopeIntolerance * abs(
delY[1]
)))
&& ((-delX[2]) > (swipeSlopeIntolerance * abs(
delY[2]
)))
&& ((-delX[3]) > (swipeSlopeIntolerance * abs(
delY[3]
)))
) {
return SWIPE_4_LEFT
}
if (((delX[0]) > (swipeSlopeIntolerance * abs(
delY[0]
)))
&& ((delX[1]) > (swipeSlopeIntolerance * abs(
delY[1]
)))
&& ((delX[2]) > (swipeSlopeIntolerance * abs(
delY[2]
)))
&& ((delX[3]) > (swipeSlopeIntolerance * abs(
delY[3]
)))
) {
return SWIPE_4_RIGHT
}
if ((finalFingDist(0, 1) > 1.5 * (initialFingDist(0, 1)))
&& (finalFingDist(1, 2) > 1.5 * (initialFingDist(1, 2)))
&& (finalFingDist(2, 3) > 1.5 * (initialFingDist(2, 3)))
&& (finalFingDist(3, 0) > 1.5 * (initialFingDist(3, 0)))
) {
return UNPINCH_4
}
if ((finalFingDist(0, 1) < 0.8 * (initialFingDist(0, 1)))
&& (finalFingDist(1, 2) < 0.8 * (initialFingDist(1, 2)))
&& (finalFingDist(2, 3) < 0.8 * (initialFingDist(2, 3)))
&& (finalFingDist(3, 0) < 0.8 * (initialFingDist(3, 0)))
) {
return PINCH_4
}
}
return 0
}
private fun initialFingDist(fingNum1: Int, fingNum2: Int): Double {
return sqrt(
(initialX[fingNum1] - initialX[fingNum2]).pow(2.0) + (initialY[fingNum1] - initialY[fingNum2]).pow(
2.0
)
)
}
private fun finalFingDist(fingNum1: Int, fingNum2: Int): Double {
return sqrt(
(finalX[fingNum1] - finalX[fingNum2]).pow(2.0) + (finalY[fingNum1] - finalY[fingNum2]).pow(
2.0
)
)
}
val isDoubleTap: Boolean
get() = if (initialT - prevFinalT < doubleTapMaxDelayMillis && finalT - initialT < doubleTapMaxDownMillis && prevFinalT - prevInitialT < doubleTapMaxDownMillis) {
true
} else {
false
}
inner class GestureType {
var gestureFlag: Int = 0
var gestureDuration: Long = 0
var gestureDistance: Double = 0.0
}
companion object {
const val DEBUG: Boolean = true
// Finished gestures flags
const val SWIPE_1_UP: Int = 11
const val SWIPE_1_DOWN: Int = 12
const val SWIPE_1_LEFT: Int = 13
const val SWIPE_1_RIGHT: Int = 14
const val SWIPE_2_UP: Int = 21
const val SWIPE_2_DOWN: Int = 22
const val SWIPE_2_LEFT: Int = 23
const val SWIPE_2_RIGHT: Int = 24
const val SWIPE_3_UP: Int = 31
const val SWIPE_3_DOWN: Int = 32
const val SWIPE_3_LEFT: Int = 33
const val SWIPE_3_RIGHT: Int = 34
const val SWIPE_4_UP: Int = 41
const val SWIPE_4_DOWN: Int = 42
const val SWIPE_4_LEFT: Int = 43
const val SWIPE_4_RIGHT: Int = 44
const val PINCH_2: Int = 25
const val UNPINCH_2: Int = 26
const val PINCH_3: Int = 35
const val UNPINCH_3: Int = 36
const val PINCH_4: Int = 45
const val UNPINCH_4: Int = 46
const val DOUBLE_TAP_1: Int = 107
//Ongoing gesture flags
const val SWIPING_1_UP: Int = 101
const val SWIPING_1_DOWN: Int = 102
const val SWIPING_1_LEFT: Int = 103
const val SWIPING_1_RIGHT: Int = 104
const val SWIPING_2_UP: Int = 201
const val SWIPING_2_DOWN: Int = 202
const val SWIPING_2_LEFT: Int = 203
const val SWIPING_2_RIGHT: Int = 204
const val PINCHING: Int = 205
const val UNPINCHING: Int = 206
private const val TAG = "GestureAnalyser"
}
}
class SimpleFingerGestures : OnTouchListener {
private var debug = true
var consumeTouchEvents: Boolean = false
protected var tracking: BooleanArray = booleanArrayOf(false, false, false, false, false)
private var ga: GestureAnalyser
private var onFingerGestureListener: OnFingerGestureListener? = null
/**
* Constructor that creates an internal [in.championswimmer.sfg.lib.GestureAnalyser] object as well
*/
constructor() {
ga = GestureAnalyser()
}
constructor(
swipeSlopeIntolerance: Int,
doubleTapMaxDelayMillis: Int,
doubleTapMaxDownMillis: Int
) {
ga = GestureAnalyser(swipeSlopeIntolerance, doubleTapMaxDelayMillis, doubleTapMaxDownMillis)
}
fun setDebug(debug: Boolean) {
this.debug = debug
}
constructor(omfgl: OnFingerGestureListener?) {
ga = GestureAnalyser()
setOnFingerGestureListener(omfgl)
}
/**
* Register a callback to be invoked when multi-finger gestures take place
*
*
* <br></br>
*
*
* For the callbacks implemented via this, check the interface [in.championswimmer.sfg.lib.SimpleFingerGestures.OnFingerGestureListener]
*
*
* @param omfgl The callback that will run
*/
fun setOnFingerGestureListener(omfgl: OnFingerGestureListener?) {
onFingerGestureListener = omfgl
}
override fun onTouch(view: View, ev: MotionEvent): Boolean {
if (debug) Log.d(TAG, "onTouch")
when (ev.action and MotionEvent.ACTION_MASK) {
MotionEvent.ACTION_DOWN -> {
if (debug) Log.d(TAG, "ACTION_DOWN")
startTracking(0)
ga.trackGesture(ev)
return consumeTouchEvents
}
MotionEvent.ACTION_UP -> {
if (debug) Log.d(TAG, "ACTION_UP")
if (tracking[0]) {
doCallBack(ga.getGesture(ev))
}
stopTracking(0)
ga.untrackGesture()
return consumeTouchEvents
}
MotionEvent.ACTION_POINTER_DOWN -> {
if (debug) Log.d(TAG, "ACTION_POINTER_DOWN" + " " + "num" + ev.pointerCount)
startTracking(ev.pointerCount - 1)
ga.trackGesture(ev)
return consumeTouchEvents
}
MotionEvent.ACTION_POINTER_UP -> {
if (debug) Log.d(TAG, "ACTION_POINTER_UP" + " " + "num" + ev.pointerCount)
if (tracking[1]) {
doCallBack(ga.getGesture(ev))
}
stopTracking(ev.pointerCount - 1)
ga.untrackGesture()
return consumeTouchEvents
}
MotionEvent.ACTION_CANCEL -> {
if (debug) Log.d(TAG, "ACTION_CANCEL")
return true
}
MotionEvent.ACTION_MOVE -> {
if (debug) Log.d(TAG, "ACTION_MOVE")
return consumeTouchEvents
}
}
return consumeTouchEvents
}
private fun doCallBack(mGt: GestureAnalyser.GestureType) {
when (mGt.gestureFlag) {
GestureAnalyser.SWIPE_1_UP -> onFingerGestureListener!!.onSwipeUp(
1,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.SWIPE_1_DOWN -> onFingerGestureListener!!.onSwipeDown(
1,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.SWIPE_1_LEFT -> onFingerGestureListener!!.onSwipeLeft(
1,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.SWIPE_1_RIGHT -> onFingerGestureListener!!.onSwipeRight(
1,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.SWIPE_2_UP -> onFingerGestureListener!!.onSwipeUp(
2,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.SWIPE_2_DOWN -> onFingerGestureListener!!.onSwipeDown(
2,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.SWIPE_2_LEFT -> onFingerGestureListener!!.onSwipeLeft(
2,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.SWIPE_2_RIGHT -> onFingerGestureListener!!.onSwipeRight(
2,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.PINCH_2 -> onFingerGestureListener!!.onPinch(
2,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.UNPINCH_2 -> onFingerGestureListener!!.onUnpinch(
2,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.SWIPE_3_UP -> onFingerGestureListener!!.onSwipeUp(
3,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.SWIPE_3_DOWN -> onFingerGestureListener!!.onSwipeDown(
3,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.SWIPE_3_LEFT -> onFingerGestureListener!!.onSwipeLeft(
3,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.SWIPE_3_RIGHT -> onFingerGestureListener!!.onSwipeRight(
3,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.PINCH_3 -> onFingerGestureListener!!.onPinch(
3,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.UNPINCH_3 -> onFingerGestureListener!!.onUnpinch(
3,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.SWIPE_4_UP -> onFingerGestureListener!!.onSwipeUp(
4,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.SWIPE_4_DOWN -> onFingerGestureListener!!.onSwipeDown(
4,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.SWIPE_4_LEFT -> onFingerGestureListener!!.onSwipeLeft(
4,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.SWIPE_4_RIGHT -> onFingerGestureListener!!.onSwipeRight(
4,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.PINCH_4 -> onFingerGestureListener!!.onPinch(
4,
mGt.gestureDuration,
mGt.gestureDistance
)
GestureAnalyser.UNPINCH_4 -> {
onFingerGestureListener!!.onUnpinch(4, mGt.gestureDuration, mGt.gestureDistance)
onFingerGestureListener!!.onDoubleTap(1)
}
GestureAnalyser.DOUBLE_TAP_1 -> onFingerGestureListener!!.onDoubleTap(1)
}
}
private fun startTracking(nthPointer: Int) {
for (i in 0..nthPointer) {
tracking[i] = true
}
}
private fun stopTracking(nthPointer: Int) {
for (i in nthPointer until tracking.size) {
tracking[i] = false
}
}
/**
* Interface definition for the callback to be invoked when 2-finger gestures are performed
*/
interface OnFingerGestureListener {
/**
* Called when user swipes **up** with two fingers
*
* @param fingers number of fingers involved in this gesture
* @param gestureDuration duration in milliSeconds
* @return
*/
fun onSwipeUp(fingers: Int, gestureDuration: Long, gestureDistance: Double): Boolean
/**
* Called when user swipes **down** with two fingers
*
* @param fingers number of fingers involved in this gesture
* @param gestureDuration duration in milliSeconds
* @return
*/
fun onSwipeDown(fingers: Int, gestureDuration: Long, gestureDistance: Double): Boolean
/**
* Called when user swipes **left** with two fingers
*
* @param fingers number of fingers involved in this gesture
* @param gestureDuration duration in milliSeconds
* @return
*/
fun onSwipeLeft(fingers: Int, gestureDuration: Long, gestureDistance: Double): Boolean
/**
* Called when user swipes **right** with two fingers
*
* @param fingers number of fingers involved in this gesture
* @param gestureDuration duration in milliSeconds
* @return
*/
fun onSwipeRight(fingers: Int, gestureDuration: Long, gestureDistance: Double): Boolean
/**
* Called when user **pinches** with two fingers (bring together)
*
* @param fingers number of fingers involved in this gesture
* @param gestureDuration duration in milliSeconds
* @return
*/
fun onPinch(fingers: Int, gestureDuration: Long, gestureDistance: Double): Boolean
/**
* Called when user **un-pinches** with two fingers (take apart)
*
* @param fingers number of fingers involved in this gesture
* @param gestureDuration duration in milliSeconds
* @return
*/
fun onUnpinch(fingers: Int, gestureDuration: Long, gestureDistance: Double): Boolean
fun onDoubleTap(fingers: Int): Boolean
}
companion object {
// Will see if these need to be used. For now just returning duration in milliS
const val GESTURE_SPEED_SLOW: Long = 1500
const val GESTURE_SPEED_MEDIUM: Long = 1000
const val GESTURE_SPEED_FAST: Long = 500
private const val TAG = "SimpleFingerGestures"
}
}

View File

@ -43,16 +43,9 @@ internal class BatteryReceiver(private val progressBar: CircularProgressIndicato
override fun onReceive(context: Context?, intent: Intent?) {
val animationDuration = try {
intent?.extras?.keySet()?.let {
it.iterator().forEach {
BLog.LOGE("intent key >> ${it}")
}
}
BLog.LOGE("intent >> ${intent}")
Settings.Global.getFloat(context?.contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f)
} catch (e: Settings.SettingNotFoundException) {
e.printStackTrace()
// e.printStackTrace()
}
/* set battery percentage value to the circular progress bar */

View File

@ -53,6 +53,7 @@ import rasel.lunar.launcher.qaccess.QuickAccess
import rasel.lunar.launcher.settings.SettingsActivity
import rasel.lunar.launcher.todos.TodoAdapter
import rasel.lunar.launcher.todos.TodoManager
import rasel.lunar.launcher.utils.BLog
import rasel.lunar.launcher.utils.SimpleFingerGestures
import java.util.*
@ -216,7 +217,7 @@ internal class LauncherHome : Fragment() {
return false
}
override fun onLongPress(targetView: View): Boolean {
override fun onLongPress(targetView: View,fingers: Int): Boolean {
if (view?.equals(binding.batteryProgress) ?: false) {
lActivity!!.startActivity(Intent(requireContext(), SettingsActivity::class.java))
} else if (view?.equals(binding.notes) ?: false) {
@ -238,14 +239,24 @@ internal class LauncherHome : Fragment() {
return false
}
override fun onClick(targetView: View): Boolean {
if (view?.equals(binding.batteryProgress) ?: false) {
requireContext().startActivity(
Intent(AlarmClock.ACTION_SHOW_ALARMS).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
} else if (view?.equals(binding.batteryProgress) ?: false) {
override fun onClick(targetView: View,fingers: Int): Boolean {
BLog.LOGE("onClick ${view} , fingers ${fingers}")
when(fingers) {
1 -> {
if (view?.equals(binding.batteryProgress) ?: false && fingers == 1) {
requireContext().startActivity(
Intent(AlarmClock.ACTION_SHOW_ALARMS).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
}
}
2-> {
lockMethod(settingsPrefs.getInt(KEY_LOCK_METHOD, 0), requireContext(), binding.favAppsGroup)
}
else -> {
}
}
return false
}

View File

@ -37,6 +37,7 @@ internal class WeatherExecutor(sharedPreferences: SharedPreferences) {
companion object {
var lastedCheckTime = 0L
var weather: Weather? = null
}
private val cityName: String
@ -53,7 +54,6 @@ internal class WeatherExecutor(sharedPreferences: SharedPreferences) {
if (System.currentTimeMillis() - lastedCheckTime > (1000 * 60 * 15) && isNetworkAvailable && cityName.isNotEmpty() && owmApi.isNotEmpty()) {
try {
Executors.newSingleThreadExecutor().execute {
var weather: Weather? = null
WeatherClient().fetchWeather(weatherUrl).let {
if (!it.isNullOrEmpty()) weather = JsonParser().getMyWeather(it)
}
@ -72,6 +72,17 @@ internal class WeatherExecutor(sharedPreferences: SharedPreferences) {
} catch (exception: Exception) {
exception.printStackTrace()
}
} else {
Handler(Looper.getMainLooper()).post {
if (weather != null) {
materialTextView.apply {
visibility = View.VISIBLE
text = weather!!.temperature.toString().substringBefore(".") +
(if (tempUnit == 0) "ºC" else "ºF") +
(if (showCity) " at ${weather!!.cityName}" else "")
}
}
}
}
lastedCheckTime = System.currentTimeMillis()
}

View File

@ -0,0 +1,78 @@
//package com.example.ch16_provider
//
//import android.content.Intent
//import android.content.pm.PackageManager
//import android.os.Bundle
//import android.provider.ContactsContract
//import android.util.Log
//import androidx.activity.result.ActivityResultLauncher
//import androidx.activity.result.contract.ActivityResultContracts
//import androidx.appcompat.app.AppCompatActivity
//import androidx.core.app.ActivityCompat
//import androidx.core.content.ContextCompat
//import com.example.ch16_provider.databinding.ActivityMainBinding
//
//class MainActivity : AppCompatActivity() {
// lateinit var binding: ActivityMainBinding
// lateinit var requestLauncher: ActivityResultLauncher<Intent>
//
// override fun onCreate(savedInstanceState: Bundle?) {
// super.onCreate(savedInstanceState)
// binding = ActivityMainBinding.inflate(layoutInflater)
// setContentView(binding.root)
//
// // 퍼미션 허용했는지 확인
// val status = ContextCompat.checkSelfPermission(this, "android.permission.READ_CONTACTS")
// if (status == PackageManager.PERMISSION_GRANTED) {
// Log.d("test", "permission granted")
// } else {
// // 퍼미션 요청 다이얼로그 표시
// ActivityCompat.requestPermissions(this, arrayOf<String>("android.permission.READ_CONTACTS"), 100)
// Log.d("test", "permission denied")
// }
//
// // ActivityResultLauncher 초기화, 결과 콜백 정의
// requestLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
// if (it.resultCode == RESULT_OK) {
// Log.d("test", "Uri : ${it.data!!.data!!}")
// val cursor = contentResolver.query(
// it.data!!.data!!,
// arrayOf<String>(
// ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
// ContactsContract.CommonDataKinds.Phone.NUMBER,
// ),
// null,
// null,
// null
// )
// Log.d("test", "cursor size : ${cursor?.count}")
//
// if (cursor!!.moveToFirst()) {
// val name = cursor.getString(0)
// val phone = cursor.getString(1)
// binding.textView.text = "name: $name, phone: $phone"
// }
// }
// }
//
// binding.button.setOnClickListener {
// // 주소록 앱 연동
// val intent = Intent(Intent.ACTION_PICK, ContactsContract.CommonDataKinds.Phone.CONTENT_URI)
// requestLauncher.launch(intent)
// }
// }
//
// // 다이얼로그에서 퍼미션 허용했는지 확인
// override fun onRequestPermissionsResult(
// requestCode: Int,
// permissions: Array<out String>,
// grantResults: IntArray
// ) {
// super.onRequestPermissionsResult(requestCode, permissions, grantResults)
// if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Log.d("test", "permission granted")
// } else {
// Log.d("test", "permission denied")
// }
// }
//}

View File

@ -133,7 +133,7 @@ class GestureAnalyser @JvmOverloads constructor(
BLog.LOGE("initialT = ${initialT} , finalT = ${finalT} , result = ${finalT - initialT}")
if (finalT - initialT < 300) {
return CLICK_1
} else if(finalT - initialT > 600) {
} else if(finalT - initialT > doubleTapMaxDelayMillis) {
return LONG_CLICK_1
}
}
@ -176,6 +176,11 @@ class GestureAnalyser @JvmOverloads constructor(
if (finalFingDist(0, 1) < 0.5 * (initialFingDist(0, 1))) {
return PINCH_2
}
if (finalT - initialT < 300) {
return CLICK_2
} else if(finalT - initialT > doubleTapMaxDelayMillis) {
return LONG_CLICK_2
}
}
if (numFingers == 3) {
if (((-delY[0]) > (swipeSlopeIntolerance * abs(
@ -186,7 +191,7 @@ class GestureAnalyser @JvmOverloads constructor(
)))
&& ((-delY[2]) > (swipeSlopeIntolerance * abs(
delX[2]
)))
))) && (abs(delY[0]) > minValue || abs(delY[1]) > minValue && abs(delY[2]) > minValue)
) {
return SWIPE_3_UP
}
@ -198,7 +203,7 @@ class GestureAnalyser @JvmOverloads constructor(
)))
&& ((delY[2]) > (swipeSlopeIntolerance * abs(
delX[2]
)))
))) && (abs(delY[0]) > minValue || abs(delY[1]) > minValue && abs(delY[2]) > minValue)
) {
return SWIPE_3_DOWN
}
@ -210,7 +215,7 @@ class GestureAnalyser @JvmOverloads constructor(
)))
&& ((-delX[2]) > (swipeSlopeIntolerance * abs(
delY[2]
)))
))) && (abs(delX[0]) > minValue || abs(delX[1]) > minValue && abs(delX[2]) > minValue)
) {
return SWIPE_3_LEFT
}
@ -222,7 +227,7 @@ class GestureAnalyser @JvmOverloads constructor(
)))
&& ((delX[2]) > (swipeSlopeIntolerance * abs(
delY[2]
)))
))) && (abs(delX[0]) > minValue || abs(delX[1]) > minValue && abs(delX[2]) > minValue)
) {
return SWIPE_3_RIGHT
}
@ -239,6 +244,12 @@ class GestureAnalyser @JvmOverloads constructor(
) {
return PINCH_3
}
if (finalT - initialT < 300) {
return CLICK_3
} else if(finalT - initialT > doubleTapMaxDelayMillis) {
return LONG_CLICK_3
}
}
if (numFingers == 4) {
if (((-delY[0]) > (swipeSlopeIntolerance * abs(
@ -424,8 +435,8 @@ class SimpleFingerGestures : OnTouchListener {
ga.minValue = Math.max(screenHeight, 100)
}
this.onFingerGestureListener = onFingerGestureListener
this.targetView?.setOnClickListener { onFingerGestureListener.onClick(it) }
this.targetView?.setOnLongClickListener { onFingerGestureListener.onLongPress(it) }
this.targetView?.setOnClickListener { onFingerGestureListener.onClick(it,1) }
this.targetView?.setOnLongClickListener { onFingerGestureListener.onLongPress(it,1) }
}
/**
* Constructor that creates an internal [in.championswimmer.sfg.lib.GestureAnalyser] object as well
@ -673,7 +684,17 @@ class SimpleFingerGestures : OnTouchListener {
}
GestureAnalyser.CLICK_1 -> {
BLog.LOGE("GestureAnalyser.CLICK_1")
onFingerGestureListener!!.onClick(targetView)
onFingerGestureListener!!.onClick(targetView, 1)
// onFingerGestureListener!!.onDoubleTap(1)
}
GestureAnalyser.CLICK_2 -> {
BLog.LOGE("GestureAnalyser.CLICK_2")
onFingerGestureListener!!.onClick(targetView, 2)
// onFingerGestureListener!!.onDoubleTap(1)
}
GestureAnalyser.CLICK_3 -> {
BLog.LOGE("GestureAnalyser.CLICK_3")
onFingerGestureListener!!.onClick(targetView, 3)
// onFingerGestureListener!!.onDoubleTap(1)
}
// GestureAnalyser.CLICK_2 -> {
@ -687,9 +708,16 @@ class SimpleFingerGestures : OnTouchListener {
GestureAnalyser.LONG_CLICK_1 -> {
BLog.LOGE("GestureAnalyser.LONG_CLICK_1")
onFingerGestureListener!!.onLongPress(targetView)
onFingerGestureListener!!.onLongPress(targetView,1)
}
GestureAnalyser.LONG_CLICK_2 -> {
BLog.LOGE("GestureAnalyser.LONG_CLICK_2")
onFingerGestureListener!!.onLongPress(targetView,2)
}
GestureAnalyser.LONG_CLICK_3 -> {
BLog.LOGE("GestureAnalyser.LONG_CLICK_3")
onFingerGestureListener!!.onLongPress(targetView,3)
}
GestureAnalyser.DOUBLE_TAP_1 -> onFingerGestureListener!!.onDoubleTap(targetView,1)
}
@ -767,8 +795,8 @@ class SimpleFingerGestures : OnTouchListener {
fun onUnpinch(targetView : View,fingers: Int, gestureDuration: Long, gestureDistance: Double): Boolean
fun onDoubleTap(targetView : View,fingers: Int): Boolean
fun onLongPress(targetView : View): Boolean
fun onClick(targetView : View): Boolean
fun onLongPress(targetView : View, fingers: Int): Boolean
fun onClick(targetView : View, fingers: Int): Boolean
}
companion object {
@ -779,3 +807,4 @@ class SimpleFingerGestures : OnTouchListener {
private const val TAG = "SimpleFingerGestures"
}
}

View File

@ -0,0 +1,500 @@
package rasel.lunar.launcher.view
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapShader
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.ColorFilter
import android.graphics.Matrix
import android.graphics.Outline
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.RectF
import android.graphics.Shader
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Build
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.ViewOutlineProvider
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.annotation.NonNull
import androidx.annotation.RequiresApi
import rasel.lunar.launcher.R
import kotlin.math.min
import kotlin.math.pow
class CircleImageView : androidx.appcompat.widget.AppCompatImageView {
private val mDrawableRect = RectF()
private val mBorderRect = RectF()
private val mShaderMatrix: Matrix = Matrix()
private val mBitmapPaint: Paint = Paint()
private val mBorderPaint: Paint = Paint()
private val mCircleBackgroundPaint: Paint = Paint()
private var mBorderColor = DEFAULT_BORDER_COLOR
private var mBorderWidth = DEFAULT_BORDER_WIDTH
private var mCircleBackgroundColor = DEFAULT_CIRCLE_BACKGROUND_COLOR
private var mImageAlpha = DEFAULT_IMAGE_ALPHA
private var mBitmap: Bitmap? = null
private var mBitmapCanvas: Canvas? = null
private var mDrawableRadius = 0f
private var mBorderRadius = 0f
private var mColorFilter: ColorFilter? = null
private var mInitialized = false
private var mRebuildShader = false
private var mDrawableDirty = false
private var mBorderOverlay = false
private var mDisableCircularTransformation = false
constructor(context: Context) : super(context) {
init()
}
@JvmOverloads
constructor(context: Context, attrs: AttributeSet?, defStyle: Int = 0) : super(
context,
attrs,
defStyle
) {
val a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0)
mBorderWidth = a.getDimensionPixelSize(
R.styleable.CircleImageView_civ_border_width,
DEFAULT_BORDER_WIDTH
)
mBorderColor =
a.getColor(R.styleable.CircleImageView_civ_border_color, DEFAULT_BORDER_COLOR)
mBorderOverlay =
a.getBoolean(R.styleable.CircleImageView_civ_border_overlay, DEFAULT_BORDER_OVERLAY)
mCircleBackgroundColor = a.getColor(
R.styleable.CircleImageView_civ_circle_background_color,
DEFAULT_CIRCLE_BACKGROUND_COLOR
)
a.recycle()
init()
}
private fun init() {
mInitialized = true
super.setScaleType(SCALE_TYPE)
mBitmapPaint.setAntiAlias(true)
mBitmapPaint.setDither(true)
mBitmapPaint.setFilterBitmap(true)
mBitmapPaint.setAlpha(mImageAlpha)
mBitmapPaint.setColorFilter(mColorFilter)
mBorderPaint.setStyle(Paint.Style.STROKE)
mBorderPaint.setAntiAlias(true)
mBorderPaint.setColor(mBorderColor)
mBorderPaint.setStrokeWidth(mBorderWidth.toFloat())
mCircleBackgroundPaint.setStyle(Paint.Style.FILL)
mCircleBackgroundPaint.setAntiAlias(true)
mCircleBackgroundPaint.setColor(mCircleBackgroundColor)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
outlineProvider = OutlineProvider()
}
}
override fun setScaleType(scaleType: ScaleType) {
// require(scaleType == SCALE_TYPE) { String.format("ScaleType %s not supported.", scaleType) }
super.setScaleType(scaleType)
}
override fun setAdjustViewBounds(adjustViewBounds: Boolean) {
// require(!adjustViewBounds) { "adjustViewBounds not supported." }
super.setAdjustViewBounds(adjustViewBounds)
}
@SuppressLint("CanvasSize")
override fun onDraw(canvas: Canvas) {
if (mDisableCircularTransformation) {
super.onDraw(canvas)
return
}
if (mCircleBackgroundColor != Color.TRANSPARENT) {
canvas.drawCircle(
mDrawableRect.centerX(),
mDrawableRect.centerY(),
mDrawableRadius,
mCircleBackgroundPaint
)
}
if (mBitmap != null) {
if (mDrawableDirty && mBitmapCanvas != null) {
mDrawableDirty = false
val drawable = drawable
drawable.setBounds(0, 0, mBitmapCanvas!!.getWidth(), mBitmapCanvas!!.getHeight())
drawable.draw(mBitmapCanvas!!)
}
if (mRebuildShader) {
mRebuildShader = false
val bitmapShader =
BitmapShader(mBitmap!!, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
bitmapShader.setLocalMatrix(mShaderMatrix)
mBitmapPaint.setShader(bitmapShader)
}
canvas.drawCircle(
mDrawableRect.centerX(),
mDrawableRect.centerY(),
mDrawableRadius,
mBitmapPaint
)
}
if (mBorderWidth > 0) {
canvas.drawCircle(
mBorderRect.centerX(),
mBorderRect.centerY(),
mBorderRadius,
mBorderPaint
)
}
}
override fun invalidateDrawable(@NonNull dr: Drawable) {
mDrawableDirty = true
invalidate()
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
updateDimensions()
invalidate()
}
override fun setPadding(left: Int, top: Int, right: Int, bottom: Int) {
super.setPadding(left, top, right, bottom)
updateDimensions()
invalidate()
}
override fun setPaddingRelative(start: Int, top: Int, end: Int, bottom: Int) {
super.setPaddingRelative(start, top, end, bottom)
updateDimensions()
invalidate()
}
var borderColor: Int
get() = mBorderColor
set(borderColor) {
if (borderColor == mBorderColor) {
return
}
mBorderColor = borderColor
mBorderPaint.setColor(borderColor)
invalidate()
}
var circleBackgroundColor: Int
get() = mCircleBackgroundColor
set(circleBackgroundColor) {
if (circleBackgroundColor == mCircleBackgroundColor) {
return
}
mCircleBackgroundColor = circleBackgroundColor
mCircleBackgroundPaint.setColor(circleBackgroundColor)
invalidate()
}
@Deprecated("Use {@link #setCircleBackgroundColor(int)} instead")
fun setCircleBackgroundColorResource(@ColorRes circleBackgroundRes: Int) {
circleBackgroundColor = context.resources.getColor(circleBackgroundRes)
}
var borderWidth: Int
get() = mBorderWidth
set(borderWidth) {
if (borderWidth == mBorderWidth) {
return
}
mBorderWidth = borderWidth
mBorderPaint.setStrokeWidth(borderWidth.toFloat())
updateDimensions()
invalidate()
}
var isBorderOverlay: Boolean
get() = mBorderOverlay
set(borderOverlay) {
if (borderOverlay == mBorderOverlay) {
return
}
mBorderOverlay = borderOverlay
updateDimensions()
invalidate()
}
var isDisableCircularTransformation: Boolean
get() = mDisableCircularTransformation
set(disableCircularTransformation) {
if (disableCircularTransformation == mDisableCircularTransformation) {
return
}
mDisableCircularTransformation = disableCircularTransformation
if (disableCircularTransformation) {
mBitmap = null
mBitmapCanvas = null
mBitmapPaint.setShader(null)
} else {
initializeBitmap()
}
invalidate()
}
override fun setImageBitmap(bm: Bitmap) {
super.setImageBitmap(bm)
initializeBitmap()
invalidate()
}
override fun setImageDrawable(drawable: Drawable?) {
super.setImageDrawable(drawable)
initializeBitmap()
invalidate()
}
override fun setImageResource(@DrawableRes resId: Int) {
super.setImageResource(resId)
initializeBitmap()
invalidate()
}
override fun setImageURI(uri: Uri?) {
super.setImageURI(uri)
initializeBitmap()
invalidate()
}
override fun setImageAlpha(alpha: Int) {
var alpha = alpha
alpha = alpha and 0xFF
if (alpha == mImageAlpha) {
return
}
mImageAlpha = alpha
// This might be called during ImageView construction before
// member initialization has finished on API level >= 16.
if (mInitialized) {
mBitmapPaint.setAlpha(alpha)
invalidate()
}
}
override fun getImageAlpha(): Int {
return mImageAlpha
}
override fun setColorFilter(cf: ColorFilter) {
if (cf === mColorFilter) {
return
}
mColorFilter = cf
// This might be called during ImageView construction before
// member initialization has finished on API level <= 19.
if (mInitialized) {
mBitmapPaint.setColorFilter(cf)
invalidate()
}
}
override fun getColorFilter(): ColorFilter {
return mColorFilter ?: ColorFilter()
}
private fun getBitmapFromDrawable(drawable: Drawable?): Bitmap? {
if (drawable == null) {
return null
}
if (drawable is BitmapDrawable) {
return drawable.bitmap
}
try {
val bitmap = if (drawable is ColorDrawable) {
Bitmap.createBitmap(COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG)
} else {
Bitmap.createBitmap(
drawable.intrinsicWidth,
drawable.intrinsicHeight,
BITMAP_CONFIG
)
}
val canvas: Canvas = Canvas(bitmap)
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight())
drawable.draw(canvas)
return bitmap
} catch (e: java.lang.Exception) {
e.printStackTrace()
return null
}
}
private fun initializeBitmap() {
mBitmap = getBitmapFromDrawable(drawable)
if (mBitmap != null && mBitmap!!.isMutable) {
mBitmapCanvas = Canvas(mBitmap!!)
} else {
mBitmapCanvas = null
}
if (!mInitialized) {
return
}
if (mBitmap != null) {
updateShaderMatrix()
} else {
mBitmapPaint.setShader(null)
}
}
private fun updateDimensions() {
mBorderRect.set(calculateBounds())
mBorderRadius = min(
((mBorderRect.height() - mBorderWidth) / 2.0f).toDouble(),
((mBorderRect.width() - mBorderWidth) / 2.0f).toDouble()
)
.toFloat()
mDrawableRect.set(mBorderRect)
if (!mBorderOverlay && mBorderWidth > 0) {
mDrawableRect.inset(mBorderWidth - 1.0f, mBorderWidth - 1.0f)
}
mDrawableRadius = min(
(mDrawableRect.height() / 2.0f).toDouble(),
(mDrawableRect.width() / 2.0f).toDouble()
)
.toFloat()
updateShaderMatrix()
}
private fun calculateBounds(): RectF {
val availableWidth = width - paddingLeft - paddingRight
val availableHeight = height - paddingTop - paddingBottom
val sideLength =
min(availableWidth.toDouble(), availableHeight.toDouble()).toInt()
val left = paddingLeft + (availableWidth - sideLength) / 2f
val top = paddingTop + (availableHeight - sideLength) / 2f
return RectF(left, top, left + sideLength, top + sideLength)
}
private fun updateShaderMatrix() {
if (mBitmap == null) {
return
}
val scale: Float
var dx = 0f
var dy = 0f
mShaderMatrix.set(null)
val bitmapHeight = mBitmap!!.height
val bitmapWidth = mBitmap!!.width
if (bitmapWidth * mDrawableRect.height() > mDrawableRect.width() * bitmapHeight) {
scale = mDrawableRect.height() / bitmapHeight.toFloat()
dx = (mDrawableRect.width() - bitmapWidth * scale) * 0.5f
} else {
scale = mDrawableRect.width() / bitmapWidth.toFloat()
dy = (mDrawableRect.height() - bitmapHeight * scale) * 0.5f
}
mShaderMatrix.setScale(scale, scale)
mShaderMatrix.postTranslate(
(dx + 0.5f).toInt() + mDrawableRect.left,
(dy + 0.5f).toInt() + mDrawableRect.top
)
mRebuildShader = true
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
if (mDisableCircularTransformation) {
return super.onTouchEvent(event)
}
return inTouchableArea(event.x, event.y) && super.onTouchEvent(event)
}
private fun inTouchableArea(x: Float, y: Float): Boolean {
if (mBorderRect.isEmpty) {
return true
}
return (x - mBorderRect.centerX()).pow(2.0F) + (y - mBorderRect.centerY()).pow(2.0F) <= mBorderRadius.pow(2.0F)
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private inner class OutlineProvider : ViewOutlineProvider() {
override fun getOutline(view: View?, outline: Outline) {
if (mDisableCircularTransformation) {
BACKGROUND.getOutline(view, outline)
} else {
val bounds: Rect = Rect()
mBorderRect.roundOut(bounds)
outline.setRoundRect(bounds, bounds.width() / 2.0f)
}
}
}
companion object {
private val SCALE_TYPE = ScaleType.CENTER_CROP
private val BITMAP_CONFIG = Bitmap.Config.ARGB_8888
private const val COLORDRAWABLE_DIMENSION = 2
private const val DEFAULT_BORDER_WIDTH = 0
private val DEFAULT_BORDER_COLOR: Int = Color.BLACK
private val DEFAULT_CIRCLE_BACKGROUND_COLOR: Int = Color.TRANSPARENT
private const val DEFAULT_IMAGE_ALPHA = 255
private const val DEFAULT_BORDER_OVERLAY = false
}
}

View File

@ -0,0 +1,27 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:pathData="M44,24c0,11 -9,20 -20,20S4,35 4,24S13,4 24,4S44,13 44,24z"
android:fillColor="#ff3d00"/>
<path
android:pathData="M26,16.2c-0.6,-0.6 -1.5,-0.9 -2.5,-1.1c-0.4,-0.5 -1,-1 -1.9,-1.5c-1.6,-0.8 -3.5,-1.2 -5.3,-0.9h-0.4c-0.1,0 -0.2,0.1 -0.4,0.1c0.2,0 1,0.4 1.6,0.6c-0.3,0.2 -0.8,0.2 -1.1,0.4c0,0 0,0 -0.1,0L15.7,14c-0.1,0.2 -0.2,0.4 -0.2,0.5c1.3,-0.1 3.2,0 4.6,0.4C19,15 18,15.3 17.3,15.7c-0.5,0.3 -1,0.6 -1.3,1.1c-1.2,1.3 -1.7,3.5 -1.3,5.9c0.5,2.7 2.4,11.4 3.4,16.3l0.3,1.6c0,0 3.5,0.4 5.6,0.4c1.2,0 3.2,0.3 3.7,-0.2c-0.1,0 -0.6,-0.6 -0.8,-1.1c-0.5,-1 -1,-1.9 -1.4,-2.6c-1.2,-2.5 -2.5,-5.9 -1.9,-8.1c0.1,-0.4 0.1,-2.1 0.4,-2.3c2.6,-1.7 2.4,-0.1 3.5,-0.8c0.5,-0.4 1,-0.9 1.2,-1.5C29.4,22.1 27.8,18 26,16.2z"
android:fillColor="#fff"/>
<path
android:pathData="M24,42c-9.9,0 -18,-8.1 -18,-18c0,-9.9 8.1,-18 18,-18c9.9,0 18,8.1 18,18C42,33.9 33.9,42 24,42zM24,8C15.2,8 8,15.2 8,24s7.2,16 16,16s16,-7.2 16,-16S32.8,8 24,8z"
android:fillColor="#fff"/>
<path
android:pathData="M19,21.1c-0.6,0 -1.2,0.5 -1.2,1.2c0,0.6 0.5,1.2 1.2,1.2c0.6,0 1.2,-0.5 1.2,-1.2C20.1,21.7 19.6,21.1 19,21.1zM19.5,22.2c-0.2,0 -0.3,-0.1 -0.3,-0.3c0,-0.2 0.1,-0.3 0.3,-0.3s0.3,0.1 0.3,0.3C19.8,22.1 19.6,22.2 19.5,22.2zM26.8,20.6c-0.6,0 -1,0.5 -1,1c0,0.6 0.5,1 1,1c0.6,0 1,-0.5 1,-1S27.3,20.6 26.8,20.6zM27.2,21.5c-0.1,0 -0.3,-0.1 -0.3,-0.3c0,-0.1 0.1,-0.3 0.3,-0.3c0.1,0 0.3,0.1 0.3,0.3S27.4,21.5 27.2,21.5zM19.3,18.9c0,0 -0.9,-0.4 -1.7,0.1c-0.9,0.5 -0.8,1.1 -0.8,1.1s-0.5,-1 0.8,-1.5C18.7,18.1 19.3,18.9 19.3,18.9M27.4,18.8c0,0 -0.6,-0.4 -1.1,-0.4c-1,0 -1.3,0.5 -1.3,0.5s0.2,-1.1 1.5,-0.9C27.1,18.2 27.4,18.8 27.4,18.8"
android:fillColor="#0277bd"/>
<path
android:pathData="M23.3,35.7c0,0 -4.3,-2.3 -4.4,-1.4c-0.1,0.9 0,4.7 0.5,5s4.1,-1.9 4.1,-1.9L23.3,35.7zM25,35.6c0,0 2.9,-2.2 3.6,-2.1c0.6,0.1 0.8,4.7 0.2,4.9c-0.6,0.2 -3.9,-1.2 -3.9,-1.2L25,35.6z"
android:fillColor="#8bc34a"/>
<path
android:pathData="M22.5,35.7c0,1.5 -0.2,2.1 0.4,2.3c0.6,0.1 1.9,0 2.3,-0.3c0.4,-0.3 0.1,-2.2 -0.1,-2.6C25,34.8 22.5,35.1 22.5,35.7"
android:fillColor="#689f38"/>
<path
android:pathData="M22.3,26.8c0.1,-0.7 2,-2.1 3.3,-2.2c1.3,-0.1 1.7,-0.1 2.8,-0.3c1.1,-0.3 3.9,-1 4.7,-1.3c0.8,-0.4 4.1,0.2 1.8,1.5c-1,0.6 -3.7,1.6 -5.7,2.2c-1.9,0.6 -3.1,-0.6 -3.8,0.4c-0.5,0.8 -0.1,1.8 2.2,2c3.1,0.3 6.2,-1.4 6.5,-0.5c0.3,0.9 -2.7,2 -4.6,2.1c-1.8,0 -5.6,-1.2 -6.1,-1.6C22.9,28.7 22.2,27.8 22.3,26.8"
android:fillColor="#ffca28"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@ -3,43 +3,114 @@
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="match_parent">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/appsCount"
android:layout_width="0dp"
android:layout_height="0dp"
android:textColor="?attr/colorControlHighlight"
android:textSize="@dimen/appsCountText"
android:gravity="center"
<ScrollView
app:layout_constraintTop_toBottomOf="@id/searchInput"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/appsList"
android:layout_width="@dimen/zero"
android:layout_height="0dp"
android:background="@android:color/transparent"
android:fadingEdgeLength="@dimen/sixteen"
android:overScrollMode="never"
android:requiresFadingEdge="vertical"
android:scrollbars="none"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/searchInput"
app:layout_constraintEnd_toStartOf="@+id/search"
app:layout_constraintStart_toStartOf="parent" />
<!-- <rasel.lunar.launcher.apps.AlphabetScrollbar-->
<!-- android:id="@+id/alphabets"-->
<!-- android:layout_width="@dimen/zero"-->
<!-- android:visibility="gone"-->
<!-- android:layout_height="@dimen/zero"-->
<!-- android:layout_marginBottom="@dimen/four"-->
<!-- app:layout_constraintBottom_toTopOf="@+id/reset"-->
<!-- app:layout_constraintStart_toStartOf="@id/reset"-->
<!-- app:layout_constraintEnd_toEndOf="parent" />-->
android:layout_width="0dp"
android:layout_height="0dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:id="@+id/title_apps"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:text="앱 검색"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:id="@+id/appsCount"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:text="앱 검색"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<androidx.recyclerview.widget.RecyclerView
app:layout_constraintTop_toBottomOf="@id/title_apps"
android:id="@+id/appsList"
android:layout_width="match_parent"
android:layout_height="90dp"
android:background="@android:color/transparent"
android:overScrollMode="never"
android:requiresFadingEdge="horizontal"
android:scrollbars="none"
/>
<TextView
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:id="@+id/title_webs"
app:layout_constraintTop_toBottomOf="@id/appsList"
app:layout_constraintLeft_toLeftOf="parent"
android:text="빠른 검색"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<LinearLayout
android:id="@+id/quickSearch"
android:orientation="horizontal"
app:layout_constraintTop_toBottomOf="@id/title_webs"
android:layout_width="match_parent"
android:layout_height="50dp">
<rasel.lunar.launcher.view.CircleImageView
style="@style/SearchIcons"
android:src="@drawable/google"
android:id="@+id/search_google"/>
<rasel.lunar.launcher.view.CircleImageView
style="@style/SearchIcons"
android:src="@drawable/naver"
android:id="@+id/search_naver"/>
<rasel.lunar.launcher.view.CircleImageView
style="@style/SearchIcons"
android:src="@drawable/duckduckgo"
android:id="@+id/search_duckduckgo"/>
<rasel.lunar.launcher.view.CircleImageView
android:src="@drawable/namuwiki"
android:id="@+id/search_namuwiki"
style="@style/SearchIcons"/>
<rasel.lunar.launcher.view.CircleImageView
style="@style/SearchIcons"
android:src="@drawable/gmap"
android:id="@+id/search_googleMap"/>
<rasel.lunar.launcher.view.CircleImageView
style="@style/SearchIcons"
android:src="@drawable/navermap"
android:id="@+id/search_nmap"/>
<rasel.lunar.launcher.view.CircleImageView
android:src="@drawable/tmap"
style="@style/SearchIcons"
android:id="@+id/search_tmap"/>
</LinearLayout>
<TextView
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:id="@+id/title_contact"
app:layout_constraintTop_toBottomOf="@id/quickSearch"
app:layout_constraintLeft_toLeftOf="parent"
android:text="연락처"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<androidx.recyclerview.widget.RecyclerView
app:layout_constraintTop_toBottomOf="@id/title_contact"
android:id="@+id/contactList"
android:layout_width="match_parent"
android:layout_height="90dp"
android:background="@android:color/transparent"
android:overScrollMode="never"
android:requiresFadingEdge="horizontal"
android:scrollbars="none"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/reset"
@ -48,42 +119,10 @@
android:background="@drawable/rounded_bg"
android:padding="@dimen/eight"
android:layout_marginBottom="@dimen/four"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@+id/moveUp"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_refresh" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/moveUp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/rounded_bg"
android:padding="@dimen/eight"
android:layout_marginBottom="@dimen/four"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@+id/moveDown"
app:srcCompat="@drawable/ic_up" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/moveDown"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/rounded_bg"
android:padding="@dimen/eight"
android:layout_marginBottom="@dimen/four"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@+id/search"
app:srcCompat="@drawable/ic_down" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/search"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/rounded_bg"
android:padding="@dimen/eight"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:srcCompat="@drawable/ic_search" />
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/searchInput"
android:layout_width="0dp"
@ -92,7 +131,8 @@
android:imeOptions="actionSearch"
android:singleLine="true"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
app:layout_constraintRight_toLeftOf="@id/reset"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,39 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_width="150dp"
android:layout_height="45dp"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="@drawable/apps_bg"
android:orientation="vertical">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/appIconTwo"
android:visibility="gone"
android:visibility="visible"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_width="@dimen/forty"
android:layout_height="@dimen/forty"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="@dimen/four" />
/>
<com.google.android.material.imageview.ShapeableImageView
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginBottom="@dimen/four"
android:visibility="visible"
android:id="@+id/appIcon"
android:layout_width="@dimen/forty"
android:layout_height="@dimen/forty" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/childTextview"
app:layout_constraintTop_toBottomOf="@id/appIconTwo"
app:layout_constraintLeft_toRightOf="@id/appIcon"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginLeft="5dp"
app:layout_constraintLeft_toRightOf="@id/appIconTwo"
app:layout_constraintRight_toRightOf="parent"
android:lines="2"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_marginBottom="@dimen/four"
android:layout_height="match_parent" />

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="150dp"
android:layout_height="40dp"
android:layout_margin="5dp"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="@drawable/apps_bg"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/name"
android:visibility="visible"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_width="match_parent"
android:layout_height="20dp"
android:layout_gravity="center_horizontal"
/>
<com.google.android.material.textview.MaterialTextView
android:id="@+id/number"
app:layout_constraintTop_toBottomOf="@id/name"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_width="0dp"
android:layout_marginBottom="@dimen/four"
android:layout_height="20dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CircleImageView">
<attr name="civ_border_width" format="dimension" />
<attr name="civ_border_color" format="color" />
<attr name="civ_border_overlay" format="boolean" />
<attr name="civ_circle_background_color" format="color" />
</declare-styleable>
</resources>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<resources xmlns:app="http://schemas.android.com/apk/res-auto">
<style name="Theme.LunarLauncher.Starting" parent="Theme.SplashScreen">
<item name="windowSplashScreenBackground">@color/ic_launcher_primary</item>
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item>
@ -21,4 +21,17 @@
<style name="SettingsNavBar">
<item name="android:navigationBarColor">?attr/colorSurface</item>
</style>
<style name="SearchIcons">
<item name="android:layout_margin">5dp</item>
<item name="android:adjustViewBounds">true</item>
<item name="android:scaleType">fitCenter</item>
<item name="android:background">@null</item>
<item name="android:layout_width">40dp</item>
<item name="android:layout_height">40dp</item>
<item name="civ_border_width">1dp</item>
<item name="civ_border_color">#000000</item>
</style>
</resources>