Skip to content

Commit

Permalink
Feat: Basic starter to add Widget support.
Browse files Browse the repository at this point in the history
  • Loading branch information
HeCodes2Much committed May 22, 2024
1 parent 364fe2b commit a3c4e6f
Show file tree
Hide file tree
Showing 16 changed files with 382 additions and 10 deletions.
13 changes: 13 additions & 0 deletions .vscode/easycode.ignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
node_modules/
dist/
vendor/
cache/
.*/
*.min.*
*.test.*
*.spec.*
*.bundle.*
*.bundle-min.*
*.*.js
*.*.ts
*.log
2 changes: 1 addition & 1 deletion LICENSE_ASTER
Original file line number Diff line number Diff line change
Expand Up @@ -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:

<program> Copyright (C) <year> <name of author>
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.
Expand Down
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ android {
}
buildFeatures {
viewBinding = true
dataBinding = true
}

applicationVariants.all {
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
tools:ignore="QueryAllPackagesPermission" />
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
<uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
<uses-permission android:name="android.permission.BIND_APPWIDGET"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />


<queries>
<intent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand All @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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!!
Expand Down Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Fragment> = arrayListOf(
WidgetManagerFragment(),
HomeFragment(),
DrawFragment(),
)
Expand All @@ -23,5 +25,4 @@ class ViewPagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) :
override fun createFragment(position: Int): Fragment {
return fragments[position]
}

}
Original file line number Diff line number Diff line change
@@ -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<WidgetAdapter.WidgetViewHolder>() {

private val widgetItems = mutableListOf<WidgetItem>()

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<Int> = 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<FrameLayout>(R.id.widget_frame).addView(hostView)

itemView.setOnClickListener {
onWidgetClick(widgetItem.appWidgetId)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.github.droidworksstudio.launcher.ui.widgetmanager

import android.appwidget.AppWidgetProviderInfo

data class WidgetItem(val appWidgetId: Int, val appWidgetInfo: AppWidgetProviderInfo?)
Original file line number Diff line number Diff line change
@@ -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<Intent>
private lateinit var createWidgetLauncher: ActivityResultLauncher<Intent>

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<Int>) {
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<Int> {
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
}
}

Loading

0 comments on commit a3c4e6f

Please sign in to comment.