From e764c7eac7f291afb6a36cb839fc62e36169a19d Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Thu, 27 Oct 2022 11:38:34 +0530 Subject: [PATCH] UI enhancements --- app/build.gradle | 3 +- .../com/itsaky/androidide/EditorActivity.java | 99 +++--- .../com/itsaky/androidide/MainActivity.java | 189 ---------- .../com/itsaky/androidide/MainActivity.kt | 174 ++++++++++ .../itsaky/androidide/app/IDEActivity.java | 106 ------ .../com/itsaky/androidide/app/IDEActivity.kt | 101 ++++++ .../com/itsaky/androidide/events/FileTree.kt | 4 +- .../fragments/FileTreeFragment.java | 328 ------------------ .../androidide/fragments/FileTreeFragment.kt | 295 ++++++++++++++++ .../fragments/RunTasksDialogFragment.kt | 39 ++- .../handlers/EditorInsetsHandler.kt | 67 ++++ .../utils/EditorBottomSheetBehavior.java | 4 +- .../utils/SingleBottomSheetCallback.kt | 31 ++ .../androidide/views/EditorBottomSheet.kt | 42 ++- app/src/main/res/layout/activity_editor.xml | 19 +- app/src/main/res/layout/activity_main.xml | 86 ++--- .../res/layout/layout_editor_bottom_sheet.xml | 33 +- .../layout/layout_project_not_initialized.xml | 1 - app/src/main/res/layout/layout_run_task.xml | 21 +- .../res/layout/layout_run_task_dialog.xml | 53 ++- app/src/main/res/values/dimens.xml | 21 ++ .../androidide/utils/WindowInsetExtensions.kt | 71 ++++ .../itsaky/androidide/utils/WindowUtils.kt | 33 ++ dependencies.gradle | 2 +- resources/src/main/res/values/colors.xml | 1 + resources/src/main/res/values/dimens.xml | 1 - resources/src/main/res/values/styles.xml | 6 + tests/test-home/.androidide/init/model.jar | Bin 65261 -> 65261 bytes 28 files changed, 1031 insertions(+), 799 deletions(-) delete mode 100755 app/src/main/java/com/itsaky/androidide/MainActivity.java create mode 100755 app/src/main/java/com/itsaky/androidide/MainActivity.kt delete mode 100755 app/src/main/java/com/itsaky/androidide/app/IDEActivity.java create mode 100755 app/src/main/java/com/itsaky/androidide/app/IDEActivity.kt delete mode 100755 app/src/main/java/com/itsaky/androidide/fragments/FileTreeFragment.java create mode 100755 app/src/main/java/com/itsaky/androidide/fragments/FileTreeFragment.kt create mode 100644 app/src/main/java/com/itsaky/androidide/handlers/EditorInsetsHandler.kt create mode 100644 app/src/main/java/com/itsaky/androidide/utils/SingleBottomSheetCallback.kt create mode 100644 app/src/main/res/values/dimens.xml create mode 100644 common/src/main/java/com/itsaky/androidide/utils/WindowInsetExtensions.kt create mode 100644 common/src/main/java/com/itsaky/androidide/utils/WindowUtils.kt 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) 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 + +