Adjustments
This commit is contained in:
committed by
Mihai Alexandru
parent
c0d3b9daa2
commit
9e04db2c5c
+55
-57
@@ -488,6 +488,7 @@ class Project extends HXProject
|
||||
configureApp();
|
||||
|
||||
displayTarget();
|
||||
|
||||
configureFeatureFlags();
|
||||
configureCompileDefines();
|
||||
configureIncludeMacros();
|
||||
@@ -497,8 +498,6 @@ class Project extends HXProject
|
||||
configureHaxelibs();
|
||||
configureAssets();
|
||||
configureIcons();
|
||||
if (isAndroid())
|
||||
configureDocumentProvider();
|
||||
|
||||
readASTCExclusion();
|
||||
|
||||
@@ -512,23 +511,14 @@ class Project extends HXProject
|
||||
configureAdMobKeys();
|
||||
}
|
||||
|
||||
// To sign in ios, use -xcode.
|
||||
if (isAndroid())
|
||||
{
|
||||
if (isRelease())
|
||||
{
|
||||
configureAndroidRelease();
|
||||
}
|
||||
configureAndroid();
|
||||
}
|
||||
|
||||
if (isIOS())
|
||||
{
|
||||
config.set("ios.non-exempt-encryption", "false");
|
||||
config.set("ios.team-id", IOS_TEAM_ID);
|
||||
config.set("ios.deployment", "16.0");
|
||||
|
||||
// launchStoryboard = new LaunchStoryboard();
|
||||
// launchStoryboard.path = "./LaunchScreen.storyboard";
|
||||
configureIOS();
|
||||
}
|
||||
|
||||
info("Done configuring project.");
|
||||
@@ -979,38 +969,72 @@ class Project extends HXProject
|
||||
app.path = buildDir;
|
||||
}
|
||||
|
||||
function configureAndroidRelease()
|
||||
function configureAndroid()
|
||||
{
|
||||
if (envConfig != null)
|
||||
javaPaths.push(Path.join([SOURCE_DIR, 'funkin/mobile/external/android/java']));
|
||||
|
||||
if (isRelease())
|
||||
{
|
||||
keystore = new Keystore();
|
||||
keystore.path = Std.string(envConfig.get('ANDROID_KEYSTORE_PATH'));
|
||||
keystore.alias = Std.string(envConfig.get('ANDROID_KEYSTORE_ALIAS'));
|
||||
keystore.password = Std.string(envConfig.get('ANDROID_KEYSTORE_PASSWORD'));
|
||||
if (envConfig != null)
|
||||
{
|
||||
keystore = new Keystore();
|
||||
keystore.path = Std.string(envConfig.get('ANDROID_KEYSTORE_PATH'));
|
||||
keystore.alias = Std.string(envConfig.get('ANDROID_KEYSTORE_ALIAS'));
|
||||
keystore.password = Std.string(envConfig.get('ANDROID_KEYSTORE_PASSWORD'));
|
||||
}
|
||||
|
||||
Reflect.setField(config.get('android.manifest'), 'xmlns:tools', 'http://schemas.android.com/tools');
|
||||
|
||||
final permissionsConfig:Array<String> = [
|
||||
'<uses-permission android:name="android.permission.READ_PHONE_STATE" tools:node="remove" />',
|
||||
'<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:node="remove" />'
|
||||
];
|
||||
|
||||
final xmlChildren:Null<Array<String>> = config.get('android.manifest').xmlChildren;
|
||||
|
||||
if (xmlChildren != null)
|
||||
{
|
||||
config.get('android.manifest').xmlChildren = xmlChildren.concat(permissionsConfig);
|
||||
}
|
||||
else
|
||||
{
|
||||
config.get('android.manifest').xmlChildren = permissionsConfig;
|
||||
}
|
||||
|
||||
if (isBundle())
|
||||
{
|
||||
config.set("android.gradle-project-directory", "bin-bundle");
|
||||
}
|
||||
}
|
||||
|
||||
Reflect.setField(config.get('android.manifest'), 'xmlns:tools', 'http://schemas.android.com/tools');
|
||||
|
||||
final permissionsConfig:Array<String> = [
|
||||
'<uses-permission android:name="android.permission.READ_PHONE_STATE" tools:node="remove" />',
|
||||
'<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:node="remove" />'
|
||||
final modsFolderProvider = [
|
||||
'<provider android:authorities="$PACKAGE_NAME.docprovider" android:name="funkin.provider.DataFolderProvider" android:grantUriPermissions="true" android:exported="true" android:permission="android.permission.MANAGE_DOCUMENTS">',
|
||||
' <intent-filter>',
|
||||
' <action android:name="android.content.action.DOCUMENTS_PROVIDER" />',
|
||||
' </intent-filter>',
|
||||
'</provider>'
|
||||
];
|
||||
|
||||
final xmlChildren:Null<Array<String>> = config.get('android.manifest').xmlChildren;
|
||||
final xmlChildren:Null<Array<String>> = config.get('android.application').xmlChildren;
|
||||
|
||||
if (xmlChildren != null)
|
||||
{
|
||||
config.get('android.manifest').xmlChildren = xmlChildren.concat(permissionsConfig);
|
||||
config.get('android.application').xmlChildren = xmlChildren.concat(modsFolderProvider);
|
||||
}
|
||||
else
|
||||
{
|
||||
config.get('android.manifest').xmlChildren = permissionsConfig;
|
||||
config.get('android.application').xmlChildren = modsFolderProvider;
|
||||
}
|
||||
}
|
||||
|
||||
if (isBundle())
|
||||
{
|
||||
config.set("android.gradle-project-directory", "bin-bundle");
|
||||
}
|
||||
function configureIOS()
|
||||
{
|
||||
config.set("ios.non-exempt-encryption", "false");
|
||||
config.set("ios.team-id", IOS_TEAM_ID);
|
||||
config.set("ios.deployment", "16.0");
|
||||
|
||||
// launchStoryboard = new LaunchStoryboard();
|
||||
// launchStoryboard.path = "./LaunchScreen.storyboard";
|
||||
}
|
||||
|
||||
function configurePolymod()
|
||||
@@ -1261,32 +1285,6 @@ class Project extends HXProject
|
||||
}
|
||||
}
|
||||
|
||||
function configureDocumentProvider()
|
||||
{
|
||||
final providerXml = ['
|
||||
<provider
|
||||
android:authorities="${PACKAGE_NAME}.docprovider"
|
||||
android:name=".ModFolderProvider"
|
||||
android:grantUriPermissions="true"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.MANAGE_DOCUMENTS">
|
||||
<intent-filter>
|
||||
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
|
||||
</intent-filter>
|
||||
</provider>'];
|
||||
|
||||
final xmlChildren:Null<Array<String>> = config.get('android.application').xmlChildren;
|
||||
|
||||
if (xmlChildren != null)
|
||||
{
|
||||
config.get('android.application').xmlChildren = xmlChildren.concat(providerXml);
|
||||
}
|
||||
else
|
||||
{
|
||||
config.get('android.application').xmlChildren = providerXml;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the platforms' Ad Mob application keys.
|
||||
*/
|
||||
|
||||
+4
-5
@@ -38,12 +38,11 @@ class Main extends Sprite
|
||||
{
|
||||
// Set the current working directory for Android and iOS devices
|
||||
#if android
|
||||
// For Android we determine the appropriate directory based on Android version
|
||||
Sys.setCwd(haxe.io.Path.addTrailingSlash(extension.androidtools.os.Build.VERSION.SDK_INT > 30 ? extension.androidtools.content.Context.getObbDir() : // Use Obb directory for Android SDK version > 30
|
||||
extension.androidtools.content.Context.getExternalFilesDir() // Use External Files directory for Android SDK version < 30
|
||||
));
|
||||
// On Android use External Files Dir.
|
||||
Sys.setCwd(haxe.io.Path.addTrailingSlash(extension.androidtools.content.Context.getExternalFilesDir()));
|
||||
#elseif ios
|
||||
Sys.setCwd(haxe.io.Path.addTrailingSlash(lime.system.System.documentsDirectory)); // For iOS we use documents directory and this is only way we can do.
|
||||
// On iOS use Documents Dir.
|
||||
Sys.setCwd(haxe.io.Path.addTrailingSlash(lime.system.System.documentsDirectory));
|
||||
#end
|
||||
|
||||
// We need to make the crash handler LITERALLY FIRST so nothing EVER gets past it.
|
||||
|
||||
@@ -114,7 +114,7 @@ class InitState extends FlxState
|
||||
|
||||
#if ios
|
||||
// Setup Audio session
|
||||
funkin.mobile.external.AudioSession.initialize();
|
||||
funkin.mobile.external.ios.AudioSession.initialize();
|
||||
#end
|
||||
|
||||
// This ain't a pixel art game! (most of the time)
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package funkin.mobile.external.android;
|
||||
|
||||
import lime.math.Rectangle;
|
||||
import lime.system.JNI;
|
||||
|
||||
/**
|
||||
* A Utility class to get Android screen related informations.
|
||||
*/
|
||||
class ScreenUtils
|
||||
{
|
||||
@:noCompletion
|
||||
private static var staticMethodCache:Map<String, Dynamic> = [];
|
||||
|
||||
@:noCompletion
|
||||
private static var memberFieldCache:Map<String, JNIMemberField> = [];
|
||||
|
||||
public static function getCutoutDimensions():Array<Rectangle>
|
||||
{
|
||||
final getCutoutDimensionsJNI:Null<Dynamic> = createStaticMethod('funkin/util/ScreenUtils', 'getCutoutDimensions', '()[Landroid/graphics/Rect;');
|
||||
|
||||
if (getCutoutDimensionsJNI != null)
|
||||
{
|
||||
final rectangles:Array<Rectangle> = [];
|
||||
|
||||
for (rectangle in cast(getCutoutDimensionsJNI(), Array<Dynamic>))
|
||||
{
|
||||
if (rectangle == null) continue;
|
||||
|
||||
final topJNI:Null<JNIMemberField> = createMemberField('android/graphics/Rect', 'top', 'I');
|
||||
final leftJNI:Null<JNIMemberField> = createMemberField('android/graphics/Rect', 'left', 'I');
|
||||
final rightJNI:Null<JNIMemberField> = createMemberField('android/graphics/Rect', 'right', 'I');
|
||||
final bottomJNI:Null<JNIMemberField> = createMemberField('android/graphics/Rect', 'bottom', 'I');
|
||||
|
||||
if (topJNI != null && leftJNI != null && rightJNI != null && bottomJNI != null)
|
||||
{
|
||||
final top:Int = topJNI.get(rectangle);
|
||||
final left:Int = leftJNI.get(rectangle);
|
||||
final right:Int = rightJNI.get(rectangle);
|
||||
final bottom:Int = bottomJNI.get(rectangle);
|
||||
|
||||
rectangles.push(new Rectangle(left, top, right - left, bottom - top));
|
||||
}
|
||||
}
|
||||
|
||||
return rectangles;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public static function createStaticMethod(className:String, methodName:String, signature:String, cache:Bool = true):Null<Dynamic>
|
||||
{
|
||||
@:privateAccess
|
||||
className = JNI.transformClassName(className);
|
||||
|
||||
final key:String = '$className::$methodName::$signature';
|
||||
|
||||
if (cache && !staticMethodCache.exists(key)) staticMethodCache.set(key, JNI.createStaticMethod(className, methodName, signature));
|
||||
else if (!cache) return JNI.createStaticMethod(className, methodName, signature);
|
||||
|
||||
return staticMethodCache.get(key);
|
||||
}
|
||||
|
||||
public static function createMemberField(className:String, fieldName:String, signature:String, cache:Bool = true):Null<JNIMemberField>
|
||||
{
|
||||
@:privateAccess
|
||||
className = JNI.transformClassName(className);
|
||||
|
||||
final key:String = '$className::$fieldName::$signature';
|
||||
|
||||
if (cache && !memberFieldCache.exists(key)) memberFieldCache.set(key, JNI.createMemberField(className, fieldName, signature));
|
||||
else if (!cache) return JNI.createMemberField(className, fieldName, signature);
|
||||
|
||||
return memberFieldCache.get(key);
|
||||
}
|
||||
}
|
||||
+302
@@ -0,0 +1,302 @@
|
||||
package funkin.provider;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.graphics.Point;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.DocumentsProvider;
|
||||
import android.provider.DocumentsContract.Root;
|
||||
import android.provider.DocumentsContract.Document;
|
||||
import android.util.Log;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class DataFolderProvider extends DocumentsProvider
|
||||
{
|
||||
private static final String TAG = "DataFolderProvider";
|
||||
|
||||
private static final String ROOT = "root";
|
||||
|
||||
private static final String[] DEFAULT_ROOT_PROJECTION = new String[]{Root.COLUMN_ROOT_ID, Root.COLUMN_MIME_TYPES, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE, Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID, Root.COLUMN_AVAILABLE_BYTES};
|
||||
|
||||
private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[]{Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS, Document.COLUMN_SIZE};
|
||||
|
||||
private File rootDir;
|
||||
|
||||
@Override
|
||||
public Cursor queryRoots(String[] projection) throws FileNotFoundException
|
||||
{
|
||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_ROOT_PROJECTION);
|
||||
|
||||
final MatrixCursor.RowBuilder row = result.newRow();
|
||||
|
||||
row.add(Root.COLUMN_ROOT_ID, ROOT);
|
||||
|
||||
row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_IS_CHILD);
|
||||
|
||||
row.add(Root.COLUMN_SUMMARY, "Data Folder");
|
||||
|
||||
row.add(Root.COLUMN_DOCUMENT_ID, getDocumentID(rootDir));
|
||||
|
||||
::if (APP_TITLE != "")::row.add(Root.COLUMN_TITLE, "::APP_TITLE::");::end::
|
||||
|
||||
::if (APP_PACKAGE != "")::row.add(Root.COLUMN_ICON, ::APP_PACKAGE::.R.mipmap.ic_launcher);::end::
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException
|
||||
{
|
||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||
|
||||
includeFile(result, getFileFromDocumentID(documentId));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException
|
||||
{
|
||||
final MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||
|
||||
for (File file : getFileFromDocumentID(parentDocumentId).listFiles())
|
||||
{
|
||||
includeFile(result, file);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openDocument(String documentId, String mode, CancellationSignal signal) throws FileNotFoundException
|
||||
{
|
||||
return ParcelFileDescriptor.open(getFileFromDocumentID(documentId), ParcelFileDescriptor.parseMode(mode));
|
||||
}
|
||||
|
||||
@SuppressLint({"SetWorldReadable", "SetWorldWritable"})
|
||||
@Override
|
||||
public String createDocument(String parentDocumentId, String mimeType, String displayName) throws FileNotFoundException
|
||||
{
|
||||
File parentPath = getFileFromDocumentID(parentDocumentId);
|
||||
|
||||
try
|
||||
{
|
||||
File file = new File(parentPath, displayName);
|
||||
|
||||
if (mimeType.equals(Document.MIME_TYPE_DIR))
|
||||
assert file.mkdir();
|
||||
else
|
||||
assert file.createNewFile();
|
||||
|
||||
assert file.setReadable(true, false);
|
||||
assert file.setWritable(true, false);
|
||||
|
||||
return getDocumentID(file);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new FileNotFoundException("Cannot create file with id " + parentDocumentId + "/" + displayName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteDocument(String documentId) throws FileNotFoundException
|
||||
{
|
||||
File file = getFileFromDocumentID(documentId);
|
||||
|
||||
Boolean result = false;
|
||||
|
||||
try
|
||||
{
|
||||
if (file.isDirectory())
|
||||
{
|
||||
result = deleteFolderRecursively(file);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = file.delete();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.e(TAG, e.toString());
|
||||
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (!result)
|
||||
{
|
||||
throw new FileNotFoundException("Failed to delete document with id " + documentId);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean deleteFolderRecursively(File folder)
|
||||
{
|
||||
boolean isSuccessful = true;
|
||||
|
||||
final File[] files = folder.listFiles();
|
||||
|
||||
if (files == null || !folder.exists())
|
||||
return false;
|
||||
|
||||
for (File item : files)
|
||||
{
|
||||
if (item.isDirectory())
|
||||
{
|
||||
isSuccessful = isSuccessful && deleteFolderRecursively(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
isSuccessful = isSuccessful && item.delete();
|
||||
}
|
||||
}
|
||||
|
||||
isSuccessful = isSuccessful && folder.delete();
|
||||
|
||||
return isSuccessful;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String renameDocument(String documentId, String displayName) throws FileNotFoundException
|
||||
{
|
||||
File origin = getFileFromDocumentID(documentId);
|
||||
File dest = new File(origin.getParentFile(), displayName);
|
||||
|
||||
if (!origin.renameTo(dest))
|
||||
{
|
||||
throw new FileNotFoundException("Failed to rename document with id " + documentId + " to " + displayName);
|
||||
}
|
||||
|
||||
return getDocumentID(dest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChildDocument(String parentDocumentId, String documentId)
|
||||
{
|
||||
return documentId.startsWith(parentDocumentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AssetFileDescriptor openDocumentThumbnail(String documentId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException
|
||||
{
|
||||
final File file = getFileFromDocumentID(documentId);
|
||||
final ParcelFileDescriptor parcelFileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||
return new AssetFileDescriptor(parcelFileDescriptor, 0, AssetFileDescriptor.UNKNOWN_LENGTH);
|
||||
}
|
||||
|
||||
private String getDocumentID(File file)
|
||||
{
|
||||
Path filePath = file.toPath().normalize();
|
||||
Path rootPath = rootDir.toPath();
|
||||
|
||||
if (!filePath.startsWith(rootPath))
|
||||
return null;
|
||||
|
||||
return ROOT + ":" + rootPath.relativize(filePath).toString();
|
||||
}
|
||||
|
||||
private File getFileFromDocumentID(String id)
|
||||
{
|
||||
final String[] separatedPath = id.split(":", 2);
|
||||
|
||||
if (separatedPath.length < 2)
|
||||
return null;
|
||||
|
||||
if (!separatedPath[0].equals(ROOT))
|
||||
return null;
|
||||
|
||||
final String filePath = separatedPath[1];
|
||||
|
||||
if (filePath.isEmpty())
|
||||
return rootDir;
|
||||
|
||||
final Path rootPath = rootDir.toPath().normalize().toAbsolutePath();
|
||||
final Path desiredPath = rootPath.resolve(filePath).normalize().toAbsolutePath();
|
||||
|
||||
if (!desiredPath.startsWith(rootPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return desiredPath.toFile();
|
||||
}
|
||||
|
||||
private void includeFile(MatrixCursor result, File file)
|
||||
{
|
||||
if (file == null)
|
||||
file = rootDir;
|
||||
|
||||
int flags = 0;
|
||||
final String mimeType = getTypeForFile(file);
|
||||
|
||||
if (file.canWrite())
|
||||
{
|
||||
flags |= Document.FLAG_SUPPORTS_WRITE |
|
||||
Document.FLAG_SUPPORTS_DELETE |
|
||||
Document.FLAG_SUPPORTS_RENAME;
|
||||
|
||||
if (file.isDirectory())
|
||||
flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
|
||||
}
|
||||
|
||||
if (mimeType.startsWith("image/"))
|
||||
{
|
||||
flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
|
||||
}
|
||||
|
||||
final MatrixCursor.RowBuilder row = result.newRow();
|
||||
row.add(Document.COLUMN_DOCUMENT_ID, getDocumentID(file));
|
||||
row.add(Document.COLUMN_DISPLAY_NAME, file.getName());
|
||||
row.add(Document.COLUMN_SIZE, file.length());
|
||||
row.add(Document.COLUMN_MIME_TYPE, mimeType);
|
||||
row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
|
||||
row.add(Document.COLUMN_FLAGS, flags);
|
||||
}
|
||||
|
||||
private static String getTypeForFile(File file)
|
||||
{
|
||||
if (file.isDirectory())
|
||||
{
|
||||
return Document.MIME_TYPE_DIR;
|
||||
}
|
||||
else
|
||||
{
|
||||
return getTypeForName(file.getName());
|
||||
}
|
||||
}
|
||||
|
||||
private static String getTypeForName(String name)
|
||||
{
|
||||
final int lastDot = name.lastIndexOf('.');
|
||||
if (lastDot >= 0)
|
||||
{
|
||||
final String extension = name.substring(lastDot + 1);
|
||||
|
||||
if (extension.equals("hxc") || extension.equals("hx"))
|
||||
return "application/*";
|
||||
|
||||
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||
if (mime != null)
|
||||
{
|
||||
return mime;
|
||||
}
|
||||
}
|
||||
return "application/octet-stream";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreate()
|
||||
{
|
||||
rootDir = getContext().getExternalFilesDir(null);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package funkin.util;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.view.DisplayCutout;
|
||||
import android.view.WindowInsets;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.haxe.extension.Extension;
|
||||
|
||||
public class ScreenUtils
|
||||
{
|
||||
public static Rect[] getCutoutDimensions()
|
||||
{
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
|
||||
{
|
||||
WindowInsets insets = Extension.mainActivity.getWindow().getDecorView().getRootWindowInsets();
|
||||
|
||||
if (insets != null)
|
||||
{
|
||||
DisplayCutout cutout = insets.getDisplayCutout();
|
||||
|
||||
if (cutout != null)
|
||||
{
|
||||
List<Rect> boundingRects = cutout.getBoundingRects();
|
||||
|
||||
if (boundingRects != null && !boundingRects.isEmpty())
|
||||
return boundingRects.toArray(new Rect[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Rect[0];
|
||||
}
|
||||
}
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
package funkin.mobile.external;
|
||||
package funkin.mobile.external.ios;
|
||||
|
||||
/**
|
||||
* A Utility class to manage iOS audio.
|
||||
Vendored
+2
-1
@@ -1,4 +1,4 @@
|
||||
package funkin.mobile.external;
|
||||
package funkin.mobile.external.ios;
|
||||
|
||||
/**
|
||||
* A Utility class to get iOS screen related informations.
|
||||
@@ -10,6 +10,7 @@ extern class ScreenUtils
|
||||
{
|
||||
@:native('getSafeAreaInsets')
|
||||
static function getSafeAreaInsets(top:cpp.RawPointer<Float>, bottom:cpp.RawPointer<Float>, left:cpp.RawPointer<Float>, right:cpp.RawPointer<Float>):Void;
|
||||
|
||||
@:native('getScreenSize')
|
||||
static function getScreenSize(width:cpp.RawPointer<Float>, height:cpp.RawPointer<Float>):Void;
|
||||
}
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xml>
|
||||
<pragma once="true" />
|
||||
|
||||
<files id="haxe">
|
||||
<compilerflag value="-I${this_dir}/ios/include" if="iphoneos || iphonesim" />
|
||||
</files>
|
||||
|
||||
<files id="__main__">
|
||||
<compilerflag value="-I${this_dir}/ios/include" if="iphoneos || iphonesim" />
|
||||
</files>
|
||||
|
||||
<files id="external-ios" dir="${this_dir}/ios" if="iphoneos || iphonesim">
|
||||
<compilerflag value="-I${this_dir}/ios/include" />
|
||||
|
||||
<file name="src/ScreenUtils.mm" />
|
||||
<file name="src/AudioSession.mm" />
|
||||
</files>
|
||||
|
||||
<target id="haxe">
|
||||
<section if="iphoneos || iphonesim">
|
||||
<vflag name="-framework" value="UIKit" />
|
||||
<vflag name="-framework" value="Foundation" />
|
||||
<vflag name="-framework" value="AVFAudio" />
|
||||
</section>
|
||||
|
||||
<files id="external-ios" if="iphoneos || iphonesim" />
|
||||
</target>
|
||||
</xml>
|
||||
@@ -1,4 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
void initialize();
|
||||
void setActive(bool active);
|
||||
@@ -1,4 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
void getSafeAreaInsets(double* top, double* bottom, double* left, double* right);
|
||||
void getScreenSize(double* width, double* height);
|
||||
@@ -1,41 +0,0 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AVFAudio/AVFAudio.h>
|
||||
|
||||
void initialize()
|
||||
{
|
||||
AVAudioSession *session = [AVAudioSession sharedInstance];
|
||||
|
||||
NSError *error;
|
||||
[session setCategory:AVAudioSessionCategoryPlayback
|
||||
mode:AVAudioSessionModeDefault
|
||||
options:AVAudioSessionCategoryOptionAllowBluetoothA2DP|AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers
|
||||
error:&error];
|
||||
|
||||
if (@available(iOS 17.0, *))
|
||||
{
|
||||
[session setPrefersInterruptionOnRouteDisconnect:false error:nil];
|
||||
}
|
||||
|
||||
if (@available(iOS 14.5, *))
|
||||
{
|
||||
[session setPrefersNoInterruptionsFromSystemAlerts:true error:nil];
|
||||
}
|
||||
|
||||
if (error)
|
||||
{
|
||||
NSLog(@"Unable to set category of audio session: %@", error);
|
||||
}
|
||||
}
|
||||
|
||||
void setActive(bool active)
|
||||
{
|
||||
AVAudioSession *session = [AVAudioSession sharedInstance];
|
||||
NSError *error;
|
||||
|
||||
[session setActive:YES error:&error];
|
||||
|
||||
if (error)
|
||||
{
|
||||
NSLog(@"Unable to set active of audio session: %@", error);
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
#import "ScreenUtils.hpp"
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
void getSafeAreaInsets(double* top, double* bottom, double* left, double* right)
|
||||
{
|
||||
if (@available(iOS 11, *))
|
||||
{
|
||||
UIWindow* window = [UIApplication sharedApplication].windows[0];
|
||||
UIEdgeInsets safeAreaInsets = window.safeAreaInsets;
|
||||
|
||||
float scale = [UIScreen mainScreen].scale;
|
||||
|
||||
(*top) = safeAreaInsets.top * scale;
|
||||
(*bottom) = safeAreaInsets.bottom * scale;
|
||||
(*left) = safeAreaInsets.left * scale;
|
||||
(*right) = safeAreaInsets.right * scale;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
(*top) = 0.0;
|
||||
(*bottom) = 0.0;
|
||||
(*left) = 0.0;
|
||||
(*right) = 0.0;
|
||||
}
|
||||
|
||||
void getScreenSize(double* width, double* height)
|
||||
{
|
||||
CGRect screenRect = [[UIScreen mainScreen] bounds];
|
||||
float scale = [UIScreen mainScreen].scale;
|
||||
|
||||
(*width) = (double)screenRect.size.width * scale;
|
||||
(*height) = (double)screenRect.size.height * scale;
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package funkin.mobile.util;
|
||||
|
||||
#if ios
|
||||
import funkin.mobile.external.ScreenUtils;
|
||||
import funkin.mobile.external.ios.ScreenUtils;
|
||||
#elseif android
|
||||
import extension.androidtools.Tools;
|
||||
import funkin.mobile.external.android.ScreenUtils;
|
||||
#end
|
||||
import lime.math.Rectangle;
|
||||
import lime.system.System;
|
||||
@@ -26,7 +26,7 @@ class ScreenUtil
|
||||
final rectDimensions:Array<Array<Float>> = [[], [], [], []];
|
||||
|
||||
// Push all the dimensions of the cutouts into an array
|
||||
for (rect in Tools.getCutoutDimensions())
|
||||
for (rect in ScreenUtils.getCutoutDimensions())
|
||||
{
|
||||
rectDimensions[0].push(rect.x);
|
||||
rectDimensions[1].push(rect.y);
|
||||
|
||||
@@ -18,7 +18,7 @@ class CLIUtil
|
||||
var cwd:String = Path.addTrailingSlash(Sys.getCwd());
|
||||
var gameDir:String = '';
|
||||
#if android
|
||||
gameDir = Path.addTrailingSlash(extension.androidtools.os.Build.VERSION.SDK_INT > 30 ? extension.androidtools.content.Context.getObbDir() : extension.androidtools.content.Context.getExternalFilesDir());
|
||||
gameDir = Path.addTrailingSlash(extension.androidtools.content.Context.getExternalFilesDir());
|
||||
#elseif ios
|
||||
// Why? Because for some reason lime.system.System.documentsDirectory is returning a directory that's different and we're unable to read or write from, so it's disabled and no solution is found...
|
||||
trace('[WARN]: Reseting the Current Working Directory is unavailable on iOS targets');
|
||||
|
||||
@@ -1,299 +0,0 @@
|
||||
package me.funkin.fnf;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.graphics.Point;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.DocumentsProvider;
|
||||
import android.provider.DocumentsContract.Root;
|
||||
import android.provider.DocumentsContract.Document;
|
||||
import android.util.Log;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class ModFolderProvider extends DocumentsProvider
|
||||
{
|
||||
private static final String TAG = "ModFolderProvider";
|
||||
private File rootDir;
|
||||
private static final String[] DEFAULT_ROOT_PROJECTION =
|
||||
new String[]{Root.COLUMN_ROOT_ID, Root.COLUMN_MIME_TYPES,
|
||||
Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE,
|
||||
Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID,
|
||||
Root.COLUMN_AVAILABLE_BYTES,};
|
||||
private static final String[] DEFAULT_DOCUMENT_PROJECTION = new
|
||||
String[]{Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE,
|
||||
Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED,
|
||||
Document.COLUMN_FLAGS, Document.COLUMN_SIZE,};
|
||||
|
||||
private static final String ROOT = "root";
|
||||
|
||||
@Override
|
||||
public Cursor queryRoots(String[] projection) throws FileNotFoundException
|
||||
{
|
||||
final MatrixCursor result =
|
||||
new MatrixCursor(projection != null ? projection : DEFAULT_ROOT_PROJECTION);
|
||||
|
||||
final MatrixCursor.RowBuilder row = result.newRow();
|
||||
row.add(Root.COLUMN_ROOT_ID, ROOT);
|
||||
row.add(Root.COLUMN_DOCUMENT_ID, getDocumentID(rootDir));
|
||||
row.add(Root.COLUMN_TITLE, "Friday Night Funkin'");
|
||||
row.add(Root.COLUMN_SUMMARY, "Mods folder");
|
||||
row.add(Root.COLUMN_FLAGS, Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_IS_CHILD);
|
||||
row.add(Root.COLUMN_ICON, R.mipmap.ic_launcher);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException
|
||||
{
|
||||
final MatrixCursor result =
|
||||
new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||
|
||||
includeFile(result, getFileFromDocumentID(documentId));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException
|
||||
{
|
||||
final MatrixCursor result =
|
||||
new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
|
||||
|
||||
final File parent = getFileFromDocumentID(parentDocumentId);
|
||||
|
||||
for (File file : parent.listFiles())
|
||||
{
|
||||
includeFile(result, file);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openDocument(String documentId, String mode, CancellationSignal signal) throws FileNotFoundException
|
||||
{
|
||||
return ParcelFileDescriptor.open(getFileFromDocumentID(documentId), ParcelFileDescriptor.parseMode(mode));
|
||||
}
|
||||
|
||||
@SuppressLint({"SetWorldReadable", "SetWorldWritable"})
|
||||
@Override
|
||||
public String createDocument(String parentDocumentId, String mimeType, String displayName) throws FileNotFoundException
|
||||
{
|
||||
File parentPath = getFileFromDocumentID(parentDocumentId);
|
||||
try
|
||||
{
|
||||
File file = new File(parentPath, displayName);
|
||||
if (mimeType.equals(Document.MIME_TYPE_DIR))
|
||||
assert file.mkdir();
|
||||
else
|
||||
assert file.createNewFile();
|
||||
assert file.setReadable(true, false);
|
||||
assert file.setWritable(true, false);
|
||||
|
||||
return getDocumentID(file);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new FileNotFoundException("Cannot create file with id " + parentDocumentId + "/" + displayName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteDocument(String documentId) throws FileNotFoundException
|
||||
{
|
||||
File file = getFileFromDocumentID(documentId);
|
||||
|
||||
Boolean result = false;
|
||||
|
||||
try
|
||||
{
|
||||
if (file.isDirectory())
|
||||
{
|
||||
result = deleteFolderRecursively(file);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = file.delete();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.e(TAG, e.toString());
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (!result)
|
||||
{
|
||||
throw new FileNotFoundException("Failed to delete document with id " + documentId);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean deleteFolderRecursively(File folder)
|
||||
{
|
||||
boolean isSuccessful = true;
|
||||
|
||||
final File[] files = folder.listFiles();
|
||||
|
||||
if (files == null || !folder.exists())
|
||||
return false;
|
||||
|
||||
for (File item : files)
|
||||
{
|
||||
if (item.isDirectory())
|
||||
{
|
||||
isSuccessful = isSuccessful && deleteFolderRecursively(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
isSuccessful = isSuccessful && item.delete();
|
||||
}
|
||||
}
|
||||
|
||||
isSuccessful = isSuccessful && folder.delete();
|
||||
|
||||
return isSuccessful;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String renameDocument(String documentId, String displayName) throws FileNotFoundException
|
||||
{
|
||||
File origin = getFileFromDocumentID(documentId);
|
||||
File dest = new File(origin.getParentFile(), displayName);
|
||||
|
||||
if (!origin.renameTo(dest))
|
||||
{
|
||||
throw new FileNotFoundException("Failed to rename document with id " + documentId + " to " + displayName);
|
||||
}
|
||||
|
||||
return getDocumentID(dest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChildDocument(String parentDocumentId, String documentId)
|
||||
{
|
||||
return documentId.startsWith(parentDocumentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AssetFileDescriptor openDocumentThumbnail(String documentId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException
|
||||
{
|
||||
final File file = getFileFromDocumentID(documentId);
|
||||
final ParcelFileDescriptor parcelFileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||
return new AssetFileDescriptor(parcelFileDescriptor, 0, AssetFileDescriptor.UNKNOWN_LENGTH);
|
||||
}
|
||||
|
||||
private String getDocumentID(File file)
|
||||
{
|
||||
Path filePath = file.toPath().normalize();
|
||||
Path rootPath = rootDir.toPath();
|
||||
|
||||
if (!filePath.startsWith(rootPath))
|
||||
return null;
|
||||
|
||||
return ROOT + ":" + rootPath.relativize(filePath).toString();
|
||||
}
|
||||
|
||||
private File getFileFromDocumentID(String id)
|
||||
{
|
||||
final String[] separatedPath = id.split(":", 2);
|
||||
|
||||
if (separatedPath.length < 2)
|
||||
return null;
|
||||
|
||||
if (!separatedPath[0].equals(ROOT))
|
||||
return null;
|
||||
|
||||
final String filePath = separatedPath[1];
|
||||
|
||||
if (filePath.isEmpty())
|
||||
return rootDir;
|
||||
|
||||
final Path rootPath = rootDir.toPath().normalize().toAbsolutePath();
|
||||
final Path desiredPath = rootPath.resolve(filePath).normalize().toAbsolutePath();
|
||||
|
||||
if (!desiredPath.startsWith(rootPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return desiredPath.toFile();
|
||||
}
|
||||
|
||||
private void includeFile(MatrixCursor result, File file)
|
||||
{
|
||||
if (file == null)
|
||||
file = rootDir;
|
||||
|
||||
int flags = 0;
|
||||
final String mimeType = getTypeForFile(file);
|
||||
|
||||
if (file.canWrite())
|
||||
{
|
||||
flags |= Document.FLAG_SUPPORTS_WRITE |
|
||||
Document.FLAG_SUPPORTS_DELETE |
|
||||
Document.FLAG_SUPPORTS_RENAME;
|
||||
|
||||
if (file.isDirectory())
|
||||
flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
|
||||
}
|
||||
|
||||
if (mimeType.startsWith("image/"))
|
||||
{
|
||||
flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
|
||||
}
|
||||
|
||||
final MatrixCursor.RowBuilder row = result.newRow();
|
||||
row.add(Document.COLUMN_DOCUMENT_ID, getDocumentID(file));
|
||||
row.add(Document.COLUMN_DISPLAY_NAME, file.getName());
|
||||
row.add(Document.COLUMN_SIZE, file.length());
|
||||
row.add(Document.COLUMN_MIME_TYPE, mimeType);
|
||||
row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
|
||||
row.add(Document.COLUMN_FLAGS, flags);
|
||||
}
|
||||
|
||||
private static String getTypeForFile(File file)
|
||||
{
|
||||
if (file.isDirectory())
|
||||
{
|
||||
return Document.MIME_TYPE_DIR;
|
||||
}
|
||||
else
|
||||
{
|
||||
return getTypeForName(file.getName());
|
||||
}
|
||||
}
|
||||
|
||||
private static String getTypeForName(String name)
|
||||
{
|
||||
final int lastDot = name.lastIndexOf('.');
|
||||
if (lastDot >= 0)
|
||||
{
|
||||
final String extension = name.substring(lastDot + 1);
|
||||
|
||||
if (extension.equals("hxc") || extension.equals("hx"))
|
||||
return "application/*";
|
||||
|
||||
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||
if (mime != null)
|
||||
{
|
||||
return mime;
|
||||
}
|
||||
}
|
||||
return "application/octet-stream";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreate()
|
||||
{
|
||||
rootDir = getContext().getObbDir();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user