diff --git a/app/build.gradle b/app/build.gradle
index bd81d3d13..43cc1c9bc 100755
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -109,12 +109,11 @@ dependencies {
implementation common.editor
implementation common.utilcode
implementation common.glide
+ implementation common.jsoup
implementation common.quickaction
implementation google.gson
implementation google.guava
- implementation common.jsoup
-
// AndroidX
implementation androidx.splashscreen
implementation androidx.annotation
diff --git a/app/src/main/java/com/itsaky/androidide/EditorActivity.java b/app/src/main/java/com/itsaky/androidide/EditorActivity.java
index adeef7a6c..8268ab5d7 100755
--- a/app/src/main/java/com/itsaky/androidide/EditorActivity.java
+++ b/app/src/main/java/com/itsaky/androidide/EditorActivity.java
@@ -20,11 +20,11 @@
package com.itsaky.androidide;
import static com.blankj.utilcode.util.IntentUtils.getShareTextIntent;
+import static com.itsaky.androidide.preferences.internal.GeneralPreferencesKt.NO_OPENED_PROJECT;
+import static com.itsaky.androidide.preferences.internal.GeneralPreferencesKt.setLastOpenedProject;
import static com.itsaky.androidide.resources.R.color;
import static com.itsaky.androidide.resources.R.drawable;
import static com.itsaky.androidide.resources.R.string;
-import static com.itsaky.androidide.preferences.internal.GeneralPreferencesKt.NO_OPENED_PROJECT;
-import static com.itsaky.androidide.preferences.internal.GeneralPreferencesKt.setLastOpenedProject;
import static com.itsaky.toaster.ToastUtilsKt.toast;
import android.annotation.SuppressLint;
@@ -53,6 +53,7 @@ import android.view.ViewTreeObserver;
import android.widget.CheckBox;
import android.widget.LinearLayout;
+import androidx.activity.OnBackPressedCallback;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
@@ -65,7 +66,6 @@ import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.view.menu.MenuBuilder;
import androidx.core.content.ContextCompat;
import androidx.core.view.GravityCompat;
-import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import com.blankj.utilcode.util.FileUtils;
@@ -92,6 +92,7 @@ import com.itsaky.androidide.fragments.sheets.ProgressSheet;
import com.itsaky.androidide.fragments.sheets.TextSheetFragment;
import com.itsaky.androidide.handlers.EditorActivityLifecyclerObserver;
import com.itsaky.androidide.handlers.EditorEventListener;
+import com.itsaky.androidide.handlers.EditorInsetsHandler;
import com.itsaky.androidide.interfaces.DiagnosticClickListener;
import com.itsaky.androidide.interfaces.EditorActivityProvider;
import com.itsaky.androidide.lookup.Lookup;
@@ -196,6 +197,24 @@ public class EditorActivity extends IDEActivity
LOG.info("Disconnected from Gradle build service...");
}
};
+
+ private final OnBackPressedCallback onBackPressedCallback =
+ new OnBackPressedCallback(true) {
+ @Override
+ public void handleOnBackPressed() {
+ if (mBinding.getRoot().isDrawerOpen(GravityCompat.END)) {
+ mBinding.getRoot().closeDrawer(GravityCompat.END);
+ } else if (mBinding.getRoot().isDrawerOpen(GravityCompat.START)) {
+ mBinding.getRoot().closeDrawer(GravityCompat.START);
+ } else if (getDaemonStatusFragment().isShowing()) {
+ getDaemonStatusFragment().dismiss();
+ } else if (mEditorBottomSheet.getState() != BottomSheetBehavior.STATE_COLLAPSED) {
+ mEditorBottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED);
+ } else {
+ confirmProjectClose();
+ }
+ }
+ };
private boolean isFinishing = false;
@SuppressLint("RestrictedApi")
@@ -312,8 +331,8 @@ public class EditorActivity extends IDEActivity
.bottomSheet
.getPagerAdapter()
.findIndexOfFragmentByClass(SearchResultFragment.class);
- if (index >= 0 && index < mBinding.bottomSheet.getTabs().getTabCount()) {
- final var tab = mBinding.bottomSheet.getTabs().getTabAt(index);
+ if (index >= 0 && index < mBinding.bottomSheet.binding.tabs.getTabCount()) {
+ final var tab = mBinding.bottomSheet.binding.tabs.getTabAt(index);
if (tab != null) {
tab.select();
}
@@ -482,6 +501,14 @@ public class EditorActivity extends IDEActivity
return mBinding;
}
+ public FileTreeFragment getFileTreeFragment() {
+ if (mFileTreeFragment == null) {
+ mFileTreeFragment =
+ (FileTreeFragment) getSupportFragmentManager().findFragmentByTag(FileTreeFragment.TAG);
+ }
+ return mFileTreeFragment;
+ }
+
@Override
public void onTabSelected(@NonNull TabLayout.Tab tab) {
final var position = tab.getPosition();
@@ -532,10 +559,6 @@ public class EditorActivity extends IDEActivity
return false;
}
- public void loadFragment(Fragment fragment) {
- super.loadFragment(fragment, mBinding.editorFrameLayout.getId());
- }
-
public ProgressSheet getProgressSheet(int msg) {
if (mSearchingProgress != null && mSearchingProgress.isShowing()) {
mSearchingProgress.dismiss();
@@ -852,25 +875,24 @@ public class EditorActivity extends IDEActivity
@Override
protected void onCreate(Bundle savedInstanceState) {
+ fitsSystemWindows = true;
super.onCreate(savedInstanceState);
-
+ EditorInsetsHandler.setupInsets(this);
ILanguageServerRegistry.getDefault().register(new JavaLanguageServer());
ILanguageServerRegistry.getDefault().register(new XMLLanguageServer());
if (savedInstanceState != null && savedInstanceState.containsKey(KEY_PROJECT_PATH)) {
ProjectManager.INSTANCE.setProjectPath(savedInstanceState.getString(KEY_PROJECT_PATH));
}
+ getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback);
getLifecycle().addObserver(mLifecycleObserver);
setSupportActionBar(mBinding.editorToolbar);
mViewModel = new ViewModelProvider(this).get(EditorViewModel.class);
-
- mFileTreeFragment = FileTreeFragment.newInstance();
mDaemonStatusFragment = new TextSheetFragment().setTextSelectable(true);
setupDrawerToggle();
- loadFragment(mFileTreeFragment);
mBinding.tabs.addOnTabSelectedListener(this);
setupViews();
@@ -903,8 +925,9 @@ public class EditorActivity extends IDEActivity
protected void onPause() {
super.onPause();
- if (mFileTreeFragment != null) {
- mFileTreeFragment.saveTreeState();
+ final var frag = getFileTreeFragment();
+ if (frag != null) {
+ frag.saveTreeState();
}
}
@@ -918,8 +941,9 @@ public class EditorActivity extends IDEActivity
invalidateOptionsMenu();
try {
- if (mFileTreeFragment != null) {
- mFileTreeFragment.listProjectFiles();
+ final var frag = getFileTreeFragment();
+ if (frag != null) {
+ frag.listProjectFiles();
}
} catch (Throwable th) {
LOG.error("Failed to update files list", th);
@@ -933,21 +957,6 @@ public class EditorActivity extends IDEActivity
super.onSaveInstanceState(outState);
}
- @Override
- public void onBackPressed() {
- if (mBinding.getRoot().isDrawerOpen(GravityCompat.END)) {
- mBinding.getRoot().closeDrawer(GravityCompat.END);
- } else if (mBinding.getRoot().isDrawerOpen(GravityCompat.START)) {
- mBinding.getRoot().closeDrawer(GravityCompat.START);
- } else if (getDaemonStatusFragment().isShowing()) {
- getDaemonStatusFragment().dismiss();
- } else if (mEditorBottomSheet.getState() == BottomSheetBehavior.STATE_EXPANDED) {
- mEditorBottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED);
- } else {
- confirmProjectClose();
- }
- }
-
@Override
protected void onDestroy() {
isFinishing = true;
@@ -1189,16 +1198,12 @@ public class EditorActivity extends IDEActivity
mEditorBottomSheet =
(EditorBottomSheetBehavior extends View>)
EditorBottomSheetBehavior.from(mBinding.bottomSheet);
- mEditorBottomSheet.setBinding(mBinding.bottomSheet.getPager());
+ mEditorBottomSheet.setBinding(mBinding.bottomSheet.binding.pager);
mEditorBottomSheet.addBottomSheetCallback(
new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
- mBinding
- .bottomSheet
- .getHeaderContainer()
- .setVisibility(
- newState == BottomSheetBehavior.STATE_EXPANDED ? View.INVISIBLE : View.VISIBLE);
+ mBinding.bottomSheet.onStateChanged(newState);
if (newState == BottomSheetBehavior.STATE_EXPANDED) {
final var editor = getCurrentEditor();
if (editor != null && editor.getEditor() != null) {
@@ -1209,23 +1214,19 @@ public class EditorActivity extends IDEActivity
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
- mBinding.bottomSheet.getHeaderContainer().setAlpha(1f - slideOffset);
-
+ mBinding.bottomSheet.onSlide(slideOffset);
final var editorScale = 1 - (slideOffset * (1 - EDITOR_CONTAINER_SCALE_FACTOR));
mBinding.viewContainer.setScaleX(editorScale);
mBinding.viewContainer.setScaleY(editorScale);
}
});
- mBinding
- .bottomSheet
- .getHeaderContainer()
- .setOnClickListener(
- v -> {
- if (mEditorBottomSheet.getState() != BottomSheetBehavior.STATE_EXPANDED) {
- mEditorBottomSheet.setState(BottomSheetBehavior.STATE_EXPANDED);
- }
- });
+ mBinding.bottomSheet.binding.headerContainer.setOnClickListener(
+ v -> {
+ if (mEditorBottomSheet.getState() != BottomSheetBehavior.STATE_EXPANDED) {
+ mEditorBottomSheet.setState(BottomSheetBehavior.STATE_EXPANDED);
+ }
+ });
final var observer =
new ViewTreeObserver.OnGlobalLayoutListener() {
diff --git a/app/src/main/java/com/itsaky/androidide/MainActivity.java b/app/src/main/java/com/itsaky/androidide/MainActivity.java
deleted file mode 100755
index 66220fcd8..000000000
--- a/app/src/main/java/com/itsaky/androidide/MainActivity.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * This file is part of AndroidIDE.
- *
- *
- *
- * AndroidIDE is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * AndroidIDE is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with AndroidIDE. If not, see .
- *
- */
-
-package com.itsaky.androidide;
-
-import static com.itsaky.androidide.R.id;
-import static com.itsaky.androidide.resources.R.string;
-import static com.itsaky.androidide.preferences.internal.GeneralPreferencesKt.NO_OPENED_PROJECT;
-import static com.itsaky.androidide.preferences.internal.GeneralPreferencesKt.getAutoOpenProjects;
-import static com.itsaky.androidide.preferences.internal.GeneralPreferencesKt.getConfirmProjectOpen;
-import static com.itsaky.androidide.preferences.internal.GeneralPreferencesKt.getLastOpenedProject;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.text.TextUtils;
-import android.text.method.LinkMovementMethod;
-import android.view.View;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.core.splashscreen.SplashScreen;
-import androidx.core.text.HtmlCompat;
-
-import com.blankj.utilcode.util.SizeUtils;
-import com.google.android.material.dialog.MaterialAlertDialogBuilder;
-import com.itsaky.androidide.app.IDEActivity;
-import com.itsaky.androidide.app.IDEApplication;
-import com.itsaky.androidide.databinding.ActivityMainBinding;
-import com.itsaky.androidide.fragments.MainFragment;
-import com.itsaky.androidide.projects.ProjectManager;
-import com.itsaky.androidide.utils.DialogUtils;
-import com.itsaky.androidide.utils.Environment;
-import com.itsaky.toaster.Toaster;
-import com.itsaky.toaster.ToastUtilsKt;
-
-import java.io.File;
-
-public class MainActivity extends IDEActivity {
- private ActivityMainBinding binding;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- if (!IDEApplication.isAbiSupported()) {
- showDeviceNotSupported();
- return;
- }
-
- if (!checkToolsIsInstalled()) {
- showDialogInstallJdkSdk();
- } else {
- openLastProject();
- }
-
- getSupportFragmentManager()
- .beginTransaction()
- .replace(id.container, new MainFragment(), MainFragment.TAG)
- .commit();
- }
-
- @Override
- protected void onStorageGranted() {}
-
- @Override
- protected void onStorageDenied() {
- ToastUtilsKt.toast(string.msg_storage_denied, Toaster.Type.ERROR);
- finishAffinity();
- }
-
- @Override
- protected void preSetContentLayout() {
- SplashScreen.installSplashScreen(this);
- }
-
- @Override
- protected View bindLayout() {
- binding = ActivityMainBinding.inflate(getLayoutInflater());
- return binding.getRoot();
- }
-
- private void showDialogInstallJdkSdk() {
- final var dp24 = SizeUtils.dp2px(24);
- final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this);
- builder.setTitle(string.title_warning);
- final var view = new TextView(this);
- view.setPaddingRelative(dp24, dp24, dp24, dp24);
- view.setText(
- HtmlCompat.fromHtml(
- getString(string.msg_require_install_jdk_and_android_sdk),
- HtmlCompat.FROM_HTML_MODE_COMPACT));
- view.setMovementMethod(LinkMovementMethod.getInstance());
- builder.setView(view);
- builder.setCancelable(false);
- builder.setPositiveButton(android.R.string.ok, (d, w) -> openTerminal());
- builder.setNegativeButton(android.R.string.cancel, (d, w) -> finishAffinity());
- builder.setNeutralButton(string.btn_docs, (d, w) -> getApp().openDocs());
- builder.show();
- }
-
- private void openTerminal() {
- startActivity(new Intent(this, TerminalActivity.class));
- }
-
- private void showDeviceNotSupported() {
- final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this);
- builder.setTitle(string.title_device_not_supported);
- builder.setMessage(string.msg_device_not_supported);
- builder.setCancelable(false);
- builder.setPositiveButton(android.R.string.ok, (p1, p2) -> finishAffinity());
- builder.create().show();
- }
-
- private void openLastProject() {
- binding.getRoot().post(this::tryOpenLastProject);
- }
-
- private void tryOpenLastProject() {
- if (!getAutoOpenProjects()) {
- return;
- }
-
- final var openedProject = getLastOpenedProject();
- if (NO_OPENED_PROJECT.equals(openedProject)) {
- return;
- }
-
- if (TextUtils.isEmpty(openedProject)) {
- getApp();
- ToastUtilsKt.toast(string.msg_opened_project_does_not_exist, Toaster.Type.INFO);
- return;
- }
-
- final var project = new File(openedProject);
- if (!project.exists()) {
- ToastUtilsKt.toast(string.msg_opened_project_does_not_exist, Toaster.Type.INFO);
- return;
- }
-
- if (getConfirmProjectOpen()) {
- askProjectOpenPermission(project);
- return;
- }
-
- openProject(project);
- }
-
- private void askProjectOpenPermission(File root) {
- final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this);
- builder.setTitle(string.title_confirm_open_project);
- builder.setMessage(getString(string.msg_confirm_open_project, root.getAbsolutePath()));
- builder.setCancelable(false);
- builder.setPositiveButton(string.yes, (d, w) -> openProject(root));
- builder.setNegativeButton(string.no, null);
- builder.show();
- }
-
- public void openProject(@NonNull File root) {
- ProjectManager.INSTANCE.setProjectPath(root.getAbsolutePath());
- startActivity(new Intent(this, EditorActivity.class));
- }
-
- private boolean checkToolsIsInstalled() {
- return Environment.JAVA.exists() && Environment.ANDROID_HOME.exists();
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- binding = null;
- }
-}
diff --git a/app/src/main/java/com/itsaky/androidide/MainActivity.kt b/app/src/main/java/com/itsaky/androidide/MainActivity.kt
new file mode 100755
index 000000000..a41fcc9be
--- /dev/null
+++ b/app/src/main/java/com/itsaky/androidide/MainActivity.kt
@@ -0,0 +1,174 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ *
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ *
+ */
+package com.itsaky.androidide
+
+import android.R
+import android.content.Intent
+import android.os.Bundle
+import android.text.TextUtils
+import android.text.method.LinkMovementMethod
+import android.view.View
+import android.view.ViewGroup.MarginLayoutParams
+import android.widget.TextView
+import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
+import androidx.core.text.HtmlCompat
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.updateLayoutParams
+import androidx.core.view.updateMargins
+import com.blankj.utilcode.util.SizeUtils
+import com.itsaky.androidide.R.id
+import com.itsaky.androidide.app.IDEActivity
+import com.itsaky.androidide.app.IDEApplication
+import com.itsaky.androidide.databinding.ActivityMainBinding
+import com.itsaky.androidide.fragments.MainFragment
+import com.itsaky.androidide.preferences.internal.NO_OPENED_PROJECT
+import com.itsaky.androidide.preferences.internal.autoOpenProjects
+import com.itsaky.androidide.preferences.internal.confirmProjectOpen
+import com.itsaky.androidide.preferences.internal.lastOpenedProject
+import com.itsaky.androidide.projects.ProjectManager.projectPath
+import com.itsaky.androidide.resources.R.string
+import com.itsaky.androidide.utils.DialogUtils
+import com.itsaky.androidide.utils.Environment
+import com.itsaky.toaster.Toaster.Type.ERROR
+import com.itsaky.toaster.Toaster.Type.INFO
+import com.itsaky.toaster.toast
+import java.io.File
+
+class MainActivity : IDEActivity() {
+ private var binding: ActivityMainBinding? = null
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ if (!IDEApplication.isAbiSupported()) {
+ showDeviceNotSupported()
+ return
+ }
+ if (!checkToolsIsInstalled()) {
+ showDialogInstallJdkSdk()
+ } else {
+ openLastProject()
+ }
+ supportFragmentManager
+ .beginTransaction()
+ .replace(id.container, MainFragment(), MainFragment.TAG)
+ .commit()
+ }
+
+ override fun onStorageGranted() {}
+ override fun onStorageDenied() {
+ toast(string.msg_storage_denied, ERROR)
+ finishAffinity()
+ }
+
+ override fun preSetContentLayout() {
+ installSplashScreen()
+ }
+
+ override fun bindLayout(): View {
+ binding = ActivityMainBinding.inflate(layoutInflater)
+ return binding!!.root
+ }
+
+ private fun showDialogInstallJdkSdk() {
+ val dp24 = SizeUtils.dp2px(24f)
+ val builder = DialogUtils.newMaterialDialogBuilder(this)
+ builder.setTitle(string.title_warning)
+ val view = TextView(this)
+ view.setPaddingRelative(dp24, dp24, dp24, dp24)
+ view.text =
+ HtmlCompat.fromHtml(
+ getString(string.msg_require_install_jdk_and_android_sdk),
+ HtmlCompat.FROM_HTML_MODE_COMPACT
+ )
+ view.movementMethod = LinkMovementMethod.getInstance()
+ builder.setView(view)
+ builder.setCancelable(false)
+ builder.setPositiveButton(R.string.ok) { _, _ -> openTerminal() }
+ builder.setNegativeButton(R.string.cancel) { _, _ -> finishAffinity() }
+ builder.setNeutralButton(string.btn_docs) { _, _ -> app.openDocs() }
+ builder.show()
+ }
+
+ private fun openTerminal() {
+ startActivity(Intent(this, TerminalActivity::class.java))
+ }
+
+ private fun showDeviceNotSupported() {
+ val builder = DialogUtils.newMaterialDialogBuilder(this)
+ builder.setTitle(string.title_device_not_supported)
+ builder.setMessage(string.msg_device_not_supported)
+ builder.setCancelable(false)
+ builder.setPositiveButton(R.string.ok) { _, _ -> finishAffinity() }
+ builder.create().show()
+ }
+
+ private fun openLastProject() {
+ binding!!.root.post { tryOpenLastProject() }
+ }
+
+ private fun tryOpenLastProject() {
+ if (!autoOpenProjects) {
+ return
+ }
+ val openedProject = lastOpenedProject
+ if (NO_OPENED_PROJECT == openedProject) {
+ return
+ }
+ if (TextUtils.isEmpty(openedProject)) {
+ app
+ toast(string.msg_opened_project_does_not_exist, INFO)
+ return
+ }
+ val project = File(openedProject)
+ if (!project.exists()) {
+ toast(string.msg_opened_project_does_not_exist, INFO)
+ return
+ }
+ if (confirmProjectOpen) {
+ askProjectOpenPermission(project)
+ return
+ }
+ openProject(project)
+ }
+
+ private fun askProjectOpenPermission(root: File) {
+ val builder = DialogUtils.newMaterialDialogBuilder(this)
+ builder.setTitle(string.title_confirm_open_project)
+ builder.setMessage(getString(string.msg_confirm_open_project, root.absolutePath))
+ builder.setCancelable(false)
+ builder.setPositiveButton(string.yes) { _, _ -> openProject(root) }
+ builder.setNegativeButton(string.no, null)
+ builder.show()
+ }
+
+ private fun openProject(root: File) {
+ projectPath = root.absolutePath
+ startActivity(Intent(this, EditorActivity::class.java))
+ }
+
+ private fun checkToolsIsInstalled(): Boolean {
+ return Environment.JAVA.exists() && Environment.ANDROID_HOME.exists()
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ binding = null
+ }
+}
diff --git a/app/src/main/java/com/itsaky/androidide/app/IDEActivity.java b/app/src/main/java/com/itsaky/androidide/app/IDEActivity.java
deleted file mode 100755
index a80fdbfed..000000000
--- a/app/src/main/java/com/itsaky/androidide/app/IDEActivity.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/************************************************************************************
- * This file is part of AndroidIDE.
- *
- * AndroidIDE is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * AndroidIDE is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with AndroidIDE. If not, see .
- *
- **************************************************************************************/
-package com.itsaky.androidide.app;
-
-import android.Manifest;
-import android.content.pm.PackageManager;
-import android.os.Bundle;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.core.app.ActivityCompat;
-import androidx.core.content.ContextCompat;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentTransaction;
-
-import com.itsaky.androidide.utils.ILogger;
-
-public abstract class IDEActivity extends AppCompatActivity {
- public static final int REQCODE_STORAGE = 1009;
- protected static ILogger LOG = ILogger.newInstance("StudioActivity");
-
- public void loadFragment(Fragment fragment, int id) {
- FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
- transaction.replace(id, fragment);
- transaction.commit();
- }
-
- public IDEApplication getApp() {
- return (IDEApplication) getApplication();
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- preSetContentLayout();
- setContentView(bindLayout());
-
- if (!isStoragePermissionGranted()) {
- requestStorage();
- return;
- }
-
- if (isStoragePermissionGranted()) {
- onStorageAlreadyGranted();
- } else {
- onStorageDenied();
- }
- }
-
- public boolean isStoragePermissionGranted() {
- return ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
- == PackageManager.PERMISSION_GRANTED
- && ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
- == PackageManager.PERMISSION_GRANTED;
- }
-
- protected void requestStorage() {
- if (isStoragePermissionGranted()) {
- onStorageGranted();
- return;
- }
- ActivityCompat.requestPermissions(
- this,
- new String[] {
- Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE
- },
- REQCODE_STORAGE);
- }
-
- protected void onStorageGranted() {}
-
- @Override
- public void onRequestPermissionsResult(
- int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults);
- if (requestCode == REQCODE_STORAGE) {
- if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)
- onStorageGranted();
- else onStorageDenied();
- }
- }
-
- protected void onStorageAlreadyGranted() {}
-
- protected void onStorageDenied() {}
-
- protected void preSetContentLayout() {}
-
- protected abstract View bindLayout();
-}
diff --git a/app/src/main/java/com/itsaky/androidide/app/IDEActivity.kt b/app/src/main/java/com/itsaky/androidide/app/IDEActivity.kt
new file mode 100755
index 000000000..aa02affee
--- /dev/null
+++ b/app/src/main/java/com/itsaky/androidide/app/IDEActivity.kt
@@ -0,0 +1,101 @@
+/**
+ * ********************************************************************************** This file is
+ * part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License as published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with AndroidIDE. If not,
+ * see //www.gnu.org/licenses/>.
+ */
+package com.itsaky.androidide.app
+
+import android.Manifest.permission
+import android.content.pm.PackageManager
+import android.os.Bundle
+import android.view.View
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.app.ActivityCompat
+import androidx.core.content.ContextCompat
+import androidx.core.view.WindowCompat
+import androidx.fragment.app.Fragment
+import com.itsaky.androidide.utils.ILogger
+
+abstract class IDEActivity : AppCompatActivity() {
+ fun loadFragment(fragment: Fragment?, id: Int) {
+ val transaction = supportFragmentManager.beginTransaction()
+ transaction.replace(id, fragment!!)
+ transaction.commit()
+ }
+
+ val app: IDEApplication
+ get() = application as IDEApplication
+
+ @JvmField
+ var fitsSystemWindows = false
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ WindowCompat.setDecorFitsSystemWindows(window, !fitsSystemWindows)
+ preSetContentLayout()
+ setContentView(bindLayout())
+ if (!isStoragePermissionGranted) {
+ requestStorage()
+ return
+ }
+ if (isStoragePermissionGranted) {
+ onStorageAlreadyGranted()
+ } else {
+ onStorageDenied()
+ }
+ }
+
+ val isStoragePermissionGranted: Boolean
+ get() =
+ (ContextCompat.checkSelfPermission(this, permission.READ_EXTERNAL_STORAGE) ==
+ PackageManager.PERMISSION_GRANTED &&
+ ContextCompat.checkSelfPermission(this, permission.WRITE_EXTERNAL_STORAGE) ==
+ PackageManager.PERMISSION_GRANTED)
+
+ protected fun requestStorage() {
+ if (isStoragePermissionGranted) {
+ onStorageGranted()
+ return
+ }
+ ActivityCompat.requestPermissions(
+ this,
+ arrayOf(permission.WRITE_EXTERNAL_STORAGE, permission.READ_EXTERNAL_STORAGE),
+ REQCODE_STORAGE
+ )
+ }
+
+ protected open fun onStorageGranted() {}
+ override fun onRequestPermissionsResult(
+ requestCode: Int,
+ permissions: Array,
+ grantResults: IntArray
+ ) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ if (requestCode == REQCODE_STORAGE) {
+ if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)
+ onStorageGranted()
+ else onStorageDenied()
+ }
+ }
+
+ protected open fun onStorageAlreadyGranted() {}
+ protected open fun onStorageDenied() {}
+ protected open fun preSetContentLayout() {}
+
+ protected abstract fun bindLayout(): View
+
+ companion object {
+ const val REQCODE_STORAGE = 1009
+ protected var LOG = ILogger.newInstance("StudioActivity")
+ }
+}
diff --git a/app/src/main/java/com/itsaky/androidide/events/FileTree.kt b/app/src/main/java/com/itsaky/androidide/events/FileTree.kt
index 885a68ec2..5c6a66366 100644
--- a/app/src/main/java/com/itsaky/androidide/events/FileTree.kt
+++ b/app/src/main/java/com/itsaky/androidide/events/FileTree.kt
@@ -23,6 +23,6 @@ import com.unnamed.b.atv.model.TreeNode
internal data class FileContextMenuItemClickEvent(val option: SheetOption) : Event()
-internal data class ExpandTreeNodeRequestEvent(val node: TreeNode) : Event()
+data class ExpandTreeNodeRequestEvent(val node: TreeNode) : Event()
-internal class ListProjectFilesRequestEvent : Event()
+class ListProjectFilesRequestEvent : Event()
diff --git a/app/src/main/java/com/itsaky/androidide/fragments/FileTreeFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/FileTreeFragment.java
deleted file mode 100755
index 0b25f9d47..000000000
--- a/app/src/main/java/com/itsaky/androidide/fragments/FileTreeFragment.java
+++ /dev/null
@@ -1,328 +0,0 @@
-/*
- * This file is part of AndroidIDE.
- *
- * AndroidIDE is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * AndroidIDE is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with AndroidIDE. If not, see .
- *
- */
-package com.itsaky.androidide.fragments;
-
-import static com.unnamed.b.atv.view.AndroidTreeView.NODES_PATH_SEPARATOR;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.transition.ChangeBounds;
-import androidx.transition.TransitionManager;
-
-import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
-import com.itsaky.androidide.resources.R;
-import com.itsaky.androidide.adapters.viewholders.FileTreeViewHolder;
-import com.itsaky.androidide.databinding.LayoutEditorFileTreeBinding;
-import com.itsaky.androidide.eventbus.events.filetree.FileClickEvent;
-import com.itsaky.androidide.eventbus.events.filetree.FileLongClickEvent;
-import com.itsaky.androidide.events.ExpandTreeNodeRequestEvent;
-import com.itsaky.androidide.events.ListProjectFilesRequestEvent;
-import com.itsaky.androidide.projects.ProjectManager;
-import com.itsaky.androidide.tasks.TaskExecutor;
-import com.itsaky.androidide.tasks.callables.FileTreeCallable;
-import com.itsaky.androidide.utils.ILogger;
-import com.unnamed.b.atv.model.TreeNode;
-import com.unnamed.b.atv.view.AndroidTreeView;
-
-import org.greenrobot.eventbus.EventBus;
-import org.greenrobot.eventbus.Subscribe;
-import org.greenrobot.eventbus.ThreadMode;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-
-public class FileTreeFragment extends BottomSheetDialogFragment
- implements TreeNode.TreeNodeClickListener, TreeNode.TreeNodeLongClickListener {
-
- private static final String KEY_STORED_TREE_STATE = "fileTree_state";
- private static final ILogger LOG = ILogger.newInstance("FileTreeFragment");
- private LayoutEditorFileTreeBinding binding;
- private AndroidTreeView mFileTreeView;
- private TreeNode mRoot;
- private String mTreeState;
-
- public FileTreeFragment() {}
-
- @NonNull
- public static FileTreeFragment newInstance() {
- return new FileTreeFragment();
- }
-
- @Override
- public View onCreateView(
- @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- binding = LayoutEditorFileTreeBinding.inflate(inflater, container, false);
- return binding.getRoot();
- }
-
- @Override
- public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
-
- if (savedInstanceState != null && savedInstanceState.containsKey(KEY_STORED_TREE_STATE)) {
- mTreeState = savedInstanceState.getString(KEY_STORED_TREE_STATE, null);
- }
-
- listProjectFiles();
- }
-
- @Override
- public void onStart() {
- super.onStart();
- if (!EventBus.getDefault().isRegistered(this)) {
- EventBus.getDefault().register(this);
- }
- }
-
- @Override
- public void onSaveInstanceState(@NonNull Bundle outState) {
- super.onSaveInstanceState(outState);
- saveTreeState();
- outState.putString(KEY_STORED_TREE_STATE, mTreeState);
- }
-
- @Override
- public void onStop() {
- super.onStop();
- EventBus.getDefault().unregister(this);
- }
-
- @Override
- public void onDestroyView() {
- super.onDestroyView();
- binding = null;
- mFileTreeView = null;
- }
-
- public void saveTreeState() {
- if (mFileTreeView != null) {
- mTreeState = mFileTreeView.getSaveState();
- } else {
- LOG.error("Unable to save tree state. TreeView is null.");
- mTreeState = null;
- }
- }
-
- @Override
- public void onClick(TreeNode node, Object p2) {
- final File file = (File) p2;
- if (!file.exists()) {
- return;
- }
-
- if (file.isDirectory()) {
- if (node.isExpanded()) {
- collapseNode(node);
- } else {
- setLoading(node);
- listNode(node, () -> expandNode(node));
- }
- }
-
- final var event = new FileClickEvent(file);
- event.put(Context.class, requireContext());
- EventBus.getDefault().post(event);
- }
-
- public void collapseNode(TreeNode node) {
- if (mFileTreeView == null) {
- return;
- }
-
- TransitionManager.beginDelayedTransition(binding.getRoot(), new ChangeBounds());
- mFileTreeView.collapseNode(node);
- updateChevron(node);
- }
-
- private void updateChevron(@NonNull TreeNode node) {
- if (node.getViewHolder() instanceof FileTreeViewHolder) {
- ((FileTreeViewHolder) node.getViewHolder()).updateChevron(node.isExpanded());
- }
- }
-
- public void expandNode(TreeNode node) {
- if (mFileTreeView == null) {
- return;
- }
-
- TransitionManager.beginDelayedTransition(binding.getRoot(), new ChangeBounds());
- mFileTreeView.expandNode(node);
- updateChevron(node);
- }
-
- private void setLoading(@NonNull TreeNode node) {
- if (node.getViewHolder() instanceof FileTreeViewHolder) {
- ((FileTreeViewHolder) node.getViewHolder()).setLoading(true);
- }
- }
-
- private void listNode(@NonNull TreeNode node, Runnable whenDone) {
-
- if (whenDone == null) {
- whenDone = () -> {};
- }
-
- node.getChildren().clear();
- node.setExpanded(false);
-
- final var finalWhenDone = whenDone;
- TaskExecutor.executeAsync(
- () -> {
- listFilesForNode(node.getValue().listFiles(), node);
- TreeNode temp = node;
- while (temp.size() == 1) {
- temp = temp.childAt(0);
- if (!temp.getValue().isDirectory()) {
- break;
- }
- listFilesForNode(temp.getValue().listFiles(), temp);
- temp.setExpanded(true);
- }
- return null;
- },
- __ -> finalWhenDone.run());
- }
-
- private void listFilesForNode(File[] files, TreeNode parent) {
- Arrays.sort(files, new FileTreeCallable.SortFileName());
- Arrays.sort(files, new FileTreeCallable.SortFolder());
- for (File file : files) {
- TreeNode node = new TreeNode(file);
- node.setViewHolder(new FileTreeViewHolder(getContext()));
- parent.addChild(node);
- }
- }
-
- @Override
- public boolean onLongClick(TreeNode node, Object value) {
- final var event = new FileLongClickEvent((File) value);
- event.put(Context.class, requireContext());
- event.put(TreeNode.class, node);
- EventBus.getDefault().post(event);
- return true;
- }
-
- @SuppressWarnings("unused")
- @Subscribe(threadMode = ThreadMode.MAIN)
- public void onGetListFilesRequested(ListProjectFilesRequestEvent event) {
- if (!isVisible() || getContext() == null) {
- return;
- }
-
- listProjectFiles();
- }
-
- @SuppressWarnings("unused")
- @Subscribe(threadMode = ThreadMode.MAIN)
- public void onGetExpandTreeNodeRequest(ExpandTreeNodeRequestEvent event) {
- if (!isVisible() || getContext() == null) {
- return;
- } else {
- event.getNode();
- }
-
- expandNode(event.getNode());
- }
-
- public void listProjectFiles() {
- if (binding == null) {
- // Fragment has been destroyed
- return;
- }
- final var projectDirPath = ProjectManager.INSTANCE.getProjectDirPath();
- final var projectDir = new File(projectDirPath);
- mRoot = TreeNode.root(projectDir);
- mRoot.setViewHolder(new FileTreeViewHolder(getContext()));
-
- binding.filetreeHorizontalScrollView.setVisibility(View.GONE);
- binding.fileTreeLoadingProgress.setVisibility(View.VISIBLE);
- TaskExecutor.executeAsync(
- new FileTreeCallable(getContext(), mRoot, projectDir),
- (result) -> {
- if (binding == null) {
- // Fragment has been destroyed
- return;
- }
- binding.filetreeHorizontalScrollView.setVisibility(View.VISIBLE);
- binding.fileTreeLoadingProgress.setVisibility(View.GONE);
- AndroidTreeView tree = createTreeView(mRoot);
- if (tree != null) {
- tree.setUseAutoToggle(false);
- tree.setDefaultNodeClickListener(FileTreeFragment.this);
- tree.setDefaultNodeLongClickListener(FileTreeFragment.this);
- binding.filetreeHorizontalScrollView.removeAllViews();
-
- final var view = tree.getView();
- binding.filetreeHorizontalScrollView.addView(view);
-
- view.post(this::tryRestoreState);
- }
- });
- }
-
- public AndroidTreeView createTreeView(TreeNode node) {
- Context ctx = null;
- if (getActivity() != null) {
- ctx = getActivity();
- } else if (getContext() != null) {
- ctx = getContext();
- }
-
- if (ctx == null) {
- return null;
- }
-
- return mFileTreeView = new AndroidTreeView(ctx, node, R.drawable.bg_ripple);
- }
-
- private void tryRestoreState() {
- tryRestoreState(mTreeState);
- }
-
- private void tryRestoreState(String state) {
- if (!TextUtils.isEmpty(state) && mFileTreeView != null) {
- mFileTreeView.collapseAll();
- final var openNodesArray = state.split(NODES_PATH_SEPARATOR);
- final var openNodes = new HashSet<>(Arrays.asList(openNodesArray));
- restoreNodeState(mRoot, openNodes);
- }
- }
-
- private void restoreNodeState(@NonNull TreeNode root, Set openNodes) {
- final var children = root.getChildren();
- for (int i = 0, childrenSize = children.size(); i < childrenSize; i++) {
- final var node = children.get(i);
- if (openNodes.contains(node.getPath())) {
- listNode(
- node,
- () -> {
- expandNode(node);
- restoreNodeState(node, openNodes);
- });
- }
- }
- }
-}
diff --git a/app/src/main/java/com/itsaky/androidide/fragments/FileTreeFragment.kt b/app/src/main/java/com/itsaky/androidide/fragments/FileTreeFragment.kt
new file mode 100755
index 000000000..e616b3e16
--- /dev/null
+++ b/app/src/main/java/com/itsaky/androidide/fragments/FileTreeFragment.kt
@@ -0,0 +1,295 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ *
+ */
+package com.itsaky.androidide.fragments
+
+import android.content.Context
+import android.os.Bundle
+import android.text.TextUtils
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewGroup.MarginLayoutParams
+import androidx.core.view.WindowInsetsCompat.Type.statusBars
+import androidx.core.view.updateLayoutParams
+import androidx.core.view.updateMargins
+import androidx.fragment.app.FragmentContainerView
+import androidx.transition.ChangeBounds
+import androidx.transition.TransitionManager
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment
+import com.itsaky.androidide.adapters.viewholders.FileTreeViewHolder
+import com.itsaky.androidide.databinding.LayoutEditorFileTreeBinding
+import com.itsaky.androidide.eventbus.events.filetree.FileClickEvent
+import com.itsaky.androidide.eventbus.events.filetree.FileLongClickEvent
+import com.itsaky.androidide.events.ExpandTreeNodeRequestEvent
+import com.itsaky.androidide.events.ListProjectFilesRequestEvent
+import com.itsaky.androidide.projects.ProjectManager.getProjectDirPath
+import com.itsaky.androidide.resources.R.drawable
+import com.itsaky.androidide.tasks.TaskExecutor.executeAsync
+import com.itsaky.androidide.tasks.callables.FileTreeCallable
+import com.itsaky.androidide.tasks.callables.FileTreeCallable.SortFileName
+import com.itsaky.androidide.tasks.callables.FileTreeCallable.SortFolder
+import com.itsaky.androidide.utils.ILogger
+import com.itsaky.androidide.utils.doOnApplyWindowInsets
+import com.unnamed.b.atv.model.TreeNode
+import com.unnamed.b.atv.model.TreeNode.TreeNodeClickListener
+import com.unnamed.b.atv.model.TreeNode.TreeNodeLongClickListener
+import com.unnamed.b.atv.view.AndroidTreeView
+import java.io.File
+import java.util.Arrays
+import org.greenrobot.eventbus.EventBus
+import org.greenrobot.eventbus.Subscribe
+import org.greenrobot.eventbus.ThreadMode.MAIN
+
+class FileTreeFragment :
+ BottomSheetDialogFragment(), TreeNodeClickListener, TreeNodeLongClickListener {
+ private var binding: LayoutEditorFileTreeBinding? = null
+ private var mFileTreeView: AndroidTreeView? = null
+ private var mRoot: TreeNode? = null
+ private var mTreeState: String? = null
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ binding = LayoutEditorFileTreeBinding.inflate(inflater, container, false)
+ return binding!!.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ if (savedInstanceState != null && savedInstanceState.containsKey(KEY_STORED_TREE_STATE)) {
+ mTreeState = savedInstanceState.getString(KEY_STORED_TREE_STATE, null)
+ }
+ listProjectFiles()
+ }
+
+ override fun onStart() {
+ super.onStart()
+ if (!EventBus.getDefault().isRegistered(this)) {
+ EventBus.getDefault().register(this)
+ }
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ saveTreeState()
+ outState.putString(KEY_STORED_TREE_STATE, mTreeState)
+ }
+
+ override fun onStop() {
+ super.onStop()
+ EventBus.getDefault().unregister(this)
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ binding = null
+ mFileTreeView = null
+ }
+
+ fun saveTreeState() {
+ mTreeState =
+ if (mFileTreeView != null) {
+ mFileTreeView!!.saveState
+ } else {
+ LOG.error("Unable to save tree state. TreeView is null.")
+ null
+ }
+ }
+
+ override fun onClick(node: TreeNode, p2: Any) {
+ val file = p2 as File
+ if (!file.exists()) {
+ return
+ }
+ if (file.isDirectory) {
+ if (node.isExpanded) {
+ collapseNode(node)
+ } else {
+ setLoading(node)
+ listNode(node) { expandNode(node) }
+ }
+ }
+ val event = FileClickEvent(file)
+ event.put(Context::class.java, requireContext())
+ EventBus.getDefault().post(event)
+ }
+
+ private fun collapseNode(node: TreeNode) {
+ if (mFileTreeView == null) {
+ return
+ }
+ TransitionManager.beginDelayedTransition(binding!!.root, ChangeBounds())
+ mFileTreeView!!.collapseNode(node)
+ updateChevron(node)
+ }
+
+ private fun updateChevron(node: TreeNode) {
+ if (node.viewHolder is FileTreeViewHolder) {
+ (node.viewHolder as FileTreeViewHolder).updateChevron(node.isExpanded)
+ }
+ }
+
+ private fun expandNode(node: TreeNode) {
+ if (mFileTreeView == null) {
+ return
+ }
+ TransitionManager.beginDelayedTransition(binding!!.root, ChangeBounds())
+ mFileTreeView!!.expandNode(node)
+ updateChevron(node)
+ }
+
+ private fun setLoading(node: TreeNode) {
+ if (node.viewHolder is FileTreeViewHolder) {
+ (node.viewHolder as FileTreeViewHolder).setLoading(true)
+ }
+ }
+
+ private fun listNode(node: TreeNode, whenDone: Runnable) {
+ node.children.clear()
+ node.isExpanded = false
+ executeAsync({
+ listFilesForNode(node.value.listFiles() ?: return@executeAsync null, node)
+ var temp = node
+ while (temp.size() == 1) {
+ temp = temp.childAt(0)
+ if (!temp.value.isDirectory) {
+ break
+ }
+ listFilesForNode(temp.value.listFiles() ?: continue, temp)
+ temp.isExpanded = true
+ }
+ null
+ }) {
+ whenDone.run()
+ }
+ }
+
+ private fun listFilesForNode(files: Array, parent: TreeNode) {
+ Arrays.sort(files, SortFileName())
+ Arrays.sort(files, SortFolder())
+ for (file in files) {
+ val node = TreeNode(file)
+ node.viewHolder = FileTreeViewHolder(context)
+ parent.addChild(node)
+ }
+ }
+
+ override fun onLongClick(node: TreeNode, value: Any): Boolean {
+ val event = FileLongClickEvent((value as File))
+ event.put(Context::class.java, requireContext())
+ event.put(TreeNode::class.java, node)
+ EventBus.getDefault().post(event)
+ return true
+ }
+
+ @Suppress("unused", "UNUSED_PARAMETER")
+ @Subscribe(threadMode = MAIN)
+ fun onGetListFilesRequested(event: ListProjectFilesRequestEvent?) {
+ if (!isVisible || context == null) {
+ return
+ }
+ listProjectFiles()
+ }
+
+ @Suppress("unused")
+ @Subscribe(threadMode = MAIN)
+ fun onGetExpandTreeNodeRequest(event: ExpandTreeNodeRequestEvent) {
+ if (!isVisible || context == null) {
+ return
+ } else {
+ event.node
+ }
+ expandNode(event.node)
+ }
+
+ fun listProjectFiles() {
+ if (binding == null) {
+ // Fragment has been destroyed
+ return
+ }
+ val projectDirPath = getProjectDirPath()
+ val projectDir = File(projectDirPath)
+ mRoot = TreeNode.root(projectDir)
+ mRoot?.viewHolder = FileTreeViewHolder(context)
+ binding!!.filetreeHorizontalScrollView.visibility = View.GONE
+ binding!!.fileTreeLoadingProgress.visibility = View.VISIBLE
+ executeAsync(FileTreeCallable(context, mRoot, projectDir)) {
+ if (binding == null) {
+ // Fragment has been destroyed
+ return@executeAsync
+ }
+ binding!!.filetreeHorizontalScrollView.visibility = View.VISIBLE
+ binding!!.fileTreeLoadingProgress.visibility = View.GONE
+ val tree = createTreeView(mRoot)
+ if (tree != null) {
+ tree.setUseAutoToggle(false)
+ tree.setDefaultNodeClickListener(this@FileTreeFragment)
+ tree.setDefaultNodeLongClickListener(this@FileTreeFragment)
+ binding!!.filetreeHorizontalScrollView.removeAllViews()
+ val view = tree.view
+ binding!!.filetreeHorizontalScrollView.addView(view)
+ view.post { tryRestoreState() }
+ }
+ }
+ }
+
+ private fun createTreeView(node: TreeNode?): AndroidTreeView? {
+ return if (context == null) {
+ null
+ } else AndroidTreeView(context, node, drawable.bg_ripple).also { mFileTreeView = it }
+ }
+
+ private fun tryRestoreState(state: String? = mTreeState) {
+ if (!TextUtils.isEmpty(state) && mFileTreeView != null) {
+ mFileTreeView!!.collapseAll()
+ val openNodes =
+ state!!.split(AndroidTreeView.NODES_PATH_SEPARATOR.toRegex()).dropLastWhile { it.isEmpty() }
+ restoreNodeState(mRoot!!, HashSet(openNodes))
+ }
+ }
+
+ private fun restoreNodeState(root: TreeNode, openNodes: Set) {
+ val children = root.children
+ var i = 0
+ val childrenSize = children.size
+ while (i < childrenSize) {
+ val node = children[i]
+ if (openNodes.contains(node.path)) {
+ listNode(node) {
+ expandNode(node)
+ restoreNodeState(node, openNodes)
+ }
+ }
+ i++
+ }
+ }
+
+ companion object {
+
+ // Should be same as defined in layout/activity_editor.xml
+ const val TAG = "editor.fileTree"
+ private const val KEY_STORED_TREE_STATE = "fileTree_state"
+ private val LOG = ILogger.newInstance("FileTreeFragment")
+ @JvmStatic
+ fun newInstance(): FileTreeFragment {
+ return FileTreeFragment()
+ }
+ }
+}
diff --git a/app/src/main/java/com/itsaky/androidide/fragments/RunTasksDialogFragment.kt b/app/src/main/java/com/itsaky/androidide/fragments/RunTasksDialogFragment.kt
index f2f4af5bb..a6057ece7 100644
--- a/app/src/main/java/com/itsaky/androidide/fragments/RunTasksDialogFragment.kt
+++ b/app/src/main/java/com/itsaky/androidide/fragments/RunTasksDialogFragment.kt
@@ -17,16 +17,25 @@
package com.itsaky.androidide.fragments
+import android.app.Dialog
+import android.graphics.Color
import android.os.Bundle
-import android.os.Handler
import android.text.Editable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.view.ViewGroup.MarginLayoutParams
+import androidx.core.view.WindowCompat
+import androidx.core.view.WindowInsetsCompat.Type.navigationBars
+import androidx.core.view.WindowInsetsCompat.Type.statusBars
+import androidx.core.view.updateLayoutParams
+import androidx.core.view.updateMargins
+import androidx.core.view.updatePadding
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.RecyclerView
import androidx.transition.TransitionManager
import com.blankj.utilcode.util.ThreadUtils
+import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.transition.MaterialSharedAxis
import com.itsaky.androidide.R.string
@@ -44,6 +53,8 @@ import com.itsaky.androidide.tasks.executeAsync
import com.itsaky.androidide.tooling.api.model.GradleTask
import com.itsaky.androidide.utils.ILogger
import com.itsaky.androidide.utils.SingleTextWatcher
+import com.itsaky.androidide.utils.doOnApplyWindowInsets
+import com.itsaky.androidide.utils.updateSystemBarColors
import com.itsaky.androidide.viewmodel.RunTasksViewModel
import com.itsaky.toaster.toastInfo
@@ -73,6 +84,32 @@ class RunTasksDialogFragment : BottomSheetDialogFragment() {
private const val SEARCH_DELAY = 500L
}
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ return object : BottomSheetDialog(requireContext(), theme) {
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ window?.apply {
+ WindowCompat.setDecorFitsSystemWindows(this, false)
+ updateSystemBarColors(navigationBarColor = Color.TRANSPARENT)
+ }
+ findViewById(com.google.android.material.R.id.container)?.apply {
+ doOnApplyWindowInsets { view, insets, paddings, margins ->
+ insets.getInsets(statusBars() or navigationBars()).apply {
+ view.updateLayoutParams {
+ updateMargins(top = margins.top + top)
+ }
+ run.categories.apply {
+ updatePadding(bottom = bottom)
+ clipToPadding = false
+ clipChildren = false
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
diff --git a/app/src/main/java/com/itsaky/androidide/handlers/EditorInsetsHandler.kt b/app/src/main/java/com/itsaky/androidide/handlers/EditorInsetsHandler.kt
new file mode 100644
index 000000000..727e12046
--- /dev/null
+++ b/app/src/main/java/com/itsaky/androidide/handlers/EditorInsetsHandler.kt
@@ -0,0 +1,67 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+
+package com.itsaky.androidide.handlers
+
+import android.graphics.Color
+import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams
+import androidx.core.content.ContextCompat
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.forEach
+import androidx.core.view.updateLayoutParams
+import androidx.core.view.updatePadding
+import com.blankj.utilcode.util.SizeUtils
+import com.google.android.material.bottomsheet.BottomSheetBehavior
+import com.itsaky.androidide.EditorActivity
+import com.itsaky.androidide.R
+import com.itsaky.androidide.utils.doOnApplyWindowInsets
+import com.itsaky.androidide.utils.updateSystemBarColors
+
+/** @author Akash Yadav */
+object EditorInsetsHandler {
+
+ @JvmStatic
+ fun setupInsets(activity: EditorActivity) {
+ activity.window.updateSystemBarColors(
+ statusBarColor = Color.TRANSPARENT,
+ navigationBarColor = ContextCompat.getColor(activity, R.color.primaryDarkColor)
+ )
+
+ val binding = activity.binding
+ binding.root.doOnApplyWindowInsets { view, insets, _, _ ->
+ val appBarHeight = binding.editorAppBarLayout.height
+ insets.getInsets(WindowInsetsCompat.Type.statusBars()).apply {
+ binding.editorAppBarLayout.updatePadding(top = top)
+ binding.endNav.getChildAt(0).updatePadding(top = top + SizeUtils.dp2px(16f))
+ val behaviour = BottomSheetBehavior.from(binding.bottomSheet)
+ behaviour.isFitToContents = false
+ behaviour.expandedOffset = appBarHeight + SizeUtils.dp2px(1f)
+ }
+ insets.getInsets(WindowInsetsCompat.Type.navigationBars()).apply {
+ binding.root.updatePadding(bottom = bottom)
+ binding.bottomSheet.binding.spaceBottom.updateLayoutParams {
+ height = bottom + appBarHeight + SizeUtils.dp2px(1f)
+ }
+ }
+ (view as? ViewGroup)?.forEach {
+ ViewCompat.dispatchApplyWindowInsets(it, insets)
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/itsaky/androidide/utils/EditorBottomSheetBehavior.java b/app/src/main/java/com/itsaky/androidide/utils/EditorBottomSheetBehavior.java
index b5a912076..f0e0cb1f1 100644
--- a/app/src/main/java/com/itsaky/androidide/utils/EditorBottomSheetBehavior.java
+++ b/app/src/main/java/com/itsaky/androidide/utils/EditorBottomSheetBehavior.java
@@ -27,11 +27,9 @@ import androidx.annotation.NonNull;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
-import com.itsaky.androidide.databinding.LayoutEditorBottomSheetBinding;
public class EditorBottomSheetBehavior extends BottomSheetBehavior {
- private static final ILogger LOG = ILogger.newInstance("EditorBottomSheetBehavior");
private View pager;
public EditorBottomSheetBehavior(Context context, AttributeSet attributeSet) {
@@ -39,7 +37,7 @@ public class EditorBottomSheetBehavior extends BottomSheetBehavi
}
public void setBinding(View pager) {
- this.pager= pager;
+ this.pager = pager;
}
@Override
diff --git a/app/src/main/java/com/itsaky/androidide/utils/SingleBottomSheetCallback.kt b/app/src/main/java/com/itsaky/androidide/utils/SingleBottomSheetCallback.kt
new file mode 100644
index 000000000..b0ef73968
--- /dev/null
+++ b/app/src/main/java/com/itsaky/androidide/utils/SingleBottomSheetCallback.kt
@@ -0,0 +1,31 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+
+package com.itsaky.androidide.utils
+
+import android.view.View
+import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
+
+/**
+ * A [BottomSheetCallback] which allows you to override any single method.
+ *
+ * @author Akash Yadav
+ */
+open class SingleBottomSheetCallback : BottomSheetCallback() {
+ override fun onStateChanged(bottomSheet: View, newState: Int) {}
+ override fun onSlide(bottomSheet: View, slideOffset: Float) {}
+}
diff --git a/app/src/main/java/com/itsaky/androidide/views/EditorBottomSheet.kt b/app/src/main/java/com/itsaky/androidide/views/EditorBottomSheet.kt
index e60ab3363..162de9cd6 100644
--- a/app/src/main/java/com/itsaky/androidide/views/EditorBottomSheet.kt
+++ b/app/src/main/java/com/itsaky/androidide/views/EditorBottomSheet.kt
@@ -25,21 +25,19 @@ import android.util.AttributeSet
import android.view.Gravity
import android.view.LayoutInflater
import android.widget.RelativeLayout
-import android.widget.ViewFlipper
import androidx.annotation.GravityInt
import androidx.appcompat.widget.TooltipCompat
+import androidx.core.view.updateLayoutParams
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.transition.Slide
import androidx.transition.TransitionManager
-import androidx.viewpager2.widget.ViewPager2
import com.blankj.utilcode.util.KeyboardUtils
import com.blankj.utilcode.util.ThreadUtils.runOnUiThread
-import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayout.OnTabSelectedListener
import com.google.android.material.tabs.TabLayout.Tab
import com.google.android.material.tabs.TabLayoutMediator
-import com.itsaky.androidide.resources.R.string
+import com.itsaky.androidide.R
import com.itsaky.androidide.adapters.DiagnosticsAdapter
import com.itsaky.androidide.adapters.EditorBottomSheetTabAdapter
import com.itsaky.androidide.adapters.SearchListAdapter
@@ -47,6 +45,7 @@ import com.itsaky.androidide.databinding.LayoutBottomActionBinding
import com.itsaky.androidide.databinding.LayoutEditorBottomSheetBinding
import com.itsaky.androidide.fragments.ShareableOutputFragment
import com.itsaky.androidide.models.LogLine
+import com.itsaky.androidide.resources.R.string
import com.itsaky.androidide.tasks.TaskExecutor.CallbackWithError
import com.itsaky.androidide.tasks.TaskExecutor.executeAsync
import com.itsaky.androidide.tasks.TaskExecutor.executeAsyncProvideError
@@ -78,21 +77,17 @@ constructor(
defStyleRes: Int = 0,
) : RelativeLayout(context, attrs, defStyleAttr, defStyleRes) {
- private var binding: LayoutEditorBottomSheetBinding
private var action: LayoutBottomActionBinding
private var symbolInput: SymbolInputView
+ private val collapsedHeight: Float by lazy {
+ val localContext = getContext() ?: return@lazy 0f
+ localContext.resources.getDimension(R.dimen.editor_sheet_collapsed_height)
+ }
+
+ @JvmField var binding: LayoutEditorBottomSheetBinding
val pagerAdapter: EditorBottomSheetTabAdapter
- val tabs: TabLayout
- get() = binding.tabs
-
- val pager: ViewPager2
- get() = binding.pager
-
- val headerContainer: ViewFlipper
- get() = binding.headerContainer
-
private val log = ILogger.newInstance("EditorBottomSheet")
companion object {
@@ -102,6 +97,7 @@ constructor(
}
private fun initialize(context: FragmentActivity) {
+
val mediator =
TabLayoutMediator(binding.tabs, binding.pager, true, true) { tab, position ->
tab.text = pagerAdapter.getTitle(position)
@@ -138,6 +134,7 @@ constructor(
}
val filename = fragment.getFilename()
+
@Suppress("DEPRECATION")
val progress = ProgressDialog.show(context, null, context.getString(string.please_wait))
executeAsync(fragment::getContent) {
@@ -178,6 +175,17 @@ constructor(
initialize(context)
}
+ fun onStateChanged(newState: Int) {
+ // binding.headerContainer.visibility =
+ // if (newState == BottomSheetBehavior.STATE_EXPANDED) INVISIBLE else VISIBLE
+ }
+
+ fun onSlide(offset: Float) {
+ if (offset >= 0.5f) {
+ updateCollapsedHeight(((0.5f - offset) + 0.5f) * 2f)
+ } else updateCollapsedHeight(1f)
+ }
+
fun showChild(index: Int) {
binding.headerContainer.displayedChild = index
}
@@ -243,6 +251,12 @@ constructor(
}
}
+ private fun updateCollapsedHeight(offset: Float) {
+ binding.headerContainer.updateLayoutParams {
+ height = (collapsedHeight * offset).toInt()
+ }
+ }
+
private fun shareFile(file: File) {
shareFile(context, file, "text/plain")
}
diff --git a/app/src/main/res/layout/activity_editor.xml b/app/src/main/res/layout/activity_editor.xml
index e3f490e7b..a85c76225 100755
--- a/app/src/main/res/layout/activity_editor.xml
+++ b/app/src/main/res/layout/activity_editor.xml
@@ -17,13 +17,13 @@
android:id="@+id/editor_appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:background="@color/primaryDarkColor"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$Behavior">
+ app:layout_behavior="com.itsaky.androidide.utils.EditorBottomSheetBehavior" />
+ android:layout_gravity="end"
+ android:fitsSystemWindows="false">
-
+
+ android:layout_height="match_parent"
+ android:tag="editor.fileTree" />
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 7b1e1619f..c66bdae33 100755
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -1,48 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:name="com.itsaky.androidide.fragments.MainFragment" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_editor_bottom_sheet.xml b/app/src/main/res/layout/layout_editor_bottom_sheet.xml
index eb8a0c16d..7babb7451 100644
--- a/app/src/main/res/layout/layout_editor_bottom_sheet.xml
+++ b/app/src/main/res/layout/layout_editor_bottom_sheet.xml
@@ -16,29 +16,30 @@
-->
+ android:layout_height="@dimen/editor_sheet_collapsed_height"
+ android:background="@color/primaryDarkColor">
+ android:orientation="vertical"
+ tools:ignore="UselessParent">
@@ -47,6 +48,7 @@
android:layout_width="match_parent"
android:layout_height="18dp"
android:gravity="center"
+ android:maxLines="1"
android:text="@string/msg_swipe_up"
android:textColor="@color/secondaryTextColor"
android:textSize="11sp" />
@@ -58,10 +60,9 @@
+
+
@@ -81,8 +90,8 @@
android:id="@+id/clear_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_above="@id/space_bottom"
android:layout_alignParentEnd="true"
- android:layout_alignParentBottom="true"
android:layout_margin="16dp"
android:src="@drawable/ic_close"
app:backgroundTint="@color/secondaryColor" />
@@ -93,8 +102,12 @@
android:layout_height="wrap_content"
android:layout_above="@id/clear_fab"
android:layout_alignParentEnd="true"
- android:layout_margin="16dp"
+ android:layout_marginStart="16dp"
+ android:layout_marginTop="16dp"
+ android:layout_marginEnd="16dp"
+ android:layout_marginBottom="16dp"
android:src="@drawable/ic_share"
app:backgroundTint="@color/secondaryColor" />
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_project_not_initialized.xml b/app/src/main/res/layout/layout_project_not_initialized.xml
index 23d7e4649..7c0340ddd 100644
--- a/app/src/main/res/layout/layout_project_not_initialized.xml
+++ b/app/src/main/res/layout/layout_project_not_initialized.xml
@@ -28,7 +28,6 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintVertical_bias="0.4"
app:srcCompat="@drawable/ic_android"
app:tint="@color/green_500" />
diff --git a/app/src/main/res/layout/layout_run_task.xml b/app/src/main/res/layout/layout_run_task.xml
index 057d8cd04..bbddcd6a1 100644
--- a/app/src/main/res/layout/layout_run_task.xml
+++ b/app/src/main/res/layout/layout_run_task.xml
@@ -15,25 +15,22 @@
~ along with AndroidIDE. If not, see .
-->
-
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ app:boxBackgroundColor="@color/content_background">
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_run_task_dialog.xml b/app/src/main/res/layout/layout_run_task_dialog.xml
index a1221f55d..8e89e7dd6 100644
--- a/app/src/main/res/layout/layout_run_task_dialog.xml
+++ b/app/src/main/res/layout/layout_run_task_dialog.xml
@@ -15,55 +15,51 @@
~ along with AndroidIDE. If not, see .
-->
-
-
-
+ app:iconTint="@color/green_500" />
+
+
+
+ android:layout_below="@id/exec"
+ android:background="@color/primaryLightColor" />
+ android:layout_below="@id/separator">
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 000000000..b56276dc0
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,21 @@
+
+
+
+
+ 56dp
+
\ No newline at end of file
diff --git a/common/src/main/java/com/itsaky/androidide/utils/WindowInsetExtensions.kt b/common/src/main/java/com/itsaky/androidide/utils/WindowInsetExtensions.kt
new file mode 100644
index 000000000..1d747a04f
--- /dev/null
+++ b/common/src/main/java/com/itsaky/androidide/utils/WindowInsetExtensions.kt
@@ -0,0 +1,71 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+
+package com.itsaky.androidide.utils
+
+import android.view.View
+import androidx.core.graphics.Insets
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.marginBottom
+import androidx.core.view.marginEnd
+import androidx.core.view.marginStart
+import androidx.core.view.marginTop
+
+/**
+ * A helper method wrapping [ViewCompat.setOnApplyWindowInsetsListener], additionally providing the
+ * initial padding and margins of the view.
+ *
+ * This method does not consume any window insets, allowing any and all children to receive the same
+ * insets.
+ *
+ * This is a `set` listener, so only the last [windowInsetsListener] applied by
+ * [doOnApplyWindowInsets] will be ran.
+ */
+fun View.doOnApplyWindowInsets(
+ windowInsetsListener:
+ (
+ insetView: View,
+ windowInsets: WindowInsetsCompat,
+ initialPadding: Insets,
+ initialMargins: Insets
+ ) -> Unit
+) {
+ val initialPadding = Insets.of(paddingStart, paddingTop, paddingEnd, paddingBottom)
+ val initialMargins = Insets.of(marginStart, marginTop, marginEnd, marginBottom)
+
+ ViewCompat.setOnApplyWindowInsetsListener(this) { insetView, windowInsets ->
+ windowInsets.also {
+ windowInsetsListener(insetView, windowInsets, initialPadding, initialMargins)
+ }
+ }
+
+ addOnAttachStateChangeListener(
+ object : View.OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(v: View) {
+ v.requestApplyInsets()
+ }
+
+ override fun onViewDetachedFromWindow(v: View) = Unit
+ }
+ )
+
+ // If the view is already attached, immediately request insets be applied.
+ if (isAttachedToWindow) {
+ requestApplyInsets()
+ }
+}
diff --git a/common/src/main/java/com/itsaky/androidide/utils/WindowUtils.kt b/common/src/main/java/com/itsaky/androidide/utils/WindowUtils.kt
new file mode 100644
index 000000000..ba33217c8
--- /dev/null
+++ b/common/src/main/java/com/itsaky/androidide/utils/WindowUtils.kt
@@ -0,0 +1,33 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+
+package com.itsaky.androidide.utils
+
+import android.view.Window
+import android.view.WindowManager
+
+@JvmOverloads
+fun Window.updateSystemBarColors(statusBarColor: Int = -1, navigationBarColor: Int = -1) {
+ @Suppress("DEPRECATION") clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
+ addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
+ if (statusBarColor != -1) {
+ this.statusBarColor = statusBarColor
+ }
+ if (navigationBarColor != -1) {
+ this.navigationBarColor = navigationBarColor
+ }
+}
\ No newline at end of file
diff --git a/dependencies.gradle b/dependencies.gradle
index edaa01626..b3fdf1bc1 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -50,7 +50,7 @@ ext {
]
google = [
- material : 'com.google.android.material:material:1.7.0-rc01',
+ material : 'com.google.android.material:material:1.7.0',
gson : 'com.google.code.gson:gson:2.9.1',
guava : 'com.google.guava:guava:31.1-android',
guava_jre : 'com.google.guava:guava:31.1-jre',
diff --git a/resources/src/main/res/values/colors.xml b/resources/src/main/res/values/colors.xml
index 2af0622ac..c38bb2cc3 100755
--- a/resources/src/main/res/values/colors.xml
+++ b/resources/src/main/res/values/colors.xml
@@ -1,6 +1,7 @@
#121212
+ #90121212
#373737
#010101
#ff5252
diff --git a/resources/src/main/res/values/dimens.xml b/resources/src/main/res/values/dimens.xml
index 61234154a..f54d09a10 100755
--- a/resources/src/main/res/values/dimens.xml
+++ b/resources/src/main/res/values/dimens.xml
@@ -1,5 +1,4 @@
- 36dp
40dp
diff --git a/resources/src/main/res/values/styles.xml b/resources/src/main/res/values/styles.xml
index 82e1b7bd9..cef8838df 100755
--- a/resources/src/main/res/values/styles.xml
+++ b/resources/src/main/res/values/styles.xml
@@ -48,6 +48,7 @@
- @style/AppTheme.UIDesigner.WidgetGroupChip
- @style/AppTheme.SwitchPreference
- @style/AppTheme.ShapeOverlay.Large
+ - @style/AppTheme.BottomSheetDialog
@@ -117,6 +118,11 @@
- 25dp
+
+