oh....
This commit is contained in:
parent
b8c08da378
commit
4e26fab27c
@ -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">
|
||||
|
||||
@ -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)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
131
app/src/main/kotlin/rasel/lunar/launcher/apps/ContactAdapter.kt
Normal file
131
app/src/main/kotlin/rasel/lunar/launcher/apps/ContactAdapter.kt
Normal 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]
|
||||
}
|
||||
438
app/src/main/kotlin/rasel/lunar/launcher/apps/ContactMenu.kt
Normal file
438
app/src/main/kotlin/rasel/lunar/launcher/apps/ContactMenu.kt
Normal 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
|
||||
}
|
||||
|
||||
}
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
@ -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 */
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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")
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
500
app/src/main/kotlin/rasel/lunar/launcher/view/CircleImageView.kt
Normal file
500
app/src/main/kotlin/rasel/lunar/launcher/view/CircleImageView.kt
Normal 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
|
||||
}
|
||||
}
|
||||
27
app/src/main/res/drawable/duckduckgo.xml
Normal file
27
app/src/main/res/drawable/duckduckgo.xml
Normal 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>
|
||||
BIN
app/src/main/res/drawable/gmap.png
Normal file
BIN
app/src/main/res/drawable/gmap.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
BIN
app/src/main/res/drawable/google.png
Normal file
BIN
app/src/main/res/drawable/google.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.0 KiB |
BIN
app/src/main/res/drawable/namuwiki.png
Normal file
BIN
app/src/main/res/drawable/namuwiki.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
BIN
app/src/main/res/drawable/naver.png
Normal file
BIN
app/src/main/res/drawable/naver.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
BIN
app/src/main/res/drawable/navermap.png
Normal file
BIN
app/src/main/res/drawable/navermap.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
app/src/main/res/drawable/tmap.png
Normal file
BIN
app/src/main/res/drawable/tmap.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.0 KiB |
@ -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>
|
||||
|
||||
@ -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" />
|
||||
|
||||
|
||||
32
app/src/main/res/layout/contact_item.xml
Normal file
32
app/src/main/res/layout/contact_item.xml
Normal 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>
|
||||
9
app/src/main/res/values/attrs.xml
Normal file
9
app/src/main/res/values/attrs.xml
Normal 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>
|
||||
@ -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>
|
||||
Loading…
x
Reference in New Issue
Block a user