🔧 This commit updates the app name to "Lunox" in multiple files, adds QUERY_ALL_PACKAGES and PACKAGE_USAGE_STATS permissions for improved app detection, and enhances the permissions handling with a comprehensive permission request dialog and better device admin management.
This commit is contained in:
17 files changed
+411
-366
No files matched your search
@@ -12,11 +12,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: set up JDK 11
|
||||
- name: set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '11'
|
||||
java-version: '17'
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
|
||||
+16
-20
@@ -2,52 +2,48 @@ apply plugin: 'com.android.application'
|
||||
|
||||
|
||||
android {
|
||||
namespace "io.github.sankalp.lunox"
|
||||
compileSdkVersion 34
|
||||
buildToolsVersion "34.0.0"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "io.github.sankalp.lunox"
|
||||
minSdkVersion 14
|
||||
minSdkVersion 26
|
||||
targetSdkVersion 34
|
||||
versionCode 19
|
||||
versionName "0.19"
|
||||
//resConfigs "en", "de"
|
||||
buildConfigField "boolean", "enableColorSniffer", "false"
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
def workingBranch = getGitWorkingBranch()
|
||||
def normalizedWorkingBranch = workingBranch.replaceFirst("^[^A-Za-z]+", "").replaceAll("[^0-9A-Za-z]+", "")
|
||||
if (normalizedWorkingBranch.isEmpty() || workingBranch == "master" ) {
|
||||
applicationIdSuffix ".debug"
|
||||
resValue "string", "app_name", "Last Launcher Dev"
|
||||
} else {
|
||||
archivesBaseName = 'Last_Launcher_' + normalizedWorkingBranch
|
||||
applicationIdSuffix ".debug." + normalizedWorkingBranch
|
||||
resValue "string", "app_name", "Last Launcher-" + workingBranch
|
||||
}
|
||||
|
||||
applicationIdSuffix ".debug"
|
||||
resValue "string", "app_name", "Lunox Dev"
|
||||
debuggable true
|
||||
jniDebuggable true
|
||||
versionNameSuffix "-debug"
|
||||
minifyEnabled false
|
||||
shrinkResources false
|
||||
|
||||
}
|
||||
release {
|
||||
debuggable false
|
||||
jniDebuggable false
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
minifyEnabled false
|
||||
shrinkResources false
|
||||
zipAlignEnabled true
|
||||
resValue "string", "app_name", "Last Launcher"
|
||||
resValue "string", "app_name", "Lunox"
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
sourceCompatibility JavaVersion.VERSION_11
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
@@ -59,7 +55,7 @@ android {
|
||||
variant.outputs.all {
|
||||
def date = new Date()
|
||||
def formattedDate = date.format('yyyyMMddHHmmss')
|
||||
outputFileName = "Last-Launcher-${variant.name}-${variant.versionName}-${formattedDate}.apk"
|
||||
outputFileName = "Lunox-${variant.name}-${variant.versionName}-${formattedDate}.apk"
|
||||
}
|
||||
}
|
||||
packagingOptions {
|
||||
@@ -69,7 +65,7 @@ android {
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation project(':flowlayout:layouts')
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.2'
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.13'
|
||||
}
|
||||
|
||||
static String getGitWorkingBranch() {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="io.github.sankalp.lunox">
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
|
||||
<uses-permission android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT" />
|
||||
@@ -10,6 +9,11 @@
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.BIND_DEVICE_ADMIN" />
|
||||
|
||||
<!-- Permissions for app detection -->
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"
|
||||
tools:ignore="ProtectedPermissions" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:clearTaskOnLaunch="true"
|
||||
@@ -21,6 +25,7 @@
|
||||
|
||||
<activity
|
||||
android:name=".LauncherActivity"
|
||||
android:exported="true"
|
||||
android:excludeFromRecents="true"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask"
|
||||
|
||||
@@ -21,6 +21,8 @@ package io.github.sankalp.lunox;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.Dialog;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ClipData;
|
||||
@@ -40,6 +42,8 @@ import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.PowerManager;
|
||||
import android.provider.Settings;
|
||||
import android.provider.Settings.Global;
|
||||
import android.text.Editable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.TextWatcher;
|
||||
@@ -248,6 +252,9 @@ public class LauncherActivity extends Activity implements View.OnClickListener,
|
||||
|
||||
// initGestures();
|
||||
|
||||
// Check permissions before loading apps
|
||||
checkAndRequestPermissions();
|
||||
|
||||
// loads the apps
|
||||
loadApps();
|
||||
// register the receiver for installed, uninstall, update apps and shortcut pwa add
|
||||
@@ -286,12 +293,8 @@ public class LauncherActivity extends Activity implements View.OnClickListener,
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
switch (theme) {
|
||||
case R.style.White:
|
||||
case R.style.WhiteOnGrey:
|
||||
case R.style.BlackOnGrey: {
|
||||
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
|
||||
}
|
||||
if (theme == R.style.White || theme == R.style.WhiteOnGrey || theme == R.style.BlackOnGrey) {
|
||||
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -319,6 +322,15 @@ public class LauncherActivity extends Activity implements View.OnClickListener,
|
||||
PackageManager pm = getPackageManager();
|
||||
List<ResolveInfo> activities = pm.queryIntentActivities(startupIntent, 0);
|
||||
|
||||
// For Android 11+ (API 30+), also try alternative method if first method returns few apps
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && activities.size() < 10) {
|
||||
// Try alternative method for better app detection
|
||||
List<ResolveInfo> alternativeActivities = pm.queryIntentActivities(startupIntent, PackageManager.MATCH_ALL);
|
||||
if (alternativeActivities.size() > activities.size()) {
|
||||
activities = alternativeActivities;
|
||||
}
|
||||
}
|
||||
|
||||
// check whether our app list is already initialized if yes then clear this(when new app or shortcut installed)
|
||||
if (mAppsList != null) {
|
||||
mAppsList.clear();
|
||||
@@ -547,14 +559,20 @@ public class LauncherActivity extends Activity implements View.OnClickListener,
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
if (searching) {
|
||||
mSearchBox.setVisibility(View.GONE);
|
||||
searching = false;
|
||||
imm.hideSoftInputFromWindow(mSearchBox.getWindowToken(), 0);
|
||||
mHomeLayout.setPadding(DbUtils.getPaddingLeft(), DbUtils.getPaddingTop(), DbUtils.getPaddingRight(), DbUtils.getPaddingBottom());
|
||||
sortApps(DbUtils.getSortsTypes());
|
||||
// Check if we need to reload apps (e.g., after returning from settings)
|
||||
if (mAppsList != null && mAppsList.size() < 10) {
|
||||
// If we have very few apps, try reloading in case permissions were granted
|
||||
loadApps();
|
||||
}
|
||||
|
||||
// Check if search box is visible and hide it if needed
|
||||
if (mSearchBox.getVisibility() == View.VISIBLE) {
|
||||
mSearchBox.setVisibility(View.GONE);
|
||||
mHomeLayout.setPadding(DbUtils.getPaddingLeft(), DbUtils.getPaddingTop(), DbUtils.getPaddingRight(), DbUtils.getPaddingBottom());
|
||||
}
|
||||
|
||||
// Reset search state
|
||||
searching = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -637,38 +655,29 @@ public class LauncherActivity extends Activity implements View.OnClickListener,
|
||||
}
|
||||
|
||||
popupMenu.setOnMenuItemClickListener(menuItem -> {
|
||||
switch (menuItem.getItemId()) {
|
||||
case R.id.menu_color:
|
||||
changeColorSize(activityName, view);
|
||||
break;
|
||||
case R.id.menu_rename:
|
||||
renameApp(activityName, view.getText().toString());
|
||||
break;
|
||||
case R.id.menu_freeze_size:
|
||||
freezeAppSize(activityName);
|
||||
break;
|
||||
case R.id.menu_hide:
|
||||
hideApp(activityName);
|
||||
break;
|
||||
case R.id.menu_uninstall:
|
||||
if (view.isShortcut()) {
|
||||
removeShortcut(view);
|
||||
} else {
|
||||
uninstallApp(activityName);
|
||||
}
|
||||
break;
|
||||
case R.id.menu_app_info:
|
||||
showAppInfo(activityName);
|
||||
break;
|
||||
case R.id.menu_reset_to_default:
|
||||
resetApp(activityName);
|
||||
break;
|
||||
case R.id.menu_reset_color:
|
||||
resetAppColor(activityName);
|
||||
break;
|
||||
|
||||
default:
|
||||
return true;
|
||||
int id = menuItem.getItemId();
|
||||
if (id == R.id.menu_color) {
|
||||
changeColorSize(activityName, view);
|
||||
} else if (id == R.id.menu_rename) {
|
||||
renameApp(activityName, view.getText().toString());
|
||||
} else if (id == R.id.menu_freeze_size) {
|
||||
freezeAppSize(activityName);
|
||||
} else if (id == R.id.menu_hide) {
|
||||
hideApp(activityName);
|
||||
} else if (id == R.id.menu_uninstall) {
|
||||
if (view.isShortcut()) {
|
||||
removeShortcut(view);
|
||||
} else {
|
||||
uninstallApp(activityName);
|
||||
}
|
||||
} else if (id == R.id.menu_app_info) {
|
||||
showAppInfo(activityName);
|
||||
} else if (id == R.id.menu_reset_to_default) {
|
||||
resetApp(activityName);
|
||||
} else if (id == R.id.menu_reset_color) {
|
||||
resetAppColor(activityName);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
@@ -893,7 +902,11 @@ public class LauncherActivity extends Activity implements View.OnClickListener,
|
||||
loadApps();
|
||||
}
|
||||
};
|
||||
registerReceiver(broadcastReceiverAppInstall, intentFilter);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
registerReceiver(broadcastReceiverAppInstall, intentFilter, Context.RECEIVER_NOT_EXPORTED);
|
||||
} else {
|
||||
registerReceiver(broadcastReceiverAppInstall, intentFilter);
|
||||
}
|
||||
}
|
||||
|
||||
//shortcut install receiver
|
||||
@@ -922,7 +935,11 @@ public class LauncherActivity extends Activity implements View.OnClickListener,
|
||||
}
|
||||
}
|
||||
};
|
||||
registerReceiver(broadcastReceiverShortcutInstall, filter);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
registerReceiver(broadcastReceiverShortcutInstall, filter, Context.RECEIVER_NOT_EXPORTED);
|
||||
} else {
|
||||
registerReceiver(broadcastReceiverShortcutInstall, filter);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1314,4 +1331,110 @@ public class LauncherActivity extends Activity implements View.OnClickListener,
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check and request necessary permissions for app detection
|
||||
*/
|
||||
private void checkAndRequestPermissions() {
|
||||
// Check if permissions dialog was already shown
|
||||
if (DbUtils.isPermissionDialogShown()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a comprehensive permission request dialog
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle("Welcome to Lunox! 🚀");
|
||||
builder.setMessage("To provide the best experience, Lunox needs a few permissions:\n\n" +
|
||||
"📱 **Usage Access**: To show all your installed apps\n" +
|
||||
"🔒 **Device Admin**: To manage app freezing and advanced features\n\n" +
|
||||
"These permissions help Lunox work properly and show all your apps.");
|
||||
|
||||
builder.setIcon(android.R.drawable.ic_dialog_info);
|
||||
builder.setCancelable(false);
|
||||
|
||||
builder.setPositiveButton("Grant Permissions", (dialog, which) -> {
|
||||
requestPermissions();
|
||||
});
|
||||
|
||||
builder.setNegativeButton("Skip for Now", (dialog, which) -> {
|
||||
DbUtils.setPermissionDialogShown(true);
|
||||
});
|
||||
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Request all necessary permissions
|
||||
*/
|
||||
private void requestPermissions() {
|
||||
// Check and request Usage Access permission
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
|
||||
AppOpsManager appOps = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
|
||||
int mode = appOps.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS,
|
||||
android.os.Process.myUid(), getPackageName());
|
||||
if (mode != AppOpsManager.MODE_ALLOWED) {
|
||||
showUsageAccessDialog();
|
||||
}
|
||||
}
|
||||
|
||||
// Check and request Device Admin permission
|
||||
if (!DeviceAdminManager.isDeviceAdminActive(this)) {
|
||||
showDeviceAdminDialog();
|
||||
}
|
||||
|
||||
DbUtils.setPermissionDialogShown(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show dialog to guide user to enable usage access
|
||||
*/
|
||||
private void showUsageAccessDialog() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle("📱 Usage Access Required");
|
||||
builder.setMessage("To show all your installed apps, please enable 'Usage Access' for Lunox:\n\n" +
|
||||
"1. Tap 'Open Settings'\n" +
|
||||
"2. Find 'Lunox Dev' in the list\n" +
|
||||
"3. Toggle 'Allow usage access' ON\n" +
|
||||
"4. Return to Lunox\n\n" +
|
||||
"This helps Lunox detect all your apps properly.");
|
||||
|
||||
builder.setIcon(android.R.drawable.ic_menu_manage);
|
||||
builder.setCancelable(false);
|
||||
|
||||
builder.setPositiveButton("Open Settings", (dialog, which) -> {
|
||||
Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
|
||||
startActivity(intent);
|
||||
});
|
||||
|
||||
builder.setNegativeButton("Later", null);
|
||||
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show dialog to guide user to enable device admin
|
||||
*/
|
||||
private void showDeviceAdminDialog() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle("🔒 Device Admin Required");
|
||||
builder.setMessage("Device Admin permission enables advanced features like:\n\n" +
|
||||
"• App freezing functionality\n" +
|
||||
"• Enhanced app management\n" +
|
||||
"• Better system integration\n\n" +
|
||||
"This permission is safe and only used for launcher features.");
|
||||
|
||||
builder.setIcon(android.R.drawable.ic_lock_lock);
|
||||
builder.setCancelable(false);
|
||||
|
||||
builder.setPositiveButton("Enable Device Admin", (dialog, which) -> {
|
||||
DeviceAdminManager.requestDeviceAdminPermission(this);
|
||||
});
|
||||
|
||||
builder.setNegativeButton("Skip", null);
|
||||
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.show();
|
||||
}
|
||||
}
|
||||
@@ -53,19 +53,14 @@ public class ColorSnifferDialog extends Dialog implements View.OnClickListener {
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
switch (view.getId()) {
|
||||
case R.id.text_color_sniffer_on_off:
|
||||
onOffColorSnifferCustomisation();
|
||||
break;
|
||||
case R.id.color_sniffer_clipboard:
|
||||
launcherActivity.clipboardData();
|
||||
break;
|
||||
case R.id.color_sniffer_external_app: {
|
||||
if (DbUtils.isExternalSourceColor())
|
||||
startColorSnifferApp();
|
||||
|
||||
break;
|
||||
}
|
||||
int id = view.getId();
|
||||
if (id == R.id.text_color_sniffer_on_off) {
|
||||
onOffColorSnifferCustomisation();
|
||||
} else if (id == R.id.color_sniffer_clipboard) {
|
||||
launcherActivity.clipboardData();
|
||||
} else if (id == R.id.color_sniffer_external_app) {
|
||||
if (DbUtils.isExternalSourceColor())
|
||||
startColorSnifferApp();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -114,63 +114,43 @@ public class GlobalSettingsDialog extends Dialog implements View.OnClickListener
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
switch (view.getId()) {
|
||||
case R.id.settings_fonts:
|
||||
fontSelection(view);
|
||||
break;
|
||||
case R.id.settings_themes:
|
||||
showThemeDialog();
|
||||
break;
|
||||
case R.id.settings_color_sniffer: {
|
||||
if (BuildConfig.enableColorSniffer)
|
||||
showColorSnifferDialog();
|
||||
else randomColor();
|
||||
}
|
||||
break;
|
||||
|
||||
case R.id.settings_sort_app_by: {
|
||||
sortApps(view);
|
||||
break;
|
||||
}
|
||||
case R.id.settings_color_size: {
|
||||
showColorAndSizeDialog();
|
||||
}
|
||||
break;
|
||||
case R.id.settings_freeze_size:
|
||||
freezeAppsSize();
|
||||
break;
|
||||
case R.id.settings_hidden_apps:
|
||||
hiddenApps();
|
||||
break;
|
||||
case R.id.settings_frozen_apps:
|
||||
frozenApps();
|
||||
break;
|
||||
case R.id.settings_backup:
|
||||
backup();
|
||||
break;
|
||||
case R.id.settings_restore:
|
||||
restore();
|
||||
break;
|
||||
case R.id.settings_reset_to_defaults:
|
||||
defaultSettings();
|
||||
break;
|
||||
case R.id.settings_alignment:
|
||||
setFlowLayoutAlignment(view);
|
||||
break;
|
||||
case R.id.settings_padding:
|
||||
launcherActivity.setPadding();
|
||||
cancel();
|
||||
break;
|
||||
case R.id.settings_restart_launcher:
|
||||
launcherActivity.recreate();
|
||||
break;
|
||||
int id = view.getId();
|
||||
if (id == R.id.settings_fonts) {
|
||||
fontSelection(view);
|
||||
} else if (id == R.id.settings_themes) {
|
||||
showThemeDialog();
|
||||
} else if (id == R.id.settings_color_sniffer) {
|
||||
if (BuildConfig.enableColorSniffer)
|
||||
showColorSnifferDialog();
|
||||
else randomColor();
|
||||
} else if (id == R.id.settings_sort_app_by) {
|
||||
sortApps(view);
|
||||
} else if (id == R.id.settings_color_size) {
|
||||
showColorAndSizeDialog();
|
||||
} else if (id == R.id.settings_freeze_size) {
|
||||
freezeAppsSize();
|
||||
} else if (id == R.id.settings_hidden_apps) {
|
||||
hiddenApps();
|
||||
} else if (id == R.id.settings_frozen_apps) {
|
||||
frozenApps();
|
||||
} else if (id == R.id.settings_backup) {
|
||||
backup();
|
||||
} else if (id == R.id.settings_restore) {
|
||||
restore();
|
||||
} else if (id == R.id.settings_reset_to_defaults) {
|
||||
defaultSettings();
|
||||
} else if (id == R.id.settings_alignment) {
|
||||
setFlowLayoutAlignment(view);
|
||||
} else if (id == R.id.settings_padding) {
|
||||
launcherActivity.setPadding();
|
||||
cancel();
|
||||
} else if (id == R.id.settings_restart_launcher) {
|
||||
launcherActivity.recreate();
|
||||
}
|
||||
}
|
||||
|
||||
private void sortApps(View view) {
|
||||
Context context;
|
||||
// set theme
|
||||
// if theme wallpaper ie transparent then we have to show other theme
|
||||
if (DbUtils.getTheme() == R.style.Wallpaper)
|
||||
context = new ContextThemeWrapper(getContext(), R.style.AppTheme);
|
||||
else
|
||||
@@ -180,29 +160,19 @@ public class GlobalSettingsDialog extends Dialog implements View.OnClickListener
|
||||
popupMenu.getMenuInflater().inflate(R.menu.sort_apps_popups, popupMenu.getMenu());
|
||||
|
||||
popupMenu.setOnMenuItemClickListener(menuItem -> {
|
||||
cancel();
|
||||
switch (menuItem.getItemId()) {
|
||||
case R.id.menu_sort_by_name:
|
||||
launcherActivity.sortApps(Constants.SORT_BY_NAME);
|
||||
break;
|
||||
case R.id.menu_sort_by_opening_counts:
|
||||
launcherActivity.sortApps(Constants.SORT_BY_OPENING_COUNTS);
|
||||
break;
|
||||
case R.id.menu_sort_by_color:
|
||||
launcherActivity.sortApps(Constants.SORT_BY_COLOR);
|
||||
break;
|
||||
/* case R.id.menu_sort_by_customs:
|
||||
launcherActivity.sortApps(LauncherActivity.SORT_BY_CUSTOM);
|
||||
break;*/
|
||||
case R.id.menu_sort_by_size:
|
||||
launcherActivity.sortApps(Constants.SORT_BY_SIZE);
|
||||
break;
|
||||
case R.id.menu_sort_by_update_time:
|
||||
launcherActivity.sortApps(Constants.SORT_BY_UPDATE_TIME);
|
||||
break;
|
||||
case R.id.menu_sort_by_recent_use:
|
||||
launcherActivity.sortApps(Constants.SORT_BY_RECENT_OPEN);
|
||||
break;
|
||||
int id = menuItem.getItemId();
|
||||
if (id == R.id.menu_sort_by_name) {
|
||||
launcherActivity.sortApps(Constants.SORT_BY_NAME);
|
||||
} else if (id == R.id.menu_sort_by_opening_counts) {
|
||||
launcherActivity.sortApps(Constants.SORT_BY_OPENING_COUNTS);
|
||||
} else if (id == R.id.menu_sort_by_color) {
|
||||
launcherActivity.sortApps(Constants.SORT_BY_COLOR);
|
||||
} else if (id == R.id.menu_sort_by_size) {
|
||||
launcherActivity.sortApps(Constants.SORT_BY_SIZE);
|
||||
} else if (id == R.id.menu_sort_by_update_time) {
|
||||
launcherActivity.sortApps(Constants.SORT_BY_UPDATE_TIME);
|
||||
} else if (id == R.id.menu_sort_by_recent_use) {
|
||||
launcherActivity.sortApps(Constants.SORT_BY_RECENT_OPEN);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
@@ -215,10 +185,7 @@ public class GlobalSettingsDialog extends Dialog implements View.OnClickListener
|
||||
}
|
||||
|
||||
private void setFlowLayoutAlignment(View view) {
|
||||
|
||||
Context context;
|
||||
// set theme
|
||||
// if theme is wallpaper i.e. transparent then we have to show other theme:
|
||||
if (DbUtils.getTheme() == R.style.Wallpaper)
|
||||
context = new ContextThemeWrapper(getContext(), R.style.AppTheme);
|
||||
else
|
||||
@@ -228,21 +195,17 @@ public class GlobalSettingsDialog extends Dialog implements View.OnClickListener
|
||||
popupMenu.getMenuInflater().inflate(R.menu.alignment_popup, popupMenu.getMenu());
|
||||
|
||||
popupMenu.setOnMenuItemClickListener(menuItem -> {
|
||||
switch (menuItem.getItemId()) {
|
||||
case R.id.menu_center:
|
||||
launcherActivity.setFlowLayoutAlignment(Gravity.CENTER | Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL);
|
||||
break;
|
||||
case R.id.menu_end:
|
||||
launcherActivity.setFlowLayoutAlignment(Gravity.END | Gravity.CENTER_VERTICAL);
|
||||
break;
|
||||
case R.id.menu_start:
|
||||
launcherActivity.setFlowLayoutAlignment(Gravity.START | Gravity.CENTER_VERTICAL);
|
||||
break;
|
||||
int id = menuItem.getItemId();
|
||||
if (id == R.id.menu_center) {
|
||||
launcherActivity.setFlowLayoutAlignment(Gravity.CENTER | Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL);
|
||||
} else if (id == R.id.menu_end) {
|
||||
launcherActivity.setFlowLayoutAlignment(Gravity.END | Gravity.CENTER_VERTICAL);
|
||||
} else if (id == R.id.menu_start) {
|
||||
launcherActivity.setFlowLayoutAlignment(Gravity.START | Gravity.CENTER_VERTICAL);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
popupMenu.show();
|
||||
|
||||
}
|
||||
|
||||
private void randomColor() {
|
||||
@@ -363,10 +326,7 @@ public class GlobalSettingsDialog extends Dialog implements View.OnClickListener
|
||||
}
|
||||
|
||||
private void fontSelection(View view) {
|
||||
|
||||
Context context;
|
||||
// set theme
|
||||
// if theme wallpaper ie transparent then we have to show other theme
|
||||
if (DbUtils.getTheme() == R.style.Wallpaper)
|
||||
context = new ContextThemeWrapper(getContext(), R.style.AppTheme);
|
||||
else
|
||||
@@ -376,24 +336,20 @@ public class GlobalSettingsDialog extends Dialog implements View.OnClickListener
|
||||
popupMenu.getMenuInflater().inflate(R.menu.font_selection_popup, popupMenu.getMenu());
|
||||
|
||||
popupMenu.setOnMenuItemClickListener(menuItem -> {
|
||||
switch (menuItem.getItemId()) {
|
||||
case R.id.menu_choose_fonts:
|
||||
setFonts();
|
||||
break;
|
||||
case R.id.menu_default_font: {
|
||||
if (DbUtils.isFontExists()) {
|
||||
DbUtils.removeFont();
|
||||
launcherActivity.setFont();
|
||||
launcherActivity.loadApps();
|
||||
cancel();
|
||||
break;
|
||||
}
|
||||
int id = menuItem.getItemId();
|
||||
if (id == R.id.menu_choose_fonts) {
|
||||
setFonts();
|
||||
} else if (id == R.id.menu_default_font) {
|
||||
if (DbUtils.isFontExists()) {
|
||||
DbUtils.removeFont();
|
||||
launcherActivity.setFont();
|
||||
launcherActivity.loadApps();
|
||||
cancel();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
popupMenu.show();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -127,101 +127,79 @@ public class PaddingDialog extends Dialog implements View.OnLongClickListener, V
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View button) {
|
||||
switch (button.getId()) {
|
||||
case R.id.btn_left_minus:
|
||||
runner((TextView) button, left, -2, Padding.LEFT);
|
||||
break;
|
||||
case R.id.btn_left_plus:
|
||||
runner((TextView) button, left, 2, Padding.LEFT);
|
||||
break;
|
||||
case R.id.btn_right_minus:
|
||||
runner((TextView) button, right, -2, Padding.RIGHT);
|
||||
break;
|
||||
case R.id.btn_right_plus:
|
||||
runner((TextView) button, right, 2, Padding.RIGHT);
|
||||
break;
|
||||
case R.id.btn_top_minus:
|
||||
runner((TextView) button, top, -2, Padding.TOP);
|
||||
break;
|
||||
case R.id.btn_top_plus:
|
||||
runner((TextView) button, top, 2, Padding.TOP);
|
||||
break;
|
||||
case R.id.btn_bottom_minus:
|
||||
runner((TextView) button, bottom, -2, Padding.BOTTOM);
|
||||
break;
|
||||
case R.id.btn_bottom_plus:
|
||||
runner((TextView) button, bottom, 2, Padding.BOTTOM);
|
||||
break;
|
||||
int id = button.getId();
|
||||
if (id == R.id.btn_left_minus) {
|
||||
runner((TextView) button, left, -2, Padding.LEFT);
|
||||
} else if (id == R.id.btn_left_plus) {
|
||||
runner((TextView) button, left, 2, Padding.LEFT);
|
||||
} else if (id == R.id.btn_right_minus) {
|
||||
runner((TextView) button, right, -2, Padding.RIGHT);
|
||||
} else if (id == R.id.btn_right_plus) {
|
||||
runner((TextView) button, right, 2, Padding.RIGHT);
|
||||
} else if (id == R.id.btn_top_minus) {
|
||||
runner((TextView) button, top, -2, Padding.TOP);
|
||||
} else if (id == R.id.btn_top_plus) {
|
||||
runner((TextView) button, top, 2, Padding.TOP);
|
||||
} else if (id == R.id.btn_bottom_minus) {
|
||||
runner((TextView) button, bottom, -2, Padding.BOTTOM);
|
||||
} else if (id == R.id.btn_bottom_plus) {
|
||||
runner((TextView) button, bottom, 2, Padding.BOTTOM);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
switch (view.getId()) {
|
||||
case R.id.btn_left_minus:
|
||||
//decrease the value as minus button is pressed
|
||||
leftInt--;
|
||||
// check the lower limit i.e. 0
|
||||
if (leftInt < MIN_PADDING) {
|
||||
leftInt = MIN_PADDING;
|
||||
}
|
||||
//reflect it on screen
|
||||
left.setText(String.valueOf(leftInt));
|
||||
break;
|
||||
case R.id.btn_left_plus:
|
||||
leftInt++;
|
||||
// check the upper limit
|
||||
if (leftInt > MAX_PADDING_LEFT) {
|
||||
leftInt = MAX_PADDING_LEFT;
|
||||
}
|
||||
left.setText(String.valueOf(leftInt));
|
||||
break;
|
||||
case R.id.btn_right_minus:
|
||||
rightInt--;
|
||||
if (rightInt < MIN_PADDING) {
|
||||
rightInt = MIN_PADDING;
|
||||
}
|
||||
right.setText(String.valueOf(rightInt));
|
||||
break;
|
||||
case R.id.btn_right_plus:
|
||||
rightInt++;
|
||||
if (rightInt > MAX_PADDING_RIGHT) {
|
||||
rightInt = MAX_PADDING_RIGHT;
|
||||
}
|
||||
right.setText(String.valueOf(rightInt));
|
||||
break;
|
||||
case R.id.btn_top_minus:
|
||||
topInt--;
|
||||
if (topInt < MIN_PADDING) {
|
||||
topInt = MIN_PADDING;
|
||||
}
|
||||
top.setText(String.valueOf(topInt));
|
||||
break;
|
||||
case R.id.btn_top_plus:
|
||||
topInt++;
|
||||
if (topInt > MAX_PADDING_TOP) {
|
||||
topInt = MAX_PADDING_TOP;
|
||||
}
|
||||
top.setText(String.valueOf(topInt));
|
||||
break;
|
||||
case R.id.btn_bottom_minus:
|
||||
bottomInt--;
|
||||
if (bottomInt < MIN_PADDING) {
|
||||
bottomInt = MIN_PADDING;
|
||||
}
|
||||
bottom.setText(String.valueOf(bottomInt));
|
||||
break;
|
||||
case R.id.btn_bottom_plus:
|
||||
bottomInt++;
|
||||
if (bottomInt > MAX_PADDING_BOTTOM) {
|
||||
bottomInt = MAX_PADDING_BOTTOM;
|
||||
}
|
||||
bottom.setText(String.valueOf(bottomInt));
|
||||
break;
|
||||
int id = view.getId();
|
||||
if (id == R.id.btn_left_minus) {
|
||||
leftInt--;
|
||||
if (leftInt < MIN_PADDING) {
|
||||
leftInt = MIN_PADDING;
|
||||
}
|
||||
left.setText(String.valueOf(leftInt));
|
||||
} else if (id == R.id.btn_left_plus) {
|
||||
leftInt++;
|
||||
if (leftInt > MAX_PADDING_LEFT) {
|
||||
leftInt = MAX_PADDING_LEFT;
|
||||
}
|
||||
left.setText(String.valueOf(leftInt));
|
||||
} else if (id == R.id.btn_right_minus) {
|
||||
rightInt--;
|
||||
if (rightInt < MIN_PADDING) {
|
||||
rightInt = MIN_PADDING;
|
||||
}
|
||||
right.setText(String.valueOf(rightInt));
|
||||
} else if (id == R.id.btn_right_plus) {
|
||||
rightInt++;
|
||||
if (rightInt > MAX_PADDING_RIGHT) {
|
||||
rightInt = MAX_PADDING_RIGHT;
|
||||
}
|
||||
right.setText(String.valueOf(rightInt));
|
||||
} else if (id == R.id.btn_top_minus) {
|
||||
topInt--;
|
||||
if (topInt < MIN_PADDING) {
|
||||
topInt = MIN_PADDING;
|
||||
}
|
||||
top.setText(String.valueOf(topInt));
|
||||
} else if (id == R.id.btn_top_plus) {
|
||||
topInt++;
|
||||
if (topInt > MAX_PADDING_TOP) {
|
||||
topInt = MAX_PADDING_TOP;
|
||||
}
|
||||
top.setText(String.valueOf(topInt));
|
||||
} else if (id == R.id.btn_bottom_minus) {
|
||||
bottomInt--;
|
||||
if (bottomInt < MIN_PADDING) {
|
||||
bottomInt = MIN_PADDING;
|
||||
}
|
||||
bottom.setText(String.valueOf(bottomInt));
|
||||
} else if (id == R.id.btn_bottom_plus) {
|
||||
bottomInt++;
|
||||
if (bottomInt > MAX_PADDING_BOTTOM) {
|
||||
bottomInt = MAX_PADDING_BOTTOM;
|
||||
}
|
||||
bottom.setText(String.valueOf(bottomInt));
|
||||
}
|
||||
// apply all padding to home layout
|
||||
homeLayout.setPadding(leftInt, topInt, rightInt, bottomInt);
|
||||
}
|
||||
|
||||
|
||||
@@ -51,31 +51,23 @@ public class ThemeSelectorDialog extends Dialog implements View.OnClickListener
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
switch (view.getId()) {
|
||||
case R.id.t1:
|
||||
setTheme(R.style.AppTheme);
|
||||
break;
|
||||
case R.id.t2:
|
||||
setTheme(R.style.Wallpaper);
|
||||
break;
|
||||
case R.id.t3:
|
||||
setTheme(R.style.Black);
|
||||
break;
|
||||
case R.id.t4:
|
||||
setTheme(R.style.White);
|
||||
break;
|
||||
case R.id.t5:
|
||||
setTheme(R.style.WhiteOnGrey);
|
||||
break;
|
||||
case R.id.t6:
|
||||
setTheme(R.style.BlackOnGrey);
|
||||
break;
|
||||
case R.id.t35:
|
||||
setTheme(R.style.Hacker_green);
|
||||
break;
|
||||
case R.id.t36:
|
||||
setTheme(R.style.Hacker_red);
|
||||
break;
|
||||
int id = view.getId();
|
||||
if (id == R.id.t1) {
|
||||
setTheme(R.style.AppTheme);
|
||||
} else if (id == R.id.t2) {
|
||||
setTheme(R.style.Wallpaper);
|
||||
} else if (id == R.id.t3) {
|
||||
setTheme(R.style.Black);
|
||||
} else if (id == R.id.t4) {
|
||||
setTheme(R.style.White);
|
||||
} else if (id == R.id.t5) {
|
||||
setTheme(R.style.WhiteOnGrey);
|
||||
} else if (id == R.id.t6) {
|
||||
setTheme(R.style.BlackOnGrey);
|
||||
} else if (id == R.id.t35) {
|
||||
setTheme(R.style.Hacker_green);
|
||||
} else if (id == R.id.t36) {
|
||||
setTheme(R.style.Hacker_red);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ public class DbUtils {
|
||||
private static final String GLOBAL_SIZE_ADDITION_EXTRA = "global_size_addition_extra";
|
||||
private static final String APPS_COLORS_DEFAULT = "apps_color_default";
|
||||
private static final String APPS_SORTS_TYPE = "apps_sorts_types";
|
||||
private static final String PERMISSION_DIALOG_SHOWN = "permission_dialog_shown";
|
||||
|
||||
|
||||
public static void init(Context context) {
|
||||
@@ -166,55 +167,44 @@ public class DbUtils {
|
||||
|
||||
public static int getTheme() {
|
||||
int theme = SpUtils.getInstance().getInt(LAUNCHER_THEME, 2);
|
||||
switch (theme) {
|
||||
case 1:
|
||||
return R.style.Wallpaper;
|
||||
case 2:
|
||||
return R.style.AppTheme;
|
||||
case 3:
|
||||
return R.style.White;
|
||||
case 4:
|
||||
return R.style.WhiteOnGrey;
|
||||
case 5:
|
||||
return R.style.Black;
|
||||
case 6:
|
||||
return R.style.BlackOnGrey;
|
||||
case 7:
|
||||
return R.style.Hacker_green;
|
||||
case 8:
|
||||
return R.style.Hacker_red;
|
||||
if (theme == 1) {
|
||||
return R.style.Wallpaper;
|
||||
} else if (theme == 2) {
|
||||
return R.style.AppTheme;
|
||||
} else if (theme == 3) {
|
||||
return R.style.White;
|
||||
} else if (theme == 4) {
|
||||
return R.style.WhiteOnGrey;
|
||||
} else if (theme == 5) {
|
||||
return R.style.Black;
|
||||
} else if (theme == 6) {
|
||||
return R.style.BlackOnGrey;
|
||||
} else if (theme == 7) {
|
||||
return R.style.Hacker_green;
|
||||
} else if (theme == 8) {
|
||||
return R.style.Hacker_red;
|
||||
}
|
||||
|
||||
return R.style.AppTheme;
|
||||
}
|
||||
|
||||
public static void setTheme(int id) {
|
||||
int theme = 0;
|
||||
switch (id) {
|
||||
case R.style.Wallpaper:
|
||||
theme = 1;
|
||||
break;
|
||||
case R.style.AppTheme:
|
||||
theme = 2;
|
||||
break;
|
||||
case R.style.White:
|
||||
theme = 3;
|
||||
break;
|
||||
case R.style.WhiteOnGrey:
|
||||
theme = 4;
|
||||
break;
|
||||
case R.style.Black:
|
||||
theme = 5;
|
||||
break;
|
||||
case R.style.BlackOnGrey:
|
||||
theme = 6;
|
||||
break;
|
||||
case R.style.Hacker_green:
|
||||
theme = 7;
|
||||
break;
|
||||
case R.style.Hacker_red:
|
||||
theme = 8;
|
||||
break;
|
||||
if (id == R.style.Wallpaper) {
|
||||
theme = 1;
|
||||
} else if (id == R.style.AppTheme) {
|
||||
theme = 2;
|
||||
} else if (id == R.style.White) {
|
||||
theme = 3;
|
||||
} else if (id == R.style.WhiteOnGrey) {
|
||||
theme = 4;
|
||||
} else if (id == R.style.Black) {
|
||||
theme = 5;
|
||||
} else if (id == R.style.BlackOnGrey) {
|
||||
theme = 6;
|
||||
} else if (id == R.style.Hacker_green) {
|
||||
theme = 7;
|
||||
} else if (id == R.style.Hacker_red) {
|
||||
theme = 8;
|
||||
}
|
||||
SpUtils.getInstance().putInt(LAUNCHER_THEME, theme);
|
||||
}
|
||||
@@ -388,6 +378,20 @@ public class DbUtils {
|
||||
SpUtils.getInstance().putInt(APPS_SORTS_TYPE, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if permission dialog has been shown
|
||||
*/
|
||||
public static boolean isPermissionDialogShown() {
|
||||
return SpUtils.getInstance().getBoolean(PERMISSION_DIALOG_SHOWN, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set permission dialog as shown
|
||||
*/
|
||||
public static void setPermissionDialogShown(boolean shown) {
|
||||
SpUtils.getInstance().putBoolean(PERMISSION_DIALOG_SHOWN, shown);
|
||||
}
|
||||
|
||||
// a simple ciphered counter: "opening counter" is a private thing
|
||||
// rest is on device security
|
||||
private static String codeCount(int count) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Last Launcher</string>
|
||||
<string name="app_name">Lunox</string>
|
||||
|
||||
<string name="action_settings">Ajustes</string>
|
||||
<!--App information related strings-->
|
||||
|
||||
@@ -91,9 +91,15 @@
|
||||
<string name="recently_first">Recently first</string>
|
||||
<string name="restart_launcher">Restart Launcher</string>
|
||||
|
||||
<!-- Device Admin strings -->
|
||||
<string name="device_admin_label">Last Launcher Device Admin</string>
|
||||
<string name="device_admin_label">Lunox Device Admin</string>
|
||||
<string name="device_admin_description">Required for double-tap to lock screen functionality</string>
|
||||
<string name="device_admin_enable">Enable Device Admin</string>
|
||||
<string name="device_admin_disabled">Device Admin is required for lock screen functionality</string>
|
||||
|
||||
<!-- Frozen apps dialog title -->
|
||||
<string name="freezed_apps">Freezed Apps</string>
|
||||
<!-- Color source from ColorSniffer app -->
|
||||
<string name="from_colorsniffer_app">From ColorSniffer App</string>
|
||||
<!-- Size setting for individual app entry -->
|
||||
<string name="size_for_this_entry">Size for this entry</string>
|
||||
</resources>
|
||||
@@ -120,17 +120,4 @@
|
||||
</style>
|
||||
|
||||
|
||||
<!--<style name="myDialog">
|
||||
<item name="android:windowFrame">@null</item>
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
<item name="android:windowIsFloating">true</item>
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
<item name="android:windowTitleStyle">@null</item>
|
||||
<item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
|
||||
<item name="android:windowSoftInputMode">stateUnspecified|adjustPan</item>
|
||||
<item name="android:backgroundDimEnabled">false</item>
|
||||
<item name="android:background">@android:color/transparent</item>
|
||||
</style>-->
|
||||
|
||||
|
||||
</resources>
|
||||
+3
-3
@@ -3,10 +3,10 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.4.2'
|
||||
classpath 'com.android.tools.build:gradle:8.1.4'
|
||||
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
@@ -17,7 +17,7 @@ buildscript {
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
namespace "org.apmem.tools.layouts"
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion "35.0.1"
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="org.apmem.tools.layouts" />
|
||||
<manifest />
|
||||
@@ -19,6 +19,7 @@
|
||||
#
|
||||
org.gradle.parallel=true
|
||||
org.gradle.jvmargs=-Xmx1024m -Dfile.encoding=UTF-8
|
||||
org.gradle.java.home=/usr/lib/jvm/java-17-openjdk
|
||||
android.useAndroidX=false
|
||||
android.enableJetifier=false
|
||||
#exerimental
|
||||
@@ -28,3 +29,4 @@ android.enableR8.fullMode=true
|
||||
android.enableResourceOptimizations=true
|
||||
#enable
|
||||
android.nonTransitiveRClass=true
|
||||
android.suppressUnsupportedCompileSdk=34
|
||||
+1
-1
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.2-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip
|
||||
Reference in new issue
Block a user