Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,20 @@

package com.itsaky.androidide.activities.editor

import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.webkit.WebViewClient
import androidx.activity.OnBackPressedCallback
import androidx.core.net.toUri
import androidx.core.view.WindowCompat
import org.adfa.constants.CONTENT_KEY
import com.itsaky.androidide.resources.R
import com.itsaky.androidide.app.BaseIDEActivity
import com.itsaky.androidide.common.databinding.ActivityHelpBinding
import com.itsaky.androidide.utils.DeviceFormFactorUtils
import com.itsaky.androidide.utils.isSystemInDarkMode
import com.itsaky.androidide.utils.UrlManager
import org.adfa.constants.CONTENT_TITLE_KEY
Expand All @@ -34,6 +39,29 @@ class HelpActivity : BaseIDEActivity() {

companion object {
private val EXTERNAL_SCHEMES = listOf("mailto:", "tel:", "sms:")
private const val MULTI_WINDOW_URI = "cogo-help://tooltip/active-window"

fun launch(context: Context, url: String, title: String) {
val intent = Intent(context, HelpActivity::class.java).apply {
putExtra(CONTENT_KEY, url)
putExtra(CONTENT_TITLE_KEY, title)

val formFactor = DeviceFormFactorUtils.getCurrent(context)

if (formFactor.isLargeScreenLike) {
addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_NEW_DOCUMENT or
Intent.FLAG_ACTIVITY_SINGLE_TOP or
Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT
)
data = MULTI_WINDOW_URI.toUri()
} else if (context !is Activity) {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
}
context.startActivity(intent)
}
}

private var _binding: ActivityHelpBinding? = null
Expand Down Expand Up @@ -113,6 +141,22 @@ class HelpActivity : BaseIDEActivity() {
handleBackNavigation()
}
})
updateUIFromIntent(intent)
}

override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
updateUIFromIntent(intent)
}

private fun updateUIFromIntent(currentIntent: Intent) {
val pageTitle = currentIntent.getStringExtra(CONTENT_TITLE_KEY)
supportActionBar?.title = pageTitle ?: getString(R.string.help)

currentIntent.getStringExtra(CONTENT_KEY)?.let { url ->
binding.webView.loadUrl(url)
}
}

private fun handleUrlLoading(view: android.webkit.WebView?, url: String?): Boolean {
Expand Down
122 changes: 122 additions & 0 deletions common/src/main/java/com/itsaky/androidide/utils/DeviceFormFactor.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package com.itsaky.androidide.utils

import android.content.Context
import android.content.res.Configuration
import android.os.Build
import android.util.DisplayMetrics
import android.view.WindowManager

private const val TABLET_MIN_SMALLEST_WIDTH_DP = 500

data class DeviceFormFactor(
val isTablet: Boolean,
val isDexMode: Boolean,
) {
val isLargeScreenLike: Boolean
get() = isTablet || isDexMode
}

object DeviceFormFactorUtils {

fun getCurrent(context: Context): DeviceFormFactor {
return DeviceFormFactor(
isTablet = isTablet(context),
isDexMode = isDexMode(context),
)
}

fun isDexMode(context: Context): Boolean {
val uiModeType = context.resources.configuration.uiMode and Configuration.UI_MODE_TYPE_MASK
if (uiModeType == Configuration.UI_MODE_TYPE_DESK) {
return true
}

if (isSamsungDexModeModern(context)) {
return true
}

return isSamsungDexModeLegacy(context.resources.configuration)
}

fun isTablet(context: Context): Boolean {
if (context.resources.configuration.smallestScreenWidthDp >= TABLET_MIN_SMALLEST_WIDTH_DP) {
return true
}

return isPhysicalLargeScreen(context)
}

private fun isPhysicalLargeScreen(context: Context): Boolean {
return try {
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager

val smallestPhysicalWidthDp = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val metrics = windowManager.maximumWindowMetrics
val density = context.resources.configuration.densityDpi / 160f
val widthDp = metrics.bounds.width() / density
val heightDp = metrics.bounds.height() / density
widthDp.coerceAtMost(heightDp)
} else {
val display = windowManager.defaultDisplay
val realMetrics = DisplayMetrics()
@Suppress("DEPRECATION")
display.getRealMetrics(realMetrics)
val widthDp = realMetrics.widthPixels / realMetrics.density
val heightDp = realMetrics.heightPixels / realMetrics.density
widthDp.coerceAtMost(heightDp)
}

smallestPhysicalWidthDp >= TABLET_MIN_SMALLEST_WIDTH_DP
} catch (_: Exception) {
false
}
}

private fun isSamsungDexModeModern(context: Context): Boolean {
return try {
val desktopModeManager = context.applicationContext.getSystemService("desktopmode") ?: return false

val getDesktopModeStateMethod = desktopModeManager.javaClass.getDeclaredMethod("getDesktopModeState")
val desktopModeState = getDesktopModeStateMethod.invoke(desktopModeManager) ?: return false
val desktopModeStateClass = desktopModeState.javaClass

val getEnabledMethod = desktopModeStateClass.getDeclaredMethod("getEnabled")
val enabled = getEnabledMethod.invoke(desktopModeState) as Int
val enabledConstant = desktopModeStateClass.getDeclaredField("ENABLED").getInt(desktopModeStateClass)

enabled == enabledConstant
} catch (_: Exception) {
false
}
}

private fun isSamsungDexModeLegacy(configuration: Configuration): Boolean {
val enabledValue = readSamsungDesktopModeValue(
target = configuration.javaClass,
fieldName = "SEM_DESKTOP_MODE_ENABLED",
targetClass = configuration.javaClass,
) ?: return false

val currentValue = readSamsungDesktopModeValue(
target = configuration,
fieldName = "semDesktopModeEnabled",
targetClass = configuration.javaClass,
) ?: return false

return currentValue == enabledValue
}

private fun readSamsungDesktopModeValue(
target: Any?,
fieldName: String,
targetClass: Class<*>,
): Int? {
return runCatching {
targetClass.getField(fieldName).getInt(target)
}.recoverCatching {
targetClass.getDeclaredField(fieldName).apply {
isAccessible = true
}.getInt(target)
}.getOrNull()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.database.sqlite.SQLiteDatabase
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
Expand Down Expand Up @@ -38,8 +37,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.adfa.constants.CONTENT_KEY
import org.adfa.constants.CONTENT_TITLE_KEY
import java.io.File


Expand Down Expand Up @@ -217,19 +214,11 @@ object TooltipManager {
tooltipItem = tooltipItem,
requestFocus = requestFocus,
onHelpLinkClicked = { context, url, title ->
val intent =
Intent(context, HelpActivity::class.java).apply {
putExtra(CONTENT_KEY, url)
putExtra(CONTENT_TITLE_KEY, context.getString(R.string.back_to_cogo))
if (context !is android.app.Activity) {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
}
context.startActivity(intent)
HelpActivity.launch(context, url, title)
}
)
} else {
Log.e("TooltipManager", "Tooltip item $tooltipItem is null")
Log.e(TAG, "Tooltip item $tooltipItem is null")
}
}
}
Expand Down
Loading