diff --git a/.vscode/easycode.ignore b/.vscode/easycode.ignore new file mode 100644 index 0000000..84722bf --- /dev/null +++ b/.vscode/easycode.ignore @@ -0,0 +1,13 @@ +node_modules/ +dist/ +vendor/ +cache/ +.*/ +*.min.* +*.test.* +*.spec.* +*.bundle.* +*.bundle-min.* +*.*.js +*.*.ts +*.log \ No newline at end of file diff --git a/LICENSE_ASTER b/LICENSE_ASTER index f288702..5f650fd 100644 --- a/LICENSE_ASTER +++ b/LICENSE_ASTER @@ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: - Copyright (C) + AsterLauncher Copyright (C) 2024 neophtex This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d435c27..2025d81 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -51,6 +51,7 @@ android { } buildFeatures { viewBinding = true + dataBinding = true } applicationVariants.all { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 89b10a5..6e4dfa9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -13,6 +13,10 @@ tools:ignore="QueryAllPackagesPermission" /> + + + diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/Constants.kt b/app/src/main/java/com/github/droidworksstudio/launcher/Constants.kt index fc1a5b9..4719aac 100644 --- a/app/src/main/java/com/github/droidworksstudio/launcher/Constants.kt +++ b/app/src/main/java/com/github/droidworksstudio/launcher/Constants.kt @@ -4,6 +4,9 @@ object Constants { const val PACKAGE_NAME = "app.easy.launcher" const val PACKAGE_NAME_DEBUG = "$PACKAGE_NAME.debug" + const val WIDGETS_PREFS = "EasyLauncherWidgets.pref" + const val APP_WIDGETS_ID = "APP_WIDGETS_ID" + const val PREFS_FILENAME = "EasyLauncher.pref" const val FIRST_LAUNCH = "FIRST_LAUNCH" const val SHOW_DATE = "SHOW_DATE" @@ -39,8 +42,6 @@ object Constants { const val HOME_TIME_ALIGNMENT = "HOME_TIME_ALIGNMENT" const val HOME_APP_ALIGNMENT = "HOME_APP_ALIGNMENT" const val HOME_DAILY_WORD_ALIGNMENT = "HOME_DAILY_WORD_ALIGNMENT" - const val HOME_DRAW_ALIGNMENT = "HOME_DRAW_ALIGNMENT" - const val HOME_BATTERY_ALIGNMENT = "HOME_BATTERY_ALIGNMENT" const val NOTIFICATION_SERVICE = "statusbar" const val NOTIFICATION_MANAGER = "android.app.StatusBarManager" @@ -61,5 +62,6 @@ object Constants { const val URL_GOOGLE_PLAY_STORE = "https://play.google.com/store/search?c=apps&q" const val APP_GOOGLE_PLAY_STORE = "market://search?c=apps&q" + const val APP_WIDGET_HOST_ID = 1024 const val REQUEST_CODE_ENABLE_ADMIN = 123 } \ No newline at end of file diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/helper/AppHelper.kt b/app/src/main/java/com/github/droidworksstudio/launcher/helper/AppHelper.kt index 7e8be3b..4014b08 100644 --- a/app/src/main/java/com/github/droidworksstudio/launcher/helper/AppHelper.kt +++ b/app/src/main/java/com/github/droidworksstudio/launcher/helper/AppHelper.kt @@ -101,11 +101,11 @@ class AppHelper @Inject constructor() { fun dayNightMod(context: Context, view: View) { when (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) { Configuration.UI_MODE_NIGHT_YES -> { - view.setBackgroundColor(context.resources.getColor(R.color.blackTrans50, context.theme)) + view.setBackgroundColor(context.resources.getColor(R.color.blackTrans25, context.theme)) } Configuration.UI_MODE_NIGHT_NO -> { - view.setBackgroundColor(context.resources.getColor(R.color.whiteTrans50, context.theme)) + view.setBackgroundColor(context.resources.getColor(R.color.whiteTrans25, context.theme)) } } } diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/ui/activities/MainActivity.kt b/app/src/main/java/com/github/droidworksstudio/launcher/ui/activities/MainActivity.kt index 0ba2a54..08582eb 100644 --- a/app/src/main/java/com/github/droidworksstudio/launcher/ui/activities/MainActivity.kt +++ b/app/src/main/java/com/github/droidworksstudio/launcher/ui/activities/MainActivity.kt @@ -78,7 +78,7 @@ class MainActivity : AppCompatActivity() { } private fun observeUI() { - binding.pager.currentItem = 0 + binding.pager.currentItem = 1 preferenceViewModel.setShowStatusBar(preferenceHelper.showStatusBar) preferenceViewModel.showStatusBarLiveData.observe(this) { if (it) appHelper.showStatusBar(this.window) diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/ui/drawer/DrawFragment.kt b/app/src/main/java/com/github/droidworksstudio/launcher/ui/drawer/DrawFragment.kt index ccd6ed9..e9f3512 100644 --- a/app/src/main/java/com/github/droidworksstudio/launcher/ui/drawer/DrawFragment.kt +++ b/app/src/main/java/com/github/droidworksstudio/launcher/ui/drawer/DrawFragment.kt @@ -40,7 +40,7 @@ class DrawFragment : Fragment(), OnItemClickedListener.OnAppsClickedListener, OnItemClickedListener.OnAppLongClickedListener, OnItemClickedListener.BottomSheetDismissListener, OnItemClickedListener.OnAppStateClickListener, - FingerprintHelper.Callback{ + FingerprintHelper.Callback { private var _binding: FragmentDrawBinding? = null private val binding get() = _binding!! @@ -181,14 +181,12 @@ class DrawFragment : Fragment(), OnItemClickedListener.OnAppsClickedListener, override fun onResume() { super.onResume() observeDrawerApps() - binding.drawAdapter.scrollToPosition(0) if (preferenceHelper.automaticKeyboard) binding.searchViewText.showKeyboard() } override fun onStart() { super.onStart() observeDrawerApps() - binding.drawAdapter.scrollToPosition(0) } override fun onStop() { diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/ui/viewpager/ViewPagerAdapter.kt b/app/src/main/java/com/github/droidworksstudio/launcher/ui/viewpager/ViewPagerAdapter.kt index fc33d53..8d36ccf 100644 --- a/app/src/main/java/com/github/droidworksstudio/launcher/ui/viewpager/ViewPagerAdapter.kt +++ b/app/src/main/java/com/github/droidworksstudio/launcher/ui/viewpager/ViewPagerAdapter.kt @@ -6,12 +6,14 @@ import androidx.lifecycle.Lifecycle import androidx.viewpager2.adapter.FragmentStateAdapter import com.github.droidworksstudio.launcher.ui.drawer.DrawFragment import com.github.droidworksstudio.launcher.ui.home.HomeFragment +import com.github.droidworksstudio.launcher.ui.widgetmanager.WidgetManagerFragment class ViewPagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) : FragmentStateAdapter(fragmentManager, lifecycle) { private val fragments: ArrayList = arrayListOf( + WidgetManagerFragment(), HomeFragment(), DrawFragment(), ) @@ -23,5 +25,4 @@ class ViewPagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) : override fun createFragment(position: Int): Fragment { return fragments[position] } - } diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/ui/widgetmanager/WidgetAdapter.kt b/app/src/main/java/com/github/droidworksstudio/launcher/ui/widgetmanager/WidgetAdapter.kt new file mode 100644 index 0000000..67c6041 --- /dev/null +++ b/app/src/main/java/com/github/droidworksstudio/launcher/ui/widgetmanager/WidgetAdapter.kt @@ -0,0 +1,58 @@ +package com.github.droidworksstudio.launcher.ui.widgetmanager + +import android.appwidget.AppWidgetHost +import android.appwidget.AppWidgetManager +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.recyclerview.widget.RecyclerView +import com.github.droidworksstudio.launcher.R + +class WidgetAdapter( + private val context: Context, + private val appWidgetHost: AppWidgetHost, + private val onWidgetClick: (Int) -> Unit +) : RecyclerView.Adapter() { + + private val widgetItems = mutableListOf() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WidgetViewHolder { + val itemView = LayoutInflater.from(parent.context).inflate(R.layout.item_widget, parent, false) + return WidgetViewHolder(itemView) + } + + override fun onBindViewHolder(holder: WidgetViewHolder, position: Int) { + val widgetItem = widgetItems[position] + holder.bind(widgetItem) + } + + override fun getItemCount(): Int = widgetItems.size + + fun addWidget(widgetItem: WidgetItem) { + widgetItems.add(widgetItem) + notifyItemInserted(widgetItems.size - 1) + } + + fun removeWidget(appWidgetId: Int) { + val position = widgetItems.indexOfFirst { it.appWidgetId == appWidgetId } + if (position != -1) { + widgetItems.removeAt(position) + notifyItemRemoved(position) + } + } + + fun getWidgetIds(): List = widgetItems.map { it.appWidgetId } + + inner class WidgetViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + fun bind(widgetItem: WidgetItem) { + val hostView = appWidgetHost.createView(context, widgetItem.appWidgetId, widgetItem.appWidgetInfo) + itemView.findViewById(R.id.widget_frame).addView(hostView) + + itemView.setOnClickListener { + onWidgetClick(widgetItem.appWidgetId) + } + } + } +} diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/ui/widgetmanager/WidgetItem.kt b/app/src/main/java/com/github/droidworksstudio/launcher/ui/widgetmanager/WidgetItem.kt new file mode 100644 index 0000000..3748501 --- /dev/null +++ b/app/src/main/java/com/github/droidworksstudio/launcher/ui/widgetmanager/WidgetItem.kt @@ -0,0 +1,5 @@ +package com.github.droidworksstudio.launcher.ui.widgetmanager + +import android.appwidget.AppWidgetProviderInfo + +data class WidgetItem(val appWidgetId: Int, val appWidgetInfo: AppWidgetProviderInfo?) diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/ui/widgetmanager/WidgetManagerFragment.kt b/app/src/main/java/com/github/droidworksstudio/launcher/ui/widgetmanager/WidgetManagerFragment.kt new file mode 100644 index 0000000..6936db2 --- /dev/null +++ b/app/src/main/java/com/github/droidworksstudio/launcher/ui/widgetmanager/WidgetManagerFragment.kt @@ -0,0 +1,182 @@ +package com.github.droidworksstudio.launcher.ui.widgetmanager + +import android.app.Activity +import android.appwidget.AppWidgetHost +import android.appwidget.AppWidgetManager +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.LinearLayoutManager +import com.github.droidworksstudio.launcher.Constants +import com.github.droidworksstudio.launcher.databinding.FragmentWidgetManagerBinding +import com.github.droidworksstudio.launcher.helper.AppHelper +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject + +/** + * A simple [Fragment] subclass as the second destination in the navigation. + */ +@AndroidEntryPoint +class WidgetManagerFragment : Fragment(), WidgetOptionsDialogFragment.WidgetOptionsListener { + + private var _binding: FragmentWidgetManagerBinding? = null + + private val binding get() = _binding!! + + private lateinit var pickWidgetLauncher: ActivityResultLauncher + private lateinit var createWidgetLauncher: ActivityResultLauncher + + private lateinit var appWidgetManager: AppWidgetManager + private lateinit var appWidgetHost: AppWidgetHost + private lateinit var widgetAdapter: WidgetAdapter + + @Inject + lateinit var appHelper: AppHelper + + private lateinit var context: Context + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + + _binding = FragmentWidgetManagerBinding.inflate(inflater, container, false) + + return binding.root + + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + appHelper.dayNightMod(requireContext(), binding.drawBackground) + super.onViewCreated(view, savedInstanceState) + + context = requireContext() + + appWidgetManager = AppWidgetManager.getInstance(requireContext()) + appWidgetHost = AppWidgetHost(requireContext(), Constants.APP_WIDGET_HOST_ID) + + widgetAdapter = WidgetAdapter(requireContext(), appWidgetHost) { appWidgetId -> + showWidgetOptionsDialog(appWidgetId) + } + + binding.widgetContainer.apply { + layoutManager = LinearLayoutManager(requireContext()) + adapter = widgetAdapter + } + + // Load saved widgets + val savedWidgetIds = loadWidgetIds() + for (widgetId in savedWidgetIds) { + addWidget(widgetId) + } + + // Initialize the ActivityResultLaunchers + pickWidgetLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode == Activity.RESULT_OK) { + val data = result.data + data?.let { + val appWidgetId = it.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) + if (appWidgetId != -1) { + val appWidgetInfo = appWidgetManager.getAppWidgetInfo(appWidgetId) + if (appWidgetInfo?.configure != null) { + val intent = Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE) + intent.component = appWidgetInfo.configure + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) + createWidgetLauncher.launch(intent) + } else { + addWidget(appWidgetId) + } + } + } + } else if (result.resultCode == Activity.RESULT_CANCELED) { + val appWidgetId = result.data?.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) + if (appWidgetId != null && appWidgetId != -1) { + appWidgetHost.deleteAppWidgetId(appWidgetId) + } + } + } + + createWidgetLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode == Activity.RESULT_OK) { + val data = result.data + data?.let { + val appWidgetId = it.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) + if (appWidgetId != -1) { + addWidget(appWidgetId) + } + } + } else if (result.resultCode == Activity.RESULT_CANCELED) { + val appWidgetId = result.data?.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) + if (appWidgetId != null && appWidgetId != -1) { + appWidgetHost.deleteAppWidgetId(appWidgetId) + } + } + } + + // Set long-click listener on the root view + binding.widgetParent.setOnLongClickListener { + selectWidget() + true // Indicate that the long-click event has been handled + } + } + + private fun selectWidget() { + val appWidgetId = appWidgetHost.allocateAppWidgetId() + val intent = Intent(AppWidgetManager.ACTION_APPWIDGET_PICK) + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) + pickWidgetLauncher.launch(intent) + } + + + private fun addWidget(appWidgetId: Int) { + val appWidgetInfo = appWidgetManager.getAppWidgetInfo(appWidgetId) + appWidgetInfo?.let { + val widgetItem = WidgetItem(appWidgetId, it) + widgetAdapter.addWidget(widgetItem) + saveWidgetIds(widgetAdapter.getWidgetIds()) // Save the updated widget IDs + } ?: Log.e("WidgetManagerFragment", "Failed to get app widget info for ID: $appWidgetId") + } + + override fun onRemoveWidget(appWidgetId: Int) { + appWidgetHost.deleteAppWidgetId(appWidgetId) + widgetAdapter.removeWidget(appWidgetId) + saveWidgetIds(widgetAdapter.getWidgetIds()) + } + + private fun showWidgetOptionsDialog(appWidgetId: Int) { + val dialog = WidgetOptionsDialogFragment.newInstance(appWidgetId) + dialog.show(childFragmentManager, "WidgetOptionsDialog") + } + + override fun onOpenAppWidget(appWidgetId: Int) { + // Implement the logic to open the widget's configuration or app + // This is typically dependent on the widget and its associated app + } + + private fun saveWidgetIds(widgetIds: List) { + val sharedPreferences = requireContext().getSharedPreferences(Constants.WIDGETS_PREFS, Activity.MODE_PRIVATE) + val editor = sharedPreferences.edit() + val widgetIdsSet = widgetIds.map { it.toString() }.toSet() + editor.putStringSet(Constants.APP_WIDGETS_ID, widgetIdsSet) + editor.apply() + } + + private fun loadWidgetIds(): List { + val sharedPreferences = requireContext().getSharedPreferences(Constants.WIDGETS_PREFS, Activity.MODE_PRIVATE) + val widgetIdsSet = sharedPreferences.getStringSet(Constants.APP_WIDGETS_ID, emptySet()) + return widgetIdsSet?.map { it.toInt() } ?: emptyList() + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} + diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/ui/widgetmanager/WidgetOptionsDialogFragment.kt b/app/src/main/java/com/github/droidworksstudio/launcher/ui/widgetmanager/WidgetOptionsDialogFragment.kt new file mode 100644 index 0000000..2bcc626 --- /dev/null +++ b/app/src/main/java/com/github/droidworksstudio/launcher/ui/widgetmanager/WidgetOptionsDialogFragment.kt @@ -0,0 +1,47 @@ +package com.github.droidworksstudio.launcher.ui.widgetmanager + +import android.annotation.SuppressLint +import android.app.Dialog +import android.os.Bundle +import android.view.LayoutInflater +import android.widget.Button +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.DialogFragment +import com.github.droidworksstudio.launcher.Constants +import com.github.droidworksstudio.launcher.R + +class WidgetOptionsDialogFragment : DialogFragment() { + + interface WidgetOptionsListener { + fun onRemoveWidget(appWidgetId: Int) + fun onOpenAppWidget(appWidgetId: Int) + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val appWidgetId = arguments?.getInt(ARG_APP_WIDGET_ID) ?: return super.onCreateDialog(savedInstanceState) + + val builder = AlertDialog.Builder(requireContext()) + builder.setTitle("Widget Options") + .setItems(arrayOf("Remove Widget", "Open Widget")) { _, which -> + val listener = parentFragment as? WidgetOptionsListener + when (which) { + 0 -> listener?.onRemoveWidget(appWidgetId) + 1 -> listener?.onOpenAppWidget(appWidgetId) + } + } + return builder.create() + } + + companion object { + private const val ARG_APP_WIDGET_ID = Constants.APP_WIDGETS_ID + + fun newInstance(appWidgetId: Int): WidgetOptionsDialogFragment { + val fragment = WidgetOptionsDialogFragment() + val args = Bundle().apply { + putInt(ARG_APP_WIDGET_ID, appWidgetId) + } + fragment.arguments = args + return fragment + } + } +} diff --git a/app/src/main/res/layout/fragment_widget_manager.xml b/app/src/main/res/layout/fragment_widget_manager.xml new file mode 100644 index 0000000..e017890 --- /dev/null +++ b/app/src/main/res/layout/fragment_widget_manager.xml @@ -0,0 +1,26 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_widget.xml b/app/src/main/res/layout/item_widget.xml new file mode 100644 index 0000000..0196973 --- /dev/null +++ b/app/src/main/res/layout/item_widget.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/widget_options_dialog.xml b/app/src/main/res/layout/widget_options_dialog.xml new file mode 100644 index 0000000..16207b8 --- /dev/null +++ b/app/src/main/res/layout/widget_options_dialog.xml @@ -0,0 +1,29 @@ + + +