Skip to content
This repository was archived by the owner on Mar 5, 2023. It is now read-only.

Commit 7a480fd

Browse files
authored
Merge pull request #19 from vcmi/features/mods-launcher
Features/mods launcher
2 parents bb847ba + a580283 commit 7a480fd

File tree

15 files changed

+533
-56
lines changed

15 files changed

+533
-56
lines changed

ext/vcmi

Submodule vcmi updated 108 files

project/vcmi-app/src/main/java/eu/vcmi/vcmi/ActivityMods.java

Lines changed: 100 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
package eu.vcmi.vcmi;
22

3+
import android.content.DialogInterface;
34
import android.os.AsyncTask;
45
import android.os.Bundle;
5-
import android.os.Environment;
6+
67
import androidx.annotation.Nullable;
78
import com.google.android.material.snackbar.Snackbar;
9+
10+
import androidx.appcompat.app.AlertDialog;
811
import androidx.recyclerview.widget.DefaultItemAnimator;
912
import androidx.recyclerview.widget.DividerItemDecoration;
1013
import androidx.recyclerview.widget.LinearLayoutManager;
1114
import androidx.recyclerview.widget.RecyclerView;
12-
import androidx.appcompat.widget.Toolbar;
15+
1316
import android.view.Menu;
1417
import android.view.MenuInflater;
1518
import android.view.MenuItem;
@@ -24,24 +27,25 @@
2427
import java.util.ArrayList;
2528
import java.util.Collections;
2629
import java.util.List;
27-
import java.util.stream.Stream;
28-
import java.util.stream.Collectors;
30+
import java.util.Locale;
2931

3032
import eu.vcmi.vcmi.content.ModBaseViewHolder;
3133
import eu.vcmi.vcmi.content.ModsAdapter;
32-
import eu.vcmi.vcmi.content.ModsViewHolder;
34+
import eu.vcmi.vcmi.mods.VCMIMod;
3335
import eu.vcmi.vcmi.mods.VCMIModContainer;
3436
import eu.vcmi.vcmi.mods.VCMIModsRepo;
37+
import eu.vcmi.vcmi.util.InstallModAsync;
3538
import eu.vcmi.vcmi.util.FileUtil;
3639
import eu.vcmi.vcmi.util.Log;
40+
import eu.vcmi.vcmi.util.ServerResponse;
3741

3842
/**
3943
* @author F
4044
*/
4145
public class ActivityMods extends ActivityWithToolbar
4246
{
43-
private static final boolean ENABLE_REPO_DOWNLOADING = false;
44-
private static final String REPO_URL = "http://download.vcmi.eu/mods/repository/repository.json";
47+
private static final boolean ENABLE_REPO_DOWNLOADING = true;
48+
private static final String REPO_URL = "https://raw.githubusercontent.com/vcmi/vcmi-mods-repository/develop/github.json";
4549
private VCMIModsRepo mRepo;
4650
private RecyclerView mRecycler;
4751

@@ -70,15 +74,18 @@ protected void onCreate(@Nullable final Bundle savedInstanceState)
7074
mRecycler.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
7175
mRecycler.setVisibility(View.GONE);
7276

77+
mModsAdapter = new ModsAdapter(new OnAdapterItemAction());
78+
mRecycler.setAdapter(mModsAdapter);
79+
7380
new AsyncLoadLocalMods().execute((Void) null);
7481
}
7582

7683
private void loadLocalModData() throws IOException, JSONException
7784
{
78-
final String dataRoot = getDataDir() + "/" + Const.VCMI_DATA_ROOT_FOLDER_NAME;
85+
final File dataRoot = Storage.getVcmiDataDir(this);
7986
final String internalDataRoot = getFilesDir() + "/" + Const.VCMI_DATA_ROOT_FOLDER_NAME;
8087

81-
final File modsRoot = new File(dataRoot + "/Mods");
88+
final File modsRoot = new File(dataRoot,"/Mods");
8289
final File internalModsRoot = new File(internalDataRoot + "/Mods");
8390
if (!modsRoot.exists() && !internalModsRoot.exists())
8491
{
@@ -98,8 +105,7 @@ private void loadLocalModData() throws IOException, JSONException
98105
}
99106
mModContainer = VCMIModContainer.createContainer(topLevelModsFolders);
100107

101-
final String configPath = dataRoot + "/config/modSettings.json";
102-
final File modConfigFile = new File(configPath);
108+
final File modConfigFile = new File(dataRoot, "config/modSettings.json");
103109
if (!modConfigFile.exists())
104110
{
105111
Log.w(this, "We don't have mods config");
@@ -129,6 +135,7 @@ public boolean onOptionsItemSelected(final MenuItem item)
129135
Log.i(this, "Should download repo now...");
130136
if (ENABLE_REPO_DOWNLOADING)
131137
{
138+
mProgress.setVisibility(View.VISIBLE);
132139
mRepo.init(REPO_URL, new OnModsRepoInitialized()); // disabled because the json is broken anyway
133140
}
134141
else
@@ -150,16 +157,21 @@ private void handleNoData()
150157

151158
private void saveModSettingsToFile()
152159
{
153-
mModContainer.saveToFile(new File(getDataDir(), Const.VCMI_DATA_ROOT_FOLDER_NAME + "/config/modSettings.json"));
160+
mModContainer.saveToFile(
161+
new File(
162+
Storage.getVcmiDataDir(this),
163+
"config/modSettings.json"));
154164
}
155165

156166
private class OnModsRepoInitialized implements VCMIModsRepo.IOnModsRepoDownloaded
157167
{
158168
@Override
159-
public void onSuccess()
169+
public void onSuccess(ServerResponse<List<VCMIMod>> response)
160170
{
161171
Log.i(this, "Initialized mods repo");
162-
// TODO update dataset
172+
mModContainer.updateFromRepo(response.mContent);
173+
mModsAdapter.updateModsList(mModContainer.submods());
174+
mProgress.setVisibility(View.GONE);
163175
}
164176

165177
@Override
@@ -206,13 +218,7 @@ protected void onPostExecute(final Void aVoid)
206218
{
207219
mProgress.setVisibility(View.GONE);
208220
mRecycler.setVisibility(View.VISIBLE);
209-
mModsAdapter = new ModsAdapter(
210-
mModContainer.submods()
211-
.stream()
212-
.map(ModsAdapter.ModItem::new)
213-
.collect(Collectors.toList()),
214-
new OnAdapterItemAction());
215-
mRecycler.setAdapter(mModsAdapter);
221+
mModsAdapter.updateModsList(mModContainer.submods());
216222
}
217223
}
218224
}
@@ -242,14 +248,84 @@ public void onItemPressed(final ModsAdapter.ModItem mod, final RecyclerView.View
242248
public void onDownloadPressed(final ModsAdapter.ModItem mod, final RecyclerView.ViewHolder vh)
243249
{
244250
Log.i(this, "Mod download pressed: " + mod);
251+
mModsAdapter.downloadProgress(mod, "0%");
252+
installModAsync(mod);
245253
}
246254

247255
@Override
248256
public void onTogglePressed(final ModsAdapter.ModItem item, final ModBaseViewHolder holder)
249257
{
250-
item.mMod.mActive = !item.mMod.mActive;
251-
mModsAdapter.notifyItemChanged(holder.getAdapterPosition());
252-
saveModSettingsToFile();
258+
if(!item.mMod.mSystem && item.mMod.mInstalled)
259+
{
260+
item.mMod.mActive = !item.mMod.mActive;
261+
mModsAdapter.notifyItemChanged(holder.getAdapterPosition());
262+
saveModSettingsToFile();
263+
}
264+
}
265+
266+
@Override
267+
public void onUninstall(ModsAdapter.ModItem item, ModBaseViewHolder holder)
268+
{
269+
File installationFolder = item.mMod.installationFolder;
270+
ActivityMods activity = ActivityMods.this;
271+
272+
if(installationFolder != null){
273+
new AlertDialog.Builder(activity)
274+
.setTitle(activity.getString(R.string.mods_removal_title, item.mMod.mName))
275+
.setMessage(activity.getString(R.string.mods_removal_confirmation, item.mMod.mName))
276+
.setIcon(android.R.drawable.ic_dialog_alert)
277+
.setNegativeButton(android.R.string.no, null)
278+
.setPositiveButton(android.R.string.yes, (dialog, whichButton) ->
279+
{
280+
FileUtil.clearDirectory(installationFolder);
281+
installationFolder.delete();
282+
283+
mModsAdapter.modRemoved(item);
284+
})
285+
.show();
286+
}
287+
}
288+
}
289+
290+
private void installModAsync(ModsAdapter.ModItem mod){
291+
File dataDir = Storage.getVcmiDataDir(this);
292+
File modFolder = new File(
293+
new File(dataDir, "Mods"),
294+
mod.mMod.mId.toLowerCase(Locale.US));
295+
296+
InstallModAsync modInstaller = new InstallModAsync(
297+
modFolder,
298+
this,
299+
new InstallModCallback(mod)
300+
);
301+
302+
modInstaller.execute(mod.mMod.mArchiveUrl);
303+
}
304+
305+
public class InstallModCallback implements InstallModAsync.PostDownload
306+
{
307+
private ModsAdapter.ModItem mod;
308+
309+
public InstallModCallback(ModsAdapter.ModItem mod)
310+
{
311+
this.mod = mod;
312+
}
313+
314+
@Override
315+
public void downloadDone(Boolean succeed, File modFolder)
316+
{
317+
if(succeed){
318+
mModsAdapter.modInstalled(mod, modFolder);
319+
}
320+
}
321+
322+
@Override
323+
public void downloadProgress(String... progress)
324+
{
325+
if(progress.length > 0)
326+
{
327+
mModsAdapter.downloadProgress(mod, progress[0]);
328+
}
253329
}
254330
}
255331
}

project/vcmi-app/src/main/java/eu/vcmi/vcmi/Const.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,4 @@ public class Const
2121
public static final int SUPPRESS_TRY_WITH_RESOURCES_WARNING = Build.VERSION_CODES.KITKAT;
2222

2323
public static final String VCMI_DATA_ROOT_FOLDER_NAME = "vcmi-data";
24-
25-
public static final String VCMI_DATA_ZIP_FILE_NAME = "vcmi-data.zip";
26-
public static final String STORAGE_CONFIG_FILE_NAME = "storage.config";
27-
public static final boolean INTERNAL_STORAGE_AVAILABLE = Build.VERSION.SDK_INT >= 24;
2824
}

project/vcmi-app/src/main/java/eu/vcmi/vcmi/Storage.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,4 @@ public static boolean testH3DataFolder(final File baseDir)
2828
public static String getH3DataFolder(Context context){
2929
return getVcmiDataDir(context).getAbsolutePath();
3030
}
31-
32-
private static File getStorageSettingsFile(Context context)
33-
{
34-
return new File(context.getFilesDir(),"storage.config");
35-
}
3631
}

project/vcmi-app/src/main/java/eu/vcmi/vcmi/content/AsyncLauncherInitialization.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ private InitResult handleDataFoldersInitialization()
6666
Log.i(this, "Using " + vcmiDir.getAbsolutePath() + " as root vcmi dir");
6767

6868
if(!vcmiInternalDir.exists()) vcmiInternalDir.mkdir();
69+
if(!vcmiDir.exists()) vcmiDir.mkdir();
6970

7071
if (!Storage.testH3DataFolder(ctx))
7172
{

project/vcmi-app/src/main/java/eu/vcmi/vcmi/content/ModsAdapter.java

Lines changed: 80 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import android.view.View;
66
import android.view.ViewGroup;
77

8+
import java.io.File;
89
import java.util.stream.Collectors;
910
import java.util.stream.Stream;
1011

@@ -27,10 +28,9 @@ public class ModsAdapter extends RecyclerView.Adapter<ModBaseViewHolder>
2728
private final List<ModItem> mDataset = new ArrayList<>();
2829
private final IOnItemAction mItemListener;
2930

30-
public ModsAdapter(final List<ModItem> mods, final IOnItemAction itemListener)
31+
public ModsAdapter(final IOnItemAction itemListener)
3132
{
3233
mItemListener = itemListener;
33-
mDataset.addAll(mods);
3434
}
3535

3636
@Override
@@ -75,12 +75,32 @@ public void onBindViewHolder(final ModBaseViewHolder holder, final int position)
7575
modHolder.mModAuthor.setText(ctx.getString(R.string.mods_item_author_template, item.mMod.mAuthor));
7676
modHolder.mStatusIcon.setImageResource(selectModStatusIcon(item.mMod.mActive));
7777

78+
modHolder.mDownloadBtn.setVisibility(View.GONE);
79+
modHolder.mDownloadProgress.setVisibility(View.GONE);
80+
modHolder.mUninstall.setVisibility(View.GONE);
7881

79-
modHolder.mDownloadBtn.setVisibility(View.GONE); // TODO visible for mods that aren't downloaded
82+
if(!item.mMod.mSystem)
83+
{
84+
if (item.mDownloadProgress != null)
85+
{
86+
modHolder.mDownloadProgress.setText(item.mDownloadProgress);
87+
modHolder.mDownloadProgress.setVisibility(View.VISIBLE);
88+
}
89+
else if (!item.mMod.mInstalled)
90+
{
91+
modHolder.mDownloadBtn.setVisibility(View.VISIBLE);
92+
}
93+
else if (item.mMod.installationFolder != null)
94+
{
95+
modHolder.mUninstall.setVisibility(View.VISIBLE);
96+
}
97+
98+
modHolder.itemView.setOnClickListener(v -> mItemListener.onItemPressed(item, holder));
99+
modHolder.mStatusIcon.setOnClickListener(v -> mItemListener.onTogglePressed(item, holder));
100+
modHolder.mDownloadBtn.setOnClickListener(v -> mItemListener.onDownloadPressed(item, holder));
101+
modHolder.mUninstall.setOnClickListener(v -> mItemListener.onUninstall(item, holder));
102+
}
80103

81-
modHolder.itemView.setOnClickListener(v -> mItemListener.onItemPressed(item, holder));
82-
modHolder.mStatusIcon.setOnClickListener(v -> mItemListener.onTogglePressed(item, holder));
83-
modHolder.mDownloadBtn.setOnClickListener(v -> mItemListener.onDownloadPressed(item, holder));
84104
break;
85105
case VIEWTYPE_FAILED_MOD:
86106
holder.mModName.setText(ctx.getString(R.string.mods_failed_mod_loading, item.mMod.mName));
@@ -138,20 +158,74 @@ public void detachSubmods(final ModItem mod, final RecyclerView.ViewHolder vh)
138158
notifyItemRangeRemoved(checkedPosition, detachedElements);
139159
}
140160

161+
public void updateModsList(List<VCMIMod> mods)
162+
{
163+
mDataset.clear();
164+
mDataset.addAll(
165+
mods.stream()
166+
.map(ModItem::new)
167+
.collect(Collectors.toList()));
168+
169+
notifyDataSetChanged();
170+
}
171+
172+
public void modInstalled(ModItem mod, File modFolder)
173+
{
174+
try
175+
{
176+
mod.mMod.updateFromModInfo(modFolder);
177+
mod.mMod.mLoadedCorrectly = true;
178+
mod.mMod.mActive = true; // active by default
179+
mod.mMod.mInstalled = true;
180+
mod.mMod.installationFolder = modFolder;
181+
mod.mDownloadProgress = null;
182+
notifyItemChanged(mDataset.indexOf(mod));
183+
} catch (Exception ex)
184+
{
185+
Log.e("Failed to install mod", ex);
186+
}
187+
}
188+
189+
public void downloadProgress(ModItem mod, String progress)
190+
{
191+
mod.mDownloadProgress = progress;
192+
notifyItemChanged(mDataset.indexOf(mod));
193+
}
194+
195+
public void modRemoved(ModItem item)
196+
{
197+
int itemIndex = mDataset.indexOf(item);
198+
199+
if(item.mMod.mArchiveUrl != null && item.mMod.mArchiveUrl != "")
200+
{
201+
item.mMod.mInstalled = false;
202+
item.mMod.installationFolder = null;
203+
204+
notifyItemChanged(itemIndex);
205+
}
206+
else{
207+
mDataset.remove(item);
208+
notifyItemRemoved(itemIndex);
209+
}
210+
}
211+
141212
public interface IOnItemAction
142213
{
143214
void onItemPressed(final ModItem mod, final RecyclerView.ViewHolder vh);
144215

145216
void onDownloadPressed(final ModItem mod, final RecyclerView.ViewHolder vh);
146217

147218
void onTogglePressed(ModItem item, ModBaseViewHolder holder);
219+
220+
void onUninstall(ModItem item, ModBaseViewHolder holder);
148221
}
149222

150223
public static class ModItem
151224
{
152225
public final VCMIMod mMod;
153226
public int mNestingLevel;
154227
public boolean mExpanded;
228+
public String mDownloadProgress;
155229

156230
public ModItem(final VCMIMod mod)
157231
{

project/vcmi-app/src/main/java/eu/vcmi/vcmi/content/ModsViewHolder.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ public class ModsViewHolder extends ModBaseViewHolder
1818
final TextView mModSize;
1919
final ImageView mStatusIcon;
2020
final View mDownloadBtn;
21+
final TextView mDownloadProgress;
22+
final View mUninstall;
2123

2224
ModsViewHolder(final View parentView)
2325
{
@@ -27,5 +29,7 @@ public class ModsViewHolder extends ModBaseViewHolder
2729
mModSize = (TextView) itemView.findViewById(R.id.mods_adapter_item_size);
2830
mDownloadBtn = itemView.findViewById(R.id.mods_adapter_item_btn_download);
2931
mStatusIcon = (ImageView) itemView.findViewById(R.id.mods_adapter_item_status);
32+
mDownloadProgress = (TextView) itemView.findViewById(R.id.mods_adapter_item_install_progress);
33+
mUninstall = itemView.findViewById(R.id.mods_adapter_item_btn_uninstall);
3034
}
3135
}

0 commit comments

Comments
 (0)