diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0aebb899..026ec983 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -18,6 +18,7 @@ + closeSearch() -// false -> openSearch() -// } -// } - binding.searchInput.setOnKeyListener { v, keyCode, event -> if (keyCode == 66 && contactList.size < 1 && packageList.size < 1) { binding.searchGoogle.performClick() - true + true }else { false } } - binding.searchInput.doOnTextChanged { inputText, _, _, _ -> + binding.searchInput.doOnTextChanged{ inputText, _, _, _ -> binding.searchInput.text?.let { binding.searchInput.setSelection(it.length) } filterAppsList(inputText.toString()) } } + fun checkResult(keyword: String) { + + if(!isAdded || !isResumed || keyword.length < 1) return + var dialog : AlertDialog.Builder? = null + var filted = packageList.filter { it.appName.equals(keyword) } + BLog.LOGE("filted >> ${filted.size}") + var filtedContact = contactList.filter { it.name.equals(keyword) } + BLog.LOGE("filtedContact >> ${filtedContact.size}") + if (keyword.length > 0 && (packageList.size == 1 || filted.size > 0)) { + dialog = AlertDialog.Builder(requireContext()) + dialog?.setTitle("앱 실행 확인") + if (packageList.size == 1) { + dialog?.setMessage("${keyword} 검색 결과 '${packageList[0].appName}' 준비됨") + dialog?.setPositiveButton("실행") { s, d -> + runonUi { + startActivity(packageManager?.getLaunchIntentForPackage(packageList[0].packageName)) + s.dismiss() + } + } + } else if (filted.size > 0) { + dialog?.setMessage("${keyword} 검색 결과 '${filted[0].appName}' 준비됨") + dialog?.setPositiveButton("${filted[0].appName} 실행") { s, d -> + runonUi { + startActivity(packageManager?.getLaunchIntentForPackage(filted[0].packageName)) + s.dismiss() + } + } + if(filted.size > 1) { + dialog?.setNeutralButton("${filted[1].appName} 실행") { s, d -> + runonUi { + startActivity(packageManager?.getLaunchIntentForPackage(filted[1].packageName)) + s.dismiss() + } + } + } + } + dialog?.setCancelable(false) + dialog?.setNegativeButton("취소") { s, d -> + runonUi { s.dismiss() } + } + dialog?.setOnDismissListener { registCancelSearch() } + dialog?.show() + } else if (contactList.size == 1 || filtedContact.size > 0) { + dialog = AlertDialog.Builder(requireContext()) + dialog?.setTitle("연락처 실행 확인") + dialog?.setCancelable(false) + dialog?.setNegativeButton("취소") { s, d -> + runonUi { s.dismiss() } + } + if (contactList.size == 1) { + dialog?.setMessage("${keyword} 검색 결과 '${contactList[0].name}' 준비됨") + dialog?.setPositiveButton("자세히 보기") { s, d -> + runonUi { + ContactMenu().show(childFragmentManager, contactList[0].id.toString()) + s.dismiss() + } + } + } else if(filtedContact.size > 0) { + dialog?.setMessage("${keyword} 검색 결과 '${filtedContact[0].name}' 준비됨") + dialog?.setPositiveButton("'${filtedContact[0].name},\n${filtedContact[0].phoneNumber}'\n자세히 보기") { s, d -> + runonUi { + ContactMenu().show(childFragmentManager, filtedContact[0].id.toString()) + s.dismiss() + } + } + if (filtedContact.size > 1) { + dialog?.setNeutralButton("'${filtedContact[1].name},\n${filtedContact[1].phoneNumber}'\n자세히 보기") { s, d -> + runonUi { + ContactMenu().show(childFragmentManager, filtedContact[1].id.toString()) + s.dismiss() + } + } + } + } + dialog?.setOnDismissListener { registCancelSearch() } + dialog?.show() + } else { + lActivity?.openSearchMenus(keyword) { + registCancelSearch() + } + } + } + + + + fun runonUi(invoke : () -> Unit) { + Handler(Looper.getMainLooper()).run { + try { + invoke.invoke() + }catch (e : Exception) { + e.printStackTrace() + } + } + } + override fun onResume() { super.onResume() + BLog.LOGE("onResume") fetchApps() GetContact() + binding.appsCount.visibility = if (settingsPrefs!!.getBoolean(KEY_APPS_COUNT, true)) VISIBLE else GONE - if (settingsPrefs!!.getInt(KEY_APPS_LAYOUT, 0) in 0..1) { - appsAdapter?.updateGravity(settingsPrefs!!.getInt(KEY_DRAW_ALIGN, Gravity.CENTER)) - } +// if (settingsPrefs!!.getInt(KEY_APPS_LAYOUT, 0) in 0..1) { +// appsAdapter?.updateGravity(settingsPrefs!!.getInt(KEY_DRAW_ALIGN, Gravity.CENTER)) +// } contactAdapter?.updateData(contactList) openSearch() + setKeyboardPadding() + + contactAdapter?.updateData(contactList) + + /* pop up the keyboard */ + openSearch() + + registCancelSearch() + BLog.LOGE("onResume after chechHandler.postDelayed(cancelSearch, 3000L)") + } + + val chechHandler = Handler(Looper.getMainLooper()) + + val cancelSearch = Runnable { timerCheck() } + + fun registCancelSearch() { + BLog.LOGE("Called registCancelSearch") + chechHandler.removeCallbacks(cancelSearch) + chechHandler.postDelayed(cancelSearch, 3000L) + } + + fun clearCancelSearch() { + chechHandler.removeCallbacks(cancelSearch) + } + + private fun timerCheck() { + lActivity?.onBackPressed() } override fun onPause() { @@ -308,62 +413,30 @@ internal class AppDrawer : Fragment() { /* update app list with app and package name */ fun fetchApps() { - if (oringinPackageList.size > 0) { - packageList.clear() - for(pkg in oringinPackageList) { - packageList.add(pkg) - } - } else { - packageList.clear() - oringinPackageList.clear() - GlobalScope.launch { - packageInfoList = (if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - packageManager?.queryIntentActivities( - Intent(Intent.ACTION_MAIN, null).addCategory(Intent.CATEGORY_LAUNCHER), - PackageManager.ResolveInfoFlags.of(0) - ) - } else { - (packageManager?.queryIntentActivities( - Intent(Intent.ACTION_MAIN, null).addCategory(Intent.CATEGORY_LAUNCHER), 0 - )) - })?.apply { - removeIf { it.activityInfo.packageName.equals(BuildConfig.APPLICATION_ID) } - - forEach { - oringinPackageList.add( - Packages( - it.activityInfo.packageName, - appName(it), - getCategory(it.activityInfo.applicationInfo.category) - ) - ) - packageList.add( - Packages( - it.activityInfo.packageName, - appName(it), - getCategory(it.activityInfo.applicationInfo.category) - ) - ) - } - }!! - } - /* add package and app names to the list */ - -// var edit = appNamesPrefs?.edit() -// for (resolver in packageInfoList) { -// packageList.add(Packages(resolver.activityInfo.packageName, appName(resolver))) -// } + packageList.clear() + oringinPackageList.clear() + GlobalScope.launch { + packageInfoList = (if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + packageManager?.queryIntentActivities( + Intent(Intent.ACTION_MAIN, null).addCategory(Intent.CATEGORY_LAUNCHER), + PackageManager.ResolveInfoFlags.of(0) + ) + } else { + (packageManager?.queryIntentActivities( + Intent(Intent.ACTION_MAIN, null).addCategory(Intent.CATEGORY_LAUNCHER), 0 + )) + })?.apply { + removeIf { it.activityInfo.packageName.equals(BuildConfig.APPLICATION_ID) } + forEach { oringinPackageList.add(Packages(it.activityInfo.packageName, normalize(appName(it)), getCategory(it.activityInfo.applicationInfo.category))) } + oringinPackageList.sortBy { it.appName } + packageList.addAll( + oringinPackageList + ) + binding.appsList.post { if (packageList.size > 0) { + appsAdapter?.updateData(packageList) + } } + }!! } -// when { -// packageList.size < 1 -> return -// else -> { - if (packageList.size > 0) { - MainScope().launch { - appsAdapter?.updateData(packageList) - } - } -// } - } private fun getAlphabetItems() { @@ -395,7 +468,7 @@ internal class AppDrawer : Fragment() { var lastSearchString : String = "" private fun filterAppsList(searchString: String) { /* check each app name and add if it matches the search string */ - if (lastSearchStringLength > 0 && (lastSearchStringLength != searchString.length || lastSearchString.equals(searchString) == false)) { + if (searchString.length > 0 && (lastSearchStringLength != searchString.length || lastSearchString.equals(searchString) == false)) { BLog.LOGE("START FILTER") packageList.clear() for (pkg in oringinPackageList) { @@ -404,38 +477,22 @@ internal class AppDrawer : Fragment() { packageList.add(pkg) } } + packageList.sortBy { it.appName } BLog.LOGE("MIDDLE FILTER") - if (searchString.length > 2 && packageList.size == 1 && settingsPrefs!!.getBoolean( - KEY_QUICK_LAUNCH, - true - ) - ) { - var dialog = AlertDialog.Builder(requireContext()) - dialog.setTitle("앱 실행 확인") - dialog.setMessage("${searchString} 검색 결과 '${packageList[0].appName}' 준비됨") - dialog.setCancelable(false) - dialog.setNegativeButton("취소") { s,d-> - binding.searchInput.setText("") - s.dismiss() - } - dialog.setPositiveButton("실행") { s, d -> - startActivity(packageManager?.getLaunchIntentForPackage(packageList[0].packageName)) - s.dismiss() - binding.searchInput.setText("") - } - dialog.show() - } else { - appsAdapter?.updateData(packageList) - } + + appsAdapter?.updateData(packageList) + contactList.clear() for (item in originContactList) { if (item.name.contains(searchString) || item.phoneNumber.contains(searchString)) { contactList.add(item) } } + contactList.sortBy { it.name } contactAdapter?.updateData(contactList) BLog.LOGE("END FILTER") + } else if(lastSearchStringLength == 0){ contactList.clear() for (item in originContactList) { @@ -447,9 +504,28 @@ internal class AppDrawer : Fragment() { } appsAdapter?.updateData(packageList) contactAdapter?.updateData(contactList) + } else { + afterClearSearch() + } lastSearchString = searchString lastSearchStringLength = searchString.length + registCancelSearch() + } + + private fun afterClearSearch() { + contactList.clear() + packageList.clear() + for (item in originContactList) { + contactList.add(item) + } + for (resolver in oringinPackageList) { + packageList.add(resolver) + } + packageList.sortBy { it.appName } + contactList.sortBy { it.name } + appsAdapter?.updateData(packageList) + contactAdapter?.updateData(contactList) } private fun normalize(str: String): String { @@ -460,26 +536,21 @@ internal class AppDrawer : Fragment() { } private fun openSearch() { - isSearchShown = true binding.searchInput.apply { visibility = VISIBLE requestFocus() let { - (lActivity!!.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager) - .showSoftInput(it, InputMethodManager.SHOW_IMPLICIT) + (lActivity?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager)?.showSoftInput(it, InputMethodManager.SHOW_IMPLICIT) } } } /* clear search string, hide keyboard and search box */ private fun closeSearch() { - isSearchShown = false binding.searchInput.apply { - text?.clear() - visibility = GONE let { - (lActivity!!.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager) - .hideSoftInputFromWindow(it.windowToken, 0) + text?.clear() + (lActivity?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager)?.hideSoftInputFromWindow(it.windowToken, 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 17f48a20..e3795231 100644 --- a/app/src/main/kotlin/rasel/lunar/launcher/apps/AppsAdapter.kt +++ b/app/src/main/kotlin/rasel/lunar/launcher/apps/AppsAdapter.kt @@ -47,7 +47,7 @@ internal class AppsAdapter( private val appsCount: TextView) : RecyclerView.Adapter() { private var oldList = mutableListOf() - private var appGravity: Int = Gravity.CENTER +// private var appGravity: Int = Gravity.CENTER companion object { @JvmStatic var appsSize: Int? = null @@ -106,21 +106,6 @@ internal class AppsAdapter( 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 Packages ( diff --git a/app/src/main/kotlin/rasel/lunar/launcher/apps/ContactAdapter.kt b/app/src/main/kotlin/rasel/lunar/launcher/apps/ContactAdapter.kt index ba93ddcd..37ac76c5 100644 --- a/app/src/main/kotlin/rasel/lunar/launcher/apps/ContactAdapter.kt +++ b/app/src/main/kotlin/rasel/lunar/launcher/apps/ContactAdapter.kt @@ -70,7 +70,7 @@ internal class ContactAdapter ( holder.view.root.apply { /* on click - open app */ setOnClickListener { - context.startActivity(Intent(Intent.ACTION_DIAL, Uri.parse("tel://${item.phoneNumber}"))) + ContactMenu().show(fragmentManager, item.id.toString()) } /* on long click - open app menu */ diff --git a/app/src/main/kotlin/rasel/lunar/launcher/apps/ContactMenu.kt b/app/src/main/kotlin/rasel/lunar/launcher/apps/ContactMenu.kt index 233f46fd..8fd934a4 100644 --- a/app/src/main/kotlin/rasel/lunar/launcher/apps/ContactMenu.kt +++ b/app/src/main/kotlin/rasel/lunar/launcher/apps/ContactMenu.kt @@ -58,6 +58,7 @@ 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.databinding.ContactMenuBinding 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 @@ -74,14 +75,15 @@ import java.util.* internal class ContactMenu : BottomSheetDialogFragment() { - private lateinit var binding: AppMenuBinding + private lateinit var binding: ContactMenuBinding private lateinit var packageName: String private lateinit var packageManager: PackageManager private lateinit var appInfo: ApplicationInfo private lateinit var defAppName: String - + var contactName : String = "" + var contactPhoneNumber : String = "" override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - binding = AppMenuBinding.inflate(inflater, container, false) + binding = ContactMenuBinding.inflate(inflater, container, false) /* get package name from fragment's tag */ packageName = tag.toString() @@ -96,14 +98,12 @@ internal class ContactMenu : BottomSheetDialogFragment() { val cursor = resolver.query(phoneUri, projection, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = " + packageName, null , null) 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) + contactName = cursor.getString(nameIndex) var number = cursor.getString(numberIndex) - number = number.replace("-", "") - BLog.LOGE("GetContact", "이름 : $name 번호 : $number ") + contactPhoneNumber = number.replace("-", "") + BLog.LOGE("GetContact", "이름 : $contactName 번호 : $contactPhoneNumber ") } } // 데이터 계열은 반드시 닫아줘야 한다. @@ -124,8 +124,8 @@ internal class ContactMenu : BottomSheetDialogFragment() { // copyToClipboard(requireContext(), packageName) // } // -// appName() -// binding.detailedInfo.setOnClickListener { detailedInfo() } + appName() + binding.detailedInfo.setOnClickListener { detailedInfo() } // binding.activityBrowser.setOnClickListener { activityBrowser() } // binding.appStore.setOnClickListener { appStore() } // binding.appFreeform.setOnClickListener { freeform() } @@ -165,7 +165,7 @@ internal class ContactMenu : BottomSheetDialogFragment() { /* listen on clicks */ binding.favGroup.addOnButtonCheckedListener { _: MaterialButtonToggleGroup?, - checkedId: Int, isChecked: Boolean -> + checkedId: Int, isChecked: Boolean -> try { if (checkedId == button.id) { if (isChecked) { @@ -186,66 +186,17 @@ internal class ContactMenu : BottomSheetDialogFragment() { } 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 - } + binding.appName.text = contactName + binding.phoneNumber.text = contactPhoneNumber } /* 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() + var intent = Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(ContactsContract.Contacts.CONTENT_URI.toString() + "/" + packageName)); + startActivity(intent); - /* 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 */ diff --git a/app/src/main/kotlin/rasel/lunar/launcher/apps/SearchMenu.kt b/app/src/main/kotlin/rasel/lunar/launcher/apps/SearchMenu.kt new file mode 100644 index 00000000..4f85a98f --- /dev/null +++ b/app/src/main/kotlin/rasel/lunar/launcher/apps/SearchMenu.kt @@ -0,0 +1,179 @@ +/* + * 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.DialogInterface +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.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.Toast +import androidx.appcompat.widget.LinearLayoutCompat +import androidx.core.content.FileProvider +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.FragmentTransaction +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.BuildConfig +import rasel.lunar.launcher.LauncherActivity.Companion.lActivity +import rasel.lunar.launcher.R +import rasel.lunar.launcher.databinding.ActivityBrowserDialogBinding +import rasel.lunar.launcher.databinding.ContactMenuBinding +import rasel.lunar.launcher.databinding.SearchMenuBinding +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.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 SearchMenu : BottomSheetDialogFragment() { + + private lateinit var binding: SearchMenuBinding + private lateinit var searchWord: String + private lateinit var packageManager: PackageManager + private lateinit var appInfo: ApplicationInfo + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + binding = SearchMenuBinding.inflate(inflater, container, false) + + /* get package name from fragment's tag */ + searchWord = tag.toString() + + 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() } + binding.searchNmap.setOnClickListener { + openSearchApps("nmap://search?query=${searchWord}&appname=${BuildConfig.APPLICATION_ID}","com.nhn.android.nmap") + } + binding.searchGoogleMap.setOnClickListener { + openSearchApps("geo:0,0?q=${searchWord}","com.google.android.apps.maps") + } + binding.searchGoogle.setOnClickListener { + openSearchApps("https://www.google.com/search?q=${searchWord}","com.android.chrome") + } + binding.searchTmap.setOnClickListener { + openSearchApps("tmap://search?name=${searchWord}","com.skt.tmap.ku") + + } + binding.searchNaver.setOnClickListener { + openSearchApps("https://search.naver.com/search.naver?where=nexearch&query=${searchWord}", "com.nhn.android.search") + } + binding.searchDuckduckgo.setOnClickListener { + openSearchApps("https://duckduckgo.com/?t=h_&q=${searchWord}","com.duckduckgo.mobile.android") + } + binding.searchNamuwiki.setOnClickListener { + openSearchApps("https://namu.wiki/Search?q=${searchWord}") + } + + binding.searchTranslate.setOnClickListener { + openSearchApps("https://translate.google.com/?hl=ko&sl=ko&tl=en&text=${searchWord}&op=translate","com.android.chrome") + } + + binding.searchCoupang.setOnClickListener { + openSearchApps("coupang://search?q=${searchWord}","com.coupang.mobile") + } + } + + 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) + try { + dismiss() + } catch (e : Exception) { + e.printStackTrace() + } + } + + + private fun appName() { + binding.keyworkd.text = searchWord + } + + var mDismissCalback : DismissCalback? = null + + fun show(manager: FragmentManager, tag: String? , dismissCalback : DismissCalback?) { + this.mDismissCalback = dismissCalback + this.show(manager, tag) + } + + override fun show(manager: FragmentManager, tag: String?) { + super.show(manager, tag) + } + + override fun dismiss() { + BLog.LOGE("dismiss()") + mDismissCalback?.invoke() + super.dismiss() + } + + override fun onDismiss(dialog: DialogInterface) { + BLog.LOGE("onDismiss(dialog: DialogInterface)") + mDismissCalback?.invoke() + super.onDismiss(dialog) + } +} + +typealias DismissCalback = ()->Unit diff --git a/app/src/main/kotlin/rasel/lunar/launcher/helpers/Constants.kt b/app/src/main/kotlin/rasel/lunar/launcher/helpers/Constants.kt index 87d366bd..b117d2b2 100644 --- a/app/src/main/kotlin/rasel/lunar/launcher/helpers/Constants.kt +++ b/app/src/main/kotlin/rasel/lunar/launcher/helpers/Constants.kt @@ -67,7 +67,7 @@ internal class Constants { const val KEY_LOCK_METHOD = "lock_method" /* --- */ - const val DEFAULT_DATE_FORMAT = "EEE dx MMM, yyyy" + const val DEFAULT_DATE_FORMAT = "EEE, dd, MM, yyyy" const val DEFAULT_ICON_SIZE = 44 const val DEFAULT_ICON_PACK = "default_icon_pack" const val DEFAULT_GRID_COLUMNS = 4 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 fbaee711..32c14ab0 100644 --- a/app/src/main/kotlin/rasel/lunar/launcher/home/LauncherHome.kt +++ b/app/src/main/kotlin/rasel/lunar/launcher/home/LauncherHome.kt @@ -25,14 +25,23 @@ import android.content.IntentFilter import android.content.SharedPreferences import android.os.Bundle import android.provider.AlarmClock +import android.provider.CallLog import android.text.format.DateFormat import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.RadioButton import android.widget.Toast import androidx.biometric.BiometricPrompt +import androidx.core.view.get import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.async +import kotlinx.coroutines.launch import rasel.lunar.launcher.LauncherActivity.Companion.lActivity import rasel.lunar.launcher.R import rasel.lunar.launcher.databinding.LauncherHomeBinding @@ -51,10 +60,12 @@ import rasel.lunar.launcher.helpers.UniUtils.Companion.lockMethod import rasel.lunar.launcher.home.weather.WeatherExecutor import rasel.lunar.launcher.qaccess.QuickAccess import rasel.lunar.launcher.settings.SettingsActivity +import rasel.lunar.launcher.todos.MissedCallsAdapter 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.text.SimpleDateFormat import java.util.* @@ -74,7 +85,9 @@ internal class LauncherHome : Fragment() { batteryReceiver = BatteryReceiver(binding.batteryProgress) binding.favAppsGroup.visibility = View.GONE - + Thread("CALLED").run { + getCallDetails() + } return binding.root } @@ -105,6 +118,8 @@ internal class LauncherHome : Fragment() { requireContext().registerReceiver(batteryReceiver, IntentFilter(Intent.ACTION_BATTERY_CHANGED)) /* time and date */ + binding.time.textLocale = Locale.US + binding.date.textLocale = Locale.US if (DateFormat.is24HourFormat(requireContext())) { binding.time.format24Hour = timeFormat binding.date.format24Hour = dateFormat @@ -120,6 +135,79 @@ internal class LauncherHome : Fragment() { } } + var callList = arrayListOf() + + + private fun getCallDetails() { + var dateParam = Date(System.currentTimeMillis() - (1000 * 60 * 60 * 24 * 3)).time.toString() + val managedCursor = lActivity!!.managedQuery( + CallLog.Calls.CONTENT_URI, arrayOf( + CallLog.Calls.NUMBER, + CallLog.Calls.TYPE, + CallLog.Calls.DATE, + CallLog.Calls.DURATION, + CallLog.Calls.CACHED_NAME, + ), CallLog.Calls.DATE + "> ? AND " + CallLog.Calls.TYPE + " > ?", arrayOf(dateParam, "2"), CallLog.Calls.DATE + " desc") + val number = managedCursor.getColumnIndex(CallLog.Calls.NUMBER) + val type = managedCursor.getColumnIndex(CallLog.Calls.TYPE) + val date = managedCursor.getColumnIndex(CallLog.Calls.DATE) + val duration = managedCursor.getColumnIndex(CallLog.Calls.DURATION) + val name = managedCursor.getColumnIndex(CallLog.Calls.CACHED_NAME) + var missedCalls = hashMapOf() + if (missedCalls.size != callList.size) { + while (managedCursor.moveToNext()) { + val phNumber = managedCursor.getString(number) // mobile number + val callType = managedCursor.getString(type) // call type + val callDate = managedCursor.getString(date) // call date + val callDayTime: Date = Date(callDate.toLong()) + val callDuration = managedCursor.getString(duration) + val callerName = managedCursor.getString(name) + + var dir: String = "" + val dircode = callType.toInt() + when (dircode) { + CallLog.Calls.INCOMING_TYPE -> { + dir = "INCOMING_TYPE" + } + CallLog.Calls.OUTGOING_TYPE -> { + dir = "OUTGOING_TYPE" + } + CallLog.Calls.MISSED_TYPE -> { + dir = "MISSED_TYPE" + } + CallLog.Calls.VOICEMAIL_TYPE -> { + dir = "VOICEMAIL_TYPE" + } + CallLog.Calls.REJECTED_TYPE -> { + dir = "REJECTED_TYPE" + } + CallLog.Calls.BLOCKED_TYPE -> { + dir = "BLOCKED_TYPE" + } + CallLog.Calls.ANSWERED_EXTERNALLY_TYPE -> { + dir = "ANSWERED_EXTERNALLY_TYPE" + } + } + var missed: MissedCall = if (missedCalls.containsKey(phNumber)) { + missedCalls.get(phNumber)!!.apply { + count = count + 1 + } + } else { + MissedCall(1,callerName,phNumber, dircode, dir, SimpleDateFormat("yyy/MM/dd-HH:mm:ss").format(callDayTime)) + } + missedCalls.put(phNumber, missed) + } + } + managedCursor.close() + if (callList.size == missedCalls.size) { + } else { + callList.clear() + missedCalls.forEach { t, u -> + callList.add(u) + } + } + } + override fun onPause() { super.onPause() /* unregister battery changes */ @@ -380,8 +468,18 @@ internal class LauncherHome : Fragment() { } /* to-do list */ + @SuppressLint("NotifyDataSetChanged") private fun showTodoList() { - binding.notes.adapter = TodoAdapter(null, requireContext()) + if (binding.missedCalls.isChecked == true) { + if (callList.size > 0) { + BLog.LOGE("callList >>> ${callList.size}") + binding.notes.adapter = MissedCallsAdapter(callList, requireContext())?.apply { + this.notifyDataSetChanged() + } + } + } else { + binding.notes.adapter = TodoAdapter(null, requireContext()) + } } /* get time format string */ @@ -390,9 +488,9 @@ internal class LauncherHome : Fragment() { 0 -> return if (DateFormat.is24HourFormat(requireContext())) { "kk:mm" } else { - "h:mm a" + "HH:mm a" } - 1 -> return "h:mm a" + 1 -> return "HH:mm a" 2 -> return "kk:mm" } return null @@ -420,3 +518,22 @@ internal class LauncherHome : Fragment() { } } + + +class MissedCall { + constructor(count: Int, name: String?, number: String, type: Int, typeString: String, date : String) { + this.count = count + this.name = name ?: "unknown" + this.number = number + this.type = type + this.typeString = typeString + this.date = date + } + + var count : Int = 1 + var name : String = "how" + var number : String = "" + var type : Int = 0 + var typeString : String = "" + var date : String = "" +} \ No newline at end of file diff --git a/app/src/main/kotlin/rasel/lunar/launcher/todos/MissedCallsAdapter.kt b/app/src/main/kotlin/rasel/lunar/launcher/todos/MissedCallsAdapter.kt new file mode 100644 index 00000000..236441ba --- /dev/null +++ b/app/src/main/kotlin/rasel/lunar/launcher/todos/MissedCallsAdapter.kt @@ -0,0 +1,113 @@ +/* + * 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.todos + +import android.annotation.SuppressLint +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.bottomsheet.BottomSheetDialog +import rasel.lunar.launcher.LauncherActivity.Companion.lActivity +import rasel.lunar.launcher.R +import rasel.lunar.launcher.databinding.ListItemBinding +import rasel.lunar.launcher.databinding.TodoDialogBinding +import rasel.lunar.launcher.helpers.UniUtils.Companion.copyToClipboard +import rasel.lunar.launcher.home.MissedCall +import rasel.lunar.launcher.utils.BLog +import java.util.* +import kotlin.collections.ArrayList + + +internal class MissedCallsAdapter( + private val callList: ArrayList, + private val context: Context) : RecyclerView.Adapter() { + + private val currentFragment = lActivity!!.supportFragmentManager.findFragmentById(R.id.mainFragmentsContainer) + + override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): MissedCallsHolder { + val binding = ListItemBinding.inflate(LayoutInflater.from(viewGroup.context), viewGroup, false) + return MissedCallsHolder(binding) + } + + override fun getItemCount(): Int { +// BLog.LOGE("callList.size >>> ${callList.size}") + return callList.size + } + + @SuppressLint("SetTextI18n") + override fun onBindViewHolder(holder: MissedCallsHolder, position: Int) { + val todo = callList[position] + BLog.LOGE("callList >>> ${callList[position]}") + holder.view.itemText.text = "\u25CF ${if(todo.name.equals("unknown")) todo.number else { todo.name}} , ${todo.typeString} : ${todo.count} : ${todo.date}" + + /* multiline texts are enabled for TodoManager */ + holder.view.itemText.isSingleLine = false + /* launch edit or update dialog on item click */ + holder.view.itemText.setOnClickListener { updateDialog(position) } + /* copy texts on long click */ + holder.view.itemText.setOnLongClickListener { + copyToClipboard(context, todo.name) + true + } + + } + + inner class MissedCallsHolder(var view: ListItemBinding) : RecyclerView.ViewHolder(view.root) + + /* update dialog */ + private fun updateDialog(position: Int) { + val bottomSheetDialog = BottomSheetDialog(lActivity!!, R.style.BottomSheetDialog) + val dialogBinding = TodoDialogBinding.inflate(LayoutInflater.from(context)) + bottomSheetDialog.setContentView(dialogBinding.root) + bottomSheetDialog.show() + bottomSheetDialog.dismissWithAnimation = true + + val databaseHandler = DatabaseHandler(context) + val todo = databaseHandler.todos[position] + + dialogBinding.apply { + deleteAllConfirmation.visibility = View.GONE + todoInput.setText(todo.name) + todoCancel.text = context.getString(R.string.delete) + todoCancel.setTextColor(ContextCompat.getColor(context, android.R.color.holo_red_light)) + todoOk.text = context.getString(R.string.update) + } + + /* delete the item */ + dialogBinding.todoCancel.setOnClickListener { + + } + + /* update the item */ + dialogBinding.todoOk.setOnClickListener { + val updatedTodoString = Objects.requireNonNull(dialogBinding.todoInput.text).toString().trim { it <= ' ' } + if (updatedTodoString.isNotEmpty()) { + todo.name = updatedTodoString + databaseHandler.updateTodo(todo) + bottomSheetDialog.dismiss() + } else { + dialogBinding.todoInput.error = context.getString(R.string.empty_text_field) + } + } + } + +} diff --git a/app/src/main/kotlin/rasel/lunar/launcher/utils/BLog.kt b/app/src/main/kotlin/rasel/lunar/launcher/utils/BLog.kt index d46efbae..0e247e26 100644 --- a/app/src/main/kotlin/rasel/lunar/launcher/utils/BLog.kt +++ b/app/src/main/kotlin/rasel/lunar/launcher/utils/BLog.kt @@ -5,7 +5,7 @@ import rasel.lunar.launcher.BuildConfig import java.lang.Exception object BLog { - val DEFAULT_TAG = "MyEBook_TAG" + val DEFAULT_TAG = "Lunatic" enum class BLogType { D,I,E } diff --git a/app/src/main/kotlin/rasel/lunar/launcher/view/CircleImageView.kt b/app/src/main/kotlin/rasel/lunar/launcher/view/CircleImageView.kt index f0d18906..27fb7ec0 100644 --- a/app/src/main/kotlin/rasel/lunar/launcher/view/CircleImageView.kt +++ b/app/src/main/kotlin/rasel/lunar/launcher/view/CircleImageView.kt @@ -38,6 +38,7 @@ class CircleImageView : androidx.appcompat.widget.AppCompatImageView { private val mShaderMatrix: Matrix = Matrix() private val mBitmapPaint: Paint = Paint() private val mBorderPaint: Paint = Paint() + private val mLabelPaint: Paint = Paint() private val mCircleBackgroundPaint: Paint = Paint() private var mBorderColor = DEFAULT_BORDER_COLOR @@ -60,6 +61,7 @@ class CircleImageView : androidx.appcompat.widget.AppCompatImageView { private var mBorderOverlay = false private var mDisableCircularTransformation = false + private var label : String = "" constructor(context: Context) : super(context) { init() } @@ -85,6 +87,8 @@ class CircleImageView : androidx.appcompat.widget.AppCompatImageView { DEFAULT_CIRCLE_BACKGROUND_COLOR ) + label = a.getString(R.styleable.CircleImageView_civ_label) ?: "" + a.recycle() init() @@ -110,6 +114,9 @@ class CircleImageView : androidx.appcompat.widget.AppCompatImageView { mCircleBackgroundPaint.setAntiAlias(true) mCircleBackgroundPaint.setColor(mCircleBackgroundColor) + mLabelPaint.color = Color.WHITE + mLabelPaint.isFakeBoldText = true + mLabelPaint.textAlign = Paint.Align.CENTER if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { outlineProvider = OutlineProvider() } @@ -175,6 +182,10 @@ class CircleImageView : androidx.appcompat.widget.AppCompatImageView { mBorderPaint ) } + + if (label.length > 0) { + canvas.drawText(label.toUpperCase(),mBorderRect.centerX(),mBorderRect.height() * 0.98f, mLabelPaint) + } } override fun invalidateDrawable(@NonNull dr: Drawable) { @@ -185,6 +196,7 @@ class CircleImageView : androidx.appcompat.widget.AppCompatImageView { override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) updateDimensions() + mLabelPaint.textSize = h * 0.2f invalidate() } diff --git a/app/src/main/res/drawable/coupang.png b/app/src/main/res/drawable/coupang.png new file mode 100644 index 00000000..3ca44f42 Binary files /dev/null and b/app/src/main/res/drawable/coupang.png differ diff --git a/app/src/main/res/layout/app_drawer.xml b/app/src/main/res/layout/app_drawer.xml index fb71cb0c..927e89d5 100644 --- a/app/src/main/res/layout/app_drawer.xml +++ b/app/src/main/res/layout/app_drawer.xml @@ -110,6 +110,7 @@ style="@style/SearchIcons" android:id="@+id/search_store"/> + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/launcher_home.xml b/app/src/main/res/layout/launcher_home.xml index f656e74c..dda4a512 100644 --- a/app/src/main/res/layout/launcher_home.xml +++ b/app/src/main/res/layout/launcher_home.xml @@ -2,6 +2,7 @@ @@ -29,11 +30,13 @@ android:textIsSelectable="false" android:textSize="@dimen/clockText" android:textStyle="bold" + android:textLocale="en_US" app:layout_constraintBottom_toBottomOf="@+id/batteryProgress" app:layout_constraintEnd_toEndOf="@+id/batteryProgress" app:layout_constraintStart_toStartOf="@+id/batteryProgress" app:layout_constraintTop_toTopOf="@+id/batteryProgress" - app:layout_constraintVertical_bias="0.450" /> + app:layout_constraintVertical_bias="0.450" + tools:ignore="UnusedAttribute" /> + app:layout_constraintVertical_bias="0.075" + tools:ignore="UnusedAttribute" /> + + + + + + + app:layout_constraintTop_toBottomOf="@+id/summaryChoose" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index ff8702de..84d292d5 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -5,5 +5,6 @@ + \ 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 85b2a019..033f6316 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -37,4 +37,28 @@ #000000 + + + + + + + \ No newline at end of file