From cee0ca96615fdcca3a4102b0d1c41bbf8610db57 Mon Sep 17 00:00:00 2001 From: Miku AuahDark Date: Sun, 22 May 2022 13:18:40 +0800 Subject: [PATCH] Implement game selector. Item selection is currently not implemented. --- .../org/love2d/android/GameListAdapter.java | 70 +++++++++++ .../java/org/love2d/android/MainActivity.java | 117 +++++++++++++++++- app/src/normal/res/layout/activity_main.xml | 2 + 3 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 app/src/normal/java/org/love2d/android/GameListAdapter.java diff --git a/app/src/normal/java/org/love2d/android/GameListAdapter.java b/app/src/normal/java/org/love2d/android/GameListAdapter.java new file mode 100644 index 00000000..10504cb2 --- /dev/null +++ b/app/src/normal/java/org/love2d/android/GameListAdapter.java @@ -0,0 +1,70 @@ +package org.love2d.android; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import java.io.File; + +public class GameListAdapter extends RecyclerView.Adapter { + + private Data[] data = null; + + @NonNull + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_game, parent, false); + ViewHolder holder = new ViewHolder(view); + + return holder; + } + + @Override + public void onBindViewHolder(ViewHolder holder, int position) { + Data data = this.data[position]; + holder.setName(data.path.getName()); + holder.setIsDirectory(data.directory); + } + + @Override + public int getItemCount() { + return data != null ? data.length : 0; + } + + public void setData(Data[] data) { + this.data = data; + } + + static class ViewHolder extends RecyclerView.ViewHolder { + + private final TextView name; + private final ImageView image; + + public ViewHolder(View itemView) { + super(itemView); + + name = itemView.findViewById(R.id.textView); + image = itemView.findViewById(R.id.imageView); + } + + public void setName(String name) { + this.name.setText(name); + } + + public void setIsDirectory(boolean directory) { + this.image.setImageResource(directory ? R.drawable.ic_baseline_folder_32 : R.drawable.ic_baseline_insert_drive_file_32); + } + } + + static class Data { + // Absolute path of the file. + public File path; + // Denote if this game is a directory. + public boolean directory; + } +} diff --git a/app/src/normal/java/org/love2d/android/MainActivity.java b/app/src/normal/java/org/love2d/android/MainActivity.java index 62091616..0aa198b4 100644 --- a/app/src/normal/java/org/love2d/android/MainActivity.java +++ b/app/src/normal/java/org/love2d/android/MainActivity.java @@ -2,6 +2,9 @@ package org.love2d.android; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import android.content.Intent; @@ -10,19 +13,40 @@ import android.os.Bundle; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import android.view.View; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.zip.ZipFile; public class MainActivity extends AppCompatActivity { + private Executor executor = Executors.newSingleThreadExecutor(); + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); + RecyclerView recyclerView = findViewById(R.id.recyclerView); SwipeRefreshLayout swipeLayout = findViewById(R.id.swipeRefreshLayout); + ConstraintLayout noGameText = findViewById(R.id.constraintLayout); + + GameListAdapter adapter = new GameListAdapter(); + + // Set refresh listener swipeLayout.setOnRefreshListener(() -> { - // TODO: Actually update list of games - swipeLayout.setRefreshing(false); + scanGames(adapter, noGameText, swipeLayout); }); + + // Set layout manager and adapter + recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)); + recyclerView.setAdapter(adapter); + + scanGames(adapter, noGameText, null); } @Override @@ -69,4 +93,93 @@ public class MainActivity extends AppCompatActivity { return builder.setMessage(message.toString()).create(); } + + private void scanGames(GameListAdapter adapter, ConstraintLayout noGameText, SwipeRefreshLayout swipeRefreshLayout) { + final MainActivity current = this; + + executor.execute(() -> { + File extDir = getExternalFilesDir("games"); + + if (!extDir.isDirectory()) { + if (!extDir.mkdir()) { + // Scan failure, abort + if (swipeRefreshLayout != null) { + swipeRefreshLayout.setRefreshing(false); + } + + new AlertDialog.Builder(current) + .setTitle("Scan error") + .setMessage("Scan failure") + .setPositiveButton(R.string.ok, (dialog1, which) -> { }) + .create() + .show(); + return; + } + } + + ArrayList validGames = new ArrayList<>(); + File[] files = extDir.listFiles(); + + if (files != null) { + for (File file: files) { + GameListAdapter.Data gameData = null; + + if (file.isDirectory()) { + if (isValidGamedir(file)) { + gameData = new GameListAdapter.Data(); + gameData.path = file; + gameData.directory = true; + } + } else { + if (isValidLovegame(file)) { + gameData = new GameListAdapter.Data(); + gameData.path = file; + gameData.directory = false; + } + } + + if (gameData != null) { + validGames.add(gameData); + } + } + } + + boolean empty = validGames.isEmpty(); + + runOnUiThread(() -> { + if (empty) { + adapter.setData(null); + } else { + GameListAdapter.Data[] gameDatas = new GameListAdapter.Data[validGames.size()]; + validGames.toArray(gameDatas); + adapter.setData(gameDatas); + } + + adapter.notifyDataSetChanged(); + + if (swipeRefreshLayout != null) { + swipeRefreshLayout.setRefreshing(false); + } + + noGameText.setVisibility(empty ? View.VISIBLE : View.INVISIBLE); + }); + }); + } + + public static boolean isValidLovegame(File file) { + boolean valid = false; + + try { + ZipFile zip = new ZipFile(file, ZipFile.OPEN_READ); + valid = zip.getEntry("main.lua") != null; + zip.close(); + } catch (IOException ignored) { } + + return valid; + } + + public static boolean isValidGamedir(File file) { + File mainLua = new File(file, "main.lua"); + return mainLua.isFile(); + } } diff --git a/app/src/normal/res/layout/activity_main.xml b/app/src/normal/res/layout/activity_main.xml index e49e729d..8ec13997 100644 --- a/app/src/normal/res/layout/activity_main.xml +++ b/app/src/normal/res/layout/activity_main.xml @@ -12,6 +12,7 @@ android:layout_height="match_parent"> @@ -20,6 +21,7 @@