Skip to content
This repository was archived by the owner on Oct 18, 2024. It is now read-only.
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ class DefaultActionsRegistry : ActionsRegistry() {

private fun addActionToMenu(menu: Menu, action: ActionItem, data: ActionData) {
val context = data[Context::class.java]

val item: MenuItem = if (action is ActionMenu) {
val sub = menu.addSubMenu(action.label)

Expand All @@ -141,6 +142,7 @@ class DefaultActionsRegistry : ActionsRegistry() {
}

item.isEnabled = action.enabled

item.icon = action.icon?.apply {
colorFilter = PorterDuffColorFilter(context!!.resolveAttr(R.attr.colorOnSurface), SRC_ATOP)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@
package com.itsaky.androidide.actions.etc

import android.content.Context
import android.view.MenuItem
import androidx.core.content.ContextCompat
import androidx.core.view.GravityCompat
import com.itsaky.androidide.resources.R
import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.EditorActivityAction
import com.itsaky.androidide.preferences.internal.hideFileTreeButton

/** @author Akash Yadav */
class FileTreeAction(context: Context) : EditorActivityAction() {
Expand Down Expand Up @@ -51,4 +53,12 @@ class FileTreeAction(context: Context) : EditorActivityAction() {

return false
}

override fun getShowAsActionFlags(data: ActionData): Int {
return if (hideFileTreeButton) {
MenuItem.SHOW_AS_ACTION_NEVER
} else {
MenuItem.SHOW_AS_ACTION_IF_ROOM
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ import com.itsaky.androidide.utils.DialogUtils.newYesNoDialog
import com.itsaky.androidide.utils.IntentUtils.openImage
import com.itsaky.androidide.utils.UniqueNameBuilder
import com.itsaky.androidide.utils.flashSuccess
import io.github.rosemoe.sora.event.ContentChangeEvent
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import java.io.File
Expand Down Expand Up @@ -439,15 +438,20 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler {

open fun ensureToolbarMenu(menu: Menu) {
menu.clear()

val data = ActionData()
val currentEditor = getCurrentEditor()

data.put(Context::class.java, this)
data.put(CodeEditorView::class.java, currentEditor)

if (currentEditor != null) {
data.put(IDEEditor::class.java, currentEditor.editor)
data.put(File::class.java, currentEditor.editor.file)
}

getInstance().fillMenu(data, EDITOR_TOOLBAR, menu)
binding.editorToolbar.updateMenuDisplay()
}

private fun closeAll(runAfter: () -> Unit = {}) {
Expand Down
16 changes: 13 additions & 3 deletions app/src/main/java/com/itsaky/androidide/preferences/editor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import com.itsaky.androidide.preferences.internal.FLAG_PASSWORD
import com.itsaky.androidide.preferences.internal.FONT_LIGATURES
import com.itsaky.androidide.preferences.internal.FONT_SIZE
import com.itsaky.androidide.preferences.internal.PRINTABLE_CHARS
import com.itsaky.androidide.preferences.internal.HIDE_FILE_TREE_BUTTON
import com.itsaky.androidide.preferences.internal.TAB_SIZE
import com.itsaky.androidide.preferences.internal.USE_CUSTOM_FONT
import com.itsaky.androidide.preferences.internal.USE_ICU
Expand All @@ -51,6 +52,7 @@ import com.itsaky.androidide.preferences.internal.drawLineBreak
import com.itsaky.androidide.preferences.internal.drawTrailingWs
import com.itsaky.androidide.preferences.internal.fontLigatures
import com.itsaky.androidide.preferences.internal.fontSize
import com.itsaky.androidide.preferences.internal.hideFileTreeButton
import com.itsaky.androidide.preferences.internal.tabSize
import com.itsaky.androidide.preferences.internal.useCustomFont
import com.itsaky.androidide.preferences.internal.useIcu
Expand Down Expand Up @@ -92,6 +94,7 @@ private class CommonConfigurations(
addPreference(UseCustomFont())
addPreference(UseSoftTab())
addPreference(WordWrap())
addPreference(HideFileTreeButton())
addPreference(UseMagnifier())
addPreference(UseICU())
addPreference(AutoSave())
Expand Down Expand Up @@ -257,6 +260,14 @@ private class WordWrap(
override val icon: Int? = drawable.ic_wrap_text,
) : SwitchPreference(setValue = ::wordwrap::set, getValue = ::wordwrap::get)

@Parcelize
private class HideFileTreeButton(
override val key: String = HIDE_FILE_TREE_BUTTON,
override val title: Int = string.idepref_editor_hide_file_tree_button_title,
override val summary: Int? = string.idepref_editor_hide_file_tree_button_summary,
override val icon: Int? = drawable.ic_folder,
) : SwitchPreference(setValue = ::hideFileTreeButton::set, getValue = ::hideFileTreeButton::get)

@Parcelize
private class UseMagnifier(
override val key: String = USE_MAGNIFER,
Expand All @@ -279,11 +290,10 @@ private class CompletionsMatchLower(
override val title: Int = string.idepref_java_matchLower_title,
override val summary: Int? = string.idepref_java_matchLower_summary,
override val icon: Int? = drawable.ic_text_lower,
) :
SwitchPreference(
) : SwitchPreference(
setValue = ::completionsMatchLower::set,
getValue = ::completionsMatchLower::get
)
)

@Parcelize
private class VisibiblePasswordFlag(
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/layout/activity_editor.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
android:background="?attr/colorSurface"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$Behavior">

<com.google.android.material.appbar.MaterialToolbar
<com.itsaky.androidide.ui.ExtendedMenuToolbar
android:id="@+id/editor_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
Expand Down
130 changes: 130 additions & 0 deletions common/src/main/java/com/itsaky/androidide/ui/ExtendedMenuToolbar.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* 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.ui

import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Configuration
import android.util.AttributeSet
import android.view.MenuItem
import android.view.ViewTreeObserver
import androidx.appcompat.view.menu.MenuItemImpl
import androidx.appcompat.widget.ActionMenuView
import androidx.core.view.children
import androidx.core.view.iterator
import com.google.android.material.appbar.MaterialToolbar
import kotlin.math.floor

/** @author Smooth E */
class ExtendedMenuToolbar : MaterialToolbar {

private var cachedRequiredSpace: Float = 0f
private var cachedItemWidth: Int = 0

constructor(context: Context) : super(context)

constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)

override fun onAttachedToWindow() {
super.onAttachedToWindow()
}

override fun onConfigurationChanged(newConfig: Configuration?) {
super.onConfigurationChanged(newConfig)

viewTreeObserver.addOnGlobalLayoutListener(
object: ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
updateMenuDisplay()
viewTreeObserver.removeOnGlobalLayoutListener(this)
}
}
)
}

@SuppressLint("RestrictedApi")
fun updateMenuDisplay() {
if (cachedRequiredSpace == 0f || cachedItemWidth == 0) {
var requiredSpace = 0f
var actionMenuView: ActionMenuView? = null

for (child in children) {
if (child is ActionMenuView) {
actionMenuView = child
continue
}

requiredSpace = maxOf(child.x + child.width - x, requiredSpace)
}

cachedRequiredSpace = requiredSpace

if (actionMenuView?.getChildAt(0) == null) {
return
}

val itemWidth = actionMenuView.getChildAt(0).width

if (itemWidth == 0) {
return
}

cachedItemWidth = itemWidth
}

val maxItemCount = floor((width - cachedRequiredSpace) / cachedItemWidth).toInt()

var itemsModified = 0
for (item in menu.iterator()) {
val flag = getShowAsActionFlag(item)

val ignoreItem =
flag == MenuItemImpl.SHOW_AS_ACTION_NEVER ||
flag == MenuItemImpl.SHOW_AS_ACTION_WITH_TEXT

if (ignoreItem) {
continue
}

itemsModified++

if (itemsModified < maxItemCount) {
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
} else {
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
}
}
}

// https://stackoverflow.com/a/48392871
@SuppressLint("RestrictedApi")
private fun getShowAsActionFlag(item: MenuItem): Int {
val itemImpl = item as MenuItemImpl
return if (itemImpl.requiresActionButton()) MenuItemImpl.SHOW_AS_ACTION_ALWAYS
else if (itemImpl.requestsActionButton()) MenuItemImpl.SHOW_AS_ACTION_IF_ROOM
else if (itemImpl.showsTextAsAction()) MenuItemImpl.SHOW_AS_ACTION_WITH_TEXT
else MenuItemImpl.SHOW_AS_ACTION_NEVER
}

}
85 changes: 85 additions & 0 deletions common/src/main/java/com/itsaky/androidide/utils/NavigationBar.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* 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.annotation.SuppressLint
import android.content.Context
import androidx.annotation.IntDef

// Source: https://gist.github.com/Thorsten1976/07d61b3f697364e5f1c08ae076641d58

object NavigationBar {
/** Classic three-button navigation (Back, Home, Recent Apps) */
const val MODE_THREE_BUTTONS = 0

/** Two-button navigation (Android P navigation mode: Back, combined Home and Recent Apps) */
const val MODE_TWO_BUTTONS = 1

/** Full screen gesture mode (introduced with Android Q) */
const val MODE_GESTURES = 2

/**
* Returns the interaction mode of the system navigation bar as defined by
* [NavigationBarInteractionMode]. Depending on the Android version and OEM implementation,
* users might change the interaction mode via system settings: System>Gestures>System Navigation.
* This can lead to conflicts with apps that use specific system-gestures internally for
* navigation (i. e. swiping), especially if the Android 10 full screen gesture mode is enabled.
*
*
* Before Android P the system has used a classic three button navigation. Starting with Android P
* a two-button-based interaction mode was introduced (also referred as Android P navigation).
*
*
* Android Q changed the interaction and navigation concept to a gesture approach, the so called
* full screen gesture mode: system-wide gestures are used to navigate within an app or to
* interact with the system (i. e. back-navigation, open the home-screen, changing apps, or toggle
* a fullscreen mode).
*
*
* Based on [https://stackoverflow.com/questions/56689210/how-to-detect-full-screen-gesture-mode-in-android-10](https://stackoverflow.com/questions/56689210/how-to-detect-full-screen-gesture-mode-in-android-10)
*
* @param context The [Context] that is used to read the resource configuration.
* @return the [NavigationBarInteractionMode]
* @see .MODE_THREE_BUTTONS
*
* @see .MODE_TWO_BUTTONS
*
* @see .MODE_GESTURES
*/
@SuppressLint("DiscouragedApi")
@NavigationBarInteractionMode
fun getInteractionMode(context: Context): Int {
val resources = context.resources

val resourceId = resources.getIdentifier(
"config_navBarInteractionMode",
"integer",
"android"
)

return if (resourceId > 0)
resources.getInteger(resourceId)
else
MODE_THREE_BUTTONS
}

@Retention(AnnotationRetention.SOURCE)
@IntDef(MODE_THREE_BUTTONS, MODE_TWO_BUTTONS, MODE_GESTURES)
internal annotation class NavigationBarInteractionMode

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@

package com.itsaky.androidide.preferences.internal

import com.itsaky.androidide.app.BaseApplication
import com.itsaky.androidide.utils.NavigationBar

const val COMPLETIONS_MATCH_LOWER = "idepref_editor_completions_matchLower"

const val FLAG_WS_LEADING = "idepref_editor_wsLeading"
Expand All @@ -31,6 +34,7 @@ const val AUTO_SAVE = "idepref_editor_autoSave"
const val FONT_LIGATURES = "idepref_editor_fontLigatures"
const val FLAG_PASSWORD = "idepref_editor_flagPassword"
const val WORD_WRAP = "idepref_editor_word_wrap"
const val HIDE_FILE_TREE_BUTTON = "idepref_hide_file_tree_button"
const val USE_MAGNIFER = "idepref_editor_use_magnifier"
const val USE_ICU = "idepref_editor_useIcu"
const val USE_SOFT_TAB = "idepref_editor_useSoftTab"
Expand Down Expand Up @@ -113,6 +117,19 @@ var wordwrap: Boolean
prefManager.putBoolean(WORD_WRAP, value)
}

/** By default the File Tree button is hidden into the overflow menu on devices where
* Gesture Navigation is enabled at the moment of accessing the preference.
*/
var hideFileTreeButton: Boolean
get() {
val navigationMode = NavigationBar.getInteractionMode(BaseApplication.getBaseInstance())
val gestureNavigationDisabled = navigationMode != NavigationBar.MODE_GESTURES
return prefManager.getBoolean(HIDE_FILE_TREE_BUTTON, gestureNavigationDisabled)
}
set(value) {
prefManager.putBoolean(HIDE_FILE_TREE_BUTTON, value)
}

var useMagnifier: Boolean
get() = prefManager.getBoolean(USE_MAGNIFER, true)
set(value) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ import com.itsaky.androidide.managers.PreferenceManager

/** @author Akash Yadav */
val prefManager: PreferenceManager
get() = BaseApplication.getBaseInstance().prefManager
get() = BaseApplication.getBaseInstance().prefManager
Loading