Separate issue and PR list/detail tabs, paginated, with comments (issues) and reviews+comments (PRs). Deleted/unavailable authors render a fixed placeholder instead of crashing or inventing identity; every status is a text label, never color-only. Reuses TASK-010 directly; no shared/domain file touched. Both list types dedupe by number per page. Entry point is a new button on the repository references screen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
209 lines
9.8 KiB
Kotlin
209 lines
9.8 KiB
Kotlin
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.RepositoryRef
|
|
import com.ubisam.ubgitea.feature.codebrowser.CodeBrowserScreen
|
|
import com.ubisam.ubgitea.feature.codebrowser.CodeBrowserTarget
|
|
import com.ubisam.ubgitea.feature.codebrowser.CodeBrowserUiState
|
|
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.HistoryUiState
|
|
import com.ubisam.ubgitea.feature.history.HistoryViewModel
|
|
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.WorkItemsUiState
|
|
import com.ubisam.ubgitea.feature.workitems.WorkItemsViewModel
|
|
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)
|
|
}
|
|
|
|
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()
|
|
var browsingCode by rememberSaveable {
|
|
mutableStateOf(codeBrowserState !is CodeBrowserUiState.Idle)
|
|
}
|
|
var browsingHistory by rememberSaveable {
|
|
mutableStateOf(historyState !is HistoryUiState.Idle)
|
|
}
|
|
var browsingWorkItems by rememberSaveable {
|
|
mutableStateOf(workItemsState !is WorkItemsUiState.Idle)
|
|
}
|
|
UBGiteaTheme {
|
|
if (browsingWorkItems) {
|
|
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()
|
|
browsingWorkItems = false
|
|
},
|
|
onAuthenticationExpired = {
|
|
workItemsViewModel.close()
|
|
browsingWorkItems = false
|
|
viewModel.logout()
|
|
},
|
|
)
|
|
} else if (browsingHistory) {
|
|
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()
|
|
browsingHistory = false
|
|
},
|
|
onCompare = historyViewModel::compareAgainstCurrentRef,
|
|
onCompareNextPage = historyViewModel::compareNextPage,
|
|
onComparePreviousPage = historyViewModel::comparePreviousPage,
|
|
onRetryCompare = historyViewModel::retryCompare,
|
|
onLoadComparePatch = historyViewModel::loadComparePatch,
|
|
onAuthenticationExpired = {
|
|
historyViewModel.close()
|
|
codeBrowserViewModel.close()
|
|
browsingHistory = false
|
|
browsingCode = false
|
|
viewModel.logout()
|
|
},
|
|
)
|
|
} else if (browsingCode) {
|
|
CodeBrowserScreen(
|
|
state = codeBrowserState,
|
|
onEntrySelected = codeBrowserViewModel::openEntry,
|
|
onBreadcrumbSelected = codeBrowserViewModel::openBreadcrumb,
|
|
onRetry = codeBrowserViewModel::retry,
|
|
onBack = {
|
|
if (!codeBrowserViewModel.navigateUp()) {
|
|
codeBrowserViewModel.close()
|
|
browsingCode = false
|
|
}
|
|
},
|
|
onAuthenticationExpired = {
|
|
codeBrowserViewModel.close()
|
|
browsingCode = false
|
|
viewModel.logout()
|
|
},
|
|
onViewHistory = { target ->
|
|
historyViewModel.open(HistoryTarget(target.repository, target.ref))
|
|
browsingHistory = true
|
|
},
|
|
)
|
|
} else {
|
|
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)
|
|
codeBrowserViewModel.open(
|
|
CodeBrowserTarget(
|
|
repository = repository.coordinates,
|
|
ref = RepositoryRef.of(name),
|
|
kind = if (kind == ReferenceKind.BRANCH) {
|
|
CodeReferenceKind.BRANCH
|
|
} else {
|
|
CodeReferenceKind.TAG
|
|
},
|
|
),
|
|
)
|
|
browsingCode = true
|
|
},
|
|
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 ->
|
|
workItemsViewModel.open(WorkItemsTarget(repository.coordinates))
|
|
browsingWorkItems = true
|
|
},
|
|
)
|
|
}
|
|
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)
|
|
}
|
|
}
|