Select mod with external APP

This commit is contained in:
ShirosakiMio 2024-08-12 12:00:01 +08:00
parent 2777f7999a
commit c45f144b10
7 changed files with 159 additions and 14 deletions

View File

@ -2,9 +2,12 @@ package com.tungsten.fcl.ui.manage;
import static com.tungsten.fclcore.util.Logging.LOG;
import static com.tungsten.fclcore.util.StringUtils.isNotBlank;
import static com.tungsten.fcllibrary.browser.FileBrowser.SELECTED_FILES;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
import android.view.View;
import android.widget.ListView;
import android.widget.RelativeLayout;
@ -277,22 +280,42 @@ public class ModListPage extends FCLCommonPage implements ManageUI.VersionLoadab
builder.setSelectionMode(SelectionMode.MULTIPLE_SELECTION);
builder.create().browse(getActivity(), RequestCodes.SELECT_MODS_CODE, (requestCode, resultCode, data) -> {
if (requestCode == RequestCodes.SELECT_MODS_CODE && resultCode == Activity.RESULT_OK && data != null) {
List<File> res = FileBrowser.getSelectedFiles(data).stream().filter(Objects::nonNull).map(File::new).collect(Collectors.toList());
ArrayList<Uri> selectedFiles = data.getParcelableArrayListExtra(SELECTED_FILES);
List<Object> res = selectedFiles.stream().filter(Objects::nonNull).map(uri -> {
if (Objects.equals(uri.getScheme(), ContentResolver.SCHEME_CONTENT) || Objects.equals(uri.getScheme(), ContentResolver.SCHEME_FILE)) {
return uri;
} else {
return new File(uri.toString());
}
}).collect(Collectors.toList());
// It's guaranteed that succeeded and failed are thread safe here.
List<String> succeeded = new ArrayList<>(res.size());
List<String> failed = new ArrayList<>();
Task.runAsync(() -> {
for (File file : res) {
try {
modManager.addMod(file.toPath());
succeeded.add(file.getName());
} catch (Exception e) {
LOG.log(Level.WARNING, "Unable to add mod " + file, e);
failed.add(file.getName());
for (Object obj : res) {
if (obj instanceof File) {
File file = (File) obj;
try {
modManager.addMod(file.toPath());
succeeded.add(file.getName());
} catch (Exception e) {
LOG.log(Level.WARNING, "Unable to add mod " + file, e);
failed.add(file.getName());
// Actually addMod will not throw exceptions because FileChooser has already filtered files.
// Actually addMod will not throw exceptions because FileChooser has already filtered files.
}
} else {
try {
Uri uri = (Uri) obj;
modManager.addMod(getActivity(), uri);
succeeded.add(new File(uri.getPath()).getName());
} catch (Exception e) {
LOG.log(Level.WARNING, "Unable to add mod " + obj.toString(), e);
failed.add(obj.toString());
// Actually addMod will not throw exceptions because FileChooser has already filtered files.
}
}
}
}).withRunAsync(Schedulers.androidUIThread(), () -> {

View File

@ -17,6 +17,9 @@
*/
package com.tungsten.fclcore.mod;
import android.app.Activity;
import android.net.Uri;
import com.google.gson.JsonParseException;
import com.tungsten.fclcore.game.GameRepository;
import com.tungsten.fclcore.mod.modinfo.FabricModMetadata;
@ -31,9 +34,24 @@ import com.tungsten.fclcore.util.io.CompressingUtils;
import com.tungsten.fclcore.util.io.FileUtils;
import com.tungsten.fclcore.util.versioning.VersionNumber;
import org.apache.commons.io.IOUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.*;
import java.util.*;
import java.io.InputStream;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import java.util.TreeSet;
public final class ModManager {
@FunctionalInterface
@ -167,6 +185,28 @@ public final class ModManager {
addModInfo(newFile);
}
public void addMod(Activity activity, Uri uri) throws IOException {
if (!isFileNameMod(uri))
throw new IllegalArgumentException("File " + uri.toString() + " is not a valid mod file.");
if (!loaded)
refreshMods();
Path modsDirectory = getModsDirectory();
Files.createDirectories(modsDirectory);
String name = new File(uri.getPath()).getName();
Path newFile = modsDirectory.resolve(name);
InputStream inputStream = activity.getContentResolver().openInputStream(uri);
if(inputStream == null) {
throw new IOException("Failed to open content stream");
}
try (FileOutputStream outputStream = new FileOutputStream(newFile.toFile())){
IOUtils.copy(inputStream,outputStream);
}
inputStream.close();
addModInfo(newFile);
}
public void removeMods(LocalModFile... localModFiles) throws IOException {
for (LocalModFile localModFile : localModFiles) {
Files.deleteIfExists(localModFile.getFile());
@ -279,6 +319,10 @@ public final class ModManager {
return name.endsWith(".zip") || name.endsWith(".jar") || name.endsWith(".litemod");
}
public static boolean isFileNameMod(Uri uri) {
return isFileNameMod(new File(uri.toString()).toPath());
}
public static boolean isFileMod(Path modFile) {
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(modFile)) {
if (Files.exists(fs.getPath("mcmod.info")) || Files.exists(fs.getPath("META-INF/mods.toml"))) {

View File

@ -1,6 +1,8 @@
package com.tungsten.fcllibrary.browser;
import android.app.Activity;
import android.content.ClipData;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
@ -10,6 +12,9 @@ import android.view.View;
import android.widget.ListView;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContract;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.FileProvider;
@ -43,11 +48,51 @@ public class FileBrowserActivity extends FCLActivity implements View.OnClickList
private FCLButton sharedDir;
private FCLButton privateDir;
private FCLButton openExternal;
private FCLButton selectExternal;
private FCLButton confirm;
private Path currentPath;
private ArrayList<String> selectedFiles;
private ArrayList<Uri> extSelected;
private final ActivityResultLauncher<Object> launcher = registerForActivityResult(new ActivityResultContract<Object, Uri>() {
@NonNull
@Override
public Intent createIntent(@NonNull Context context, Object o) {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, fileBrowser.getSelectionMode() == SelectionMode.MULTIPLE_SELECTION);
return intent;
}
@Override
public Uri parseResult(int resultCode, @Nullable Intent data) {
if (data == null || resultCode != Activity.RESULT_OK) {
return null;
}
ClipData clipData = data.getClipData();
if (clipData != null && clipData.getItemCount() > 0) {
for (int i = 0; i < clipData.getItemCount(); i++) {
Uri uri = clipData.getItemAt(i).getUri();
if (uri != null) {
extSelected.add(uri);
}
}
} else {
extSelected.add(data.getData());
}
return null;
}
}, result -> {
if (!extSelected.isEmpty()) {
Intent intent = new Intent();
intent.putParcelableArrayListExtra(FileBrowser.SELECTED_FILES, extSelected);
FileBrowserActivity.this.setResult(Activity.RESULT_OK, intent);
FileBrowserActivity.this.finish();
}
});
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
@ -73,16 +118,23 @@ public class FileBrowserActivity extends FCLActivity implements View.OnClickList
sharedDir = findViewById(R.id.shared_dir);
privateDir = findViewById(R.id.private_dir);
openExternal = findViewById(R.id.open_external);
selectExternal = findViewById(R.id.select_external);
confirm = findViewById(R.id.confirm);
sharedDir.setOnClickListener(this);
privateDir.setOnClickListener(this);
openExternal.setOnClickListener(this);
selectExternal.setOnClickListener(this);
confirm.setOnClickListener(this);
selectedFiles = new ArrayList<>();
extSelected = new ArrayList<>();
currentText = findViewById(R.id.current_folder);
listView = findViewById(R.id.list);
refreshList(currentPath != null ? currentPath : new File(fileBrowser.getInitDir()).toPath());
if (fileBrowser.getLibMode() != LibMode.FILE_CHOOSER) {
selectExternal.setVisibility(View.GONE);
}
}
private String getMode() {
@ -177,7 +229,10 @@ public class FileBrowserActivity extends FCLActivity implements View.OnClickList
intent.setDataAndType(uri, "*/*");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
startActivity(Intent.createChooser(intent,getString(R.string.file_browser_open_external)));
startActivity(Intent.createChooser(intent, getString(R.string.file_browser_open_external)));
}
if (view == selectExternal) {
launcher.launch(null);
}
if (view == confirm) {
if (selectedFiles.size() == 0 && fileBrowser.getLibMode() != LibMode.FILE_BROWSER) {
@ -190,4 +245,10 @@ public class FileBrowserActivity extends FCLActivity implements View.OnClickList
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
callback = false;
super.onActivityResult(requestCode, resultCode, data);
}
}

View File

@ -20,6 +20,7 @@ import com.tungsten.fcllibrary.component.theme.ThemeEngine;
import com.tungsten.fcllibrary.util.LocaleUtils;
public class FCLActivity extends AppCompatActivity {
public boolean callback = true;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
@ -64,6 +65,8 @@ public class FCLActivity extends AppCompatActivity {
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
ResultListener.onActivityResult(requestCode, resultCode, data);
if (callback) {
ResultListener.onActivityResult(requestCode, resultCode, data);
}
}
}

View File

@ -77,6 +77,18 @@
app:layout_constraintEnd_toEndOf="parent"
android:text="@string/file_browser_open_external"/>
<com.tungsten.fcllibrary.component.view.FCLButton
android:id="@+id/select_external"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
app:layout_constraintTop_toBottomOf="@+id/open_external"
app:layout_constraintStart_toEndOf="@+id/split"
app:layout_constraintEnd_toEndOf="parent"
android:text="@string/file_browser_select_external"/>
<com.tungsten.fcllibrary.component.view.FCLTextView
android:id="@+id/mode_hint"
android:layout_width="wrap_content"

View File

@ -34,4 +34,5 @@
<string name="dialog_info">提示</string>
<string name="dialog_positive">确认</string>
<string name="dialog_negative">取消</string>
<string name="file_browser_select_external">使用外部应用选择</string>
</resources>

View File

@ -8,6 +8,7 @@
<string name="file_browser_shared">Shared directory</string>
<string name="file_browser_private">Private directory</string>
<string name="file_browser_open_external">Open in external APP(Like MT)</string>
<string name="file_browser_select_external">Select with external APP</string>
<string name="file_browser_private_alert">Cannot access to private directory, please check the permission.</string>
<string name="file_browser_mode">Mode:</string>
<string name="file_browser_mode_browse">Browse File</string>