Support for renaming apps
- Adds support for renaming apps: By pressing A or X on the controller, you can choose an alternate app name / icon. You can reset these changes back to default in Settings.
This commit is contained in:
@@ -23,6 +23,7 @@ import java.util.Calendar;
|
||||
import java.util.Map;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.graphics.Bitmap;
|
||||
@@ -152,7 +153,6 @@ public class AppInfo extends UnityPlayerActivity {
|
||||
}
|
||||
|
||||
public static void unzip(String zipFileName, String targetPath) {
|
||||
|
||||
File outDir = new File(targetPath);
|
||||
|
||||
// Create target path if not exist
|
||||
@@ -181,9 +181,85 @@ public class AppInfo extends UnityPlayerActivity {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stream.closeEntry();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.out.println(e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void addFileToZip(String zipFilePath, String sourceFilePath, String entryName)
|
||||
{
|
||||
Log.v(TAG, "Adding to zip: " + sourceFilePath + " to " + zipFilePath + " with entry name " + entryName);
|
||||
|
||||
File zipFile = new File(zipFilePath);
|
||||
File tempZipFile;
|
||||
byte[] buffer = new byte[8192];
|
||||
|
||||
try {
|
||||
// Create temporary zip file in same location as zip file
|
||||
tempZipFile = File.createTempFile(zipFile.getName(), null, new File(zipFile.getParent()));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
try (FileOutputStream fos = new FileOutputStream(tempZipFile);
|
||||
BufferedOutputStream bos = new BufferedOutputStream(fos, buffer.length);
|
||||
ZipOutputStream zos = new ZipOutputStream(bos)) {
|
||||
|
||||
if (zipFile.exists()) {
|
||||
try (FileInputStream fis = new FileInputStream(zipFilePath);
|
||||
BufferedInputStream bis = new BufferedInputStream(fis);
|
||||
ZipInputStream zin = new ZipInputStream(bis)) {
|
||||
|
||||
// Copy contents of input zip file to temp zip file
|
||||
ZipEntry ze = null;
|
||||
while ((ze = zin.getNextEntry()) != null) {
|
||||
if (ze.getName().equalsIgnoreCase(entryName)) {
|
||||
// The file we're trying to add already exists, so skip it
|
||||
continue;
|
||||
}
|
||||
|
||||
zos.putNextEntry(ze);
|
||||
int len;
|
||||
while ((len = zin.read(buffer)) > 0) {
|
||||
zos.write(buffer, 0, len);
|
||||
}
|
||||
zos.closeEntry();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add our new file
|
||||
zos.putNextEntry(new ZipEntry(entryName));
|
||||
try (FileInputStream fileFis = new FileInputStream(sourceFilePath);
|
||||
BufferedInputStream fileBis = new BufferedInputStream(fileFis)) {
|
||||
int len;
|
||||
while ((len = fileBis.read(buffer)) > 0) {
|
||||
zos.write(buffer, 0, len);
|
||||
}
|
||||
}
|
||||
zos.closeEntry();
|
||||
zos.finish();
|
||||
zos.close();
|
||||
bos.close();
|
||||
fos.close();
|
||||
|
||||
// Copy temp file to original zip file
|
||||
if (zipFile.exists()) {
|
||||
zipFile.delete();
|
||||
}
|
||||
if (!tempZipFile.renameTo(zipFile)) {
|
||||
throw new Exception("Could not rename file " + tempZipFile.getName() + " to " + zipFile.getName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (tempZipFile.exists()) {
|
||||
tempZipFile.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,6 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 20553fac56ec59645857c0732b787431, type: 3}
|
||||
m_Name: OVRBuildConfig
|
||||
m_EditorClassIdentifier:
|
||||
androidSDKPath:
|
||||
androidSDKPath: C:\Users\tvero\AppData\Local\Android\Sdk
|
||||
gradlePath:
|
||||
jdkPath:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,9 @@ public class AppEntry : MonoBehaviour
|
||||
|
||||
public string packageId;
|
||||
public string appName;
|
||||
public string externalIconPath;
|
||||
public int installedApkIndex;
|
||||
public bool isRenameMode;
|
||||
|
||||
// Start is called before the first frame update
|
||||
void Start()
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace QuestAppLauncher
|
||||
/// </summary>
|
||||
public class ProcessedApp
|
||||
{
|
||||
public int Index;
|
||||
public int Index = -1;
|
||||
public string PackageName;
|
||||
public string AppName;
|
||||
public string AutoTabName;
|
||||
@@ -32,7 +32,7 @@ namespace QuestAppLauncher
|
||||
/// Class used to deserialize appnames.json entries
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
class JsonAppNamesEntry
|
||||
public class JsonAppNamesEntry
|
||||
{
|
||||
public string Name;
|
||||
public string Category;
|
||||
@@ -43,6 +43,10 @@ namespace QuestAppLauncher
|
||||
const string AppNameOverrideTxtFileSearch = "appnames*.txt";
|
||||
const string AppNameOverrideJsonFileSearch = "appnames*.json";
|
||||
|
||||
// Rename file names
|
||||
public const string RenameJsonFileName = "appnames_rename.json";
|
||||
public const string RenameIconPackFileName = "iconpack_rename.zip";
|
||||
|
||||
// Icon pack search string
|
||||
const string IconPackSearch = "iconpack*.zip";
|
||||
|
||||
@@ -66,7 +70,14 @@ namespace QuestAppLauncher
|
||||
|
||||
public static readonly string[] Auto_Tabs = { Tab_Quest, Tab_Go, Tab_2D };
|
||||
|
||||
public static Dictionary<string, ProcessedApp> ProcessApps(Config config)
|
||||
/// <summary>
|
||||
/// Entry point for app processing: Applies app name overrides (from appnames.txt/json) and app icons (from individual jpgs or icon packs).
|
||||
/// Handles extraction of icon packs if zip file modified. Returns a list of processed apps.
|
||||
/// </summary>
|
||||
/// <param name="config">Application config</param>
|
||||
/// <param name="isRenameMode">Whether in rename mode. If so, returns all apps found, not just installed ones.</param>
|
||||
/// <returns>Dictionary of processed apps</returns>
|
||||
public static Dictionary<string, ProcessedApp> ProcessApps(Config config, bool isRenameMode = false)
|
||||
{
|
||||
var persistentDataPath = UnityEngine.Application.persistentDataPath;
|
||||
Debug.Log("Persistent data path: " + persistentDataPath);
|
||||
@@ -78,7 +89,6 @@ namespace QuestAppLauncher
|
||||
using (AndroidJavaClass unity = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
|
||||
using (AndroidJavaObject currentActivity = unity.GetStatic<AndroidJavaObject>("currentActivity"))
|
||||
{
|
||||
|
||||
// Get # of installed apps
|
||||
int numApps = currentActivity.Call<int>("getSize");
|
||||
Debug.Log("# installed apps: " + numApps);
|
||||
@@ -91,12 +101,16 @@ namespace QuestAppLauncher
|
||||
processedLastTimeUsed = true;
|
||||
}
|
||||
|
||||
// Add current package name to excludedPackageNames to filter it out
|
||||
excludedPackageNames.Add(currentActivity.Call<string>("getPackageName"));
|
||||
if (!isRenameMode)
|
||||
{
|
||||
// Add current package name to excludedPackageNames to filter it out
|
||||
excludedPackageNames.Add(currentActivity.Call<string>("getPackageName"));
|
||||
}
|
||||
|
||||
// This is a file containing packageNames that will be excluded
|
||||
// This is a file containing packageNames that will be excluded.
|
||||
// Skip this if in rename mode.
|
||||
var excludedPackageNamesFilePath = Path.Combine(persistentDataPath, ExcludedPackagesFile);
|
||||
if (File.Exists(excludedPackageNamesFilePath))
|
||||
if (!isRenameMode && File.Exists(excludedPackageNamesFilePath))
|
||||
{
|
||||
Debug.Log("Found file: " + excludedPackageNamesFilePath);
|
||||
string[] excludedPackages = File.ReadAllLines(excludedPackageNamesFilePath);
|
||||
@@ -151,16 +165,16 @@ namespace QuestAppLauncher
|
||||
}
|
||||
|
||||
// Process app name overrides files (both downloaded & manually created)
|
||||
ProcessAppNameOverrideFiles(apps, AssetsDownloader.GetOrCreateDownloadPath());
|
||||
ProcessAppNameOverrideFiles(apps, persistentDataPath);
|
||||
ProcessAppNameOverrideFiles(isRenameMode, apps, AssetsDownloader.GetOrCreateDownloadPath());
|
||||
ProcessAppNameOverrideFiles(isRenameMode, apps, persistentDataPath);
|
||||
|
||||
// Extract icon packs (both downloaded & manually created)
|
||||
ExtractIconPacks(currentActivity, AssetsDownloader.GetOrCreateDownloadPath());
|
||||
ExtractIconPacks(currentActivity, persistentDataPath);
|
||||
|
||||
// Process extracted icons (both downloaded & manually created)
|
||||
ProcessExtractedIcons(apps, AssetsDownloader.GetOrCreateDownloadPath());
|
||||
ProcessExtractedIcons(apps, persistentDataPath);
|
||||
ProcessExtractedIcons(isRenameMode, apps, AssetsDownloader.GetOrCreateDownloadPath());
|
||||
ProcessExtractedIcons(isRenameMode, apps, persistentDataPath);
|
||||
|
||||
// Process any individual icons
|
||||
var iconOverridePath = persistentDataPath;
|
||||
@@ -173,25 +187,32 @@ namespace QuestAppLauncher
|
||||
return apps;
|
||||
}
|
||||
|
||||
private static void ProcessAppNameOverrideFiles(Dictionary<string, ProcessedApp> apps, string path)
|
||||
private static void ProcessAppNameOverrideFiles(bool isRenameMode, Dictionary<string, ProcessedApp> apps, string path)
|
||||
{
|
||||
// Process appname*.json files, sorted by name
|
||||
foreach (var filePath in Directory.GetFiles(
|
||||
path, AppNameOverrideJsonFileSearch).OrderBy(f => f))
|
||||
{
|
||||
ProcessAppNameOverrideJsonFile(apps, filePath);
|
||||
ProcessAppNameOverrideJsonFile(isRenameMode, apps, filePath);
|
||||
}
|
||||
|
||||
// Process appname*.txt files, sorted by name
|
||||
foreach (var filePath in Directory.GetFiles(
|
||||
path, AppNameOverrideTxtFileSearch).OrderBy(f => f))
|
||||
{
|
||||
ProcessAppNameOverrideTxtFile(apps, filePath);
|
||||
ProcessAppNameOverrideTxtFile(isRenameMode, apps, filePath);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessAppNameOverrideJsonFile(Dictionary<string, ProcessedApp> apps, string appNameOverrideFilePath)
|
||||
private static void ProcessAppNameOverrideJsonFile(bool isRenameMode, Dictionary<string, ProcessedApp> apps, string appNameOverrideFilePath)
|
||||
{
|
||||
if (isRenameMode && appNameOverrideFilePath.Equals(Path.Combine(UnityEngine.Application.persistentDataPath, RenameJsonFileName),
|
||||
StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
// In rename mode, so skip the rename json file itself
|
||||
return;
|
||||
}
|
||||
|
||||
// Override app names, if any
|
||||
Debug.Log("Found file: " + appNameOverrideFilePath);
|
||||
|
||||
@@ -207,7 +228,17 @@ namespace QuestAppLauncher
|
||||
|
||||
if (!apps.ContainsKey(packageName))
|
||||
{
|
||||
// App is not installed, so skip
|
||||
// App is not installed
|
||||
if (isRenameMode)
|
||||
{
|
||||
// If rename mode, just add it
|
||||
apps[packageName] = new ProcessedApp
|
||||
{
|
||||
PackageName = packageName,
|
||||
AppName = appName,
|
||||
};
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -264,7 +295,7 @@ namespace QuestAppLauncher
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessAppNameOverrideTxtFile(Dictionary<string, ProcessedApp> apps, string appNameOverrideFilePath)
|
||||
private static void ProcessAppNameOverrideTxtFile(bool isRenameMode, Dictionary<string, ProcessedApp> apps, string appNameOverrideFilePath)
|
||||
{
|
||||
// Override app names, if any
|
||||
// This is just a file with comma-separated packageName,appName[,category1[, category2]]
|
||||
@@ -295,7 +326,17 @@ namespace QuestAppLauncher
|
||||
|
||||
if (!apps.ContainsKey(packageName))
|
||||
{
|
||||
// App is not installed, so skip
|
||||
// App is not installed
|
||||
if (isRenameMode)
|
||||
{
|
||||
// If rename mode, so just add it
|
||||
apps[packageName] = new ProcessedApp
|
||||
{
|
||||
PackageName = packageName,
|
||||
AppName = appName,
|
||||
};
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -401,7 +442,7 @@ namespace QuestAppLauncher
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessExtractedIcons(Dictionary<string, ProcessedApp> apps, string iconsPath)
|
||||
private static void ProcessExtractedIcons(bool isRenameMode, Dictionary<string, ProcessedApp> apps, string iconsPath)
|
||||
{
|
||||
// Full path of extraction dir
|
||||
var extractionDirPath = Path.Combine(iconsPath, IconPackExtractionDir);
|
||||
@@ -412,6 +453,13 @@ namespace QuestAppLauncher
|
||||
var dirs = Directory.GetDirectories(extractionDirPath).OrderBy(f => f);
|
||||
foreach (var dir in dirs)
|
||||
{
|
||||
if (isRenameMode && dir.StartsWith(Path.Combine(UnityEngine.Application.persistentDataPath, RenameIconPackFileName),
|
||||
StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
// In rename mode, so skip the extracted rename icon pack itself
|
||||
continue;
|
||||
}
|
||||
|
||||
ProcessIconsInPath(apps, dir);
|
||||
}
|
||||
}
|
||||
@@ -449,7 +497,7 @@ namespace QuestAppLauncher
|
||||
catch (Exception e)
|
||||
{
|
||||
// Fall back to using the apk icon
|
||||
Debug.Log(string.Format("Error reading app icon from file [{0}]: {1}", iconPath, e.Message));
|
||||
Debug.LogFormat("Error reading app icon from file [{0}]: {1}", iconPath, e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -500,6 +548,30 @@ namespace QuestAppLauncher
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Static method to delete the rename json and icon pack
|
||||
/// </summary>
|
||||
/// <returns>true if file exists</returns>
|
||||
static public bool DeleteRenameFiles()
|
||||
{
|
||||
var ret = false;
|
||||
var renameJsonFilePath = Path.Combine(UnityEngine.Application.persistentDataPath, RenameJsonFileName);
|
||||
if (File.Exists(renameJsonFilePath))
|
||||
{
|
||||
File.Delete(renameJsonFilePath);
|
||||
ret = true;
|
||||
}
|
||||
|
||||
var renameIconPackFilePath = Path.Combine(UnityEngine.Application.persistentDataPath, RenameIconPackFileName);
|
||||
if (File.Exists(renameIconPackFilePath))
|
||||
{
|
||||
File.Delete(renameIconPackFilePath);
|
||||
ret = true;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Static method for launching an Android app
|
||||
/// </summary>
|
||||
|
||||
@@ -105,7 +105,7 @@ namespace QuestAppLauncher
|
||||
{
|
||||
// We downloaded new assets, so re-load the scene
|
||||
Debug.Log("Downloaded new assets. Re-populating panel");
|
||||
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
|
||||
SceneManager.LoadSceneAsync(SceneManager.GetActiveScene().name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -137,7 +137,6 @@ namespace QuestAppLauncher
|
||||
{
|
||||
Debug.Log(string.Format("Failed to write config: {0}", e.Message));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,10 @@ namespace QuestAppLauncher
|
||||
public GameObject leftTabContainerContent;
|
||||
public GameObject rightTabContainerContent;
|
||||
|
||||
// Rename panel game objects
|
||||
public GameObject renamePanelContainer;
|
||||
public GameObject scrollRenameContainer;
|
||||
|
||||
// Tracking space
|
||||
public GameObject trackingSpace;
|
||||
|
||||
@@ -78,13 +82,21 @@ namespace QuestAppLauncher
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private Functions
|
||||
/// <summary>
|
||||
/// Populates rename grid
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task PopulateRenameAsync()
|
||||
{
|
||||
PopulateAsync(true);
|
||||
}
|
||||
|
||||
#region Private Functions
|
||||
/// <summary>
|
||||
/// Populate the grid from installed apps
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private async Task PopulateAsync()
|
||||
private async Task PopulateAsync(bool isRenameMode = false)
|
||||
{
|
||||
// Load configuration
|
||||
var config = ConfigPersistence.LoadConfig();
|
||||
@@ -96,7 +108,7 @@ namespace QuestAppLauncher
|
||||
|
||||
try
|
||||
{
|
||||
return AppProcessor.ProcessApps(config);
|
||||
return AppProcessor.ProcessApps(config, isRenameMode);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -105,14 +117,21 @@ namespace QuestAppLauncher
|
||||
});
|
||||
|
||||
// Download updates in the background
|
||||
if (config.autoUpdate && !GlobalState.Instance.CheckedForUpdate)
|
||||
if (!isRenameMode && config.autoUpdate && !GlobalState.Instance.CheckedForUpdate)
|
||||
{
|
||||
GlobalState.Instance.CheckedForUpdate = true;
|
||||
AssetsDownloader.DownloadAssetsAsync(config, this.downloadStatusIndicator);
|
||||
}
|
||||
|
||||
// Populate the panel content
|
||||
await PopulatePanelContentAsync(config, apps);
|
||||
if (!isRenameMode)
|
||||
{
|
||||
await PopulatePanelContentAsync(config, apps);
|
||||
}
|
||||
else
|
||||
{
|
||||
await PopulateRenamePanelContentAsync(config, apps);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PopulatePanelContentAsync(
|
||||
@@ -168,14 +187,14 @@ namespace QuestAppLauncher
|
||||
// Process the tab containers
|
||||
var gridContents = new Dictionary<string, GameObject>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
ProcessTabContainer(config, topTabs, this.topTabContainer, this.topTabContainerContent, true, gridContents);
|
||||
ProcessTabContainer(config, leftTabs, this.leftTabContainer, this.leftTabContainerContent, false, gridContents);
|
||||
ProcessTabContainer(config, rightTabs, this.rightTabContainer, this.rightTabContainerContent, false, gridContents);
|
||||
ProcessTabContainer(config, topTabs, this.panelContainer, this.scrollContainer, this.topTabContainer, this.topTabContainerContent, true, gridContents);
|
||||
ProcessTabContainer(config, leftTabs, this.panelContainer, this.scrollContainer, this.leftTabContainer, this.leftTabContainerContent, false, gridContents);
|
||||
ProcessTabContainer(config, rightTabs, this.panelContainer, this.scrollContainer, this.rightTabContainer, this.rightTabContainerContent, false, gridContents);
|
||||
|
||||
// Set panel size, use any grid content for reference (since they are all the same size)
|
||||
if (gridContents.Count > 0)
|
||||
{
|
||||
var size = SetGridSize(gridContents.First().Value, config.gridSize.rows, config.gridSize.cols);
|
||||
var size = SetGridSize(this.panelContainer, gridContents.First().Value, config.gridSize.rows, config.gridSize.cols);
|
||||
|
||||
// Adjust tab sizes
|
||||
ResizeTabContent(this.topTabContainer.transform, size, topTabs.Count, true);
|
||||
@@ -210,6 +229,28 @@ namespace QuestAppLauncher
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PopulateRenamePanelContentAsync(
|
||||
Config config, Dictionary<string, ProcessedApp> apps)
|
||||
{
|
||||
// Create scroll view
|
||||
var scrollView = (GameObject)Instantiate(this.prefabScrollView, this.scrollRenameContainer.transform);
|
||||
scrollView.SetActive(true);
|
||||
var scrollRectOverride = scrollView.GetComponent<ScrollRectOverride>();
|
||||
scrollRectOverride.trackingSpace = this.trackingSpace.transform;
|
||||
|
||||
var gridContent = scrollRectOverride.content.gameObject;
|
||||
|
||||
// Set panel size
|
||||
SetGridSize(this.renamePanelContainer, gridContent, config.gridSize.rows, config.gridSize.cols);
|
||||
|
||||
// Populate grid with app information (name & icon)
|
||||
// Sort alphabetically
|
||||
foreach (var app in apps.OrderBy(key => key.Value.AppName, StringComparer.InvariantCultureIgnoreCase))
|
||||
{
|
||||
await AddCellToGridAsync(app.Value, gridContent.transform, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void ResizeTabContent(Transform transform, Vector2 size, int childCount, bool isHorizontal)
|
||||
{
|
||||
var rect = transform.GetComponent<RectTransform>();
|
||||
@@ -235,7 +276,7 @@ namespace QuestAppLauncher
|
||||
transform.gameObject.GetComponent<ScrollButtonHandler>().RefreshScrollContent(childCount);
|
||||
}
|
||||
|
||||
private Vector2 SetGridSize(GameObject gridContent, int rows, int cols)
|
||||
private Vector2 SetGridSize(GameObject panel, GameObject gridContent, int rows, int cols)
|
||||
{
|
||||
// Make sure grid size have sane value
|
||||
cols = Math.Min(cols, 10);
|
||||
@@ -264,7 +305,7 @@ namespace QuestAppLauncher
|
||||
// Adjust grid container rect transform
|
||||
var size = new Vector2(width, height);
|
||||
|
||||
var gridTransform = this.panelContainer.GetComponent<RectTransform>();
|
||||
var gridTransform = panel.GetComponent<RectTransform>();
|
||||
gridTransform.sizeDelta = size;
|
||||
|
||||
// Adjust grid container Y position to maintain constant height.
|
||||
@@ -277,7 +318,7 @@ namespace QuestAppLauncher
|
||||
return size;
|
||||
}
|
||||
|
||||
private void ProcessTabContainer(Config config, List<string> tabs, GameObject tabContainer,
|
||||
private void ProcessTabContainer(Config config, List<string> tabs, GameObject panel, GameObject scroll, GameObject tabContainer,
|
||||
GameObject tabContainerContent, bool setFirstTabActive, Dictionary<string, GameObject> gridContents)
|
||||
{
|
||||
// Create scroll views and tabs
|
||||
@@ -286,7 +327,7 @@ namespace QuestAppLauncher
|
||||
Debug.LogFormat("Populating tab '{0}'", tabName);
|
||||
|
||||
// Create scroll view
|
||||
var scrollView = (GameObject)Instantiate(this.prefabScrollView, this.scrollContainer.transform);
|
||||
var scrollView = (GameObject)Instantiate(this.prefabScrollView, scroll.transform);
|
||||
var scrollRectOverride = scrollView.GetComponent<ScrollRectOverride>();
|
||||
scrollRectOverride.trackingSpace = this.trackingSpace.transform;
|
||||
|
||||
@@ -299,7 +340,7 @@ namespace QuestAppLauncher
|
||||
|
||||
var toggle = tab.GetComponent<Toggle>();
|
||||
toggle.isOn = setFirstTabActive;
|
||||
toggle.group = this.panelContainer.GetComponent<ToggleGroup>();
|
||||
toggle.group = panel.GetComponent<ToggleGroup>();
|
||||
toggle.onValueChanged.AddListener(scrollView.SetActive);
|
||||
|
||||
setFirstTabActive = false;
|
||||
@@ -309,8 +350,14 @@ namespace QuestAppLauncher
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AddCellToGridAsync(ProcessedApp app, Transform transform)
|
||||
private async Task AddCellToGridAsync(ProcessedApp app, Transform transform, bool isRenameMode = false)
|
||||
{
|
||||
if (app.Index == -1 && string.IsNullOrEmpty(app.IconPath))
|
||||
{
|
||||
// If we have neither app index or icon path, skip this
|
||||
return;
|
||||
}
|
||||
|
||||
// Get app icon in background
|
||||
var bytesIcon = await Task.Run(() =>
|
||||
{
|
||||
@@ -333,6 +380,9 @@ namespace QuestAppLauncher
|
||||
var appEntry = newObj.GetComponent("AppEntry") as AppEntry;
|
||||
appEntry.packageId = app.PackageName;
|
||||
appEntry.appName = app.AppName;
|
||||
appEntry.isRenameMode = isRenameMode;
|
||||
appEntry.installedApkIndex = app.Index;
|
||||
appEntry.externalIconPath = app.IconPath;
|
||||
|
||||
// Set the icon image
|
||||
if (null != bytesIcon)
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace QuestAppLauncher
|
||||
{
|
||||
public class HideAppHandler : MonoBehaviour
|
||||
{
|
||||
public TextMeshProUGUI text;
|
||||
private AppEntry appEntryToHide;
|
||||
|
||||
public void OnHideApp(AppEntry appEntryToHide)
|
||||
{
|
||||
this.appEntryToHide = appEntryToHide;
|
||||
|
||||
// Set dialog text
|
||||
this.text.text = string.Format("Hide app '{0}'?", appEntryToHide.appName);
|
||||
|
||||
// Launch dialog
|
||||
this.gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
public async void OnOk()
|
||||
{
|
||||
// Add package name to excluded file
|
||||
Debug.Log("Hiding: " + this.appEntryToHide.appName + " (package id: " + this.appEntryToHide.packageId + ")");
|
||||
AppProcessor.AddAppToExcludedFile(this.appEntryToHide.packageId);
|
||||
|
||||
// Reload the scene - this will force all the tabs to update
|
||||
await SceneManager.LoadSceneAsync(SceneManager.GetActiveScene().name);
|
||||
}
|
||||
|
||||
public void OnCancel()
|
||||
{
|
||||
this.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1f9275ecafce3994394538da0abe4977
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,147 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using UnityEngine;
|
||||
|
||||
namespace QuestAppLauncher
|
||||
{
|
||||
public class AsyncCoroutineRunner : MonoBehaviour
|
||||
{
|
||||
static AsyncCoroutineRunner _instance;
|
||||
|
||||
public static AsyncCoroutineRunner Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = new GameObject("AsyncCoroutineRunner")
|
||||
.AddComponent<AsyncCoroutineRunner>();
|
||||
}
|
||||
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
void Awake()
|
||||
{
|
||||
// Don't show in scene hierarchy
|
||||
gameObject.hideFlags = HideFlags.HideAndDontSave;
|
||||
|
||||
DontDestroyOnLoad(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
public static class IEnumeratorAwaitExtensions
|
||||
{
|
||||
public static SimpleCoroutineAwaiter<AsyncOperation> GetAwaiter(this AsyncOperation instruction)
|
||||
{
|
||||
return GetAwaiterReturnSelf(instruction);
|
||||
}
|
||||
|
||||
static SimpleCoroutineAwaiter<T> GetAwaiterReturnSelf<T>(T instruction)
|
||||
{
|
||||
var awaiter = new SimpleCoroutineAwaiter<T>();
|
||||
RunOnUnityScheduler(() => AsyncCoroutineRunner.Instance.StartCoroutine(
|
||||
InstructionWrappers.ReturnSelf(awaiter, instruction)));
|
||||
return awaiter;
|
||||
}
|
||||
|
||||
static void RunOnUnityScheduler(Action action)
|
||||
{
|
||||
if (SynchronizationContext.Current == SyncContextUtil.UnitySynchronizationContext)
|
||||
{
|
||||
action();
|
||||
}
|
||||
else
|
||||
{
|
||||
SyncContextUtil.UnitySynchronizationContext.Post(_ => action(), null);
|
||||
}
|
||||
}
|
||||
|
||||
public class SimpleCoroutineAwaiter<T> : INotifyCompletion
|
||||
{
|
||||
bool _isDone;
|
||||
Exception _exception;
|
||||
Action _continuation;
|
||||
T _result;
|
||||
|
||||
public bool IsCompleted
|
||||
{
|
||||
get { return _isDone; }
|
||||
}
|
||||
|
||||
public T GetResult()
|
||||
{
|
||||
Debug.Assert(_isDone);
|
||||
|
||||
if (_exception != null)
|
||||
{
|
||||
ExceptionDispatchInfo.Capture(_exception).Throw();
|
||||
}
|
||||
|
||||
return _result;
|
||||
}
|
||||
|
||||
public void Complete(T result, Exception e)
|
||||
{
|
||||
Debug.Assert(!_isDone);
|
||||
|
||||
_isDone = true;
|
||||
_exception = e;
|
||||
_result = result;
|
||||
|
||||
// Always trigger the continuation on the unity thread when awaiting on unity yield
|
||||
// instructions
|
||||
if (_continuation != null)
|
||||
{
|
||||
RunOnUnityScheduler(_continuation);
|
||||
}
|
||||
}
|
||||
|
||||
void INotifyCompletion.OnCompleted(Action continuation)
|
||||
{
|
||||
Debug.Assert(_continuation == null);
|
||||
Debug.Assert(!_isDone);
|
||||
|
||||
_continuation = continuation;
|
||||
}
|
||||
}
|
||||
|
||||
static class InstructionWrappers
|
||||
{
|
||||
public static IEnumerator ReturnSelf<T>(
|
||||
SimpleCoroutineAwaiter<T> awaiter, T instruction)
|
||||
{
|
||||
yield return instruction;
|
||||
awaiter.Complete(instruction, null);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SyncContextUtil
|
||||
{
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
||||
static void Install()
|
||||
{
|
||||
UnitySynchronizationContext = SynchronizationContext.Current;
|
||||
UnityThreadId = Thread.CurrentThread.ManagedThreadId;
|
||||
}
|
||||
|
||||
public static int UnityThreadId
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public static SynchronizationContext UnitySynchronizationContext
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e90d336a3fcd1ce46bda7a08309b4e83
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,16 +1,17 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.UI;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace QuestAppLauncher
|
||||
{
|
||||
public class OnInteraction : MonoBehaviour
|
||||
{
|
||||
protected Material oldHoverMat;
|
||||
public Material yellowMat;
|
||||
public Material backIdle;
|
||||
public Material backACtive;
|
||||
public UnityEngine.UI.Text outText;
|
||||
// Hide app handler
|
||||
public HideAppHandler hideAppHandler;
|
||||
|
||||
// Rename app handler
|
||||
public RenameHandler renameHandler;
|
||||
|
||||
public void OnHoverEnter(Transform t)
|
||||
{
|
||||
@@ -32,28 +33,51 @@ namespace QuestAppLauncher
|
||||
}
|
||||
}
|
||||
|
||||
public void OnSelected(Transform t)
|
||||
public async void OnSelected(Transform t)
|
||||
{
|
||||
var appEntry = t.gameObject.GetComponent("AppEntry") as AppEntry;
|
||||
if (null != appEntry)
|
||||
{
|
||||
// Launch app
|
||||
Debug.Log("Launching: " + appEntry.appName + " (package id: " + appEntry.packageId + ")");
|
||||
AppProcessor.LaunchApp(appEntry.packageId);
|
||||
if (appEntry.isRenameMode)
|
||||
{
|
||||
this.renameHandler.Rename(appEntry);
|
||||
}
|
||||
else
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
AndroidJNI.AttachCurrentThread();
|
||||
|
||||
try
|
||||
{
|
||||
// Launch app
|
||||
Debug.Log("Launching: " + appEntry.appName + " (package id: " + appEntry.packageId + ")");
|
||||
AppProcessor.LaunchApp(appEntry.packageId);
|
||||
}
|
||||
finally
|
||||
{
|
||||
AndroidJNI.DetachCurrentThread();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnSelectedPressedBorY(Transform t)
|
||||
{
|
||||
var appEntry = t.gameObject.GetComponent("AppEntry") as AppEntry;
|
||||
if (null != appEntry)
|
||||
if (null != appEntry && !appEntry.isRenameMode)
|
||||
{
|
||||
// Add package name to excluded file
|
||||
Debug.Log("Hiding: " + appEntry.appName + " (package id: " + appEntry.packageId + ")");
|
||||
AppProcessor.AddAppToExcludedFile(appEntry.packageId);
|
||||
this.hideAppHandler.OnHideApp(appEntry);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove ourselves from the gridview
|
||||
Destroy(t.gameObject);
|
||||
public void OnSelectedPressedAorX(Transform t)
|
||||
{
|
||||
var appEntry = t.gameObject.GetComponent("AppEntry") as AppEntry;
|
||||
if (null != appEntry && !appEntry.isRenameMode)
|
||||
{
|
||||
this.renameHandler.OpenRenamePanel(appEntry);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace QuestAppLauncher
|
||||
{
|
||||
public class RenameHandler : MonoBehaviour
|
||||
{
|
||||
// Referenced gameobjects
|
||||
public GameObject panelContainer;
|
||||
public GameObject closeRenameButton;
|
||||
public GameObject renamePanelContainer;
|
||||
public GameObject renameScrollContainer;
|
||||
public TextMeshProUGUI renameLabel;
|
||||
public GridPopulation gridPopulation;
|
||||
|
||||
// Whether the rename grid has already been populated
|
||||
private bool isPopulated = false;
|
||||
|
||||
// App entry of the app to rename
|
||||
private AppEntry appToRename;
|
||||
|
||||
/// <summary>
|
||||
/// Opens the rename panel.
|
||||
/// </summary>
|
||||
/// <param name="appToRename">App to rename</param>
|
||||
public async void OpenRenamePanel(AppEntry appToRename)
|
||||
{
|
||||
Debug.Log("Open Rename Panel");
|
||||
this.panelContainer.SetActive(false);
|
||||
this.closeRenameButton.SetActive(true);
|
||||
this.renamePanelContainer.SetActive(true);
|
||||
|
||||
this.appToRename = appToRename;
|
||||
|
||||
this.renameLabel.text = string.Format("Pick entry to rename app '{0}' (package '{1}')", appToRename.appName, appToRename.packageId);
|
||||
|
||||
// Only populate once.
|
||||
// This will be reset in the next scene load (by design, since we may download new assets).
|
||||
if (!this.isPopulated)
|
||||
{
|
||||
await gridPopulation.PopulateRenameAsync();
|
||||
this.isPopulated = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the rename dialog
|
||||
/// </summary>
|
||||
/// <param name="entry"></param>
|
||||
public async void Rename(AppEntry appTarget)
|
||||
{
|
||||
Debug.Assert(null != this.appToRename, "App to rename is null");
|
||||
var filePath = appTarget.externalIconPath;
|
||||
bool cleanupFile = false;
|
||||
|
||||
// If icon path is null, extract icon from apk
|
||||
if (null == filePath)
|
||||
{
|
||||
filePath = Path.Combine(AssetsDownloader.GetOrCreateDownloadPath(), this.appToRename.packageId + ".jpg");
|
||||
cleanupFile = true;
|
||||
|
||||
var bytes = appTarget.sprite.GetComponent<Image>().sprite.texture.EncodeToJPG();
|
||||
using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None,
|
||||
bufferSize: 4096, useAsync: true))
|
||||
{
|
||||
await fileStream.WriteAsync(bytes, 0, bytes.Length);
|
||||
};
|
||||
}
|
||||
|
||||
await Task.Run(() =>
|
||||
{
|
||||
AndroidJNI.AttachCurrentThread();
|
||||
|
||||
try
|
||||
{
|
||||
// Add to json file
|
||||
AddToRenameJsonFile(this.appToRename.packageId, appTarget.appName);
|
||||
|
||||
// Add icon to zip
|
||||
using (AndroidJavaClass unity = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
|
||||
using (AndroidJavaObject currentActivity = unity.GetStatic<AndroidJavaObject>("currentActivity"))
|
||||
{
|
||||
var renameIconPackFilePath = Path.Combine(UnityEngine.Application.persistentDataPath, AppProcessor.RenameIconPackFileName);
|
||||
currentActivity.CallStatic("addFileToZip", renameIconPackFilePath, filePath, this.appToRename.packageId + ".jpg");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
AndroidJNI.DetachCurrentThread();
|
||||
}
|
||||
});
|
||||
|
||||
if (cleanupFile && File.Exists(filePath))
|
||||
{
|
||||
File.Delete(filePath);
|
||||
}
|
||||
|
||||
// Reload the scene
|
||||
await SceneManager.LoadSceneAsync(SceneManager.GetActiveScene().name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the rename panel
|
||||
/// </summary>
|
||||
public void CloseRenamePanel()
|
||||
{
|
||||
Debug.Log("Close Rename Panel");
|
||||
this.panelContainer.SetActive(true);
|
||||
this.closeRenameButton.SetActive(false);
|
||||
this.renamePanelContainer.SetActive(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds app to rename to json file
|
||||
/// </summary>
|
||||
/// <param name="packageId">Package id of app</param>
|
||||
/// <param name="appName">App name</param>
|
||||
private void AddToRenameJsonFile(string packageId, string appName)
|
||||
{
|
||||
var renameJsonFilePath = Path.Combine(UnityEngine.Application.persistentDataPath, AppProcessor.RenameJsonFileName);
|
||||
Dictionary<string, AppProcessor.JsonAppNamesEntry> jsonAppNames = null;
|
||||
|
||||
if (File.Exists(renameJsonFilePath))
|
||||
{
|
||||
// Read rename json file
|
||||
try
|
||||
{
|
||||
var json = File.ReadAllText(renameJsonFilePath, Encoding.UTF8);
|
||||
jsonAppNames = JsonConvert.DeserializeObject<Dictionary<string, AppProcessor.JsonAppNamesEntry>>(json);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// On exception, we'll keep going & just overwrite existing file contents
|
||||
Debug.LogFormat("Failed to process json rename app file: {0}", e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
// Add entry
|
||||
if (null == jsonAppNames)
|
||||
{
|
||||
jsonAppNames = new Dictionary<string, AppProcessor.JsonAppNamesEntry>();
|
||||
}
|
||||
|
||||
jsonAppNames[packageId] = new AppProcessor.JsonAppNamesEntry { Name = appName };
|
||||
|
||||
// Persist
|
||||
try
|
||||
{
|
||||
File.WriteAllText(renameJsonFilePath, JsonConvert.SerializeObject(jsonAppNames, Formatting.Indented));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.Log(string.Format("Failed to write json rename app file: {0}", e.Message));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5f0617ad70a22344684e254e37ddef2e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -39,6 +39,7 @@ namespace QuestAppLauncher
|
||||
public GameObject usageStatsPermText;
|
||||
|
||||
private bool deletedHiddenAppsFile = false;
|
||||
private bool deletedRenameFiles = false;
|
||||
|
||||
private Config config = null;
|
||||
|
||||
@@ -50,6 +51,9 @@ namespace QuestAppLauncher
|
||||
this.closeSettingsButton.SetActive(true);
|
||||
this.settingsContainer.SetActive(true);
|
||||
|
||||
this.deletedHiddenAppsFile = false;
|
||||
this.deletedRenameFiles = false;
|
||||
|
||||
// Load config
|
||||
this.config = ConfigPersistence.LoadConfig();
|
||||
|
||||
@@ -138,6 +142,16 @@ namespace QuestAppLauncher
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteRenameFiles()
|
||||
{
|
||||
Debug.Log("Delete Rename files");
|
||||
|
||||
if (!this.deletedRenameFiles)
|
||||
{
|
||||
this.deletedRenameFiles = AppProcessor.DeleteRenameFiles();
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateGridColText()
|
||||
{
|
||||
var cols = gridCols.GetComponent<Slider>().value;
|
||||
@@ -211,7 +225,7 @@ namespace QuestAppLauncher
|
||||
});
|
||||
}
|
||||
|
||||
private void PersistConfig()
|
||||
private async void PersistConfig()
|
||||
{
|
||||
bool saveConfig = false;
|
||||
|
||||
@@ -317,10 +331,10 @@ namespace QuestAppLauncher
|
||||
}
|
||||
|
||||
// If we touched the config file or we deleted the hidden apps file, re-populate the grid
|
||||
if (saveConfig || deletedHiddenAppsFile)
|
||||
if (saveConfig || this.deletedHiddenAppsFile || this.deletedRenameFiles)
|
||||
{
|
||||
Debug.Log("Re-populating panel");
|
||||
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
|
||||
await SceneManager.LoadSceneAsync(SceneManager.GetActiveScene().name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ PlayerSettings:
|
||||
16:10: 1
|
||||
16:9: 1
|
||||
Others: 1
|
||||
bundleVersion: 0.9.1
|
||||
bundleVersion: 0.10
|
||||
preloadedAssets: []
|
||||
metroInputSource: 0
|
||||
wsaTransparentSwapchain: 0
|
||||
|
||||
Reference in New Issue
Block a user