diff --git a/FCL/src/main/java/com/tungsten/fcl/ui/controller/ControllerManagePage.java b/FCL/src/main/java/com/tungsten/fcl/ui/controller/ControllerManagePage.java index e08db3991e..79d9849b0f 100644 --- a/FCL/src/main/java/com/tungsten/fcl/ui/controller/ControllerManagePage.java +++ b/FCL/src/main/java/com/tungsten/fcl/ui/controller/ControllerManagePage.java @@ -23,6 +23,7 @@ import com.tungsten.fcl.ui.PageManager; import com.tungsten.fcl.ui.UIManager; import com.tungsten.fcl.util.AndroidUtils; +import com.tungsten.fcl.util.LayoutConverter; import com.tungsten.fcl.util.RequestCodes; import com.tungsten.fclauncher.utils.FCLPath; import com.tungsten.fclcore.fakefx.beans.binding.Bindings; @@ -33,9 +34,12 @@ import com.tungsten.fclcore.task.Schedulers; import com.tungsten.fclcore.task.Task; import com.tungsten.fclcore.util.Logging; +import com.tungsten.fclcore.util.function.ExceptionalConsumer; import com.tungsten.fclcore.util.io.FileUtils; import com.tungsten.fcllibrary.browser.FileBrowser; import com.tungsten.fcllibrary.browser.options.LibMode; + +import kotlin.Unit; import com.tungsten.fcllibrary.browser.options.SelectionMode; import com.tungsten.fcllibrary.component.ui.FCLCommonPage; import com.tungsten.fcllibrary.component.view.FCLButton; @@ -43,10 +47,12 @@ import com.tungsten.fcllibrary.component.view.FCLProgressBar; import com.tungsten.fcllibrary.component.view.FCLTextView; import com.tungsten.fcllibrary.component.view.FCLUILayout; +import com.tungsten.fcllibrary.ui.ProgressDialog; import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.logging.Level; public class ControllerManagePage extends FCLCommonPage implements View.OnClickListener { @@ -238,14 +244,22 @@ public void onClick(View view) { ControllerPageManager.getInstance().showTempPage(page); } if (view == share) { - Intent intent = new Intent(Intent.ACTION_SEND); - Uri uri = FileProvider.getUriForFile(getContext(), getContext().getString(com.tungsten.fcllibrary.R.string.file_browser_provider), new File(FCLPath.CONTROLLER_DIR, getSelectedController().getFileName())); - intent.setType(AndroidUtils.getMimeType(FCLPath.CONTROLLER_DIR + "/" + getSelectedController().getFileName())); - intent.putExtra(Intent.EXTRA_STREAM, uri); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - intent.addCategory(Intent.CATEGORY_DEFAULT); - getActivity().startActivity(Intent.createChooser(intent, getContext().getString(R.string.control_share))); + DialogUtilKt.showItemSelectionDialog( + getContext(), + getContext().getString(R.string.control_share_choose), + new ArrayList<>(Arrays.asList( + getContext().getString(R.string.control_share_direct), + getContext().getString(R.string.control_share_zl2) + )), + selected -> { + if (selected.equals(getContext().getString(R.string.control_share_direct))) { + shareDirect(); + } else if (selected.equals(getContext().getString(R.string.control_share_zl2))) { + shareAsZl2(); + } + return Unit.INSTANCE; + } + ); } if (view == editInfo) { ControllerInfoDialog dialog = new ControllerInfoDialog(getContext(), false, selectedController.get(), (controller) -> changeControllerInfo(selectedController.get(), controller)); @@ -259,4 +273,53 @@ public void onClick(View view) { getActivity().startActivity(intent); } } + + /** 直接分享原始 FCL 控制布局 JSON 文件。 */ + private void shareDirect() { + File file = new File(FCLPath.CONTROLLER_DIR, getSelectedController().getFileName()); + shareFile(file, R.string.control_share, AndroidUtils.getMimeType(file.getAbsolutePath())); + } + + /** 转换为 ZL2 格式后分享。 */ + private void shareAsZl2() { + if (!LayoutConverter.isSupported()) { + Toast.makeText(getContext(), R.string.control_convert_unsupported, Toast.LENGTH_LONG).show(); + return; + } + ProgressDialog dialog = new ProgressDialog(getContext()); + dialog.show(); + Controller controller = getSelectedController(); + File input = new File(FCLPath.CONTROLLER_DIR, controller.getFileName()); + // 输出到公共目录 FCL/share/,便于文件管理器定位 + File output = new File(FCLPath.SHARE_DIR, controller.getId() + "_zl2.json"); + //noinspection ResultOfMethodCallIgnored + output.delete(); + Task.supplyAsync(() -> { + return LayoutConverter.convertFclToZl2(input, output); + }).thenAcceptAsync(Schedulers.androidUIThread(), (ExceptionalConsumer) error -> { + if (error == null) { + shareFile(output, R.string.control_share_zl2_title, "application/json"); + } else { + DialogUtilKt.showErrorDialog(getContext(), getContext().getString(R.string.control_convert_failed) + "\n" + error); + } + }).whenComplete(Schedulers.androidUIThread(), exception -> { + dialog.dismiss(); + if (exception != null) { + Logging.LOG.log(Level.SEVERE, "Failed to convert controller to ZL2", exception); + DialogUtilKt.showErrorDialog(getContext(), getContext().getString(R.string.control_convert_failed) + "\n" + exception.getMessage()); + } + }).start(); + } + + /** 通过系统分享面板分享指定文件。 */ + private void shareFile(File file, int chooserTitleRes, String mimeType) { + Intent intent = new Intent(Intent.ACTION_SEND); + Uri uri = FileProvider.getUriForFile(getContext(), getContext().getString(com.tungsten.fcllibrary.R.string.file_browser_provider), file); + intent.setType(mimeType); + intent.putExtra(Intent.EXTRA_STREAM, uri); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.addCategory(Intent.CATEGORY_DEFAULT); + getActivity().startActivity(Intent.createChooser(intent, getContext().getString(chooserTitleRes))); + } } diff --git a/FCL/src/main/java/com/tungsten/fcl/util/LayoutConverter.java b/FCL/src/main/java/com/tungsten/fcl/util/LayoutConverter.java new file mode 100644 index 0000000000..e74e8fd2fe --- /dev/null +++ b/FCL/src/main/java/com/tungsten/fcl/util/LayoutConverter.java @@ -0,0 +1,56 @@ +package com.tungsten.fcl.util; + +import android.os.Build; + +/** + * 通过 JNI 调用 control-converter 项目编译的原生库 {@code libcc.so} + * 实现 FCL 控制布局与 ZalithLauncher2 (ZL2) 布局之间的转换。 + * + * 原生库来源:d:\project\control-converter\go (Go 实现, c-shared 编译) + * 打包位置:jniLibs/arm64-v8a/libcc.so + * 通过 JNI 调用,避免命令执行方式在 Android 上的兼容性问题。 + */ +public final class LayoutConverter { + + static { + System.loadLibrary("cc"); + } + + private LayoutConverter() { + } + + /** 当前设备是否支持运行转换(仅 arm64-v8a)。 */ + public static boolean isSupported() { + for (String abi : Build.SUPPORTED_ABIS) { + if ("arm64-v8a".equals(abi)) { + return true; + } + } + return false; + } + + /** + * JNI 原生方法:将 FCL 控制布局 JSON 转换为 ZL2 格式。 + * + * @param inputPath FCL 控制布局 JSON 文件路径 + * @param outputPath 转换后的 ZL2 JSON 输出文件路径 + * @return 转换成功返回 null;失败返回错误信息 + */ + private static native String convertFclToZl2Native(String inputPath, String outputPath); + + /** + * 将 FCL 控制布局 JSON 转换为 ZL2 格式。 + *

同步阻塞,调用方需在后台线程执行。 + * + * @param input FCL 控制布局 JSON 文件 + * @param output 转换后的 ZL2 JSON 输出文件 + * @return 转换成功返回 null;失败返回错误信息 + */ + public static String convertFclToZl2(java.io.File input, java.io.File output) { + try { + return convertFclToZl2Native(input.getAbsolutePath(), output.getAbsolutePath()); + } catch (Throwable t) { + return t.getClass().getSimpleName() + ": " + t.getMessage(); + } + } +} diff --git a/FCL/src/main/jniLibs/arm64-v8a/libcc.so b/FCL/src/main/jniLibs/arm64-v8a/libcc.so new file mode 100644 index 0000000000..17fdad2290 Binary files /dev/null and b/FCL/src/main/jniLibs/arm64-v8a/libcc.so differ diff --git a/FCL/src/main/res/values-zh-rHK/strings.xml b/FCL/src/main/res/values-zh-rHK/strings.xml index 5fb4d52f87..a21dcc8153 100644 --- a/FCL/src/main/res/values-zh-rHK/strings.xml +++ b/FCL/src/main/res/values-zh-rHK/strings.xml @@ -165,6 +165,13 @@ 佈局列表 選擇佈局 分享佈局 + 選擇分享方式 + 直接分享 + 分享為 ZL2 控制佈局 + 分享 ZL2 控制佈局 + 正在轉換… + 轉換失敗! + 當前設備不支持 ZL2 轉換(需 arm64) 上傳佈局 diff --git a/FCL/src/main/res/values-zh/strings.xml b/FCL/src/main/res/values-zh/strings.xml index d6fd7e4eef..b440292d32 100644 --- a/FCL/src/main/res/values-zh/strings.xml +++ b/FCL/src/main/res/values-zh/strings.xml @@ -170,6 +170,13 @@ 布局列表 选择布局 分享布局 + 选择分享方式 + 直接分享 + 分享为 ZL2 控制布局 + 分享 ZL2 控制布局 + 正在转换… + 转换失败! + 当前设备不支持 ZL2 转换(需 arm64) 上传布局 全部 diff --git a/FCL/src/main/res/values/strings.xml b/FCL/src/main/res/values/strings.xml index e5d48682b5..106db04a66 100644 --- a/FCL/src/main/res/values/strings.xml +++ b/FCL/src/main/res/values/strings.xml @@ -192,6 +192,13 @@ Controller List Select Controller Share Controller + Choose share method + Direct share + Share as ZL2 layout + Share ZL2 layout + Converting… + Conversion failed! + ZL2 conversion is not supported on this device (arm64 required) Upload Controller All diff --git a/FCLauncher/src/main/java/com/tungsten/fclauncher/utils/FCLPath.java b/FCLauncher/src/main/java/com/tungsten/fclauncher/utils/FCLPath.java index cd3e76b3f7..9413ceaaad 100644 --- a/FCLauncher/src/main/java/com/tungsten/fclauncher/utils/FCLPath.java +++ b/FCLauncher/src/main/java/com/tungsten/fclauncher/utils/FCLPath.java @@ -30,6 +30,7 @@ public class FCLPath { public static String PLUGIN_DIR; public static String BACKGROUND_DIR; public static String CONTROLLER_DIR; + public static String SHARE_DIR; public static String PRIVATE_COMMON_DIR; public static String SHARED_COMMON_DIR = Environment.getExternalStorageDirectory().getAbsolutePath() + "/FCL/.minecraft"; @@ -66,6 +67,7 @@ public static void loadPaths(Context context) { PLUGIN_DIR = FILES_DIR + "/plugins"; BACKGROUND_DIR = FILES_DIR + "/background"; CONTROLLER_DIR = Environment.getExternalStorageDirectory().getAbsolutePath() + "/FCL/control"; + SHARE_DIR = Environment.getExternalStorageDirectory().getAbsolutePath() + "/FCL/share"; File externalFilesDir = context.getExternalFilesDir(null); if (externalFilesDir == null) { externalFilesDir = new File(Environment.getExternalStorageDirectory(), "Android/data/" + context.getPackageName() + "/files"); @@ -94,6 +96,7 @@ public static void loadPaths(Context context) { init(PLUGIN_DIR); init(BACKGROUND_DIR); init(CONTROLLER_DIR); + init(SHARE_DIR); init(PRIVATE_COMMON_DIR); init(SHARED_COMMON_DIR); } diff --git a/README.md b/README.md index e8e2cb16eb..d920678e4e 100644 --- a/README.md +++ b/README.md @@ -89,4 +89,5 @@ - [virglrenderer](https://github.com/AngelAuraMC/virglrenderer): [MIT License](https://gitlab.freedesktop.org/virgl/virglrenderer/-/blob/master/COPYING). - [OpenAL-Soft](https://github.com/kcat/openal-soft): [GNU GPLv2](app_pojavlauncher/src/main/assets/licenses/OPENAL-SOFT_GPL2) - [oboe](https://github.com/google/oboe): [Apache License 2.0](app_pojavlauncher/src/main/assets/licenses/OBOE_APACHE2). - - [pfffft](https://bitbucket.org/jpommier/pffft/src/master/): [ARR](app_pojavlauncher/src/main/assets/licenses/PFFFT_LICENSE) \ No newline at end of file + - [pfffft](https://bitbucket.org/jpommier/pffft/src/master/): [ARR](app_pojavlauncher/src/main/assets/licenses/PFFFT_LICENSE) +- [control-converter](https://github.com/NingZeStudio/control-converter) (FCL→ZL2 控制布局转换,Go 实现,打包为 `libcc.so`): [MIT License](https://opensource.org/licenses/MIT) \ No newline at end of file diff --git a/README_EN.md b/README_EN.md index dfeb52027d..493dde13e3 100644 --- a/README_EN.md +++ b/README_EN.md @@ -88,3 +88,4 @@ This project is licensed under **[GPL-3.0 License](https://www.gnu.org/licenses/ - [OpenAL-Soft](https://github.com/kcat/openal-soft): [GNU GPLv2](app_pojavlauncher/src/main/assets/licenses/OPENAL-SOFT_GPL2) - [oboe](https://github.com/google/oboe): [Apache License 2.0](app_pojavlauncher/src/main/assets/licenses/OBOE_APACHE2). - [pfffft](https://bitbucket.org/jpommier/pffft/src/master/): [ARR](app_pojavlauncher/src/main/assets/licenses/PFFFT_LICENSE) +- [control-converter](https://github.com/NingZeStudio/control-converter) (FCL→ZL2 control layout converter, Go implementation, packaged as `libcc.so`): [MIT License](https://opensource.org/licenses/MIT) diff --git a/README_RU.md b/README_RU.md index 3cb4c8ae44..58a7ba7c81 100644 --- a/README_RU.md +++ b/README_RU.md @@ -86,4 +86,5 @@ - [virglrenderer](https://github.com/AngelAuraMC/virglrenderer): [MIT License](https://gitlab.freedesktop.org/virgl/virglrenderer/-/blob/master/COPYING). - [OpenAL-Soft](https://github.com/kcat/openal-soft): [GNU GPLv2](app_pojavlauncher/src/main/assets/licenses/OPENAL-SOFT_GPL2) - [oboe](https://github.com/google/oboe): [Apache License 2.0](app_pojavlauncher/src/main/assets/licenses/OBOE_APACHE2). - - [pfffft](https://bitbucket.org/jpommier/pffft/src/master/): [ARR](app_pojavlauncher/src/main/assets/licenses/PFFFT_LICENSE) \ No newline at end of file + - [pfffft](https://bitbucket.org/jpommier/pffft/src/master/): [ARR](app_pojavlauncher/src/main/assets/licenses/PFFFT_LICENSE) +- [control-converter](https://github.com/NingZeStudio/control-converter) (конвертер раскладок управления FCL→ZL2, реализация на Go, упакован как `libcc.so`): [MIT License](https://opensource.org/licenses/MIT) \ No newline at end of file