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
7 changes: 7 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@
- On Windows, use `.\gradlew.bat` instead of `./gradlew`.
- CI runs JDK 21, writes `app/google-services.json` from secrets, initializes submodules, and runs `./gradlew testDebugUnitTest`; test failures are `continue-on-error`, so inspect uploaded reports.

## Crashlytics investigation

- For Crashlytics issue URLs, extract the issue id from `/issues/{issue-id}` and the event id from the `sessionEventKey` query parameter.
- Use Firebase Console or authenticated local tooling to inspect issue and event details. Keep Firebase project ids, app ids, OAuth tokens, raw logs, and private Crashlytics URLs out of commits, PR descriptions, and public docs.
- The Firebase CLI only exposes Crashlytics mapping/symbol upload commands, not issue/event reads. If REST API access is needed, use the current local Firebase CLI auth token without printing or copying token values.
- Inspect the issue title, fatal exception, app version, device/OS, stack trace, breadcrumbs, and logs. Verify the inferred app flow against app logs; if logs do not identify the triggering UI action, add a targeted `AppLog` at the app-owned boundary that launches the crashing flow.

## Release and open testing

- "Prepare the branch for release" means preparing a publishable Play/open-testing version, not installing on a device. Before creating an open testing release, bump `versionCode`, regenerate the release baseline profile with `:app:generateReleaseBaselineProfile` on a physical device, and include any changed generated baseline profile files.
Expand Down
8 changes: 3 additions & 5 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,9 @@
android:taskAffinity="com.anod.appwatcher.notification"
android:theme="@style/InvisibleTheme" />
<activity
android:name="com.google.android.gms.oss.licenses.OssLicensesActivity"
android:theme="@style/Theme.AppCompat.DayNight" />
<activity
android:name="com.google.android.gms.oss.licenses.OssLicensesMenuActivity"
android:theme="@style/Theme.AppCompat.DayNight" />
android:name="com.google.android.gms.oss.licenses.v2.OssLicensesMenuActivity"
android:theme="@style/Theme.AppCompat.DayNight"
tools:replace="android:theme" />

<provider
android:name="com.anod.appwatcher.database.DbContentProvider"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import com.anod.appwatcher.sync.SyncScheduler
import com.anod.appwatcher.sync.UpdatedApp
import com.anod.appwatcher.utils.BaseFlowViewModel
import com.anod.appwatcher.utils.prefs
import com.google.android.gms.oss.licenses.OssLicensesMenuActivity
import com.google.android.gms.oss.licenses.v2.OssLicensesMenuActivity
import info.anodsplace.applog.AppLog
import info.anodsplace.compose.PreferenceItem
import info.anodsplace.context.ApplicationContext
Expand Down Expand Up @@ -138,10 +138,13 @@ class SettingsViewModel : BaseFlowViewModel<SettingsViewState, SettingsViewEvent
navKey = SceneNavKey.UserLog
))

SettingsViewEvent.OssLicenses -> emitAction(
SettingsViewAction.StartActivity(
Intent(application, OssLicensesMenuActivity::class.java),
))
SettingsViewEvent.OssLicenses -> {
AppLog.i("Open source licenses selected", "Settings")
emitAction(
SettingsViewAction.StartActivity(
Intent(application, OssLicensesMenuActivity::class.java),
))
}
is SettingsViewEvent.SetRecreateFlag -> {
val result = setRecreateFlag(event.item, event.enabled)
event.update(result)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ abstract class ListEndpointPagingSource(private val listType: DfeListType, priva
AppLog.d("ListPagingSource load: [${params.key}] null")
return LoadResult.Page(emptyList(), null, null)
}
val nextPageUrl = params.key ?: ""
val response = execute(nextPageUrl = nextPageUrl).toListResponse(listType)
val requestedNextPageUrl = params.key ?: ""
val response = execute(nextPageUrl = requestedNextPageUrl).toListResponse(listType)

val items = response.items.map {
val installedInfo = installedApps.packageInfo(it.docId)
Expand All @@ -43,11 +43,15 @@ abstract class ListEndpointPagingSource(private val listType: DfeListType, priva
}

isFirst = false
AppLog.d("ListPagingSource load: [${params.key}] $nextPageUrl")
val nextPageUrl = resolveNextPageUrl(
requestedNextPageUrl = requestedNextPageUrl,
responseNextPageUrl = response.nextPageUrl
)
AppLog.d("ListPagingSource load: [${params.key}] $requestedNextPageUrl -> $nextPageUrl")
return LoadResult.Page(
data = items,
prevKey = null, // Only paging forward.
nextKey = response.nextPageUrl
nextKey = nextPageUrl
)
} catch (e: Exception) {
AppLog.e(e)
Expand All @@ -56,4 +60,11 @@ abstract class ListEndpointPagingSource(private val listType: DfeListType, priva
}

override fun getRefreshKey(state: PagingState<String, ListItem>): String? = null

companion object {
internal fun resolveNextPageUrl(requestedNextPageUrl: String, responseNextPageUrl: String?): String? {
val nextPageUrl = responseNextPageUrl?.takeIf { it.isNotBlank() }
return nextPageUrl?.takeUnless { it == requestedNextPageUrl }
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.anod.appwatcher.search

import org.junit.Assert.assertEquals
import org.junit.Test

class ListEndpointPagingSourceTest {

@Test
fun `resolveNextPageUrl returns new continuation url`() {
assertEquals(
"purchaseHistory?o=158",
ListEndpointPagingSource.resolveNextPageUrl(
requestedNextPageUrl = "purchaseHistory?o=79",
responseNextPageUrl = "purchaseHistory?o=158"
)
)
}

@Test
fun `resolveNextPageUrl stops paging when continuation url repeats`() {
assertEquals(
null,
ListEndpointPagingSource.resolveNextPageUrl(
requestedNextPageUrl = "purchaseHistory?o=158",
responseNextPageUrl = "purchaseHistory?o=158"
)
)
}

@Test
fun `resolveNextPageUrl stops paging when continuation url is missing`() {
assertEquals(
null,
ListEndpointPagingSource.resolveNextPageUrl(
requestedNextPageUrl = "purchaseHistory?o=158",
responseNextPageUrl = null
)
)
}

@Test
fun `resolveNextPageUrl stops paging when continuation url is blank`() {
assertEquals(
null,
ListEndpointPagingSource.resolveNextPageUrl(
requestedNextPageUrl = "purchaseHistory?o=158",
responseNextPageUrl = ""
)
)
}
}
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ paging = "3.3.6"
palette = "1.0.0"
play-services-auth = "21.5.0"
play-services-identity = "18.1.0"
play-services-oss-licenses = "17.3.0"
play-services-oss-licenses = "17.5.1"
Comment thread
anod marked this conversation as resolved.
process-phoenix = "3.0.0"
room = "2.8.4"
runtime-tracing = "1.11.2"
Expand Down
Loading