Skip to content

Commit

Permalink
feat: crude icon selector port
Browse files Browse the repository at this point in the history
  • Loading branch information
butzist committed Feb 17, 2024
1 parent 4719e51 commit 5d423e6
Show file tree
Hide file tree
Showing 7 changed files with 280 additions and 261 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ abstract class ServicesModule {
abstract fun bindIconCreatorService(
iconCreatorServiceImpl: IconCreatorServiceImpl
): IconCreatorService

@ActivityScoped
@Binds
abstract fun bindIconLoaderService(
iconLoaderServiceImpl: IconLoaderServiceImpl
): IconLoaderService
}

@Module
Expand Down
Original file line number Diff line number Diff line change
@@ -1,46 +1,106 @@
package de.szalkowski.activitylauncher.todo;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Build;

class IconLoader {
private final PackageManager pm;
private final Context context;

IconLoader(Context context) {
this.context = context;
this.pm = context.getPackageManager();
}
package de.szalkowski.activitylauncher.services

@TargetApi(19)
static Drawable getDrawable(Resources res, int id) {
return res.getDrawable(id);
}
import android.annotation.TargetApi
import android.content.Context
import android.content.pm.PackageManager
import android.content.pm.PackageManager.NameNotFoundException
import android.content.res.Resources
import android.content.res.Resources.Theme
import android.graphics.drawable.Drawable
import android.os.Build
import android.util.DisplayMetrics
import android.widget.Toast
import dagger.hilt.android.qualifiers.ActivityContext
import de.szalkowski.activitylauncher.R
import de.szalkowski.activitylauncher.ui.AsyncProvider
import de.szalkowski.activitylauncher.ui.IconListAdapter
import java.util.TreeMap
import javax.inject.Inject

@TargetApi(21)
static Drawable getDrawable(Resources res, int id, Resources.Theme theme) {
return res.getDrawable(id, theme);
}
interface IconLoaderService {
fun getIcon(iconResourceString: String): Drawable
fun tryGetIcon(iconResourceString: String): Result<Drawable>
fun loadIcons(updater: AsyncProvider<IconListAdapter>.Updater?): List<IconInfo>

data class IconInfo(
val iconResourceName: String, val icon: Drawable
)

class NullResourceException : Exception("Resource ID is zero")
}

class IconLoaderServiceImpl @Inject constructor(
@ActivityContext private val context: Context,
private val packageListService: PackageListService,
private val activityListService: ActivityListService,
settingsService: SettingsService,
) : IconLoaderService {
private val pm: PackageManager = context.packageManager
private val configuration = settingsService.getLocaleConfiguration()

override fun getIcon(iconResourceString: String): Drawable =
tryGetIcon(iconResourceString).getOrElse {
val errorText = when (it) {
is IconLoaderService.NullResourceException -> R.string.error_invalid_icon_resource
is NameNotFoundException -> R.string.error_invalid_icon_resource
else -> R.string.error_invalid_icon_format
}

Toast.makeText(context, errorText, Toast.LENGTH_LONG).show()
pm.defaultActivityIcon
}


override fun tryGetIcon(iconResourceString: String): Result<Drawable> {
return runCatching {
val pack = iconResourceString.substringBefore(":")
val typeAndName = iconResourceString.substringAfter(":")
val type = typeAndName.substringBefore("/")
val name = typeAndName.substringAfter("/")

val res = pm.getResourcesForApplication(pack)
res.updateConfiguration(configuration, DisplayMetrics())
val id = res.getIdentifier(name, type, pack)

Drawable getIcon(String icon_resource_string) {
try {
String pack = icon_resource_string.substring(0, icon_resource_string.indexOf(':'));
String type = icon_resource_string.substring(icon_resource_string.indexOf(':') + 1, icon_resource_string.indexOf('/'));
String name = icon_resource_string.substring(icon_resource_string.indexOf('/') + 1);
Resources res = this.pm.getResourcesForApplication(pack);
int id = res.getIdentifier(name, type, pack);
if (id == 0) throw IconLoaderService.NullResourceException()

if (Build.VERSION.SDK_INT >= 21) {
return IconLoader.getDrawable(res, id, this.context.getTheme());
getDrawable(res, id, context.theme)
} else {
return IconLoader.getDrawable(res, id);
getDrawable(res, id)
}
} catch (Exception e) {
return this.pm.getDefaultActivityIcon();
}
}
}

override fun loadIcons(updater: AsyncProvider<IconListAdapter>.Updater?): List<IconLoaderService.IconInfo> {
val icons: TreeMap<String, Drawable> = TreeMap()

val packages = packageListService.packages
updater?.updateMax(packages.size)
updater?.update(0)

for (pack in packages.withIndex()) {
updater?.update(pack.index + 1)

runCatching {
val activities = activityListService.getActivities(pack.value.packageName)
for (activity in listOfNotNull(activities.defaultActivity) + activities.activities) {
activity.iconResourceName?.let { icons[it] = activity.icon }
}
}
}

return icons.map { entry -> IconLoaderService.IconInfo(entry.key, entry.value) }.toList()
}

companion object {
private fun getDrawable(res: Resources, id: Int): Drawable {
return res.getDrawable(id)
}

@TargetApi(21)
private fun getDrawable(res: Resources, id: Int, theme: Theme?): Drawable {
return res.getDrawable(id, theme)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.navArgs
import dagger.hilt.android.AndroidEntryPoint
import de.szalkowski.activitylauncher.databinding.FragmentActivityDetailsBinding
import de.szalkowski.activitylauncher.services.ActivityLauncherService
import de.szalkowski.activitylauncher.services.ActivityListService
import de.szalkowski.activitylauncher.services.IconCreatorService
import de.szalkowski.activitylauncher.services.IconLoaderService
import de.szalkowski.activitylauncher.services.MyActivityInfo
import javax.inject.Inject

Expand All @@ -29,6 +31,9 @@ class ActivityDetailsFragment : Fragment() {
@Inject
internal lateinit var iconCreatorService: IconCreatorService

@Inject
internal lateinit var iconLoaderService: IconLoaderService

private var _binding: FragmentActivityDetailsBinding? = null

// This property is only valid between onCreateView and
Expand All @@ -53,15 +58,27 @@ class ActivityDetailsFragment : Fragment() {

val actionBar = activity as? ActionBarSearch
// FIXME just hide the search menu item, instead
actionBar?.actionBarSearchText = ""
actionBar?.actionBarSearchText = ""

binding.tiName.setText(activityInfo.name)
binding.tiPackage.setText(activityInfo.componentName.packageName)
binding.tiClass.setText(activityInfo.componentName.shortClassName)
binding.tiIcon.setText(activityInfo.iconResourceName ?: "")
binding.ibIconPicker.setImageDrawable(activityInfo.icon)

// TODO binding.ibIconPicker
binding.ibIconPicker.setOnClickListener {
val dialog = IconPickerDialogFragment()
dialog.attachIconPickerListener { icon ->
binding.tiIcon.setText(icon)
}
dialog.show(childFragmentManager, "icon picker")
}

binding.tiIcon.doAfterTextChanged { text ->
val icon = text.toString()
val drawable = iconLoaderService.getIcon(icon)
binding.ibIconPicker.setImageDrawable(drawable)
}

binding.btCreateShortcut.setOnClickListener {
iconCreatorService.createLauncherIcon(editedActivityInfo)
Expand Down
137 changes: 59 additions & 78 deletions app/src/main/java/de/szalkowski/activitylauncher/ui/AsyncProvider.kt
Original file line number Diff line number Diff line change
@@ -1,106 +1,87 @@
package de.szalkowski.activitylauncher.todo;

import android.content.Context;
import android.os.AsyncTask;
import android.view.LayoutInflater;

import androidx.appcompat.app.AlertDialog;

import java.text.NumberFormat;
import java.util.Locale;

import de.szalkowski.activitylauncher.R;
import de.szalkowski.activitylauncher.databinding.ProgressDialogBinding;

public abstract class AsyncProvider<ReturnType> extends AsyncTask<Void, Integer, ReturnType> {
private final CharSequence message;
private final Listener<ReturnType> listener;
private final AlertDialog dialog;
private final ProgressDialogBinding binding;
private final NumberFormat progressPercentFormat = NumberFormat.getPercentInstance();
private int max;

AsyncProvider(Context context, Listener<ReturnType> listener, boolean showProgressDialog) {
this.message = context.getText(R.string.dialog_progress_loading);
this.listener = listener;

package de.szalkowski.activitylauncher.ui

import android.content.Context
import android.os.AsyncTask
import android.view.LayoutInflater
import androidx.appcompat.app.AlertDialog
import de.szalkowski.activitylauncher.R
import de.szalkowski.activitylauncher.databinding.ProgressDialogBinding
import java.text.NumberFormat
import java.util.Locale

abstract class AsyncProvider<ReturnType> internal constructor(
context: Context, private val listener: Listener<ReturnType>?, showProgressDialog: Boolean
) : AsyncTask<Void?, Int?, ReturnType>() {
private val message = context.getText(R.string.dialog_progress_loading)
private var dialog: AlertDialog? = null
private var binding: ProgressDialogBinding? = null
private val progressPercentFormat: NumberFormat = NumberFormat.getPercentInstance()
private var max = 0

init {
if (showProgressDialog) {
this.binding = ProgressDialogBinding.inflate(LayoutInflater.from(context));
this.dialog = new AlertDialog.Builder(context).setView(binding.getRoot()).create();
this.progressPercentFormat.setMaximumFractionDigits(0);
this.binding = ProgressDialogBinding.inflate(LayoutInflater.from(context))
this.dialog = AlertDialog.Builder(context).setView(binding!!.getRoot()).create()
progressPercentFormat.maximumFractionDigits = 0
} else {
this.binding = null;
this.dialog = null;
this.binding = null
this.dialog = null
}
}

@Override
protected void onProgressUpdate(Integer... values) {
if (this.binding != null && values.length > 0) {
int value = values[0];
protected fun onProgressUpdate(vararg values: Int) {
if (values.isNotEmpty()) {
val value = values[0]

if (value == 0) {
this.binding.progress.setIndeterminate(false);
this.binding.progress.setMax(this.max);
binding?.progress?.isIndeterminate = false
binding?.progress?.max = this.max
}

this.binding.progress.setProgress(value);
this.binding.progressNumber.setText(String.format(Locale.getDefault(), "%1d/%2d", value, this.max));
double percent = (double) value / (double) this.max;
this.binding.progressPercent.setText(this.progressPercentFormat.format(percent));
binding?.progress?.progress = value
binding?.progressNumber?.text = String.format(
Locale.getDefault(), "%1d/%2d", value, this.max
)
val percent = value.toDouble() / max.toDouble()
binding?.progressPercent?.text = progressPercentFormat.format(percent)
}
}

@Override
protected void onPreExecute() {
super.onPreExecute();
override fun onPreExecute() {
super.onPreExecute()

if (this.dialog != null && this.binding != null) {
this.dialog.setCancelable(false);
this.dialog.setTitle(this.message);
this.binding.progress.setIndeterminate(true);
this.dialog.show();
}
dialog?.setCancelable(false)
dialog?.setTitle(this.message)
binding?.progress?.isIndeterminate = true
dialog?.show()
}

@Override
protected void onPostExecute(ReturnType result) {
super.onPostExecute(result);
if (this.listener != null) {
this.listener.onProviderFinished(this, result);
}
override fun onPostExecute(result: ReturnType) {
super.onPostExecute(result)
listener?.onProviderFinished(this, result)

if (this.dialog != null) {
try {
this.dialog.dismiss();
} catch (IllegalArgumentException e) { /* ignore */ }
runCatching {
dialog?.dismiss()
}
}

abstract protected ReturnType run(Updater updater);
protected abstract fun run(updater: Updater?): ReturnType

@Override
protected ReturnType doInBackground(Void... params) {
return run(new Updater(this));
override fun doInBackground(vararg params: Void?): ReturnType {
return run(Updater(this))
}

public interface Listener<ReturnType> {
void onProviderFinished(AsyncProvider<ReturnType> task, ReturnType value);
interface Listener<ReturnType> {
fun onProviderFinished(task: AsyncProvider<ReturnType>?, value: ReturnType)
}

class Updater {
private final AsyncProvider<ReturnType> provider;

Updater(AsyncProvider<ReturnType> provider) {
this.provider = provider;
}

void update(int value) {
this.provider.publishProgress(value);
inner class Updater(private val provider: AsyncProvider<ReturnType>) {
fun update(value: Int) {
provider.publishProgress(value)
}

void updateMax(int value) {
this.provider.max = value;
fun updateMax(value: Int) {
provider.max = value
}
}
}
}
Loading

0 comments on commit 5d423e6

Please sign in to comment.