diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..30fe0ca --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +# Gradle files +.gradle +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Log Files +*.log + +# Android Studio generated files and folders +.idea/ +.cxx/ +captures/ +.externalNativeBuild/ + +# OSX files +.DS_Store + +# Misc +*.iml +.project +*.swp +*~ + +# Keystore files +*.jks +*.keystore + +# Google Services (safety) +/app/google-services.json diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..6898516 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,43 @@ +plugins { + alias(libs.plugins.android.application) +} + +android { + namespace = "be.gyu.android.file.explorer" + compileSdk { + version = release(36) + } + + defaultConfig { + applicationId = "be.gyu.android.file.explorer" + minSdk = 24 + targetSdk = 36 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } +} + +dependencies { + implementation(libs.appcompat) + implementation(libs.material) + implementation("commons-net:commons-net:3.10.0") + testImplementation(libs.junit) + androidTestImplementation(libs.ext.junit) + androidTestImplementation(libs.espresso.core) +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/be/gyu/android/file/explorer/ExampleInstrumentedTest.java b/app/src/androidTest/java/be/gyu/android/file/explorer/ExampleInstrumentedTest.java new file mode 100644 index 0000000..4e93717 --- /dev/null +++ b/app/src/androidTest/java/be/gyu/android/file/explorer/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package be.gyu.android.file.explorer; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("be.gyu.android.file.explorer", appContext.getPackageName()); + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..db9cf23 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/be/gyu/android/file/explorer/DbHelper.java b/app/src/main/java/be/gyu/android/file/explorer/DbHelper.java new file mode 100644 index 0000000..2a9bcc3 --- /dev/null +++ b/app/src/main/java/be/gyu/android/file/explorer/DbHelper.java @@ -0,0 +1,81 @@ +package be.gyu.android.file.explorer; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +import java.util.ArrayList; +import java.util.List; + +public class DbHelper extends SQLiteOpenHelper { + + private static final String DATABASE_NAME = "remote_servers.db"; + private static final int DATABASE_VERSION = 1; + + public static final String TABLE_SERVERS = "servers"; + public static final String COLUMN_ID = "_id"; + public static final String COLUMN_HOST = "host"; + public static final String COLUMN_PORT = "port"; + public static final String COLUMN_USERNAME = "username"; + public static final String COLUMN_PASSWORD = "password"; + + private static final String TABLE_CREATE = + "CREATE TABLE " + TABLE_SERVERS + " (" + + COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + COLUMN_HOST + " TEXT, " + + COLUMN_PORT + " INTEGER, " + + COLUMN_USERNAME + " TEXT, " + + COLUMN_PASSWORD + " TEXT" + + ");"; + + public DbHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL(TABLE_CREATE); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + db.execSQL("DROP TABLE IF EXISTS " + TABLE_SERVERS); + onCreate(db); + } + + public void addServer(RemoteServer server) { + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(COLUMN_HOST, server.getHost()); + values.put(COLUMN_PORT, server.getPort()); + values.put(COLUMN_USERNAME, server.getUsername()); + values.put(COLUMN_PASSWORD, server.getPassword()); + db.insert(TABLE_SERVERS, null, values); + db.close(); + } + + public List getAllServers() { + List serverList = new ArrayList<>(); + String selectQuery = "SELECT * FROM " + TABLE_SERVERS; + SQLiteDatabase db = this.getWritableDatabase(); + Cursor cursor = db.rawQuery(selectQuery, null); + + if (cursor.moveToFirst()) { + do { + RemoteServer server = new RemoteServer( + cursor.getLong(cursor.getColumnIndexOrThrow(COLUMN_ID)), + cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_HOST)), + cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_PORT)), + cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_USERNAME)), + cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_PASSWORD)) + ); + serverList.add(server); + } while (cursor.moveToNext()); + } + cursor.close(); + db.close(); + return serverList; + } +} \ No newline at end of file diff --git a/app/src/main/java/be/gyu/android/file/explorer/FTPClientHelper.java b/app/src/main/java/be/gyu/android/file/explorer/FTPClientHelper.java new file mode 100644 index 0000000..663c544 --- /dev/null +++ b/app/src/main/java/be/gyu/android/file/explorer/FTPClientHelper.java @@ -0,0 +1,60 @@ +package be.gyu.android.file.explorer; + +import org.apache.commons.net.ftp.FTPClient; +import org.apache.commons.net.ftp.FTPFile; +import org.apache.commons.net.ftp.FTPReply; + +import java.io.IOException; + +public class FTPClientHelper { + + private FTPClient ftpClient; + + public boolean connect(String host, int port, String username, String password) { + ftpClient = new FTPClient(); + try { + ftpClient.connect(host, port); + int reply = ftpClient.getReplyCode(); + if (!FTPReply.isPositiveCompletion(reply)) { + ftpClient.disconnect(); + return false; + } + if (!ftpClient.login(username, password)) { + return false; + } + ftpClient.enterLocalPassiveMode(); + return true; + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + + public void disconnect() { + if (ftpClient != null && ftpClient.isConnected()) { + try { + ftpClient.logout(); + ftpClient.disconnect(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + public FTPFile[] listFiles(String path) { + try { + // First, try to change the working directory. + if (ftpClient.changeWorkingDirectory(path)) { + // If successful, list files in the new directory. + return ftpClient.listFiles(); + } else { + // If changing directory fails, it might be a file path, not a directory. + // Or the path is invalid. For now, we return null for simplicity. + return null; + } + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/be/gyu/android/file/explorer/FileAdapter.java b/app/src/main/java/be/gyu/android/file/explorer/FileAdapter.java index e0fc639..32c8e70 100644 --- a/app/src/main/java/be/gyu/android/file/explorer/FileAdapter.java +++ b/app/src/main/java/be/gyu/android/file/explorer/FileAdapter.java @@ -12,6 +12,15 @@ import java.util.List; public class FileAdapter extends RecyclerView.Adapter { private final List fileList; + private OnItemClickListener listener; + + public interface OnItemClickListener { + void onItemClick(FileItem item); + } + + public void setOnItemClickListener(OnItemClickListener listener) { + this.listener = listener; + } public FileAdapter(List fileList) { this.fileList = fileList; @@ -33,6 +42,12 @@ public class FileAdapter extends RecyclerView.Adapter { + if (listener != null) { + listener.onItemClick(fileItem); + } + }); } @Override diff --git a/app/src/main/java/be/gyu/android/file/explorer/MainActivity.java b/app/src/main/java/be/gyu/android/file/explorer/MainActivity.java index 2bf8356..668fccb 100644 --- a/app/src/main/java/be/gyu/android/file/explorer/MainActivity.java +++ b/app/src/main/java/be/gyu/android/file/explorer/MainActivity.java @@ -25,18 +25,29 @@ import androidx.core.content.FileProvider; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import org.apache.commons.net.ftp.FTPFile; + import java.io.File; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity implements FileAdapter.OnItemClickListener { + // Common private static final int REQUEST_CODE_MANAGE_EXTERNAL_STORAGE = 1; private RecyclerView recyclerView; private FileAdapter fileAdapter; private List fileList; + + // Local Mode private File currentDirectory; + // Remote (FTP) Mode + private boolean isRemoteMode = false; + private RemoteServer remoteServer; + private FTPClientHelper ftpHelper; + private String currentRemotePath; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -53,13 +64,141 @@ public class MainActivity extends AppCompatActivity implements FileAdapter.OnIte fileAdapter.setOnItemClickListener(this); recyclerView.setAdapter(fileAdapter); - if (checkStoragePermission()) { - loadFiles(Environment.getExternalStorageDirectory()); + Intent intent = getIntent(); + if (intent != null && intent.hasExtra("remote_server")) { + isRemoteMode = true; + remoteServer = (RemoteServer) intent.getSerializableExtra("remote_server"); + ftpHelper = new FTPClientHelper(); + connectAndLoadFtpFiles(); } else { - requestStoragePermission(); + isRemoteMode = false; + if (checkStoragePermission()) { + loadFiles(Environment.getExternalStorageDirectory()); + } else { + requestStoragePermission(); + } } } + // --- FTP Methods --- + private void connectAndLoadFtpFiles() { + setTitle("Connecting to " + remoteServer.getHost()); + new Thread(() -> { + boolean success = ftpHelper.connect(remoteServer.getHost(), remoteServer.getPort(), remoteServer.getUsername(), remoteServer.getPassword()); + runOnUiThread(() -> { + if (success) { + Toast.makeText(MainActivity.this, "Connected", Toast.LENGTH_SHORT).show(); + loadFtpFiles("/"); + } else { + Toast.makeText(MainActivity.this, "Connection Failed", Toast.LENGTH_LONG).show(); + finish(); // Close activity if connection fails + } + }); + }).start(); + } + + private void loadFtpFiles(String path) { + this.currentRemotePath = path; + setTitle(path); + new Thread(() -> { + FTPFile[] files = ftpHelper.listFiles(path); + runOnUiThread(() -> { + fileList.clear(); + if (files != null) { + for (FTPFile file : files) { + if (file == null || file.getName() == null || file.getName().equals(".") || file.getName().equals("..")) continue; + String fullPath = path.equals("/") ? "/" + file.getName() : path + "/" + file.getName(); + fileList.add(new FileItem(file.getName(), fullPath, file.isDirectory())); + } + } else { + Toast.makeText(this, "Cannot read this directory!", Toast.LENGTH_SHORT).show(); + } + fileAdapter.notifyDataSetChanged(); + }); + }).start(); + } + + // --- Local Storage Methods --- + private void loadFiles(File directory) { + this.currentDirectory = directory; + if (directory != null) { + setTitle(directory.getName()); + fileList.clear(); + File[] files = directory.listFiles(); + if (files != null) { + for (File file : files) { + fileList.add(new FileItem(file.getName(), file.getAbsolutePath(), file.isDirectory())); + } + } else { + Toast.makeText(this, "Cannot read this directory!", Toast.LENGTH_SHORT).show(); + } + } + fileAdapter.notifyDataSetChanged(); + } + + // --- Common Overridden Methods --- + @Override + public void onItemClick(FileItem item) { + if (isRemoteMode) { + if (item.isDirectory()) { + loadFtpFiles(item.getPath()); + } else { + // TODO: Implement file download/open for FTP + Toast.makeText(this, "Opening remote files is not implemented yet.", Toast.LENGTH_SHORT).show(); + } + } else { + File file = new File(item.getPath()); + if (file.isDirectory()) { + if (file.canRead()) { + loadFiles(file); + } else { + Toast.makeText(this, "Permission Denied", Toast.LENGTH_SHORT).show(); + } + } else { + openFile(file); + } + } + } + + @Override + public void onBackPressed() { + if (isRemoteMode) { + if (currentRemotePath != null && !currentRemotePath.equals("/")) { + int lastSlash = currentRemotePath.lastIndexOf('/'); + String parentPath = (lastSlash > 0) ? currentRemotePath.substring(0, lastSlash) : "/"; + loadFtpFiles(parentPath); + } else { + super.onBackPressed(); + } + } else { + // Logic for local storage back press + StorageManager storageManager = (StorageManager) getSystemService(STORAGE_SERVICE); + List storageVolumes = storageManager.getStorageVolumes(); + boolean isAtRootOfAVolume = false; + for (StorageVolume volume : storageVolumes) { + File volumePath = volume.getDirectory(); + if (volumePath != null && currentDirectory.equals(volumePath)) { + isAtRootOfAVolume = true; + break; + } + } + if (currentDirectory != null && !isAtRootOfAVolume) { + loadFiles(currentDirectory.getParentFile()); + } else { + super.onBackPressed(); + } + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (isRemoteMode && ftpHelper != null) { + new Thread(() -> ftpHelper.disconnect()).start(); + } + } + + // --- Menu Methods --- @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main_menu, menu); @@ -68,13 +207,20 @@ public class MainActivity extends AppCompatActivity implements FileAdapter.OnIte @Override public boolean onOptionsItemSelected(@NonNull MenuItem item) { - if (item.getItemId() == R.id.select_storage) { + int itemId = item.getItemId(); + if (itemId == R.id.select_storage) { showStorageSelectionDialog(); return true; + } else if (itemId == R.id.remote_storage) { + Intent intent = new Intent(this, RemoteStorageActivity.class); + startActivity(intent); + return true; } return super.onOptionsItemSelected(item); } + // --- Boilerplate Permission/File Handling --- + private void showStorageSelectionDialog() { StorageManager storageManager = (StorageManager) getSystemService(STORAGE_SERVICE); List storageVolumes = storageManager.getStorageVolumes(); @@ -99,7 +245,19 @@ public class MainActivity extends AppCompatActivity implements FileAdapter.OnIte builder.show(); } - + private void openFile(File file) { + Uri uri = FileProvider.getUriForFile(this, getApplicationContext().getPackageName() + ".provider", file); + Intent intent = new Intent(Intent.ACTION_VIEW); + String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(MimeTypeMap.getFileExtensionFromUrl(uri.toString())); + intent.setDataAndType(uri, mime); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + try { + startActivity(intent); + } catch (Exception e) { + Toast.makeText(this, "Cannot open file", Toast.LENGTH_SHORT).show(); + } + } + private boolean checkStoragePermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { return Environment.isExternalStorageManager(); @@ -125,75 +283,10 @@ public class MainActivity extends AppCompatActivity implements FileAdapter.OnIte } } - private void loadFiles(File directory) { - this.currentDirectory = directory; - if (directory != null) { - setTitle(directory.getName()); - fileList.clear(); - File[] files = directory.listFiles(); - if (files != null) { - for (File file : files) { - fileList.add(new FileItem(file.getName(), file.getAbsolutePath(), file.isDirectory())); - } - } else { - Toast.makeText(this, "Cannot read this directory!", Toast.LENGTH_SHORT).show(); - } - } - fileAdapter.notifyDataSetChanged(); - } - - @Override - public void onItemClick(FileItem item) { - File file = new File(item.getPath()); - if (file.isDirectory()) { - if (file.canRead()) { - loadFiles(file); - } else { - Toast.makeText(this, "Permission Denied", Toast.LENGTH_SHORT).show(); - } - } else { - openFile(file); - } - } - - private void openFile(File file) { - Uri uri = FileProvider.getUriForFile(this, getApplicationContext().getPackageName() + ".provider", file); - Intent intent = new Intent(Intent.ACTION_VIEW); - String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(MimeTypeMap.getFileExtensionFromUrl(uri.toString())); - intent.setDataAndType(uri, mime); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - try { - startActivity(intent); - } catch (Exception e) { - Toast.makeText(this, "Cannot open file", Toast.LENGTH_SHORT).show(); - } - } - - @Override - public void onBackPressed() { - // Check against all storage volume roots - StorageManager storageManager = (StorageManager) getSystemService(STORAGE_SERVICE); - List storageVolumes = storageManager.getStorageVolumes(); - boolean isAtRootOfAVolume = false; - for (StorageVolume volume : storageVolumes) { - File volumePath = volume.getDirectory(); - if (volumePath != null && currentDirectory.equals(volumePath)) { - isAtRootOfAVolume = true; - break; - } - } - - if (currentDirectory != null && !isAtRootOfAVolume) { - loadFiles(currentDirectory.getParentFile()); - } else { - super.onBackPressed(); - } - } - @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); - if (requestCode == REQUEST_CODE_MANAGE_EXTERNAL_STORAGE) { + if (requestCode == REQUEST_CODE_MANAGE_EXTERNAL_STORAGE && !isRemoteMode) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { loadFiles(Environment.getExternalStorageDirectory()); } else { @@ -205,7 +298,7 @@ public class MainActivity extends AppCompatActivity implements FileAdapter.OnIte @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - if (requestCode == REQUEST_CODE_MANAGE_EXTERNAL_STORAGE) { + if (requestCode == REQUEST_CODE_MANAGE_EXTERNAL_STORAGE && !isRemoteMode) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (Environment.isExternalStorageManager()) { loadFiles(Environment.getExternalStorageDirectory()); diff --git a/app/src/main/java/be/gyu/android/file/explorer/RemoteServer.java b/app/src/main/java/be/gyu/android/file/explorer/RemoteServer.java new file mode 100644 index 0000000..2fd7123 --- /dev/null +++ b/app/src/main/java/be/gyu/android/file/explorer/RemoteServer.java @@ -0,0 +1,60 @@ +package be.gyu.android.file.explorer; + +import java.io.Serializable; + +public class RemoteServer implements Serializable { + private long id; + private String host; + private int port; + private String username; + private String password; + + public RemoteServer(long id, String host, int port, String username, String password) { + this.id = id; + this.host = host; + this.port = port; + this.username = username; + this.password = password; + } + + // Getters and Setters + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} \ No newline at end of file diff --git a/app/src/main/java/be/gyu/android/file/explorer/RemoteServerAdapter.java b/app/src/main/java/be/gyu/android/file/explorer/RemoteServerAdapter.java new file mode 100644 index 0000000..360cd76 --- /dev/null +++ b/app/src/main/java/be/gyu/android/file/explorer/RemoteServerAdapter.java @@ -0,0 +1,63 @@ +package be.gyu.android.file.explorer; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import java.util.List; + +public class RemoteServerAdapter extends RecyclerView.Adapter { + + private final List serverList; + private OnItemClickListener listener; + + public interface OnItemClickListener { + void onItemClick(RemoteServer server); + } + + public void setOnItemClickListener(OnItemClickListener listener) { + this.listener = listener; + } + + public RemoteServerAdapter(List serverList) { + this.serverList = serverList; + } + + @NonNull + @Override + public ServerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_remote_server, parent, false); + return new ServerViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull ServerViewHolder holder, int position) { + RemoteServer server = serverList.get(position); + holder.host.setText(server.getHost()); + holder.username.setText(server.getUsername()); + + holder.itemView.setOnClickListener(v -> { + if (listener != null) { + listener.onItemClick(server); + } + }); + } + + @Override + public int getItemCount() { + return serverList.size(); + } + + static class ServerViewHolder extends RecyclerView.ViewHolder { + TextView host; + TextView username; + + public ServerViewHolder(@NonNull View itemView) { + super(itemView); + host = itemView.findViewById(R.id.server_host); + username = itemView.findViewById(R.id.server_username); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/be/gyu/android/file/explorer/RemoteStorageActivity.java b/app/src/main/java/be/gyu/android/file/explorer/RemoteStorageActivity.java new file mode 100644 index 0000000..ec51440 --- /dev/null +++ b/app/src/main/java/be/gyu/android/file/explorer/RemoteStorageActivity.java @@ -0,0 +1,92 @@ +package be.gyu.android.file.explorer; + +import android.content.Intent; +import android.os.Bundle; +import android.text.InputType; +import android.widget.EditText; +import android.widget.LinearLayout; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import java.util.List; + +public class RemoteStorageActivity extends AppCompatActivity implements RemoteServerAdapter.OnItemClickListener { + + private DbHelper dbHelper; + private RemoteServerAdapter adapter; + private List serverList; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_remote_storage); + + dbHelper = new DbHelper(this); + serverList = dbHelper.getAllServers(); + + RecyclerView recyclerView = findViewById(R.id.remote_storage_recycler_view); + recyclerView.setLayoutManager(new LinearLayoutManager(this)); + adapter = new RemoteServerAdapter(serverList); + adapter.setOnItemClickListener(this); + recyclerView.setAdapter(adapter); + + FloatingActionButton fab = findViewById(R.id.fab_add_remote_storage); + fab.setOnClickListener(view -> showAddServerDialog()); + } + + @Override + public void onItemClick(RemoteServer server) { + Intent intent = new Intent(this, MainActivity.class); + intent.putExtra("remote_server", server); + startActivity(intent); + } + + private void showAddServerDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Add FTP Server"); + + LinearLayout layout = new LinearLayout(this); + layout.setOrientation(LinearLayout.VERTICAL); + layout.setPadding(50, 50, 50, 50); + + final EditText hostInput = new EditText(this); + hostInput.setHint("Host"); + layout.addView(hostInput); + + final EditText portInput = new EditText(this); + portInput.setHint("Port (default: 21)"); + portInput.setInputType(InputType.TYPE_CLASS_NUMBER); + layout.addView(portInput); + + final EditText usernameInput = new EditText(this); + usernameInput.setHint("Username"); + layout.addView(usernameInput); + + final EditText passwordInput = new EditText(this); + passwordInput.setHint("Password"); + passwordInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + layout.addView(passwordInput); + + builder.setView(layout); + + builder.setPositiveButton("Save", (dialog, which) -> { + String host = hostInput.getText().toString(); + int port = portInput.getText().toString().isEmpty() ? 21 : Integer.parseInt(portInput.getText().toString()); + String username = usernameInput.getText().toString(); + String password = passwordInput.getText().toString(); + + RemoteServer server = new RemoteServer(0, host, port, username, password); + dbHelper.addServer(server); + + // Refresh the list + serverList.clear(); + serverList.addAll(dbHelper.getAllServers()); + adapter.notifyDataSetChanged(); + }); + builder.setNegativeButton("Cancel", (dialog, which) -> dialog.cancel()); + + builder.show(); + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_add.xml b/app/src/main/res/drawable/ic_add.xml new file mode 100644 index 0000000..2d79abf --- /dev/null +++ b/app/src/main/res/drawable/ic_add.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_remote_storage.xml b/app/src/main/res/layout/activity_remote_storage.xml new file mode 100644 index 0000000..f559675 --- /dev/null +++ b/app/src/main/res/layout/activity_remote_storage.xml @@ -0,0 +1,24 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_item_remote_server.xml b/app/src/main/res/layout/list_item_remote_server.xml new file mode 100644 index 0000000..3ec236a --- /dev/null +++ b/app/src/main/res/layout/list_item_remote_server.xml @@ -0,0 +1,20 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/main_menu.xml b/app/src/main/res/menu/main_menu.xml index 240c3c6..3e615e6 100644 --- a/app/src/main/res/menu/main_menu.xml +++ b/app/src/main/res/menu/main_menu.xml @@ -6,4 +6,9 @@ android:title="Select Storage" android:icon="@drawable/ic_storage" app:showAsAction="ifRoom" /> + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..3d5a473 --- /dev/null +++ b/app/src/main/res/values-night/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..f8c6127 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..d753e7a --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Gyub_s File Explorer + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..d4f680f --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml new file mode 100644 index 0000000..4df9255 --- /dev/null +++ b/app/src/main/res/xml/backup_rules.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml new file mode 100644 index 0000000..9ee9997 --- /dev/null +++ b/app/src/main/res/xml/data_extraction_rules.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/test/java/be/gyu/android/file/explorer/ExampleUnitTest.java b/app/src/test/java/be/gyu/android/file/explorer/ExampleUnitTest.java new file mode 100644 index 0000000..463428f --- /dev/null +++ b/app/src/test/java/be/gyu/android/file/explorer/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package be.gyu.android.file.explorer; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..3756278 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,4 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +plugins { + alias(libs.plugins.android.application) apply false +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..4387edc --- /dev/null +++ b/gradle.properties @@ -0,0 +1,21 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. For more details, visit +# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..c3aac0a --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,18 @@ +[versions] +agp = "8.13.2" +junit = "4.13.2" +junitVersion = "1.1.5" +espressoCore = "3.5.1" +appcompat = "1.6.1" +material = "1.10.0" + +[libraries] +junit = { group = "junit", name = "junit", version.ref = "junit" } +ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } +espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } +appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } +material = { group = "com.google.android.material", name = "material", version.ref = "material" } + +[plugins] +android-application = { id = "com.android.application", version.ref = "agp" } + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..8bdaf60 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ca23608 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,8 @@ +#Sun Jan 04 22:03:06 KST 2026 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..ef07e01 --- /dev/null +++ b/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..db3a6ac --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..a1a6054 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,24 @@ +pluginManagement { + repositories { + google { + content { + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.google.*") + includeGroupByRegex("androidx.*") + } + } + mavenCentral() + gradlePluginPortal() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "Gyub_s File Explorer" +include(":app") + \ No newline at end of file