package com.ubisam.ubgitea import android.content.ActivityNotFoundException import android.content.Intent import android.net.Uri import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.activity.viewModels import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.runtime.saveable.rememberSaveable import com.ubisam.ubgitea.domain.model.RepositoryCoordinates import com.ubisam.ubgitea.domain.model.RepositoryRef import com.ubisam.ubgitea.feature.codebrowser.CodeBrowserScreen import com.ubisam.ubgitea.feature.codebrowser.CodeBrowserTarget import com.ubisam.ubgitea.feature.codebrowser.CodeBrowserViewModel import com.ubisam.ubgitea.feature.codebrowser.CodeReferenceKind import com.ubisam.ubgitea.feature.history.HistoryScreen import com.ubisam.ubgitea.feature.history.HistoryTarget import com.ubisam.ubgitea.feature.history.HistoryViewModel import com.ubisam.ubgitea.feature.notifications.NotificationsScreen import com.ubisam.ubgitea.feature.notifications.NotificationsViewModel import com.ubisam.ubgitea.feature.repositories.ReferenceKind import com.ubisam.ubgitea.feature.repositories.RepositoryListScreen import com.ubisam.ubgitea.feature.repositories.RepositoryListViewModel import com.ubisam.ubgitea.feature.workitems.WorkItemsScreen import com.ubisam.ubgitea.feature.workitems.WorkItemsTarget import com.ubisam.ubgitea.feature.workitems.WorkItemsViewModel import com.ubisam.ubgitea.ui.navigation.Route import com.ubisam.ubgitea.ui.navigation.RouteStackSaver import com.ubisam.ubgitea.ui.theme.UBGiteaTheme class MainActivity : ComponentActivity() { private val viewModel: RepositoryListViewModel by viewModels { RepositoryListViewModel.Factory(application) } private val codeBrowserViewModel: CodeBrowserViewModel by viewModels { CodeBrowserViewModel.Factory(application) } private val historyViewModel: HistoryViewModel by viewModels { HistoryViewModel.Factory(application) } private val workItemsViewModel: WorkItemsViewModel by viewModels { WorkItemsViewModel.Factory(application) } private val notificationsViewModel: NotificationsViewModel by viewModels { NotificationsViewModel.Factory(application) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { val state by viewModel.state.collectAsState() val codeBrowserState by codeBrowserViewModel.state.collectAsState() val historyState by historyViewModel.state.collectAsState() val workItemsState by workItemsViewModel.state.collectAsState() val notificationsState by notificationsViewModel.state.collectAsState() var routeStack by rememberSaveable(stateSaver = RouteStackSaver) { mutableStateOf(listOf(Route.Repositories)) } fun push(route: Route) { routeStack = routeStack + route } fun popToRepositories() { routeStack = listOf(Route.Repositories) } fun pop() { routeStack = routeStack.dropLast(1).ifEmpty { listOf(Route.Repositories) } } val currentRoute = routeStack.last() LaunchedEffect(currentRoute) { when (val route = currentRoute) { Route.Repositories -> Unit // RepositoryListViewModel restores its own session on creation. is Route.CodeBrowser -> codeBrowserViewModel.open( CodeBrowserTarget( repository = RepositoryCoordinates.of(route.owner, route.name), ref = RepositoryRef.of(route.ref), kind = CodeReferenceKind.valueOf(route.refKind), ), ) is Route.History -> historyViewModel.open( HistoryTarget(RepositoryCoordinates.of(route.owner, route.name), RepositoryRef.of(route.ref)), ) is Route.WorkItems -> workItemsViewModel.open( WorkItemsTarget(RepositoryCoordinates.of(route.owner, route.name)), ) Route.Notifications -> notificationsViewModel.open() } } fun authenticationExpired() { codeBrowserViewModel.close() historyViewModel.close() workItemsViewModel.close() notificationsViewModel.close() popToRepositories() viewModel.logout() } UBGiteaTheme { when (currentRoute) { Route.Notifications -> NotificationsScreen( state = notificationsState, onShowUnreadOnly = notificationsViewModel::showUnreadOnly, onShowAll = notificationsViewModel::showAll, onNotificationSelected = { notification -> push(Route.WorkItems(notification.repositoryOwner, notification.repositoryName)) }, onNextPage = notificationsViewModel::nextPage, onPreviousPage = notificationsViewModel::previousPage, onRetry = notificationsViewModel::retry, onBack = { notificationsViewModel.close() pop() }, onAuthenticationExpired = ::authenticationExpired, ) is Route.WorkItems -> WorkItemsScreen( state = workItemsState, onShowIssues = workItemsViewModel::showIssues, onShowPullRequests = workItemsViewModel::showPullRequests, onIssueSelected = workItemsViewModel::openIssue, onPullRequestSelected = workItemsViewModel::openPullRequest, onNextPage = workItemsViewModel::nextPage, onPreviousPage = workItemsViewModel::previousPage, onRetryList = workItemsViewModel::retryList, onRetryDetail = workItemsViewModel::retryDetail, onBackToList = workItemsViewModel::backToList, onBack = { workItemsViewModel.close() pop() }, onAuthenticationExpired = ::authenticationExpired, ) is Route.History -> HistoryScreen( state = historyState, onCommitSelected = historyViewModel::openCommit, onNextPage = historyViewModel::nextPage, onPreviousPage = historyViewModel::previousPage, onRetryList = historyViewModel::retryList, onRetryDetail = historyViewModel::retryDetail, onRetryPatch = historyViewModel::retryPatch, onBackToList = historyViewModel::backToList, onBack = { historyViewModel.close() pop() }, onCompare = historyViewModel::compareAgainstCurrentRef, onCompareNextPage = historyViewModel::compareNextPage, onComparePreviousPage = historyViewModel::comparePreviousPage, onRetryCompare = historyViewModel::retryCompare, onLoadComparePatch = historyViewModel::loadComparePatch, onAuthenticationExpired = ::authenticationExpired, ) is Route.CodeBrowser -> CodeBrowserScreen( state = codeBrowserState, onEntrySelected = codeBrowserViewModel::openEntry, onBreadcrumbSelected = codeBrowserViewModel::openBreadcrumb, onRetry = codeBrowserViewModel::retry, onBack = { if (!codeBrowserViewModel.navigateUp()) { codeBrowserViewModel.close() pop() } }, onAuthenticationExpired = ::authenticationExpired, onViewHistory = { target -> push(Route.History(target.repository.owner, target.repository.name, target.ref.value)) }, ) Route.Repositories -> RepositoryListScreen( state = state, onSignIn = viewModel::signIn, onCancelSignIn = viewModel::cancelSignIn, onRetry = viewModel::retry, onLogout = viewModel::logout, onRepositorySelected = viewModel::selectRepository, onReferenceSelected = { repository, name, kind -> viewModel.selectReference(name, kind) push( Route.CodeBrowser( owner = repository.coordinates.owner, name = repository.coordinates.name, refKind = if (kind == ReferenceKind.BRANCH) { CodeReferenceKind.BRANCH.name } else { CodeReferenceKind.TAG.name }, ref = name, ), ) }, onReferencesRefresh = viewModel::refreshReferences, onBackToRepositories = viewModel::backToRepositories, onNextPage = viewModel::nextPage, onPreviousPage = viewModel::previousPage, onRefreshRepositories = viewModel::refreshRepositories, onRetryLastPushedBranch = viewModel::retryRowLastPushedBranch, onShowAllRepositories = viewModel::showAllRepositories, onShowFavorites = viewModel::showFavorites, onToggleFavorite = viewModel::toggleFavorite, onViewWorkItems = { repository -> push(Route.WorkItems(repository.coordinates.owner, repository.coordinates.name)) }, onSelectOrganization = viewModel::selectOrganization, onViewNotifications = { push(Route.Notifications) }, ) } LaunchedEffect(viewModel) { viewModel.browserRequests.collect(::openSystemBrowser) } } } consumeCallback(intent) } override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) setIntent(intent) consumeCallback(intent) } private fun openSystemBrowser(uri: String) { try { startActivity( Intent(Intent.ACTION_VIEW, Uri.parse(uri)).apply { addCategory(Intent.CATEGORY_BROWSABLE) }, ) } catch (_: ActivityNotFoundException) { viewModel.browserLaunchFailed() } } private fun consumeCallback(intent: Intent?) { val callback = intent?.dataString ?: return setIntent(Intent(intent).setData(null)) viewModel.handleCallback(callback) } }