diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e30947b..b27a486 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -16,6 +16,7 @@ + @@ -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"> diff --git a/app/src/main/kotlin/rasel/lunar/launcher/apps/AppDrawer.kt b/app/src/main/kotlin/rasel/lunar/launcher/apps/AppDrawer.kt index 23b0cc4..09ce3b5 100644 --- a/app/src/main/kotlin/rasel/lunar/launcher/apps/AppDrawer.kt +++ b/app/src/main/kotlin/rasel/lunar/launcher/apps/AppDrawer.kt @@ -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 = mutableListOf() private var packageList = mutableListOf() @@ -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() - @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() + val contactList = arrayListOf() + 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) +// } +// } +// } +// } } } + diff --git a/app/src/main/kotlin/rasel/lunar/launcher/apps/AppsAdapter.kt b/app/src/main/kotlin/rasel/lunar/launcher/apps/AppsAdapter.kt index 7da00b1..4a55523 100644 --- a/app/src/main/kotlin/rasel/lunar/launcher/apps/AppsAdapter.kt +++ b/app/src/main/kotlin/rasel/lunar/launcher/apps/AppsAdapter.kt @@ -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() { + private val appsCount: TextView) : RecyclerView.Adapter() { private var oldList = mutableListOf() 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)) } } diff --git a/app/src/main/kotlin/rasel/lunar/launcher/apps/ContactAdapter.kt b/app/src/main/kotlin/rasel/lunar/launcher/apps/ContactAdapter.kt new file mode 100644 index 0000000..ea906fd --- /dev/null +++ b/app/src/main/kotlin/rasel/lunar/launcher/apps/ContactAdapter.kt @@ -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 . + */ + +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() { + + private var oldList = mutableListOf() + 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) { + 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, private val newList: List +) : 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] +} diff --git a/app/src/main/kotlin/rasel/lunar/launcher/apps/ContactMenu.kt b/app/src/main/kotlin/rasel/lunar/launcher/apps/ContactMenu.kt new file mode 100644 index 0000000..233f46f --- /dev/null +++ b/app/src/main/kotlin/rasel/lunar/launcher/apps/ContactMenu.kt @@ -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 . + */ + +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 = + 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 + } + +} diff --git a/app/src/main/kotlin/rasel/lunar/launcher/apps/SimpleGesture.kt b/app/src/main/kotlin/rasel/lunar/launcher/apps/SimpleGesture.kt deleted file mode 100644 index f5b8a66..0000000 --- a/app/src/main/kotlin/rasel/lunar/launcher/apps/SimpleGesture.kt +++ /dev/null @@ -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 - * - * - *

- * - * - * 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" - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/rasel/lunar/launcher/home/BatteryReceiver.kt b/app/src/main/kotlin/rasel/lunar/launcher/home/BatteryReceiver.kt index a24b1fa..99e204e 100644 --- a/app/src/main/kotlin/rasel/lunar/launcher/home/BatteryReceiver.kt +++ b/app/src/main/kotlin/rasel/lunar/launcher/home/BatteryReceiver.kt @@ -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 */ diff --git a/app/src/main/kotlin/rasel/lunar/launcher/home/LauncherHome.kt b/app/src/main/kotlin/rasel/lunar/launcher/home/LauncherHome.kt index d9f838f..fbaee71 100644 --- a/app/src/main/kotlin/rasel/lunar/launcher/home/LauncherHome.kt +++ b/app/src/main/kotlin/rasel/lunar/launcher/home/LauncherHome.kt @@ -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 } diff --git a/app/src/main/kotlin/rasel/lunar/launcher/home/weather/WeatherExecutor.kt b/app/src/main/kotlin/rasel/lunar/launcher/home/weather/WeatherExecutor.kt index 9e597e9..f2097af 100644 --- a/app/src/main/kotlin/rasel/lunar/launcher/home/weather/WeatherExecutor.kt +++ b/app/src/main/kotlin/rasel/lunar/launcher/home/weather/WeatherExecutor.kt @@ -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() } diff --git a/app/src/main/kotlin/rasel/lunar/launcher/utils/MainActivity.kt b/app/src/main/kotlin/rasel/lunar/launcher/utils/MainActivity.kt new file mode 100644 index 0000000..85dfd77 --- /dev/null +++ b/app/src/main/kotlin/rasel/lunar/launcher/utils/MainActivity.kt @@ -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 +// +// 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("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( +// 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, +// grantResults: IntArray +// ) { +// super.onRequestPermissionsResult(requestCode, permissions, grantResults) +// if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { +// Log.d("test", "permission granted") +// } else { +// Log.d("test", "permission denied") +// } +// } +//} \ No newline at end of file diff --git a/app/src/main/kotlin/rasel/lunar/launcher/utils/SimpleGesture.kt b/app/src/main/kotlin/rasel/lunar/launcher/utils/SimpleGesture.kt index 8019ea2..c42501f 100644 --- a/app/src/main/kotlin/rasel/lunar/launcher/utils/SimpleGesture.kt +++ b/app/src/main/kotlin/rasel/lunar/launcher/utils/SimpleGesture.kt @@ -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 { @@ -778,4 +806,5 @@ class SimpleFingerGestures : OnTouchListener { const val GESTURE_SPEED_FAST: Long = 500 private const val TAG = "SimpleFingerGestures" } -} \ No newline at end of file +} + diff --git a/app/src/main/kotlin/rasel/lunar/launcher/view/CircleImageView.kt b/app/src/main/kotlin/rasel/lunar/launcher/view/CircleImageView.kt new file mode 100644 index 0000000..f0d1890 --- /dev/null +++ b/app/src/main/kotlin/rasel/lunar/launcher/view/CircleImageView.kt @@ -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 + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/duckduckgo.xml b/app/src/main/res/drawable/duckduckgo.xml new file mode 100644 index 0000000..eefd78f --- /dev/null +++ b/app/src/main/res/drawable/duckduckgo.xml @@ -0,0 +1,27 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/gmap.png b/app/src/main/res/drawable/gmap.png new file mode 100644 index 0000000..8543444 Binary files /dev/null and b/app/src/main/res/drawable/gmap.png differ diff --git a/app/src/main/res/drawable/google.png b/app/src/main/res/drawable/google.png new file mode 100644 index 0000000..832d03f Binary files /dev/null and b/app/src/main/res/drawable/google.png differ diff --git a/app/src/main/res/drawable/namuwiki.png b/app/src/main/res/drawable/namuwiki.png new file mode 100644 index 0000000..9813ade Binary files /dev/null and b/app/src/main/res/drawable/namuwiki.png differ diff --git a/app/src/main/res/drawable/naver.png b/app/src/main/res/drawable/naver.png new file mode 100644 index 0000000..8a3a31f Binary files /dev/null and b/app/src/main/res/drawable/naver.png differ diff --git a/app/src/main/res/drawable/navermap.png b/app/src/main/res/drawable/navermap.png new file mode 100644 index 0000000..2829b03 Binary files /dev/null and b/app/src/main/res/drawable/navermap.png differ diff --git a/app/src/main/res/drawable/tmap.png b/app/src/main/res/drawable/tmap.png new file mode 100644 index 0000000..f20ca6e Binary files /dev/null and b/app/src/main/res/drawable/tmap.png differ diff --git a/app/src/main/res/layout/app_drawer.xml b/app/src/main/res/layout/app_drawer.xml index e85e2c9..99ee65a 100644 --- a/app/src/main/res/layout/app_drawer.xml +++ b/app/src/main/res/layout/app_drawer.xml @@ -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"> - - - - - - - - - - - - - + android:layout_width="0dp" + android:layout_height="0dp"> + + + + + + + + + + + + + + + + + + - - - - - - + app:layout_constraintRight_toLeftOf="@id/reset"/> + diff --git a/app/src/main/res/layout/apps_child.xml b/app/src/main/res/layout/apps_child.xml index 25c3e25..173f7f0 100644 --- a/app/src/main/res/layout/apps_child.xml +++ b/app/src/main/res/layout/apps_child.xml @@ -1,39 +1,32 @@ + /> - - diff --git a/app/src/main/res/layout/contact_item.xml b/app/src/main/res/layout/contact_item.xml new file mode 100644 index 0000000..ed64938 --- /dev/null +++ b/app/src/main/res/layout/contact_item.xml @@ -0,0 +1,32 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml new file mode 100644 index 0000000..ff8702d --- /dev/null +++ b/app/src/main/res/values/attrs.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index ed2bb2c..46c24b8 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,5 +1,5 @@ - + + + + + \ No newline at end of file