mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
UI enhancements
This commit is contained in:
+1
-2
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
+174
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
**************************************************************************************/
|
||||
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();
|
||||
}
|
||||
+101
@@ -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 <https:></https:>//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<String>,
|
||||
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")
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
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<String> 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
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<File>, 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<String>) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<View>(com.google.android.material.R.id.container)?.apply {
|
||||
doOnApplyWindowInsets { view, insets, paddings, margins ->
|
||||
insets.getInsets(statusBars() or navigationBars()).apply {
|
||||
view.updateLayoutParams<MarginLayoutParams> {
|
||||
updateMargins(top = margins.top + top)
|
||||
}
|
||||
run.categories.apply {
|
||||
updatePadding(bottom = bottom)
|
||||
clipToPadding = false
|
||||
clipChildren = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<LayoutParams> {
|
||||
height = bottom + appBarHeight + SizeUtils.dp2px(1f)
|
||||
}
|
||||
}
|
||||
(view as? ViewGroup)?.forEach {
|
||||
ViewCompat.dispatchApplyWindowInsets(it, insets)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<V extends View> extends BottomSheetBehavior<V> {
|
||||
|
||||
private static final ILogger LOG = ILogger.newInstance("EditorBottomSheetBehavior");
|
||||
private View pager;
|
||||
|
||||
public EditorBottomSheetBehavior(Context context, AttributeSet attributeSet) {
|
||||
@@ -39,7 +37,7 @@ public class EditorBottomSheetBehavior<V extends View> extends BottomSheetBehavi
|
||||
}
|
||||
|
||||
public void setBinding(View pager) {
|
||||
this.pager= pager;
|
||||
this.pager = pager;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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) {}
|
||||
}
|
||||
@@ -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<android.view.ViewGroup.LayoutParams> {
|
||||
height = (collapsedHeight * offset).toInt()
|
||||
}
|
||||
}
|
||||
|
||||
private fun shareFile(file: File) {
|
||||
shareFile(context, file, "text/plain")
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/editor_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/primaryDarkColor"
|
||||
android:elevation="10dp" />
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
@@ -42,7 +42,7 @@
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tabs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/file_tab_height"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/primaryDarkColor"
|
||||
app:tabGravity="start"
|
||||
app:tabIndicatorAnimationMode="elastic"
|
||||
@@ -113,7 +113,7 @@
|
||||
android:layout_height="match_parent"
|
||||
app:behavior_hideable="false"
|
||||
app:behavior_peekHeight="@dimen/editor_sheet_peek_height"
|
||||
app:layout_behavior="com.itsaky.androidide.utils.EditorBottomSheetBehavior"/>
|
||||
app:layout_behavior="com.itsaky.androidide.utils.EditorBottomSheetBehavior" />
|
||||
|
||||
<include
|
||||
android:id="@+id/diagnosticInfo"
|
||||
@@ -132,14 +132,19 @@
|
||||
app:menu="@menu/menu_editor_nav" />
|
||||
|
||||
<com.google.android.material.navigation.NavigationView
|
||||
android:id="@+id/endNav"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="end">
|
||||
android:layout_gravity="end"
|
||||
android:fitsSystemWindows="false">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/editor_frameLayout"
|
||||
<!-- When changing android:tag here, also change it in FileTreeFragment.kt -->
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/file_tree"
|
||||
android:name="com.itsaky.androidide.fragments.FileTreeFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
android:layout_height="match_parent"
|
||||
android:tag="editor.fileTree" />
|
||||
|
||||
</com.google.android.material.navigation.NavigationView>
|
||||
|
||||
|
||||
@@ -1,48 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:animateLayoutChanges="true"
|
||||
android:orientation="vertical"
|
||||
tools:ignore="RtlCompat">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="?attr/colorPrimaryDark"
|
||||
android:gravity="center"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$Behavior">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/toolbar_header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:gravity="center"
|
||||
android:text="@string/app_name"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:textColor="@color/primaryTextColor"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</RelativeLayout>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:animateLayoutChanges="true"
|
||||
android:orientation="vertical"
|
||||
tools:ignore="RtlCompat">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="?attr/colorPrimaryDark"
|
||||
android:gravity="center"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$Behavior">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/toolbar_header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:gravity="center"
|
||||
android:text="@string/app_name"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:textColor="@color/primaryTextColor"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
</RelativeLayout>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
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" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -16,29 +16,30 @@
|
||||
-->
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ViewFlipper
|
||||
android:id="@+id/header_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp">
|
||||
android:layout_height="@dimen/editor_sheet_collapsed_height"
|
||||
android:background="@color/primaryDarkColor">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:background="@android:color/black"
|
||||
android:orientation="vertical">
|
||||
android:orientation="vertical"
|
||||
tools:ignore="UselessParent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/statusText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="20dp"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/primaryTextColor"
|
||||
android:textSize="14sp" />
|
||||
|
||||
@@ -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 @@
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tabs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="36dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/header_container"
|
||||
android:background="@android:color/black"
|
||||
app:tabGravity="fill"
|
||||
android:backgroundTint="@color/primaryDarkColor"
|
||||
app:tabIndicatorAnimationMode="elastic"
|
||||
app:tabIndicatorColor="@color/secondaryColor"
|
||||
app:tabIndicatorGravity="bottom"
|
||||
@@ -70,10 +71,18 @@
|
||||
app:tabSelectedTextColor="@color/primaryTextColor"
|
||||
app:tabTextAppearance="@style/AppTheme.TabLayout.Title" />
|
||||
|
||||
<androidx.legacy.widget.Space
|
||||
android:id="@+id/space_bottom"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
tools:layout_height="56dp" />
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/pager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_above="@id/space_bottom"
|
||||
android:layout_below="@id/tabs"
|
||||
android:background="@color/content_background" />
|
||||
|
||||
@@ -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" />
|
||||
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -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" />
|
||||
|
||||
|
||||
@@ -15,25 +15,22 @@
|
||||
~ along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="16dp">
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/search_input"
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.Dense"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:hint="@string/hint_search_tasks"
|
||||
android:theme="@style/AppTheme.TextInputLayout"
|
||||
app:boxBackgroundColor="@color/content_background"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
app:boxBackgroundColor="@color/content_background">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
@@ -43,17 +40,13 @@
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/categories"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:descendantFocusability="blocksDescendants"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/search_input"
|
||||
tools:itemCount="3"
|
||||
tools:listitem="@layout/layout_run_tasks_category" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
@@ -15,55 +15,51 @@
|
||||
~ along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:nestedScrollingEnabled="true">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/title_run_tasks"
|
||||
android:textAppearance="@style/TextAppearance.Material3.ActionBar.Title"
|
||||
app:layout_constraintBottom_toBottomOf="@id/exec"
|
||||
app:layout_constraintEnd_toStartOf="@id/exec"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/exec" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/exec"
|
||||
style="@style/Widget.Material3.Button.IconButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:tooltipText="@string/run"
|
||||
app:icon="@drawable/ic_run"
|
||||
app:iconTint="@color/green_500"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
app:iconTint="@color/green_500" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignTop="@+id/exec"
|
||||
android:layout_alignBottom="@+id/exec"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="-8dp"
|
||||
android:layout_marginBottom="1dp"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/title_run_tasks"
|
||||
android:textAppearance="@style/TextAppearance.Material3.ActionBar.Title" />
|
||||
|
||||
|
||||
<View
|
||||
android:id="@+id/separator"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/primaryLightColor"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/exec" />
|
||||
android:layout_below="@id/exec"
|
||||
android:background="@color/primaryLightColor" />
|
||||
|
||||
<ViewFlipper
|
||||
android:id="@+id/flipper"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/separator">
|
||||
android:layout_below="@id/separator">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/loading_tasks"
|
||||
@@ -87,5 +83,4 @@
|
||||
|
||||
</ViewFlipper>
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</RelativeLayout>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ 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 <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<dimen name="editor_sheet_collapsed_height">56dp</dimen>
|
||||
</resources>
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="primaryColor">#121212</color>
|
||||
<color name="primaryColor_translucent">#90121212</color>
|
||||
<color name="primaryLightColor">#373737</color>
|
||||
<color name="primaryDarkColor">#010101</color>
|
||||
<color name="secondaryColor">#ff5252</color>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<dimen name="file_tab_height">36dp</dimen>
|
||||
<dimen name="editor_sheet_peek_height">40dp</dimen>
|
||||
</resources>
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
<item name="chipStyle">@style/AppTheme.UIDesigner.WidgetGroupChip</item>
|
||||
<item name="switchPreferenceStyle">@style/AppTheme.SwitchPreference</item>
|
||||
<item name="shapeAppearanceLargeComponent">@style/AppTheme.ShapeOverlay.Large</item>
|
||||
<item name="bottomSheetDialogTheme">@style/AppTheme.BottomSheetDialog</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.AndroidIDE" parent="Theme.AndroidIDE.Styled" />
|
||||
@@ -117,6 +118,11 @@
|
||||
<item name="cornerSize">25dp</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.BottomSheetDialog" parent="Theme.Material3.Dark.BottomSheetDialog">
|
||||
<item name="android:windowIsFloating">false</item>
|
||||
<item name="android:windowLightStatusBar">false</item>
|
||||
</style>
|
||||
|
||||
<!-- Alert Dialog -->
|
||||
<style name="AppTheme.MaterialAlertDialog" parent="@style/ThemeOverlay.Material3.MaterialAlertDialog">
|
||||
<item name="shapeAppearanceOverlay">@style/MaterialDialog.ShapeAppearance</item>
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user