Skip to content
141 changes: 83 additions & 58 deletions app/src/main/java/com/itsaky/androidide/activities/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,9 @@ package com.itsaky.androidide.activities
import android.content.Intent
import android.content.res.Configuration
import android.os.Bundle
import android.util.Log
import android.view.KeyEvent
import android.view.View
import androidx.activity.OnBackPressedCallback
import org.koin.androidx.viewmodel.ext.android.viewModel
import androidx.core.graphics.Insets
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible
Expand All @@ -34,35 +32,39 @@ import androidx.transition.doOnEnd
import com.google.android.material.transition.MaterialSharedAxis
import com.itsaky.androidide.FeedbackButtonManager
import com.itsaky.androidide.R
import com.itsaky.androidide.activities.editor.EditorActivityKt
import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.activities.editor.EditorActivityKt
import com.itsaky.androidide.analytics.IAnalyticsManager
import com.itsaky.androidide.app.EdgeToEdgeIDEActivity
import com.itsaky.androidide.databinding.ActivityMainBinding
import com.itsaky.androidide.fragments.MainFragment
import com.itsaky.androidide.fragments.RecentProjectsFragment
import com.itsaky.androidide.idetooltips.TooltipManager
import com.itsaky.androidide.idetooltips.TooltipTag.PROJECT_RECENT_TOP
import com.itsaky.androidide.idetooltips.TooltipTag.SETUP_OVERVIEW
import com.itsaky.androidide.localWebServer.ServerConfig
import com.itsaky.androidide.localWebServer.WebServer
import com.itsaky.androidide.preferences.internal.GeneralPreferences
import com.itsaky.androidide.projects.ProjectManagerImpl
import com.itsaky.androidide.resources.R.string
import com.itsaky.androidide.roomData.recentproject.RecentProject
import com.itsaky.androidide.shortcuts.IdeShortcutActions
import com.itsaky.androidide.shortcuts.ShortcutContext
import com.itsaky.androidide.shortcuts.ShortcutExecutionContext
import com.itsaky.androidide.shortcuts.ShortcutManager
import com.itsaky.androidide.templates.ITemplateProvider
import com.itsaky.androidide.utils.DialogUtils
import com.itsaky.androidide.utils.Environment
import com.itsaky.androidide.utils.FeatureFlags
import com.itsaky.androidide.utils.MainScreenActions
import com.itsaky.androidide.utils.UrlManager
import com.itsaky.androidide.utils.applyBottomWindowInsetsPadding
import com.itsaky.androidide.utils.findValidProjects
import com.itsaky.androidide.utils.flashInfo
import com.itsaky.androidide.utils.applyBottomWindowInsetsPadding
import com.itsaky.androidide.utils.MainScreenActions
import com.itsaky.androidide.fragments.MainFragment
import com.itsaky.androidide.fragments.RecentProjectsFragment
import com.itsaky.androidide.roomData.recentproject.RecentProject
import com.itsaky.androidide.shortcuts.IdeShortcutActions
import com.itsaky.androidide.shortcuts.ShortcutContext
import com.itsaky.androidide.shortcuts.ShortcutExecutionContext
import com.itsaky.androidide.shortcuts.ShortcutManager
import com.itsaky.androidide.utils.getCreatedTime
import com.itsaky.androidide.utils.getLastModifiedTime
import com.itsaky.androidide.utils.hasVisibleDialog
import com.itsaky.androidide.utils.readProjectLanguage
import com.itsaky.androidide.viewmodel.MainViewModel
import com.itsaky.androidide.viewmodel.MainViewModel.Companion.SCREEN_CLONE_REPO
import com.itsaky.androidide.viewmodel.MainViewModel.Companion.SCREEN_DELETE_PROJECTS
Expand All @@ -74,12 +76,10 @@ import com.itsaky.androidide.viewmodel.MainViewModel.Companion.TOOLTIPS_WEB_VIEW
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import com.itsaky.androidide.localWebServer.ServerConfig
import com.itsaky.androidide.localWebServer.WebServer
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.slf4j.LoggerFactory
import java.io.File
import com.itsaky.androidide.utils.hasVisibleDialog

class MainActivity : EdgeToEdgeIDEActivity() {
private val log = LoggerFactory.getLogger(MainActivity::class.java)
Expand Down Expand Up @@ -119,15 +119,17 @@ class MainActivity : EdgeToEdgeIDEActivity() {
private val binding: ActivityMainBinding
get() = checkNotNull(_binding)

override fun onCreate(savedInstanceState: Bundle?) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

MainScreenActions.register(this)

// Start WebServer after installation is complete
startWebServer()

if (savedInstanceState == null) { openLastProject() }
if (savedInstanceState == null) {
openLastProject()
}

if (FeatureFlags.isExperimentsEnabled) {
binding.codeOnTheGoLabel.title = getString(R.string.app_name) + "."
Expand Down Expand Up @@ -172,21 +174,21 @@ class MainActivity : EdgeToEdgeIDEActivity() {
}
}

override fun dispatchKeyEvent(event: KeyEvent): Boolean {
return shortcutManager.dispatch(
override fun dispatchKeyEvent(event: KeyEvent): Boolean =
shortcutManager.dispatch(
event = event,
context = ShortcutContext.MAIN,
focusView = currentFocus,
hasModal = supportFragmentManager.hasVisibleDialog(),
executionContext = mainShortcutExecutionContext,
) || super.dispatchKeyEvent(event)
}

private val mainShortcutExecutionContext by lazy {
ShortcutExecutionContext(
ideShortcutActions = IdeShortcutActions {
ActionData.create(this)
},
ideShortcutActions =
IdeShortcutActions {
ActionData.create(this)
},
)
}

Expand Down Expand Up @@ -245,17 +247,23 @@ class MainActivity : EdgeToEdgeIDEActivity() {
*/
private fun recreateVisibleFragmentView() {
when (viewModel.currentScreen.value) {
SCREEN_MAIN ->
supportFragmentManager.beginTransaction()
SCREEN_MAIN -> {
supportFragmentManager
.beginTransaction()
.setReorderingAllowed(true)
.replace(R.id.main, MainFragment())
.commitNow()
SCREEN_SAVED_PROJECTS ->
supportFragmentManager.beginTransaction()
}

SCREEN_SAVED_PROJECTS -> {
supportFragmentManager
.beginTransaction()
.setReorderingAllowed(true)
.replace(R.id.saved_projects_view, RecentProjectsFragment())
.commitNow()
else -> { }
}

else -> {}
}
}

Expand Down Expand Up @@ -318,7 +326,7 @@ class MainActivity : EdgeToEdgeIDEActivity() {
TOOLTIPS_WEB_VIEW -> binding.tooltipWebView
SCREEN_SAVED_PROJECTS -> binding.savedProjectsView
SCREEN_DELETE_PROJECTS -> binding.deleteProjectsView
SCREEN_CLONE_REPO -> binding.cloneRepositoryView
SCREEN_CLONE_REPO -> binding.cloneRepositoryView
else -> throw IllegalArgumentException("Invalid screen id: '$screen'")
}

Expand All @@ -329,7 +337,7 @@ class MainActivity : EdgeToEdgeIDEActivity() {
binding.tooltipWebView,
binding.savedProjectsView,
binding.deleteProjectsView,
binding.cloneRepositoryView,
binding.cloneRepositoryView,
)) {
fragment.isVisible = fragment == currentFragment
}
Expand Down Expand Up @@ -365,20 +373,25 @@ class MainActivity : EdgeToEdgeIDEActivity() {
val validProjects = findValidProjects(Environment.PROJECTS_DIR)
val lastOpenedPath = GeneralPreferences.lastOpenedProject

val projectToOpen = validProjects.find { it.absolutePath == lastOpenedPath }
?: validProjects.maxByOrNull { it.lastModified() }
val projectToOpen =
validProjects.find { it.absolutePath == lastOpenedPath }
?: validProjects.maxByOrNull { it.lastModified() }

withContext(Dispatchers.Main) {
when {
projectToOpen != null -> handleOpenProject(projectToOpen)
projectToOpen != null -> {
handleOpenProject(projectToOpen)
}

lastOpenedPath.isNotBlank() && lastOpenedPath != GeneralPreferences.NO_OPENED_PROJECT -> {
if (!File(lastOpenedPath).exists()) {
flashInfo(string.msg_opened_project_does_not_exist)
}
}
lastOpenedPath.isNotBlank() && lastOpenedPath != GeneralPreferences.NO_OPENED_PROJECT -> {
if (!File(lastOpenedPath).exists()) {
flashInfo(string.msg_opened_project_does_not_exist)
}
}

else -> Unit
else -> {
Unit
}
}
}
}
Expand All @@ -402,23 +415,29 @@ class MainActivity : EdgeToEdgeIDEActivity() {
builder.show()
}

internal fun openProject(root: File, project: RecentProject? = null, hasTemplateIssues: Boolean = false) {
internal fun openProject(
root: File,
project: RecentProject? = null,
hasTemplateIssues: Boolean = false,
) {
ProjectManagerImpl.getInstance().projectPath = root.absolutePath
GeneralPreferences.lastOpenedProject = root.absolutePath

lifecycleScope.launch(Dispatchers.IO) {
val location = root.absolutePath
val recentProject = project ?: RecentProject(
name = root.name,
location = location,
createdAt = getCreatedTime(location).toString(),
lastModified = getLastModifiedTime(location).toString()
)
viewModel.saveProjectToRecents(recentProject)
}
GeneralPreferences.lastOpenedProject = root.absolutePath

lifecycleScope.launch(Dispatchers.IO) {
val location = root.absolutePath
val recentProject =
project ?: RecentProject(
name = root.name,
location = location,
createdAt = getCreatedTime(location).toString(),
lastModified = getLastModifiedTime(location).toString(),
language = readProjectLanguage(root),
)
viewModel.saveProjectToRecents(recentProject)
}

// Track project open in Firebase Analytics
analyticsManager.trackProjectOpened(root.absolutePath)
analyticsManager.trackProjectOpened(root.absolutePath)

if (isFinishing) {
return
Expand All @@ -427,21 +446,27 @@ class MainActivity : EdgeToEdgeIDEActivity() {
val intent =
Intent(this, EditorActivityKt::class.java).apply {
putExtra("PROJECT_PATH", root.absolutePath)
if (hasTemplateIssues) {
putExtra("HAS_TEMPLATE_ISSUES", true)
}
if (hasTemplateIssues) {
putExtra("HAS_TEMPLATE_ISSUES", true)
}
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
}

startActivity(intent)
}

private fun startWebServer() {
private fun startWebServer() {
lifecycleScope.launch(Dispatchers.IO) {
try {
val dbFile = Environment.DOC_DB
log.info("Starting WebServer - using database file from: {}", dbFile.absolutePath)
val server = WebServer(ServerConfig(databasePath = dbFile.absolutePath, fileDirPath = applicationContext.filesDir.absolutePath))
val server =
WebServer(
ServerConfig(
databasePath = dbFile.absolutePath,
fileDirPath = applicationContext.filesDir.absolutePath,
),
)
webServer = server
server.start()
} catch (e: Exception) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,46 @@ import androidx.room.Query

@Dao
interface RecentProjectDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(project: RecentProject)

@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(project: RecentProject)
@Query("DELETE FROM recent_project_table WHERE name = :name")
suspend fun deleteByName(name: String)

@Query("DELETE FROM recent_project_table WHERE name = :name")
suspend fun deleteByName(name: String)
@Query("SELECT * FROM recent_project_table order by last_modified DESC, create_at DESC")
suspend fun dumpAll(): List<RecentProject>?

@Query("SELECT * FROM recent_project_table order by last_modified DESC, create_at DESC")
suspend fun dumpAll(): List<RecentProject>?
@Query("SELECT * FROM recent_project_table WHERE name = :name LIMIT 1")
suspend fun getProjectByName(name: String): RecentProject?

@Query("SELECT * FROM recent_project_table WHERE name = :name LIMIT 1")
suspend fun getProjectByName(name: String): RecentProject?
@Query("SELECT * FROM recent_project_table WHERE name IN (:names)")
suspend fun getProjectsByNames(names: List<String>): List<RecentProject>

@Query("SELECT * FROM recent_project_table WHERE name IN (:names)")
suspend fun getProjectsByNames(names: List<String>): List<RecentProject>
@Query("DELETE FROM recent_project_table")
suspend fun deleteAll()

@Query("DELETE FROM recent_project_table")
suspend fun deleteAll()
@Query("DELETE FROM recent_project_table WHERE name IN (:names)")
suspend fun deleteByNames(names: List<String>)

@Query("DELETE FROM recent_project_table WHERE name IN (:names)")
suspend fun deleteByNames(names: List<String>)
@Query("UPDATE recent_project_table SET name = :newName, location = :newLocation WHERE name = :oldName")
suspend fun updateNameAndLocation(
oldName: String,
newName: String,
newLocation: String,
)

@Query("UPDATE recent_project_table SET name = :newName, location = :newLocation WHERE name = :oldName")
suspend fun updateNameAndLocation(oldName: String, newName: String, newLocation: String)
@Query("UPDATE recent_project_table SET last_modified = :lastModified WHERE name = :projectName")
suspend fun updateLastModified(
projectName: String,
lastModified: String,
)

@Query("UPDATE recent_project_table SET last_modified = :lastModified WHERE name = :projectName")
suspend fun updateLastModified(projectName: String, lastModified: String)

@Query("SELECT COUNT(*) FROM recent_project_table")
suspend fun getCount(): Int
@Query("UPDATE recent_project_table SET language = :language WHERE location = :location")
suspend fun updateLanguage(
location: String,
language: String,
)

@Query("SELECT COUNT(*) FROM recent_project_table")
suspend fun getCount(): Int
}
Loading
Loading