diff --git a/FCL/src/main/java/com/tungsten/fcl/game/FCLGameRepository.java b/FCL/src/main/java/com/tungsten/fcl/game/FCLGameRepository.java index b6651bc0..8f843cab 100644 --- a/FCL/src/main/java/com/tungsten/fcl/game/FCLGameRepository.java +++ b/FCL/src/main/java/com/tungsten/fcl/game/FCLGameRepository.java @@ -258,8 +258,6 @@ public class FCLGameRepository extends DefaultGameRepository { if (id == null || !isLoaded()) return FCLPath.CONTEXT.getDrawable(R.drawable.img_grass); - VersionSetting vs = getLocalVersionSettingOrCreate(id); - Version version = getVersion(id).resolve(this); File iconFile = getVersionIconFile(id); if (iconFile.exists()) diff --git a/FCL/src/main/java/com/tungsten/fcl/setting/Profile.java b/FCL/src/main/java/com/tungsten/fcl/setting/Profile.java index 74b04c11..156784e6 100644 --- a/FCL/src/main/java/com/tungsten/fcl/setting/Profile.java +++ b/FCL/src/main/java/com/tungsten/fcl/setting/Profile.java @@ -7,6 +7,7 @@ import com.google.gson.annotations.JsonAdapter; import com.tungsten.fcl.game.FCLCacheRepository; import com.tungsten.fcl.game.FCLGameRepository; import com.tungsten.fcl.util.WeakListenerHolder; +import com.tungsten.fclauncher.FCLPath; import com.tungsten.fclcore.download.DefaultDependencyManager; import com.tungsten.fclcore.download.DownloadProvider; import com.tungsten.fclcore.event.EventBus; @@ -86,7 +87,7 @@ public final class Profile implements Observable { } public Profile(String name) { - this(name, new File(".minecraft")); + this(name, new File(FCLPath.SHARED_COMMON_DIR)); } public Profile(String name, File initialGameDir) { diff --git a/FCL/src/main/java/com/tungsten/fcl/ui/version/AddProfileDialog.java b/FCL/src/main/java/com/tungsten/fcl/ui/version/AddProfileDialog.java new file mode 100644 index 00000000..e678c88a --- /dev/null +++ b/FCL/src/main/java/com/tungsten/fcl/ui/version/AddProfileDialog.java @@ -0,0 +1,75 @@ +package com.tungsten.fcl.ui.version; + +import android.app.Activity; +import android.content.Context; +import android.view.View; +import android.widget.Toast; + +import androidx.annotation.NonNull; + +import com.tungsten.fcl.R; +import com.tungsten.fcl.setting.Profile; +import com.tungsten.fcl.setting.Profiles; +import com.tungsten.fcl.ui.UIManager; +import com.tungsten.fcl.util.RequestCodes; +import com.tungsten.fclcore.util.StringUtils; +import com.tungsten.fcllibrary.browser.FileBrowser; +import com.tungsten.fcllibrary.browser.options.LibMode; +import com.tungsten.fcllibrary.component.dialog.FCLDialog; +import com.tungsten.fcllibrary.component.view.FCLButton; +import com.tungsten.fcllibrary.component.view.FCLEditText; +import com.tungsten.fcllibrary.component.view.FCLImageButton; +import com.tungsten.fcllibrary.component.view.FCLTextView; + +import java.io.File; +import java.util.ArrayList; + +public class AddProfileDialog extends FCLDialog implements View.OnClickListener { + + private FCLEditText editText; + private FCLTextView pathText; + private FCLImageButton editPath; + private FCLButton positive; + private FCLButton negative; + + public AddProfileDialog(@NonNull Context context) { + super(context); + setContentView(R.layout.dialog_add_profile); + setCancelable(false); + editText = findViewById(R.id.name); + pathText = findViewById(R.id.path); + editPath = findViewById(R.id.edit); + positive = findViewById(R.id.positive); + negative = findViewById(R.id.negative); + editPath.setOnClickListener(this); + positive.setOnClickListener(this); + negative.setOnClickListener(this); + } + + @Override + public void onClick(View view) { + if (view == editPath) { + FileBrowser.Builder builder = new FileBrowser.Builder(getContext()); + builder.setLibMode(LibMode.FOLDER_CHOOSER); + builder.setTitle(getContext().getString(R.string.profile_select)); + builder.create().browse(UIManager.getInstance().getVersionUI().getActivity(), RequestCodes.SELECT_PROFILE_CODE, (requestCode, resultCode, data) -> { + if (requestCode == RequestCodes.SELECT_PROFILE_CODE && resultCode == Activity.RESULT_OK && data != null) { + ArrayList strings = FileBrowser.getSelectedFiles(data); + pathText.setText(strings.get(0)); + } + }); + } + if (view == positive) { + if (StringUtils.isBlank(editText.getText().toString()) || StringUtils.isBlank(pathText.getText().toString())) { + Toast.makeText(getContext(), getContext().getString(R.string.profile_add_alert), Toast.LENGTH_SHORT).show(); + } else { + Profiles.getProfiles().add(new Profile(editText.getText().toString(), new File(pathText.getText().toString()))); + UIManager.getInstance().getVersionUI().refreshProfile(); + dismiss(); + } + } + if (view == negative) { + dismiss(); + } + } +} diff --git a/FCL/src/main/java/com/tungsten/fcl/ui/version/ProfileListAdapter.java b/FCL/src/main/java/com/tungsten/fcl/ui/version/ProfileListAdapter.java new file mode 100644 index 00000000..84dbeeb2 --- /dev/null +++ b/FCL/src/main/java/com/tungsten/fcl/ui/version/ProfileListAdapter.java @@ -0,0 +1,75 @@ +package com.tungsten.fcl.ui.version; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.tungsten.fcl.R; +import com.tungsten.fcl.setting.Profile; +import com.tungsten.fcl.setting.Profiles; +import com.tungsten.fcl.ui.UIManager; +import com.tungsten.fclcore.fakefx.collections.ObservableList; +import com.tungsten.fcllibrary.component.FCLAdapter; +import com.tungsten.fcllibrary.component.view.FCLImageButton; +import com.tungsten.fcllibrary.component.view.FCLRadioButton; +import com.tungsten.fcllibrary.component.view.FCLTextView; + +public class ProfileListAdapter extends FCLAdapter { + + private ObservableList list; + + public ProfileListAdapter(Context context, ObservableList list) { + super(context); + this.list = list; + } + + static class ViewHolder { + FCLRadioButton radioButton; + FCLTextView name; + FCLTextView path; + FCLImageButton delete; + } + + @Override + public int getCount() { + return list.size(); + } + + @Override + public Object getItem(int i) { + return list.get(i); + } + + @Override + public View getView(int i, View view, ViewGroup viewGroup) { + final ViewHolder viewHolder; + if (view == null) { + viewHolder = new ViewHolder(); + view = LayoutInflater.from(getContext()).inflate(R.layout.item_profile, null); + viewHolder.radioButton = view.findViewById(R.id.radio); + viewHolder.name = view.findViewById(R.id.name); + viewHolder.path = view.findViewById(R.id.path); + viewHolder.delete = view.findViewById(R.id.delete); + view.setTag(viewHolder); + } + else { + viewHolder = (ViewHolder) view.getTag(); + } + Profile profile = list.get(i); + viewHolder.radioButton.setChecked(profile == Profiles.getSelectedProfile()); + viewHolder.name.setText(profile.getName()); + viewHolder.path.setText(profile.getGameDir().getAbsolutePath()); + viewHolder.radioButton.setOnClickListener(view1 -> { + Profiles.setSelectedProfile(profile); + notifyDataSetChanged(); + UIManager.getInstance().getVersionUI().refresh(); + }); + viewHolder.delete.setOnClickListener(view1 -> { + Profiles.getProfiles().remove(profile); + UIManager.getInstance().getVersionUI().refresh(); + UIManager.getInstance().getVersionUI().refreshProfile(); + }); + return view; + } +} diff --git a/FCL/src/main/java/com/tungsten/fcl/ui/version/RenameVersionDialog.java b/FCL/src/main/java/com/tungsten/fcl/ui/version/RenameVersionDialog.java new file mode 100644 index 00000000..780a9fa6 --- /dev/null +++ b/FCL/src/main/java/com/tungsten/fcl/ui/version/RenameVersionDialog.java @@ -0,0 +1,63 @@ +package com.tungsten.fcl.ui.version; + +import android.content.Context; +import android.view.View; +import android.widget.Toast; + +import androidx.annotation.NonNull; + +import com.tungsten.fcl.R; +import com.tungsten.fclcore.util.FutureCallback; +import com.tungsten.fcllibrary.component.dialog.FCLDialog; +import com.tungsten.fcllibrary.component.view.FCLButton; +import com.tungsten.fcllibrary.component.view.FCLEditText; + +import java.util.concurrent.CompletableFuture; + +public class RenameVersionDialog extends FCLDialog implements View.OnClickListener { + + private final FutureCallback callback; + private final CompletableFuture future = new CompletableFuture<>(); + + private FCLEditText editText; + private FCLButton positive; + private FCLButton negative; + + public RenameVersionDialog(@NonNull Context context, String oldName, FutureCallback callback) { + super(context); + setContentView(R.layout.dialog_rename_version); + setCancelable(false); + this.callback = callback; + editText = findViewById(R.id.new_name); + positive = findViewById(R.id.positive); + negative = findViewById(R.id.negative); + positive.setOnClickListener(this); + negative.setOnClickListener(this); + editText.setText(oldName); + } + + @Override + public void onClick(View view) { + if (view == positive) { + positive.setEnabled(false); + negative.setEnabled(false); + callback.call(editText.getText().toString(), () -> { + positive.setEnabled(true); + negative.setEnabled(true); + future.complete(editText.getText().toString()); + dismiss(); + }, msg -> { + positive.setEnabled(true); + negative.setEnabled(true); + Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT).show(); + }); + } + if (view == negative) { + dismiss(); + } + } + + public CompletableFuture getFuture() { + return future; + } +} diff --git a/FCL/src/main/java/com/tungsten/fcl/ui/version/VersionListAdapter.java b/FCL/src/main/java/com/tungsten/fcl/ui/version/VersionListAdapter.java new file mode 100644 index 00000000..7e7b2896 --- /dev/null +++ b/FCL/src/main/java/com/tungsten/fcl/ui/version/VersionListAdapter.java @@ -0,0 +1,94 @@ +package com.tungsten.fcl.ui.version; + +import static com.tungsten.fclcore.util.Lang.threadPool; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.appcompat.widget.AppCompatImageView; + +import com.tungsten.fcl.R; +import com.tungsten.fcl.ui.UIManager; +import com.tungsten.fcllibrary.component.FCLAdapter; +import com.tungsten.fcllibrary.component.view.FCLImageButton; +import com.tungsten.fcllibrary.component.view.FCLRadioButton; +import com.tungsten.fcllibrary.component.view.FCLTextView; + +import java.util.ArrayList; + +public class VersionListAdapter extends FCLAdapter { + + private final ArrayList list; + + public VersionListAdapter(Context context, ArrayList list) { + super(context); + this.list = list; + } + + static class ViewHolder { + FCLRadioButton radioButton; + AppCompatImageView icon; + FCLTextView title; + FCLTextView subtitle; + FCLImageButton rename; + FCLImageButton copy; + FCLImageButton browse; + FCLImageButton delete; + } + + @Override + public int getCount() { + return list.size(); + } + + @Override + public Object getItem(int i) { + return list.get(i); + } + + @Override + public View getView(int i, View view, ViewGroup viewGroup) { + final ViewHolder viewHolder; + if (view == null) { + viewHolder = new ViewHolder(); + view = LayoutInflater.from(getContext()).inflate(R.layout.item_version, null); + viewHolder.radioButton = view.findViewById(R.id.radio); + viewHolder.icon = view.findViewById(R.id.icon); + viewHolder.title = view.findViewById(R.id.title); + viewHolder.subtitle = view.findViewById(R.id.subtitle); + viewHolder.rename = view.findViewById(R.id.rename); + viewHolder.copy = view.findViewById(R.id.copy); + viewHolder.browse = view.findViewById(R.id.browse); + viewHolder.delete = view.findViewById(R.id.delete); + view.setTag(viewHolder); + } + else { + viewHolder = (ViewHolder) view.getTag(); + } + VersionListItem versionListItem = list.get(i); + versionListItem.checkSelection(); + viewHolder.radioButton.setChecked(versionListItem.selectedProperty().get()); + viewHolder.icon.setBackground(versionListItem.getDrawable()); + viewHolder.title.setText(versionListItem.getVersion()); + viewHolder.subtitle.setText(versionListItem.getLibraries()); + viewHolder.radioButton.setOnClickListener(view1 -> { + versionListItem.getProfile().setSelectedVersion(versionListItem.getVersion()); + notifyDataSetChanged(); + }); + viewHolder.rename.setOnClickListener(view1 -> { + Versions.renameVersion(getContext(), versionListItem.getProfile(), versionListItem.getVersion()); + }); + viewHolder.copy.setOnClickListener(view1 -> { + + }); + viewHolder.browse.setOnClickListener(view1 -> { + Versions.openFolder(UIManager.getInstance().getVersionUI().getActivity(), versionListItem.getProfile(), versionListItem.getVersion()); + }); + viewHolder.delete.setOnClickListener(view1 -> { + Versions.deleteVersion(getContext(), versionListItem.getProfile(), versionListItem.getVersion()); + }); + return view; + } +} diff --git a/FCL/src/main/java/com/tungsten/fcl/ui/version/VersionListItem.java b/FCL/src/main/java/com/tungsten/fcl/ui/version/VersionListItem.java new file mode 100644 index 00000000..bb00fc3c --- /dev/null +++ b/FCL/src/main/java/com/tungsten/fcl/ui/version/VersionListItem.java @@ -0,0 +1,56 @@ +package com.tungsten.fcl.ui.version; + +import android.graphics.drawable.Drawable; + +import com.tungsten.fcl.setting.Profile; +import com.tungsten.fclcore.fakefx.beans.property.BooleanProperty; +import com.tungsten.fclcore.fakefx.beans.property.SimpleBooleanProperty; + +public class VersionListItem { + + private final Profile profile; + private final String version; + private final boolean isModpack; + private final BooleanProperty selected = new SimpleBooleanProperty(); + private final String libraries; + private final Drawable drawable; + + public VersionListItem(Profile profile, String id, String libraries, Drawable drawable) { + this.profile = profile; + this.version = id; + this.libraries = libraries; + this.drawable = drawable; + this.isModpack = profile.getRepository().isModpack(id); + + selected.set(id.equals(profile.getSelectedVersion())); + } + + public Profile getProfile() { + return profile; + } + + public String getVersion() { + return version; + } + + public String getLibraries() { + return libraries; + } + + public Drawable getDrawable() { + return drawable; + } + + public BooleanProperty selectedProperty() { + return selected; + } + + public void checkSelection() { + selected.set(version.equals(profile.getSelectedVersion())); + } + + public boolean canUpdate() { + return isModpack; + } + +} diff --git a/FCL/src/main/java/com/tungsten/fcl/ui/version/VersionUI.java b/FCL/src/main/java/com/tungsten/fcl/ui/version/VersionUI.java index bcd191d0..78003c64 100644 --- a/FCL/src/main/java/com/tungsten/fcl/ui/version/VersionUI.java +++ b/FCL/src/main/java/com/tungsten/fcl/ui/version/VersionUI.java @@ -1,17 +1,97 @@ package com.tungsten.fcl.ui.version; -import android.content.Context; +import static com.tungsten.fclcore.download.LibraryAnalyzer.LibraryType.MINECRAFT; +import android.content.Context; +import android.view.View; +import android.widget.ListView; + +import com.tungsten.fcl.R; +import com.tungsten.fcl.setting.Profiles; +import com.tungsten.fclcore.download.LibraryAnalyzer; +import com.tungsten.fclcore.task.Schedulers; import com.tungsten.fcllibrary.component.ui.FCLCommonUI; +import com.tungsten.fcllibrary.component.view.FCLButton; +import com.tungsten.fcllibrary.component.view.FCLProgressBar; import com.tungsten.fcllibrary.component.view.FCLUILayout; -public class VersionUI extends FCLCommonUI { +import java.util.ArrayList; +import java.util.stream.Collectors; + +public class VersionUI extends FCLCommonUI implements View.OnClickListener { + + private FCLButton refresh; + private FCLButton newProfile; + private FCLProgressBar progressBar; + private ListView profileListView; + private ListView versionListView; + public VersionUI(Context context, FCLUILayout parent, int id) { super(context, parent, id); } @Override - public void refresh() { + public void onCreate() { + super.onCreate(); + refresh = findViewById(R.id.refresh); + newProfile = findViewById(R.id.new_profile); + progressBar = findViewById(R.id.progress); + profileListView = findViewById(R.id.profile_list); + versionListView = findViewById(R.id.version_list); + refresh.setOnClickListener(this); + newProfile.setOnClickListener(this); + + refresh(); + } + + @Override + public void refresh() { + refresh.setEnabled(false); + versionListView.setVisibility(View.GONE); + progressBar.setVisibility(View.VISIBLE); + refreshProfile(); + Profiles.getSelectedProfile().getRepository().refreshVersionsAsync().whenComplete(Schedulers.androidUIThread(), exception -> { + ArrayList children = (ArrayList) Profiles.getSelectedProfile().getRepository().getDisplayVersions() + .map(version -> { + String game = Profiles.getSelectedProfile().getRepository().getGameVersion(version.getId()).orElse(getContext().getString(R.string.message_unknown)); + StringBuilder libraries = new StringBuilder(game); + LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(Profiles.getSelectedProfile().getRepository().getResolvedPreservingPatchesVersion(version.getId())); + for (LibraryAnalyzer.LibraryMark mark : analyzer) { + String libraryId = mark.getLibraryId(); + String libraryVersion = mark.getLibraryVersion(); + if (libraryId.equals(MINECRAFT.getPatchId())) continue; + int resId = getContext().getResources().getIdentifier("install_installer_" + libraryId.replace("-", "_"), "string", getContext().getPackageName()); + if (resId != 0 && getContext().getString(resId) != null) { + libraries.append(", ").append(getContext().getString(resId)); + if (libraryVersion != null) + libraries.append(": ").append(libraryVersion.replaceAll("(?i)" + libraryId, "")); + } + } + return new VersionListItem(Profiles.getSelectedProfile(), version.getId(), libraries.toString(), Profiles.getSelectedProfile().getRepository().getVersionIconImage(version.getId())); + }) + .collect(Collectors.toList()); + VersionListAdapter adapter = new VersionListAdapter(getContext(), children); + versionListView.setAdapter(adapter); + refresh.setEnabled(true); + versionListView.setVisibility(View.VISIBLE); + progressBar.setVisibility(View.GONE); + }).start(); + } + + public void refreshProfile() { + ProfileListAdapter adapter = new ProfileListAdapter(getContext(), Profiles.getProfiles()); + profileListView.setAdapter(adapter); + } + + @Override + public void onClick(View view) { + if (view == refresh) { + refresh(); + } + if (view == newProfile) { + AddProfileDialog dialog = new AddProfileDialog(getContext()); + dialog.show(); + } } } diff --git a/FCL/src/main/java/com/tungsten/fcl/ui/version/Versions.java b/FCL/src/main/java/com/tungsten/fcl/ui/version/Versions.java new file mode 100644 index 00000000..3dc8b481 --- /dev/null +++ b/FCL/src/main/java/com/tungsten/fcl/ui/version/Versions.java @@ -0,0 +1,124 @@ +package com.tungsten.fcl.ui.version; + +import android.app.Activity; +import android.content.Context; + +import com.tungsten.fcl.R; +import com.tungsten.fcl.setting.Profile; +import com.tungsten.fcl.ui.UIManager; +import com.tungsten.fcl.util.RequestCodes; +import com.tungsten.fclcore.download.game.GameAssetDownloadTask; +import com.tungsten.fclcore.game.GameDirectoryType; +import com.tungsten.fclcore.task.Schedulers; +import com.tungsten.fclcore.task.Task; +import com.tungsten.fclcore.task.TaskExecutor; +import com.tungsten.fclcore.util.Logging; +import com.tungsten.fclcore.util.StringUtils; +import com.tungsten.fclcore.util.platform.OperatingSystem; +import com.tungsten.fcllibrary.browser.FileBrowser; +import com.tungsten.fcllibrary.browser.options.LibMode; +import com.tungsten.fcllibrary.component.dialog.FCLAlertDialog; + +import java.io.IOException; +import java.util.concurrent.CompletableFuture; +import java.util.logging.Level; + +public class Versions { + + public static void deleteVersion(Context context, Profile profile, String version) { + boolean isIndependent = profile.getVersionSetting(version).getGameDirType() == GameDirectoryType.VERSION_FOLDER; + String message = isIndependent ? String.format(context.getString(R.string.version_manage_remove_confirm_independent), version) : String.format(context.getString(R.string.version_manage_remove_confirm), version); + + FCLAlertDialog.Builder builder = new FCLAlertDialog.Builder(context); + builder.setAlertLevel(FCLAlertDialog.AlertLevel.ALERT); + builder.setMessage(message); + builder.setPositiveButton(() -> { + profile.getRepository().removeVersionFromDisk(version); + UIManager.getInstance().getVersionUI().refresh(); + }); + builder.setNegativeButton(null); + builder.create().show(); + } + + public static CompletableFuture renameVersion(Context context, Profile profile, String version) { + RenameVersionDialog dialog = new RenameVersionDialog(context, version, (newName, resolve, reject) -> { + if (!OperatingSystem.isNameValid(newName)) { + reject.accept(context.getString(R.string.install_new_game_malformed)); + return; + } + if (profile.getRepository().renameVersion(version, newName)) { + resolve.run(); + profile.getRepository().refreshVersionsAsync() + .thenRunAsync(Schedulers.androidUIThread(), () -> { + if (profile.getRepository().hasVersion(newName)) { + profile.setSelectedVersion(newName); + } + UIManager.getInstance().getVersionUI().refresh(); + }).start(); + } else { + reject.accept(context.getString(R.string.version_manage_rename_fail)); + } + }); + dialog.show(); + return dialog.getFuture(); + } + + /* + public static void exportVersion(Profile profile, String version) { + Controllers.getDecorator().startWizard(new ExportWizardProvider(profile, version), i18n("modpack.wizard")); + } + + */ + + public static void openFolder(Activity context, Profile profile, String version) { + FileBrowser.Builder builder = new FileBrowser.Builder(context); + builder.setLibMode(LibMode.FILE_BROWSER); + builder.setInitDir(profile.getRepository().getRunDirectory(version).getAbsolutePath()); + builder.create().browse(context, RequestCodes.BROWSE_VERSION_DIR_CODE, null); + } + + /* + public static void duplicateVersion(Profile profile, String version) { + Controllers.prompt( + new PromptDialogPane.Builder(i18n("version.manage.duplicate.prompt"), (res, resolve, reject) -> { + String newVersionName = ((PromptDialogPane.Builder.StringQuestion) res.get(1)).getValue(); + boolean copySaves = ((PromptDialogPane.Builder.BooleanQuestion) res.get(2)).getValue(); + Task.runAsync(() -> profile.getRepository().duplicateVersion(version, newVersionName, copySaves)) + .thenComposeAsync(profile.getRepository().refreshVersionsAsync()) + .whenComplete(Schedulers.androidUIThread(), (result, exception) -> { + if (exception == null) { + resolve.run(); + } else { + reject.accept(StringUtils.getStackTrace(exception)); + profile.getRepository().removeVersionFromDisk(newVersionName); + } + }).start(); + }) + .addQuestion(new PromptDialogPane.Builder.HintQuestion(i18n("version.manage.duplicate.confirm"))) + .addQuestion(new PromptDialogPane.Builder.StringQuestion(null, version, + new Validator(i18n("install.new_game.already_exists"), newVersionName -> !profile.getRepository().hasVersion(newVersionName)))) + .addQuestion(new PromptDialogPane.Builder.BooleanQuestion(i18n("version.manage.duplicate.duplicate_save"), false))); + } + + public static void updateVersion(Profile profile, String version) { + Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(profile, version)); + } + + public static void updateGameAssets(Profile profile, String version) { + TaskExecutor executor = new GameAssetDownloadTask(profile.getDependency(), profile.getRepository().getVersion(version), GameAssetDownloadTask.DOWNLOAD_INDEX_FORCIBLY, true) + .executor(); + Controllers.taskDialog(executor, i18n("version.manage.redownload_assets_index"), TaskCancellationAction.NO_CANCEL); + executor.start(); + } + + */ + + public static void cleanVersion(Profile profile, String id) { + try { + profile.getRepository().clean(id); + } catch (IOException e) { + Logging.LOG.log(Level.WARNING, "Unable to clean game directory", e); + } + } + +} diff --git a/FCL/src/main/java/com/tungsten/fcl/util/RequestCodes.java b/FCL/src/main/java/com/tungsten/fcl/util/RequestCodes.java index 42515d07..7a84e51a 100644 --- a/FCL/src/main/java/com/tungsten/fcl/util/RequestCodes.java +++ b/FCL/src/main/java/com/tungsten/fcl/util/RequestCodes.java @@ -3,4 +3,8 @@ package com.tungsten.fcl.util; public class RequestCodes { public static final int PERMISSION_REQUEST_CODE = 0; + + public static final int BROWSE_VERSION_DIR_CODE = 50; + + public static final int SELECT_PROFILE_CODE = 100; } diff --git a/FCL/src/main/res/drawable/ic_baseline_content_copy_24.xml b/FCL/src/main/res/drawable/ic_baseline_content_copy_24.xml new file mode 100644 index 00000000..78548fd8 --- /dev/null +++ b/FCL/src/main/res/drawable/ic_baseline_content_copy_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/FCL/src/main/res/layout/dialog_add_profile.xml b/FCL/src/main/res/layout/dialog_add_profile.xml new file mode 100644 index 00000000..49bb1577 --- /dev/null +++ b/FCL/src/main/res/layout/dialog_add_profile.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/FCL/src/main/res/layout/dialog_rename_version.xml b/FCL/src/main/res/layout/dialog_rename_version.xml new file mode 100644 index 00000000..de17b023 --- /dev/null +++ b/FCL/src/main/res/layout/dialog_rename_version.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/FCL/src/main/res/layout/item_profile.xml b/FCL/src/main/res/layout/item_profile.xml new file mode 100644 index 00000000..4c234f33 --- /dev/null +++ b/FCL/src/main/res/layout/item_profile.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/FCL/src/main/res/layout/item_version.xml b/FCL/src/main/res/layout/item_version.xml new file mode 100644 index 00000000..8aa9f985 --- /dev/null +++ b/FCL/src/main/res/layout/item_version.xml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/FCL/src/main/res/layout/ui_version.xml b/FCL/src/main/res/layout/ui_version.xml index 3e401dd6..a819a159 100644 --- a/FCL/src/main/res/layout/ui_version.xml +++ b/FCL/src/main/res/layout/ui_version.xml @@ -1,7 +1,65 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/FCL/src/main/res/values-zh/strings.xml b/FCL/src/main/res/values-zh/strings.xml index 299cde31..13717d77 100644 --- a/FCL/src/main/res/values-zh/strings.xml +++ b/FCL/src/main/res/values-zh/strings.xml @@ -59,6 +59,7 @@ 无法校验文件。 缺少 SSL 证书。 + 无效的名称 下载超时 无法下载 @@ -73,8 +74,21 @@ 等待游戏启动 完成 + 刷新 操作已取消 + 未知 公有目录 私有目录 + 名称 + 路径 + 选择目录 + 请先填写名称与路径! + + 你确定要删除 %s 吗? 该操作无法撤回! + 该版本启用了版本隔离,删除该版本将会一并删除存档及其他数据。你仍要删除 %s 吗? + 重命名版本 + 新名称 + 重命名失败 + 新建目录 \ No newline at end of file diff --git a/FCL/src/main/res/values/strings.xml b/FCL/src/main/res/values/strings.xml index af7d3cdf..f035ceeb 100644 --- a/FCL/src/main/res/values/strings.xml +++ b/FCL/src/main/res/values/strings.xml @@ -66,7 +66,16 @@ Unable to access the file. Cannot verify the integrity of the downloaded files. Unable to establish SSL connection due to missing SSL certificates in current Java installation. - + + Fabric + Fabric API + Forge + Minecraft + LiteLoader + OptiFine + Quilt + QSL/QFAPI + Invalid Name Download timeout Unable to download @@ -80,9 +89,22 @@ Logging in Waiting for the game to launch Completing launch - + + Refresh Operation was cancelled + Unknown Shared Directory Private Directory + Name + Path + Select Directory + Please fill the name and path first! + + Are you sure you want to permanently remove the version %s? This action cannot be undone! + Since this instance is stored in an isolated directory, deleting it will also delete its saves and other data. Do you still want to delete instance %s? + Rename version + New name + Rename failed + New Directory \ No newline at end of file diff --git a/FCLCore/src/main/java/com/tungsten/fclcore/task/Task.java b/FCLCore/src/main/java/com/tungsten/fclcore/task/Task.java index de793988..d8643a17 100644 --- a/FCLCore/src/main/java/com/tungsten/fclcore/task/Task.java +++ b/FCLCore/src/main/java/com/tungsten/fclcore/task/Task.java @@ -1,6 +1,11 @@ package com.tungsten.fclcore.task; import com.tungsten.fclcore.event.EventManager; +import com.tungsten.fclcore.fakefx.beans.property.ReadOnlyDoubleProperty; +import com.tungsten.fclcore.fakefx.beans.property.ReadOnlyDoubleWrapper; +import com.tungsten.fclcore.fakefx.beans.property.ReadOnlyStringProperty; +import com.tungsten.fclcore.fakefx.beans.property.ReadOnlyStringWrapper; +import com.tungsten.fclcore.util.InvocationDispatcher; import com.tungsten.fclcore.util.Logging; import com.tungsten.fclcore.util.ReflectionHelper; import com.tungsten.fclcore.util.function.ExceptionalConsumer; @@ -296,6 +301,12 @@ public abstract class Task { } private long lastTime = Long.MIN_VALUE; + private final ReadOnlyDoubleWrapper progress = new ReadOnlyDoubleWrapper(this, "progress", -1); + private final InvocationDispatcher progressUpdate = InvocationDispatcher.runOn(Schedulers.androidUIThread(), progress::set); + + public ReadOnlyDoubleProperty progressProperty() { + return progress.getReadOnlyProperty(); + } protected void updateProgress(long progress, long total) { updateProgress(1.0 * progress / total); @@ -312,11 +323,18 @@ public abstract class Task { } protected void updateProgressImmediately(double progress) { - // TODO: update progress + progressUpdate.accept(progress); + } + + private final ReadOnlyStringWrapper message = new ReadOnlyStringWrapper(this, "message", null); + private final InvocationDispatcher messageUpdate = InvocationDispatcher.runOn(Schedulers.androidUIThread(), message::set); + + public final ReadOnlyStringProperty messageProperty() { + return message.getReadOnlyProperty(); } protected final void updateMessage(String newMessage) { - // TODO: update message + messageUpdate.accept(newMessage); } public final T run() throws Exception { @@ -334,7 +352,11 @@ public abstract class Task { } private void doSubTask(Task task) throws Exception { + message.bind(task.message); + progress.bind(task.progress); task.run(); + message.unbind(); + progress.unbind(); } public final TaskExecutor executor() { diff --git a/FCLCore/src/main/java/com/tungsten/fclcore/util/FutureCallback.java b/FCLCore/src/main/java/com/tungsten/fclcore/util/FutureCallback.java new file mode 100644 index 00000000..e94ad859 --- /dev/null +++ b/FCLCore/src/main/java/com/tungsten/fclcore/util/FutureCallback.java @@ -0,0 +1,17 @@ +package com.tungsten.fclcore.util; + +import java.util.function.Consumer; + +@FunctionalInterface +public interface FutureCallback { + + /** + * Callback of future, called after future finishes. + * This callback gives the feedback whether the result of future is acceptable or not, + * if not, giving the reason, and future will be relaunched when necessary. + * @param result result of the future + * @param resolve accept the result + * @param reject reject the result with failure reason + */ + void call(T result, Runnable resolve, Consumer reject); +} \ No newline at end of file diff --git a/FCLLibrary/src/main/java/com/tungsten/fcllibrary/anim/DynamicIslandAnim.java b/FCLLibrary/src/main/java/com/tungsten/fcllibrary/anim/DynamicIslandAnim.java index 3f77f2de..0064e551 100644 --- a/FCLLibrary/src/main/java/com/tungsten/fcllibrary/anim/DynamicIslandAnim.java +++ b/FCLLibrary/src/main/java/com/tungsten/fcllibrary/anim/DynamicIslandAnim.java @@ -3,6 +3,8 @@ package com.tungsten.fcllibrary.anim; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; +import android.os.Handler; +import android.view.View; import com.tungsten.fcllibrary.component.view.FCLDynamicIsland; @@ -10,6 +12,8 @@ public class DynamicIslandAnim { private final FCLDynamicIsland view; + private Thread thread; + private Handler handler; private ObjectAnimator expandScaleAnimatorX; private ObjectAnimator shrinkScaleAnimatorX; private ObjectAnimator expandScaleAnimatorY; @@ -18,12 +22,17 @@ public class DynamicIslandAnim { private ObjectAnimator shrinkAdjustAnimatorX; private ObjectAnimator expandAdjustAnimatorY; private ObjectAnimator shrinkAdjustAnimatorY; + private ObjectAnimator hideAnimator; public DynamicIslandAnim(FCLDynamicIsland view) { this.view = view; + this.handler = new Handler(); } public void refresh(float scale) { + if (thread != null) { + thread.interrupt(); + } if (expandScaleAnimatorX != null && expandScaleAnimatorX.isRunning()) { expandScaleAnimatorX.cancel(); } @@ -48,6 +57,9 @@ public class DynamicIslandAnim { if (shrinkAdjustAnimatorY != null && shrinkAdjustAnimatorY.isRunning()) { shrinkAdjustAnimatorY.cancel(); } + if (hideAnimator != null && hideAnimator.isRunning()) { + hideAnimator.cancel(); + } expandScaleAnimatorX = ObjectAnimator.ofFloat(view, "scaleX", scale / 2f, 0.95f).setDuration(300); shrinkScaleAnimatorX = ObjectAnimator.ofFloat(view, "scaleX", 0.95f, scale / 2f).setDuration(300); expandScaleAnimatorY = ObjectAnimator.ofFloat(view, "scaleY", 0.5f, 0.95f).setDuration(300); @@ -56,9 +68,12 @@ public class DynamicIslandAnim { shrinkAdjustAnimatorX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0.95f).setDuration(200); expandAdjustAnimatorY = ObjectAnimator.ofFloat(view, "scaleY", 0.95f, 1f).setDuration(200); shrinkAdjustAnimatorY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 0.95f).setDuration(200); + hideAnimator = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f).setDuration(2000); } public void run(String text) { + view.setVisibility(View.VISIBLE); + view.setAlpha(1f); shrinkScaleAnimatorX.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -73,6 +88,32 @@ public class DynamicIslandAnim { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); + shrinkAdjustAnimatorX.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + thread = new Thread(() -> { + try { + Thread.sleep(2000); + if (!thread.isInterrupted()) { + handler.post(() -> { + hideAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + view.setVisibility(View.GONE); + } + }); + hideAnimator.start(); + }); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + }); + thread.start(); + } + }); shrinkAdjustAnimatorX.start(); shrinkAdjustAnimatorY.start(); } diff --git a/FCLLibrary/src/main/java/com/tungsten/fcllibrary/browser/FileBrowserActivity.java b/FCLLibrary/src/main/java/com/tungsten/fcllibrary/browser/FileBrowserActivity.java index 782fe42b..dcaaab5d 100644 --- a/FCLLibrary/src/main/java/com/tungsten/fcllibrary/browser/FileBrowserActivity.java +++ b/FCLLibrary/src/main/java/com/tungsten/fcllibrary/browser/FileBrowserActivity.java @@ -120,16 +120,18 @@ public class FileBrowserActivity extends FCLActivity implements View.OnClickList selectedFiles.remove(path); } else { - if (fileBrowser.getLibMode() == LibMode.FOLDER_CHOOSER || fileBrowser.getSelectionMode() == SelectionMode.SINGLE_SELECTION) { + if (fileBrowser.getSelectionMode() == SelectionMode.SINGLE_SELECTION) { selectedFiles = new ArrayList<>(); } selectedFiles.add(path); } adapter1.setSelectedFiles(selectedFiles); adapter1.notifyDataSetChanged(); + System.out.println(selectedFiles); } }); listView.setAdapter(adapter); + System.out.println(selectedFiles); } @Override diff --git a/FCLLibrary/src/main/java/com/tungsten/fcllibrary/browser/adapter/FileBrowserAdapter.java b/FCLLibrary/src/main/java/com/tungsten/fcllibrary/browser/adapter/FileBrowserAdapter.java index 12095fd4..b2208e83 100644 --- a/FCLLibrary/src/main/java/com/tungsten/fcllibrary/browser/adapter/FileBrowserAdapter.java +++ b/FCLLibrary/src/main/java/com/tungsten/fcllibrary/browser/adapter/FileBrowserAdapter.java @@ -134,7 +134,7 @@ public class FileBrowserAdapter extends FCLAdapter { if (file.isDirectory()) { listener.onEnterDir(file.getAbsolutePath()); } - if (fileBrowser.getLibMode() != LibMode.FILE_BROWSER && !(fileBrowser.getLibMode() == LibMode.FILE_CHOOSER && file.isDirectory()) && !(fileBrowser.getLibMode() == LibMode.FOLDER_CHOOSER && file.isFile())) { + if (fileBrowser.getLibMode() != LibMode.FILE_BROWSER && fileBrowser.getLibMode() != LibMode.FOLDER_CHOOSER && !(fileBrowser.getLibMode() == LibMode.FILE_CHOOSER)) { listener.onSelect(this, file.getAbsolutePath()); } });