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 @@ -43,8 +43,8 @@ class ArchiveGroupCardHeaderLayoutTest {
appState = null,
onDateSelected = {},
userPickedDate = LocalDate(2026, 3, 25),
selectedReleaseMajor = null,
availableReleaseMajors = emptyList(),
selectedReleaseVersion = null,
availableReleaseVersions = emptyList(),
appName = FOCUS,
errorMessage = null,
isLoading = false,
Expand Down Expand Up @@ -88,8 +88,8 @@ class ArchiveGroupCardHeaderLayoutTest {
appState = null,
onDateSelected = {},
userPickedDate = null,
selectedReleaseMajor = 147,
availableReleaseMajors = listOf(147, 146),
selectedReleaseVersion = "147.0.1",
availableReleaseVersions = listOf("147.0.1", "147.0", "146.0.1"),
appName = FOCUS_RELEASE,
errorMessage = null,
isLoading = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,18 +56,6 @@ class MozillaArchiveHtmlParser {
}
}

fun parseFenixReleaseMajorsFromHtml(html: String): List<Int> {
return parseFenixReleaseVersionsFromHtml(html, ReleaseType.Release)
.mapNotNull { version -> version.substringBefore('.').toIntOrNull() }
.distinct()
}

fun parseLatestFenixReleaseForMajor(html: String, majorVersion: Int): String {
return parseFenixReleaseVersionsFromHtml(html, ReleaseType.Release)
.firstOrNull { version -> version.substringBefore('.').toIntOrNull() == majorVersion }
?: ""
}

fun parseFenixReleaseAbisFromHtml(html: String, appName: String): List<String> {
// Pattern: {appName}-D+.D+(.D+)?-android-ABI/ or {appName}-D+.D+(.D+)?-android/
// Also supports beta/alpha markers: {appName}-D+.D+(.D+)?[ab]D+-android-ABI/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,88 +94,72 @@ class DefaultMozillaArchiveRepository(
}
}

override suspend fun getFenixReleaseMajorVersions(releaseType: ReleaseType): NetworkResult<List<Int>> {
override suspend fun getFenixReleaseVersions(releaseType: ReleaseType): NetworkResult<List<String>> {
return try {
val releasesHtml = mozillaArchivesApiService.getHtmlPage(RELEASES_FENIX_BASE_URL)
val releaseMajors = when (releaseType) {
ReleaseType.Release -> mozillaArchiveHtmlParser.parseFenixReleaseMajorsFromHtml(releasesHtml)
ReleaseType.Beta -> mozillaArchiveHtmlParser.parseFenixReleaseVersionsFromHtml(releasesHtml, releaseType)
.mapNotNull { version -> version.substringBefore('.').toIntOrNull() }
.distinct()
}
val releaseVersions = mozillaArchiveHtmlParser.parseFenixReleaseVersionsFromHtml(releasesHtml, releaseType)

if (releaseMajors.isEmpty()) {
return NetworkResult.Error("No release major versions found for type $releaseType", null)
if (releaseVersions.isEmpty()) {
return NetworkResult.Error("No release versions found for type $releaseType", null)
}

NetworkResult.Success(releaseMajors)
NetworkResult.Success(releaseVersions)
} catch (e: Exception) {
NetworkResult.Error("Failed to fetch Fenix release major versions: ${e.message}", e)
NetworkResult.Error("Failed to fetch Fenix release versions: ${e.message}", e)
}
}

override suspend fun getFocusReleaseMajorVersions(): NetworkResult<List<Int>> {
override suspend fun getFocusReleaseVersions(): NetworkResult<List<String>> {
return try {
val releasesHtml = mozillaArchivesApiService.getHtmlPage(RELEASES_FOCUS_BASE_URL)
val releaseMajors = mozillaArchiveHtmlParser.parseFenixReleaseMajorsFromHtml(releasesHtml)
val releaseVersions = mozillaArchiveHtmlParser.parseFenixReleaseVersionsFromHtml(releasesHtml, ReleaseType.Release)

if (releaseMajors.isEmpty()) {
return NetworkResult.Error("No Focus release major versions found", null)
if (releaseVersions.isEmpty()) {
return NetworkResult.Error("No Focus release versions found", null)
}

NetworkResult.Success(releaseMajors)
NetworkResult.Success(releaseVersions)
} catch (e: Exception) {
NetworkResult.Error("Failed to fetch Focus release major versions: ${e.message}", e)
NetworkResult.Error("Failed to fetch Focus release versions: ${e.message}", e)
}
}

override suspend fun getFenixReleaseBuildsForMajor(
majorVersion: Int,
override suspend fun getFenixReleaseBuildsForVersion(
version: String,
releaseType: ReleaseType,
): NetworkResult<List<MozillaArchiveApk>> {
return try {
val releasesHtml = mozillaArchivesApiService.getHtmlPage(RELEASES_FENIX_BASE_URL)
val resolvedVersion = when (releaseType) {
ReleaseType.Release -> mozillaArchiveHtmlParser.parseLatestFenixReleaseForMajor(releasesHtml, majorVersion)
ReleaseType.Beta -> mozillaArchiveHtmlParser.parseFenixReleaseVersionsFromHtml(releasesHtml, releaseType)
.firstOrNull { version -> version.substringBefore('.').toIntOrNull() == majorVersion }
?: ""
}

if (resolvedVersion.isEmpty()) {
return NetworkResult.Error("No releases found for major version $majorVersion", null)
if (version.isEmpty()) {
return NetworkResult.Error("No version provided", null)
}

fetchReleaseApksForVersion(
version = resolvedVersion,
version = version,
archiveBaseUrl = RELEASES_FENIX_BASE_URL,
archiveAppName = FENIX,
resultAppName = if (releaseType == ReleaseType.Release) FENIX_RELEASE else FENIX_BETA,
releaseType = releaseType,
)
} catch (e: Exception) {
NetworkResult.Error("Failed to fetch Fenix release $majorVersion: ${e.message}", e)
NetworkResult.Error("Failed to fetch Fenix release $version: ${e.message}", e)
}
}

override suspend fun getFocusReleaseBuildsForMajor(majorVersion: Int): NetworkResult<List<MozillaArchiveApk>> {
override suspend fun getFocusReleaseBuildsForVersion(version: String): NetworkResult<List<MozillaArchiveApk>> {
return try {
val releasesHtml = mozillaArchivesApiService.getHtmlPage(RELEASES_FOCUS_BASE_URL)
val resolvedVersion = mozillaArchiveHtmlParser.parseLatestFenixReleaseForMajor(releasesHtml, majorVersion)

if (resolvedVersion.isEmpty()) {
return NetworkResult.Error("No Focus release found for major version $majorVersion", null)
if (version.isEmpty()) {
return NetworkResult.Error("No version provided", null)
}

fetchReleaseApksForVersion(
version = resolvedVersion,
version = version,
archiveBaseUrl = RELEASES_FOCUS_BASE_URL,
archiveAppName = FOCUS,
resultAppName = FOCUS_RELEASE,
releaseType = ReleaseType.Release,
)
} catch (e: Exception) {
NetworkResult.Error("Failed to fetch Focus release $majorVersion: ${e.message}", e)
NetworkResult.Error("Failed to fetch Focus release $version: ${e.message}", e)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,18 @@ import org.mozilla.tryfox.util.FENIX_RELEASE
*/
class FenixReleaseReleaseRepository(
private val mozillaArchiveRepository: MozillaArchiveRepository,
) : MajorVersionAwareReleaseRepository {
) : VersionAwareReleaseRepository {
override val appName: String = FENIX_RELEASE

override suspend fun getLatestReleases(): NetworkResult<List<MozillaArchiveApk>> {
return mozillaArchiveRepository.getFenixReleaseBuilds(ReleaseType.Release)
}

override suspend fun getAvailableReleaseMajors(): NetworkResult<List<Int>> {
return mozillaArchiveRepository.getFenixReleaseMajorVersions(ReleaseType.Release)
override suspend fun getAvailableReleaseVersions(): NetworkResult<List<String>> {
return mozillaArchiveRepository.getFenixReleaseVersions(ReleaseType.Release)
}

override suspend fun getReleasesForMajor(majorVersion: Int): NetworkResult<List<MozillaArchiveApk>> {
return mozillaArchiveRepository.getFenixReleaseBuildsForMajor(majorVersion, ReleaseType.Release)
override suspend fun getReleasesForVersion(version: String): NetworkResult<List<MozillaArchiveApk>> {
return mozillaArchiveRepository.getFenixReleaseBuildsForVersion(version, ReleaseType.Release)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@ import org.mozilla.tryfox.util.FOCUS_RELEASE
*/
class FocusReleaseRepository(
private val mozillaArchiveRepository: MozillaArchiveRepository,
) : MajorVersionAwareReleaseRepository {
) : VersionAwareReleaseRepository {
override val appName: String = FOCUS_RELEASE

override suspend fun getLatestReleases(): NetworkResult<List<MozillaArchiveApk>> {
return mozillaArchiveRepository.getFocusReleaseBuilds()
}

override suspend fun getAvailableReleaseMajors(): NetworkResult<List<Int>> {
return mozillaArchiveRepository.getFocusReleaseMajorVersions()
override suspend fun getAvailableReleaseVersions(): NetworkResult<List<String>> {
return mozillaArchiveRepository.getFocusReleaseVersions()
}

override suspend fun getReleasesForMajor(majorVersion: Int): NetworkResult<List<MozillaArchiveApk>> {
return mozillaArchiveRepository.getFocusReleaseBuildsForMajor(majorVersion)
override suspend fun getReleasesForVersion(version: String): NetworkResult<List<MozillaArchiveApk>> {
return mozillaArchiveRepository.getFocusReleaseBuildsForVersion(version)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@ interface MozillaArchiveRepository {
*/
suspend fun getFenixReleaseBuilds(releaseType: ReleaseType = ReleaseType.Beta): NetworkResult<List<MozillaArchiveApk>>

suspend fun getFenixReleaseMajorVersions(releaseType: ReleaseType = ReleaseType.Release): NetworkResult<List<Int>>
suspend fun getFenixReleaseVersions(releaseType: ReleaseType = ReleaseType.Release): NetworkResult<List<String>>

suspend fun getFenixReleaseBuildsForMajor(
majorVersion: Int,
suspend fun getFenixReleaseBuildsForVersion(
version: String,
releaseType: ReleaseType = ReleaseType.Release,
): NetworkResult<List<MozillaArchiveApk>>

suspend fun getFocusReleaseBuilds(): NetworkResult<List<MozillaArchiveApk>>

suspend fun getFocusReleaseMajorVersions(): NetworkResult<List<Int>>
suspend fun getFocusReleaseVersions(): NetworkResult<List<String>>

suspend fun getFocusReleaseBuildsForMajor(majorVersion: Int): NetworkResult<List<MozillaArchiveApk>>
suspend fun getFocusReleaseBuildsForVersion(version: String): NetworkResult<List<MozillaArchiveApk>>
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ interface DateAwareReleaseRepository : ReleaseRepository {
suspend fun getReleases(date: LocalDate? = null): NetworkResult<List<MozillaArchiveApk>>
}

interface MajorVersionAwareReleaseRepository : ReleaseRepository {
suspend fun getAvailableReleaseMajors(): NetworkResult<List<Int>>
interface VersionAwareReleaseRepository : ReleaseRepository {
suspend fun getAvailableReleaseVersions(): NetworkResult<List<String>>

suspend fun getReleasesForMajor(majorVersion: Int): NetworkResult<List<MozillaArchiveApk>>
suspend fun getReleasesForVersion(version: String): NetworkResult<List<MozillaArchiveApk>>
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,14 @@ fun ArchiveGroupCard(
appState: AppState?,
onDateSelected: (LocalDate) -> Unit,
userPickedDate: LocalDate?,
selectedReleaseMajor: Int?,
availableReleaseMajors: List<Int>,
selectedReleaseVersion: String?,
availableReleaseVersions: List<String>,
appName: String,
errorMessage: String?,
isLoading: Boolean,
dateValidator: (LocalDate) -> Boolean,
onClearDate: () -> Unit,
onReleaseVersionSelected: (Int) -> Unit,
onReleaseVersionSelected: (String) -> Unit,
) {
ElevatedCard(
modifier =
Expand All @@ -130,8 +130,8 @@ fun ArchiveGroupCard(
date = dateFromApk,
onDateSelected = onDateSelected,
userPickedDate = userPickedDate,
selectedReleaseMajor = selectedReleaseMajor,
availableReleaseMajors = availableReleaseMajors,
selectedReleaseVersion = selectedReleaseVersion,
availableReleaseVersions = availableReleaseVersions,
hasReleaseVersionPicker = hasReleaseVersionPicker,
isDatePickerEnabled = isDatePickerEnabled,
dateValidator = dateValidator,
Expand Down Expand Up @@ -199,13 +199,13 @@ private fun ArchiveGroupHeader(
onDateSelected: (LocalDate) -> Unit,
onOpenAppClick: () -> Unit,
userPickedDate: LocalDate?,
selectedReleaseMajor: Int?,
availableReleaseMajors: List<Int>,
selectedReleaseVersion: String?,
availableReleaseVersions: List<String>,
hasReleaseVersionPicker: Boolean,
isDatePickerEnabled: Boolean,
dateValidator: (LocalDate) -> Boolean,
onClearDate: () -> Unit,
onReleaseVersionSelected: (Int) -> Unit,
onReleaseVersionSelected: (String) -> Unit,
) {
var showDatePicker by remember { mutableStateOf(false) }
val displayDate = userPickedDate?.toString() ?: date
Expand Down Expand Up @@ -233,8 +233,8 @@ private fun ArchiveGroupHeader(
Spacer(modifier = Modifier.size(8.dp))
ReleaseVersionSelector(
appName = appName,
selectedReleaseMajor = selectedReleaseMajor ?: version.substringBefore('.').toIntOrNull(),
availableReleaseMajors = availableReleaseMajors,
selectedReleaseVersion = selectedReleaseVersion ?: version.takeIf { it.isNotEmpty() },
availableReleaseVersions = availableReleaseVersions,
onReleaseVersionSelected = onReleaseVersionSelected,
)
} else {
Expand Down Expand Up @@ -327,19 +327,19 @@ private fun ArchiveGroupHeader(
@Composable
private fun ReleaseVersionSelector(
appName: String,
selectedReleaseMajor: Int?,
availableReleaseMajors: List<Int>,
onReleaseVersionSelected: (Int) -> Unit,
selectedReleaseVersion: String?,
availableReleaseVersions: List<String>,
onReleaseVersionSelected: (String) -> Unit,
) {
var expanded by remember { mutableStateOf(false) }
val selectedMajor = selectedReleaseMajor ?: availableReleaseMajors.firstOrNull()
val selectedVersion = selectedReleaseVersion ?: availableReleaseVersions.firstOrNull()

Box {
Surface(
modifier = Modifier
.clickable(enabled = availableReleaseMajors.isNotEmpty()) { expanded = true }
.clickable(enabled = availableReleaseVersions.isNotEmpty()) { expanded = true }
.semantics {
contentDescription = "Selected Firefox Release major version ${selectedMajor ?: ""}"
contentDescription = "Selected Firefox Release version ${selectedVersion ?: ""}"
}
.testTag("release_version_chip_${appName.lowercase()}"),
shape = MaterialTheme.shapes.medium,
Expand All @@ -354,7 +354,7 @@ private fun ReleaseVersionSelector(
modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp),
) {
Text(
text = selectedMajor?.toString() ?: "--",
text = selectedVersion ?: "--",
style = MaterialTheme.typography.labelLarge,
fontWeight = FontWeight.SemiBold,
)
Expand All @@ -375,12 +375,12 @@ private fun ReleaseVersionSelector(
.heightIn(max = 280.dp)
.verticalScroll(rememberScrollState()),
) {
availableReleaseMajors.forEach { majorVersion ->
availableReleaseVersions.forEach { version ->
DropdownMenuItem(
text = {
Text(
text = majorVersion.toString(),
fontWeight = if (majorVersion == selectedMajor) {
text = version,
fontWeight = if (version == selectedVersion) {
FontWeight.SemiBold
} else {
FontWeight.Normal
Expand All @@ -389,7 +389,7 @@ private fun ReleaseVersionSelector(
},
onClick = {
expanded = false
onReleaseVersionSelected(majorVersion)
onReleaseVersionSelected(version)
},
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 4.dp),
)
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/java/org/mozilla/tryfox/ui/models/AppUiModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ data class AppUiModel(
val installedDate: String?,
val apks: ApksResult,
val userPickedDate: LocalDate? = null,
val selectedReleaseMajor: Int? = null,
val availableReleaseMajors: List<Int> = emptyList(),
val selectedReleaseVersion: String? = null,
val availableReleaseVersions: List<String> = emptyList(),
)

val AppUiModel.newVersionAvailable: Boolean
Expand Down
12 changes: 6 additions & 6 deletions app/src/main/java/org/mozilla/tryfox/ui/screens/HomeScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,8 @@ fun HomeScreen(
},
dateValidator = homeViewModel.getDateValidator(app.name),
onClearDate = { appName -> homeViewModel.onClearDate(appName) },
onReleaseVersionSelected = { appName, majorVersion ->
homeViewModel.onReleaseVersionSelected(appName, majorVersion)
onReleaseVersionSelected = { appName, version ->
homeViewModel.onReleaseVersionSelected(appName, version)
},
)
}
Expand Down Expand Up @@ -254,7 +254,7 @@ fun AppComponent(
onDateSelected: (String, LocalDate) -> Unit,
dateValidator: (LocalDate) -> Boolean,
onClearDate: (String) -> Unit,
onReleaseVersionSelected: (String, Int) -> Unit,
onReleaseVersionSelected: (String, String) -> Unit,
) {
val apksResult = app.apks

Expand Down Expand Up @@ -287,9 +287,9 @@ fun AppComponent(
},
onDateSelected = { date -> onDateSelected(app.name, date) },
userPickedDate = app.userPickedDate,
selectedReleaseMajor = app.selectedReleaseMajor,
availableReleaseMajors = app.availableReleaseMajors,
onReleaseVersionSelected = { major -> onReleaseVersionSelected(app.name, major) },
selectedReleaseVersion = app.selectedReleaseVersion,
availableReleaseVersions = app.availableReleaseVersions,
onReleaseVersionSelected = { version -> onReleaseVersionSelected(app.name, version) },
appName = app.name,
errorMessage = (apksResult as? ApksResult.Error)?.message,
isLoading = apksResult is ApksResult.Loading,
Expand Down
Loading
Loading