123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225 | /*
* Copyright (c) 2006-2024 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
package org.love2d.android;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
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 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 static final String TAG = "MainActivity";
private final Executor executor = Executors.newSingleThreadExecutor();
private final ActivityResultLauncher<String[]> openFileLauncher = registerForActivityResult(
new ActivityResultContracts.OpenDocument(),
(Uri result) -> {
if (result != null) {
Intent intent = new Intent(this, GameActivity.class);
intent.setData(result);
startActivity(intent);
}
}
);
@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(() -> {
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
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.options_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int itemId = item.getItemId();
// Handle item selection
if (itemId == R.id.optionItem) {
openFileLauncher.launch(new String[]{"*/*"});
return true;
} else if (itemId == R.id.optionItem2) {
Intent intent = new Intent(this, GameActivity.class);
startActivity(intent);
return true;
} else if (itemId == R.id.optionItem3) {
getGameFolderDialog().show();
return true;
} else if (itemId == R.id.optionItem4) {
Intent intent = new Intent(this, AboutActivity.class);
startActivity(intent);
return true;
} else {
return super.onOptionsItemSelected(item);
}
}
private AlertDialog getGameFolderDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this)
.setTitle(getString(R.string.game_folder))
.setPositiveButton(R.string.ok, (dialog1, which) -> {
});
StringBuilder message = new StringBuilder()
.append(getString(R.string.game_folder_location, getPackageName()))
.append("\n\n");
if (Build.VERSION.SDK_INT >= 30) {
message.append(getString(R.string.game_folder_inaccessible));
} else {
message.append(getString(R.string.game_folder_accessible));
}
return builder.setMessage(message.toString()).create();
}
@SuppressLint("NotifyDataSetChanged")
private void scanGames(GameListAdapter adapter, ConstraintLayout noGameText, SwipeRefreshLayout swipeRefreshLayout) {
executor.execute(() -> {
File extDir = getExternalFilesDir("games");
if (extDir != null) {
if (!extDir.isDirectory()) {
if (!extDir.mkdir()) {
// Scan failure, abort
runOnUiThread(() -> {
if (swipeRefreshLayout != null) {
swipeRefreshLayout.setRefreshing(false);
}
});
Log.e(TAG, "Directory creation failure.");
return;
}
}
ArrayList<GameListAdapter.Data> 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();
}
}
|