# Conflicts:
#	app/src/main/kotlin/rasel/lunar/launcher/apps/AppDrawer.kt
This commit is contained in:
lunaticbum 2024-08-22 20:44:27 +09:00
commit ed24af2d41
20 changed files with 955 additions and 213 deletions

View File

@ -18,6 +18,7 @@
<uses-permission android:name="com.android.alarm.permission.SET_ALARM"/>
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.READ_CALL_LOG"/>
<!-- api 33+ -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission

View File

@ -27,7 +27,10 @@ import android.graphics.Color
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.CallLog
import android.provider.ContactsContract
import android.provider.Settings
import android.util.Log
import android.view.WindowInsets
import android.view.WindowManager
import androidx.activity.OnBackPressedCallback
@ -44,11 +47,12 @@ import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.color.DynamicColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import rasel.lunar.launcher.apps.AppDrawer
import rasel.lunar.launcher.apps.DismissCalback
import rasel.lunar.launcher.apps.SearchMenu
import rasel.lunar.launcher.databinding.LauncherActivityBinding
import rasel.lunar.launcher.feeds.Feeds
import rasel.lunar.launcher.feeds.WidgetHost
import rasel.lunar.launcher.helpers.Constants.Companion.KEY_APPLICATION_THEME
import rasel.lunar.launcher.helpers.Constants.Companion.KEY_BACK_HOME
import rasel.lunar.launcher.helpers.Constants.Companion.KEY_FIRST_LAUNCH
import rasel.lunar.launcher.helpers.Constants.Companion.KEY_STATUS_BAR
import rasel.lunar.launcher.helpers.Constants.Companion.KEY_WINDOW_BACKGROUND
@ -59,6 +63,7 @@ import rasel.lunar.launcher.helpers.UniUtils.Companion.getColorResId
import rasel.lunar.launcher.helpers.ViewPagerAdapter
import rasel.lunar.launcher.home.LauncherHome
import rasel.lunar.launcher.utils.BLog
import java.util.Date
internal class LauncherActivity : AppCompatActivity() {
@ -152,7 +157,10 @@ internal class LauncherActivity : AppCompatActivity() {
private fun setupView() {
viewPager = binding.viewPager.apply {
adapter = ViewPagerAdapter(
supportFragmentManager, mutableListOf(Feeds(), LauncherHome(), AppDrawer()), lifecycle)
supportFragmentManager,
mutableListOf(Feeds(), LauncherHome(), AppDrawer()),
lifecycle
)
offscreenPageLimit = 1
setCurrentItem(1, false)
reduceDragSensitivity()
@ -227,4 +235,9 @@ internal class LauncherActivity : AppCompatActivity() {
}
}
}
fun openSearchMenus(keyword : String, dismissCalback: DismissCalback) {
SearchMenu().show(supportFragmentManager,keyword) {dismissCalback?.invoke()}
}
}

View File

@ -28,9 +28,11 @@ import android.content.pm.ResolveInfo
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.provider.ContactsContract
import android.util.Log
import android.view.Gravity
import android.view.KeyEvent.ACTION_UP
import android.view.LayoutInflater
import android.view.View
import android.view.View.GONE
@ -42,16 +44,12 @@ import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.GridLayoutManager
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.databinding.AppDrawerBinding
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_KEYBOARD_SEARCH
import rasel.lunar.launcher.helpers.Constants.Companion.KEY_QUICK_LAUNCH
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
@ -63,7 +61,7 @@ internal class AppDrawer : Fragment() {
private lateinit var binding: AppDrawerBinding
private var layoutType: Int = 0
private var isSearchShown: Boolean = false
// private var isSearchShown: Boolean = false
private var isKeyboardShowing: Boolean = false
companion object {
@ -226,6 +224,7 @@ internal class AppDrawer : Fragment() {
}
fun openSearchApps(schemeString : String, pakage : String? = null) {
val gmmIntentUri = Uri.parse(schemeString)
val mapIntent = Intent(Intent.ACTION_VIEW, gmmIntentUri)
@ -238,52 +237,158 @@ internal class AppDrawer : Fragment() {
@SuppressLint("ClickableViewAccessibility")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.reset.setOnClickListener { filterAppsList("") }
// binding.moveDown.setOnClickListener {
// binding.appsList.smoothScrollToPosition(packageList.size - 1)
// }
//
// binding.moveUp.setOnClickListener {
// binding.appsList.smoothScrollToPosition(0)
// }
// binding.search.setOnClickListener {
// when (isSearchShown) {
// true -> 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)
}
}
}

View File

@ -47,7 +47,7 @@ internal class AppsAdapter(
private val appsCount: TextView) : RecyclerView.Adapter<AppsAdapter.AppsViewHolder>() {
private var oldList = mutableListOf<Packages>()
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 (

View File

@ -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 */

View File

@ -58,6 +58,7 @@ import rasel.lunar.launcher.apps.AppDrawer.Companion.appNamesPrefs
import rasel.lunar.launcher.databinding.ActivityBrowserDialogBinding
import rasel.lunar.launcher.databinding.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 */

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
package rasel.lunar.launcher.apps
import android.annotation.SuppressLint
import android.app.ActivityOptions
import android.content.ActivityNotFoundException
import android.content.ComponentName
import android.content.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

View File

@ -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

View File

@ -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<MissedCall>()
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<String>(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<String, MissedCall>()
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 = ""
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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<MissedCall>,
private val context: Context) : RecyclerView.Adapter<MissedCallsAdapter.MissedCallsHolder>() {
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)
}
}
}
}

View File

@ -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
}

View File

@ -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()
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -110,6 +110,7 @@
style="@style/SearchIcons"
android:id="@+id/search_store"/>
</LinearLayout>
<TextView
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"

View File

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

View File

@ -2,6 +2,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -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" />
<TextClock
android:id="@+id/date"
@ -41,12 +44,14 @@
android:layout_height="wrap_content"
android:gravity="center"
android:maxLines="1"
android:textLocale="en_US"
android:textIsSelectable="false"
app:layout_constraintBottom_toBottomOf="@+id/batteryProgress"
app:layout_constraintEnd_toEndOf="@+id/batteryProgress"
app:layout_constraintStart_toStartOf="@+id/batteryProgress"
app:layout_constraintTop_toBottomOf="@+id/time"
app:layout_constraintVertical_bias="0.075" />
app:layout_constraintVertical_bias="0.075"
tools:ignore="UnusedAttribute" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/weather"
@ -61,18 +66,54 @@
app:layout_constraintTop_toBottomOf="@+id/date"
app:layout_constraintVertical_bias="0.100" />
<RadioGroup
android:id="@+id/summaryChoose"
android:layout_width="0dp"
android:gravity="center"
app:layout_constraintLeft_toLeftOf="parent"
android:orientation="horizontal"
app:layout_constraintRight_toRightOf="parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/batteryProgress"
>
<RadioButton
android:id="@+id/missedCalls"
android:button="@null"
android:gravity="center"
android:text="전화"
android:checked="true"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<RadioButton
android:gravity="center"
android:button="@null"
android:text="투두"
android:checked="false"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<RadioButton
android:gravity="center"
android:button="@null"
android:text="문자"
android:checked="false"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RadioGroup>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/notes"
android:layout_width="@dimen/zero"
android:layout_height="@dimen/zero"
android:layout_height="200dp"
android:overScrollMode="never"
android:padding="@dimen/fortyEight"
android:padding="20dp"
android:scrollbars="none"
app:layoutManager="LinearLayoutManager"
app:layout_constraintBottom_toTopOf="@id/favAppsGroup"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/batteryProgress" />
app:layout_constraintTop_toBottomOf="@+id/summaryChoose" />
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/favAppsGroup"

View File

@ -7,7 +7,7 @@
<com.google.android.material.textview.MaterialTextView
android:id="@+id/itemText"
android:layout_width="@dimen/zero"
android:layout_height="wrap_content"
android:layout_height="40dp"
android:padding="@dimen/twelve"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"

View File

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:padding="@dimen/twelve"
android:clickable="true"
android:focusableInTouchMode="true">
<TextView
android:id="@+id/keyworkd"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_height="wrap_content"
android:minWidth="@dimen/zero"
android:textSize="24dp"
android:gravity="center"
android:padding="@dimen/eight"
android:inputType="textNoSuggestions"
/>
<LinearLayout
style="@style/SearchMenus"
android:id="@+id/quickSearch"
android:orientation="horizontal"
app:layout_constraintTop_toBottomOf="@id/keyworkd">
<rasel.lunar.launcher.view.CircleImageView
style="@style/SearchMenuItem"
android:src="@drawable/google"
android:id="@+id/search_google"/>
<rasel.lunar.launcher.view.CircleImageView
style="@style/SearchMenuItem"
android:src="@drawable/naver"
android:id="@+id/search_naver"/>
<rasel.lunar.launcher.view.CircleImageView
style="@style/SearchMenuItem"
app:civ_label="DDuckGo"
android:src="@drawable/duckduckgo"
android:id="@+id/search_duckduckgo"/>
</LinearLayout>
<LinearLayout
android:id="@+id/quickSearch2"
android:orientation="horizontal"
app:layout_constraintTop_toBottomOf="@id/quickSearch"
style="@style/SearchMenus">
<rasel.lunar.launcher.view.CircleImageView
android:src="@drawable/namuwiki"
android:id="@+id/search_namuwiki"
style="@style/SearchMenuItem"/>
<rasel.lunar.launcher.view.CircleImageView
style="@style/SearchMenuItem"
android:src="@drawable/gmap"
android:id="@+id/search_googleMap"/>
<rasel.lunar.launcher.view.CircleImageView
style="@style/SearchMenuItem"
android:src="@drawable/navermap"
android:id="@+id/search_nmap"/>
</LinearLayout>
<LinearLayout
android:id="@+id/quickSearch3"
android:orientation="horizontal"
app:layout_constraintTop_toBottomOf="@id/quickSearch2"
style="@style/SearchMenus">
<rasel.lunar.launcher.view.CircleImageView
style="@style/SearchMenuItem"
android:src="@drawable/coupang"
android:id="@+id/search_coupang"/>
<rasel.lunar.launcher.view.CircleImageView
android:src="@drawable/tmap"
style="@style/SearchMenuItem"
android:id="@+id/search_tmap"/>
<rasel.lunar.launcher.view.CircleImageView
android:src="@drawable/translate"
style="@style/SearchMenuItem"
android:id="@+id/search_translate"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -5,5 +5,6 @@
<attr name="civ_border_color" format="color" />
<attr name="civ_border_overlay" format="boolean" />
<attr name="civ_circle_background_color" format="color" />
<attr name="civ_label" format="string" />
</declare-styleable>
</resources>

View File

@ -37,4 +37,28 @@
<item name="civ_border_color">#000000</item>
</style>
<style name="SearchMenuItem">
<item name="android:layout_margin">5dp</item>
<item name="android:adjustViewBounds">true</item>
<item name="android:scaleType">fitCenter</item>
<item name="android:background">@null</item>
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_weight">1</item>
<item name="civ_border_width">1dp</item>
<item name="civ_border_color">#000000</item>
</style>
<style name="SearchMenus">
<item name="layout_constraintLeft_toLeftOf">parent</item>
<item name="layout_constraintRight_toRightOf">parent</item>
<item name="android:orientation">horizontal</item>
<item name="android:background">@null</item>
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">80dp</item>
</style>
</resources>