diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c538cc7..de50c2c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + mSearchSheet; private CombinedAdapter mSearchAdapter; private Fragment mCurrentFragment; + private Drawable mWallpaperDrawable; private final BroadcastReceiver mPackageReceiver = new BroadcastReceiver() { @Override @@ -105,7 +110,7 @@ protected void onCreate(Bundle savedInstanceState) { mFavouritesHelper = new FavouritesRepository(mExecutor, mMainHandler); mAppLoader = new AppLoader(this); - var appSearchProvider = new AppSearchProvider(mAppLoader); + var appSearchProvider = new AppSearchProvider(mAppLoader, mFavouritesHelper); var searchProviders = List.of( appSearchProvider, new WebSearchProvider(), @@ -120,6 +125,7 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().setDimAmount(0f); setContentView(R.layout.activity_main); + loadWallpaperBackground(); initialiseSearchView(); @@ -475,11 +481,44 @@ private void performSearch(String query) { }); } - private Fragment findPagerFragment(int position) { - long itemId = mPagerAdapter.getItemId(position); + private void loadWallpaperBackground() { + var wallpaperPath = SettingsManager.getWallpaper(); + if (wallpaperPath == null || wallpaperPath.isEmpty()) { + return; + } + + var wallpaperUri = Uri.parse(wallpaperPath); + try (var inputStream = getContentResolver().openInputStream(wallpaperUri)) { + if (inputStream != null) { + mWallpaperDrawable = Drawable.createFromStream(inputStream, wallpaperUri.toString()); + if (mWallpaperDrawable == null) { + return; + } + + setWallpaperDim(); + } + } catch (Exception ignored) { } + } + + private void setWallpaperDim() { + var dimColor = getDimColour(SettingsManager.getWallpaperDimAmount()); + var filter = new BlendModeColorFilter(dimColor, BlendMode.SRC_ATOP); + mWallpaperDrawable.setColorFilter(filter); + var wallpaperHost = (ImageView) findViewById(R.id.wallpaper_image); + wallpaperHost.setImageDrawable(mWallpaperDrawable); + } + + private Fragment findPagerFragment(final int position) { + var itemId = mPagerAdapter.getItemId(position); return getSupportFragmentManager().findFragmentByTag("f" + itemId); } + private int getDimColour(float dimAmount) { + dimAmount = Math.max(0f, Math.min(dimAmount, 1f)); + var alpha = (int) (dimAmount * 255); + return alpha << 24; + } + @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { if (SettingsManager.KEY_ICON_PACK.equals(key) || @@ -490,5 +529,11 @@ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, Strin ) { refreshUi(); } + + if (SettingsManager.KEY_WALLPAPER.equals(key) || + SettingsManager.KEY_WALLPAPER_DIM_AMOUNT.equals(key) + ) { + loadWallpaperBackground(); + } } } \ No newline at end of file diff --git a/app/src/main/java/com/chadderbox/launchbox/search/AppSearchProvider.java b/app/src/main/java/com/chadderbox/launchbox/search/AppSearchProvider.java index 1eeb60c..2e8649a 100644 --- a/app/src/main/java/com/chadderbox/launchbox/search/AppSearchProvider.java +++ b/app/src/main/java/com/chadderbox/launchbox/search/AppSearchProvider.java @@ -6,6 +6,7 @@ import com.chadderbox.launchbox.data.AppItem; import com.chadderbox.launchbox.data.ListItem; import com.chadderbox.launchbox.utils.AppLoader; +import com.chadderbox.launchbox.utils.FavouritesRepository; import java.util.ArrayList; import java.util.List; @@ -20,9 +21,11 @@ public final class AppSearchProvider implements ISearchProvider { private final ExecutorService mExecutor = Executors.newSingleThreadExecutor(); private final AppLoader mAppLoader; + private final FavouritesRepository mFavouritesRepository; - public AppSearchProvider(AppLoader appLoader) { + public AppSearchProvider(AppLoader appLoader, FavouritesRepository favouritesRepository) { mAppLoader = appLoader; + mFavouritesRepository = favouritesRepository; } @Override @@ -35,18 +38,29 @@ public int getPriority() { public void searchAsync(String query, Consumer> callback) { mExecutor.execute(() -> { var searchQuery = query.toLowerCase(Locale.getDefault()); + + var favourites = mFavouritesRepository.loadFavourites(); + var results = new ArrayList(); + var favouriteResults = new ArrayList(); + var normalResults = new ArrayList(); var fuzzyResults = new ArrayList(); var apps = mAppLoader.getInstalledApps(); for (var app : apps) { if (app.getLabel().toLowerCase(Locale.getDefault()).contains(searchQuery)) { - results.add(new AppItem(app)); + if (favourites.contains(app.getPackageName())) { + favouriteResults.add(new AppItem(app)); + } else { + normalResults.add(new AppItem(app)); + } } else if (searchQuery.length() > 3 && SearchHelpers.calculateLevenshteinDistance(searchQuery, app.getLabel().toLowerCase(Locale.getDefault())) < LEVENSHTEIN_HEURISTIC) { fuzzyResults.add(new AppItem(app)); } } - // We want the fuzzy results to show after actual search results + // Show in the order that the user probably wants them + results.addAll(favouriteResults); + results.addAll(normalResults); results.addAll(fuzzyResults); new Handler(Looper.getMainLooper()).post(() -> callback.accept(results)); diff --git a/app/src/main/java/com/chadderbox/launchbox/settings/SettingsActivity.java b/app/src/main/java/com/chadderbox/launchbox/settings/SettingsActivity.java index cb60cd9..94abf0c 100644 --- a/app/src/main/java/com/chadderbox/launchbox/settings/SettingsActivity.java +++ b/app/src/main/java/com/chadderbox/launchbox/settings/SettingsActivity.java @@ -10,6 +10,8 @@ import android.widget.TextView; import android.widget.Toast; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; @@ -18,13 +20,16 @@ import androidx.recyclerview.widget.RecyclerView; import com.chadderbox.launchbox.R; +import com.chadderbox.launchbox.utils.FileHelpers; import java.util.ArrayList; import java.util.Objects; public final class SettingsActivity extends AppCompatActivity { + private SettingOptionAdapter mOptionsAdapter; private ArrayList mOptions; + private ActivityResultLauncher mWallpaperPickerLauncher; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { @@ -39,6 +44,25 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { actionBar.setDisplayHomeAsUpEnabled(true); } + mWallpaperPickerLauncher = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == RESULT_OK && result.getData() != null) { + var selectedImageUri = result.getData().getData(); + if (selectedImageUri != null) { + + // By selecting it, we gain permission to access it + // That's pretty neat actually, but it's gonna be super annoying if we don't save that permission + getContentResolver().takePersistableUriPermission(selectedImageUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); + + SettingsManager.setWallpaper(selectedImageUri.toString()); + Toast.makeText(SettingsActivity.this, "Wallpaper selected", Toast.LENGTH_SHORT).show(); + refreshOptions(); + } + } + } + ); + buildOptions(); } @@ -83,6 +107,18 @@ private void buildOptions() { ctx -> showThemeDialog() )); + mOptions.add(new SettingOption( + "Wallpaper", + ctx -> getCurrentWallpaper(), + ctx -> showWallpaperDialog() + )); + + mOptions.add(new SettingOption( + "Wallpaper Dim Percentage", + ctx -> getWallpaperDimPercentage(), + ctx -> showWallpaperDimDialog() + )); + mOptions.add(new SettingOption( "Other settings…", ctx -> "More coming soon", @@ -95,7 +131,15 @@ private void buildOptions() { private void setupOptions() { var recyclerView = (RecyclerView) findViewById(R.id.recyclerViewSettings); recyclerView.setLayoutManager(new LinearLayoutManager(this)); - recyclerView.setAdapter(new SettingOptionAdapter(mOptions)); + + mOptionsAdapter = new SettingOptionAdapter(mOptions); + recyclerView.setAdapter(mOptionsAdapter); + } + + @SuppressLint("NotifyDataSetChanged") + private void refreshOptions() { + // Re-evaluate all options + mOptionsAdapter.notifyDataSetChanged(); } private void showCharacterHeadingDialog() { @@ -114,7 +158,7 @@ private void showCharacterHeadingDialog() { SettingsManager.setCharacterHeadings(enabled); Toast.makeText(this, "Character headings: " + (enabled ? "On" : "Off"), Toast.LENGTH_SHORT).show(); - buildOptions(); + refreshOptions(); dialog.dismiss(); }) .setNegativeButton("Cancel", null) @@ -161,7 +205,7 @@ private void showIconPackDialog() { : "Icon pack applied: " + names.get(which), Toast.LENGTH_SHORT).show(); - buildOptions(); + refreshOptions(); }) .setNegativeButton("Cancel", null) .show(); @@ -187,7 +231,7 @@ private void showFontDialog() { SettingsManager.setFont(chosenFont); Toast.makeText(this, "Font applied: " + chosenFont, Toast.LENGTH_SHORT).show(); - buildOptions(); + refreshOptions(); }) .setNegativeButton("Cancel", null) .show(); @@ -232,7 +276,7 @@ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { SettingsManager.setFontSize(selectedSize); Toast.makeText(this, "Size applied: " + selectedSize, Toast.LENGTH_SHORT).show(); - buildOptions(); + refreshOptions(); }) .setNegativeButton("Cancel", null) .show(); @@ -255,7 +299,7 @@ private void showThemeDialog() { getDelegate().applyDayNight(); Toast.makeText(this, "Theme applied: " + themes[which], Toast.LENGTH_SHORT).show(); - buildOptions(); + refreshOptions(); }) .setNegativeButton("Cancel", null) .show(); @@ -269,12 +313,101 @@ private String getCurrentThemeName() { }; } - @SuppressLint("UnsafeIntentLaunch") - private void fullRefreshUi() { - // HACK - finish(); - overrideActivityTransition(OVERRIDE_TRANSITION_CLOSE, 0, 0); - startActivity(getIntent()); - overrideActivityTransition(OVERRIDE_TRANSITION_OPEN, 0, 0); + private void showWallpaperDialog() { + + var options = new String[]{ + "Choose Wallpaper", + "Clear Wallpaper" + }; + + new AlertDialog.Builder(this) + .setTitle("Select Wallpaper") + .setItems(options, (dialog, which) -> { + switch (which) { + case 0: + var intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.setType("image/*"); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + mWallpaperPickerLauncher.launch(intent); + break; + + case 1: + new AlertDialog.Builder(this) + .setTitle("Clear Wallpaper") + .setMessage("Are you sure you want to remove the current wallpaper?") + .setPositiveButton("Yes", (d, w) -> { + SettingsManager.setWallpaper(""); + Toast.makeText(this, "Wallpaper cleared", Toast.LENGTH_SHORT).show(); + + refreshOptions(); + }) + .setNegativeButton("Cancel", null) + .show(); + break; + } + + refreshOptions(); + }) + .setNegativeButton("Cancel", null) + .show(); + } + + private String getCurrentWallpaper() { + var wallpaper = FileHelpers.tryGetFileNameFromString(getApplicationContext(), SettingsManager.getWallpaper()); + if (wallpaper == null || wallpaper.isEmpty()) { + return "None"; + } + + return wallpaper; + } + + private void showWallpaperDimDialog() { + var seekBar = new SeekBar(this); + seekBar.setMax(100); + seekBar.setProgress((int) (SettingsManager.getWallpaperDimAmount() * 100)); + seekBar.setPadding(40, 40, 40, 40); + + var preview = new TextView(this); + preview.setText(getString(R.string.wallpaper_dim_pick, SettingsManager.getWallpaperDimAmount() * 100 + "%")); + preview.setTextSize(16); + preview.setPadding(40, 20, 40, 20); + + seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + preview.setText(getString(R.string.wallpaper_dim_pick, progress + "%")); + } + + @Override public void onStartTrackingTouch(SeekBar seekBar) {} + @Override public void onStopTrackingTouch(SeekBar seekBar) {} + }); + + var layout = new LinearLayout(this); + layout.setOrientation(LinearLayout.VERTICAL); + layout.addView(preview); + layout.addView(seekBar); + + new AlertDialog.Builder(this) + .setTitle("Select Dim Amount") + .setView(layout) + .setPositiveButton("Ok", (dialog, which) -> { + var dimAmount = seekBar.getProgress(); + if (dimAmount == SettingsManager.getWallpaperDimAmount()) { + return; + } + + // Remember to convert back from percentage + SettingsManager.setWallpaperDimAmount((float) dimAmount / 100); + Toast.makeText(this, "Wallpaper Dim Amount: " + (dimAmount + "%"), Toast.LENGTH_SHORT).show(); + + refreshOptions(); + }) + .setNegativeButton("Cancel", null) + .show(); + } + + private String getWallpaperDimPercentage() { + return SettingsManager.getWallpaperDimAmount() * 100 + "%"; } } \ No newline at end of file diff --git a/app/src/main/java/com/chadderbox/launchbox/settings/SettingsManager.java b/app/src/main/java/com/chadderbox/launchbox/settings/SettingsManager.java index 9fa905b..7b65ddc 100644 --- a/app/src/main/java/com/chadderbox/launchbox/settings/SettingsManager.java +++ b/app/src/main/java/com/chadderbox/launchbox/settings/SettingsManager.java @@ -16,6 +16,8 @@ public final class SettingsManager { public static final String KEY_FONT_SIZE = "font_size"; public static final String KEY_FAVORITES = "favorites"; public static final String KEY_THEME = "theme"; + public static final String KEY_WALLPAPER = "wallpaper"; + public static final String KEY_WALLPAPER_DIM_AMOUNT = "wallpaper_dim_amount"; private static SharedPreferences sPrefs; @@ -78,4 +80,20 @@ public static void setTheme(int mode) { public static int getTheme() { return sPrefs.getInt(KEY_THEME, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); } + + public static void setWallpaper(String wallpaper) { + sPrefs.edit().putString(KEY_WALLPAPER, wallpaper).apply(); + } + + public static String getWallpaper() { + return sPrefs.getString(KEY_WALLPAPER, null); + } + + public static void setWallpaperDimAmount(float dimAmount) { + sPrefs.edit().putFloat(KEY_WALLPAPER_DIM_AMOUNT, dimAmount).apply(); + } + + public static float getWallpaperDimAmount() { + return sPrefs.getFloat(KEY_WALLPAPER_DIM_AMOUNT, 0.375f); + } } diff --git a/app/src/main/java/com/chadderbox/launchbox/utils/FileHelpers.java b/app/src/main/java/com/chadderbox/launchbox/utils/FileHelpers.java new file mode 100644 index 0000000..6224207 --- /dev/null +++ b/app/src/main/java/com/chadderbox/launchbox/utils/FileHelpers.java @@ -0,0 +1,54 @@ +package com.chadderbox.launchbox.utils; + +import android.content.Context; +import android.net.Uri; +import android.provider.OpenableColumns; + +public final class FileHelpers { + + private FileHelpers() { } + + public static String tryGetFileNameFromString(final Context context, final String uriPath) { + Uri uri; + try { + uri = Uri.parse(uriPath); + } catch (Exception ignored) { + return null; + } + + return tryGetFileNameFromUri(context, uri); + } + + public static String tryGetFileNameFromUri(final Context context, final Uri uri) { + String result = null; + if (uri == null) { + return null; + } + + if ("content".equals(uri.getScheme())) { + try (var cursor = context.getContentResolver().query(uri, null, null, null, null)) { + if (cursor != null && cursor.moveToFirst()) { + var nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); + if (nameIndex >= 0) { + result = cursor.getString(nameIndex); + } + } + } catch (Exception ignored) { } + } + + if (result == null) { + var path = uri.getPath(); + if (path != null) { + int cut = path.lastIndexOf('/'); + if (cut != -1) { + result = path.substring(cut + 1); + } else { + result = path; + } + } + } + + return result; + } + +} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 5eae544..096f099 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -6,6 +6,16 @@ android:clickable="true" android:focusable="true"> + + + + + android:scrollbars="none" /> + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0810830..53a64f8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -9,4 +9,6 @@ Search the web for \"%1$s\" Play / Pause "Font size: \"%1$s\" + "Font size: \"%1$s\" + Wallpaper Background \ No newline at end of file