Compare commits

...
915 Commits
Author SHA1 Message Date
GitHub Actions 9ba9791de8 feat(release): bump version 2026-05-29 16:39:35 +00:00
Harsh Shandilya 43408c2bec chore: update changelog 2026-05-29 21:59:24 +05:30
Harsh Shandilya 7ec580d9c6 fix: update selectors and test assertions 2026-05-29 21:52:59 +05:30
Harsh Shandilya 18b33e550a fix: refresh test fixtures 2026-05-29 21:52:59 +05:30
Harsh Shandilya 6d932c84d5 feat: revert comments UI back to current stable for hotfix release
This will be reverted back after the release

Revert "feat: re-do comment UI to look like Boost for Reddit"

This reverts commit 03e0a13e1d.

Revert "build: add compose screenshot testing support"

This reverts commit 5992a5fa15.

Revert "feat: rework comment entry layout and enable upvote action"

This reverts commit 4904c76220.

Revert "feat: re-do comment upvote UI"

This reverts commit c32ed849d2.
2026-05-29 21:52:59 +05:30
Harsh Shandilya 03e0a13e1d feat: re-do comment UI to look like Boost for Reddit 2026-05-29 19:51:05 +05:30
Harsh Shandilya 5992a5fa15 build: add compose screenshot testing support 2026-05-29 19:44:16 +05:30
renovate[bot]andGitHub 6b84d5277b chore(deps): update dependency com.facebook:ktfmt to v0.63 2026-05-28 21:04:01 +00:00
renovate[bot]andGitHub f41c36c24c fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v6.9.0 2026-05-28 14:40:29 +00:00
renovate[bot]andGitHub 9b4449c890 chore(deps): update plugin dependencyanalysis to v3.14.0 2026-05-28 06:08:21 +00:00
Harsh Shandilya 4904c76220 feat: rework comment entry layout and enable upvote action 2026-05-28 00:12:13 +05:30
Harsh Shandilya c5a8dea7d2 Merge remote-tracking branches 'origin/renovate/com.diffplug.spotless-spotless-plugin-gradle-8.x' and 'origin/renovate/sentry'
* origin/renovate/com.diffplug.spotless-spotless-plugin-gradle-8.x:
  fix(deps): update dependency com.diffplug.spotless:spotless-plugin-gradle to v8.6.0

* origin/renovate/sentry:
  fix(deps): update sentry to v8.43.0
2026-05-27 23:18:05 +05:30
renovate[bot]andGitHub 8930c0c5dc fix(deps): update sentry to v8.43.0 2026-05-27 17:47:05 +00:00
renovate[bot]andGitHub 5dc4afbf36 fix(deps): update dependency com.diffplug.spotless:spotless-plugin-gradle to v8.6.0 2026-05-27 17:47:01 +00:00
renovate[bot]andHarsh Shandilya c3ec92582f fix(deps): update dependency com.slack.lint.compose:compose-lint-checks to v1.5.0 2026-05-27 23:15:00 +05:30
renovate[bot]andGitHub b8ee16be84 chore(deps): update plugin ksp to v2.3.9 2026-05-26 21:55:32 +00:00
Harsh Shandilya c32ed849d2 feat: re-do comment upvote UI 2026-05-27 00:33:46 +05:30
Harsh Shandilya 997b37ccb0 feat: switch expand/collapse UI to match Lobsters 2026-05-27 00:24:06 +05:30
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
976ed3b31a chore(deps): update plugin com.gradle.develocity to v4.4.2 (#1174)
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| com.gradle.develocity | `4.4.1` → `4.4.2` |
![age](https://developer.mend.io/api/mc/badges/age/maven/com.gradle.develocity:com.gradle.develocity.gradle.plugin/4.4.2?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.gradle.develocity:com.gradle.develocity.gradle.plugin/4.4.1/4.4.2?slim=true)
|

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xOTQuMCIsInVwZGF0ZWRJblZlciI6IjQzLjE5NC4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-26 15:16:49 +00:00
Harsh Shandilya 75863cdf94 refactor: move CommentEntry composable to CommentEntry.kt 2026-05-26 17:13:04 +05:30
GitHub Actions a3687d0b4c feat(release): start next development iteration 2026-05-26 08:56:03 +00:00
GitHub Actions 2b571be006 feat(release): bump version 2026-05-26 08:49:32 +00:00
renovate[bot]andGitHub 820744409e fix(deps): update aboutlibraries to v14.2.1 2026-05-26 03:45:25 +00:00
Harsh Shandilya 1b6ae68e0d Fix login session detection and comment timestamps
Only persist Lobsters login cookies after the WebView proves the
session is authenticated, and treat anonymous lobster_trap cookies as
logged out state. Also clear the WebView cookie jar on logout so the
embedded login flow cannot silently restore a prior session.

Simplify comment timestamps to match the current site markup by
tracking one timestamp plus an edited flag instead of fabricating a
creation time we no longer receive.
2026-05-23 13:04:38 +05:30
renovate[bot]andGitHub 8c67a874a5 chore(deps): update plugin dependencyanalysis to v3.13.0 2026-05-21 22:43:29 +00:00
renovate[bot]andGitHub 8296f58ad9 fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v6.8.1 2026-05-21 18:11:24 +00:00
renovate[bot]andGitHub 1cd40a1383 fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v6.8.0 2026-05-21 13:28:46 +00:00
Harsh Shandilya dce9dc33fe chore: refresh baseline profiles 2026-05-21 18:56:33 +05:30
Harsh Shandilya 12c98aa2c4 chore: fix some dependency health violations 2026-05-21 18:56:33 +05:30
renovate[bot]andGitHub bb05c46e57 chore(deps): update plugin dependencyanalysis to v3.12.2 2026-05-21 01:35:42 +00:00
renovate[bot]andGitHub 12f075a23d fix(deps): update sentry to v8.42.0 2026-05-20 20:58:51 +00:00
renovate[bot]andGitHub 5065889e27 chore(deps): update mindsers/changelog-reader-action action to v2.4.0 2026-05-20 18:48:06 +00:00
renovate[bot]andGitHub 39ea7c9c2e chore(deps): update plugin dependencyanalysis to v3.12.1 2026-05-20 14:31:57 +00:00
renovate[bot]andGitHub 6f972a8099 fix(deps): update dependency org.junit:junit-bom to v6.1.0 2026-05-19 22:17:27 +00:00
renovate[bot]andGitHub 4d8357c774 fix(deps): update androidx 2026-05-19 17:28:52 +00:00
renovate[bot]andGitHub c92be255d2 fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v6.7.1 2026-05-19 14:40:26 +00:00
renovate[bot]andGitHub 007c303272 fix(deps): update dependency com.diffplug.spotless:spotless-plugin-gradle to v8.5.1 2026-05-16 01:37:53 +00:00
renovate[bot]andGitHub b922dfafee fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v6.7.0 2026-05-15 14:31:15 +00:00
renovate[bot]andGitHub 9fab85419c fix(deps): update dependency be.digitalia.compose.htmlconverter:htmlconverter to v1.1.1 2026-05-15 14:14:26 +00:00
renovate[bot]andGitHub 09bc8f8ff8 fix(deps): update dependency com.diffplug.spotless:spotless-plugin-gradle to v8.5.0 2026-05-15 01:03:30 +00:00
renovate[bot]andGitHub e248796c49 chore(deps): update plugin dependencyanalysis to v3.12.0 2026-05-14 22:09:07 +00:00
renovate[bot]andGitHub 9297799549 fix(deps): update metro to v1.1.1 2026-05-14 04:47:25 +00:00
renovate[bot]andGitHub 26fdbe0bed chore(deps): update plugin ksp to v2.3.8 2026-05-13 17:04:35 +00:00
renovate[bot]andGitHub 4faf965851 chore(deps): update gradle to v9.5.1 2026-05-12 14:57:06 +00:00
renovate[bot]andGitHub 62016180b9 chore(deps): update plugin dependencyanalysis to v3.11.0 2026-05-12 04:47:11 +00:00
Harsh Shandilya 875b41d0bf fix: default comment count to 1 instead of 0 2026-05-12 08:53:33 +05:30
Harsh Shandilya 052cce6a05 chore: update changelog 2026-05-10 17:12:31 +05:30
Harsh ShandilyaandGitHub 54697ce4ca feat: migrate from unsupported JSON endpoints to parsing HTML (#1162) 2026-05-10 17:01:26 +05:30
Harsh Shandilya 378f140d1a fix(api): remove now unused test dependencies 2026-05-10 15:02:14 +05:30
Harsh Shandilya 00f0e27a0b feat(api): parse tags from html 2026-05-10 15:02:14 +05:30
Harsh Shandilya e21759e6f1 feat(api): parse user profiles from html 2026-05-10 15:01:47 +05:30
Harsh Shandilya 0d88a7cf78 feat(api): parse post details from html 2026-05-10 15:01:45 +05:30
Harsh Shandilya 87fe06a419 feat(api): parse post lists from html 2026-05-10 15:01:35 +05:30
Harsh Shandilya fcdc9db79c feat(api): add kspoon retrofit integration 2026-05-10 14:53:54 +05:30
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
943398bb67 fix(deps): update dependency com.fleeksoft.ksoup:ksoup to v0.2.6 (#1161)
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
|
[com.fleeksoft.ksoup:ksoup](https://redirect.github.com/fleeksoft/ksoup)
| `0.2.4` → `0.2.6` |
![age](https://developer.mend.io/api/mc/badges/age/maven/com.fleeksoft.ksoup:ksoup/0.2.6?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.fleeksoft.ksoup:ksoup/0.2.4/0.2.6?slim=true)
|

---

### Release Notes

<details>
<summary>fleeksoft/ksoup (com.fleeksoft.ksoup:ksoup)</summary>

###
[`v0.2.6`](https://redirect.github.com/fleeksoft/ksoup/releases/tag/0.2.6)

[Compare
Source](https://redirect.github.com/fleeksoft/ksoup/compare/0.2.5...0.2.6)

- Updated build + CI (Gradle **9.3.1**, workflow refresh, cleaner build
scripts)
- Dependency upgrades: Kotlin **2.3.10**, AGP **8.13.2**, Ktor
**3.4.0**, plus kotlinx/Okio/Dokka updates
- Improved KMP/JS exports (moved to platform-agnostic `@KmpJsExport`)
- Aligned code  with **jsoup 1.22.1**
- Better HTML/XML parsing reliability + expanded fuzz/integration test
coverage

#### New Contributors

- [@&#8203;peacefulprogram](https://redirect.github.com/peacefulprogram)
made their first contribution in
[#&#8203;124](https://redirect.github.com/fleeksoft/ksoup/pull/124)

**Full Changelog**:
<https://github.com/fleeksoft/ksoup/compare/0.2.5...0.2.6>

###
[`v0.2.5`](https://redirect.github.com/fleeksoft/ksoup/releases/tag/0.2.5)

[Compare
Source](https://redirect.github.com/fleeksoft/ksoup/compare/0.2.4...0.2.5)

#### 🛠️ Build & Dependencies

- Kotlin `2.2.0`, Ktor `3.2.2`, Okio `3.16.0`, kotlinx-io `0.8.0`
- Gradle Wrapper updated to `8.14.3`
- Removed deprecated: `kotlin.native.ignoreIncorrectDependencies`

#### 🔬 CI & Compatibility

- GitHub Actions matrix: Java `17`, `21`, `24`
- Expanded test coverage: unit, concurrency, edge cases

#### ⚙️ Enhancements

- Improved DOM performance with `childNodes` cache
- Refactored iterators, attribute, and parser logic
- Build cleanup and code simplification

#### 🐛 Bug Fixes

- Base URI resolution, text node whitespace, stream reader edge cases
- Maven publish ordering
-  Resolved **Bug
[#&#8203;122](https://redirect.github.com/fleeksoft/ksoup/issues/122)**

#### 📚 Documentation

- Deprecated `PseudoTextElement` → use `Element#selectNodes`

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xNTkuMiIsInVwZGF0ZWRJblZlciI6IjQzLjE1OS4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-09 18:41:58 +00:00
Harsh Shandilya fc67bda7cb refactor: remove jsoup dependency 2026-05-09 23:56:40 +05:30
Harsh Shandilya 62a71a309b refactor(api): parse search results with ksoup 2026-05-09 23:56:29 +05:30
Harsh Shandilya 3b0a0e3289 refactor(api): parse reply forms with ksoup 2026-05-09 23:56:24 +05:30
Harsh Shandilya 1274897eac refactor(api): parse csrf token with ksoup 2026-05-09 23:56:19 +05:30
Harsh Shandilya 017f970f61 test(api): characterize html converters 2026-05-09 23:55:33 +05:30
Harsh Shandilya bda3c4c26c feat(common): add an ugly as sin test UI for upvote/unvote 2026-05-09 23:55:03 +05:30
Harsh Shandilya c14f529f5e refactor(api): ensure correct ordering for Retrofit converters 2026-05-09 23:55:01 +05:30
Harsh Shandilya 0ff78e5b00 test(api): cover authenticated Lobsters facade 2026-05-09 23:54:50 +05:30
Harsh Shandilya de966de4ec feat(api): add authenticated Lobsters facade 2026-05-09 23:54:43 +05:30
Harsh Shandilya 8a1a64d173 feat(api): add raw authenticated Lobsters endpoints 2026-05-09 18:42:12 +05:30
Harsh Shandilya d1e5df8bc4 feat(api): parse Lobsters reply form 2026-05-09 18:42:12 +05:30
renovate[bot]andGitHub d80ccfe284 chore(deps): update actions/dependency-review-action action to v5 2026-05-09 01:59:35 +00:00
renovate[bot]andGitHub c2cf007b34 fix(deps): update haze to v2.0.0-alpha02 2026-05-08 21:25:45 +00:00
renovate[bot]andGitHub 9cd9d3830a fix(deps): update kotlinx-coroutines monorepo to v1.11.0 2026-05-08 14:01:43 +00:00
renovate[bot]andGitHub 38052b359e fix(deps): update aboutlibraries to v14.2.0 2026-05-07 08:57:12 +00:00
renovate[bot]andGitHub 96151df57f fix(deps): update dependency com.slack.lint.compose:compose-lint-checks to v1.4.3 2026-05-07 01:35:51 +00:00
renovate[bot]andGitHub a141573442 fix(deps): update sentry to v8.41.0 2026-05-06 22:31:41 +00:00
renovate[bot]andGitHub ac537183fb fix(deps): update androidx 2026-05-06 17:16:29 +00:00
renovate[bot]andGitHub 9faac26d57 fix(deps): update agp to v9.2.1 2026-05-05 18:18:26 +00:00
renovate[bot]andGitHub 03baf5edc1 fix(deps): update aboutlibraries to v14.1.0 2026-05-01 19:03:01 +00:00
renovate[bot]andGitHub 2b8a245481 fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v6.6.0 2026-04-30 20:01:43 +00:00
renovate[bot]andGitHub 2d88ebafa3 chore(deps): update plugin dependencyanalysis to v3.10.0 2026-04-30 04:35:12 +00:00
Harsh Shandilya a1f0c79172 fix: bump Haze to 2.0.0-alpha01 2026-04-30 10:04:00 +05:30
renovate[bot]andGitHub 039c084967 chore(deps): update gradle to v9.5.0 2026-04-28 14:01:48 +00:00
renovate[bot]andGitHub a1ae8af8a8 fix(deps): update metro to v1.0.0 2026-04-28 05:31:30 +00:00
renovate[bot]andGitHub ad269f9edc fix(deps): update kotlinx-coroutines monorepo to v1.11.0-rc02 2026-04-27 17:31:09 +00:00
renovate[bot]andGitHub 7bf21ba23e fix(deps): update dependency org.junit:junit-bom to v6.1.0-rc1 2026-04-25 13:37:29 +00:00
renovate[bot]andGitHub 12226b48e8 chore(deps): update plugin poko to v0.22.1 2026-04-25 05:50:07 +00:00
renovate[bot]andGitHub 1d3ba7c0de fix(deps): update metro to v1.0.0-rc4 2026-04-25 00:46:14 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>Harsh Shandilya
6e779cd438 fix(deps): update metro to v1.0.0-rc3 (#1148)
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [dev.zacsweers.metro](https://redirect.github.com/ZacSweers/metro) |
`1.0.0-RC2` → `1.0.0-RC3` |
![age](https://developer.mend.io/api/mc/badges/age/maven/dev.zacsweers.metro:dev.zacsweers.metro.gradle.plugin/1.0.0-RC3?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/dev.zacsweers.metro:dev.zacsweers.metro.gradle.plugin/1.0.0-RC2/1.0.0-RC3?slim=true)
|
|
[dev.zacsweers.metro:metrox-viewmodel-compose](https://redirect.github.com/ZacSweers/metro)
| `1.0.0-RC2` → `1.0.0-RC3` |
![age](https://developer.mend.io/api/mc/badges/age/maven/dev.zacsweers.metro:metrox-viewmodel-compose/1.0.0-RC3?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/dev.zacsweers.metro:metrox-viewmodel-compose/1.0.0-RC2/1.0.0-RC3?slim=true)
|
|
[dev.zacsweers.metro:metrox-viewmodel](https://redirect.github.com/ZacSweers/metro)
| `1.0.0-RC2` → `1.0.0-RC3` |
![age](https://developer.mend.io/api/mc/badges/age/maven/dev.zacsweers.metro:metrox-viewmodel/1.0.0-RC3?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/dev.zacsweers.metro:metrox-viewmodel/1.0.0-RC2/1.0.0-RC3?slim=true)
|
|
[dev.zacsweers.metro:metrox-android](https://redirect.github.com/ZacSweers/metro)
| `1.0.0-RC2` → `1.0.0-RC3` |
![age](https://developer.mend.io/api/mc/badges/age/maven/dev.zacsweers.metro:metrox-android/1.0.0-RC3?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/dev.zacsweers.metro:metrox-android/1.0.0-RC2/1.0.0-RC3?slim=true)
|

---

> [!WARNING]
> Some dependencies could not be looked up. Check the [Dependency
Dashboard](../issues/266) for more information.

---

### Release Notes

<details>
<summary>ZacSweers/metro (dev.zacsweers.metro)</summary>

###
[`v1.0.0-RC3`](https://redirect.github.com/ZacSweers/metro/blob/HEAD/CHANGELOG.md#100-RC3)

[Compare
Source](https://redirect.github.com/ZacSweers/metro/compare/1.0.0-RC2...1.0.0-RC3)

*2026-04-23*

This is the third release candidate for Metro 1.0!

This means that its *runtime* APIs (`runtime`, `metrox` artifacts,
Gradle plugin, etc) will be API stable unless annotated with an
experimental annotation.

*Changes since RC2*

##### `enableFunctionProviders` enabled by default

This release promotes `enableFunctionProviders` (i.e. `() -> T` syntax
for providers) to stable, enables it by default, and marks usage of
Metro's native `Provider` as a warning.

This may require some migration in existing codebases. To help with
this, there's a [new section in
docs](https://zacsweers.github.io/metro/latest/adoption/#migrating-providert-to-function-syntax)
with information as well as a helper script: adoption docs guidance here
too.

##### New

- Add a new `desugaredProviderSeverity` option (default: `WARN`) that
reports a diagnostic when `Provider<T>` is used instead of the preferred
`() -> T` form. Set this to `NONE` to disable the warning during
migration, or `ERROR` to enforce the new style. Automatically treated as
`NONE` when `enableFunctionProviders` is disabled.
- **\[FIR]** Add diagnostic checks against providing intrinsic types
(`Provider`, `Lazy`, etc.) from `@Provides` declarations.
- **\[Gradle]** Introduce a new `compilerOptions {}` DSL for free Metro
compiler options and flags.
- **\[Gradle]** Add `IDE_WARN` and `IDE_ERROR` members to
`DiagnosticSeverity` to allow configuring some diagnostics to *only* run
in IDE sessions. Useful for diagnostics you only want to surface to
readers in the IDE without emitting compiler warnings in real (CLI)
compilations.

##### Enhancements

- **\[FIR]** Add a new diagnostic for ambiguous inject constructors.
Namely, cases where a class is annotated with `@Inject`, defines a
secondary constructor but no primary constructor. This is ambiguous,
metro now asks you to pick a lane.
- **\[FIR]** Add a new diagnostic for check against `private`
contributions.
- **\[FIR]** When rendering diagnostics in the IDE, use short names for
classes since they are shown in context already.
- **\[JVM/JS]** Generate `@JvmStatic` and `@JsStatic` annotations onto
static-ish functions for better staticization on those platforms.
- **\[docs]** Migrate doc site to Zensical. Works the same, fresh-ish
coat of paint!

##### Fixes

- **\[FIR]** Fix not recognizing `FirDeclarationOrigin.Precompiled`
origins when checking resolved default binding types. Previously we only
considered `FirDeclarationOrigin.Library`, but incremental compilation
uses `FirDeclarationOrigin.Precompiled` to differentiate. This would
result in misreads of default binding types in some cases during IC.
- **\[FIR]** When merging `@ContributesTo` types to graph supertypes,
add the original interface as well. This ensures they are visible in
ObjC exports, as the metro-generated contribution interfaces are
normally excluded.
- **\[IR]** Fix secondary inject constructors support when
`generateContributionProviders` is enabled.
- **\[IR]** Fix implicit class key lookup for map keys on
source-declared `@Binds` declarations.
- **\[IR]** Fix `implementsProviderType()` check in the compiler to only
exactly match `Function0` types when `enableFunctionProviders` is
enabled.
- **\[IR]** Set `thisGraphInstance` field types as the graph impl type
to avoid a Wasm issue.
- **\[interop]** Fix `@ContributesSubcomponent.Factory` interop with
square/anvil.
- **\[interop]** Fix `@MergeSubcomponent.Factory` interop with
zacsweers/anvil (anvil-ksp).
- **\[docs]** Fix source links in Dokka API docs.
- **\[docs]** Don't publish `**.internal.**` APIs in Dokka API docs.

##### Changes

- `enableFunctionProviders` (i.e. `() -> T` syntax for providers) is now
enabled by default. Previously this required opting in. The
function-syntax form is now the **recommended** way to declare provider
dependencies; `Provider<T>` is still supported but treated as a
desugared alternative and a **warning** by default, similar to if you
were to use `Function0` instead of `() -> T` syntax for functions. See
the [metro-intrinsics](docs/metro-intrinsics.md) docs for more details.
- **\[IR]** Remove deprecated `indexInOldValueParameters` use in IR for
better `2.4.0`+ support.
- **\[Gradle]** Promote `enableFunctionProviders` to stable.
- **\[Gradle]** Remove deprecated `useAssistedParamNamesAsIdentifiers`
property.
- **\[Gradle]** Remove `deduplicateInjectedParams` property.
- **\[Gradle]** Remove `enableKlibParamsCheck` property, use the new
`compilerOptions` API.
- **\[Gradle]** Remove `enableFullBindingGraphValidation` property, use
the new `compilerOptions` API.
- **\[Gradle]** Remove `enableGraphImplClassAsReturnType` property, use
the new `compilerOptions` API.
- **\[Gradle]** Remove `shrinkUnusedBindings` property, use the new
`compilerOptions` API.
- **\[metrox-android]** Change `MetroAppComponentProviders` accessor
multibindings to expose function types instead of `Provider` types.
- **\[metrox-viewmodel]** Change `MetroViewModelFactory` and
`MetroViewModelMultibindings` accessor multibindings to expose function
types instead of `Provider` types.
- Support Kotlin `2.4.0-Beta2`.
- Update embedded androidx.tracing to `2.0.0-alpha06`.

##### Contributors

Special thanks to the following contributors for contributing to this
release!

- [@&#8203;anddani](https://redirect.github.com/anddani)
- [@&#8203;jonamireh](https://redirect.github.com/jonamireh)

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these
updates again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMzkuNyIsInVwZGF0ZWRJblZlciI6IjQzLjEzOS43IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

---------

Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Harsh Shandilya <me@msfjarvis.dev>
2026-04-23 18:12:19 +00:00
Harsh Shandilya 21ba2c3869 Merge remote-tracking branches 'origin/renovate/sentry' and 'origin/renovate/kotlin-monorepo'
* origin/renovate/sentry:
  fix(deps): update sentry

* origin/renovate/kotlin-monorepo:
  fix(deps): update kotlin monorepo to v2.3.21
2026-04-23 23:26:44 +05:30
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
61d8c4e4e0 fix(deps): update kotlin and compose to v2.3.21 (#1145)
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [org.jetbrains.kotlin.plugin.serialization](https://kotlinlang.org/)
([source](https://redirect.github.com/JetBrains/kotlin)) | `2.3.20` →
`2.3.21` |
![age](https://developer.mend.io/api/mc/badges/age/maven/org.jetbrains.kotlin.plugin.serialization:org.jetbrains.kotlin.plugin.serialization.gradle.plugin/2.3.21?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/org.jetbrains.kotlin.plugin.serialization:org.jetbrains.kotlin.plugin.serialization.gradle.plugin/2.3.20/2.3.21?slim=true)
|
| [org.jetbrains.kotlin.plugin.compose](https://kotlinlang.org/)
([source](https://redirect.github.com/JetBrains/kotlin)) | `2.3.20` →
`2.3.21` |
![age](https://developer.mend.io/api/mc/badges/age/maven/org.jetbrains.kotlin.plugin.compose:org.jetbrains.kotlin.plugin.compose.gradle.plugin/2.3.21?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/org.jetbrains.kotlin.plugin.compose:org.jetbrains.kotlin.plugin.compose.gradle.plugin/2.3.20/2.3.21?slim=true)
|
| [org.jetbrains.kotlin:kotlin-gradle-plugin](https://kotlinlang.org/)
([source](https://redirect.github.com/JetBrains/kotlin)) | `2.3.20` →
`2.3.21` |
![age](https://developer.mend.io/api/mc/badges/age/maven/org.jetbrains.kotlin:kotlin-gradle-plugin/2.3.21?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/org.jetbrains.kotlin:kotlin-gradle-plugin/2.3.20/2.3.21?slim=true)
|

---

> [!WARNING]
> Some dependencies could not be looked up. Check the [Dependency
Dashboard](../issues/266) for more information.

---

### Release Notes

<details>
<summary>JetBrains/kotlin
(org.jetbrains.kotlin.plugin.serialization)</summary>

###
[`v2.3.21`](https://redirect.github.com/JetBrains/kotlin/blob/HEAD/ChangeLog.md#2321)

##### Backend. Wasm

- [`KT-84610`](https://youtrack.jetbrains.com/issue/KT-84610) \[Wasm]
Failed to compile klibs in IC mode

##### Compiler

- [`KT-84566`](https://youtrack.jetbrains.com/issue/KT-84566) Prevent
launching Default dispatcher threads from IJ SDK in kotlin compiler
- [`KT-85358`](https://youtrack.jetbrains.com/issue/KT-85358) Native:
roll back the workaround for KT-84678 once MapLibre has been properly
fixed
- [`KT-85626`](https://youtrack.jetbrains.com/issue/KT-85626)
`@JvmRecord` in commonMain breaks compileCommonMainKotlinMetadata with
"Cannot access 'java.lang.Record'"
- [`KT-85405`](https://youtrack.jetbrains.com/issue/KT-85405)
Postpone/Revert `DontIgnoreUpperBoundViolatedOnImplicitArguments`
- [`KT-84678`](https://youtrack.jetbrains.com/issue/KT-84678) K/N:
Undefined symbol from SPM-added ObjC frameworks when linking iOS target
- [`KT-85021`](https://youtrack.jetbrains.com/issue/KT-85021) False
positive SUBCLASS\_CANT\_CALL\_COMPANION\_PROTECTED\_NON\_STATIC error
in multi-module project

##### JavaScript

- [`KT-82395`](https://youtrack.jetbrains.com/issue/KT-82395) Support
top-level declarations from compiler plugins in JS incremental
compilation
- [`KT-84475`](https://youtrack.jetbrains.com/issue/KT-84475) K/JS:
false-positive exportability warnings in multi-module project
- [`KT-84633`](https://youtrack.jetbrains.com/issue/KT-84633) Kotlin/JS:
"Serializer for class not found" error when IR output granularity is
`whole-program`
- [`KT-85047`](https://youtrack.jetbrains.com/issue/KT-85047) Kotlin/JS:
`@JsStatic` on suspend fun of class companion generates incorrect d.ts
- [`KT-84517`](https://youtrack.jetbrains.com/issue/KT-84517) K/JS: bad
mappings data in outputted Kotlin stdlib source map

##### Libraries

- [`KT-71848`](https://youtrack.jetbrains.com/issue/KT-71848)
Kotlinx.metadata: Add `CompilerPluginData` into Km API

##### Native. C and ObjC Import

- [`KT-85399`](https://youtrack.jetbrains.com/issue/KT-85399)
Kotlin/Native: TypeCastException when casting ObjC Protocol MetaClass
with genericSafeCasts enabled
- [`KT-85508`](https://youtrack.jetbrains.com/issue/KT-85508) K/N:
TypeCastException when using nw\_parameters\_create\_secure\_tcp block
parameter on 2.3.20

##### Tools. Gradle

- [`KT-84729`](https://youtrack.jetbrains.com/issue/KT-84729) Update
Gradle plugin-publish version to enable configuration cache badge on
Gradle plugins portal

##### Tools. Gradle. Compiler plugins

- [`KT-85257`](https://youtrack.jetbrains.com/issue/KT-85257)
AGP/Compose: MergeMappingFileTask clears R8 artifacts due to
`@OutputDirectory` annotation on AGP 9.1+

##### Tools. Scripts

- [`KT-85105`](https://youtrack.jetbrains.com/issue/KT-85105) Scripts:
JVM backend internal error (IR lowering) when scratch file contains
anonymous object
- [`KT-85103`](https://youtrack.jetbrains.com/issue/KT-85103) Exception
while generating code when explain destructuring decls
- [`KT-84842`](https://youtrack.jetbrains.com/issue/KT-84842)
scriptCompilationClasspathFromContext behavior changed from 2.3.10 to
2.3.20
- [`KT-85029`](https://youtrack.jetbrains.com/issue/KT-85029) Kotlin
Scripting: ScriptDiagnostic reports "at null" instead of error location

##### Tools. Statistics (FUS)

- [`KT-85628`](https://youtrack.jetbrains.com/issue/KT-85628) KGP:
composite build FUS metrics fail on access of 'configurationTimeMetrics'

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these
updates again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMzkuNyIsInVwZGF0ZWRJblZlciI6IjQzLjEzOS43IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-23 17:10:53 +00:00
renovate[bot]andGitHub ccdc75f7a3 fix(deps): update sentry 2026-04-23 16:58:32 +00:00
renovate[bot]andGitHub f1ae3c1a44 fix(deps): update kotlin monorepo to v2.3.21 2026-04-23 16:58:25 +00:00
renovate[bot]andGitHub aa32599a59 chore(deps): update plugin ksp to v2.3.7 2026-04-23 01:55:47 +00:00
renovate[bot]andGitHub a58edda23e fix(deps): update androidx 2026-04-22 20:32:10 +00:00
renovate[bot]andGitHub a833820be3 fix(deps): update agp to v9.2.0 2026-04-21 14:32:06 +00:00
renovate[bot]andGitHub 16cc3097b7 chore(deps): update r0adkll/upload-google-play action to v1.1.5 2026-04-21 02:41:06 +00:00
renovate[bot]andGitHub 49318b2766 chore(deps): update plugin dependencyanalysis to v3.9.0 2026-04-20 21:57:06 +00:00
renovate[bot]andGitHub c9a22df572 chore(deps): update r0adkll/upload-google-play action to v1.1.4 2026-04-20 19:43:40 +00:00
renovate[bot]andGitHub 13d368b86a chore(deps): update plugin com.gradle.develocity to v4.4.1 2026-04-20 13:13:55 +00:00
renovate[bot]andGitHub 4c14890170 fix(deps): update dependency org.jsoup:jsoup to v1.22.2 2026-04-20 08:40:49 +00:00
renovate[bot]andGitHub 43d1b51901 chore(deps): update plugin dependencyanalysis to v3.8.0 2026-04-19 09:09:49 +00:00
Harsh Shandilya 088fcf5a62 fix: bump dependencies 2026-04-18 21:26:19 +05:30
renovate[bot]andGitHub e0bb04ce4c fix(deps): update sentry to v8.39.1 2026-04-17 09:20:42 +00:00
renovate[bot]andGitHub b621fb0480 fix(deps): update sentry to v8.39.0 2026-04-16 20:36:33 +00:00
renovate[bot]andGitHub def7d1a727 fix(deps): update agp to v9.2.0-rc01 2026-04-13 17:03:34 +00:00
renovate[bot]andGitHub 24abd4171a chore(deps): update plugin modulegraphassert to v2.9.1 2026-04-12 14:00:50 +00:00
Harsh Shandilya 31be02c521 refactor: replace if with when 2026-04-12 14:25:49 +05:30
Harsh Shandilya 79f2b5d01c chore: rename ProGuard files to R8 2026-04-12 14:25:49 +05:30
Harsh Shandilya ae047d8a6d fix: remove obsoleted ProGuard rule 2026-04-12 14:10:13 +05:30
Harsh Shandilya 864b023302 refactor: remove useless twiddling of Main dispatcher 2026-04-12 14:01:59 +05:30
Harsh Shandilya 502eb751eb chore: add changelog entry for login 2026-04-12 13:55:59 +05:30
renovate[bot]andGitHub 210bc89346 chore(deps): update plugin dependencyanalysis to v3.7.0 (#1135) 2026-04-11 10:14:32 +05:30
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
9ecb0fa704 chore(deps): update actions/upload-artifact action to v7.0.1 (#1134)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[actions/upload-artifact](https://redirect.github.com/actions/upload-artifact)
| action | patch | `v7.0.0` → `v7.0.1` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the [Dependency
Dashboard](../issues/266) for more information.

---

### Release Notes

<details>
<summary>actions/upload-artifact (actions/upload-artifact)</summary>

###
[`v7.0.1`](https://redirect.github.com/actions/upload-artifact/compare/v7.0.0...v7.0.1)

[Compare
Source](https://redirect.github.com/actions/upload-artifact/compare/v7.0.0...v7.0.1)

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMTAuMiIsInVwZGF0ZWRJblZlciI6IjQzLjExMC4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-11 04:38:22 +00:00
Harsh Shandilya 635e33233b fix: move Lobsters URI creation out of class init
For some reason this is now too early and explodes
2026-04-11 08:17:54 +05:30
Harsh Shandilya 2cf53422bf fix: regenerate baseline profiles 2026-04-11 01:55:54 +05:30
Harsh Shandilya 80980af1c9 fix: move baseline profiles back to the emulators 2026-04-10 22:35:26 +05:30
Harsh Shandilya 3b5c4b3ddb fix(build): remove obsolete source set 2026-04-10 22:35:15 +05:30
Harsh Shandilya 84e36496d3 fix: downgrade Kotlin 2026-04-10 22:26:26 +05:30
Harsh Shandilya 3f714f41c3 fix: upgrade to latest everything 2026-04-10 21:44:31 +05:30
Harsh ShandilyaandGitHub e20c90297a fix: remove now-empty core-ktx library 2026-04-10 00:13:11 +05:30
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
0ac8c91789 fix(deps): update androidx (#1128)
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
|
[androidx.lint:lint-gradle](https://developer.android.com/jetpack/androidx/releases/lint#1.0.0-alpha06)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.0.0-alpha05` → `1.0.0-alpha06` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.lint:lint-gradle/1.0.0-alpha06?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.lint:lint-gradle/1.0.0-alpha05/1.0.0-alpha06?slim=true)
|
|
[androidx.sqlite:sqlite](https://developer.android.com/jetpack/androidx/releases/sqlite#2.6.2)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `2.7.0-alpha02` → `2.7.0-alpha03` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.sqlite:sqlite/2.7.0-alpha03?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.sqlite:sqlite/2.7.0-alpha02/2.7.0-alpha03?slim=true)
|
|
[androidx.compose.runtime:runtime-saveable](https://developer.android.com/jetpack/androidx/releases/compose-runtime#1.10.6)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-beta02` → `1.11.0-rc01` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.runtime:runtime-saveable/1.11.0-rc01?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.runtime:runtime-saveable/1.11.0-beta02/1.11.0-rc01?slim=true)
|
|
[androidx.paging:paging-compose](https://developer.android.com/jetpack/androidx/releases/paging#3.4.2)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `3.5.0-alpha01` → `3.5.0-beta01` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.paging:paging-compose/3.5.0-beta01?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.paging:paging-compose/3.5.0-alpha01/3.5.0-beta01?slim=true)
|
|
[androidx.paging:paging-common](https://developer.android.com/jetpack/androidx/releases/paging#3.4.2)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `3.5.0-alpha01` → `3.5.0-beta01` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.paging:paging-common/3.5.0-beta01?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.paging:paging-common/3.5.0-alpha01/3.5.0-beta01?slim=true)
|
|
[androidx.compose.material3.adaptive:adaptive-navigation3](https://developer.android.com/jetpack/androidx/releases/compose-material3-adaptive#1.3.0-alpha10)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.3.0-alpha09` → `1.3.0-alpha10` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.material3.adaptive:adaptive-navigation3/1.3.0-alpha10?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.material3.adaptive:adaptive-navigation3/1.3.0-alpha09/1.3.0-alpha10?slim=true)
|
|
[androidx.compose.material3.adaptive:adaptive-layout](https://developer.android.com/jetpack/androidx/releases/compose-material3-adaptive#1.2.0)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.3.0-alpha09` → `1.3.0-alpha10` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.material3.adaptive:adaptive-layout/1.3.0-alpha10?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.material3.adaptive:adaptive-layout/1.3.0-alpha09/1.3.0-alpha10?slim=true)
|
|
[androidx.compose.material3.adaptive:adaptive](https://developer.android.com/jetpack/androidx/releases/compose-material3-adaptive#1.2.0)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.3.0-alpha09` → `1.3.0-alpha10` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.material3.adaptive:adaptive/1.3.0-alpha10?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.material3.adaptive:adaptive/1.3.0-alpha09/1.3.0-alpha10?slim=true)
|
|
[androidx.navigation3:navigation3-ui](https://developer.android.com/jetpack/androidx/releases/navigation3#1.1.0)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.1.0-rc01` → `1.1.0` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.navigation3:navigation3-ui/1.1.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.navigation3:navigation3-ui/1.1.0-rc01/1.1.0?slim=true)
|
|
[androidx.navigation3:navigation3-runtime](https://developer.android.com/jetpack/androidx/releases/navigation3#1.1.0)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.1.0-rc01` → `1.1.0` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.navigation3:navigation3-runtime/1.1.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.navigation3:navigation3-runtime/1.1.0-rc01/1.1.0?slim=true)
|
|
[androidx.compose.ui:ui-unit](https://developer.android.com/jetpack/androidx/releases/compose-ui#1.10.6)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-beta02` → `1.11.0-rc01` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.ui:ui-unit/1.11.0-rc01?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.ui:ui-unit/1.11.0-beta02/1.11.0-rc01?slim=true)
|
|
[androidx.compose.ui:ui-tooling-preview](https://developer.android.com/jetpack/androidx/releases/compose-ui#1.10.6)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-beta02` → `1.11.0-rc01` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.ui:ui-tooling-preview/1.11.0-rc01?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.ui:ui-tooling-preview/1.11.0-beta02/1.11.0-rc01?slim=true)
|
|
[androidx.compose.ui:ui-tooling](https://developer.android.com/jetpack/androidx/releases/compose-ui#1.10.6)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-beta02` → `1.11.0-rc01` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.ui:ui-tooling/1.11.0-rc01?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.ui:ui-tooling/1.11.0-beta02/1.11.0-rc01?slim=true)
|
|
[androidx.compose.ui:ui-text](https://developer.android.com/jetpack/androidx/releases/compose-ui#1.10.6)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-beta02` → `1.11.0-rc01` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.ui:ui-text/1.11.0-rc01?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.ui:ui-text/1.11.0-beta02/1.11.0-rc01?slim=true)
|
|
[androidx.compose.ui:ui-graphics](https://developer.android.com/jetpack/androidx/releases/compose-ui#1.10.6)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-beta02` → `1.11.0-rc01` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.ui:ui-graphics/1.11.0-rc01?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.ui:ui-graphics/1.11.0-beta02/1.11.0-rc01?slim=true)
|
|
[androidx.compose.ui:ui](https://developer.android.com/jetpack/androidx/releases/compose-ui#1.10.6)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-beta02` → `1.11.0-rc01` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.ui:ui/1.11.0-rc01?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.ui:ui/1.11.0-beta02/1.11.0-rc01?slim=true)
|
|
[androidx.compose.runtime:runtime-annotation](https://developer.android.com/jetpack/androidx/releases/compose-runtime#1.10.6)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-beta02` → `1.11.0-rc01` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.runtime:runtime-annotation/1.11.0-rc01?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.runtime:runtime-annotation/1.11.0-beta02/1.11.0-rc01?slim=true)
|
|
[androidx.compose.runtime:runtime](https://developer.android.com/jetpack/androidx/releases/compose-runtime#1.10.6)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-beta02` → `1.11.0-rc01` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.runtime:runtime/1.11.0-rc01?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.runtime:runtime/1.11.0-beta02/1.11.0-rc01?slim=true)
|
|
[androidx.compose.material3:material3-window-size-class](https://developer.android.com/jetpack/androidx/releases/compose-material3#1.4.0)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.5.0-alpha16` → `1.5.0-alpha17` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.material3:material3-window-size-class/1.5.0-alpha17?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.material3:material3-window-size-class/1.5.0-alpha16/1.5.0-alpha17?slim=true)
|
|
[androidx.compose.material3:material3](https://developer.android.com/jetpack/androidx/releases/compose-material3#1.4.0)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.5.0-alpha16` → `1.5.0-alpha17` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.material3:material3/1.5.0-alpha17?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.material3:material3/1.5.0-alpha16/1.5.0-alpha17?slim=true)
|
|
[androidx.compose.foundation:foundation-layout](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.10.6)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-beta02` → `1.11.0-rc01` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.foundation:foundation-layout/1.11.0-rc01?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.foundation:foundation-layout/1.11.0-beta02/1.11.0-rc01?slim=true)
|
|
[androidx.compose.foundation:foundation](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.10.6)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-beta02` → `1.11.0-rc01` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.foundation:foundation/1.11.0-rc01?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.foundation:foundation/1.11.0-beta02/1.11.0-rc01?slim=true)
|
|
[androidx.compose.animation:animation-core](https://developer.android.com/jetpack/androidx/releases/compose-animation#1.10.6)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-beta02` → `1.11.0-rc01` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.animation:animation-core/1.11.0-rc01?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.animation:animation-core/1.11.0-beta02/1.11.0-rc01?slim=true)
|
|
[androidx.compose.animation:animation](https://developer.android.com/jetpack/androidx/releases/compose-animation#1.10.6)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-beta02` → `1.11.0-rc01` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.animation:animation/1.11.0-rc01?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.animation:animation/1.11.0-beta02/1.11.0-rc01?slim=true)
|
|
[androidx.annotation:annotation](https://developer.android.com/jetpack/androidx/releases/annotation#1.10.0)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.10.0-rc01` → `1.10.0` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.annotation:annotation/1.10.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.annotation:annotation/1.10.0-rc01/1.10.0?slim=true)
|

---

> [!WARNING]
> Some dependencies could not be looked up. Check the [Dependency
Dashboard](../issues/266) for more information.

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMTAuMiIsInVwZGF0ZWRJblZlciI6IjQzLjExMC4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-09 12:16:38 +00:00
CopilotGitHubcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>msfjarvisHarsh Shandilya
d74b0c3f40 Centralize Android SDK version constants and wire benchmark through build-logic (#1129)
`benchmark` was hardcoding `compileSdk` because it applies
`com.android.test` + `kotlin-android` — neither of which pulls in
`AndroidCommonPlugin`. The SDK values were also duplicated across two
plugin files with no shared constant.

## Changes

- **New `AndroidConstants.kt`** — single source of truth for
`COMPILE_SDK = 37` and `TARGET_SDK = 37`
- **`AndroidCommonPlugin`** — replace literal `37` with `COMPILE_SDK`
- **`ApplicationPlugin`** — replace literal `37` with `TARGET_SDK`
- **`benchmark/build.gradle.kts`** — apply
`dev.msfjarvis.claw.android-common` so `compileSdk` is set by the
plugin; drop the hardcoded value. `TestExtension` implements
`CommonExtension`, so the plugin's `configure<CommonExtension>` block
applies cleanly.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: msfjarvis <13348378+msfjarvis@users.noreply.github.com>
Co-authored-by: Harsh Shandilya <me@msfjarvis.dev>
2026-04-09 12:05:14 +00:00
renovate[bot]andGitHub 7040518fc7 fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v6.4.0 2026-04-09 11:34:17 +00:00
renovate[bot]andGitHub e27a3582f8 fix(deps): update sentry to v8.38.0 2026-04-08 19:31:23 +00:00
renovate[bot]andGitHub f4b3f936ae fix(deps): update metro to v0.13.2 2026-04-07 01:29:44 +00:00
renovate[bot]andGitHub 199ddc9ca8 fix(deps): update aboutlibraries to v14.0.0 2026-04-06 20:39:38 +00:00
renovate[bot]andGitHub 792b8b343b fix(deps): update metro to v0.13.1 2026-04-06 08:50:46 +00:00
renovate[bot]andGitHub b10c87c33d fix(deps): update metro to v0.13.0 2026-04-05 05:17:12 +00:00
renovate[bot]andGitHub b6ed8e914e chore(deps): update gradle/actions digest to 50e97c2 2026-04-03 16:40:31 +00:00
Harsh Shandilya 3c6145c1f3 chore: refresh Lint baseline 2026-04-03 22:01:41 +05:30
Harsh Shandilya e1ca964185 feat(android): wire Login destination into nav graph and Settings 2026-04-03 21:52:39 +05:30
Harsh Shandilya 2e84ada707 feat(android): add login/logout rows to SettingsScreen 2026-04-03 21:52:39 +05:30
Harsh Shandilya 758f88e02f feat(android): add SettingsViewModel with login state 2026-04-03 21:52:39 +05:30
Harsh Shandilya eef58ab267 feat(android): add LoginScreen WebView composable 2026-04-03 21:52:39 +05:30
Harsh Shandilya 9d34cf6c2e feat(android): add Login nav destination 2026-04-03 21:52:38 +05:30
Harsh Shandilya bfb402d4ae feat(core): add SessionExpiryInterceptor to handle expired sessions 2026-04-03 21:52:38 +05:30
Harsh Shandilya 66c4dc78f4 feat(core): add LobstersCookieJar and wire into OkHttpModule 2026-04-03 21:52:38 +05:30
Harsh Shandilya 34e4836f2d feat(common): implement SessionCookieStore backed by SQLDelight 2026-04-03 21:52:38 +05:30
Harsh Shandilya c77d10942f feat(database): add SessionCookie table and queries 2026-04-03 21:52:38 +05:30
Harsh Shandilya 9c6c5e82cb feat(core): add SessionCookieStore interface 2026-04-03 21:45:52 +05:30
Harsh Shandilya dac23176ff chore: document tag migration removal 2026-04-03 18:15:26 +05:30
Harsh Shandilya 5c8174879f chore: ignore local Git worktrees 2026-04-03 18:15:26 +05:30
Harsh Shandilya 30a5bcc687 refactor(test): remove unnecessary advanceUntilIdle calls 2026-04-03 18:15:26 +05:30
Harsh Shandilya 30293d29f6 fix(build): remove datastore dependencies 2026-04-03 01:06:10 +05:30
Harsh Shandilya 0269a284ef refactor(test): use deterministic tag block dispatchers 2026-04-03 01:06:10 +05:30
Harsh Shandilya 19103c19b9 refactor(test): cover reactive tag block flows 2026-04-03 01:06:10 +05:30
Harsh Shandilya 96f2c78a4e refactor: remove tag datastore migration 2026-04-03 01:06:10 +05:30
GitHub Actions 4e44758c39 feat(release): start next development iteration 2026-04-02 09:29:53 +00:00
GitHub Actions a54940f47a feat(release): bump version 2026-04-02 09:24:42 +00:00
renovate[bot]andGitHub e61bb6bcd8 fix(deps): update metro to v0.12.1 2026-03-30 20:49:51 +00:00
Harsh Shandilya 25909e9949 fix(ci): stop the infinite loop 2026-03-27 23:37:59 +05:30
renovate[bot]andGitHub 6f5eecb694 chore(deps): update msfjarvis/compose-lobsters digest to 92aae74 2026-03-27 05:38:19 +00:00
renovate[bot]andGitHub 92aae743ab fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v6.3.0 2026-03-26 20:32:30 +00:00
renovate[bot]andGitHub bd1d8dd8a0 chore(deps): pin msfjarvis/compose-lobsters action to f3fb95d 2026-03-26 18:53:01 +00:00
renovate[bot]andGitHub f3fb95dc5f fix(deps): update sentry to v8.37.1 2026-03-26 13:43:43 +00:00
renovate[bot]andGitHub dc98d29fde fix(deps): update konvert to v4.5.0 2026-03-26 04:36:38 +00:00
renovate[bot]andGitHub 4234fda686 chore(deps): update plugin com.gradle.develocity to v4.4.0 2026-03-25 21:37:42 +00:00
renovate[bot]andGitHub f9122cf0b6 fix(deps): update androidx 2026-03-25 18:15:21 +00:00
renovate[bot]andGitHub 4c6c7f4995 fix(deps): update sentry to v8.37.0 2026-03-25 12:58:06 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>Harsh Shandilya
ba59ba9015 fix(deps): update metro to v0.12.0 (#1116)
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [dev.zacsweers.metro](https://redirect.github.com/ZacSweers/metro) |
`0.11.4` → `0.12.0` |
![age](https://developer.mend.io/api/mc/badges/age/maven/dev.zacsweers.metro:dev.zacsweers.metro.gradle.plugin/0.12.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/dev.zacsweers.metro:dev.zacsweers.metro.gradle.plugin/0.11.4/0.12.0?slim=true)
|
|
[dev.zacsweers.metro:metrox-viewmodel-compose](https://redirect.github.com/ZacSweers/metro)
| `0.11.4` → `0.12.0` |
![age](https://developer.mend.io/api/mc/badges/age/maven/dev.zacsweers.metro:metrox-viewmodel-compose/0.12.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/dev.zacsweers.metro:metrox-viewmodel-compose/0.11.4/0.12.0?slim=true)
|
|
[dev.zacsweers.metro:metrox-viewmodel](https://redirect.github.com/ZacSweers/metro)
| `0.11.4` → `0.12.0` |
![age](https://developer.mend.io/api/mc/badges/age/maven/dev.zacsweers.metro:metrox-viewmodel/0.12.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/dev.zacsweers.metro:metrox-viewmodel/0.11.4/0.12.0?slim=true)
|
|
[dev.zacsweers.metro:metrox-android](https://redirect.github.com/ZacSweers/metro)
| `0.11.4` → `0.12.0` |
![age](https://developer.mend.io/api/mc/badges/age/maven/dev.zacsweers.metro:metrox-android/0.12.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/dev.zacsweers.metro:metrox-android/0.11.4/0.12.0?slim=true)
|

---

> [!WARNING]
> Some dependencies could not be looked up. Check the [Dependency
Dashboard](../issues/266) for more information.

---

### Release Notes

<details>
<summary>ZacSweers/metro (dev.zacsweers.metro)</summary>

###
[`v0.12.0`](https://redirect.github.com/ZacSweers/metro/blob/HEAD/CHANGELOG.md#0120)

[Compare
Source](https://redirect.github.com/ZacSweers/metro/compare/0.11.4...0.12.0)

*2026-03-24*

##### New

#####
[**\[MEEP-2014\]**](https://redirect.github.com/ZacSweers/metro/discussions/2014)
Implicit class (map) keys

`MapKey.implicitClassKey` is a new API to allow for class-based map keys
to have their class parameters inferred on classes and `@Binds`
declarations.

This means that instead of redeclaring the annotated class in the key,
for example `@ViewModelKey`, you can now omit it and it will be
inferred.

```kotlin
@&#8203;ViewModelKey // <-- implicitly HomeViewModel::class
@&#8203;ContributesIntoMap(AppScope::class)
class HomeViewModel : ViewModel()
```

For classes, the implicit type is the annotated class. For `@Binds`
declarations, the receiver or single parameter are the implicit type.

You may still specify an explicit type. The compiler will warn you if
you specify a redundant one. If you need to suppress this diagnostic
temporarily to ease migration, you can add
`-Xwarning-level=MAP_KEY_REDUNDANT_IMPLICIT_CLASS_KEY:disabled` to your
compiler arguments.

The compiler will also error if you attempt to do this on `@Provides`
declarations, as those cannot be inferred.

Metro's first-party class-based map keys (like `@ClassKey`,
`@ViewModelKey`, etc.) now support this. Custom map keys can opt-in to
this by setting `MapKey.implicitClassKey` to true. See its doc for more
details.

```kotlin
@&#8203;MapKey(implicitClassKey = true)
annotation class ViewModelKey(val value: KClass<out ViewModel> = Nothing::class)
```

##### Misc

- **\[metrox-viewmodel]** Add `mingwX64` target.

##### Enhancements

- **\[FIR]** Add diagnostic to ensure map key annotations support
`FUNCTION` targets if they have a `@Target` annotation.
- **\[FIR]** Improve annotation argument matching to only use fully
resolved names or none at all. This helps avoid situations in the past
with interop where an argument at the same index and type but different
name could incorrectly be used.

##### Fixes

- **\[IR]** Fix `IllegalArgumentException` thrown when there are
multiple top-level functions with the same name but only one is
annotated with `@Inject`.
- **\[IR]** Only store a given binding container's own provider
factories in metro metadata. This resolves a bug where we could end up
duplicate-processing upstream providers in dynamic factories.
- **\[IR]** Fix a severity conversion compat function call for Kotlin
2.3.20+.
- **\[IR]** Ensure stable sort of output `SuspiciousUnusedMultibinding`
locations.
- **\[IR]** Don't skip dynamic keys inherited from parent graphs when
working with dynamic graphs.
- **\[IR]** Propagate `@OptionalBinding` annotations to generated static
factory creators if present.
- **\[IR]** Preserve nullability when remapping parameters with generic
layers.
- **\[Runtime]** `IntoSet` and `IntoMap` no longer have a `Target` of
`AnnotationTarget.CLASS`

##### Changes

- The Metro compiler now requires JVM 21+. Note that the runtime JVM
artifacts still target 11 unless otherwise documented.
- The Metro Gradle plugin now requires JVM 21+.
- The Metro Gradle plugin now requires Gradle 9+. Note that if you do
not use Kotlin Gradle DSL, it may work on older versions but YMMV.
- The Metro Gradle plugin now targets Kotlin `2.2`.
- `@Assisted.value` is formally deprecated now. See the
[docs](https://zacsweers.github.io/metro/latest/injection-types/#assisted-injection)
on why in case you missed this! TL;DR, Metro matches by parameter names
going forward.
- Metro's main branch now builds with Kotlin `2.3.20` but still targets
Kotlin 2.2 for its runtime artifacts and supports 2.2.20 all to 2.4.0
dev builds in its compiler.
- Remove deprecated `macosX64`, `tvosX64`, and `watchosX64` targets.
- Update Kotlin 2.4 compat support from `2.4.0-dev-539` to
`2.4.0-dev-2124`. This should support the upcoming IntelliJ 2026.1
release as well as the upcoming Kotlin `2.4.0-Beta1`.
- Test IntelliJ `2026.1 RC`.
- Update shaded Wire dependency to `6.1.0`.

##### Contributors

Special thanks to the following contributors for contributing to this
release!

- [@&#8203;Asapha](https://redirect.github.com/Asapha)
-
[@&#8203;ChristianKatzmann](https://redirect.github.com/ChristianKatzmann)
- [@&#8203;grandstaish](https://redirect.github.com/grandstaish)
- [@&#8203;jonamireh](https://redirect.github.com/jonamireh)
- [@&#8203;kevinguitar](https://redirect.github.com/kevinguitar)
- [@&#8203;svenjacobs](https://redirect.github.com/svenjacobs)
- [@&#8203;vRallev](https://redirect.github.com/vRallev)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these
updates again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My42Ni40IiwidXBkYXRlZEluVmVyIjoiNDMuNjYuNCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Harsh Shandilya <me@msfjarvis.dev>
2026-03-25 05:58:37 +00:00
renovate[bot]andGitHub 4d4bbb440e chore(deps): update gradle/actions digest to 39e147c 2026-03-24 18:08:31 +00:00
renovate[bot]andGitHub f0ed3871df chore(deps): update gradle/actions action to v6 2026-03-23 18:09:03 +00:00
Harsh Shandilya d5170b201f fixup! fix(comments): refactor state handling to not mark first-visit as unread 2026-03-23 14:26:38 +05:30
Harsh Shandilya 77c635b824 chore: update changelog 2026-03-23 14:23:01 +05:30
Harsh Shandilya 83db9bb8a8 fix(comments): refactor state handling to not mark first-visit as unread 2026-03-23 14:18:26 +05:30
Charles LombardoandHarsh Shandilya 6512567990 LobstersCard: Use M3 button for save and comment
The previous buttons were difficult to hit on a device that used gesture navigation. This adds more horizontal and vertical padding to the card and uses the M3 button with a larger clickable region to make this more accessible.

Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
2026-03-22 01:21:19 +05:30
Charles LombardoandHarsh Shandilya f244d35f74 LobstersCard: Increase max title lines to 2
I've frequently had to tap the comments button to read the full title of a post even though it could fit in a card within 2 lines. This prevents that issue in most cases.
2026-03-21 21:50:19 +05:30
renovate[bot]andGitHub 4b948cb7f4 chore(deps): update gradle to v9.4.1 2026-03-19 14:04:37 +00:00
renovate[bot]andGitHub 54455cc4c4 fix(deps): update dependency com.diffplug.spotless:spotless-plugin-gradle to v8.4.0 2026-03-18 18:12:25 +00:00
renovate[bot]andGitHub a4828040a8 fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v6.2.0 2026-03-18 13:20:53 +00:00
renovate[bot]andHarsh Shandilya 349f1fbc27 chore(deps): update dependency com.facebook:ktfmt to v0.62 2026-03-18 02:41:53 +05:30
renovate[bot]andGitHub 6ac339f56b fix(deps): update sentry to v8.36.0 2026-03-17 20:50:56 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
e8cc694950 fix(deps): update aboutlibraries to v14.0.0-b03 (#1108)
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| com.mikepenz.aboutlibraries.plugin.android | `14.0.0-b02` →
`14.0.0-b03` |
![age](https://developer.mend.io/api/mc/badges/age/maven/com.mikepenz.aboutlibraries.plugin.android:com.mikepenz.aboutlibraries.plugin.android.gradle.plugin/14.0.0-b03?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.mikepenz.aboutlibraries.plugin.android:com.mikepenz.aboutlibraries.plugin.android.gradle.plugin/14.0.0-b02/14.0.0-b03?slim=true)
|
|
[com.mikepenz:aboutlibraries-compose-m3](https://redirect.github.com/mikepenz/AboutLibraries)
| `14.0.0-b02` → `14.0.0-b03` |
![age](https://developer.mend.io/api/mc/badges/age/maven/com.mikepenz:aboutlibraries-compose-m3/14.0.0-b03?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.mikepenz:aboutlibraries-compose-m3/14.0.0-b02/14.0.0-b03?slim=true)
|
|
[com.mikepenz:aboutlibraries-core](https://redirect.github.com/mikepenz/AboutLibraries)
| `14.0.0-b02` → `14.0.0-b03` |
![age](https://developer.mend.io/api/mc/badges/age/maven/com.mikepenz:aboutlibraries-core/14.0.0-b03?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.mikepenz:aboutlibraries-core/14.0.0-b02/14.0.0-b03?slim=true)
|
|
[com.mikepenz:aboutlibraries-compose-core](https://redirect.github.com/mikepenz/AboutLibraries)
| `14.0.0-b02` → `14.0.0-b03` |
![age](https://developer.mend.io/api/mc/badges/age/maven/com.mikepenz:aboutlibraries-compose-core/14.0.0-b03?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.mikepenz:aboutlibraries-compose-core/14.0.0-b02/14.0.0-b03?slim=true)
|

---

### Release Notes

<details>
<summary>mikepenz/AboutLibraries
(com.mikepenz:aboutlibraries-compose-m3)</summary>

###
[`v14.0.0-b03`](https://redirect.github.com/mikepenz/AboutLibraries/releases/tag/14.0.0-b03)

[Compare
Source](https://redirect.github.com/mikepenz/AboutLibraries/compare/14.0.0-b02...14.0.0-b03)

##### 🚀 Features

- Migrate to ArtifactView API
- PR:
[#&#8203;1297](https://redirect.github.com/mikepenz/AboutLibraries/issues/1297)

##### 🧪 Tests

- Add Paparazzi preview tests and update dependencies
- PR:
[#&#8203;1298](https://redirect.github.com/mikepenz/AboutLibraries/issues/1298)

##### 💬 Other

- docs: add section about AGP 9.0 to README
- PR:
[#&#8203;1295](https://redirect.github.com/mikepenz/AboutLibraries/issues/1295)
- Bump actions/upload-artifact from 6 to 7
- PR:
[#&#8203;1307](https://redirect.github.com/mikepenz/AboutLibraries/issues/1307)
- docs: add description resource strings to keep file guidance
- PR:
[#&#8203;1308](https://redirect.github.com/mikepenz/AboutLibraries/issues/1308)
- Bump mikepenz/action-gh-release from 1 to 2
- PR:
[#&#8203;1309](https://redirect.github.com/mikepenz/AboutLibraries/issues/1309)

##### Contributors:

- [@&#8203;svenjacobs](https://redirect.github.com/svenjacobs),
[@&#8203;mikepenz](https://redirect.github.com/mikepenz),
[@&#8203;dependabot](https://redirect.github.com/dependabot)\[bot],
[@&#8203;jlee129589](https://redirect.github.com/jlee129589)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these
updates again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My42Ni40IiwidXBkYXRlZEluVmVyIjoiNDMuNjYuNCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-17 18:32:05 +00:00
Harsh Shandilya e3615795c0 fix: update dependencies to latest alphas 2026-03-17 23:49:23 +05:30
renovate[bot]andGitHub 43a2c2f4b3 chore(deps): update plugin poko to v0.22.0 2026-03-17 05:35:33 +00:00
renovate[bot]andGitHub a9f0569944 fix(deps): update sqldelight to v2.3.2 2026-03-17 00:59:07 +00:00
renovate[bot]andGitHub 5901b22fef fix(deps): update metro to v0.11.3 2026-03-16 22:04:39 +00:00
Harsh Shandilya 36213ad0a9 fixup! fix(deps): update kotlin and compose to v2.3.20 2026-03-16 20:58:12 +05:30
renovate[bot]andGitHub a10fe200e6 fix(deps): update kotlin and compose to v2.3.20 2026-03-16 10:01:50 +00:00
renovate[bot]andGitHub f6bfca6bbb fix(deps): update kotlinresult to v2.3.1 2026-03-14 14:30:41 +00:00
Harsh Shandilya 94b680192a chore: refresh feature graphics 2026-03-13 23:27:02 +05:30
renovate[bot]andGitHub a8dc8d3991 fix(deps): update sqldelight to v2.3.1 2026-03-13 04:49:06 +00:00
renovate[bot]andGitHub 46e55a5c26 fix(deps): update kotlinresult to v2.3.0 2026-03-13 00:42:08 +00:00
renovate[bot]andGitHub 820e422cfe fix(deps): update sentry to v8.35.0 2026-03-12 12:52:13 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
cc4c022e4e fix(deps): update kotlinresult to v2.2.0 (#1100)
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
|
[com.michael-bull.kotlin-result:kotlin-result-coroutines](https://redirect.github.com/michaelbull/kotlin-result)
| `2.1.0` → `2.2.0` |
![age](https://developer.mend.io/api/mc/badges/age/maven/com.michael-bull.kotlin-result:kotlin-result-coroutines/2.2.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.michael-bull.kotlin-result:kotlin-result-coroutines/2.1.0/2.2.0?slim=true)
|
|
[com.michael-bull.kotlin-result:kotlin-result](https://redirect.github.com/michaelbull/kotlin-result)
| `2.1.0` → `2.2.0` |
![age](https://developer.mend.io/api/mc/badges/age/maven/com.michael-bull.kotlin-result:kotlin-result/2.2.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.michael-bull.kotlin-result:kotlin-result/2.1.0/2.2.0?slim=true)
|

---

### Release Notes

<details>
<summary>michaelbull/kotlin-result
(com.michael-bull.kotlin-result:kotlin-result-coroutines)</summary>

###
[`v2.2.0`](https://redirect.github.com/michaelbull/kotlin-result/releases/tag/2.2.0)

[Compare
Source](https://redirect.github.com/michaelbull/kotlin-result/compare/2.1.0...2.2.0)

##### Fallible Operations (`try*`)

Fallible collection operations now use a `try` prefix, inspired by
[Gleam's naming
convention](https://hexdocs.pm/gleam_stdlib/gleam/list.html#try_map) and
mirroring stdlib's collection API more closely. Several new operations
have also been added.

- Rename `mapResult*` functions to `tryMap*`, `mapAll` to `tryMap`,
`fold`/`foldRight` to `tryFold`/`tryFoldRight`
([`d052cf5`](https://redirect.github.com/michaelbull/kotlin-result/commit/d052cf5c4c2cad12f9bb04686d5ecbf82fbf8ec6))
- The previous names are deprecated with `@Deprecated` + `ReplaceWith`
for easy migration
- Add `tryFind`, `tryFindLast`, `tryForEach`, `tryForEachIndexed`,
`tryReduce`, `tryReduceIndexed` for `Iterable`
([`d2011f0`](https://redirect.github.com/michaelbull/kotlin-result/commit/d2011f025873286cc550cab4174a5280560a01cd))
- Add `tryFilter`, `tryAssociate`, `tryFlatMap`, `tryGroupBy`,
`tryPartition` + variants for `Iterable`
([`f32985e`](https://redirect.github.com/michaelbull/kotlin-result/commit/f32985e4c9bafa0f4356aaca710245889b5acc15))

##### Flow Extensions

The `kotlin-result-coroutines` module now includes extensions for the
`kotlinx-coroutines` `Flow` type, bringing the same Result-aware
operations available on `Iterable` to asynchronous streams. This was
born out of the discussion in
[#&#8203;78](https://redirect.github.com/michaelbull/kotlin-result/issues/78).

- Add `Flow` extensions for `kotlin-coroutines`
([`15d44a0`](https://redirect.github.com/michaelbull/kotlin-result/commit/15d44a0c86795de5aa1899d68db2f2991cd9324a))
  - Factory: `Result<Flow<V>, E>.toFlow()`
- Flow: `filterOk`, `filterErr`, `onEachOk`, `onEachErr`, `allOk`,
`allErr`, `anyOk`, `anyErr`, `countOk`, `countErr`, `partition`,
`combine`, `combineErr`
- Try: `tryFilter`, `tryFilterNot`, `tryMap`, `tryMapNotNull`,
`tryFlatMap`, `tryForEach`, `tryReduce`, `tryFold`, `tryFind`,
`tryFindLast`, `tryAssociate`, `tryAssociateBy`, `tryAssociateWith`,
`tryGroupBy`, `tryPartition`

##### Other

- Rename `onSuccess`/`onFailure` to `onOk`/`onErr`
([`a7c66c0`](https://redirect.github.com/michaelbull/kotlin-result/commit/a7c66c0d8750c2602c748007a32b613a4e3c3220))
- The new indexed variants (`onEachOkIndexed`, `onEachErrIndexed`)
proved that the `Success`/`Failure` naming becomes too verbose.
`Ok`/`Err` is more readable.
- Rename `filterValues`/`filterErrors` to `filterOk`/`filterErr`
([`87f4c36`](https://redirect.github.com/michaelbull/kotlin-result/commit/87f4c36364b849313644fb8465f6e9091983117d))
- Aligns naming with existing
`allOk`/`allErr`/`anyOk`/`anyErr`/`countOk`/`countErr` conventions
- Add `onEachOk`, `onEachOkIndexed`, `onEachErr`, `onEachErrIndexed` for
`Iterable<Result>`
([`a7c66c0`](https://redirect.github.com/michaelbull/kotlin-result/commit/a7c66c0d8750c2602c748007a32b613a4e3c3220))
- Add `combineErr` and `combineTo`/`combineErrTo` for `Iterable<Result>`
([`1133e00`](https://redirect.github.com/michaelbull/kotlin-result/commit/1133e00a8ac14d7d45a2c492f779c55ae5198f07),
[`c06eeb3`](https://redirect.github.com/michaelbull/kotlin-result/commit/c06eeb373f81f582da9c101629ba50afb6f558f3))
- Propagate nested `coroutineBinding` failures by
[@&#8203;dbottillo](https://redirect.github.com/dbottillo)
([`d952b52`](https://redirect.github.com/michaelbull/kotlin-result/commit/d952b52141608f653855883f1cc6274d121341a2))
- Closes
[#&#8203;128](https://redirect.github.com/michaelbull/kotlin-result/issues/128)
and
[#&#8203;133](https://redirect.github.com/michaelbull/kotlin-result/issues/133)
- Make `BindingException` public
([`549ed30`](https://redirect.github.com/michaelbull/kotlin-result/commit/549ed30d0a7bed36175c2f6abcc0d93c830c7642))
- Fixes `'internal' type accessed from public inline declaration`
warning that will become an error in Kotlin 2.4
- Update Kotlin to 2.3.10
([`12ceff7`](https://redirect.github.com/michaelbull/kotlin-result/commit/12ceff754cb5ace39720a45603ae5c0dacf87b67))
- Update Gradle to 9.4.0
([`468b1a5`](https://redirect.github.com/michaelbull/kotlin-result/commit/468b1a5509403dd759917b137793f4482ffb4588))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these
updates again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My41OS4wIiwidXBkYXRlZEluVmVyIjoiNDMuNTkuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-12 06:58:39 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>Copilotmsfjarvis
70e7c305d0 fix(deps): update androidx (#1098)
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
|
[androidx.test.uiautomator:uiautomator](https://developer.android.com/jetpack/androidx/releases/test-uiautomator#2.3.0)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `2.4.0-beta01` → `2.4.0-beta02` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.test.uiautomator:uiautomator/2.4.0-beta02?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.test.uiautomator:uiautomator/2.4.0-beta01/2.4.0-beta02?slim=true)
|
|
[androidx.core:core-ktx](https://developer.android.com/jetpack/androidx/releases/core#1.18.0)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.17.0` → `1.18.0` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.core:core-ktx/1.18.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.core:core-ktx/1.17.0/1.18.0?slim=true)
|
|
[androidx.core:core](https://developer.android.com/jetpack/androidx/releases/core#1.18.0)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.17.0` → `1.18.0` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.core:core/1.18.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.core:core/1.17.0/1.18.0?slim=true)
|
|
[androidx.activity:activity-compose](https://developer.android.com/jetpack/androidx/releases/activity#1.13.0)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.12.4` → `1.13.0` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.activity:activity-compose/1.13.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.activity:activity-compose/1.12.4/1.13.0?slim=true)
|
|
[androidx.activity:activity](https://developer.android.com/jetpack/androidx/releases/activity#1.13.0)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.12.4` → `1.13.0` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.activity:activity/1.13.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.activity:activity/1.12.4/1.13.0?slim=true)
|
|
[androidx.compose.runtime:runtime-saveable](https://developer.android.com/jetpack/androidx/releases/compose-runtime#1.10.5)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-alpha06` → `1.11.0-beta01` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.runtime:runtime-saveable/1.11.0-beta01?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.runtime:runtime-saveable/1.11.0-alpha06/1.11.0-beta01?slim=true)
|
|
[androidx.paging:paging-compose](https://developer.android.com/jetpack/androidx/releases/paging#3.4.2)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `3.4.1` → `3.4.2` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.paging:paging-compose/3.4.2?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.paging:paging-compose/3.4.1/3.4.2?slim=true)
|
|
[androidx.paging:paging-common](https://developer.android.com/jetpack/androidx/releases/paging#3.4.2)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `3.4.1` → `3.4.2` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.paging:paging-common/3.4.2?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.paging:paging-common/3.4.1/3.4.2?slim=true)
|
|
[androidx.datastore:datastore-preferences-core](https://developer.android.com/jetpack/androidx/releases/datastore#1.2.1)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.2.0` → `1.2.1` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.datastore:datastore-preferences-core/1.2.1?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.datastore:datastore-preferences-core/1.2.0/1.2.1?slim=true)
|
|
[androidx.datastore:datastore-core](https://developer.android.com/jetpack/androidx/releases/datastore#1.2.1)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.2.0` → `1.2.1` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.datastore:datastore-core/1.2.1?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.datastore:datastore-core/1.2.0/1.2.1?slim=true)
|
|
[androidx.datastore:datastore-preferences](https://developer.android.com/jetpack/androidx/releases/datastore#1.2.1)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.2.0` → `1.2.1` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.datastore:datastore-preferences/1.2.1?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.datastore:datastore-preferences/1.2.0/1.2.1?slim=true)
|
|
[androidx.compose.ui:ui-unit](https://developer.android.com/jetpack/androidx/releases/compose-ui#1.10.5)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-alpha06` → `1.11.0-beta01` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.ui:ui-unit/1.11.0-beta01?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.ui:ui-unit/1.11.0-alpha06/1.11.0-beta01?slim=true)
|
|
[androidx.compose.ui:ui-tooling-preview](https://developer.android.com/jetpack/androidx/releases/compose-ui#1.10.5)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-alpha06` → `1.11.0-beta01` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.ui:ui-tooling-preview/1.11.0-beta01?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.ui:ui-tooling-preview/1.11.0-alpha06/1.11.0-beta01?slim=true)
|
|
[androidx.compose.ui:ui-tooling](https://developer.android.com/jetpack/androidx/releases/compose-ui#1.10.5)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-alpha06` → `1.11.0-beta01` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.ui:ui-tooling/1.11.0-beta01?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.ui:ui-tooling/1.11.0-alpha06/1.11.0-beta01?slim=true)
|
|
[androidx.compose.ui:ui-text](https://developer.android.com/jetpack/androidx/releases/compose-ui#1.10.5)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-alpha06` → `1.11.0-beta01` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.ui:ui-text/1.11.0-beta01?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.ui:ui-text/1.11.0-alpha06/1.11.0-beta01?slim=true)
|
|
[androidx.compose.ui:ui-graphics](https://developer.android.com/jetpack/androidx/releases/compose-ui#1.10.5)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-alpha06` → `1.11.0-beta01` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.ui:ui-graphics/1.11.0-beta01?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.ui:ui-graphics/1.11.0-alpha06/1.11.0-beta01?slim=true)
|
|
[androidx.compose.ui:ui](https://developer.android.com/jetpack/androidx/releases/compose-ui#1.10.5)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-alpha06` → `1.11.0-beta01` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.ui:ui/1.11.0-beta01?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.ui:ui/1.11.0-alpha06/1.11.0-beta01?slim=true)
|
|
[androidx.baselineprofile](https://developer.android.com/jetpack/androidx/releases/benchmark#1.4.1)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.5.0-alpha03` → `1.5.0-alpha04` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.baselineprofile:androidx.baselineprofile.gradle.plugin/1.5.0-alpha04?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.baselineprofile:androidx.baselineprofile.gradle.plugin/1.5.0-alpha03/1.5.0-alpha04?slim=true)
|
|
[androidx.benchmark:benchmark-macro-junit4](https://developer.android.com/jetpack/androidx/releases/benchmark#1.4.1)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.5.0-alpha03` → `1.5.0-alpha04` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.benchmark:benchmark-macro-junit4/1.5.0-alpha04?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.benchmark:benchmark-macro-junit4/1.5.0-alpha03/1.5.0-alpha04?slim=true)
|
|
[androidx.benchmark:benchmark-macro](https://developer.android.com/jetpack/androidx/releases/benchmark#1.4.1)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.5.0-alpha03` → `1.5.0-alpha04` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.benchmark:benchmark-macro/1.5.0-alpha04?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.benchmark:benchmark-macro/1.5.0-alpha03/1.5.0-alpha04?slim=true)
|
|
[androidx.compose.runtime:runtime-annotation](https://developer.android.com/jetpack/androidx/releases/compose-runtime#1.10.5)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-alpha06` → `1.11.0-beta01` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.runtime:runtime-annotation/1.11.0-beta01?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.runtime:runtime-annotation/1.11.0-alpha06/1.11.0-beta01?slim=true)
|
|
[androidx.compose.runtime:runtime](https://developer.android.com/jetpack/androidx/releases/compose-runtime#1.10.5)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-alpha06` → `1.11.0-beta01` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.runtime:runtime/1.11.0-beta01?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.runtime:runtime/1.11.0-alpha06/1.11.0-beta01?slim=true)
|
|
[androidx.compose.foundation:foundation-layout](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.10.5)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-alpha06` → `1.11.0-beta01` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.foundation:foundation-layout/1.11.0-beta01?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.foundation:foundation-layout/1.11.0-alpha06/1.11.0-beta01?slim=true)
|
|
[androidx.compose.foundation:foundation](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.10.5)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-alpha06` → `1.11.0-beta01` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.foundation:foundation/1.11.0-beta01?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.foundation:foundation/1.11.0-alpha06/1.11.0-beta01?slim=true)
|
|
[androidx.compose.animation:animation-core](https://developer.android.com/jetpack/androidx/releases/compose-animation#1.10.5)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-alpha06` → `1.11.0-beta01` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.animation:animation-core/1.11.0-beta01?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.animation:animation-core/1.11.0-alpha06/1.11.0-beta01?slim=true)
|
|
[androidx.compose.animation:animation](https://developer.android.com/jetpack/androidx/releases/compose-animation#1.10.5)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-alpha06` → `1.11.0-beta01` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.animation:animation/1.11.0-beta01?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.animation:animation/1.11.0-alpha06/1.11.0-beta01?slim=true)
|
|
[androidx.collection:collection](https://developer.android.com/jetpack/androidx/releases/collection#1.6.0)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.5.0` → `1.6.0` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.collection:collection/1.6.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.collection:collection/1.5.0/1.6.0?slim=true)
|

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My41OS4wIiwidXBkYXRlZEluVmVyIjoiNDMuNTkuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: msfjarvis <13348378+msfjarvis@users.noreply.github.com>
2026-03-11 20:46:56 +00:00
GitHub Actions e2242fce2e feat(release): start next development iteration 2026-03-10 05:26:48 +00:00
GitHub Actions 153867426a feat(release): bump version 2026-03-10 05:20:43 +00:00
Harsh Shandilya 68c3e0f58d chore: update changelog 2026-03-10 03:39:56 +05:30
Harsh Shandilya 7c9b93fc3a refactor: extract strings to be translatable 2026-03-10 03:38:00 +05:30
Harsh Shandilya c6cf566b13 fix(android): refresh baseline profiles 2026-03-09 15:42:18 +05:30
Harsh Shandilya 51c62bb441 fix(benchmark): give up on emulators
Trusty old Pixel 4a will have to do
2026-03-09 15:42:10 +05:30
Harsh Shandilya 389de2f9af chore: update Lint baseline 2026-03-09 13:18:49 +05:30
Harsh Shandilya 19a0f67d6d chore: update changelog 2026-03-09 13:17:40 +05:30
Harsh Shandilya 5bd3c3fbb9 perf(common): reduce UI jank in deeply nested comments by flattening comment tree
Replace the recursive logic that rendered individual Columns inside a LazyColumn
with a simpler implementation that just makes a flat list that LazyColumn can
directly render.
2026-03-09 10:46:26 +05:30
Harsh Shandilya 12d9b1e720 perf(api): optimize Jsoup parsing by streaming ResponseBody
`value.string()` parses the whole body which can be quite large, instead
use the ByteStream to do it in a more memory efficient manner.
2026-03-09 10:46:26 +05:30
Harsh Shandilya 944200cb42 fix(build): adjust maxParallelForks to avoid memory starvation
If this actually spawns the 48 forks on my machine it'll exhaust
memory way before it can even run anything.
2026-03-09 10:46:26 +05:30
Harsh Shandilya 4fa947ea18 fix(benchmark): only clear app data once before running tests 2026-03-09 10:46:26 +05:30
Harsh Shandilya 5f9ee4effb fix(common): prevent theoretical ClassCastException in Theme.kt
When applying EdgeToEdge window styling inside LobstersTheme, the code
was directly casting LocalView.current.context to Activity. This causes
a ClassCastException in scenarios where Compose wraps the context inside a
ContextWrapper (e.g. TintContextWrapper).
2026-03-09 10:46:26 +05:30
Harsh Shandilya 956afbd0c2 fix(common): flow state down properly for lazy list items
Hoisting isSaved and isRead out also has the nice downstream benefit
of making the different UI states easier to test as well as have them
actually reflect in the UI instead of needing to be faked by the local
saved state.
2026-03-09 10:46:26 +05:30
Harsh Shandilya de2366cf94 perf(android): optimize read/saved post lookups
These are guaranteed to only have unique items so we can make the hot lookups in UI code be
a Set's O(1) instead of a List's O(N).
2026-03-09 10:46:26 +05:30
renovate[bot]andGitHub 7dde15b65d fix(deps): update sentry to v8.34.1 2026-03-05 17:40:44 +00:00
Harsh ShandilyaandGitHub 173b5ef242 Revert "fix(deps): update sentry to v8.34.0" (#1092) 2026-03-05 15:06:52 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
89110596d0 fix(deps): update sentry to v8.34.0 (#1090)
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
|
[io.sentry:sentry-bom](https://redirect.github.com/getsentry/sentry-java)
| `8.33.0` → `8.34.0` |
![age](https://developer.mend.io/api/mc/badges/age/maven/io.sentry:sentry-bom/8.34.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/io.sentry:sentry-bom/8.33.0/8.34.0?slim=true)
|
|
[io.sentry:sentry-android-sqlite](https://redirect.github.com/getsentry/sentry-java)
| `8.33.0` → `8.34.0` |
![age](https://developer.mend.io/api/mc/badges/age/maven/io.sentry:sentry-android-sqlite/8.34.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/io.sentry:sentry-android-sqlite/8.33.0/8.34.0?slim=true)
|
|
[io.sentry:sentry-android-core](https://redirect.github.com/getsentry/sentry-java)
| `8.33.0` → `8.34.0` |
![age](https://developer.mend.io/api/mc/badges/age/maven/io.sentry:sentry-android-core/8.34.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/io.sentry:sentry-android-core/8.33.0/8.34.0?slim=true)
|
| [io.sentry:sentry](https://redirect.github.com/getsentry/sentry-java)
| `8.33.0` → `8.34.0` |
![age](https://developer.mend.io/api/mc/badges/age/maven/io.sentry:sentry/8.34.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/io.sentry:sentry/8.33.0/8.34.0?slim=true)
|

---

### Release Notes

<details>
<summary>getsentry/sentry-java (io.sentry:sentry-bom)</summary>

###
[`v8.34.0`](https://redirect.github.com/getsentry/sentry-java/blob/HEAD/CHANGELOG.md#8340)

[Compare
Source](https://redirect.github.com/getsentry/sentry-java/compare/8.33.0...8.34.0)

##### Features

- Add scope-level attributes API
([#&#8203;5118](https://redirect.github.com/getsentry/sentry-java/pull/5118))
via
([#&#8203;5148](https://redirect.github.com/getsentry/sentry-java/pull/5148))
- Automatically include scope attributes in logs and metrics
([#&#8203;5120](https://redirect.github.com/getsentry/sentry-java/pull/5120))
- New APIs are `Sentry.setAttribute`, `Sentry.setAttributes`,
`Sentry.removeAttribute`
- Support collections and arrays in attribute type inference
([#&#8203;5124](https://redirect.github.com/getsentry/sentry-java/pull/5124))
- Add support for `SENTRY_SAMPLE_RATE` environment variable /
`sample-rate` property
([#&#8203;5112](https://redirect.github.com/getsentry/sentry-java/pull/5112))
- Create `sentry-opentelemetry-otlp` and
`sentry-opentelemetry-otlp-spring` modules for combining OpenTelemetry
SDK OTLP export with Sentry SDK
([#&#8203;5100](https://redirect.github.com/getsentry/sentry-java/pull/5100))
- OpenTelemetry is configured to send spans to Sentry directly using an
OTLP endpoint.
- Sentry only uses trace and span ID from OpenTelemetry (via
`OpenTelemetryOtlpEventProcessor`) but will not send spans through
OpenTelemetry nor use OpenTelemetry `Context` for `Scopes` propagation.
- See the OTLP setup docs for
[Java](https://docs.sentry.io/platforms/java/opentelemetry/setup/otlp/)
and [Spring
Boot](https://docs.sentry.io/platforms/java/guides/spring-boot/opentelemetry/setup/otlp/)
for installation and configuration instructions.
- Add screenshot masking support using view hierarchy
([#&#8203;5077](https://redirect.github.com/getsentry/sentry-java/pull/5077))
- Masks sensitive content (text, images) in error screenshots using the
same view hierarchy approach as Session Replay
- Requires the `sentry-android-replay` module to be present at runtime
for masking to work
  - Enable via code:
    ```kotlin
    SentryAndroid.init(context) { options ->
        options.isAttachScreenshot = true
        options.screenshot.setMaskAllText(true)
        options.screenshot.setMaskAllImages(true)
        // Or mask specific view classes
        options.screenshot.addMaskViewClass("com.example.MyCustomView")
    }
    ```
  - Or via `AndroidManifest.xml`:
    ```xml
<meta-data android:name="io.sentry.attach-screenshot"
android:value="true" />
<meta-data android:name="io.sentry.screenshot.mask-all-text"
android:value="true" />
<meta-data android:name="io.sentry.screenshot.mask-all-images"
android:value="true" />
    ```
- The `ManifestMetaDataReader` now read the `DIST`
([#&#8203;5107](https://redirect.github.com/getsentry/sentry-java/pull/5107))

##### Fixes

- Fix attribute type detection for `Long`, `Short`, `Byte`,
`BigInteger`, `AtomicInteger`, and `AtomicLong` being incorrectly
inferred as `double` instead of `integer`
([#&#8203;5122](https://redirect.github.com/getsentry/sentry-java/pull/5122))
- Remove `AndroidRuntimeManager` StrictMode relaxation to prevent ANRs
during SDK init
([#&#8203;5127](https://redirect.github.com/getsentry/sentry-java/pull/5127))
- **IMPORTANT:** StrictMode violations may appear again in debug builds.
This is intentional to prevent ANRs in production releases.
- Fix crash when unregistering `SystemEventsBroadcastReceiver` with
try-catch block.
([#&#8203;5106](https://redirect.github.com/getsentry/sentry-java/pull/5106))
- Use `peekDecorView` instead of `getDecorView` in
`SentryGestureListener` to avoid forcing view hierarchy construction
([#&#8203;5134](https://redirect.github.com/getsentry/sentry-java/pull/5134))
- Log an actionable error message when Relay returns HTTP 413 (Content
Too Large)
([#&#8203;5115](https://redirect.github.com/getsentry/sentry-java/pull/5115))
- Also switch the client report discard reason for all HTTP 4xx/5xx
errors (except 429) from `network_error` to `send_error`
- Trim DSN string before parsing to avoid `URISyntaxException` caused by
trailing whitespace
([#&#8203;5113](https://redirect.github.com/getsentry/sentry-java/pull/5113))
- Reduce allocations and bytecode instructions during `Sentry.init`
([#&#8203;5135](https://redirect.github.com/getsentry/sentry-java/pull/5135))

##### Dependencies

- Bump Native SDK from v0.12.7 to v0.13.1
([#&#8203;5104](https://redirect.github.com/getsentry/sentry-java/pull/5104))
-
[changelog](https://redirect.github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0131)
-
[diff](https://redirect.github.com/getsentry/sentry-native/compare/0.12.7...0.13.1)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these
updates again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My40OC4xIiwidXBkYXRlZEluVmVyIjoiNDMuNDguMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-04 22:17:53 +00:00
renovate[bot]andGitHub fa15fc9fb2 chore(deps): update gradle to v9.4.0 2026-03-04 14:35:44 +00:00
renovate[bot]andGitHub dac3d505d0 fix(deps): update agp to v9.1.0 2026-03-03 16:52:01 +00:00
renovate[bot]andGitHub 2dbd8b0cb0 fix(deps): update dependency com.diffplug.spotless:spotless-plugin-gradle to v8.3.0 2026-03-03 05:03:29 +00:00
renovate[bot]andGitHub c3d5a4aeb2 chore(deps): update plugin poko to v0.21.3 2026-03-03 01:41:09 +00:00
renovate[bot]andGitHub 1636adec9d fix(deps): update metro to v0.11.2 2026-03-02 09:58:08 +00:00
GitHub Actions 55c3f3165a feat(release): start next development iteration 2026-02-28 13:17:26 +00:00
GitHub Actions 11f232ba44 feat(release): bump version 2026-02-28 13:13:11 +00:00
renovate[bot]andGitHub c750c492ae chore(deps): update plugin poko to v0.21.2 2026-02-27 22:16:39 +00:00
renovate[bot]andGitHub 0ee2fd5963 chore(deps): update gradle to v9.4.0-rc-2 2026-02-27 14:00:45 +00:00
renovate[bot]andGitHub 6529567817 chore(deps): update actions/upload-artifact action to v7 2026-02-27 02:41:17 +00:00
renovate[bot]andGitHub 219b53c23d chore(deps): update plugin dependencyanalysis to v3.6.1 2026-02-26 21:29:11 +00:00
renovate[bot]andGitHub 1c2635e2fd chore(deps): update plugin dependencyanalysis to v3.6.0 2026-02-26 08:58:40 +00:00
renovate[bot]andGitHub 960e226f8f fix(deps): update metro to v0.11.1 2026-02-26 06:01:29 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
e1e691ff4a fix(deps): update androidx (#1083)
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
|
[androidx.compose.runtime:runtime-saveable](https://developer.android.com/jetpack/androidx/releases/compose-runtime#1.10.4)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-alpha05` → `1.11.0-alpha06` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.runtime:runtime-saveable/1.11.0-alpha06?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.runtime:runtime-saveable/1.11.0-alpha05/1.11.0-alpha06?slim=true)
|
|
[androidx.compose.material3.adaptive:adaptive-navigation3](https://developer.android.com/jetpack/androidx/releases/compose-material3-adaptive#1.3.0-alpha09)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.3.0-alpha08` → `1.3.0-alpha09` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.material3.adaptive:adaptive-navigation3/1.3.0-alpha09?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.material3.adaptive:adaptive-navigation3/1.3.0-alpha08/1.3.0-alpha09?slim=true)
|
|
[androidx.compose.material3.adaptive:adaptive-layout](https://developer.android.com/jetpack/androidx/releases/compose-material3-adaptive#1.2.0)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.3.0-alpha08` → `1.3.0-alpha09` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.material3.adaptive:adaptive-layout/1.3.0-alpha09?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.material3.adaptive:adaptive-layout/1.3.0-alpha08/1.3.0-alpha09?slim=true)
|
|
[androidx.compose.material3.adaptive:adaptive](https://developer.android.com/jetpack/androidx/releases/compose-material3-adaptive#1.2.0)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.3.0-alpha08` → `1.3.0-alpha09` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.material3.adaptive:adaptive/1.3.0-alpha09?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.material3.adaptive:adaptive/1.3.0-alpha08/1.3.0-alpha09?slim=true)
|
|
[androidx.compose.ui:ui-unit](https://developer.android.com/jetpack/androidx/releases/compose-ui#1.10.4)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-alpha05` → `1.11.0-alpha06` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.ui:ui-unit/1.11.0-alpha06?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.ui:ui-unit/1.11.0-alpha05/1.11.0-alpha06?slim=true)
|
|
[androidx.compose.ui:ui-tooling-preview](https://developer.android.com/jetpack/androidx/releases/compose-ui#1.10.4)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-alpha05` → `1.11.0-alpha06` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.ui:ui-tooling-preview/1.11.0-alpha06?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.ui:ui-tooling-preview/1.11.0-alpha05/1.11.0-alpha06?slim=true)
|
|
[androidx.compose.ui:ui-tooling](https://developer.android.com/jetpack/androidx/releases/compose-ui#1.10.4)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-alpha05` → `1.11.0-alpha06` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.ui:ui-tooling/1.11.0-alpha06?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.ui:ui-tooling/1.11.0-alpha05/1.11.0-alpha06?slim=true)
|
|
[androidx.compose.ui:ui-text](https://developer.android.com/jetpack/androidx/releases/compose-ui#1.10.4)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-alpha05` → `1.11.0-alpha06` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.ui:ui-text/1.11.0-alpha06?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.ui:ui-text/1.11.0-alpha05/1.11.0-alpha06?slim=true)
|
|
[androidx.compose.ui:ui-graphics](https://developer.android.com/jetpack/androidx/releases/compose-ui#1.10.4)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-alpha05` → `1.11.0-alpha06` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.ui:ui-graphics/1.11.0-alpha06?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.ui:ui-graphics/1.11.0-alpha05/1.11.0-alpha06?slim=true)
|
|
[androidx.compose.ui:ui](https://developer.android.com/jetpack/androidx/releases/compose-ui#1.10.4)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-alpha05` → `1.11.0-alpha06` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.ui:ui/1.11.0-alpha06?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.ui:ui/1.11.0-alpha05/1.11.0-alpha06?slim=true)
|
|
[androidx.compose.runtime:runtime-annotation](https://developer.android.com/jetpack/androidx/releases/compose-runtime#1.10.4)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-alpha05` → `1.11.0-alpha06` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.runtime:runtime-annotation/1.11.0-alpha06?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.runtime:runtime-annotation/1.11.0-alpha05/1.11.0-alpha06?slim=true)
|
|
[androidx.compose.runtime:runtime](https://developer.android.com/jetpack/androidx/releases/compose-runtime#1.10.4)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-alpha05` → `1.11.0-alpha06` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.runtime:runtime/1.11.0-alpha06?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.runtime:runtime/1.11.0-alpha05/1.11.0-alpha06?slim=true)
|
|
[androidx.compose.material3:material3-window-size-class](https://developer.android.com/jetpack/androidx/releases/compose-material3#1.4.0)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.5.0-alpha14` → `1.5.0-alpha15` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.material3:material3-window-size-class/1.5.0-alpha15?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.material3:material3-window-size-class/1.5.0-alpha14/1.5.0-alpha15?slim=true)
|
|
[androidx.compose.material3:material3](https://developer.android.com/jetpack/androidx/releases/compose-material3#1.4.0)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.5.0-alpha14` → `1.5.0-alpha15` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.material3:material3/1.5.0-alpha15?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.material3:material3/1.5.0-alpha14/1.5.0-alpha15?slim=true)
|
|
[androidx.compose.foundation:foundation-layout](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.10.4)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-alpha05` → `1.11.0-alpha06` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.foundation:foundation-layout/1.11.0-alpha06?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.foundation:foundation-layout/1.11.0-alpha05/1.11.0-alpha06?slim=true)
|
|
[androidx.compose.foundation:foundation](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.10.4)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-alpha05` → `1.11.0-alpha06` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.foundation:foundation/1.11.0-alpha06?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.foundation:foundation/1.11.0-alpha05/1.11.0-alpha06?slim=true)
|
|
[androidx.compose.animation:animation-core](https://developer.android.com/jetpack/androidx/releases/compose-animation#1.10.4)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-alpha05` → `1.11.0-alpha06` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.animation:animation-core/1.11.0-alpha06?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.animation:animation-core/1.11.0-alpha05/1.11.0-alpha06?slim=true)
|
|
[androidx.compose.animation:animation](https://developer.android.com/jetpack/androidx/releases/compose-animation#1.10.4)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.11.0-alpha05` → `1.11.0-alpha06` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose.animation:animation/1.11.0-alpha06?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose.animation:animation/1.11.0-alpha05/1.11.0-alpha06?slim=true)
|

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4zNi4yIiwidXBkYXRlZEluVmVyIjoiNDMuMzYuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-25 22:53:09 +00:00
Harsh Shandilya 3a2779f6b6 feat(android): replace uses of old TagFilterRepository with TagBlockRepository 2026-02-26 01:20:01 +05:30
Harsh Shandilya 9ba11422ab feat(common): add new tag blocking implementation
Ultimately I decided to just use SQLite instead of trying to shove
a JSON-formatted Map into a DataStore<Preferences> instance. A migration
has been included to move both the current stable tag implementation
as well as the newly added time-bound tags into the SQL database, a Sentry
metric will track the migrations and when the metric hits zero, I will
drop the DataStore dependency.
2026-02-26 01:20:01 +05:30
Harsh Shandilya 72219b7187 feat: add a table for tag blocks 2026-02-26 01:20:01 +05:30
Harsh Shandilya d022ebf475 chore: cleanup changelog 2026-02-25 18:40:45 +05:30
renovate[bot]andGitHub ec031da893 fix(deps): update metro to v0.11.0 2026-02-25 07:02:48 +00:00
renovate[bot]andGitHub e081f428e7 fix(deps): update coil3 to v3.4.0 2026-02-24 19:14:28 +00:00
Harsh Shandilya a3d8d5b4d3 feat: allow blocking tags for a specific amount of time 2026-02-24 21:55:47 +05:30
renovate[bot]andGitHub 37523588e1 chore(deps): update gradle/actions digest to 0723195 2026-02-24 02:03:04 +00:00
renovate[bot]andGitHub e5d15109ce fix(deps): update dependency nl.littlerobots.version-catalog-update:nl.littlerobots.version-catalog-update.gradle.plugin to v1.1.0 2026-02-21 13:30:16 +00:00
renovate[bot]andGitHub a9099fe25b fix(deps): update agp to v9.1.0-rc01 2026-02-19 21:08:55 +00:00
renovate[bot]andGitHub 9091e0b096 fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v6.1.0 2026-02-19 13:08:24 +00:00
renovate[bot]andGitHub 3089d8d4f6 fix(deps): update sentry to v8.33.0 2026-02-18 14:31:02 +00:00
renovate[bot]andGitHub 1e2008115b chore(deps): update plugin ksp to v2.3.6 2026-02-18 01:54:10 +00:00
Harsh Shandilya 62f51c7e91 fix(common): hoist comment handler to view model layer 2026-02-16 02:30:43 +05:30
Harsh Shandilya 54fffdbd72 chore: update changelog 2026-02-16 02:30:43 +05:30
renovate[bot]andGitHub a15f809d86 fix(deps): update dependency org.junit:junit-bom to v6.0.3 2026-02-15 14:27:20 +00:00
Harsh Shandilya 0da0cb56bb fix(common): make tag background more muted 2026-02-15 12:28:50 +05:30
GitHub Actions a1b3e2686b feat(release): start next development iteration 2026-02-14 17:41:13 +00:00
GitHub Actions 650285f2b9 feat(release): bump version 2026-02-14 17:37:22 +00:00
renovate[bot]andGitHub 9857883b52 fix(deps): update metro to v0.10.4 2026-02-14 02:29:48 +00:00
renovate[bot]andGitHub cc4f102efc fix(deps): update agp to v9.1.0-alpha09 2026-02-12 19:48:39 +00:00
renovate[bot]andGitHub 7b4d13071d fix(deps): update androidx 2026-02-11 20:31:57 +00:00
Harsh Shandilya e1a0738afb fix(android): guard against an empty backstack
Fixes COMPOSE-LOBSTERS-A1
Fixes COMPOSE-LOBSTERS-9Z
2026-02-12 00:04:01 +05:30
renovate[bot]andGitHub fcc29213d0 fix(deps): update dependency dev.chrisbanes.haze:haze to v1.7.2 2026-02-11 01:10:10 +00:00
renovate[bot]andGitHub bbfc52dfe2 fix(deps): update metro to v0.10.3 2026-02-10 14:05:25 +00:00
Harsh ShandilyaandGitHub 3db67ddbb0 fix(build): remove Sentry Size Analysis
This is out of early access and billable now.

Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
2026-02-10 16:33:33 +05:30
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
315df3032b fix(deps): update sentry to v8.32.0 (#1067)
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
|
[io.sentry:sentry-bom](https://redirect.github.com/getsentry/sentry-java)
| `8.31.0` → `8.32.0` |
![age](https://developer.mend.io/api/mc/badges/age/maven/io.sentry:sentry-bom/8.32.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/io.sentry:sentry-bom/8.31.0/8.32.0?slim=true)
|
|
[io.sentry:sentry-android-sqlite](https://redirect.github.com/getsentry/sentry-java)
| `8.31.0` → `8.32.0` |
![age](https://developer.mend.io/api/mc/badges/age/maven/io.sentry:sentry-android-sqlite/8.32.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/io.sentry:sentry-android-sqlite/8.31.0/8.32.0?slim=true)
|
|
[io.sentry:sentry-android-core](https://redirect.github.com/getsentry/sentry-java)
| `8.31.0` → `8.32.0` |
![age](https://developer.mend.io/api/mc/badges/age/maven/io.sentry:sentry-android-core/8.32.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/io.sentry:sentry-android-core/8.31.0/8.32.0?slim=true)
|
| [io.sentry:sentry](https://redirect.github.com/getsentry/sentry-java)
| `8.31.0` → `8.32.0` |
![age](https://developer.mend.io/api/mc/badges/age/maven/io.sentry:sentry/8.32.0?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/io.sentry:sentry/8.31.0/8.32.0?slim=true)
|

---

### Release Notes

<details>
<summary>getsentry/sentry-java (io.sentry:sentry-bom)</summary>

###
[`v8.32.0`](https://redirect.github.com/getsentry/sentry-java/blob/HEAD/CHANGELOG.md#8320)

[Compare
Source](https://redirect.github.com/getsentry/sentry-java/compare/8.31.0...8.32.0)

##### Features

- Add `installGroupsOverride` parameter and `installGroups` property to
Build Distribution SDK
([#&#8203;5062](https://redirect.github.com/getsentry/sentry-java/pull/5062))
- Update Android targetSdk to API 36 (Android 16)
([#&#8203;5016](https://redirect.github.com/getsentry/sentry-java/pull/5016))
- Add AndroidManifest support for Spotlight configuration via
`io.sentry.spotlight.enable` and `io.sentry.spotlight.url`
([#&#8203;5064](https://redirect.github.com/getsentry/sentry-java/pull/5064))
- Collect database transaction spans (`BEGIN`, `COMMIT`, `ROLLBACK`)
([#&#8203;5072](https://redirect.github.com/getsentry/sentry-java/pull/5072))
- To enable creation of these spans, set
`options.enableDatabaseTransactionTracing` to `true`
- `enable-database-transaction-tracing=true` when using
`sentry.properties`
- For Spring Boot, use `sentry.enable-database-transaction-tracing=true`
in `application.properties` or in `application.yml`:
    ```yaml
    sentry:
      enable-database-transaction-tracing: true
    ```
- Add support for collecting native crashes using Tombstones
([#&#8203;4933](https://redirect.github.com/getsentry/sentry-java/pull/4933),
[#&#8203;5037](https://redirect.github.com/getsentry/sentry-java/pull/5037))
- Added Tombstone integration that detects native crashes using
`ApplicationExitInfo.REASON_CRASH_NATIVE` on Android 12+
- Crashes enriched with Tombstones contain more crash details and
detailed thread info
- Tombstone and NDK integrations are now automatically merged into a
single crash event, eliminating duplicate reports
  - To enable it, add the integration in your Sentry initialization:
    ```kotlin
    SentryAndroid.init(context, options -> {
        options.isTombstoneEnabled = true
    })
    ```
    or in the `AndroidManifest.xml` using:
    ```xml
<meta-data android:name="io.sentry.tombstone.enable"
android:value="true" />
    ```

##### Fixes

- Extract `SpotlightIntegration` to separate `sentry-spotlight` module
to prevent insecure HTTP URLs from appearing in release APKs
([#&#8203;5064](https://redirect.github.com/getsentry/sentry-java/pull/5064))
- **Breaking:** Users who enable Spotlight must now add the
`io.sentry:sentry-spotlight` dependency:
    ```kotlin
    dependencies {
        debugImplementation("io.sentry:sentry-spotlight:<version>")
    }
    ```
- Fix scroll target detection for Jetpack Compose
([#&#8203;5017](https://redirect.github.com/getsentry/sentry-java/pull/5017))
- No longer fork Sentry `Scopes` for `reactor-kafka` consumer poll
`Runnable`
([#&#8203;5080](https://redirect.github.com/getsentry/sentry-java/pull/5080))
- This was causing a memory leak because `reactor-kafka`'s poll event
reschedules itself infinitely, and each invocation of
`SentryScheduleHook` created forked scopes with a parent reference,
building an unbounded chain that couldn't be garbage collected.
- Fix cold/warm app start type detection for Android devices running API
level 34+
([#&#8203;4999](https://redirect.github.com/getsentry/sentry-java/pull/4999))

##### Internal

- Establish new native exception mechanisms to differentiate events
generated by `sentry-native` from `ApplicationExitInfo`.
([#&#8203;5052](https://redirect.github.com/getsentry/sentry-java/pull/5052))
- Set `write` permission for `statuses` in the changelog preview GHA
workflow.
([#&#8203;5053](https://redirect.github.com/getsentry/sentry-java/pull/5053))

##### Dependencies

- Bump Native SDK from v0.12.3 to v0.12.4
([#&#8203;5061](https://redirect.github.com/getsentry/sentry-java/pull/5061))
-
[changelog](https://redirect.github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0124)
-
[diff](https://redirect.github.com/getsentry/sentry-native/compare/0.12.3...0.12.4)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these
updates again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi45Ny4wIiwidXBkYXRlZEluVmVyIjoiNDIuOTcuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-10 08:37:24 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
25f7189f68 chore(deps): pin gradle/actions action to f29f5a9 (#1066)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [gradle/actions](https://redirect.github.com/gradle/actions) | action
| pinDigest | → `f29f5a9` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi45Ny4wIiwidXBkYXRlZEluVmVyIjoiNDIuOTcuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-10 05:55:25 +00:00
Harsh Shandilya 393af79ded fix(android): add Sentry breadcrumbs integration for Navigation3 2026-02-10 09:07:36 +05:30
Harsh Shandilya 00b9a7a0db fix(ci): validate Gradle wrapper during setup 2026-02-09 23:05:26 +05:30
Harsh Shandilya 62f9fb8b37 fix(build): upgrade to Gradle 9.4.0-rc-1 2026-02-09 23:02:51 +05:30
Harsh Shandilya 3f04bfbbca fix(android): add Sentry breadcrumbs to debug the widget crashes 2026-02-08 02:55:53 +05:30
Harsh Shandilya 573ca7a2f9 chore: refresh baseline profiles 2026-02-08 02:13:54 +05:30
Harsh Shandilya 69e36ca09a fix(build): also configure com.android.test plugin 2026-02-08 02:08:31 +05:30
Harsh Shandilya 5bc01eab4d fix(android): ensure Glance actionStartActivity invariant is upheld
Fixes COMPOSE-LOBSTERS-9X
2026-02-08 01:19:20 +05:30
Harsh Shandilya 48b641d3b8 refactor: replace FlowRow with FlexBox and regen Lint baselines 2026-02-08 01:16:06 +05:30
Harsh Shandilya a6517120b1 chore: expand preview tags list 2026-02-08 00:31:30 +05:30
renovate[bot]andGitHub 78fecb7192 fix(deps): update agp to v9.1.0-alpha08 2026-02-05 17:53:11 +00:00
renovate[bot]andGitHub c66b110525 fix(deps): update kotlin and compose to v2.3.10 2026-02-05 08:50:00 +00:00
renovate[bot]andGitHub e90ea6a6d4 fix(deps): update kotlin and compose to v2.3.10-rc2 2026-02-02 12:47:41 +00:00
CopilotGitHubcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>msfjarvis
ae580612dd Fix IllegalStateException in RetryAfterInterceptor (#1060)
`RetryAfterInterceptor` calls `chain.proceed()` twice when retrying
rate-limited requests, violating the network interceptor contract that
requires exactly one `proceed()` call.

## Changes

- Changed interceptor registration from `addNetworkInterceptor` to
`addInterceptor` in `OkHttpModule.kt`
- Application interceptors allow multiple `proceed()` calls for retries

```kotlin
// Before: network interceptors cannot retry
interceptors.forEach(::addNetworkInterceptor)

// After: application interceptors support retries
interceptors.forEach(::addInterceptor)
```

## Context

When the Lobsters API returns a 429 or 503 with `Retry-After` header,
the interceptor sleeps for the specified duration then retries the
request. This requires calling `proceed()` twice: once for the initial
attempt (line 31) and once for the retry (line 45).

> [!WARNING]
>
> <details>
> <summary>Firewall rules blocked me from connecting to one or more
addresses (expand for details)</summary>
>
> #### I tried to connect to the following addresses, but was blocked by
firewall rules:
>
> - `dl.google.com`
> - Triggering command: `/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
/usr/lib/jvm/temurin-17-jdk-amd64/bin/java
--add-opens=java.base/java.lang=ALL-UNNAMED
--add-opens=java.base/java.lang.invoke=ALL-UNNAMED
--add-opens=java.base/java.util=ALL-UNNAMED
--add-opens=java.prefs/java.util.prefs=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
--add-opens=java.base/java.util=ALL-UNNAMED
--add-opens=java.prefs/java.util.prefs=ALL-UNNAMED
--add-opens=java.base/java.nio.charset=ALL-UNNAMED
--add-opens=java.base/java.net=ALL-UNNAMED
--add-opens=java.base/java.util.concurrent=ALL-UNNAMED
--add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED
--add-opens=java.xml/javax.xml.namespace=ALL-UNNAMED
--add-opens=java.base/java.time=ALL-UNNAMED
-XX:&#43;HeapDumpOnOutOfMemoryError -Xmx3072m -Dfile.encoding=UTF-8
-Duser.country -Duser.language=en` (dns block)
>
> If you need me to access, download, or install something from one of
these locations, you can either:
>
> - Configure [Actions setup
steps](https://gh.io/copilot/actions-setup-steps) to set up my
environment, which run before the firewall is enabled
> - Add the appropriate URLs or hosts to the custom allowlist in this
repository's [Copilot coding agent
settings](https://github.com/msfjarvis/compose-lobsters/settings/copilot/coding_agent)
(admins only)
>
> </details>

<!-- START COPILOT CODING AGENT SUFFIX -->



<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>

> Fix this crash: IllegalStateException: network interceptor
dev.msfjarvis.claw.core.network.RetryAfterInterceptor@4810a2c must call
proceed() exactly once
> at
dev.msfjarvis.claw.core.network.RetryAfterInterceptor.intercept(RetryAfterInterceptor.kt:45)


</details>



<!-- START COPILOT CODING AGENT TIPS -->
---

💡 You can make Copilot smarter by setting up custom instructions,
customizing its development environment and configuring Model Context
Protocol (MCP) servers. Learn more [Copilot coding agent
tips](https://gh.io/copilot-coding-agent-tips) in the docs.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: msfjarvis <13348378+msfjarvis@users.noreply.github.com>
2026-02-01 06:06:41 +00:00
renovate[bot]andGitHub 3736086445 fix(deps): update agp to v9.1.0-alpha07 2026-01-30 20:51:54 +00:00
renovate[bot]andGitHub 0f817ff7f9 fix(deps): update dependency androidx.activity:activity to v1.12.3 2026-01-30 02:07:24 +00:00
renovate[bot]andGitHub bf20553e15 chore(deps): update gradle to v9.3.1 2026-01-29 18:06:39 +00:00
renovate[bot]andGitHub 850ee0bec8 fix(deps): update metro to v0.10.2 2026-01-29 05:02:07 +00:00
renovate[bot]andGitHub fd073e7210 fix(deps): update androidx 2026-01-29 02:10:04 +00:00
renovate[bot]andGitHub 8ba3a9c7fa chore(deps): update plugin ksp to v2.3.5 2026-01-28 20:40:11 +00:00
renovate[bot]andGitHub 9ec30ecf81 chore(deps): update gradle/actions digest to f29f5a9 2026-01-28 19:25:53 +00:00
renovate[bot]andGitHub a363b992a7 chore(deps): update plugin com.gradle.develocity to v4.3.2 2026-01-28 17:44:13 +00:00
renovate[bot]andGitHub b604c60cc4 fix(deps): update dependency com.diffplug.spotless:spotless-plugin-gradle to v8.2.1 2026-01-27 22:38:48 +00:00
renovate[bot]andGitHub 4b80be25de fix(deps): update agp to v9.1.0-alpha06 2026-01-26 19:14:01 +00:00
Harsh Shandilya da65b056ee fix(build): tweak AGP config and regen Lint baselines 2026-01-27 00:42:43 +05:30
Harsh Shandilya 769fb6ce51 fix(android): count uses of import/export feature 2026-01-27 00:13:28 +05:30
renovate[bot]andGitHub 35a59bc53c fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v6.0.0 2026-01-26 11:02:33 +00:00
Harsh Shandilya a4d3af8049 fix(build): fix java target mismatch in build-logic module 2026-01-24 22:40:14 +05:30
Harsh Shandilya eadd898853 fix(build): enable some new Kotlin features 2026-01-24 22:38:35 +05:30
renovate[bot]andGitHub da72851ecc fix(deps): update metro to v0.10.1 2026-01-23 22:45:37 +00:00
Harsh Shandilya 006a2593c6 chore: update changelog 2026-01-23 21:15:24 +05:30
Harsh Shandilya e44f9eacf0 chore(ci): automatically inject the store links footer to release notes 2026-01-23 21:13:18 +05:30
renovate[bot]andGitHub 0d442880f0 fix(deps): update dependency com.diffplug.spotless:spotless-plugin-gradle to v8.2.0 2026-01-22 21:23:44 +00:00
renovate[bot]andGitHub 6a7d2606f9 chore(deps): update actions/checkout action to v6.0.2 2026-01-22 17:11:34 +00:00
renovate[bot]andGitHub 01ac01a051 chore(deps): update actions/setup-java action to v5.2.0 2026-01-22 05:45:17 +00:00
renovate[bot]andGitHub 88dc15a7e7 fix(deps): update sentry to v8.31.0 2026-01-21 21:37:45 +00:00
renovate[bot]andGitHub e55d9b5007 fix(deps): update serialization to v1.10.0 2026-01-21 16:55:32 +00:00
renovate[bot]andGitHub ef90a8e100 fix(deps): update aboutlibraries to v14.0.0-b02 2026-01-21 13:47:43 +00:00
Harsh Shandilya 3e8bd94cf0 refactor: adopt Metro 0.10.0 features 2026-01-20 10:36:21 +05:30
renovate[bot]andGitHub e2b3671f4c fix(deps): update metro to v0.10.0 2026-01-20 00:56:42 +00:00
Harsh Shandilya 64c486d81c fix(build): upgrade to Kotlin 2.3.10-RC 2026-01-19 22:03:50 +05:30
Harsh Shandilya 6e9b4ccdae Revert "fix(android): make internal build type an alias for release"
This reverts commit 476e25f7d9.
2026-01-19 22:03:50 +05:30
Harsh Shandilya 04ab7d1b60 fix(build): only run Sentry size analysis for production builds 2026-01-19 21:28:18 +05:30
Harsh Shandilya 419e93f940 chore: roll forward secrets 2026-01-18 22:25:42 +05:30
CopilotandGitHub a6862d4e93 Apply tag filtering to hottest posts widget (#1047) 2026-01-18 20:43:11 +05:30
Harsh ShandilyaandGitHub 8f383c895d fix: unlock CI for drafts
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
2026-01-18 19:39:32 +05:30
renovate[bot]andGitHub b4ba657185 chore(deps): update gradle to v9.3.0 2026-01-16 14:37:06 +00:00
renovate[bot]andGitHub 80bdcf0ed9 fix(deps): update agp to v9.1.0-alpha05 2026-01-16 01:47:48 +00:00
renovate[bot]andGitHub da57278fea chore(deps): update plugin com.gradle.develocity to v4.3.1 2026-01-15 23:08:03 +00:00
CopilotGitHubcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
4dd9630e92 Add database backed cache for hottest posts widget (#1041)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-01-15 23:07:06 +00:00
renovate[bot]andGitHub d4118eaa67 fix(deps): update sentry to v8.30.0 2026-01-15 14:31:58 +00:00
renovate[bot]andGitHub ed97836be6 fix(deps): update androidx 2026-01-14 20:59:47 +00:00
renovate[bot]andGitHub e0ae2392f3 fix(deps): update aboutlibraries to v14.0.0-b01 2026-01-13 21:03:16 +00:00
renovate[bot]andGitHub fd041b4344 fix(deps): update androidx 2026-01-13 04:01:56 +00:00
Harsh Shandilya f16452c360 fix(common): improve rendering of lists in comments 2026-01-13 09:30:43 +05:30
Harsh Shandilya 1294d6fc3c fix(android): make baseline profile generation a manual knob 2026-01-13 08:22:26 +05:30
Harsh Shandilya 2fe7004b8f fix(ci): set Gradle property for CI emulators 2026-01-13 07:27:03 +05:30
Harsh Shandilya f7c8f0f979 fix: use Java 25 in CI 2026-01-13 06:57:17 +05:30
Harsh Shandilya 991e7ecac4 fix: accept all Android licenses in CI 2026-01-13 06:56:36 +05:30
Harsh Shandilya c6d1a79747 refactor: cleanup all DAGP reported violations 2026-01-13 06:26:55 +05:30
Harsh Shandilya 51eb6be86c fix(android): regenerate baseline profiles 2026-01-13 06:26:55 +05:30
Harsh Shandilya 7ee0071da6 feat: restore baseline profile generation job 2026-01-13 06:26:55 +05:30
Harsh Shandilya b29785a5fc fix(android): add tests for backstack and fix deeplink navigation 2026-01-12 21:29:41 +05:30
Harsh Shandilya a44e74788d fix: differentiate between prod and debug builds for deeplinks 2026-01-12 21:29:02 +05:30
Harsh Shandilya f1b8f38aca feat: convert post links to deeplinks in comments 2026-01-12 21:29:02 +05:30
Harsh Shandilya 661033c287 fix: capture IAE from GlanceAppWidgetManager.setWidgetPreviews
Fixes COMPOSE-LOBSTERS-9T
2026-01-10 23:16:23 +05:30
renovate[bot]andGitHub 23e24c465b fix(deps): update metro to v0.9.4 2026-01-10 04:37:22 +00:00
Harsh Shandilya 2817c41790 chore: refresh feature graphics 2026-01-10 00:18:00 +05:30
GitHub Actions ee268e35fa feat(release): start next development iteration 2026-01-09 14:08:57 +00:00
GitHub Actions 18235ed55d feat(release): bump version 2026-01-09 14:01:46 +00:00
renovate[bot]andGitHub cfb4e34db0 chore(deps): update plugin poko to v0.21.1 2026-01-09 01:46:40 +00:00
renovate[bot]andGitHub 35316450cf fix(deps): update agp to v9.1.0-alpha04 2026-01-08 19:05:00 +00:00
Harsh Shandilya f4ae71b3c8 fix: add missing SingleIn annotations everywhere 2026-01-07 22:20:00 +05:30
Harsh Shandilya f94c466003 fix(common): ensure DataStore instance is a singleton
Fixes COMPOSE-LOBSTERS-9R
2026-01-07 22:11:50 +05:30
Harsh Shandilya 1c69a4bcbe fix(android): wrap ContentResolver uses in try-catch
Bad network today caused these paths to fail for me

Fixes COMPOSE-LOBSTERS-9Q
2026-01-06 23:41:20 +05:30
renovate[bot]andGitHub 6a5e8daecf fix(deps): update dependency org.junit:junit-bom to v6.0.2 2026-01-06 17:57:44 +00:00
Harsh Shandilya 1ab313ed70 fix(ci): manually upload R8 mappings to Sentry 2026-01-06 23:26:21 +05:30
Harsh Shandilya 9dfffa5219 fixup! fix(android): finally fix Android Auto Backup 2026-01-06 21:54:36 +05:30
Harsh Shandilya e07bd441a8 fix(android): finally fix Android Auto Backup 2026-01-06 21:35:33 +05:30
renovate[bot]andGitHub ff71aeb4ef fix(deps): update agp to v9.1.0-alpha03 2026-01-05 17:53:49 +00:00
Harsh Shandilya f76bb0eb08 fix(maestro): remove failing assertion 2026-01-04 23:16:33 +05:30
Harsh Shandilya 589ea7585e refactor: replace Napier with plain android.util.Log 2026-01-04 22:49:34 +05:30
Harsh Shandilya cb6e24296d fix(ci): update base sha calculation logic to use commit object 2026-01-04 20:51:23 +05:30
Harsh Shandilya 287eb04913 fix(android): make samplePosts helper generic 2026-01-04 19:39:25 +05:30
Harsh Shandilya ccafc96c78 fix(android): tweak widget layout again 2026-01-04 15:24:49 +05:30
Harsh Shandilya 02ef739624 chore: update TEST_POST 2026-01-04 15:24:49 +05:30
Harsh Shandilya 37d364b5fa fixup! fix(build): source VCS info from environment instead 2026-01-04 13:22:22 +05:30
Harsh Shandilya dd8b62f7b8 fixup! fix(build): source VCS info from environment instead 2026-01-04 13:16:43 +05:30
Harsh Shandilya db0412c308 fixup! fix(build): source VCS info from environment instead 2026-01-04 13:10:59 +05:30
Harsh Shandilya 23b3afd0a9 fix(build): source VCS info from environment instead 2026-01-04 12:47:30 +05:30
Harsh Shandilya 33dc57a928 chore: update changelog 2026-01-04 12:47:30 +05:30
Harsh Shandilya 1540208966 fix(android): integrate tag filtering 2026-01-04 12:47:30 +05:30
Harsh Shandilya b29065e27c fix(android): redo tag filtering UI 2026-01-04 12:47:29 +05:30
Harsh Shandilya 14f5528157 chore: remove now-useless JvmSuppressWildcards 2026-01-04 11:59:55 +05:30
Harsh Shandilya 50720a06e4 fix(build): set VCS Info manually for all releases 2026-01-03 20:45:55 +05:30
Harsh Shandilya 9213edd132 fix(build): enable Sentry size analysis 2026-01-03 20:24:36 +05:30
Harsh Shandilya 4c0089676a fix(android): use actionStartActivity everywhere in widgets
Fixes COMPOSE-LOBSTERS-9P
2026-01-03 20:18:49 +05:30
Harsh Shandilya 71e54aac31 feat(android): add a 'See more posts' button to widgets 2026-01-03 03:07:32 +05:30
Harsh Shandilya e648bde12a refactor(android): expand deep linking destinations 2026-01-03 03:07:32 +05:30
Harsh Shandilya 27e670a1f1 fix(sentry): fully skip plugin application when disabled 2026-01-02 21:11:21 +05:30
Harsh Shandilya 4df64bb946 fix(ci): run builds on macOS 26 2026-01-02 21:11:21 +05:30
Harsh Shandilya 7efb206c68 fix(ci): add a release build job to CI
I've broken R8 minification enough times for this to not be checked
2026-01-02 21:11:21 +05:30
Harsh Shandilya 625064f1fa fix(android): update missing classes ProGuard configuration 2026-01-02 20:32:11 +05:30
renovate[bot]andGitHub f16b9a1b54 fix(deps): update metro to v0.9.3 2026-01-02 09:48:54 +00:00
Harsh Shandilya 4137132b40 fix(android): add static widget previews 2026-01-02 13:01:06 +05:30
Harsh Shandilya 8c7aa23532 feat(android): add a widget for hottest posts 2026-01-02 03:13:01 +05:30
Harsh Shandilya 2b7c3e790a fix(android): correct icon tint for comments button in widget 2026-01-02 01:52:33 +05:30
Harsh Shandilya b7721602ff fix(widget): correct check for comments action 2026-01-02 01:50:45 +05:30
Harsh Shandilya 15a04a7684 fix(android): set package for OpenCommentsCallback 2026-01-02 01:50:05 +05:30
Harsh Shandilya 3ffe7ca7a8 chore: silence false-positive lint warnings in version catalog 2026-01-01 21:18:45 +05:30
Harsh Shandilya 561dcb2bb9 fix: remove use of compose-bom
https://jakewharton.com/defuse-the-compose-bom/ enumerates the various reasons why it's not super useful anymore
2026-01-01 21:16:48 +05:30
renovate[bot]andGitHub 1e3ca5b822 fix(deps): update dependency org.jsoup:jsoup to v1.22.1 2026-01-01 04:35:02 +00:00
Harsh Shandilya 216a6eff3a fix(android): WidgetReceiver -> SavedPostsWidgetReceiver 2026-01-01 02:54:20 +05:30
Harsh Shandilya 3390031120 refactor(glance): clean up and consolidate 2026-01-01 02:51:52 +05:30
Harsh Shandilya f079421a00 fix: make widget icon slightly bigger 2026-01-01 02:36:40 +05:30
Harsh Shandilya aa8756065c chore: update changelog 2026-01-01 02:35:00 +05:30
Harsh Shandilya 6004d31af4 fix(build): cleanup all Lint violations 2026-01-01 02:26:11 +05:30
Harsh Shandilya f3a5956216 chore: refresh Lint baselines 2026-01-01 02:21:45 +05:30
Harsh Shandilya 9d38a02819 fix(android): resolve compile error from Glance preview 2026-01-01 02:12:21 +05:30
Harsh Shandilya 7f608fd536 fix(android): prevent extraneous paging loads
Without this the LazyPagingItems.get call would trigger a ton of extra
paging loads forcing 6-7 pages to get loaded when only 2 were needed.
2026-01-01 02:04:54 +05:30
Harsh Shandilya b242333e8a fix(android): make initial post loads lazy 2025-12-31 15:51:45 +05:30
Harsh Shandilya 663271d980 feat(android): rework widget UI and add previews to IDE and widget picker 2025-12-31 15:14:25 +05:30
Harsh Shandilya 67dd99ae42 fix(core): allow injecting dispatchers in more places 2025-12-31 11:44:08 +05:30
renovate[bot]andGitHub cd0d24ecc1 chore(deps): update dependency com.facebook:ktfmt to v0.61 2025-12-30 21:56:08 +00:00
Harsh Shandilya 6b96282a70 fix: update longstanding dumbass string 2025-12-30 23:49:36 +05:30
Harsh Shandilya 8f6f5c658b fix(android): use navigation helper consistently 2025-12-30 23:40:03 +05:30
Harsh Shandilya dbe2868584 fix: cut out some more padding from widget 2025-12-30 21:58:31 +05:30
Harsh Shandilya ab872b6eaa fix: properly use deep links for widget -> comments navigation 2025-12-30 21:30:48 +05:30
Harsh Shandilya c37621ed9a fix(android): make SavedPostUpdaterWorker periodic again 2025-12-30 21:14:35 +05:30
Harsh Shandilya 16f887792d fix(android): redo widget UI a tiny bit 2025-12-30 21:14:34 +05:30
Harsh Shandilya 7c39744ddd fixup! chore: update changelog 2025-12-30 18:15:27 +05:30
Harsh Shandilya b47be69808 chore: update changelog 2025-12-30 17:47:20 +05:30
Harsh Shandilya 7f0acae2da fix: handle process death for navigation state
Fixes COMPOSE-LOBSTERS-7J
2025-12-30 17:43:44 +05:30
Harsh Shandilya b3373473a7 fix: use actionRunCallback to launch browser from widget
Fixes COMPOSE-LOBSTERS-8M
2025-12-30 14:12:53 +05:30
renovate[bot]andGitHub 7c7c217f8b fix(deps): update agp to v9.1.0-alpha02 2025-12-29 22:29:33 +00:00
renovate[bot]andGitHub 252e5bc037 fix(deps): update aboutlibraries to v14.0.0-a02 2025-12-29 18:37:31 +00:00
renovate[bot]andGitHub 12c2708b50 fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v6.0.0-rc.1 2025-12-29 12:55:22 +00:00
Harsh Shandilya 5be3dee120 chore: increase Maestro test timeouts 2025-12-29 15:35:11 +05:30
Harsh Shandilya 2110c24f0c chore: reformat run-maestro-e2e-tests 2025-12-29 15:35:11 +05:30
Harsh Shandilya ecd8248c8a fix: cleanup all Maestro tests to speed up execution 2025-12-29 15:35:11 +05:30
Harsh Shandilya e6eeaafda1 fix: make feature graphic Maestro flow more reliable 2025-12-29 15:35:11 +05:30
Harsh Shandilya c9e5b1a714 chore: disable screenshot tests 2025-12-29 02:23:38 +05:30
Harsh Shandilya 14f09f9009 fix: bump aboutlibraries 2025-12-29 00:21:25 +05:30
Harsh Shandilya a17b27d078 refactor: migrate to AGP 9
Currently blocked on https://github.com/mikepenz/AboutLibraries/pull/1283 being released
2025-12-29 00:21:25 +05:30
Harsh Shandilya 3d978884ea fix: remove ClawBackStack#shouldStack flag
This behavior is desirable on all form factors IMO
2025-12-28 19:53:42 +05:30
Harsh Shandilya 271afb980f fix: wire up navigation to system backpress handler 2025-12-28 19:52:28 +05:30
Harsh Shandilya 8d384f1f2d fix(common): make user profile scrollable 2025-12-28 18:36:24 +05:30
Harsh Shandilya e4fb99ade8 fix: ensure detail views don't stack for tablets 2025-12-28 18:35:52 +05:30
Harsh Shandilya 1af36bea91 fix(common): add a test tag for the comments header 2025-12-28 18:28:45 +05:30
Harsh Shandilya 6580afd90e chore: update comment in InternalDatabaseApi 2025-12-28 16:56:25 +05:30
Harsh Shandilya 476e25f7d9 fix(android): make internal build type an alias for release 2025-12-28 16:53:51 +05:30
Harsh Shandilya 0f4850eed3 chore: dynamically retrieve device size for montage 2025-12-28 16:45:46 +05:30
Harsh Shandilya a66903e4cf chore: refresh feature graphics 2025-12-28 16:34:01 +05:30
Harsh Shandilya e4309a6741 chore: make refresh-feature-graphic also optimize PNGs 2025-12-28 16:30:30 +05:30
Harsh ShandilyaandGitHub 87b010dea6 fix: update kotlinx.serialization
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
2025-12-28 15:57:56 +05:30
Harsh Shandilya 3b98ee3ee0 fix: also add badge for zero comments 2025-12-28 11:55:24 +05:30
Harsh Shandilya da0e5d1d97 fix(common): tweak comment count badge design
The current version's text is too small
2025-12-28 02:10:32 +05:30
Harsh Shandilya b0a469036f fix: hoist saved state out of SaveButton 2025-12-28 02:10:32 +05:30
Harsh Shandilya b4d2dbb8dd fixup! fix: make save button's tap target larger 2025-12-27 18:56:49 +05:30
Harsh Shandilya a8b94079ca fix(common): add back comment count badge 2025-12-27 18:54:23 +05:30
Harsh Shandilya a72b7ce9d1 fix: make save button's tap target larger 2025-12-27 18:50:02 +05:30
Harsh Shandilya d1a3c098a7 fix(common): add missing OkHttp BOM 2025-12-27 10:58:09 +05:30
Harsh Shandilya 40e867585d fix: upgrade to Sentry Gradle Plugin 6.0.0-beta.3
Includes fixes for early manifest parsing and AGP 9.x
2025-12-27 10:20:49 +05:30
Harsh Shandilya 65ec8e1956 fix: remove outdated CC suppression for Spotless 2025-12-27 10:10:50 +05:30
Harsh Shandilya fe7fb7197a chore: run shfmt on scripts 2025-12-27 09:55:11 +05:30
Harsh Shandilya 5540d924c5 chore: merge run-maestro-flow script into refresh-feature-graphic
That is the only place that used it so it doesn't need to be standalone
2025-12-27 09:55:11 +05:30
Harsh Shandilya 6d0953742b feat(maestro): add an end-to-end test suite 2025-12-27 09:55:11 +05:30
Harsh Shandilya a6c47d0dee fix: add missing negation in SentryConfigurationPlugin 2025-12-27 09:43:15 +05:30
Harsh Shandilya 0394d91069 chore: refresh feature graphic and Fastlane screenshots 2025-12-26 22:47:39 +05:30
GitHub Actions 82273c3e42 feat(release): start next development iteration 2025-12-26 16:51:31 +00:00
GitHub Actions 766decbe55 feat(release): bump version 2025-12-26 16:41:48 +00:00
Harsh Shandilya 4a3bca55c4 chore: update changelog and reduce character count for Google Play 2025-12-26 22:07:47 +05:30
Harsh Shandilya 4a7921d187 fix: remove comment count from list items
This currently causes a crash during configuration change, will be restored once I can get it to not crash

Fixes COMPOSE-LOBSTERS-8Z
2025-12-26 22:02:18 +05:30
Harsh Shandilya 7f5dbf79e3 fix: instrument SQLDelight with Sentry spans 2025-12-26 21:40:50 +05:30
Harsh Shandilya a725db0483 fix: exclude non-lobsters domains from Sentry instrumentation
Fixes COMPOSE-LOBSTERS-7M
2025-12-26 21:23:11 +05:30
Harsh Shandilya 9c05e9b2e2 fix: key posts with index as well
Fixes COMPOSE-LOBSTERS-4N
2025-12-26 20:53:37 +05:30
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2fee56d566 fix(deps): update dependency androidx.sqlite:sqlite to v2.6.2 (#1008)
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
|
[androidx.sqlite:sqlite](https://developer.android.com/jetpack/androidx/releases/sqlite#2.6.2)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `2.5.2` -> `2.6.2` |
![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.sqlite:sqlite/2.6.2?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.sqlite:sqlite/2.5.2/2.6.2?slim=true)
|

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi41OS4wIiwidXBkYXRlZEluVmVyIjoiNDIuNTkuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-26 05:40:48 +00:00
Harsh Shandilya 6f29a35af2 chore: sort all dependency declarations 2025-12-26 09:27:57 +05:30
Harsh Shandilya bc029bc60e chore: apply all DAGP suggestions 2025-12-26 09:27:57 +05:30
Harsh Shandilya f09f89dc6c fix(common): remove useless Context-aware ViewModels 2025-12-26 09:27:57 +05:30
Harsh ShandilyaandGitHub de1d21ec4f chore: update changelog
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
2025-12-25 22:19:54 +05:30
Harsh Shandilya 39c12f2173 refactor: push down more data operations to SQL 2025-12-25 19:47:47 +05:30
Harsh Shandilya 3ab2eb8b66 refactor: rework saved post updater
- Add a delayed random stagger between post updates
- Take posts from the last 30 days instead of just 30 posts
2025-12-25 19:27:32 +05:30
Harsh Shandilya 31b54e1443 Revert "refactor(build): automatically generate @AssistedFactory classes"
For some reason this is failing to compile for me from Android Studio, will investigate later.

This reverts commit 44d240c65e.
2025-12-25 19:07:12 +05:30
Harsh Shandilya b48bac29fc fix: cleanup duplicate WorkManager initialization 2025-12-25 19:01:46 +05:30
Harsh Shandilya 44d240c65e refactor(build): automatically generate @AssistedFactory classes 2025-12-25 18:03:57 +05:30
Harsh Shandilya 1d7d166704 refactor(core): simplify OkHttpModule a bit 2025-12-25 17:56:37 +05:30
Harsh ShandilyaandGitHub fc463a3c23 feat(core): add Retry-After header handling for rate limiting (#1006)
Fixes #642
2025-12-25 11:58:59 +00:00
Harsh Shandilya 55b458df49 chore: update changelog 2025-12-25 16:41:23 +05:30
Harsh Shandilya 76ad5f77b6 fix: upgrade to latest Glance 2025-12-25 16:40:07 +05:30
Harsh Shandilya a1af9974be refactor: migrate to Metro for dependency injection
Fixes #897
2025-12-25 16:40:07 +05:30
Harsh Shandilya e3d433d62f refactor(web): delete
Never used this
2025-12-25 16:09:19 +05:30
Harsh Shandilya 7a6878cdd1 fix(core): use longer network timeouts
Fixes #1003
2025-12-25 09:09:52 +05:30
renovate[bot]andGitHub 4d3fe5509f fix(deps): update dependency me.saket.unfurl:unfurl to v2.3.0 2025-12-22 02:17:07 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
be86dbb25b fix(deps): update aboutlibraries to v13.2.1 (#1000)
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| com.mikepenz.aboutlibraries.plugin.android | `13.1.0` -> `13.2.1` |
![age](https://developer.mend.io/api/mc/badges/age/maven/com.mikepenz.aboutlibraries.plugin.android:com.mikepenz.aboutlibraries.plugin.android.gradle.plugin/13.2.1?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.mikepenz.aboutlibraries.plugin.android:com.mikepenz.aboutlibraries.plugin.android.gradle.plugin/13.1.0/13.2.1?slim=true)
|
|
[com.mikepenz:aboutlibraries-compose-m3](https://redirect.github.com/mikepenz/AboutLibraries)
| `13.1.0` -> `13.2.1` |
![age](https://developer.mend.io/api/mc/badges/age/maven/com.mikepenz:aboutlibraries-compose-m3/13.2.1?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.mikepenz:aboutlibraries-compose-m3/13.1.0/13.2.1?slim=true)
|

---

### Release Notes

<details>
<summary>mikepenz/AboutLibraries
(com.mikepenz:aboutlibraries-compose-m3)</summary>

###
[`v13.2.1`](https://redirect.github.com/mikepenz/AboutLibraries/compare/13.2.0...13.2.1)

[Compare
Source](https://redirect.github.com/mikepenz/AboutLibraries/compare/13.1.0...13.2.1)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these
updates again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi41OS4wIiwidXBkYXRlZEluVmVyIjoiNDIuNTkuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-19 19:04:41 +00:00
Harsh Shandilya 8adbd66e9d chore: remove obsolete Gradle properties 2025-12-20 00:24:32 +05:30
Harsh Shandilya 3ddfd77771 fix: upgrade kotlin-metadata-jvm for Dagger 2025-12-20 00:23:55 +05:30
renovate[bot]andGitHub fd910f40fa fix(deps): update kotlin and compose 2025-12-18 05:43:04 +00:00
renovate[bot]andGitHub ca8a16219d chore(deps): update dependency com.facebook:ktfmt to v0.60 2025-12-18 01:11:37 +00:00
renovate[bot]andGitHub 89fe85b229 fix(deps): update androidx 2025-12-17 19:55:01 +00:00
renovate[bot]andGitHub 7cdff634b7 fix(deps): update dependency io.sentry:sentry-bom to v8.29.0 2025-12-16 08:45:56 +00:00
renovate[bot]andGitHub 0c0fefec9b chore(deps): update actions/upload-artifact action to v6 2025-12-12 23:08:45 +00:00
renovate[bot]andGitHub 28660b8940 fix(deps): update dependency com.android.security.lint:lint to v1.0.4 2025-12-12 15:26:26 +00:00
renovate[bot]andGitHub 5c311097cd fix(deps): update agp to v8.13.2 2025-12-11 06:35:47 +00:00
renovate[bot]andGitHub 4cbeffadf5 chore(deps): update plugin com.gradle.develocity to v4.3 2025-12-10 20:24:48 +00:00
Harsh Shandilya dbddbfc5bc fix(android): make post import fallible and catch errors
Fixes COMPOSE-LOBSTERS-9D
2025-12-09 23:12:23 +05:30
Harsh Shandilya 8d603d6fbd refactor(core): set annotation targets 2025-12-09 23:12:04 +05:30
Harsh Shandilya 38770b6e86 fix: ensure HTML export writer is closed 2025-12-09 23:00:27 +05:30
GitHub Actions 43e3eaf6e3 feat(release): start next development iteration 2025-12-07 16:58:53 +00:00
GitHub Actions 7284d3b856 feat(release): bump version 2025-12-07 16:58:05 +00:00
CopilotGitHubcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
a72077cc13 Reduce changelog character count to meet Google Play limit (#991)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2025-12-07 22:26:40 +05:30
Harsh ShandilyaandGitHub 53eebc9ca0 fix: increment minor version
I've gone so long without releases that the nightlies have overflowed into the next minor version and no longer allows publishing.

Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
2025-12-07 22:11:53 +05:30
Harsh ShandilyaandGitHub 7e3c9d3e79 chore: refresh changelog
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
2025-12-07 21:58:09 +05:30
renovate[bot]andGitHub 43099cab30 fix(deps): update dependency io.sentry:sentry-bom to v8.28.0 2025-12-04 17:09:16 +00:00
renovate[bot]andGitHub 9df32ece07 chore(deps): update actions/setup-java action to v5.1.0 2025-12-04 06:03:59 +00:00
renovate[bot]andGitHub 8397ef2427 fix(deps): update androidx 2025-12-03 19:11:12 +00:00
renovate[bot]andGitHub 2c3c2c5acc chore(deps): update actions/checkout action to v6.0.1 2025-12-02 18:04:51 +00:00
renovate[bot]andGitHub f7cf57519c chore(deps): update plugin dependencyanalysis to v3.5.1 2025-11-26 22:27:04 +00:00
renovate[bot]andGitHub 6e7eed7513 fix(deps): update dependency io.sentry:sentry-bom to v8.27.1 2025-11-24 13:42:49 +00:00
renovate[bot]andGitHub 29e391d0ec fix(deps): update dependency dev.chrisbanes.haze:haze to v1.7.1 2025-11-24 10:53:35 +00:00
renovate[bot]andGitHub 93ad4db20e fix(deps): update dependency io.sentry:sentry-bom to v8.27.0 2025-11-21 12:55:16 +00:00
renovate[bot]andGitHub ed0143cb7f chore(deps): update plugin ksp to v2.3.3 2025-11-21 01:32:43 +00:00
renovate[bot]andGitHub 2594066c7c chore(deps): update actions/checkout action to v6 2025-11-20 16:41:04 +00:00
renovate[bot]andGitHub 99870d2386 chore(deps): update plugin poko to v0.20.2 2025-11-20 04:44:52 +00:00
renovate[bot]andGitHub f8c46d1ab5 chore(deps): update plugin dependencyanalysis to v3.5.0 2025-11-20 01:55:39 +00:00
renovate[bot]andGitHub 755cbddb77 fix(deps): update androidx 2025-11-19 21:55:30 +00:00
renovate[bot]andGitHub 5a448c663e fix(deps): update dependency com.diffplug.spotless:spotless-plugin-gradle to v8.1.0 2025-11-19 06:10:01 +00:00
renovate[bot]andGitHub b65313cc81 fix(deps): update dependency com.squareup.okhttp3:okhttp-bom to v5.3.2 2025-11-19 01:08:29 +00:00
renovate[bot]andGitHub eabf971d26 chore(deps): update actions/checkout action to v5.0.1 2025-11-18 02:59:23 +00:00
renovate[bot]andGitHub b49533e44e chore(deps): update gradle to v9.2.1 2025-11-17 14:35:41 +00:00
renovate[bot]andGitHub ad4384cf4f fix(deps): update dependency com.squareup.okhttp3:okhttp-bom to v5.3.1 2025-11-16 17:47:01 +00:00
renovate[bot]andGitHub 4b12670416 fix(deps): update sqldelight to v2.2.1 2025-11-14 04:42:18 +00:00
renovate[bot]andGitHub 1e89a26c7e fix(deps): update dependency io.sentry:sentry-bom to v8.26.0 2025-11-12 06:44:38 +00:00
renovate[bot]andGitHub 3189f84458 fix(deps): update dependency dev.chrisbanes.haze:haze to v1.7.0 2025-11-11 10:09:47 +00:00
renovate[bot]andGitHub 25743a633b fix(deps): update dependency org.gradle.android.cache-fix:org.gradle.android.cache-fix.gradle.plugin to v3.0.3 2025-11-11 06:35:48 +00:00
renovate[bot]andGitHub 8344c4b6b0 chore(deps): update actions/dependency-review-action digest to 3c4e3dc 2025-11-11 04:00:42 +00:00
renovate[bot]andGitHub bc15846b81 fix(deps): update agp to v8.13.1 2025-11-10 17:48:58 +00:00
renovate[bot]andGitHub e1e1ac0fc9 chore(deps): update plugin ksp to v2.3.2 2025-11-06 20:09:01 +00:00
renovate[bot]andGitHub 0af30bf41b fix(deps): update androidx 2025-11-05 18:52:05 +00:00
renovate[bot]andGitHub 9fec53b517 chore(deps): update plugin ksp to v2.3.1 2025-11-04 06:44:14 +00:00
renovate[bot]andGitHub b3be3390e4 fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v5.12.2 2025-11-03 19:42:05 +00:00
renovate[bot]andGitHub 9318bee0e2 fix(deps): update dependency org.junit:junit-bom to v6.0.1 2025-10-31 19:00:38 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
1de4f02ba2 fix(deps): update konvert to v4.4.0 (#981)
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
| [io.mcarle:konvert](https://redirect.github.com/mcarleio/konvert) |
`4.3.2` -> `4.4.0` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/io.mcarle:konvert/4.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/io.mcarle:konvert/4.3.2/4.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
|
[io.mcarle:konvert-annotations](https://redirect.github.com/mcarleio/konvert)
| `4.3.2` -> `4.4.0` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/io.mcarle:konvert-annotations/4.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/io.mcarle:konvert-annotations/4.3.2/4.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>mcarleio/konvert (io.mcarle:konvert)</summary>

###
[`v4.4.0`](https://redirect.github.com/mcarleio/konvert/releases/tag/v4.4.0)

##### What's Changed

🎉 This release officially supports KSP2 in Gradle and Maven
projects

**Full Changelog**:
<https://github.com/mcarleio/konvert/compare/v4.3.2...v4.4.0>

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these
updates again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNTkuNCIsInVwZGF0ZWRJblZlciI6IjQxLjE1OS40IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-31 15:39:52 +00:00
renovate[bot]andGitHub 3d2026061b fix(deps): update dependency com.squareup.okhttp3:okhttp-bom to v5.3.0 2025-10-31 07:48:43 +00:00
renovate[bot]andGitHub 099076edc2 chore(deps): update plugin dependencyanalysis to v3.4.1 2025-10-31 04:13:16 +00:00
Harsh Shandilya ed1ab71f44 chore: fix unnecessary optional param 2025-10-30 16:41:30 +05:30
renovate[bot]andGitHub 56aa0acabc fix(deps): update dependency io.sentry:sentry-bom to v8.25.0 2025-10-30 03:52:43 +00:00
renovate[bot]andGitHub d4e8ce3375 chore(deps): update gradle to v9.2.0 2025-10-29 19:48:54 +00:00
renovate[bot]andGitHub 3f81320e92 chore(deps): update plugin dependencyanalysis to v3.4.0 2025-10-29 00:45:40 +00:00
Harsh Shandilya a84e1db833 fix: jazz up the settings UI 2025-10-28 02:26:32 +05:30
Harsh Shandilya 746d86c324 fix: add a source code link to settings 2025-10-28 01:56:53 +05:30
Harsh Shandilya e4ffbe6795 fix(scripts): use full path to sdkmanager 2025-10-28 00:22:19 +05:30
renovate[bot]andGitHub e7f51aca1c chore(deps): update actions/upload-artifact action to v5 2025-10-24 22:12:49 +00:00
renovate[bot]andGitHub b47497d9bc chore(deps): update plugin dependencyanalysis to v3.3.0 2025-10-23 23:12:05 +00:00
renovate[bot]andGitHub d55d83a859 fix(deps): update kotlin and compose to v2.2.21 2025-10-23 10:35:45 +00:00
renovate[bot]andGitHub ea29743793 fix(deps): update dependency io.sentry:sentry-bom to v8.24.0 2025-10-23 06:24:59 +00:00
renovate[bot]andGitHub cedc7ead7f chore(deps): update plugin ksp to v2.3.0 2025-10-23 04:11:15 +00:00
renovate[bot]andGitHub 0553614497 fix(deps): update androidx 2025-10-22 20:49:54 +00:00
renovate[bot]andGitHub 7249763362 chore(deps): update plugin dependencyanalysis to v3.2.0 2025-10-22 12:48:37 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
ffca1a2759 chore(deps): update dependency com.facebook:ktfmt to v0.59 (#967)
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
| [com.facebook:ktfmt](https://redirect.github.com/facebook/ktfmt) |
`0.58` -> `0.59` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/com.facebook:ktfmt/0.59?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.facebook:ktfmt/0.58/0.59?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>facebook/ktfmt (com.facebook:ktfmt)</summary>

###
[`v0.59`](https://redirect.github.com/facebook/ktfmt/blob/HEAD/CHANGELOG.md#059)

[Compare
Source](https://redirect.github.com/facebook/ktfmt/compare/v0.58...v0.59)

##### Fixed

- Special format handling of multiline strings handling of first line
and do not format string template expressions
- Do not remove semicolon after an unnamed empty companion object, if it
isn't the last element
([#&#8203;557](https://redirect.github.com/facebook/ktfmt/issues/557))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNDMuMSIsInVwZGF0ZWRJblZlciI6IjQxLjE0My4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-20 16:01:16 +00:00
Harsh Shandilya a2e5cf3eec fix: remember nav backstack and comments page state
Fixes #845
2025-10-20 00:12:58 +05:30
CopilotandGitHub e98aea68b8 fix: update release workflow to write changelog to fastlane metadata (#969) 2025-10-19 20:36:11 +05:30
renovate[bot]andGitHub ef5e6d5d8e chore(deps): update plugin poko to v0.20.1 2025-10-16 21:15:29 +00:00
renovate[bot]andGitHub d31f60882b fix(deps): update aboutlibraries to v13.1.0 2025-10-13 20:51:19 +00:00
renovate[bot]andGitHub 479cafcceb chore(deps): update plugin com.gradle.develocity to v4.2.2 2025-10-13 14:44:51 +00:00
renovate[bot]andHarsh Shandilya b47a449bdc fix(deps): update dependency com.mikepenz:aboutlibraries-compose-m3 to v13 2025-10-11 15:25:16 +05:30
renovate[bot]andGitHub a7cdb9831e chore(deps): update actions/dependency-review-action digest to 4552948 2025-10-10 21:50:12 +00:00
renovate[bot]andGitHub 7961bcf53b fix(deps): update dependency com.slack.lint:slack-lint-checks to v0.11.1 2025-10-10 18:13:05 +00:00
renovate[bot]andGitHub 0bdad20c95 fix(deps): update dependency com.squareup.okhttp3:okhttp-bom to v5.2.1 2025-10-10 02:13:22 +00:00
Harsh Shandilya e7a234b171 chore: tweak changelog entries 2025-10-09 18:38:45 +05:30
Harsh Shandilya 71e7404576 chore: remove unused repository declarations 2025-10-09 18:03:47 +05:30
Harsh Shandilya ec5fd18ff9 fix(deps): update androidx 2025-10-09 18:03:25 +05:30
renovate[bot]andGitHub e261f2103f chore(deps): update plugin licensee to v1.14.1 2025-10-09 04:32:45 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
0d9a55c756 chore(deps): update plugin dependencyanalysis to v3.1.0 (#961)
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
|
[com.autonomousapps.dependency-analysis](https://redirect.github.com/autonomousapps/dependency-analysis-android-gradle-plugin)
| `3.0.4` -> `3.1.0` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/com.autonomousapps.dependency-analysis:com.autonomousapps.dependency-analysis.gradle.plugin/3.1.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.autonomousapps.dependency-analysis:com.autonomousapps.dependency-analysis.gradle.plugin/3.0.4/3.1.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>autonomousapps/dependency-analysis-android-gradle-plugin
(com.autonomousapps.dependency-analysis)</summary>

###
[`v3.1.0`](https://redirect.github.com/autonomousapps/dependency-analysis-android-gradle-plugin/blob/HEAD/CHANGELOG.md#Version-310)

- \[Fix] Updated asm to 9.9 (and use latest asm-relocated) for Java 25
support.
- \[Fix]: Test fixtures in a Spring Boot project are allowed to have
testFixturesApi dependencies.
- \[Test]: run tests against Gradle 9.1.0.

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS4xMzEuOSIsInVwZGF0ZWRJblZlciI6IjQxLjEzMS45IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-09 03:14:56 +00:00
renovate[bot]andGitHub 032af3bab4 fix(deps): update dependency com.squareup.okhttp3:okhttp-bom to v5.2.0 2025-10-08 06:15:23 +00:00
renovate[bot]andGitHub 60b044dc59 chore(deps): update plugin ksp to v2.2.20-2.0.4 2025-10-08 00:00:13 +00:00
renovate[bot]andGitHub 47bb260047 chore(deps): update plugin licensee to v1.14.0 2025-10-07 18:11:00 +00:00
renovate[bot]andGitHub 2f0804e846 chore(deps): update plugin anvil to v2.7.0 2025-10-05 12:33:57 +00:00
renovate[bot]andGitHub b3d30f5e7d fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v5.12.1 2025-10-05 10:40:14 +00:00
renovate[bot]andGitHub 84d65e8fc9 chore(deps): update plugin com.gradle.develocity to v4.2.1 2025-10-02 09:08:02 +00:00
Harsh Shandilya 06c37f00e8 fix(common): update the HTML escape hack 2025-10-02 12:22:57 +05:30
Harsh ShandilyaandGitHub 35ce7bb99f chore: update changelog 2025-10-02 11:29:05 +05:30
renovate[bot]andGitHub 53a780a870 chore(deps): update gradle/actions action to v5 2025-10-02 00:35:26 +00:00
renovate[bot]andGitHub 8573bb6e0f fix(deps): update dependency org.gradle.android.cache-fix:org.gradle.android.cache-fix.gradle.plugin to v3.0.2 2025-10-01 21:39:05 +00:00
renovate[bot]andGitHub b330765dc4 fix(deps): update dependency io.sentry:sentry-bom to v8.23.0 2025-10-01 15:32:40 +00:00
Harsh Shandilya 64d5b757fa refactor(common): go back to the plain HTMLConverter output
Also adds a fix for the weird line break behavior with URLs in lists
2025-10-01 20:50:32 +05:30
renovate[bot]andGitHub 4062df8c30 fix(deps): update konvert to v4.3.2 2025-09-30 18:24:41 +00:00
renovate[bot]andGitHub 26a1360842 fix(deps): update dependency org.junit:junit-bom to v6 2025-09-30 13:42:07 +00:00
renovate[bot]andGitHub 7cd534cf84 fix(deps): update dependency org.junit:junit-bom to v5.14.0 2025-09-30 10:02:58 +00:00
renovate[bot]andGitHub a0994e759f chore(deps): update gradle/actions digest to 748248d 2025-09-29 14:30:48 +00:00
renovate[bot]andGitHub 50531a4b4a fix(deps): update dependency nl.littlerobots.version-catalog-update:nl.littlerobots.version-catalog-update.gradle.plugin to v1.0.1 2025-09-27 13:39:58 +00:00
renovate[bot]andGitHub 2207d11ea8 chore(deps): update actions/dependency-review-action digest to 56339e5 2025-09-26 16:54:29 +00:00
renovate[bot]andGitHub 9a5ebae77e fix(deps): update dagger to v2.57.2 2025-09-26 00:58:20 +00:00
Harsh ShandilyaandGitHub c997b14c10 chore: update changelog
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
2025-09-26 03:01:51 +05:30
renovate[bot]andGitHub b8320bdc11 chore(deps): update dependency macos to v15 2025-09-25 17:01:04 +00:00
Harsh Shandilya b64f9c6de9 fixup! fix(deps): update androidx 2025-09-25 22:29:22 +05:30
renovate[bot]andGitHub 77cee3b65e fix(deps): update androidx 2025-09-24 21:32:14 +00:00
renovate[bot]andGitHub 36b477b0d4 fix(deps): update dependency com.diffplug.spotless:spotless-plugin-gradle to v8 2025-09-24 19:04:49 +00:00
Harsh Shandilya 9226d36071 Revert "fix(renovate): disable updates to Compose BOM"
Let's try this again.

This reverts commit 3b49964bd0.
2025-09-25 00:33:41 +05:30
renovate[bot]andGitHub c4dbbe1575 fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v5.12.0 2025-09-23 10:33:10 +00:00
renovate[bot]andGitHub b6144baaae fix(deps): update dependency io.sentry:sentry-bom to v8.22.0 2025-09-22 14:15:54 +00:00
renovate[bot]andGitHub 7dc51b9a90 chore(deps): update gradle to v9.1.0 2025-09-18 14:34:59 +00:00
renovate[bot]andGitHub 0c5a0251f6 chore(deps): update plugin dependencyanalysis to v3.0.4 2025-09-17 21:44:26 +00:00
renovate[bot]andGitHub f25b0931a0 chore(deps): update plugin com.gradle.develocity to v4.2 2025-09-16 21:39:17 +00:00
renovate[bot]andGitHub 0b8f886307 chore(deps): update plugin dependencyanalysis to v3.0.3 2025-09-16 17:02:23 +00:00
renovate[bot]andGitHub ccd9f16811 chore(deps): update gradle to v9.1.0-rc-4 2025-09-16 10:49:53 +00:00
renovate[bot]andGitHub ad5863c3cc fix(deps): update konvert to v4.3.1 2025-09-15 01:53:32 +00:00
renovate[bot]andGitHub 17e0facbbd chore(deps): update kotlin and compose 2025-09-12 18:55:37 +00:00
renovate[bot]andGitHub 8331abea45 fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v5.11.0 2025-09-11 21:12:28 +00:00
renovate[bot]andGitHub 0d55b2add2 fix(deps): update konvert to v4.3.0 2025-09-11 13:53:32 +00:00
renovate[bot]andGitHub da3e17cda4 fix(deps): update dependency com.google.truth:truth to v1.4.5 2025-09-11 01:49:41 +00:00
renovate[bot]andGitHub fabba76754 chore(deps): update plugin ksp to v2.2.20-2.0.2 2025-09-10 22:34:25 +00:00
renovate[bot]andGitHub 92823a8810 fix(deps): update androidx 2025-09-10 17:28:34 +00:00
renovate[bot]andGitHub 9570ad5cc3 fix(deps): update kotlin and compose to v2.2.20 2025-09-10 07:42:07 +00:00
renovate[bot]andGitHub e6c0b2d0da chore(deps): update plugin dependencyanalysis to v3.0.2 2025-09-10 05:46:14 +00:00
renovate[bot]andGitHub bd03284097 fix(deps): update dependency io.sentry:sentry-bom to v8.21.1 2025-09-09 16:56:45 +00:00
renovate[bot]andGitHub 5b8f5295dd chore(deps): update gradle/actions digest to ed40850 2025-09-09 09:42:33 +00:00
renovate[bot]andGitHub aa3141dc6e fix(deps): update sentry 2025-09-08 16:48:43 +00:00
renovate[bot]andGitHub 26fbec90b6 chore(deps): update dependency gradle to v9.1.0-rc-3 2025-09-08 11:36:52 +00:00
renovate[bot]andGitHub d19f1dda5e fix(deps): update agp to v8.13.0 2025-09-02 18:01:29 +00:00
Harsh Shandilya e1c24783c4 fix(build): switch out of JVM toolchains 2025-09-02 23:30:17 +05:30
Harsh Shandilya 7514df99b0 fix(build): upgrade to Gradle 9.1.0-RC2 2025-09-02 23:30:17 +05:30
Harsh Shandilya 3b49964bd0 fix(renovate): disable updates to Compose BOM 2025-08-30 04:18:43 +05:30
Harsh Shandilya 6c749e172a Revert "fix(deps): update dependency androidx.compose:compose-bom-alpha to v2025.08.01"
This reverts commit 653d9dccbb.
2025-08-30 03:54:36 +05:30
renovate[bot]andGitHub 83088e7bab chore(deps): update dependency com.facebook:ktfmt to v0.58 2025-08-29 02:44:50 +00:00
renovate[bot]andGitHub e3a5798a48 fix(deps): update agp to v8.12.2 2025-08-28 20:39:16 +00:00
renovate[bot]andGitHub 653d9dccbb fix(deps): update dependency androidx.compose:compose-bom-alpha to v2025.08.01 2025-08-28 20:37:17 +00:00
Harsh Shandilya 7707372939 fix(deps): roll back AndroidX bump
Causes major regressions with the comments page not working at all
2025-08-29 02:06:31 +05:30
Harsh Shandilya 338466337b fix(scripts): use a Pixel 4a emulator 2025-08-29 01:09:16 +05:30
Harsh Shandilya bbc55cd506 fix(model): update mappings to fix Konvert warnings 2025-08-29 00:59:13 +05:30
Harsh Shandilya 596e2a6e22 fix(build): lift and use enableSentry in more places 2025-08-29 00:58:42 +05:30
Harsh Shandilya 69d0e39fa6 fix(scripts): set ANDROID_AVD_HOME in boot-emulator.sh 2025-08-29 00:50:58 +05:30
Harsh Shandilya a1a4c393da chore: update changelog 2025-08-29 00:35:17 +05:30
Harsh Shandilya caf879d6c5 fix(deps): update to workmanager 2.11.x alpha 2025-08-29 00:31:12 +05:30
Harsh Shandilya 58f4fcfcbf fix(deps): update lifecycle to 2.10.0-alpha03 2025-08-29 00:29:49 +05:30
Harsh Shandilya b54b5c0fec fix(deps): update activity-compose to 1.12.x alpha 2025-08-29 00:29:02 +05:30
Harsh Shandilya 3c03a52950 fix(deps): update nav3-adaptive to alpha release 2025-08-29 00:24:38 +05:30
renovate[bot]andGitHub 6a830adfb1 fix(deps): update androidx 2025-08-27 21:43:01 +00:00
renovate[bot]andGitHub b2ba869471 chore(deps): update actions/dependency-review-action digest to 595b5ae 2025-08-26 21:45:54 +00:00
renovate[bot]andGitHub a671c66d30 chore(deps): update plugin dependencyanalysis to v3.0.1 2025-08-25 20:11:03 +00:00
Harsh ShandilyaandGitHub 88580cb722 fix(build): upgrade AndroidX snapshots (#920) 2025-08-26 01:40:29 +05:30
renovate[bot]andGitHub 37d8350b79 fix(deps): update dependency io.sentry:sentry-bom to v8.20.0 2025-08-25 16:35:10 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
cecacb0951 fix(deps): update dependency org.jsoup:jsoup to v1.21.2 (#918)
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
| [org.jsoup:jsoup](https://jsoup.org/)
([source](https://redirect.github.com/jhy/jsoup)) | `1.21.1` -> `1.21.2`
|
[![age](https://developer.mend.io/api/mc/badges/age/maven/org.jsoup:jsoup/1.21.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/org.jsoup:jsoup/1.21.1/1.21.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>jhy/jsoup (org.jsoup:jsoup)</summary>

###
[`v1.21.2`](https://redirect.github.com/jhy/jsoup/blob/HEAD/CHANGES.md#1212-2025-Aug-25)

##### Changes

- Deprecated internal (yet visible) methods
`Normalizer#normalize(String, bool)` and
`Attribute#shouldCollapseAttribute(Document.OutputSettings)`. These will
be removed in a future version.
- Deprecated `Connection#sslSocketFactory(SSLSocketFactory)` in favor of
the new `Connection#sslContext(SSLContext)`. Using `sslSocketFactory`
will force the use of the legacy `HttpUrlConnection` implementation,
which does not support HTTP/2.
[#&#8203;2370](https://redirect.github.com/jhy/jsoup/pull/2370)

##### Improvements

- When pretty-printing, if there are consecutive text nodes (via DOM
manipulation), the non-significant whitespace between them will be
collapsed.
[#&#8203;2349](https://redirect.github.com/jhy/jsoup/pull/2349).
- Updated `Connection.Response#statusMessage()` to return a simple
loggable string message (e.g. "OK") when using the `HttpClient`
implementation, which doesn't otherwise return any server-set status
message.
[#&#8203;2356](https://redirect.github.com/jhy/jsoup/issues/2346)
- `Attributes#size()` and `Attributes#isEmpty()` now exclude any
internal attributes (such as user data) from their count. This aligns
with the attributes' serialized output and iterator.
[#&#8203;2369](https://redirect.github.com/jhy/jsoup/pull/2369)
- Added `Connection#sslContext(SSLContext)` to provide a custom SSL
(TLS) context to requests, supporting both the `HttpClient` and the
legacy `HttUrlConnection` implementations.
[#&#8203;2370](https://redirect.github.com/jhy/jsoup/pull/2370)
- Performance optimizations for DOM manipulation methods including when
repeatedly removing an element's first child
(`element.child(0).remove()`, and when using
`Parser#parseBodyFragement()` to parse a large number of direct
children.
[#&#8203;2373](https://redirect.github.com/jhy/jsoup/pull/2373).

##### Bug Fixes

- When parsing from an InputStream and a multibyte character happened to
straddle a buffer boundary, the stream would not be completely read.
[#&#8203;2353](https://redirect.github.com/jhy/jsoup/issues/2353).
- In `NodeTraversor`, if a last child element was removed during the
`head()` call, the parent would be visited twice.
[#&#8203;2355](https://redirect.github.com/jhy/jsoup/issues/2355).
- Cloning an Element that has an Attributes object would add an empty
internal user-data attribute to that clone, which would cause unexpected
results for `Attributes#size()` and `Attributes#isEmpty()`.
[#&#8203;2356](https://redirect.github.com/jhy/jsoup/issues/2356)
- In a multithreaded application where multiple threads are calling
`Element#children()` on the same element concurrently, a race condition
could happen when the method was generating the internal child element
cache (a filtered view of its child nodes). Since concurrent reads of
DOM objects should be threadsafe without external synchronization, this
method has been updated to execute atomically.
[#&#8203;2366](https://redirect.github.com/jhy/jsoup/issues/2366)
- Malformed HTML could throw an IndexOutOfBoundsException during the
adoption agency.
[#&#8203;2377](https://redirect.github.com/jhy/jsoup/pull/2377).

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS44Mi43IiwidXBkYXRlZEluVmVyIjoiNDEuODIuNyIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-25 14:51:47 +00:00
Harsh Shandilya 89aa9b66fc fix(build): remove obsolete version override 2025-08-25 20:17:39 +05:30
Harsh Shandilya cb51a2e98e fix(deps): update to latest* AndroidX snapshot
*Latest working snapshot: https://androiddev.social/@msfjarvis/115088715715678405

Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
2025-08-25 20:02:08 +05:30
renovate[bot]andGitHub 4315c2fef9 chore(deps): update actions/setup-java action to v5 2025-08-21 07:12:46 +00:00
renovate[bot]andGitHub b6cb5f0b67 chore(deps): update plugin dependencyanalysis to v3 2025-08-21 01:12:55 +00:00
renovate[bot]andGitHub dddd35480a fix(deps): update dagger to v2.57.1 2025-08-20 23:54:09 +00:00
Harsh Shandilya 151c77eea7 fix(deps): upgrade to ktfmt 0.57 2025-08-20 23:07:35 +05:30
IzzyandGitHub 066fb52f0b Merge 32b7f1e7e6 into 21ad379aab 2025-08-19 23:46:41 +05:30
Harsh Shandilya 32b7f1e7e6 chore(fastlane): add all past changelogs 2025-08-19 22:27:40 +05:30
Izzy 8d493a52ae initial fastlane structures from IzzyOnDroid 2025-08-19 18:09:18 +02:00
renovate[bot]andGitHub 21ad379aab chore(deps): update plugin com.gradle.develocity to v4.1.1 2025-08-19 09:32:10 +00:00
renovate[bot]andGitHub fdb69f950a fix(deps): update konvert to v4.2.0 2025-08-19 01:39:26 +00:00
renovate[bot]andGitHub f776c80381 fix(deps): update agp to v8.12.1 2025-08-18 21:33:18 +00:00
renovate[bot]andGitHub 01451eee78 chore(deps): update actions/dependency-review-action digest to bc41886 2025-08-18 19:10:38 +00:00
renovate[bot]andGitHub 19b6b1063a chore(deps): update plugin ksp to v2.2.10-2.0.2 2025-08-15 00:33:20 +00:00
renovate[bot]andGitHub e2bd240750 fix(deps): update kotlin and compose to v2.2.10 2025-08-14 11:42:27 +00:00
renovate[bot]andGitHub ab76c44301 fix(deps): update androidx 2025-08-13 22:14:17 +00:00
renovate[bot]andGitHub e290f00dc8 fix(deps): update dependency dev.chrisbanes.haze:haze to v1.6.10 2025-08-13 19:13:04 +00:00
renovate[bot]andGitHub 9c02762440 fix(deps): update sentry 2025-08-12 14:30:39 +00:00
renovate[bot]andGitHub 1d324d2327 chore(deps): update actions/checkout action to v5 2025-08-11 21:39:32 +00:00
renovate[bot]andGitHub 54bbeb6463 fix(deps): update dependency io.sentry:sentry-bom to v8.19.0 2025-08-11 17:01:57 +00:00
renovate[bot]andGitHub e795a5112b chore(deps): update actions/checkout action to v4.3.0 2025-08-11 12:30:05 +00:00
renovate[bot]andGitHub 3fc7f242f4 fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v5.8.1 2025-08-07 09:30:05 +00:00
Harsh Shandilya 4328ec2e3d fixup! refactor: slim down per-module dependencies 2025-08-06 00:41:18 +05:30
Harsh Shandilya 49797d46f1 fix(build): remove Invert plugin
I don't use this anymore
2025-08-05 21:34:50 +05:30
renovate[bot]andGitHub be263fff5f chore(deps): update gradle/actions digest to 017a9ef 2025-08-05 12:47:11 +00:00
Harsh Shandilya eed64b95df refactor: slim down per-module dependencies
dependency-analysis plugin coming in clutch as usual
2025-08-04 23:03:44 +05:30
renovate[bot]andGitHub 541acf4ef1 fix(deps): update kotlinresult to v2.1.0 2025-08-03 17:26:33 +00:00
renovate[bot]andGitHub fe2e3e49e1 chore(deps): update dependency gradle to v9 2025-08-02 11:34:58 +00:00
renovate[bot]andGitHub 867e360325 fix(deps): update sentry to v8.18.0 2025-08-02 07:43:10 +00:00
renovate[bot]andGitHub cd100c8a1f fix(deps): update richtext to v1.0.0-alpha03 2025-08-02 02:33:41 +00:00
renovate[bot]andGitHub 88447a3745 fix(deps): update androidx 2025-08-01 22:24:33 +00:00
renovate[bot]andGitHub d1609c54fa fix(deps): update agp to v8.12.0 2025-08-01 22:11:30 +00:00
renovate[bot]andGitHub b0b2e8dac6 chore(deps): update plugin poko to v0.19.3 2025-07-25 21:13:25 +00:00
Harsh Shandilya 13477b5c7f fix(build): bump AndroidX Nav3 snapshot 2025-07-22 23:26:31 +05:30
renovate[bot]andGitHub cf84853abb fix(deps): update coil3 to v3.3.0 2025-07-22 09:37:37 +00:00
renovate[bot]andGitHub c554a89f9f fix(deps): update dependency com.diffplug.spotless:spotless-plugin-gradle to v7.2.1 2025-07-22 01:49:57 +00:00
renovate[bot]andGitHub 0dd2c34de2 chore(deps): update plugin poko to v0.19.2 2025-07-21 19:13:25 +00:00
Harsh Shandilya b5dbcf5acc refactor(build): remove default enabled options 2025-07-22 00:41:54 +05:30
Harsh Shandilya 9f7fd29f09 fix: add missing check for userIsAuthor
Fixes #903
2025-07-22 00:39:27 +05:30
Harsh Shandilya 7487664ac2 refactor: rework post update worker logic
Directly enqueue jobs on application launch and space out API requests to avoid rate limits
2025-07-22 00:39:27 +05:30
Harsh Shandilya ef0b06cbd7 refactor: add a migration to introduce SavedPost#userIsAuthor 2025-07-22 00:39:27 +05:30
Harsh Shandilya b0432b848d fix: manually patch HTML entities
Fixes #904
2025-07-22 00:39:27 +05:30
renovate[bot]andGitHub 82b07496e9 fix(deps): update dependency org.junit:junit-bom to v5.13.4 2025-07-21 12:48:51 +00:00
renovate[bot]andGitHub ad86dbe070 fix(deps): update dependency com.diffplug.spotless:spotless-plugin-gradle to v7.2.0 2025-07-20 21:37:56 +00:00
Harsh Shandilya 1668b669d1 Merge remote-tracking branches 'origin/renovate/kotlin-and-compose' and 'origin/renovate/com.squareup.anvil-2.x' into hs/anvil-test
* origin/renovate/kotlin-and-compose:
  fix(deps): update kotlin and compose

* origin/renovate/com.squareup.anvil-2.x:
  chore(deps): update plugin anvil to v2.6.1
2025-07-19 17:20:18 +05:30
renovate[bot]andGitHub eca1924f29 fix(deps): update kotlin and compose 2025-07-19 07:09:52 +00:00
renovate[bot]andGitHub c171922182 chore(deps): update plugin anvil to v2.6.1 2025-07-19 07:09:48 +00:00
Harsh Shandilya 1322be3954 Revert "chore: disable Kotlin updates from Renovate"
This reverts commit 0c1c502186.
2025-07-19 12:38:49 +05:30
renovate[bot]andGitHub 817104f559 fix(deps): update agp to v8.12.0-rc01 2025-07-18 20:23:41 +00:00
renovate[bot]andGitHub 632fc26f4b fix(deps): update haze to v1.6.9 2025-07-18 13:35:46 +00:00
renovate[bot]andGitHub a478d23b1f fix(deps): update dependency com.slack.lint:slack-lint-checks to v0.11.0 2025-07-18 03:53:02 +00:00
renovate[bot]andGitHub 739ed33bd1 fix(deps): update dagger to v2.57 2025-07-17 02:53:28 +00:00
renovate[bot]andGitHub 1fa0565e28 fix(deps): update androidx 2025-07-16 21:04:29 +00:00
renovate[bot]andGitHub 1965f9db8f chore(deps): update plugin com.gradle.develocity to v4.1 2025-07-15 20:38:53 +00:00
Harsh ShandilyaandGitHub 911687b171 chore: document lack of upstream support
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
2025-07-15 23:48:49 +05:30
renovate[bot]andGitHub 342c09ccf0 fix(deps): update haze to v1.6.8 2025-07-15 02:40:08 +00:00
renovate[bot]andGitHub 36c3d2fb4a fix(deps): update agp to v8.12.0-alpha09 2025-07-14 20:29:27 +00:00
renovate[bot]andGitHub 790c792895 chore(deps): update plugin anvil to v2.6.0 2025-07-11 03:00:11 +00:00
renovate[bot]andGitHub f1f7b42f68 fix(deps): update sentry to v8.17.0 2025-07-08 13:38:28 +00:00
renovate[bot]andGitHub 9cf250470f fix(deps): update dependency com.squareup.okhttp3:okhttp-bom to v5.1.0 2025-07-08 02:30:55 +00:00
renovate[bot]andGitHub 35bfdc5646 fix(deps): update dependency com.diffplug.spotless:spotless-plugin-gradle to v7.1.0 2025-07-07 22:56:28 +00:00
Harsh Shandilya a6c1b51971 fix(deps): remove unused version declaration 2025-07-07 12:34:01 +05:30
renovate[bot]andGitHub 5138839325 fix(deps): update haze to v1.6.7 2025-07-06 20:53:59 +00:00
renovate[bot]andGitHub fb534270f7 fix(deps): update konvert to v4.1.2 2025-07-06 14:07:55 +00:00
Harsh Shandilya 42988debc1 fix(deps): remove unused android.test plugin 2025-07-06 02:37:38 +05:30
Harsh Shandilya fff0982de6 fix(deps): bump nav3 snapshot 2025-07-06 02:37:38 +05:30
Harsh Shandilya 049e1426bc chore: fmt 2025-07-06 01:41:20 +05:30
renovate[bot]andGitHub 3ac20e6cbc fix(deps): update konvert to v4.1.1 2025-07-05 02:36:09 +00:00
renovate[bot]andGitHub 5ef22f4edb chore(deps): update dependency gradle to v8.14.3 2025-07-04 20:01:04 +00:00
renovate[bot]andGitHub 519dd79377 fix(deps): update dependency org.junit:junit-bom to v5.13.3 2025-07-04 14:31:31 +00:00
renovate[bot]andGitHub 2bf339fb13 fix(deps): update dependency com.squareup.okhttp3:okhttp-bom to v5 2025-07-03 19:10:36 +00:00
renovate[bot]andGitHub f9ee71fd63 fix(deps): update androidx 2025-07-03 02:06:56 +00:00
renovate[bot]andGitHub 272104a00d fix(deps): update agp to v8.12.0-alpha08 2025-07-02 22:34:20 +00:00
renovate[bot]andGitHub e72e2a97e2 fix(deps): update aboutlibraries to v12.2.4 2025-07-01 22:12:06 +00:00
renovate[bot]andGitHub 4f8fa061fa chore(deps): update dependency de.mannodermaus.android-junit5 to v1.13.1.0 2025-06-29 01:40:31 +00:00
renovate[bot]andGitHub 9b6547e502 fix(deps): update haze to v1.6.6 2025-06-28 13:49:42 +00:00
renovate[bot]andGitHub 2644c4003b fix(deps): update serialization to v1.9.0 2025-06-27 20:25:49 +00:00
renovate[bot]andGitHub 215147b11b fix(deps): update haze to v1.6.5 2025-06-27 14:31:45 +00:00
renovate[bot]andGitHub 213a7836a9 fix(deps): update sentry to v8.16.0 2025-06-27 10:35:33 +00:00
Harsh ShandilyaandGitHub 0c1c502186 chore: disable Kotlin updates from Renovate
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
2025-06-27 10:36:07 +05:30
renovate[bot]andGitHub b1e8f72951 fix(deps): update agp to v8.12.0-alpha07 2025-06-26 20:31:25 +00:00
Harsh Shandilya 23698beb65 fix(build): allow warnings in the build 2025-06-26 20:55:35 +05:30
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
46ce31bcb8 fix(deps): update sentry to v8.15.1 (#876)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
|
[io.sentry:sentry-bom](https://redirect.github.com/getsentry/sentry-java)
| `8.15.0` -> `8.15.1` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/io.sentry:sentry-bom/8.15.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/io.sentry:sentry-bom/8.15.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/io.sentry:sentry-bom/8.15.0/8.15.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/io.sentry:sentry-bom/8.15.0/8.15.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
|
[io.sentry:sentry-android](https://redirect.github.com/getsentry/sentry-java)
| `8.15.0` -> `8.15.1` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/io.sentry:sentry-android/8.15.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/io.sentry:sentry-android/8.15.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/io.sentry:sentry-android/8.15.0/8.15.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/io.sentry:sentry-android/8.15.0/8.15.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>getsentry/sentry-java (io.sentry:sentry-bom)</summary>

###
[`v8.15.1`](https://redirect.github.com/getsentry/sentry-java/blob/HEAD/CHANGELOG.md#8151)

[Compare
Source](https://redirect.github.com/getsentry/sentry-java/compare/8.15.0...8.15.1)

##### Fixes

- Enabling Sentry Logs through Logback in Spring Boot config did not
work in 3.15.0
([#&#8203;4523](https://redirect.github.com/getsentry/sentry-java/pull/4523))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these
updates again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-26 15:25:03 +00:00
Harsh Shandilya a2c447d8f8 chore: put on the clown nose 2025-06-26 20:41:43 +05:30
Harsh Shandilya 73ff9e6711 Revert "fix: don't set Kotlin language version"
This reverts commit 75910a5c41.
2025-06-26 20:39:52 +05:30
Harsh Shandilya b9bef05475 Revert "fix: restore Kotlin language version to 2.1"
Anvil doesn't work unless this is pinned to Kotlin 1.9

This reverts commit 455b9e5f48.
2025-06-26 20:39:34 +05:30
Harsh ShandilyaandGitHub 455b9e5f48 fix: restore Kotlin language version to 2.1 2025-06-26 13:22:37 +05:30
renovate[bot]andGitHub 8efa67f641 chore(deps): update dependency com.facebook:ktfmt to v0.56 2025-06-25 22:33:40 +00:00
renovate[bot]andGitHub b5ac674df2 fix(deps): update sentry to v8.15.0 2025-06-25 19:09:22 +00:00
Harsh ShandilyaandGitHub 75910a5c41 fix: don't set Kotlin language version
This isn't required for app projects

Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
2025-06-25 11:54:20 +05:30
renovate[bot]andGitHub 4f9debab25 chore(deps): update plugin dependencyanalysis to v2.19.0 2025-06-25 01:57:28 +00:00
Harsh Shandilya db38195d67 fix: remove transparent navigation bar scrim
Fixes #869
2025-06-25 03:13:04 +05:30
renovate[bot]andGitHub 88baa9a9a9 fix(deps): update dependency androidx.compose:compose-bom-alpha to v2025.06.01 2025-06-24 20:34:46 +00:00
renovate[bot]andGitHub e4fd49a42b fix(deps): update dependency org.junit:junit-bom to v5.13.2 2025-06-24 14:31:23 +00:00
renovate[bot]andGitHub 1135ff826c chore(deps): update plugin modulegraphassert to v2.9.0 2025-06-23 22:56:20 +00:00
renovate[bot]andGitHub 334cc6c664 fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v5.8.0 2025-06-23 13:38:51 +00:00
renovate[bot]andGitHub dd9fe43f5a fix(deps): update dependency org.jsoup:jsoup to v1.21.1 2025-06-23 08:07:55 +00:00
renovate[bot]andGitHub f57e05b87c fix(deps): update dependency be.digitalia.compose.htmlconverter:htmlconverter to v1.1.0 2025-06-23 02:06:42 +00:00
renovate[bot]andGitHub a36b3b2006 fix(deps): update agp to v8.12.0-alpha06 2025-06-20 23:12:43 +00:00
renovate[bot]andGitHub 3af631c2d7 fix(deps): update androidx 2025-06-18 21:00:09 +00:00
renovate[bot]andGitHub 8e9eea7bd2 fix(deps): update sentry to v8.14.0 2025-06-17 18:51:44 +00:00
renovate[bot]andGitHub a6212ba675 chore(deps): update dependency com.facebook:ktfmt to v0.55 2025-06-17 02:31:16 +00:00
renovate[bot]andGitHub d1ff5c355e fix(deps): update aboutlibraries to v12.2.3 2025-06-15 08:54:35 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
470345e161 fix(deps): update agp to v8.12.0-alpha05 (#863)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [com.android.test](https://developer.android.com/studio/build)
([source](https://android.googlesource.com/platform/tools/base)) |
`8.12.0-alpha04` -> `8.12.0-alpha05` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/com.android.test:com.android.test.gradle.plugin/8.12.0-alpha05?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/com.android.test:com.android.test.gradle.plugin/8.12.0-alpha05?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/com.android.test:com.android.test.gradle.plugin/8.12.0-alpha04/8.12.0-alpha05?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.android.test:com.android.test.gradle.plugin/8.12.0-alpha04/8.12.0-alpha05?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [com.android.lint](https://developer.android.com/studio/build)
([source](https://android.googlesource.com/platform/tools/base)) |
`8.12.0-alpha04` -> `8.12.0-alpha05` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/com.android.lint:com.android.lint.gradle.plugin/8.12.0-alpha05?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/com.android.lint:com.android.lint.gradle.plugin/8.12.0-alpha05?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/com.android.lint:com.android.lint.gradle.plugin/8.12.0-alpha04/8.12.0-alpha05?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.android.lint:com.android.lint.gradle.plugin/8.12.0-alpha04/8.12.0-alpha05?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
|
[com.android.tools.build:gradle](https://developer.android.com/studio/build)
([source](https://android.googlesource.com/platform/tools/base)) |
`8.12.0-alpha04` -> `8.12.0-alpha05` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/com.android.tools.build:gradle/8.12.0-alpha05?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/com.android.tools.build:gradle/8.12.0-alpha05?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/com.android.tools.build:gradle/8.12.0-alpha04/8.12.0-alpha05?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.android.tools.build:gradle/8.12.0-alpha04/8.12.0-alpha05?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these
updates again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC41MC4wIiwidXBkYXRlZEluVmVyIjoiNDAuNTAuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-13 22:15:40 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
914092f8b0 fix(deps): update sentry to v8.13.3 (#861)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
|
[io.sentry:sentry-bom](https://redirect.github.com/getsentry/sentry-java)
| `8.13.2` -> `8.13.3` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/io.sentry:sentry-bom/8.13.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/io.sentry:sentry-bom/8.13.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/io.sentry:sentry-bom/8.13.2/8.13.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/io.sentry:sentry-bom/8.13.2/8.13.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
|
[io.sentry:sentry-android](https://redirect.github.com/getsentry/sentry-java)
| `8.13.2` -> `8.13.3` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/io.sentry:sentry-android/8.13.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/io.sentry:sentry-android/8.13.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/io.sentry:sentry-android/8.13.2/8.13.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/io.sentry:sentry-android/8.13.2/8.13.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>getsentry/sentry-java (io.sentry:sentry-bom)</summary>

###
[`v8.13.3`](https://redirect.github.com/getsentry/sentry-java/blob/HEAD/CHANGELOG.md#8133)

[Compare
Source](https://redirect.github.com/getsentry/sentry-java/compare/8.13.2...8.13.3)

##### Fixes

- Send UI Profiling app start chunk when it finishes
([#&#8203;4423](https://redirect.github.com/getsentry/sentry-java/pull/4423))
- Republish Javadoc
[#&#8203;4457](https://redirect.github.com/getsentry/sentry-java/pull/4457)
- Finalize `OkHttpEvent` even if no active span in
`SentryOkHttpInterceptor`
[#&#8203;4469](https://redirect.github.com/getsentry/sentry-java/pull/4469)
- Session Replay: Do not capture current replay for cached events from
the past
([#&#8203;4474](https://redirect.github.com/getsentry/sentry-java/pull/4474))
- Session Replay: Correctly capture Dialogs and non full-sized windows
([#&#8203;4354](https://redirect.github.com/getsentry/sentry-java/pull/4354))
- Session Replay: Fix inconsistent `segment_id`
([#&#8203;4471](https://redirect.github.com/getsentry/sentry-java/pull/4471))
- Session Replay: Fix crash on devices with the Unisoc/Spreadtrum T606
chipset
([#&#8203;4477](https://redirect.github.com/getsentry/sentry-java/pull/4477))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these
updates again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC41MC4wIiwidXBkYXRlZEluVmVyIjoiNDAuNTAuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-12 14:34:52 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
fd9e156627 chore(deps): update gradle/actions digest to ac638b0 (#859)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [gradle/actions](https://redirect.github.com/gradle/actions) | action
| digest | `8379f6a` -> `ac638b0` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC40OC41IiwidXBkYXRlZEluVmVyIjoiNDAuNDguNSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-12 01:56:50 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
5130ef9931 chore(deps): update dependency de.mannodermaus.android-junit5 to v1.13.0.0 (#858)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
|
[de.mannodermaus.android-junit5](https://redirect.github.com/mannodermaus/android-junit5)
| `1.12.2.0` -> `1.13.0.0` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/de.mannodermaus.android-junit5:de.mannodermaus.android-junit5.gradle.plugin/1.13.0.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/de.mannodermaus.android-junit5:de.mannodermaus.android-junit5.gradle.plugin/1.13.0.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/de.mannodermaus.android-junit5:de.mannodermaus.android-junit5.gradle.plugin/1.12.2.0/1.13.0.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/de.mannodermaus.android-junit5:de.mannodermaus.android-junit5.gradle.plugin/1.12.2.0/1.13.0.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC40OC41IiwidXBkYXRlZEluVmVyIjoiNDAuNDguNSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-11 19:04:07 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
68b18644a8 fix(deps): update aboutlibraries to v12.2.2 (#856)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| com.mikepenz.aboutlibraries.plugin | `12.2.1` -> `12.2.2` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/com.mikepenz.aboutlibraries.plugin:com.mikepenz.aboutlibraries.plugin.gradle.plugin/12.2.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/com.mikepenz.aboutlibraries.plugin:com.mikepenz.aboutlibraries.plugin.gradle.plugin/12.2.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/com.mikepenz.aboutlibraries.plugin:com.mikepenz.aboutlibraries.plugin.gradle.plugin/12.2.1/12.2.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.mikepenz.aboutlibraries.plugin:com.mikepenz.aboutlibraries.plugin.gradle.plugin/12.2.1/12.2.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
|
[com.mikepenz:aboutlibraries-compose-m3](https://redirect.github.com/mikepenz/AboutLibraries)
| `12.2.1` -> `12.2.2` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/com.mikepenz:aboutlibraries-compose-m3/12.2.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/com.mikepenz:aboutlibraries-compose-m3/12.2.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/com.mikepenz:aboutlibraries-compose-m3/12.2.1/12.2.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.mikepenz:aboutlibraries-compose-m3/12.2.1/12.2.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>mikepenz/AboutLibraries
(com.mikepenz:aboutlibraries-compose-m3)</summary>

###
[`v12.2.2`](https://redirect.github.com/mikepenz/AboutLibraries/releases/tag/12.2.2)

[Compare
Source](https://redirect.github.com/mikepenz/AboutLibraries/compare/12.2.1...12.2.2)

#### 🐛 Fixes

-   Revert changes causing issues with Android builds
- PR:
[#&#8203;1193](https://redirect.github.com/mikepenz/AboutLibraries/issues/1193)

#### Contributors:

-   [@&#8203;mikepenz](https://redirect.github.com/mikepenz)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these
updates again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC40OC41IiwidXBkYXRlZEluVmVyIjoiNDAuNDguNSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-11 01:37:02 +00:00
renovate[bot]andGitHub 758a8dbc82 chore(deps): update plugin ksp to v2.1.21-2.0.2 2025-06-10 03:40:56 +00:00
Harsh Shandilya c9d52985ca fix: align JUnit 5 versions
See https://junit.org/junit5/docs/current/user-guide/#running-tests-build-gradle-bom
2025-06-07 18:58:14 +05:30
renovate[bot]andGitHub 310c486b55 fix(deps): update junit5 monorepo to v5.13.1 2025-06-07 08:27:40 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
02f3004e07 fix(deps): update agp to v8.12.0-alpha04 (#851)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [com.android.test](https://developer.android.com/studio/build)
([source](https://android.googlesource.com/platform/tools/base)) |
`8.12.0-alpha03` -> `8.12.0-alpha04` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/com.android.test:com.android.test.gradle.plugin/8.12.0-alpha04?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/com.android.test:com.android.test.gradle.plugin/8.12.0-alpha04?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/com.android.test:com.android.test.gradle.plugin/8.12.0-alpha03/8.12.0-alpha04?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.android.test:com.android.test.gradle.plugin/8.12.0-alpha03/8.12.0-alpha04?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [com.android.lint](https://developer.android.com/studio/build)
([source](https://android.googlesource.com/platform/tools/base)) |
`8.12.0-alpha03` -> `8.12.0-alpha04` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/com.android.lint:com.android.lint.gradle.plugin/8.12.0-alpha04?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/com.android.lint:com.android.lint.gradle.plugin/8.12.0-alpha04?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/com.android.lint:com.android.lint.gradle.plugin/8.12.0-alpha03/8.12.0-alpha04?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.android.lint:com.android.lint.gradle.plugin/8.12.0-alpha03/8.12.0-alpha04?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
|
[com.android.tools.build:gradle](https://developer.android.com/studio/build)
([source](https://android.googlesource.com/platform/tools/base)) |
`8.12.0-alpha03` -> `8.12.0-alpha04` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/com.android.tools.build:gradle/8.12.0-alpha04?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/com.android.tools.build:gradle/8.12.0-alpha04?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/com.android.tools.build:gradle/8.12.0-alpha03/8.12.0-alpha04?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.android.tools.build:gradle/8.12.0-alpha03/8.12.0-alpha04?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these
updates again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC40MC4zIiwidXBkYXRlZEluVmVyIjoiNDAuNDAuMyIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-07 04:05:50 +00:00
Harsh Shandilya 123e2c9eb2 fix: remove benchmark project
This has barely ever worked and I'm kinda done with it
2025-06-07 09:29:36 +05:30
Harsh Shandilya 93b593bd41 refactor: move datastore module to common 2025-06-07 09:13:02 +05:30
Harsh Shandilya d31da62cfe fix: improve TagList UI 2025-06-07 09:06:52 +05:30
renovate[bot]andHarsh Shandilya d2f0630fe2 fix(deps): update haze to v1.6.4 2025-06-07 09:06:52 +05:30
renovate[bot]andHarsh Shandilya fc6931d84d fix(deps): update aboutlibraries to v12.2.1 2025-06-07 09:06:52 +05:30
Harsh Shandilya 3a7c1b9068 refactor(core): reorganize to feature packages 2025-06-07 09:06:51 +05:30
renovate[bot]andHarsh Shandilya c0fde85262 chore(deps): update dependency gradle to v8.14.2 2025-06-07 09:06:51 +05:30
Harsh Shandilya a746734ad8 common: add TagList 2025-06-07 09:06:51 +05:30
Harsh Shandilya a0f044786f feat: add get tags endpoint 2025-06-07 09:06:51 +05:30
Harsh Shandilya d69629c319 fix: upgrade AndroidX snapshot 2025-06-07 09:06:51 +05:30
Harsh Shandilya d4bfee92b5 fix: avoid deprecated API 2025-06-05 18:35:43 +05:30
renovate[bot]andGitHub 6702cf6ea2 fix(deps): update androidx 2025-06-04 18:49:48 +00:00
Harsh Shandilya 1a14095af6 fix: reset to loading state on next update
Fixes #847
2025-06-05 00:02:19 +05:30
Harsh ShandilyaandGitHub da3ca63085 chore: re-enable Renovate updates for about libraries
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
2025-06-04 14:05:15 +05:30
Harsh Shandilya 091276a3c6 fix: upgrade about libraries 2025-06-04 00:08:30 +05:30
Harsh Shandilya 669ec2ad09 fix: use nav3 snapshots
This works around a fatal crash that is triggered during configuration changes
2025-06-03 01:29:35 +05:30
Harsh Shandilya 6badde13b0 fix: disable more config changes 2025-06-03 00:48:35 +05:30
Harsh Shandilya 764be07cc0 fix: address regression in comment collapse 2025-06-02 21:52:19 +05:30
Harsh Shandilya 8947c3a3bc refactor: make CommentNode#toString non-recursive 2025-06-02 21:40:46 +05:30
renovate[bot]andGitHub 16cc116b00 fix(deps): update haze to v1.6.3 2025-06-01 09:32:16 +00:00
Harsh Shandilya 7c9f833ade refactor: pull down comments stuff into common 2025-06-01 14:40:43 +05:30
Harsh Shandilya 0d47787a73 refactor: remove unused method 2025-06-01 13:49:30 +05:30
Harsh Shandilya 8c4b2f2b9b refactor: move comment expand/collapse flow to a button 2025-06-01 13:36:04 +05:30
renovate[bot]andGitHub 18dd1d8255 fix(deps): update agp to v8.12.0-alpha03 2025-05-29 23:07:15 +00:00
renovate[bot]andGitHub 988f360a14 fix(deps): update dependency com.github.requery:sqlite-android to v3.49.0 2025-05-29 20:07:18 +00:00
Harsh Shandilya b6169ca5ca fix: add extra offset to comment counts when required 2025-05-30 00:47:28 +05:30
Harsh Shandilya 30d035baa2 fix: restore navigation rail
Fixes #834
2025-05-30 00:06:22 +05:30
Harsh Shandilya bebbe4f82c fix: key ViewModels to prevent incorrect re-use 2025-05-29 23:45:37 +05:30
renovate[bot]andGitHub 931684f76b chore(deps): update plugin dependencyanalysis to v2.18.0 2025-05-28 22:19:58 +00:00
renovate[bot]andGitHub 843b610018 fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v5.7.0 2025-05-28 14:31:25 +00:00
renovate[bot]andGitHub 26d78eb26b chore(deps): update gradle/actions digest to 8379f6a 2025-05-28 09:00:03 +00:00
renovate[bot]andGitHub e669a22d90 fix(deps): update dependency com.diffplug.spotless:spotless-plugin-gradle to v7.0.4 2025-05-27 22:45:06 +00:00
renovate[bot]andGitHub b4d3b60764 fix(deps): update sentry to v8.13.2 2025-05-27 18:15:14 +00:00
renovate[bot]andGitHub 05538cc3c6 chore(deps): update plugin com.gradle.develocity to v4.0.2 2025-05-27 16:44:41 +00:00
renovate[bot]andGitHub 4bb36e4930 fix(deps): update sentry to v8.13.1 2025-05-26 19:33:16 +00:00
Harsh Shandilya eccdb91c82 fix: disable InvalidPackage lint 2025-05-26 19:36:06 +05:30
Harsh Shandilya 0aaac90e63 chore: refresh Lint baseline 2025-05-26 19:16:12 +05:30
Harsh Shandilya bcac86d187 refactor: hoist state out of comments page 2025-05-26 19:14:23 +05:30
Harsh Shandilya 0d3c08c10a refactor: hoist state out of UserProfile
Solves the issue of every pop of the backstack causing data to be re-fetched,
but now has the issue of the data being stale for a few frames. Still better
than the current state, so I'll take it.
2025-05-26 19:02:37 +05:30
Harsh Shandilya 5d65d1ea51 common: make NetworkState public 2025-05-26 18:52:55 +05:30
Harsh Shandilya daf66a16bc refactor: move HttpFailure.toError to api module
Also expose EitherNet as an api dependency since it's part of the
public API.
2025-05-26 18:51:23 +05:30
Harsh Shandilya 1db8067616 chore: update changelog 2025-05-25 23:14:54 +05:30
Harsh Shandilya 2543f5bcc2 refactor: Nav3Screen -> LobstersPostsScreen 2025-05-25 23:12:07 +05:30
Harsh Shandilya e92017173d refactor: cleanup ClawBackStack implementation 2025-05-25 23:11:33 +05:30
Harsh Shandilya fe5a6f0a93 fix: add missing adaptive strategy 2025-05-25 22:46:34 +05:30
Harsh Shandilya f7c8f968e5 feat: add adaptive hints to Nav3Screen 2025-05-25 22:46:34 +05:30
Harsh Shandilya a3f15a911e fix(deps): add material3 navigation3 adaptive 2025-05-25 22:46:34 +05:30
Harsh Shandilya 88f4f2dd5a chore(build): explicitly set maven content for repos 2025-05-25 22:46:34 +05:30
Harsh Shandilya 24a07c9600 fix: remove unused dependencies 2025-05-25 22:46:34 +05:30
Harsh Shandilya f64d4ab9a1 refactor: eliminate Destination indirection 2025-05-25 22:46:34 +05:30
Harsh Shandilya 9dce06ff80 refactor: pull out ClawAppBar
Also change consumers of ClawBackStack to take delegating references
2025-05-25 22:46:34 +05:30
Harsh Shandilya beb1943ee6 refactor: consistently use destination over route 2025-05-25 22:46:34 +05:30
Harsh Shandilya 9c76617700 refactor: properly use TopLevelRoute 2025-05-25 22:46:34 +05:30
Harsh Shandilya e011870986 refactor: add a custom back stack implementation 2025-05-25 17:43:20 +05:30
Harsh Shandilya eef3fa0fc8 fix: add back Haze 2025-05-25 16:55:19 +05:30
Harsh Shandilya 0a466d5f54 feat: add nicer animations 2025-05-25 16:53:18 +05:30
Harsh Shandilya ec70c9ab56 fix: try fixing backstack introspection 2025-05-25 16:53:09 +05:30
Harsh Shandilya 699afafcf9 refactor: switch to rememberNavBackStack helper 2025-05-25 16:52:41 +05:30
Harsh Shandilya 1385edc3bf refactor: simplify settings screen 2025-05-25 15:30:29 +05:30
Harsh Shandilya 92fb2939e6 refactor: fix search destination 2025-05-25 15:30:29 +05:30
Harsh Shandilya bf0a6c40e8 refactor: inline BaseActivity indirection 2025-05-25 15:03:11 +05:30
Harsh Shandilya 74ccf74f8d refactor: move navigation rail to nav3 2025-05-25 13:58:35 +05:30
Harsh Shandilya 17289a26f1 feat: rewrite navigation on top of Nav3 2025-05-25 13:58:35 +05:30
Harsh Shandilya 9b322c212f chore: update changelog 2025-05-25 13:58:22 +05:30
Harsh Shandilya 46893ca71c chore: skip CI for draft PRs 2025-05-25 13:58:22 +05:30
Harsh Shandilya e39ddbb6c1 chore: remove unused version catalog entries 2025-05-25 13:58:22 +05:30
Harsh Shandilya 8edf833461 chore: add a preview to LobstersListItem 2025-05-25 13:58:22 +05:30
Harsh Shandilya 604856912f fix: increase swipe thresdhold 2025-05-25 08:51:32 +05:30
Harsh Shandilya be80812dc6 refactor: rewrite comment text rendering 2025-05-25 08:46:00 +05:30
renovate[bot]andGitHub 77da21d9b0 fix(deps): update agp to v8.12.0-alpha02 2025-05-22 19:19:14 +00:00
Harsh Shandilya 4216c93783 fix: silence more R8 warnings 2025-05-23 00:47:34 +05:30
renovate[bot]andGitHub 3382c6d781 chore(deps): update dependency gradle to v8.14.1 2025-05-22 18:37:52 +00:00
Harsh Shandilya 46d2910a33 chore: regenerate Lint baseline 2025-05-22 23:58:39 +05:30
Harsh Shandilya 2aea249e0b fix(deps): bump agp to 8.12.0-alpha01 2025-05-22 23:54:26 +05:30
renovate[bot]andGitHub 62b00ccc66 fix(deps): update androidx 2025-05-21 03:21:04 +00:00
renovate[bot]andGitHub a713484880 fix(deps): update haze to v1.6.2 2025-05-20 22:54:34 +00:00
Harsh Shandilya 79204455ca chore: regenerate lint baselines 2025-05-20 17:20:06 +05:30
Harsh Shandilya 0c96e8b62a chore: migrate away from deprecated OkHttp API 2025-05-20 17:20:06 +05:30
renovate[bot]andGitHub 9cb85c8f76 fix(deps): update retrofit monorepo to v3 2025-05-20 11:37:46 +00:00
Harsh Shandilya 32243fdf7b fix(build): address DSL deprecation 2025-05-20 17:06:49 +05:30
Harsh Shandilya 00574b7ef1 fix(build): remove kotlin.jvmToolchain 2025-05-20 17:06:49 +05:30
renovate[bot]andGitHub 775b0f6fd9 chore(deps): update plugin org.gradle.toolchains.foojay-resolver-convention to v1 2025-05-20 03:44:11 +00:00
renovate[bot]andGitHub ecc6965d70 fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v5.6.0 2025-05-19 20:36:22 +00:00
renovate[bot]andGitHub 29da084267 fix(deps): update haze to v1.6.1 2025-05-19 13:00:02 +00:00
renovate[bot]andGitHub d11feb3b1f chore(deps): update dependency de.mannodermaus.android-junit5 to v1.12.2.0 2025-05-18 10:55:33 +00:00
renovate[bot]andGitHub 99807e5421 chore(deps): update plugin poko to v0.18.7 2025-05-16 23:31:04 +00:00
renovate[bot]andGitHub 60d98427ca fix(deps): update sqldelight to v2.1.0 2025-05-16 19:58:24 +00:00
renovate[bot]andGitHub 069baf38d9 fix(deps): update retrofit monorepo to v2.12.0 2025-05-15 17:13:15 +00:00
renovate[bot]andGitHub bd4848cbd4 fix(deps): update dependency org.jetbrains.kotlinx:kotlinx-collections-immutable to v0.4.0 2025-05-14 15:44:35 +00:00
renovate[bot]andGitHub 0c8df9827c fix(deps): update sentry to v8.12.0 2025-05-14 06:01:46 +00:00
renovate[bot]andGitHub 30ffaa8d99 chore(deps): update plugin ksp to v2.1.21-2.0.1 2025-05-14 02:28:38 +00:00
renovate[bot]andGitHub 6439f73382 chore(deps): update actions/dependency-review-action digest to da24556 2025-05-13 22:55:53 +00:00
renovate[bot]andGitHub 3ede5c6240 fix(deps): update kotlin and compose to v2.1.21 2025-05-13 11:04:52 +00:00
renovate[bot]andGitHub ad4f28e53b fix(deps): update coil3 to v3.2.0 2025-05-13 07:58:40 +00:00
renovate[bot]andGitHub c45536a325 fix(deps): update haze to v1.6.0 2025-05-12 18:52:45 +00:00
renovate[bot]andGitHub 31fbad5f07 fix(deps): update agp to v8.11.0-alpha10 2025-05-09 03:21:14 +00:00
renovate[bot]andGitHub 6d3325ef93 chore(deps): update actions/dependency-review-action digest to 38ecb5b 2025-05-08 22:41:44 +00:00
renovate[bot]andGitHub 4ff599f554 fix(deps): update androidx 2025-05-07 18:49:39 +00:00
Harsh Shandilya 26318854bc chore: update Renovate config 2025-05-08 00:16:55 +05:30
renovate[bot]andGitHub 836dec2763 fix(deps): update haze to v1.6.0-rc02 2025-05-06 20:02:21 +00:00
renovate[bot]andGitHub eb0b2e4705 fix(deps): update haze to v1.6.0-rc01 2025-05-05 18:42:06 +00:00
renovate[bot]andGitHub 6e6a2582aa fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v5.5.0 2025-05-05 11:28:45 +00:00
renovate[bot]andGitHub 956a477337 fix(deps): update haze to v1.6.0-beta03 2025-05-04 14:12:56 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
5ad3f9d208 fix(deps): update agp to v8.11.0-alpha09 (#821)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [com.android.test](https://developer.android.com/studio/build)
([source](https://android.googlesource.com/platform/tools/base)) |
`8.11.0-alpha08` -> `8.11.0-alpha09` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/com.android.test:com.android.test.gradle.plugin/8.11.0-alpha09?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/com.android.test:com.android.test.gradle.plugin/8.11.0-alpha09?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/com.android.test:com.android.test.gradle.plugin/8.11.0-alpha08/8.11.0-alpha09?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.android.test:com.android.test.gradle.plugin/8.11.0-alpha08/8.11.0-alpha09?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [com.android.lint](https://developer.android.com/studio/build)
([source](https://android.googlesource.com/platform/tools/base)) |
`8.11.0-alpha08` -> `8.11.0-alpha09` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/com.android.lint:com.android.lint.gradle.plugin/8.11.0-alpha09?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/com.android.lint:com.android.lint.gradle.plugin/8.11.0-alpha09?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/com.android.lint:com.android.lint.gradle.plugin/8.11.0-alpha08/8.11.0-alpha09?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.android.lint:com.android.lint.gradle.plugin/8.11.0-alpha08/8.11.0-alpha09?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
|
[com.android.tools.build:gradle](https://developer.android.com/studio/build)
([source](https://android.googlesource.com/platform/tools/base)) |
`8.11.0-alpha08` -> `8.11.0-alpha09` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/com.android.tools.build:gradle/8.11.0-alpha09?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/com.android.tools.build:gradle/8.11.0-alpha09?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/com.android.tools.build:gradle/8.11.0-alpha08/8.11.0-alpha09?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.android.tools.build:gradle/8.11.0-alpha08/8.11.0-alpha09?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these
updates again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4yNjQuMCIsInVwZGF0ZWRJblZlciI6IjM5LjI2NC4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-02 04:36:54 +00:00
renovate[bot]andGitHub 05a3393db0 chore(deps): update plugin ksp to v2.1.20-2.0.1 2025-05-01 01:46:33 +00:00
renovate[bot]andGitHub ab64992333 fix(deps): update sentry to v8.11.1 2025-04-30 16:33:06 +00:00
renovate[bot]andGitHub 5661437d9a fix(deps): update sentry to v8.11.0 2025-04-30 02:12:50 +00:00
renovate[bot]andGitHub cdb56c8c1f fix(deps): update haze to v1.6.0-beta02 2025-04-29 23:31:10 +00:00
Harsh Shandilya 376f1b09bd chore: refresh lint baselines 2025-04-30 01:39:04 +05:30
renovate[bot]andGitHub a86f02c3d8 fix(deps): update dependency org.jsoup:jsoup to v1.20.1 2025-04-29 17:33:24 +00:00
renovate[bot]andGitHub c9ac3162eb chore(deps): update agp to v8.11.0-alpha08 2025-04-28 17:00:24 +00:00
renovate[bot]andGitHub 187d738cb8 fix(deps): update sentry to v8.10.0 2025-04-25 23:42:39 +00:00
renovate[bot]andGitHub ec56d635e3 chore(deps): update plugin com.gradle.develocity to v4.0.1 2025-04-25 20:08:08 +00:00
renovate[bot]andGitHub acdc1851c5 chore(deps): update dependency gradle to v8.14 2025-04-25 12:28:06 +00:00
renovate[bot]andGitHub 048bb7b94b chore(deps): update plugin dependencyanalysis to v2.17.0 2025-04-25 03:12:19 +00:00
renovate[bot]andGitHub 66185c0062 fix(deps): update androidx 2025-04-24 23:10:41 +00:00
Harsh Shandilya 1412ee7486 chore: refresh Lint baseline 2025-04-25 01:10:33 +05:30
renovate[bot]andGitHub 1fa2a921b6 fix(deps): update androidx 2025-04-23 19:42:18 +00:00
renovate[bot]andGitHub a6bd9f02ff fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v5.4.0 2025-04-23 16:00:15 +00:00
Harsh Shandilya d8f086ddbc fix(deps): upgrade Haze to 1.6.0-beta01 2025-04-23 13:28:55 +05:30
renovate[bot]andGitHub e9cf0ecdbc fix(deps): update konvert to v4.1.0 2025-04-22 22:58:44 +00:00
renovate[bot]andGitHub 0695ed8a92 fix(deps): update sentry to v8.9.0 2025-04-22 17:07:56 +00:00
renovate[bot]andGitHub 4c8b78b298 fix(deps): update agp to v8.11.0-alpha07 2025-04-17 20:02:08 +00:00
renovate[bot]andGitHub 526d92bba5 fix(deps): update haze to v1.5.4 2025-04-17 14:53:11 +00:00
renovate[bot]andGitHub 6595fcba42 fix(deps): update dagger to v2.56.2 2025-04-17 02:08:30 +00:00
renovate[bot]andGitHub 6ebd85c5b7 chore(deps): update plugin poko to v0.18.6 2025-04-16 19:49:50 +00:00
renovate[bot]andGitHub 5669da3b24 fix(deps): update sentry to v8.8.0 2025-04-14 16:59:46 +00:00
renovate[bot]andGitHub 9f8069dfda fix(deps): update haze to v1.5.3 2025-04-13 14:33:32 +00:00
renovate[bot]andGitHub 3c5f867123 chore(deps): update plugin dependencyanalysis to v2.16.0 2025-04-10 21:54:40 +00:00
renovate[bot]andGitHub 6a075621d8 fix(deps): update agp to v8.11.0-alpha06 2025-04-10 19:21:55 +00:00
renovate[bot]andGitHub 781482a98a chore(deps): update plugin com.gradle.develocity to v4 2025-04-10 11:55:54 +00:00
renovate[bot]andGitHub 2dc89052a3 chore(deps): update plugin org.gradle.toolchains.foojay-resolver-convention to v0.10.0 2025-04-10 07:36:03 +00:00
renovate[bot]andGitHub 49ee48bd8b fix(deps): update sentry to v8.7.0 2025-04-10 03:37:10 +00:00
renovate[bot]andGitHub be6795a0bb fix(deps): update androidx 2025-04-09 21:52:56 +00:00
renovate[bot]andGitHub 88c6716bd9 chore(deps): update plugin dependencyanalysis to v2.15.0 2025-04-09 16:30:22 +00:00
renovate[bot]andGitHub 1494d782e9 fix(deps): update agp to v8.11.0-alpha05 2025-04-09 11:57:24 +00:00
renovate[bot]andGitHub 1cbf1c24a0 chore(deps): update actions/setup-java action to v4.7.1 2025-04-09 06:12:01 +00:00
renovate[bot]andGitHub 81749b9a1a chore(deps): update plugin ksp to v2.1.20-2.0.0 2025-04-09 03:01:24 +00:00
renovate[bot]andGitHub 5a6a0e3064 fix(deps): update kotlinx-coroutines monorepo to v1.10.2 2025-04-08 21:45:31 +00:00
renovate[bot]andGitHub b4eaf86492 chore(deps): update actions/dependency-review-action digest to 67d4f4b 2025-04-08 17:57:09 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
bbff676226 fix(deps): update dependency com.diffplug.spotless:spotless-plugin-gradle to v7.0.3 (#817)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
|
[com.diffplug.spotless:spotless-plugin-gradle](https://redirect.github.com/diffplug/spotless)
| `7.0.2` -> `7.0.3` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/com.diffplug.spotless:spotless-plugin-gradle/7.0.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/com.diffplug.spotless:spotless-plugin-gradle/7.0.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/com.diffplug.spotless:spotless-plugin-gradle/7.0.2/7.0.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.diffplug.spotless:spotless-plugin-gradle/7.0.2/7.0.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4yMjcuMyIsInVwZGF0ZWRJblZlciI6IjM5LjIyNy4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-08 11:46:44 +00:00
Harsh Shandilya 0db43a8752 chore: silent Aligned16KB lint temporarily 2025-04-08 13:21:35 +05:30
Harsh Shandilya 1b80f5525b chore: refresh Lint baselines 2025-04-08 12:54:34 +05:30
renovate[bot]andGitHub 49c424512a chore(deps): update agp to v8.11.0-alpha04 2025-04-03 22:45:25 +00:00
renovate[bot]andGitHub 8b510aa2a9 fix(deps): update sentry to v8.6.0 2025-04-01 13:24:42 +00:00
renovate[bot]andGitHub 5c086474dc fix(deps): update serialization to v1.8.1 2025-04-01 12:30:45 +00:00
renovate[bot]andGitHub fd636dfcc4 chore(deps): update plugin poko to v0.18.5 2025-03-31 18:58:19 +00:00
renovate[bot]andGitHub 1a4a18e18a fix(deps): update androidx 2025-03-28 23:06:44 +00:00
Harsh Shandilya 9a4eea8d8d chore: update changelog 2025-03-29 00:30:06 +05:30
Harsh Shandilya e8804efd57 fix(build): upgrade to AGP 8.11.0-alpha03 2025-03-28 23:50:34 +05:30
Harsh Shandilya 5599d6d70d refactor: drop screenshot tests
This breaks the Anvil Gradle Plugin

See: https://github.com/ZacSweers/anvil/issues/89
2025-03-28 23:50:07 +05:30
Harsh Shandilya 82d8303bfe chore(deps): bump everything 2025-03-28 23:42:39 +05:30
renovate[bot]andGitHub 52d17679ad chore(deps): update plugin dependencyanalysis to v2.13.3 2025-03-28 05:43:49 +00:00
renovate[bot]andGitHub cc8dd42a37 fix(deps): update dependency com.slack.lint:slack-lint-checks to v0.9.0 2025-03-28 01:38:31 +00:00
renovate[bot]andGitHub 2e0a950cc5 chore(deps): update plugin poko to v0.18.4 2025-03-27 22:54:08 +00:00
renovate[bot]andGitHub 851582c307 fix(deps): update androidx 2025-03-27 01:41:52 +00:00
renovate[bot]andGitHub 0df6c6aff9 chore(deps): update kotlin and compose 2025-03-26 21:29:42 +00:00
renovate[bot]andGitHub fabdf75129 chore(deps): pin tramlinehq/deploy-action action to 58cc98c 2025-03-26 20:02:53 +00:00
Harsh Shandilya f1c618909b fix(ci): migrate to tramlinehq/deploy-action 2025-03-27 01:30:44 +05:30
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
05176a1b91 chore(deps): update plugin dependencyanalysis to v2.13.2 (#806)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
|
[com.autonomousapps.dependency-analysis](https://redirect.github.com/autonomousapps/dependency-analysis-android-gradle-plugin)
| `2.13.1` -> `2.13.2` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/com.autonomousapps.dependency-analysis:com.autonomousapps.dependency-analysis.gradle.plugin/2.13.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/com.autonomousapps.dependency-analysis:com.autonomousapps.dependency-analysis.gradle.plugin/2.13.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/com.autonomousapps.dependency-analysis:com.autonomousapps.dependency-analysis.gradle.plugin/2.13.1/2.13.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.autonomousapps.dependency-analysis:com.autonomousapps.dependency-analysis.gradle.plugin/2.13.1/2.13.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>autonomousapps/dependency-analysis-android-gradle-plugin
(com.autonomousapps.dependency-analysis)</summary>

###
[`v2.13.2`](https://redirect.github.com/autonomousapps/dependency-analysis-android-gradle-plugin/blob/HEAD/CHANGELOG.md#Version-2132)

-   \[Fix]: detect usages of SAM interfaces.
-   \[Fix]: binaryClassAccesses uses sorted keys.

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4yMDcuMSIsInVwZGF0ZWRJblZlciI6IjM5LjIwNy4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-26 06:16:31 +00:00
renovate[bot]andGitHub 5fbcfc0eaa chore(deps): update gradle/actions digest to 06832c7 2025-03-25 23:14:57 +00:00
renovate[bot]andGitHub a74105ef39 chore(deps): update plugin dependencyanalysis to v2.13.1 2025-03-24 22:57:31 +00:00
renovate[bot]andGitHub 8a98515d60 fix(deps): update haze to v1.5.2 2025-03-23 14:14:25 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
6dfbf188e8 fix(deps): update kotlin and compose (#801)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [com.google.devtools.ksp](https://goo.gle/ksp)
([source](https://redirect.github.com/google/ksp)) | `2.1.10-1.0.31` ->
`2.1.20-1.0.31` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin/2.1.20-1.0.31?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin/2.1.20-1.0.31?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin/2.1.10-1.0.31/2.1.20-1.0.31?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin/2.1.10-1.0.31/2.1.20-1.0.31?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [org.jetbrains.kotlin.plugin.serialization](https://kotlinlang.org/)
([source](https://redirect.github.com/JetBrains/kotlin)) | `2.1.10` ->
`2.1.20` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/org.jetbrains.kotlin.plugin.serialization:org.jetbrains.kotlin.plugin.serialization.gradle.plugin/2.1.20?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/org.jetbrains.kotlin.plugin.serialization:org.jetbrains.kotlin.plugin.serialization.gradle.plugin/2.1.20?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/org.jetbrains.kotlin.plugin.serialization:org.jetbrains.kotlin.plugin.serialization.gradle.plugin/2.1.10/2.1.20?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/org.jetbrains.kotlin.plugin.serialization:org.jetbrains.kotlin.plugin.serialization.gradle.plugin/2.1.10/2.1.20?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [org.jetbrains.kotlin.plugin.compose](https://kotlinlang.org/)
([source](https://redirect.github.com/JetBrains/kotlin)) | `2.1.10` ->
`2.1.20` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/org.jetbrains.kotlin.plugin.compose:org.jetbrains.kotlin.plugin.compose.gradle.plugin/2.1.20?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/org.jetbrains.kotlin.plugin.compose:org.jetbrains.kotlin.plugin.compose.gradle.plugin/2.1.20?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/org.jetbrains.kotlin.plugin.compose:org.jetbrains.kotlin.plugin.compose.gradle.plugin/2.1.10/2.1.20?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/org.jetbrains.kotlin.plugin.compose:org.jetbrains.kotlin.plugin.compose.gradle.plugin/2.1.10/2.1.20?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [org.jetbrains.kotlin:kotlin-gradle-plugin](https://kotlinlang.org/)
([source](https://redirect.github.com/JetBrains/kotlin)) | `2.1.10` ->
`2.1.20` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/org.jetbrains.kotlin:kotlin-gradle-plugin/2.1.20?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/org.jetbrains.kotlin:kotlin-gradle-plugin/2.1.20?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/org.jetbrains.kotlin:kotlin-gradle-plugin/2.1.10/2.1.20?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/org.jetbrains.kotlin:kotlin-gradle-plugin/2.1.10/2.1.20?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>google/ksp (com.google.devtools.ksp)</summary>

###
[`v2.1.20-1.0.31`](https://redirect.github.com/google/ksp/releases/tag/2.1.20-1.0.31)

#### What's Changed

- Update github actions permissions for release on 1.0.31-release by
[@&#8203;ting-yuan](https://redirect.github.com/ting-yuan) in
[https://github.com/google/ksp/pull/2383](https://redirect.github.com/google/ksp/pull/2383)
- Bump Kotlin version to 2.1.20 by
[@&#8203;mkmuir0](https://redirect.github.com/mkmuir0) in
[https://github.com/google/ksp/pull/2384](https://redirect.github.com/google/ksp/pull/2384)

**Full Changelog**:
https://github.com/google/ksp/compare/2.1.20-RC2-1.0.31...2.1.20-1.0.31

</details>

<details>
<summary>JetBrains/kotlin
(org.jetbrains.kotlin.plugin.serialization)</summary>

###
[`v2.1.20`](https://redirect.github.com/JetBrains/kotlin/releases/tag/v2.1.20):
Kotlin 2.1.20

##### Changelog

##### Analysis API

##### Fixes

- [`KT-68198`](https://youtrack.jetbrains.com/issue/KT-68198) Analysis
API: Support application service registration in plugin XMLs
- [`KT-57733`](https://youtrack.jetbrains.com/issue/KT-57733) Analysis
API: Use optimized `ModuleWithDependenciesScope`s in combined symbol
providers
- [`KT-73156`](https://youtrack.jetbrains.com/issue/KT-73156) AA: type
retrieval for erroneous typealias crashes
- [`KT-71907`](https://youtrack.jetbrains.com/issue/KT-71907) K2
debugger evaluator failed when cannot resolve unrelated annotation
- [`KT-69128`](https://youtrack.jetbrains.com/issue/KT-69128) K2 IDE:
"Unresolved reference in KDoc" reports existing Java class in reference
to its own nested class
- [`KT-71613`](https://youtrack.jetbrains.com/issue/KT-71613)
KaFirPsiJavaTypeParameterSymbol cannot be cast to
KaFirTypeParameterSymbol
- [`KT-71741`](https://youtrack.jetbrains.com/issue/KT-71741) K2 IDE.
Classifier was found in KtFile but was not found in FirFile in
`libraries/tools/kotlin-gradle-plugin-integration-tests/build.gradle.kts`
in `kotlin.git` and broken analysis
- [`KT-71942`](https://youtrack.jetbrains.com/issue/KT-71942) Need to
rethrow Intellij Platform exceptions, like ProcessCanceledException
- [`KT-70949`](https://youtrack.jetbrains.com/issue/KT-70949) Analysis
API: "containingDeclaration" does not work on nested Java classes in K2
implementation
- [`KT-69736`](https://youtrack.jetbrains.com/issue/KT-69736) K2 IDE:
False positive resolution from KDoc for `value`
- [`KT-69047`](https://youtrack.jetbrains.com/issue/KT-69047) Analysis
API: Unresolved KDoc reference to extensions with the same name
- [`KT-70815`](https://youtrack.jetbrains.com/issue/KT-70815) Analysis
API: Implement stop-the-world session invalidation
- [`KT-69630`](https://youtrack.jetbrains.com/issue/KT-69630) KAPT User
project builds with KAPT4 enabled fail with Metaspace overflow

##### Analysis API. Code Compilation

- [`KT-71263`](https://youtrack.jetbrains.com/issue/KT-71263) K2
evaluator: Error in evaluating self property with extension receiver

##### Analysis API. FIR

##### Performance Improvements

- [`KT-72025`](https://youtrack.jetbrains.com/issue/KT-72025)
FileStructureElement: reduce redundant resolve
- [`KT-74012`](https://youtrack.jetbrains.com/issue/KT-74012) Redundant
`FirAbstractBodyResolveTransformerDispatcher.<init>` CPU consumption
- [`KT-73900`](https://youtrack.jetbrains.com/issue/KT-73900)
ContextCollectorVisitor#computeContext may spend significant time on
`createSnapshot`
- [`KT-73665`](https://youtrack.jetbrains.com/issue/KT-73665)
FirElementFinder is inefficient in large files
- [`KT-73330`](https://youtrack.jetbrains.com/issue/KT-73330) Remove
bodies from functions without contracts after the CONTRACTS phase
- [`KT-73017`](https://youtrack.jetbrains.com/issue/KT-73017) Analysis
API: `FirReferenceResolveHelper.getSymbolsByResolvedImport` searches for
classes even when the selected `FqName` is a known package

##### Fixes

- [`KT-72308`](https://youtrack.jetbrains.com/issue/KT-72308)
getOrBuildFir returns null for this expression for plusAssign operator
- [`KT-72660`](https://youtrack.jetbrains.com/issue/KT-72660) ISE:
Recursive update at
org.jetbrains.kotlin.analysis.low.level.api.fir.caches.FirCaffeineCache.getValue
- [`KT-74097`](https://youtrack.jetbrains.com/issue/KT-74097) ISE:
Recursive update at
org.jetbrains.kotlin.analysis.low.level.api.fir.caches.FirCaffeineCache.getValue
- [`KT-74098`](https://youtrack.jetbrains.com/issue/KT-74098) ISE:
Recursive update at
org.jetbrains.kotlin.analysis.low.level.api.fir.caches.FirCaffeineCache.getValue
- [`KT-72148`](https://youtrack.jetbrains.com/issue/KT-72148) K2:
KISEWA: Expected FirResolvedArgumentList for FirAnnotationCallImpl of
FirValueParameterImpl(DataClassMember) but FirArgumentListImpl found
- [`KT-73079`](https://youtrack.jetbrains.com/issue/KT-73079) K2:
Internal compiler error when conflicting type aliases are present
- [`KT-73456`](https://youtrack.jetbrains.com/issue/KT-73456) Expected
FirResolvedContractDescription but FirRawContractDescriptionImpl found
for FirSimpleFunctionImpl
- [`KT-73259`](https://youtrack.jetbrains.com/issue/KT-73259) Expected
FirResolvedContractDescription but FirLegacyRawContractDescriptionImpl
found for FirSimpleFunctionImpl
- [`KT-72740`](https://youtrack.jetbrains.com/issue/KT-72740)
FirDanglingModifierList: `lazyResolveToPhase(STATUS)` cannot be called
from a transformer with a phase STATUS
- [`KT-66132`](https://youtrack.jetbrains.com/issue/KT-66132) K2:
FirRegularClass expected, but FirFileImpl found | Containing declaration
is not found
- [`KT-72196`](https://youtrack.jetbrains.com/issue/KT-72196) K2. KMP.
IllegalStateException: expect-actual matching is only possible for code
with sources
- [`KT-72652`](https://youtrack.jetbrains.com/issue/KT-72652)
`FirProvider#getContainingClass` should support
`FirDanglingModifierSymbol`
- [`KT-73105`](https://youtrack.jetbrains.com/issue/KT-73105) Lazy
resolve contract violation (BODY_RESOLVE from BODY_RESOLVE)
- [`KT-66261`](https://youtrack.jetbrains.com/issue/KT-66261) K2:
Analysis API: "FirDeclaration was not found for class
org.jetbrains.kotlin.psi.KtProperty, fir is null" with
MULTIPLE_LABELS_ARE_FORBIDDEN K2 error
- [`KT-72315`](https://youtrack.jetbrains.com/issue/KT-72315) K2. KIWA
on usage of always-true OR in guard condition
- [`KT-65707`](https://youtrack.jetbrains.com/issue/KT-65707) K2 IDE:
unresolved calls of callables imported with typealias as qualifier
- [`KT-61516`](https://youtrack.jetbrains.com/issue/KT-61516) K2:
Provide an LL FIR implementation for `getContainingClassSymbol` (in
`FirHelpers`)
- [`KT-72853`](https://youtrack.jetbrains.com/issue/KT-72853) Expected
FirResolvedArgumentList for FirAnnotationCallImpl of
FirContextReceiverImpl(Source) but FirArgumentListImpl found
- [`KT-64215`](https://youtrack.jetbrains.com/issue/KT-64215) K2: do not
resolve type annotations of receiver if it is used as an implicit return
type
- [`KT-64248`](https://youtrack.jetbrains.com/issue/KT-64248) K2: do not
resolve type annotations of context receiver if it is used as an
implicit return type
- [`KT-72821`](https://youtrack.jetbrains.com/issue/KT-72821) Add
assertion to diagnostic tests to check that all declarations have
BODY_RESOLVE phase at the end
- [`KT-64056`](https://youtrack.jetbrains.com/issue/KT-64056) K2: K2:
FirLazyBodiesCalculator shouldn't calculate annotation arguments on type
phase
- [`KT-71651`](https://youtrack.jetbrains.com/issue/KT-71651) K2 IDE:
False positive NON_LOCAL_SUSPENSION_POINT in suspend function call
- [`KT-72164`](https://youtrack.jetbrains.com/issue/KT-72164) K2.
IllegalArgumentException when pre and post increment are used
simultaneously in assignment
- [`KT-71174`](https://youtrack.jetbrains.com/issue/KT-71174) Illegal
scope used
- [`KT-72407`](https://youtrack.jetbrains.com/issue/KT-72407)
FirImplementationByDelegationWithDifferentGenericSignatureChecker:
FirLazyExpression should be calculated before accessing
- [`KT-72228`](https://youtrack.jetbrains.com/issue/KT-72228) K2:
Reformat doesn't work in project with Kotlin `2.0.21`
- [`KT-69671`](https://youtrack.jetbrains.com/issue/KT-69671) TYPES
phase contract violation through JavaSymbolProvider
- [`KT-71348`](https://youtrack.jetbrains.com/issue/KT-71348) K2:
KotlinIllegalStateExceptionWithAttachments: 'By now the annotations
argument mapping should have been resolved' during code inspection
- [`KT-72024`](https://youtrack.jetbrains.com/issue/KT-72024)
FirClassVarianceChecker: Expected FirResolvedTypeRef with ConeKotlinType
but was FirImplicitTypeRefImplWithoutSource
- [`KT-71746`](https://youtrack.jetbrains.com/issue/KT-71746) K2 IDE.
`ISE: Zero or multiple overrides found for descriptor in
FirRegularClassSymbol serializing/ExternalSerializer` and red code on
`@Serializer`(forClass) \` usage

##### Analysis API. Infrastructure

- [`KT-72922`](https://youtrack.jetbrains.com/issue/KT-72922)
KotlinFakeClsStubsCache project leakage
- [`KT-71988`](https://youtrack.jetbrains.com/issue/KT-71988) Improve
scripts test coverage by LL FIR
- [`KT-64687`](https://youtrack.jetbrains.com/issue/KT-64687) K2:
Analysis API: migrate AbstractFirLibraryModuleDeclarationResolveTest to
kotlin repo

##### Analysis API. Light Classes

- [`KT-73492`](https://youtrack.jetbrains.com/issue/KT-73492) K2. FP
error in Java file when using `@JvmSuppressWildcards` annotation without
arguments
- [`KT-66763`](https://youtrack.jetbrains.com/issue/KT-66763) K2: Get
rid of context receivers in Analysis API and LL API
- [`KT-71781`](https://youtrack.jetbrains.com/issue/KT-71781) SLC:
migrate SLC from KotlinModificationTrackerService to
KotlinModificationTrackerFactory
- [`KT-67963`](https://youtrack.jetbrains.com/issue/KT-67963) K2:
PsiInvalidElementAccessException on redeclaration of class with
constructor
- [`KT-71407`](https://youtrack.jetbrains.com/issue/KT-71407) K2: Do not
report `@JvmField` default value as PsiField initializer in K2
- [`KT-72078`](https://youtrack.jetbrains.com/issue/KT-72078) K2 PSI
change for constructor parameter with value class type

##### Analysis API. Providers and Caches

- [`KT-69247`](https://youtrack.jetbrains.com/issue/KT-69247) Analysis
API: Invalidate sessions after builtins modification events
- [`KT-72704`](https://youtrack.jetbrains.com/issue/KT-72704) ISE: No
'org.jetbrains.kotlin.fir.scopes.impl.FirDelegatedMembersFilter'(53) in
array owner: LLFirBuiltinsAndCloneableSession for Builtins for
JS/wasm-js (JS)
- [`KT-67148`](https://youtrack.jetbrains.com/issue/KT-67148) Analysis
API: Introduce a weak reference cache for the original `KtSymbol` in
`KtSymbolPointer`
- [`KT-73395`](https://youtrack.jetbrains.com/issue/KT-73395) Analysis
API: `JavaElementPsiSourceWithSmartPointer` contains strong references
to PSI
- [`KT-72390`](https://youtrack.jetbrains.com/issue/KT-72390) Kotlin
project full of red code
- [`KT-72388`](https://youtrack.jetbrains.com/issue/KT-72388)
KaFirStopWorldCacheCleaner: Control-flow exceptions
- [`KT-72644`](https://youtrack.jetbrains.com/issue/KT-72644) "PSI has
changed since creation" reason is misleading

##### Analysis API. Standalone

- [`KT-73776`](https://youtrack.jetbrains.com/issue/KT-73776) Analysis
API Standalone: Application services are missing registrations in tests
and Dokka
- [`KT-70346`](https://youtrack.jetbrains.com/issue/KT-70346) Analysis
API Standalone: Remove the custom class loader option in Standalone
session creation

##### Analysis API. Stubs and Decompilation

- [`KT-69398`](https://youtrack.jetbrains.com/issue/KT-69398) K2 IDE:
SOE on editing top level private variable name
- [`KT-72897`](https://youtrack.jetbrains.com/issue/KT-72897) Analysis
API: Smart PSI element pointers for `KtEnumEntry` stubs cannot be
restored
- [`KT-71565`](https://youtrack.jetbrains.com/issue/KT-71565)
KtClassOrObject should use isLocal from greenStub

##### Analysis API. Surface

##### New Features

- [`KT-73414`](https://youtrack.jetbrains.com/issue/KT-73414) Analysis
API: Support typealiased constructors in KaConstructorSymbol
- [`KT-70301`](https://youtrack.jetbrains.com/issue/KT-70301) Analysis
API: 'KaSamConstructorSymbol' does not allow to find the constructed SAM
type
- [`KT-68236`](https://youtrack.jetbrains.com/issue/KT-68236) Analysis
API: add `isExternal` property for KtPropertySymbol
- [`KT-68598`](https://youtrack.jetbrains.com/issue/KT-68598) Analysis
API: missed getClassLikeSymbolByClassId API

##### Performance Improvements

- [`KT-74112`](https://youtrack.jetbrains.com/issue/KT-74112) UI freeze:
`AnyThreadWriteThreadingSupport.getWritePermit`
- [`KT-73942`](https://youtrack.jetbrains.com/issue/KT-73942) Extend
resolveToSymbols cache to all references
- [`KT-73622`](https://youtrack.jetbrains.com/issue/KT-73622) Cache
`resolveToSymbols` result
- [`KT-72684`](https://youtrack.jetbrains.com/issue/KT-72684) Drop
explicit resolve from
KaFirJavaInteroperabilityComponent#asPsiTypeElement
- [`KT-60486`](https://youtrack.jetbrains.com/issue/KT-60486) Analysis
API: optimize KaExpressionTypeProvider.returnType for simple cases

##### Fixes

- [`KT-70114`](https://youtrack.jetbrains.com/issue/KT-70114) K2:
Analysis API: do not lazy resolve declarations without deprecation to
get it deprecation
- [`KT-73406`](https://youtrack.jetbrains.com/issue/KT-73406) \[Analysis
API] Allow extending KaModule resolution scope for all KaModules
- [`KT-65850`](https://youtrack.jetbrains.com/issue/KT-65850) Cover
Analysis API with KDocs
- [`KT-72099`](https://youtrack.jetbrains.com/issue/KT-72099) Analysis
API: implement an API to retrieve default imports
- [`KT-73662`](https://youtrack.jetbrains.com/issue/KT-73662)
KotlinIllegalArgumentExceptionWithAttachments: Expected
FirResolvedTypeRef with ConeKotlinType but was FirUserTypeRefImpl
- [`KT-70108`](https://youtrack.jetbrains.com/issue/KT-70108) Analysis
API: "KaScopeProvider.scopeContext" provides scopes from implicit
companion objects with inaccessible classifiers
- [`KT-68954`](https://youtrack.jetbrains.com/issue/KT-68954) Remove JAR
publications with old artifact names (high-level-api family)
- [`KT-70134`](https://youtrack.jetbrains.com/issue/KT-70134) Analysis
API: Port API documentation from the guide to KDoc
- [`KT-72973`](https://youtrack.jetbrains.com/issue/KT-72973) Introduce
KaSymbolOrigin.TYPE_ALIAS_CONSTRUCTOR
- [`KT-70356`](https://youtrack.jetbrains.com/issue/KT-70356)
analyzeCopy with IGNORE_SELF cannot find private members
- [`KT-66783`](https://youtrack.jetbrains.com/issue/KT-66783) Analysis
API: `KtFirSymbolProvider` creates symbols when given PSI from unrelated
modules
- [`KT-72937`](https://youtrack.jetbrains.com/issue/KT-72937) Migrate
KaFirReceiverParameterSymbol to KaFirSymbol/KaFirKtBasedSymbol
- [`KT-70243`](https://youtrack.jetbrains.com/issue/KT-70243) K2 IDE:
PsiMethod.callableSymbol returns `null` for constructor
- [`KT-66608`](https://youtrack.jetbrains.com/issue/KT-66608) Support
`OperatorFunctionChecks#isOperator` in AA
- [`KT-73068`](https://youtrack.jetbrains.com/issue/KT-73068) Analysis
API: A `KaFirJavaFieldSymbol` for a static Java field is open instead of
final
- [`KT-73055`](https://youtrack.jetbrains.com/issue/KT-73055) Get rid of
the deprecated Analysis API API
- [`KT-65065`](https://youtrack.jetbrains.com/issue/KT-65065) Provide
`KtTypeReference#getShortTypeText()`
- [`KT-63800`](https://youtrack.jetbrains.com/issue/KT-63800) AA: this
reference shortener doesn't simplify label
- [`KT-72793`](https://youtrack.jetbrains.com/issue/KT-72793) Analysis
API: 'expressionType' returns raw type for typealiased constructors
calls
- [`KT-72658`](https://youtrack.jetbrains.com/issue/KT-72658)
`resolveToCall` doesn't work for `KtSafeQualifiedExpression`
- [`KT-69930`](https://youtrack.jetbrains.com/issue/KT-69930) K2 IDE:
Kotlin/JS project: ISE: "Unsupported type DYNAMIC_TYPE"
- [`KT-71373`](https://youtrack.jetbrains.com/issue/KT-71373) Make
KaSessionProvider the internal API
- [`KT-71869`](https://youtrack.jetbrains.com/issue/KT-71869)
KaClassSymbol.superTypes for kotlin.Any contains kotlin.Any itself
(K1-only)
- [`KT-64190`](https://youtrack.jetbrains.com/issue/KT-64190) K2 IDE:
Analysis API: KDoc link leads to a function instead of interface
- [`KT-72075`](https://youtrack.jetbrains.com/issue/KT-72075)
`defaultType` should be available for `KaClassifierSymbol` instead of
`KaNamedClassSymbol`
- [`KT-72002`](https://youtrack.jetbrains.com/issue/KT-72002) Analysis
API: psi KaTypeParameterSymbol for default Java constructor is null

##### Backend. Native. Debug

- [`KT-73306`](https://youtrack.jetbrains.com/issue/KT-73306) Native:
add a way to specify a dir for the debug compilation unit file
- [`KT-68536`](https://youtrack.jetbrains.com/issue/KT-68536) Native:
bridges and trampolines affect stepping in the debugger
- [`KT-72398`](https://youtrack.jetbrains.com/issue/KT-72398) Native:
use `DW_AT_trampoline` for `objc2kotlin_*` functions instead of
`KonanHook` in `konan_lldb.py`

##### Backend. Wasm

- [`KT-71868`](https://youtrack.jetbrains.com/issue/KT-71868) K/Wasm:
support generating debug information in DWARF format
- [`KT-71645`](https://youtrack.jetbrains.com/issue/KT-71645) \[Wasm]
Check wasm test runner for groupByPackage=true case
- [`KT-72232`](https://youtrack.jetbrains.com/issue/KT-72232) Wasm, IC:
Compilation exception on renaming of file
- [`KT-73907`](https://youtrack.jetbrains.com/issue/KT-73907) Wasm:
Duplication of files in browser distribution
- [`KT-72223`](https://youtrack.jetbrains.com/issue/KT-72223) Compiler
generates an invalid glue-code for externals with backquoted identifiers
- [`KT-73015`](https://youtrack.jetbrains.com/issue/KT-73015) \[Wasm,
IC] Implement possibility for readonly IC cache
- [`KT-71763`](https://youtrack.jetbrains.com/issue/KT-71763) K/Wasm:
compiler generates incorrect code for is check on JsAny
- [`KT-72156`](https://youtrack.jetbrains.com/issue/KT-72156)
custom-formatters.js exists in JAR after publishToMavenLocal but not in
the published artifact in Maven public
- [`KT-71037`](https://youtrack.jetbrains.com/issue/KT-71037) \[Wasm,
IC] Investigate how make kotlin.test not fully loaded in IC

##### Compiler

##### New Features

- [`KT-74049`](https://youtrack.jetbrains.com/issue/KT-74049) Introduce
special override rule to allow overriding T! with T & Any
- [`KT-73256`](https://youtrack.jetbrains.com/issue/KT-73256) Implement
`all` meta-target for annotations
- [`KT-73255`](https://youtrack.jetbrains.com/issue/KT-73255) Change
defaulting rule for annotations
- [`KT-61447`](https://youtrack.jetbrains.com/issue/KT-61447) Support
context receivers overloads in Kotlin multiplatform
- [`KT-67034`](https://youtrack.jetbrains.com/issue/KT-67034) Warning
when a property hides a Java field from superclass
- [`KT-71092`](https://youtrack.jetbrains.com/issue/KT-71092) Native:
Write out used dependencies
- [`KT-71094`](https://youtrack.jetbrains.com/issue/KT-71094)
Kotlin/Native incremental compilation: fail compilation if cache build
failed
- [`KT-71569`](https://youtrack.jetbrains.com/issue/KT-71569) Improve
diagnostic precision for OPT_IN_ARGUMENT_IS_NOT_MARKER

##### Performance Improvements

- [`KT-73434`](https://youtrack.jetbrains.com/issue/KT-73434) Slow /
infinite compile involving ConeInferenceContext
- [`KT-73328`](https://youtrack.jetbrains.com/issue/KT-73328) Do not
spill `this` to a local variable in coroutines
- [`KT-69995`](https://youtrack.jetbrains.com/issue/KT-69995) K2: Slow
compilation when star projecting mutually recursive bounds from java
- [`KT-73687`](https://youtrack.jetbrains.com/issue/KT-73687)
Inefficient KtCommonFile#getFileAnnotationList
- [`KT-45452`](https://youtrack.jetbrains.com/issue/KT-45452) K/N
optimization: inline simple functions that aren't marked with `inline`
keyword
- [`KT-64898`](https://youtrack.jetbrains.com/issue/KT-64898) K2:
toFirProperty call in PsiRawFirBuilder forces AST loading
- [`KT-71673`](https://youtrack.jetbrains.com/issue/KT-71673) Consider
making EnhancementSymbolsCache. enhancedFunctions using simple cache
- [`KT-71973`](https://youtrack.jetbrains.com/issue/KT-71973)
KtPsiUtil#getEnclosingElementForLocalDeclaration shouldn't iterate over
directories

##### Fixes

- [`KT-75965`](https://youtrack.jetbrains.com/issue/KT-75965) The iOS
app did not run successfully in Release mode
- [`KT-57696`](https://youtrack.jetbrains.com/issue/KT-57696) Deprecate
JvmDefault annotation with level HIDDEN
- [`KT-75578`](https://youtrack.jetbrains.com/issue/KT-75578) K2: False
negative \[SUPER_CALL_WITH_DEFAULT_PARAMETERS] when calling the
upper-class implementation of a method with the default value argument
- [`KT-74764`](https://youtrack.jetbrains.com/issue/KT-74764) Native:
merge init nodes generated within the same LLVM module for the same klib
- [`KT-75444`](https://youtrack.jetbrains.com/issue/KT-75444)
Contradictions in the constraint system are ignored in case of multiple
constraints from forking points
- [`KT-75649`](https://youtrack.jetbrains.com/issue/KT-75649) K2: NPE on
assigning platform type value to non-nullable lateinit var
- [`KT-75483`](https://youtrack.jetbrains.com/issue/KT-75483) Native:
redundant unboxing generated with smart cast
- [`KT-73028`](https://youtrack.jetbrains.com/issue/KT-73028) K2.
FileAnalysisException on private property in Context
- [`KT-73937`](https://youtrack.jetbrains.com/issue/KT-73937) Context
parameters: IllegalArgumentException: source must not be null on
lateinit var with a context
- [`KT-74104`](https://youtrack.jetbrains.com/issue/KT-74104) Native:
SynchronizedLazyImpl produces NPE on 2.1.20-Beta1 on mingwX64
- [`KT-71752`](https://youtrack.jetbrains.com/issue/KT-71752) K2: Absent
non-null check for platform types in assignments
- [`KT-75526`](https://youtrack.jetbrains.com/issue/KT-75526) Regression
in K2 scripting: local name doesn't shadow one from the implicit
receiver
- [`KT-68131`](https://youtrack.jetbrains.com/issue/KT-68131) K2: build
Grazie monorepo main branch
- [`KT-72618`](https://youtrack.jetbrains.com/issue/KT-72618) Cannot
define operator inc/dec in class context
- [`KT-74739`](https://youtrack.jetbrains.com/issue/KT-74739) Native:
"IllegalArgumentException: All constructors should've been lowered:
FUNCTION_REFERENCE"
- [`KT-68768`](https://youtrack.jetbrains.com/issue/KT-68768) K2:
unsuccessful inference fork with jspecify annotations
- [`KT-71943`](https://youtrack.jetbrains.com/issue/KT-71943) K2: IAE
"source must not be null" in
FirJvmModuleAccessibilityQualifiedAccessChecker
- [`KT-75111`](https://youtrack.jetbrains.com/issue/KT-75111) False
negative "This declaration needs opt-in" for usage of enum entry with
OptIn marker in another module
- [`KT-73831`](https://youtrack.jetbrains.com/issue/KT-73831) Do not
choose `field` target in annotation classes
- [`KT-73494`](https://youtrack.jetbrains.com/issue/KT-73494) Enable
first-only-warn annotation defaulting mode
- [`KT-74929`](https://youtrack.jetbrains.com/issue/KT-74929) False
positive TYPE_PARAMETER_OF_PROPERTY_NOT_USED_IN_RECEIVER if it is used
with T\&Any
- [`KT-74227`](https://youtrack.jetbrains.com/issue/KT-74227) K2:
"Cannot infer type for this parameter. Please specify it explicitly"
caused by lambda in another lambda with a parameterized function type
- [`KT-70789`](https://youtrack.jetbrains.com/issue/KT-70789) CLI error
"mixing legacy and modern plugin arguments is prohibited" on using
-Xcompiler-plugin unless default scripting plugin is disabled
- [`KT-73903`](https://youtrack.jetbrains.com/issue/KT-73903) Design
'replaceWith' / 'test-only' kinds for the 'LanguageFeature' class
- [`KT-74474`](https://youtrack.jetbrains.com/issue/KT-74474) K2: Report
more precise diagnostic when last expression of non-unit lambda is a
statement
- [`KT-74478`](https://youtrack.jetbrains.com/issue/KT-74478) K2: False
negative RETURN TYPE_MISMATCH if the last statement of a lambda is
indexed assignment
- [`KT-67480`](https://youtrack.jetbrains.com/issue/KT-67480) K/N: a
separate inlining phase after the lowerings
- [`KT-72154`](https://youtrack.jetbrains.com/issue/KT-72154) Dokka
fails with `not array: KClass<out Annotation>` on Kotlin 2.1.20-dev with
`@SubclassOptInRequired`
- [`KT-72040`](https://youtrack.jetbrains.com/issue/KT-72040) Extra
checkers: false-positive unused parameter warnings on anonymous lambda
parameters
- [`KT-74203`](https://youtrack.jetbrains.com/issue/KT-74203) K2: False
negative NO_ELSE_IN_WHEN of a generic type with star projection <\*>
bounded by a sealed hierarchy
- [`KT-63720`](https://youtrack.jetbrains.com/issue/KT-63720) Coroutine
debugger: do not optimise out local variables
- [`KT-74024`](https://youtrack.jetbrains.com/issue/KT-74024) K2:
Prohibit declaring local type aliases
- [`KT-73146`](https://youtrack.jetbrains.com/issue/KT-73146) Context
parameters CLI & diagnostics
- [`KT-73251`](https://youtrack.jetbrains.com/issue/KT-73251) Warn users
about removal of context classes and constructors
- [`KT-72222`](https://youtrack.jetbrains.com/issue/KT-72222) Context
parameters parsing & resolution part 1
- [`KT-61175`](https://youtrack.jetbrains.com/issue/KT-61175) K2:
FirReceiverParameter does not extend FirDeclaration
- [`KT-73858`](https://youtrack.jetbrains.com/issue/KT-73858) Compose /
iOS: NullPointerException on building
- [`KT-73864`](https://youtrack.jetbrains.com/issue/KT-73864) \[Native]
Decouple `IrType.computePrimitiveBinaryTypeOrNull` from backend.native
- [`KT-73122`](https://youtrack.jetbrains.com/issue/KT-73122) Move the
upgrade references lowering to be first one in Native pipeline
- [`KT-73608`](https://youtrack.jetbrains.com/issue/KT-73608) K2:
"Initializer type mismatch" with map and typealias to object
- [`KT-73691`](https://youtrack.jetbrains.com/issue/KT-73691) DCE
removes static initializer function, which is actually called
- [`KT-74147`](https://youtrack.jetbrains.com/issue/KT-74147) K2: False
negative INCONSISTENT_TYPE_PARAMETER_VALUES
- [`KT-73454`](https://youtrack.jetbrains.com/issue/KT-73454) K2: Fix
type parameters mapping for typealiases with inner RHS
- [`KT-73043`](https://youtrack.jetbrains.com/issue/KT-73043) K2
Compiler does not allow references to inner constructors with
typealiases
- [`KT-74040`](https://youtrack.jetbrains.com/issue/KT-74040)
Compilation of inner class usage does not check the visibility of parent
class during compilation in different rounds
- [`KT-74195`](https://youtrack.jetbrains.com/issue/KT-74195) Fully
qualified names in error messages make them complicated
- [`KT-74221`](https://youtrack.jetbrains.com/issue/KT-74221) Make
`FirSupertypesChecker` a platform checker
- [`KT-72962`](https://youtrack.jetbrains.com/issue/KT-72962) Consider
enabling ConsiderForkPointsWhenCheckingContradictions LF earlier
- [`KT-74242`](https://youtrack.jetbrains.com/issue/KT-74242) Freeze on
`runCatching` call in `finally` block inside SAM conversion
- [`KT-29222`](https://youtrack.jetbrains.com/issue/KT-29222) FIR:
consider folding binary expression chains
- [`KT-73760`](https://youtrack.jetbrains.com/issue/KT-73760) Cannot
implement two Java interfaces with `@NotNull`-annotated type argument
and Kotlin's plain (nullable) type parameter
- [`KT-58933`](https://youtrack.jetbrains.com/issue/KT-58933) Applying
suggested signature from WRONG_NULLABILITY_FOR_JAVA_OVERRIDE leads to
red code
- [`KT-74107`](https://youtrack.jetbrains.com/issue/KT-74107) K2:
Calling type alias constructor with inner RHS in static scope causes
runtime crash
- [`KT-74244`](https://youtrack.jetbrains.com/issue/KT-74244) Context
parameters: context isn't checked for expect/actual property declaration
- [`KT-74276`](https://youtrack.jetbrains.com/issue/KT-74276) Update ASM
from 9.0 to 9.6.1
- [`KT-72737`](https://youtrack.jetbrains.com/issue/KT-72737) Avoid
function references creation in lowerings after
FunctionReferenceLowering
- [`KT-72295`](https://youtrack.jetbrains.com/issue/KT-72295) K2:
Generated accessors for delegated property should have property source
- [`KT-73150`](https://youtrack.jetbrains.com/issue/KT-73150)
Investigate/test approximation of context parameter type in completion
- [`KT-73862`](https://youtrack.jetbrains.com/issue/KT-73862) \[Native]
Decouple NativePreSerializationLoweringContext from backend.native
- [`KT-70507`](https://youtrack.jetbrains.com/issue/KT-70507) Should
parentheses prevent from plus/set operator desugaring?
- [`KT-72677`](https://youtrack.jetbrains.com/issue/KT-72677) K2 IDE /
Kotlin Debugger: “Couldn't find virtual file for p1/MainKt$foo$iface$1”
on evaluating inline function from another module
- [`KT-72672`](https://youtrack.jetbrains.com/issue/KT-72672) K2 IDE /
Kotlin Debugger: “Couldn't find virtual file” on evaluating inline
function for enum class entries from test module
- [`KT-73912`](https://youtrack.jetbrains.com/issue/KT-73912) Cannot
evaluate inline methods from another module in KMP project
- [`KT-73765`](https://youtrack.jetbrains.com/issue/KT-73765) K2:
Prohibit nested type aliases with inner RHS when it captures type
parameters implicitly
- [`KT-73869`](https://youtrack.jetbrains.com/issue/KT-73869) \[Native]
Move KonanSymbols out of `backend.native`
- [`KT-73823`](https://youtrack.jetbrains.com/issue/KT-73823)
Kotlin/Native: IndexOutOfBounds for java.util.Map::getOrDefault
- [`KT-73755`](https://youtrack.jetbrains.com/issue/KT-73755) K2: type
mismatch error contains unsubstituted type parameter types
- [`KT-72837`](https://youtrack.jetbrains.com/issue/KT-72837)
ERROR_IN_CONTRACT_DESCRIPTION message contains compiler internals
- [`KT-73771`](https://youtrack.jetbrains.com/issue/KT-73771) K2:
Infinite compilation caused by buildList without type
- [`KT-67520`](https://youtrack.jetbrains.com/issue/KT-67520) Change of
behaviour of inline function with safe cast on value type
- [`KT-67518`](https://youtrack.jetbrains.com/issue/KT-67518) Value
classes leak their carrier type implementation details via inlining
- [`KT-71767`](https://youtrack.jetbrains.com/issue/KT-71767) Generate
default compatibility bridges in -Xjvm-default=all/all-compatibility
mode
- [`KT-73716`](https://youtrack.jetbrains.com/issue/KT-73716) Context
parameters expose visibility
- [`KT-73671`](https://youtrack.jetbrains.com/issue/KT-73671) Context
parameters: val/var on context parameter on a property is possible
- [`KT-73510`](https://youtrack.jetbrains.com/issue/KT-73510) Context
parameters: It is possible to declare a context for init block
- [`KT-72305`](https://youtrack.jetbrains.com/issue/KT-72305) K2: Report
error when using synthetic properties in case of mapped collections
- [`KT-72429`](https://youtrack.jetbrains.com/issue/KT-72429)
StackOverflowError when compiling large files
- [`KT-72500`](https://youtrack.jetbrains.com/issue/KT-72500) K2
Debugger: NSME on evaluating lambda with a call to internal class field
- [`KT-73845`](https://youtrack.jetbrains.com/issue/KT-73845) K2:
IllegalArgumentException during FIR2IR transformation when processing
nested default values in annotations
- [`KT-73538`](https://youtrack.jetbrains.com/issue/KT-73538) K2 IDE /
Kotlin Debugger: ISE “couldn't find inline method" on evaluating
internal inline function with default arg from main module in test
module
- [`KT-73347`](https://youtrack.jetbrains.com/issue/KT-73347) K2:
Expected is FirResolvedDeclarationStatus
- [`KT-71226`](https://youtrack.jetbrains.com/issue/KT-71226) K2
Evaluator: Code fragment compilation with unresolved classes does not
fail with exception
- [`KT-73902`](https://youtrack.jetbrains.com/issue/KT-73902) Clean-up
code around lateinit inline/value classes
- [`KT-73693`](https://youtrack.jetbrains.com/issue/KT-73693) K2:
DslMarker checker doesn't report violation for callable reference with
bound receiver
- [`KT-73667`](https://youtrack.jetbrains.com/issue/KT-73667) K2:
DslMarker checker ignores function type annotations for invokeExtension
- [`KT-72797`](https://youtrack.jetbrains.com/issue/KT-72797) K2 IDE /
Kotlin Debugger: AE “No such value argument slot in IrCallImpl” on
evaluating inc()-operator for private field
- [`KT-68388`](https://youtrack.jetbrains.com/issue/KT-68388) Compiler
crash on convesion to fun interface with extension receiver
- [`KT-73801`](https://youtrack.jetbrains.com/issue/KT-73801) False
positive CONFLICTING_OVERLOADS between base suspend fun and derived
property
- [`KT-62833`](https://youtrack.jetbrains.com/issue/KT-62833) K2: Run
smoke FP tests with SLOW_ASSERTIONS enabled
- [`KT-54068`](https://youtrack.jetbrains.com/issue/KT-54068) Context
receivers with lambda nesting result in Type mismatch
- [`KT-51383`](https://youtrack.jetbrains.com/issue/KT-51383) Lambdas
with context receivers do not accept context receivers from scope
- [`KT-73331`](https://youtrack.jetbrains.com/issue/KT-73331) Context
parameters implicit invoke
- [`KT-73650`](https://youtrack.jetbrains.com/issue/KT-73650) Implement
DIFFERENT_NAMES_FOR_THE_SAME_PARAMETER_IN_SUPERTYPES for K2
- [`KT-73745`](https://youtrack.jetbrains.com/issue/KT-73745) Migrate
modularized tests to the latest stable version
- [`KT-70233`](https://youtrack.jetbrains.com/issue/KT-70233) Implement
a deprecation error for FIELD-targeted annotations on annotation
properties
- [`KT-72996`](https://youtrack.jetbrains.com/issue/KT-72996)
false-positive unresolved reference error on an overloaded callable
reference in a lambda return position on the left-hand size of an elvis
operator
- [`KT-73791`](https://youtrack.jetbrains.com/issue/KT-73791) Forbid
using `inline` and `value` class modifiers together
- [`KT-73704`](https://youtrack.jetbrains.com/issue/KT-73704) \[Native]
Decouple KonanIrLinker from cinterop deserialization
- [`KT-73641`](https://youtrack.jetbrains.com/issue/KT-73641) Context
parameters DSL marker support
- [`KT-59880`](https://youtrack.jetbrains.com/issue/KT-59880) K2:
Disappeared CONFLICTING_OVERLOADS
- [`KT-73339`](https://youtrack.jetbrains.com/issue/KT-73339) K2:
"VerifyError: Bad type on operand stack" because of missing implicit
cast on generic field receiver with star projection
- [`KT-72585`](https://youtrack.jetbrains.com/issue/KT-72585) K2:
Compilation failure when upgrading to Kotlin 2.0.20+: Cannot replace
top-level type with star projection: S
- [`KT-59443`](https://youtrack.jetbrains.com/issue/KT-59443) K2:
Implement missing K1 diagnostics
- [`KT-67517`](https://youtrack.jetbrains.com/issue/KT-67517) Value
class upcast to Any leaks carrier type interfaces
- [`KT-73527`](https://youtrack.jetbrains.com/issue/KT-73527) Prohibit
(via a deprecation warning) accessing nested class through generic outer
class
- [`KT-72852`](https://youtrack.jetbrains.com/issue/KT-72852)
JAVA_CLASS_ON_COMPANION compiler warning missing in K2
- [`KT-71704`](https://youtrack.jetbrains.com/issue/KT-71704) K2:
subAtom already initialized
- [`KT-73399`](https://youtrack.jetbrains.com/issue/KT-73399)
compile-time JVM codegen failure on a KProperty argument of a
KSuspendFunction parameter
- [`KT-72281`](https://youtrack.jetbrains.com/issue/KT-72281) K/N:
"Failed to wait for cache to be built"
- [`KT-73049`](https://youtrack.jetbrains.com/issue/KT-73049) Kotlin
Debugger: CNFE on evaluating local function inside lambda
- [`KT-72725`](https://youtrack.jetbrains.com/issue/KT-72725) KMP:
Unsupported actualization of inherited java field in expect class
- [`KT-73476`](https://youtrack.jetbrains.com/issue/KT-73476) K2:
Visibility of nested type aliases is not respected if RHS is inner
- [`KT-72957`](https://youtrack.jetbrains.com/issue/KT-72957) K2: Don't
use offsets for mapping annotations from IR plugins injected into
metadata
- [`KT-72814`](https://youtrack.jetbrains.com/issue/KT-72814) FIR: don't
use function references in FirThisReference
- [`KT-73143`](https://youtrack.jetbrains.com/issue/KT-73143) Context
parameters resolution leftovers
- [`KT-71649`](https://youtrack.jetbrains.com/issue/KT-71649) K2: Put
operator on mutableMap\<T?, V>() causes crashes on null key
- [`KT-72832`](https://youtrack.jetbrains.com/issue/KT-72832) Erroneous
implicit cast inserted by smartcast
- [`KT-72930`](https://youtrack.jetbrains.com/issue/KT-72930) K2 IDE /
Kotlin Debugger: ISE “couldn't find inline method” on evaluating
internal inline function from main module in test module
- [`KT-73095`](https://youtrack.jetbrains.com/issue/KT-73095) K2:
"Failed to find functional supertype for ConeIntersectionType"
- [`KT-70366`](https://youtrack.jetbrains.com/issue/KT-70366) K2:
"KotlinIllegalArgumentExceptionWithAttachments: Failed to find
functional supertype for class "
- [`KT-73260`](https://youtrack.jetbrains.com/issue/KT-73260) Rename
context receivers to context parameters in frontend
- [`KT-73375`](https://youtrack.jetbrains.com/issue/KT-73375) K2/JVM:
-Xuse-type-table generates incorrect metadata for local delegated
properties
- [`KT-72470`](https://youtrack.jetbrains.com/issue/KT-72470)
Annotations on effect declarations are unresolved
- [`KT-72409`](https://youtrack.jetbrains.com/issue/KT-72409) False
negative "Type parameter is forbidden for catch parameter"
- [`KT-72723`](https://youtrack.jetbrains.com/issue/KT-72723) K2:
Replace unused FIR properties required by inheritence with computed
properties
- [`KT-72246`](https://youtrack.jetbrains.com/issue/KT-72246) Exception
from FirReceiverAccessBeforeSuperCallChecker on red code
- [`KT-47289`](https://youtrack.jetbrains.com/issue/KT-47289) No error
on companion object inside inner class in enum constructor call
- [`KT-46120`](https://youtrack.jetbrains.com/issue/KT-46120) No error
reported when Java interface method is implemented by delegation to Java
class where corresponding method has different generic signature
- [`KT-72746`](https://youtrack.jetbrains.com/issue/KT-72746) K2: No IR
overriddens generated for Nothing.toString
- [`KT-70389`](https://youtrack.jetbrains.com/issue/KT-70389) K2:
StackOverflowError at
org.jetbrains.kotlin.fir.resolve.calls.CreateFreshTypeVariableSubstitutorStage.shouldBeFlexible
- [`KT-72537`](https://youtrack.jetbrains.com/issue/KT-72537) \[FIR
Analysis] 'IllegalArgumentException: source must not be null' when
typing '++++' (four pluses)
- [`KT-73010`](https://youtrack.jetbrains.com/issue/KT-73010) K2:
Refactor `DispatchReceiverMemberScopeTowerLevel.processMembers`
- [`KT-72924`](https://youtrack.jetbrains.com/issue/KT-72924) Extension
property declaration shouldn't be possible in when
- [`KT-72826`](https://youtrack.jetbrains.com/issue/KT-72826)
UNUSED_LAMBDA_EXPRESSION compiler warning missing in K2
- [`KT-25513`](https://youtrack.jetbrains.com/issue/KT-25513) Report
compilation error when in generated JVM bytecode there is a need for
CHECKCAST to inaccessible interface
- [`KT-73153`](https://youtrack.jetbrains.com/issue/KT-73153) K2:
Standalone diagnostics on type arguments are not reported
- [`KT-71252`](https://youtrack.jetbrains.com/issue/KT-71252) JVM: Set
the proper visibility to backing fields of lateinit properties
- [`KT-73213`](https://youtrack.jetbrains.com/issue/KT-73213) K2:
Initialize outer type parameter refs for inner (local) type aliases
during FIR building
- [`KT-73215`](https://youtrack.jetbrains.com/issue/KT-73215) Set up
isInner property for inner type aliases during FIR building
- [`KT-73088`](https://youtrack.jetbrains.com/issue/KT-73088) K2:
Introduce NestedTypeAliases experimental feature
- [`KT-73192`](https://youtrack.jetbrains.com/issue/KT-73192) K2:
FirJavaField has incorrect modality
- [`KT-60310`](https://youtrack.jetbrains.com/issue/KT-60310) K2:
introduce FirErrorContractDescription to distinguish unresolved contract
from error one
- [`KT-73008`](https://youtrack.jetbrains.com/issue/KT-73008) K2:
Resolve nested type aliases in derived classes
- [`KT-73009`](https://youtrack.jetbrains.com/issue/KT-73009) K2: Treat
nested type aliases as classes during supertypes resolution (they are
not inner by default)
- [`KT-59886`](https://youtrack.jetbrains.com/issue/KT-59886) K2:
Disappeared ERROR_IN_CONTRACT_DESCRIPTION
- [`KT-72839`](https://youtrack.jetbrains.com/issue/KT-72839) Rewrite
processConstraintStorageFromExpression using resolution atoms
- [`KT-73147`](https://youtrack.jetbrains.com/issue/KT-73147) Context
parameters FIR2IR support
- [`KT-72789`](https://youtrack.jetbrains.com/issue/KT-72789) Fix
inconsistent IR produced by ScriptsToClassesLowering for script instance
feature
- [`KT-66711`](https://youtrack.jetbrains.com/issue/KT-66711) K2:
INITIALIZER_TYPE_MISMATCH is reported on the whole lambda instead of
RETURN_TYPE_MISMATCH on each return expression
- [`KT-73011`](https://youtrack.jetbrains.com/issue/KT-73011) K2: Allow
overloads resolution for callable references based on expected type
variable with constraints
- [`KT-73031`](https://youtrack.jetbrains.com/issue/KT-73031) K2:
Callable reference unresolved inside elvis with a complex function type
- [`KT-66161`](https://youtrack.jetbrains.com/issue/KT-66161) K2:
False-positive REDUNDANT_VISIBILITY_MODIFIER for protected modifier in
private class
- [`KT-73065`](https://youtrack.jetbrains.com/issue/KT-73065) CCE with
context receivers
- [`KT-72345`](https://youtrack.jetbrains.com/issue/KT-72345) K2: Method
'get' without `@Override` annotation not called
- [`KT-69981`](https://youtrack.jetbrains.com/issue/KT-69981) K2:
Refactor ResolutionMode.WithExpectedType.expectedType to be a
ConeKotlinType
- [`KT-68363`](https://youtrack.jetbrains.com/issue/KT-68363)
`ABSTRACT_MEMBER_NOT_IMPLEMENTED` diagnostic reported only for the first
not implemented function
- [`KT-72105`](https://youtrack.jetbrains.com/issue/KT-72105) JVM:
typeOf() result is sometimes incorrectly optimized to null in bytecode
- [`KT-72813`](https://youtrack.jetbrains.com/issue/KT-72813) FIR: fix
containing declaration for annotations of a receiver parameter
- [`KT-72552`](https://youtrack.jetbrains.com/issue/KT-72552)
AutoboxingTransformer fails on during linkage on nested lambdas with
cinteroped types
- [`KT-71751`](https://youtrack.jetbrains.com/issue/KT-71751) K2:
Skipping code in last statement of lambda
- [`KT-72863`](https://youtrack.jetbrains.com/issue/KT-72863) K2: failed
compilation for a context receiver with an annotated type
- [`KT-68984`](https://youtrack.jetbrains.com/issue/KT-68984) K2:
Typealiased SAM constructors resolve to the expanded interface
- [`KT-57471`](https://youtrack.jetbrains.com/issue/KT-57471) K2: Wrong
diagnostics for named lambda arguments
- [`KT-69560`](https://youtrack.jetbrains.com/issue/KT-69560) Tidy up
test data that affected by `PrioritizedEnumEntries` or
`ProperUninitializedEnumEntryAccessAnalysis` features
- [`KT-72894`](https://youtrack.jetbrains.com/issue/KT-72894)
"Placeholder projection cannot be mapped." error from resolve when using
placeholder in a typealias
- [`KT-70886`](https://youtrack.jetbrains.com/issue/KT-70886) FIR/AA:
Reduce strong memory footprint of cached symbol names providers
- [`KT-72238`](https://youtrack.jetbrains.com/issue/KT-72238) Argument
type mismatch in builder inside extension function after ?:
- [`KT-72738`](https://youtrack.jetbrains.com/issue/KT-72738) Simplify
naming scheme for function references
- [`KT-72340`](https://youtrack.jetbrains.com/issue/KT-72340) K1/K2
difference in de-duplication of OPT_IN_USAGE and OPT_IN_TO_INHERITANCE
- [`KT-61272`](https://youtrack.jetbrains.com/issue/KT-61272) Frontend:
error message "feature ... is experimental and should be enabled
explicitly" does not explain how to do it
- [`KT-72664`](https://youtrack.jetbrains.com/issue/KT-72664) K2:
Function type kind is not propagated for parameters of incomplete calls
- [`KT-64247`](https://youtrack.jetbrains.com/issue/KT-64247) K2:
FirContextReceiver does not extend FirDeclaration
- [`KT-67383`](https://youtrack.jetbrains.com/issue/KT-67383) Incorrect
optimisation when optimising for loop with UByte
- [`KT-70975`](https://youtrack.jetbrains.com/issue/KT-70975) K2:
Confusing INVISIBLE_REFERENCE message when accessing nested class in
private-in-file class
- [`KT-72743`](https://youtrack.jetbrains.com/issue/KT-72743) CCE in
`FirUninitializedEnumChecker`: `FirPropertySymbol` cannot be cast to
`FirEnumEntrySymbol`
- [`KT-71708`](https://youtrack.jetbrains.com/issue/KT-71708) False
negative UNSUPPORTED for collection literals as trailing return value
- [`KT-67707`](https://youtrack.jetbrains.com/issue/KT-67707) K2: CCE
"ArrayMapImpl cannot be cast to class OneElementArrayMap" from FIR
evaluator
- [`KT-71966`](https://youtrack.jetbrains.com/issue/KT-71966) Seemingly
bug in SupertypeComputationSession#breakLoopFor
- [`KT-17455`](https://youtrack.jetbrains.com/issue/KT-17455) Confusing
error message "There's a cycle in the inheritance hierarchy for this
type" when outer class inherits nested class
- [`KT-71119`](https://youtrack.jetbrains.com/issue/KT-71119) K2:
"AssertionError: Should be primitive or nullable primitive type" caused
by comparing Double/Float and Any successor type
- [`KT-57527`](https://youtrack.jetbrains.com/issue/KT-57527) K1/K2:
"IllegalArgumentException: Some properties have the same names" with
inline class
- [`KT-57851`](https://youtrack.jetbrains.com/issue/KT-57851) Wrong
ValueClassRepresentation inside value class
- [`KT-67998`](https://youtrack.jetbrains.com/issue/KT-67998) K2:
CANNOT_INFER_PARAMETER_TYPE on incomplete call inside if in a Java SAM
- [`KT-71961`](https://youtrack.jetbrains.com/issue/KT-71961) K2
debugger evaluation ClassCastException in
IrElementsCreationUtilsKt#createFilesWithBuiltinsSyntheticDeclarationsIfNeeded
- [`KT-72504`](https://youtrack.jetbrains.com/issue/KT-72504) Optimize
`KotlinLocalVirtualFile.isDirectory` for parent virtual files
- [`KT-71399`](https://youtrack.jetbrains.com/issue/KT-71399) Kotlin
Script: NPE on type resolve
- [`KT-69283`](https://youtrack.jetbrains.com/issue/KT-69283) Incorrect
synthetic line numbers when inlining suspend funs
- [`KT-52929`](https://youtrack.jetbrains.com/issue/KT-52929) Java
cannot extend instantiations of generic Kotlin collections in the
presence of instantiated Kotlin subclasses
- [`KT-71885`](https://youtrack.jetbrains.com/issue/KT-71885) K2:
confusing message when lateinit var is assigned once
- [`KT-69920`](https://youtrack.jetbrains.com/issue/KT-69920) K2:
java.lang.IllegalArgumentException:
FirNamedArgumentExpressionImpl.replaceConeTypeOrNull() during Space
project compilation
- [`KT-55894`](https://youtrack.jetbrains.com/issue/KT-55894) NI:
Compile errors for out-projected types are more cryptic than previously
- [`KT-72231`](https://youtrack.jetbrains.com/issue/KT-72231) K2:
NONE_APPLICABLE instead of NAMED_ARGUMENTS_NOT_ALLOWED for non-Kotlin
functions with overloads
- [`KT-72422`](https://youtrack.jetbrains.com/issue/KT-72422) KMP:
False-positive report of ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT on
SublcassOptInRequired
- [`KT-72257`](https://youtrack.jetbrains.com/issue/KT-72257)
'javaClass' method cannot be evaluated in Kotlin project itself
- [`KT-72408`](https://youtrack.jetbrains.com/issue/KT-72408) Introduce
new TYPE_VARIANCE_CONFLICT diagnostics
- [`KT-71508`](https://youtrack.jetbrains.com/issue/KT-71508)
JAVA_CLASS_INHERITS_KT_PRIVATE_CLASS isn't reported when java class is
inherited from an effectively private class
- [`KT-72177`](https://youtrack.jetbrains.com/issue/KT-72177) K2:
Argument type mismatch when using star projection
- [`KT-72245`](https://youtrack.jetbrains.com/issue/KT-72245) K2: When
Java source roots are passed by file, fully qualified deep packages are
unresolved
- [`KT-63923`](https://youtrack.jetbrains.com/issue/KT-63923) Confusing
error messages for TYPE_MISMATCH from inference
- [`KT-57708`](https://youtrack.jetbrains.com/issue/KT-57708) Unclear
TYPE_MISMATCH messages in certain situations with generics
- [`KT-72178`](https://youtrack.jetbrains.com/issue/KT-72178) K2:
"Unexpected FirPlaceholderProjectionImpl" exception when using "\_" as
key type in EnumMap
- [`KT-62455`](https://youtrack.jetbrains.com/issue/KT-62455)
"NullPointerException" with 'multi-field value class'
- [`KT-72302`](https://youtrack.jetbrains.com/issue/KT-72302) K2: no
error on type operator in annotation parameter default value
- [`KT-72212`](https://youtrack.jetbrains.com/issue/KT-72212)
\[Scripting] Guava dependency is not packaged correctly
- [`KT-71662`](https://youtrack.jetbrains.com/issue/KT-71662) PCLA: a
type variable is not fixed on demand to a type containing a not-fixed
type variable
- [`KT-72229`](https://youtrack.jetbrains.com/issue/KT-72229) K2: Change
LV of ProhibitConstructorAndSupertypeOnTypealiasWithTypeProjection to
2.2
- [`KT-70256`](https://youtrack.jetbrains.com/issue/KT-70256) K2: Check
for `MISSING_BUILT_IN_DECLARATION` not only for JVM but for all
platforms
- [`KT-72173`](https://youtrack.jetbrains.com/issue/KT-72173) K2: simple
object names from root package are resolved without imports in non-root
packages when used as values
- [`KT-71480`](https://youtrack.jetbrains.com/issue/KT-71480)
JAVA_CLASS_INHERITS_KT_PRIVATE_CLASS isn't reported while java object
isn't created
- [`KT-60034`](https://youtrack.jetbrains.com/issue/KT-60034) K2:
Introduced NO_GET_METHOD
- [`KT-72199`](https://youtrack.jetbrains.com/issue/KT-72199) K1: Match
the shape of references to synthetic Java properties to the shape of
their getters
- [`KT-15672`](https://youtrack.jetbrains.com/issue/KT-15672) Improve
diagnostics for accessing Enum companion object from enum constructor
- [`KT-71321`](https://youtrack.jetbrains.com/issue/KT-71321) K2:
ClassCastException caused by missed type mismatch when passing a method
reference
- [`KT-72041`](https://youtrack.jetbrains.com/issue/KT-72041) Extra
checkers: false-positive unused parameter warnings on implicit lambda
parameters
- [`KT-71959`](https://youtrack.jetbrains.com/issue/KT-71959)
NO_VALUE_FOR_PARAMETER should use actual lambda parameter name
- [`KT-69985`](https://youtrack.jetbrains.com/issue/KT-69985) K2: simple
classifier names from root package are resolved without imports in
non-root packages
- [`KT-70139`](https://youtrack.jetbrains.com/issue/KT-70139) Remove
dependencies of debugger on K1 and old JVM backend
- [`KT-72142`](https://youtrack.jetbrains.com/issue/KT-72142) PSI:
unrelated enums are treated as equivalent
- [`KT-57358`](https://youtrack.jetbrains.com/issue/KT-57358) False
positive "Const 'val' initializer should be a constant value" caused by
equality with literals
- [`KT-71753`](https://youtrack.jetbrains.com/issue/KT-71753) PCLA:
false-negative operator ambiguity error on fixing a type variable on
demand for an operator assignment
- [`KT-70844`](https://youtrack.jetbrains.com/issue/KT-70844) K2 IDE:
deprecated marker shouldn't highlight not deprecated type argument
- [`KT-70854`](https://youtrack.jetbrains.com/issue/KT-70854) K2 IDE:
annotation on delegation causes illegal argument exception
- [`KT-56901`](https://youtrack.jetbrains.com/issue/KT-56901) NI:
Missing error on passing star-projection to reified type argument
- [`KT-70856`](https://youtrack.jetbrains.com/issue/KT-70856) K2:
IllegalStateException: Non-empty unresolved argument list
- [`KT-71897`](https://youtrack.jetbrains.com/issue/KT-71897) K2: Don't
erase in projections in SAM conversion if -Xsam-conversion=class like in
K1
- [`KT-66464`](https://youtrack.jetbrains.com/issue/KT-66464) Introduce
`isInlineable` parameter for `FunctionTypeKind`
- [`KT-71590`](https://youtrack.jetbrains.com/issue/KT-71590) K2: false
alarm from `UselessCallOnNotNullChecker`
- [`KT-71919`](https://youtrack.jetbrains.com/issue/KT-71919) Wrapped
ProcessCanceledException in GenerationState#loadClassBuilderInterceptors
- [`KT-70922`](https://youtrack.jetbrains.com/issue/KT-70922) PSI for
KtPropertyAccessor is inconsistent with KtNamedFunction
- [`KT-28598`](https://youtrack.jetbrains.com/issue/KT-28598) Type is
inferred incorrectly to Any on a deep generic type with out projection
- [`KT-71490`](https://youtrack.jetbrains.com/issue/KT-71490) K2:
missing REDUNDANT_ELSE_IN_WHEN
- [`KT-36107`](https://youtrack.jetbrains.com/issue/KT-36107) Remove
deprecated mod operator convention
- [`KT-71166`](https://youtrack.jetbrains.com/issue/KT-71166) Generic
synthetic property is unresolved when parameterized with Unit
- [`KT-71738`](https://youtrack.jetbrains.com/issue/KT-71738) K2: False
negative REDECLARATION inside object expression
- [`KT-59908`](https://youtrack.jetbrains.com/issue/KT-59908) K2:
Disappeared RECURSIVE_TYPEALIAS_EXPANSION
- [`KT-69937`](https://youtrack.jetbrains.com/issue/KT-69937) Define &
enforce user-friendly terminology for extended checkers
- [`KT-68834`](https://youtrack.jetbrains.com/issue/KT-68834)
Parentheses don't influence calls of any convention operators (except
invoke operator) after safe navigation operator
- [`KT-58437`](https://youtrack.jetbrains.com/issue/KT-58437) K2: Do not
use descriptors in KonanSymbols
- [`KT-18563`](https://youtrack.jetbrains.com/issue/KT-18563) Do not
generate inline reified functions as private in bytecode

##### Compose compiler

- [`b/397855145`](https://issuetracker.google.com/issues/397855145) Fix
"Unknown file" error in target annotation inference
- [`b/377499888`](https://issuetracker.google.com/issues/377499888)
Allow restarting overridden functions in a final class
- [`b/390151896`](https://issuetracker.google.com/issues/390151896) Fix
default arguments with varargs in `@Composable` functions
- [`b/388030459`](https://issuetracker.google.com/issues/388030459)
Kotlin compiler backend exception when lambda with anonymous object is
memoized
- [`b/310004740`](https://issuetracker.google.com/issues/310004740)
Check vararg parameter length in skipping logic
- [`b/393400768`](https://issuetracker.google.com/issues/393400768) Use
-1 for `.changed` call if nullable enum parameter is `null`
- [`b/388505454`](https://issuetracker.google.com/issues/388505454)
Change order of $changed bits with context parameters
- [`b/165812010`](https://issuetracker.google.com/issues/165812010)
Support default values in open `@Composable` functions (K2 only)
- [`b/285336821`](https://issuetracker.google.com/issues/285336821) Use
stability of parent class in stability inference
- [`b/353744956`](https://issuetracker.google.com/issues/353744956) Fix
context receiver/parameter handling in Compose
-   [`b/1

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4yMDcuMSIsInVwZGF0ZWRJblZlciI6IjM5LjIwNy4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-21 02:44:45 +00:00
renovate[bot]andGitHub 1fdc279751 chore(deps): update plugin licensee to v1.13.0 2025-03-20 19:36:30 +00:00
renovate[bot]andGitHub d4f7836c82 chore(deps): update plugin dependencyanalysis to v2.13.0 2025-03-20 06:51:11 +00:00
renovate[bot]andGitHub d1e480f9cf fix(deps): update haze to v1.5.1 2025-03-19 22:35:54 +00:00
renovate[bot]andGitHub 7b10eee986 chore(deps): update actions/upload-artifact action to v4.6.2 2025-03-19 19:24:53 +00:00
renovate[bot]andGitHub 8506a26a86 fix(deps): update sentry to v8.5.0 2025-03-18 22:13:43 +00:00
renovate[bot]andGitHub f09fe40b4c fix(deps): update sentry to v8.4.0 2025-03-14 15:05:41 +00:00
renovate[bot]andGitHub 69ba764e42 chore(deps): update plugin dependencyanalysis to v2.12.0 2025-03-13 19:23:39 +00:00
Harsh Shandilya ab675bdcc0 fix(database): enable write-ahead logging 2025-03-13 11:22:37 +05:30
renovate[bot]andGitHub 481749d17c fix(deps): update androidx 2025-03-12 19:19:57 +00:00
renovate[bot]andGitHub 5241ff672e chore(deps): update plugin dependencyanalysis to v2.11.0 2025-03-11 20:21:25 +00:00
Harsh Shandilya 0c58a2a0c2 refactor: simplify variant handling 2025-03-11 19:57:00 +05:30
Harsh Shandilya 52382d7918 fix: remove useless Sentry-related code 2025-03-11 19:27:29 +05:30
Harsh Shandilya 976c9dd064 feat: separate db reads and writes to separate dispatchers 2025-03-11 17:32:56 +05:30
Harsh Shandilya f818da6f64 fix(deps): upgrade to Invert 0.0.3-dev 2025-03-11 17:26:06 +05:30
Harsh Shandilya 1d291eacae chore: refresh baseline 2025-03-06 21:06:46 +05:30
renovate[bot]andGitHub bec7ca446b fix(deps): update dependency org.jsoup:jsoup to v1.19.1 2025-03-06 14:24:16 +00:00
renovate[bot]andGitHub 074eaeac37 fix(deps): update haze to v1.5.0 2025-03-06 10:11:35 +00:00
renovate[bot]andGitHub a6418ffe65 chore(deps): update plugin modulegraphassert to v2.8.0 2025-03-02 13:42:08 +00:00
renovate[bot]andGitHub 099c9086b1 chore(deps): update dependency de.mannodermaus.android-junit5 to v1.12.0.0 2025-03-01 06:36:32 +00:00
renovate[bot]andGitHub 0dc0a06edd fix(deps): update haze to v1.4.0 2025-02-28 10:49:27 +00:00
renovate[bot]andGitHub a5d8041f22 chore(deps): update plugin ksp to v2.1.10-1.0.31 2025-02-27 21:57:23 +00:00
renovate[bot]andGitHub 80dedc9c7a fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v5.3.0 2025-02-27 18:25:29 +00:00
renovate[bot]andGitHub 6608fe2c7f fix(deps): update androidx 2025-02-26 22:58:23 +00:00
renovate[bot]andGitHub 69b5467e32 fix(deps): update agp to v8.8.2 2025-02-26 19:14:09 +00:00
renovate[bot]andGitHub 6b63664353 fix(deps): update dependency io.sentry:sentry-android to v8.3.0 2025-02-26 14:30:52 +00:00
renovate[bot]andGitHub 5bda102f2d chore(deps): update dependency gradle to v8.13 2025-02-25 13:03:27 +00:00
renovate[bot]andGitHub a93e0f2f3b chore(deps): update plugin dependencyanalysis to v2.10.1 2025-02-22 02:25:40 +00:00
renovate[bot]andGitHub dfb6b1b9a4 chore(deps): update actions/upload-artifact action to v4.6.1 2025-02-21 21:41:59 +00:00
renovate[bot]andGitHub d32c5f9f9f chore(deps): update plugin dependencyanalysis to v2.10.0 2025-02-21 02:23:42 +00:00
renovate[bot]andGitHub 2583452d3d chore(deps): update plugin screenshot to v0.0.1-alpha09 2025-02-20 23:07:02 +00:00
renovate[bot]andGitHub 38c6867f83 chore(deps): update dependency gradle to v8.13-rc-2 2025-02-20 16:37:39 +00:00
renovate[bot]andGitHub 7194c09040 chore(deps): update plugin com.gradle.develocity to v3.19.2 2025-02-18 21:43:18 +00:00
Harsh Shandilya d47098d019 refactor(common): improve multipreview annotations 2025-02-16 18:57:23 +05:30
renovate[bot]andGitHub 75aaf928fe fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v5.2.0 2025-02-14 12:10:01 +00:00
renovate[bot]andGitHub 1fbfa13bbd chore(deps): update plugin ksp to v2.1.10-1.0.30 2025-02-14 07:23:48 +00:00
renovate[bot]andGitHub 7f89220bcb fix(deps): update agp to v8.8.1 2025-02-14 02:45:59 +00:00
Devon SawatskyandHarsh Shandilya bb11ede32a fix: rename updatedAt field to lastEditedAt for comments 2025-02-14 04:51:38 +05:30
Harsh Shandilya bff573ec10 Partially revert "fix(api): refresh post details golden snapshot"
There's a more complete patch available from the person who filed the issue

This partially reverts commit e81e28c395.
2025-02-14 04:51:11 +05:30
GitHub Actions 833fcb616d feat(release): start next development iteration 2025-02-13 22:16:04 +00:00
GitHub Actions 8ef213ffdb feat(release): bump version 2025-02-13 22:08:23 +00:00
Harsh Shandilya 6d8088e15f chore: update changelog 2025-02-14 03:29:09 +05:30
Harsh Shandilya e81e28c395 fix(api): refresh post details golden snapshot
Fixes #775
2025-02-14 03:26:58 +05:30
Harsh Shandilya 94ccda61f6 fix(api): refresh hottest posts golden snapshot 2025-02-14 03:26:50 +05:30
Harsh Shandilya 38830cee53 fix(api): refresh search page golden snapshot 2025-02-14 03:26:50 +05:30
Harsh Shandilya cdeac000ce fix(api): refresh CSRF golden snapshot 2025-02-14 03:26:46 +05:30
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>Harsh Shandilya
f488b34802 fix(deps): update androidx (#773)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
|
[androidx.paging:paging-compose](https://developer.android.com/jetpack/androidx/releases/paging#3.3.6)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `3.3.5` -> `3.3.6` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.paging:paging-compose/3.3.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/androidx.paging:paging-compose/3.3.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/androidx.paging:paging-compose/3.3.5/3.3.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.paging:paging-compose/3.3.5/3.3.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
|
[androidx.compose:compose-bom-alpha](https://developer.android.com/jetpack)
| `2025.01.01` -> `2025.02.00` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.compose:compose-bom-alpha/2025.02.00?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/androidx.compose:compose-bom-alpha/2025.02.00?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/androidx.compose:compose-bom-alpha/2025.01.01/2025.02.00?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.compose:compose-bom-alpha/2025.01.01/2025.02.00?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
|
[androidx.navigation.safeargs.kotlin](https://developer.android.com/jetpack/androidx/releases/navigation#2.8.7)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `2.9.0-alpha05` -> `2.9.0-alpha06` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.navigation.safeargs.kotlin:androidx.navigation.safeargs.kotlin.gradle.plugin/2.9.0-alpha06?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/androidx.navigation.safeargs.kotlin:androidx.navigation.safeargs.kotlin.gradle.plugin/2.9.0-alpha06?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/androidx.navigation.safeargs.kotlin:androidx.navigation.safeargs.kotlin.gradle.plugin/2.9.0-alpha05/2.9.0-alpha06?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.navigation.safeargs.kotlin:androidx.navigation.safeargs.kotlin.gradle.plugin/2.9.0-alpha05/2.9.0-alpha06?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
|
[androidx.navigation:navigation-compose](https://developer.android.com/jetpack/androidx/releases/navigation#2.8.7)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `2.9.0-alpha05` -> `2.9.0-alpha06` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.navigation:navigation-compose/2.9.0-alpha06?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/androidx.navigation:navigation-compose/2.9.0-alpha06?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/androidx.navigation:navigation-compose/2.9.0-alpha05/2.9.0-alpha06?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.navigation:navigation-compose/2.9.0-alpha05/2.9.0-alpha06?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
|
[androidx.lifecycle:lifecycle-viewmodel-compose](https://developer.android.com/jetpack/androidx/releases/lifecycle#2.8.7)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `2.9.0-alpha09` -> `2.9.0-alpha10` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.lifecycle:lifecycle-viewmodel-compose/2.9.0-alpha10?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/androidx.lifecycle:lifecycle-viewmodel-compose/2.9.0-alpha10?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/androidx.lifecycle:lifecycle-viewmodel-compose/2.9.0-alpha09/2.9.0-alpha10?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.lifecycle:lifecycle-viewmodel-compose/2.9.0-alpha09/2.9.0-alpha10?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
|
[androidx.baselineprofile](https://developer.android.com/jetpack/androidx/releases/benchmark#1.3.3)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.4.0-alpha07` -> `1.4.0-alpha08` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.baselineprofile:androidx.baselineprofile.gradle.plugin/1.4.0-alpha08?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/androidx.baselineprofile:androidx.baselineprofile.gradle.plugin/1.4.0-alpha08?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/androidx.baselineprofile:androidx.baselineprofile.gradle.plugin/1.4.0-alpha07/1.4.0-alpha08?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.baselineprofile:androidx.baselineprofile.gradle.plugin/1.4.0-alpha07/1.4.0-alpha08?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
|
[androidx.benchmark:benchmark-macro-junit4](https://developer.android.com/jetpack/androidx/releases/benchmark#1.3.3)
([source](https://cs.android.com/androidx/platform/frameworks/support))
| `1.4.0-alpha07` -> `1.4.0-alpha08` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.benchmark:benchmark-macro-junit4/1.4.0-alpha08?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/androidx.benchmark:benchmark-macro-junit4/1.4.0-alpha08?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/androidx.benchmark:benchmark-macro-junit4/1.4.0-alpha07/1.4.0-alpha08?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.benchmark:benchmark-macro-junit4/1.4.0-alpha07/1.4.0-alpha08?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xNjcuMSIsInVwZGF0ZWRJblZlciI6IjM5LjE2Ny4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Harsh Shandilya <me@msfjarvis.dev>
2025-02-13 14:38:36 +00:00
renovate[bot]andGitHub 40160777f6 fix(deps): update sentry to v8.2.0 2025-02-12 19:22:01 +00:00
Harsh Shandilya 2cae82b9be chore: upgrade to Gradle 8.13-rc-1 2025-02-12 18:24:39 +05:30
renovate[bot]andGitHub 64d424a1dd fix(deps): update haze to v1.3.1 2025-02-11 17:10:17 +00:00
renovate[bot]andGitHub 1aa632f5e3 chore(deps): update plugin dependencyanalysis to v2.8.2 2025-02-11 11:45:20 +00:00
renovate[bot]andGitHub 695b5d0661 chore(deps): update plugin dependencyanalysis to v2.8.1 2025-02-08 02:09:04 +00:00
renovate[bot]andGitHub ad3a2e05f0 fix(deps): update coil3 to v3.1.0 2025-02-05 01:44:31 +00:00
renovate[bot]andGitHub 75009f05a3 fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v5.1.0 2025-01-31 17:31:33 +00:00
GitHub Actions 0bb745b8e7 feat(release): start next development iteration 2025-01-31 12:58:20 +00:00
339 changed files with 74080 additions and 37933 deletions
-1
View File
@@ -2,4 +2,3 @@
**/*.html linguist-vendored
android/src/main/baseline-prof.txt linguist-generated -diff
android/src/main/generated/** linguist-generated -diff
**/screenshotTest/**/*.png filter=lfs diff=lfs merge=lfs -text
Binary file not shown.

Before

Width:  |  Height:  |  Size: 319 KiB

After

Width:  |  Height:  |  Size: 432 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 315 KiB

After

Width:  |  Height:  |  Size: 435 KiB

+9 -12
View File
@@ -8,8 +8,8 @@
],
"branchConcurrentLimit": 15,
"github-actions": {
"fileMatch": [
".github/reusable-workflows/.+\\.ya?ml$",
"managerFilePatterns": [
"/.github/reusable-workflows/.+\\.ya?ml$/",
],
},
"packageRules": [
@@ -54,23 +54,20 @@
"allowedVersions": "!/.*-1-8$/",
},
{
"matchPackageNames": [
"/^androidx.glance/",
"matchManagers": [
"github-actions"
],
"allowedVersions": "<=1.0.0",
},
{
"matchPackageNames": [
"/.*aboutlibraries.*/",
"matchDepNames": [
"msfjarvis/compose-lobsters"
],
"allowedVersions": "<11.3.0",
"enabled": false,
},
],
"customManagers": [
{
"customType": "regex",
"fileMatch": [
"SpotlessPlugin\.kt$"
"managerFilePatterns": [
"/SpotlessPlugin\.kt$/"
],
"matchStrings": [
"KTFMT_VERSION = \"(?<currentValue>.*)\""
@@ -25,21 +25,35 @@ runs:
using: "composite"
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: ${{ inputs.fetch-depth }}
token: ${{ inputs.token }}
lfs: true
persist-credentials: ${{ inputs.persist-credentials }}
- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6
- name: Set up JDK
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: temurin
java-version: 23
java-version: 25
- name: Accept Android SDK licenses
shell: bash
run: |
yes | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --licenses || true
- name: Set global Gradle properties
shell: bash
run: |
mkdir -p ~/.gradle/
echo "android.testoptions.manageddevices.emulator.gpu=swiftshader_indirect" >> ~/.gradle/gradle.properties
- name: Setup Gradle
uses: gradle/actions/setup-gradle@94baf225fe0a508e581a564467443d0e2379123b # v4
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6
with:
add-job-summary: always
cache-read-only: ${{ inputs.cache-read-only }}
+26 -7
View File
@@ -16,17 +16,17 @@ concurrency:
jobs:
check:
runs-on: macos-14
runs-on: macos-26
steps:
- name: Setup build environment
uses: msfjarvis/compose-lobsters/.github/reusable-workflows/setup-gradle@main
uses: msfjarvis/compose-lobsters/.github/reusable-workflows/setup-gradle@92aae743ab8e1c98741128f1bfda3715750f5cf6 # main
with:
fetch-depth: 0
cache-read-only: ${{ github.ref != 'refs/heads/main' }}
dependency-graph: generate-and-submit
- name: Check changelog format
uses: mindsers/changelog-reader-action@32aa5b4c155d76c94e4ec883a223c947b2f02656 # v2.2.3
uses: mindsers/changelog-reader-action@1faaf50aa09d5793d9a100819973df801febfb31 # v2.4.0
with:
path: ./CHANGELOG.md
validation_level: error
@@ -38,24 +38,43 @@ jobs:
- name: (Fail-only) Upload test report
if: failure()
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Test report
path: '**/build/reports/tests/**'
dependency-review:
runs-on: macos-14
runs-on: macos-26
if: "github.event_name == 'push' && github.event.ref != 'refs/heads/main'"
needs:
- check
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Dependency Review
uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4
uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
with:
base-ref: refs/heads/main
head-ref: ${{ github.ref }}
release-app:
runs-on: macos-26
needs:
- check
steps:
- name: Setup build environment
uses: msfjarvis/compose-lobsters/.github/reusable-workflows/setup-gradle@92aae743ab8e1c98741128f1bfda3715750f5cf6 # main
with:
cache-read-only: true
- name: Build release app
run: |
./scripts/setup-age.sh
./scripts/signing-setup.sh "$AGE_SECRET_KEY"
./gradlew --no-configuration-cache --stacktrace assembleRelease collectReleaseBundle
./scripts/signing-cleanup.sh
env:
AGE_SECRET_KEY: ${{ secrets.AGE_SECRET_KEY }}
+77 -14
View File
@@ -13,10 +13,10 @@ concurrency:
jobs:
publish-google-play-release:
runs-on: macos-14
runs-on: macos-26
steps:
- name: Setup build environment
uses: msfjarvis/compose-lobsters/.github/reusable-workflows/setup-gradle@main
uses: msfjarvis/compose-lobsters/.github/reusable-workflows/setup-gradle@92aae743ab8e1c98741128f1bfda3715750f5cf6 # main
with:
token: ${{ secrets.POST_RELEASE_GH_TOKEN }}
fetch-depth: 0
@@ -40,8 +40,10 @@ jobs:
shell: bash
run: |
./gradlew -q --no-configuration-cache clearPreRelease
VERSION="$(tail -n1 android/version.properties | cut -d = -f 2)"
VERSION="$(grep 'versioning-plugin.versionName' android/version.properties | cut -d = -f 2)"
VERSION_CODE="$(grep 'versioning-plugin.versionCode' android/version.properties | cut -d = -f 2)"
echo VERSION="${VERSION}" >> $GITHUB_ENV
echo VERSION_CODE="${VERSION_CODE}" >> $GITHUB_ENV
- name: Update changelog
uses: msfjarvis/keep-a-changelog-new-release@e60399f488b25149c0963a4e22fff928a9277ce7 # v2.2.0
@@ -49,30 +51,68 @@ jobs:
tag: v${{ env.VERSION }}
version: ${{ env.VERSION }}
- name: Get changelog entry
id: changelog_reader
uses: mindsers/changelog-reader-action@1faaf50aa09d5793d9a100819973df801febfb31 # v2.4.0
with:
version: ${{ env.VERSION }}
path: ./CHANGELOG.md
- name: Write changelog to fastlane metadata
shell: bash
env:
CHANGELOG: ${{ steps.changelog_reader.outputs.changes }}
run: |
mkdir -p fastlane/metadata/android/en-US/changelogs
printf '%s\n' "${CHANGELOG}" > fastlane/metadata/android/en-US/changelogs/${VERSION_CODE}.txt
- name: Commit changes
shell: bash
run: |
git commit -am 'feat(release): bump version'
git add .
git commit -m 'feat(release): bump version'
- name: Calculate VCS info for Sentry
shell: bash
run: |
# Get HEAD commit SHA
HEAD_SHA=$(git rev-parse HEAD)
echo "SENTRY_VCS_HEAD_SHA=${HEAD_SHA}" >> $GITHUB_ENV
# Get parent info (format: commit parent1 [parent2...])
PARENTS=$(git rev-list --parents -n 1 HEAD)
PARENT_COUNT=$(echo "$PARENTS" | wc -w)
if [ "$PARENT_COUNT" -ge 3 ]; then
# Merge commit (commit + 2+ parents): use first parent
BASE_SHA=$(echo "$PARENTS" | awk '{print $2}')
else
# Normal commit: use last release
LAST_TAG=$(gh release list --limit 1 --json tagName -q '.[0].tagName' || echo "")
if [ -n "$LAST_TAG" ]; then
BASE_SHA=$(git rev-parse "${LAST_TAG}^{commit}")
else
BASE_SHA="${HEAD_SHA}"
fi
fi
echo "SENTRY_VCS_BASE_SHA=${BASE_SHA}" >> $GITHUB_ENV
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build release assets
shell: bash
env:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_VCS_HEAD_SHA: ${{ env.SENTRY_VCS_HEAD_SHA }}
SENTRY_VCS_BASE_SHA: ${{ env.SENTRY_VCS_BASE_SHA }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
./gradlew --no-configuration-cache assembleRelease collectReleaseApks collectReleaseBundle -PenableSentry
./gradlew --no-configuration-cache assembleRelease uploadSentryProguardMappingsRelease collectReleaseApks collectReleaseBundle -PenableSentry
- name: Clean secrets
run: scripts/signing-cleanup.sh
- name: Get changelog entry
id: changelog_reader
uses: mindsers/changelog-reader-action@32aa5b4c155d76c94e4ec883a223c947b2f02656 # v2.2.3
with:
version: ${{ env.VERSION }}
path: ./CHANGELOG.md
- name: Write release notes file
shell: bash
env:
@@ -82,7 +122,7 @@ jobs:
printf '%s' "${CHANGELOG}" >> distribution/whatsnew/whatsnew-en-GB
- name: Publish bundle to Google Play
uses: r0adkll/upload-google-play@935ef9c68bb393a8e6116b1575626a7f5be3a7fb # v1.1.3
uses: r0adkll/upload-google-play@e738b9dd8f2476ea806d921b64aacd24f34515a5 # v1.1.5
with:
mappingFile: android/bundle/mapping.txt
packageName: dev.msfjarvis.claw.android
@@ -104,8 +144,31 @@ jobs:
# Push the tag to GitHub
git push origin "v${VERSION}"
# Create GitHub release notes with footer
RELEASE_NOTES=$(mktemp)
{
cat distribution/whatsnew/whatsnew-en-GB
echo ""
echo "---"
echo ""
echo '<a href="https://play.google.com/store/apps/details?id=dev.msfjarvis.claw.android">'
echo ' <img src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png"'
echo ' alt="Get it on Google Play"'
echo ' height="80" />'
echo '</a>'
echo ""
echo '<a href="https://android.izzysoft.de/repo/apk/dev.msfjarvis.claw.android">'
echo ' <img src="./.github/izzy-badge.webp"'
echo ' alt="Get it on IzzyOnDroid"'
echo ' height="65" />'
echo '</a>'
} > "$RELEASE_NOTES"
# Create a GitHub release
gh release create "v${VERSION}" --notes-file distribution/whatsnew/whatsnew-en-GB --title "v${VERSION}" ./android/apk/*.apk ./android/bundle/*.aab
gh release create "v${VERSION}" --notes-file "$RELEASE_NOTES" --title "v${VERSION}" ./android/apk/*.apk ./android/bundle/*.aab
# Clean up temp file
rm "$RELEASE_NOTES"
# Start the next development iteration
./gradlew -q --no-configuration-cache bumpSnapshot
+44 -13
View File
@@ -3,39 +3,70 @@ name: Android release build
on:
workflow_dispatch:
inputs:
versionName:
description: 'User-facing release version name'
required: true
default: ""
versionCode:
description: 'versionCode or build number'
required: true
default: ''
tramline-input:
description: "Tramline input"
required: false
jobs:
signed-build:
runs-on: ubuntu-latest
steps:
- name: Setup build environment
uses: msfjarvis/compose-lobsters/.github/reusable-workflows/setup-gradle@main
uses: msfjarvis/compose-lobsters/.github/reusable-workflows/setup-gradle@92aae743ab8e1c98741128f1bfda3715750f5cf6 # main
with:
cache-read-only: true
fetch-depth: 0
- name: Configure Tramline
id: tramline
uses: tramlinehq/deploy-action@58cc98c188b9eda853e549ede6a8b2f58fe29516 # v0.1.7
with:
input: ${{ github.event.inputs.tramline-input }}
- name: Calculate VCS info for Sentry
shell: bash
run: |
# Get HEAD commit SHA
HEAD_SHA=$(git rev-parse HEAD)
echo "SENTRY_VCS_HEAD_SHA=${HEAD_SHA}" >> $GITHUB_ENV
# Get parent info (format: commit parent1 [parent2...])
PARENTS=$(git rev-list --parents -n 1 HEAD)
PARENT_COUNT=$(echo "$PARENTS" | wc -w)
if [ "$PARENT_COUNT" -ge 3 ]; then
# Merge commit (commit + 2+ parents): use first parent
BASE_SHA=$(echo "$PARENTS" | awk '{print $2}')
else
# Normal commit: use last release
LAST_TAG=$(gh release list --limit 1 --json tagName -q '.[0].tagName' || echo "")
if [ -n "$LAST_TAG" ]; then
BASE_SHA=$(git rev-parse "${LAST_TAG}^{commit}")
else
BASE_SHA="${HEAD_SHA}"
fi
fi
echo "SENTRY_VCS_BASE_SHA=${BASE_SHA}" >> $GITHUB_ENV
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build release app
run: |
./scripts/setup-age.sh
./scripts/signing-setup.sh "$AGE_SECRET_KEY"
./gradlew --no-configuration-cache --stacktrace assembleRelease collectReleaseBundle -PenableSentry
./gradlew --no-configuration-cache --stacktrace assembleRelease uploadSentryProguardMappingsRelease collectReleaseBundle -PenableSentry
./scripts/signing-cleanup.sh
env:
AGE_SECRET_KEY: ${{ secrets.AGE_SECRET_KEY }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
ORG_GRADLE_PROJECT_VERSION_NAME: ${{ github.event.inputs.versionName }}
ORG_GRADLE_PROJECT_VERSION_CODE: ${{ github.event.inputs.versionCode }}
SENTRY_VCS_HEAD_SHA: ${{ env.SENTRY_VCS_HEAD_SHA }}
SENTRY_VCS_BASE_SHA: ${{ env.SENTRY_VCS_BASE_SHA }}
ORG_GRADLE_PROJECT_VERSION_NAME: ${{ steps.tramline.outputs.version_name }}
ORG_GRADLE_PROJECT_VERSION_CODE: ${{ steps.tramline.outputs.version_code }}
- name: Upload app bundle
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-aab
path: android/build/outputs/bundle/release/android-release.aab
+1
View File
@@ -248,3 +248,4 @@ distribution/
screenshots/
claw-export.json
.kotlin/
.worktrees/
+139 -5
View File
@@ -7,6 +7,130 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [1.65.0] - 2026-05-29
### Fixed
- Address fallout from Lobste.rs changing its HTML markup. This will keep happening :(
### Changed
- The comment expand/collapse indicators now match the Lobsters UI
## [1.64.0] - 2026-05-26
### Added
- Allow logging in to avoid rate limiting problems.
### Changed
- The migration path for tag data format changes in 1.61.0 has been removed.
- The app no longer uses the unsupported JSON endpoints on lobste.rs, instead relying
on parsing the HTML pages instead.
## [1.63.0] - 2026-04-02
### Fixed
- Comments are no longer indicated as unread on the first visit of a post
### Changed
- Post titles in the list view can now take up 2 lines to make ellipsizing less likely
- Action buttons now have a prominent background to make their touch target more obvious
- Action buttons now have additional padding to avoid accidental presses when interacting
with system gestures
Thanks to Charles Lombardo for contributing the action button UI changes.
## [1.62.0] - 2026-03-10
### Fixed
- Improve comment scrolling performance
- Ensure saved/read state for posts updates properly in UI
- All strings in the app are now translatable, open to volunteer contributions.
## [1.61.0] - 2026-02-28
### Fixed
- Collapsed comments state are now persisted across navigation events
### Changed
- Tags use a more muted background color to avoid being distracting.
- Tag blocks can now be time-bound
## [1.60.0] - 2026-02-14
### Fixed
- Rare crash in widget handling
- Line-breaking behavior in comments now packs text densely
- Tag filtering now applies to widgets as well
### Changed
- Links to other posts now open in the app itself
- Widget handles bad network scenarios better
## [1.59.0] - 2026-01-09
### Fixed
- Crashes from process death
- Crashes in widget handling
- Android Auto Backup works again
### Changed
- Added a tag filtering option to settings
- Comment count badge has been restored
- Navigation behavior has been improved to avoid too-deep hierarchies
- Widget clicks are handled reliably
- Widget UI has been touched up and a preview added for Android 16+
- User profile page is now scrollable
## [1.58.0] - 2025-12-26
### Fixed
- Avoid crash from trying to import invalid backups
- Avoid crash when posts move between pages
### Changed
- Widget update is more reliable
- Increased network timeouts
- Better network retries and rate limit avoidance
- Temporarily remove comment count to fix crashes
## [1.57.0] - 2025-12-07
### Changed
- Navigation rewritten for larger screens
- Comment expand/collapse now uses button UI
- Comment rendering rewritten for better performance
- Settings page redesigned
### Fixed
- Database concurrency improvements
- Upgrade to Compose 2025-12 alpha
- Swipe action trigger distance increased for Android 16 QPR1 Beta 1
- Deeply nested comment tree collapsing
- Post author indication in details
- Dark mode toggle crash
## [1.55.0] - 2025-02-13
### Fixed
- Adapt to lobsters API change
## [1.54.0] - 2025-01-31
### Fixed
@@ -74,7 +198,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Try to workaround an infrequent crash when scrolling hottest/newest posts
- Try to work around an infrequent crash when scrolling hottest/newest posts
### Changed
@@ -119,7 +243,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed a crash when clicking an item on the bottom navigation bar
too quickly
- Removed buggy deeplinks
- Removed buggy deep links
- Clicking a username now correctly navigates to the right page in-app
## [1.44.0] - 2024-03-19
@@ -376,7 +500,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Fix bug where newest posts tab actually showed hottest posts instead
- Fix bug where the newest posts tab actually showed the hottest posts instead
## [1.15.0] - 2022-12-20
@@ -398,7 +522,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Fix crash when viewing [jcs](https://lobste.rs/u/jcs)' comments
- Fix crash when viewing comments by ~jcs
## [1.12.0] - 2022-12-09
@@ -497,7 +621,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Initial Play Store release
[Unreleased]: https://github.com/msfjarvis/compose-lobsters/compare/v1.54.0...HEAD
[Unreleased]: https://github.com/msfjarvis/compose-lobsters/compare/v1.65.0...HEAD
[1.65.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.64.0...v1.65.0
[1.64.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.63.0...v1.64.0
[1.63.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.62.0...v1.63.0
[1.62.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.61.0...v1.62.0
[1.61.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.60.0...v1.61.0
[1.60.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.59.0...v1.60.0
[1.59.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.58.0...v1.59.0
[1.58.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.57.0...v1.58.0
[1.57.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.55.0...v1.57.0
[1.55.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.54.0...v1.55.0
[1.54.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.53.0...v1.54.0
[1.53.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.52.0...v1.53.0
[1.52.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.51.0...v1.52.0
+3
View File
@@ -2,6 +2,9 @@
Unofficial Android app for read-only access to [lobste.rs](https://lobste.rs), built with [Jetpack Compose](https://developer.android.com/jetpack/compose).
> [!WARNING]
> This app uses a JSON API that Lobsters does not expose by choice and [the maintainer has no interest in having](https://github.com/lobsters/lobsters/issues/1663#issuecomment-3074472781), so it may break in any manner at any point in time. File an issue [here](https://github.com/msfjarvis/compose-lobsters/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen) if you come across any bugs, do not bother the upstream developers about a feature they don't support.
<a href="https://play.google.com/store/apps/details?id=dev.msfjarvis.claw.android">
<img src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png"
alt="Get it on Google Play"
+44 -48
View File
@@ -6,35 +6,27 @@
*/
@file:Suppress("UnstableApiUsage")
import com.android.build.api.dsl.ApplicationExtension
import dev.msfjarvis.claw.gradle.addTestDependencies
import org.jetbrains.kotlin.compose.compiler.gradle.ComposeFeatureFlag
import dev.zacsweers.metro.gradle.DiagnosticSeverity
plugins {
id("dev.msfjarvis.claw.android-application")
id("dev.msfjarvis.claw.rename-artifacts")
id("dev.msfjarvis.claw.kotlin-android")
id("dev.msfjarvis.claw.kotlin-kapt")
id("dev.msfjarvis.claw.sentry")
id("dev.msfjarvis.claw.versioning-plugin")
id("kotlin-parcelize")
alias(libs.plugins.aboutlibraries)
alias(libs.plugins.android.junit5)
alias(libs.plugins.anvil)
alias(libs.plugins.modulegraphassert)
alias(libs.plugins.whetstone)
alias(libs.plugins.baselineprofile)
alias(libs.plugins.licensee)
alias(libs.plugins.tracelog)
alias(libs.plugins.kotlin.composeCompiler)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.navigation.safeargs)
alias(libs.plugins.screenshot)
alias(libs.plugins.dependencyAnalysis)
alias(libs.plugins.metro)
}
// Directly using the generated `android` accessor lights up bright red
extensions.configure<ApplicationExtension> {
android {
namespace = "dev.msfjarvis.claw.android"
defaultConfig.applicationId = "dev.msfjarvis.claw.android"
defaultConfig.testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
@@ -45,87 +37,93 @@ extensions.configure<ApplicationExtension> {
applicationIdSuffix = ".internal"
isDebuggable = true
}
experimentalProperties["android.experimental.enableScreenshotTest"] = true
}
aboutLibraries.gitHubApiToken = providers.environmentVariable("GITHUB_TOKEN").orNull
aboutLibraries.collect.gitHubApiToken = providers.environmentVariable("GITHUB_TOKEN").orNull
baselineProfile {
mergeIntoMain = true
saveInSrc = true
// dependencyProject is deprecated, needs new APIs in the baseline profile plugin.
@Suppress("deprecation") from(projects.benchmark.dependencyProject)
}
composeCompiler {
featureFlags.addAll(
ComposeFeatureFlag.OptimizeNonSkippingGroups,
ComposeFeatureFlag.PausableComposition,
)
}
licensee {
allow("Apache-2.0")
allow("MIT")
allow("BSD-3-Clause")
ignoreDependencies("com.michael-bull.kotlin-result") { because("kotlin-result is ISC licensed") }
ignoreDependencies("org.commonmark") { because("Commonmark is BSD licensed") }
allowUrl("https://jsoup.org/license") { because("Jsoup is MIT licensed") }
allowUrl("https://opensource.org/license/MIT") { because("Ksoup is MIT licensed") }
}
metro { unusedGraphInputsSeverity = DiagnosticSeverity.ERROR }
moduleGraphAssert {
assertOnAnyBuild = true
maxHeight = 4
restricted = arrayOf(":core -X> :.*")
}
whetstone {
addOns {
compose.set(true)
workManager.set(true)
}
}
dependencies {
implementation(platform(libs.androidx.compose.bom))
baselineProfile(projects.benchmark)
implementation(platform(libs.okhttp.bom))
implementation(libs.aboutLibraries.compose.core)
implementation(libs.aboutLibraries.core)
implementation(libs.aboutLibraries.m3)
implementation(libs.androidx.activity)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.compose.animation)
implementation(libs.androidx.compose.animation.core)
implementation(libs.androidx.compose.foundation)
implementation(libs.androidx.compose.foundation.layout)
implementation(libs.androidx.compose.glance)
implementation(libs.androidx.compose.glance.core)
implementation(libs.androidx.compose.glance.m3)
implementation(libs.androidx.compose.material.icons.core)
implementation(libs.androidx.compose.material.icons.extended)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.material3.window.size)
implementation(libs.androidx.compose.material3.adaptive)
implementation(libs.androidx.compose.material3.adaptive.layout)
implementation(libs.androidx.compose.material3.adaptive.navigation)
implementation(libs.androidx.compose.material3.adaptive.navigation.suite)
implementation(libs.androidx.compose.material3.window.size)
implementation(libs.androidx.compose.runtime)
implementation(libs.androidx.compose.runtime.saveable)
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.graphics)
implementation(libs.androidx.compose.ui.text)
implementation(libs.androidx.compose.ui.util)
implementation(libs.androidx.compose.ui.unit)
implementation(libs.androidx.annotation)
implementation(libs.androidx.collection)
implementation(libs.androidx.core)
implementation(libs.androidx.core.splashscreen)
implementation(libs.androidx.lifecycle.common)
implementation(libs.androidx.lifecycle.compose)
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.lifecycle.runtime.compose)
implementation(libs.androidx.lifecycle.viewmodel)
implementation(libs.androidx.material3.navigation3)
implementation(libs.androidx.navigation3.runtime)
implementation(libs.androidx.navigation3.ui)
implementation(libs.androidx.paging.common)
implementation(libs.androidx.paging.compose)
implementation(libs.androidx.profileinstaller)
implementation(libs.androidx.work.runtime)
implementation(libs.copydown)
implementation(libs.dagger)
implementation(libs.eithernet)
implementation(libs.haze)
implementation(libs.haze.materials)
implementation(libs.javax.inject)
implementation(libs.haze.blur)
implementation(libs.kotlin.parcelize.runtime)
implementation(libs.kotlinx.collections.immutable)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.serialization.core)
implementation(libs.kotlinx.serialization.json)
implementation(libs.napier)
implementation(libs.kotlin.stdlib)
implementation(libs.metrox.android)
implementation(libs.metrox.viewmodel)
implementation(libs.metrox.viewmodel.compose)
implementation(libs.okhttp.core)
implementation(libs.okhttp.loggingInterceptor)
implementation(libs.retrofit)
implementation(libs.sentry)
implementation(libs.sentry.android.core)
implementation(libs.sqldelight.extensions.coroutines)
implementation(libs.sqldelight.runtime)
implementation(libs.swipe)
implementation(libs.unfurl)
implementation(projects.api)
@@ -134,14 +132,12 @@ dependencies {
implementation(projects.database.core)
implementation(projects.database.impl)
implementation(projects.model)
implementation(projects.web)
kapt(libs.dagger.compiler)
compileOnly(libs.androidx.compose.glance.preview)
screenshotTestImplementation(libs.androidx.compose.ui.tooling)
debugRuntimeOnly(libs.androidx.compose.glance)
runtimeOnly(libs.androidx.profileinstaller)
addTestDependencies(project)
androidTestImplementation(libs.androidx.test.espresso.core)
androidTestImplementation(libs.androidx.test.uiautomator)
androidTestImplementation(libs.leakcanary.android.test)
}
+118 -8
View File
@@ -1,15 +1,125 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.7.1" type="baseline" client="gradle" dependencies="true" name="AGP (8.7.1)" variant="all" version="8.7.1">
<issues format="6" by="lint 9.2.1" type="baseline" client="gradle" dependencies="true" name="AGP (9.2.1)" variant="all" version="9.2.1">
<issue
id="VectorPath"
message="Very long vector path (918 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
errorLine1=" android:pathData=&quot;M431.3,341.2C435.9,337 580.3,236 710.5,223.8C747.3,220.3 790.7,254.7 833.2,312.5C875.9,370.7 892.9,433.4 875.9,443C863.2,450.2 838.6,407.4 758.8,396.3C743.4,394.2 730.4,393.4 719.4,393.5C738.5,423.4 745.6,451.1 737.2,458C733.1,461.4 673.6,438.7 612.9,436.5C570.1,435 535.2,440.9 506.7,451.3C496.9,454.9 486,453.8 477.2,448.3C444.5,427.9 424.2,406.2 418.4,376.2C415.8,362.8 420.9,349.4 431.3,341.2ZM274.7,394.8C285.8,370.1 312.2,325.9 346.3,305.4C383.6,282.9 335,390.3 427.3,489.8C519.5,589.3 586.5,623.8 655.1,581.5C723.7,539.2 746.5,518.1 753,518.7C764,519.6 772.9,536.1 764.4,589.5C763,598.2 761.3,606.5 759.3,614.2C828.7,575 871.7,516.3 889,520C912,524.8 918.3,617.8 852.1,721C811.8,784 768.8,830.7 741.4,857.5C724.3,874 699.7,880 677,873.1C512,822.6 315.5,733.8 202.9,652C193.8,645.2 186.8,635.9 183,625.1C171.8,593.7 161.3,548.1 168.5,495.3C179.5,414.2 222.7,352.9 245.2,335.9C261.9,323.2 267.1,362.4 274.7,394.8Z&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
id="Instantiatable"
message="This class should provide a default constructor (a public constructor with no arguments) (`dev.msfjarvis.claw.android.MainActivity`)"
errorLine1=" android:name=&quot;.MainActivity&quot;"
errorLine2=" ~~~~~~~~~~~~~">
<location
file="src/main/res/drawable/ic_launcher_foreground.xml"
line="17"
column="27"/>
file="src/main/AndroidManifest.xml"
line="28"
column="21"/>
</issue>
<issue
id="DeprecatedCall"
message="android.webkit.WebView.loadUrl is deprecated; consider using an alternative."
errorLine1=" webViewRef?.loadUrl(LOGIN_URL)"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/LoginScreen.kt"
line="69"
column="15"/>
</issue>
<issue
id="DeprecatedCall"
message="android.webkit.WebView.loadUrl is deprecated; consider using an alternative."
errorLine1=" webViewRef?.loadUrl(LOGIN_URL)"
errorLine2=" ~~~~~~~~~">
<location
file="src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/LoginScreen.kt"
line="69"
column="35"/>
</issue>
<issue
id="DeprecatedCall"
message="android.webkit.WebView.WebView is deprecated; consider using an alternative."
errorLine1=" WebView(context)"
errorLine2=" ~~~~~~~~~~~~~~~~">
<location
file="src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/LoginScreen.kt"
line="78"
column="13"/>
</issue>
<issue
id="DeprecatedCall"
message="android.webkit.WebView.WebView is deprecated; consider using an alternative."
errorLine1=" WebView(context)"
errorLine2=" ~~~~~~~">
<location
file="src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/LoginScreen.kt"
line="78"
column="21"/>
</issue>
<issue
id="DeprecatedCall"
message="android.webkit.WebView.getSettings is deprecated; consider using an alternative."
errorLine1=" settings.javaScriptEnabled = true"
errorLine2=" ~~~~~~~~">
<location
file="src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/LoginScreen.kt"
line="81"
column="17"/>
</issue>
<issue
id="DeprecatedCall"
message="android.webkit.WebView.setWebViewClient is deprecated; consider using an alternative."
errorLine1=" webViewClient ="
errorLine2=" ^">
<location
file="src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/LoginScreen.kt"
line="82"
column="17"/>
</issue>
<issue
id="DeprecatedCall"
message="android.webkit.WebView.setWebViewClient is deprecated; consider using an alternative."
errorLine1=" object : WebViewClient() {"
errorLine2=" ^">
<location
file="src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/LoginScreen.kt"
line="83"
column="19"/>
</issue>
<issue
id="DeprecatedCall"
message="android.webkit.WebView.loadUrl is deprecated; consider using an alternative."
errorLine1=" loadUrl(LOGIN_URL)"
errorLine2=" ~~~~~~~~~~~~~~~~~~">
<location
file="src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/LoginScreen.kt"
line="118"
column="17"/>
</issue>
<issue
id="DeprecatedCall"
message="android.webkit.WebView.loadUrl is deprecated; consider using an alternative."
errorLine1=" loadUrl(LOGIN_URL)"
errorLine2=" ~~~~~~~~~">
<location
file="src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/LoginScreen.kt"
line="118"
column="25"/>
</issue>
<issue
id="MemberExtensionConflict"
message="`KeepUntilTransitionsFinished` is defined both as a member in class `androidx.compose.animation.ExitTransition.Companion` and an extension in package `androidx.compose.animation`. The defined behavior for this is to use the member, but since the extension is explicitly imported into this file, there&apos;s a chance that this was not expected. (One common way this happens is for members to be added to a class after code was already written to use an extension)."
errorLine1=" ) togetherWith ExitTransition.KeepUntilTransitionsFinished"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/LobstersPostsScreen.kt"
line="259"
column="49"/>
</issue>
</issues>
@@ -1,17 +1,9 @@
-dontwarn android.window.OnBackInvokedCallback
-dontwarn android.window.OnBackInvokedDispatcher
-dontwarn org.bouncycastle.jsse.BCSSLParameters
-dontwarn org.bouncycastle.jsse.BCSSLSocket
-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
-dontwarn org.conscrypt.Conscrypt$Version
-dontwarn org.conscrypt.Conscrypt
-dontwarn org.conscrypt.ConscryptHostnameVerifier
-dontwarn org.openjsse.javax.net.ssl.SSLParameters
-dontwarn org.openjsse.javax.net.ssl.SSLSocket
-dontwarn org.openjsse.net.ssl.OpenJSSE
-dontwarn androidx.window.extensions.WindowExtensions
-dontwarn androidx.window.extensions.WindowExtensionsProvider
-dontwarn androidx.window.extensions.area.ExtensionWindowAreaPresentation
-dontwarn androidx.window.extensions.core.util.function.Consumer
-dontwarn androidx.window.extensions.core.util.function.Function
-dontwarn androidx.window.extensions.core.util.function.Predicate
-dontwarn androidx.window.extensions.layout.DisplayFeature
-dontwarn androidx.window.extensions.layout.FoldingFeature
-dontwarn androidx.window.extensions.layout.WindowLayoutComponent
@@ -22,3 +14,6 @@
-dontwarn androidx.window.sidecar.SidecarInterface
-dontwarn androidx.window.sidecar.SidecarProvider
-dontwarn androidx.window.sidecar.SidecarWindowLayoutInfo
-dontwarn io.mcarle.konvert.api.GeneratedKonverter
-dontwarn com.google.re2j.Matcher
-dontwarn com.google.re2j.Pattern
@@ -1,59 +0,0 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import androidx.test.uiautomator.BySelector
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
import com.google.common.truth.Truth.assertThat
import de.mannodermaus.junit5.ActivityScenarioExtension
import leakcanary.repeatingAndroidInProcessScenario
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.RegisterExtension
import shark.HeapDiff
class HeapGrowthCheck {
@JvmField
@RegisterExtension
@Suppress("Unused")
val scenarioExtension = ActivityScenarioExtension.launch<MainActivity>()
private val detector = HeapDiff.repeatingAndroidInProcessScenario()
private lateinit var device: UiDevice
@BeforeEach
fun setUp() {
val instrumentation = InstrumentationRegistry.getInstrumentation()
device = UiDevice.getInstance(instrumentation)
}
@Test
fun verify_heap_growth() {
val heapGrowth = detector.findRepeatedlyGrowingObjects { device.exploreScreens() }
assertThat(heapGrowth.growingObjects).isEmpty()
}
private companion object {
fun UiDevice.exploreScreens() {
listOf("HOTTEST", "NEWEST", "SAVED").forEach { tag ->
waitForObject(By.res(tag)).click()
waitForIdle()
}
}
private fun UiDevice.waitForObject(selector: BySelector, timeout: Long = 10_000L): UiObject2 {
if (wait(Until.hasObject(selector), timeout)) {
return findObject(selector)
}
error("Object with selector [$selector] not found")
}
}
}
+46
View File
@@ -0,0 +1,46 @@
<!--
~ Copyright © Harsh Shandilya.
~ Use of this source code is governed by an MIT-style
~ license that can be found in the LICENSE file or at
~ https://opensource.org/licenses/MIT.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="claw-debug"
android:host="comments" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="claw-debug"
android:host="hottest" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="claw-debug"
android:host="newest" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="claw-debug"
android:host="saved" />
</intent-filter>
</activity>
</application>
</manifest>
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:10defb7f6abec5d8bd07e07625e30e5b68d3a330e87f157e8138688f8d7ef0aa
size 214795
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:64dd5c002c021774469d7d9a15433678a014f720b6cde2281fbbf20daee98712
size 216093
+42 -17
View File
@@ -10,20 +10,23 @@
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".ClawApplication"
android:appComponentFactory="dev.msfjarvis.claw.android.BackupSafeAppComponentFactory"
android:dataExtractionRules="@xml/data_extraction_rules"
android:enableOnBackInvokedCallback="true"
android:fullBackupContent="@xml/full_backup_content"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/Theme.Claw.SplashScreen"
tools:replace="android:appComponentFactory"
tools:targetApi="tiramisu">
<profileable
android:shell="true"
tools:targetApi="29" />
<activity
android:name=".MainActivity"
android:configChanges="colorMode|density|fontScale|keyboard|keyboardHidden|layoutDirection|locale|mcc|mnc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|touchscreen|uiMode"
android:configChanges="assetsPaths|colorMode|density|fontScale|fontWeightAdjustment|grammaticalGender|keyboard|keyboardHidden|layoutDirection|locale|mcc|mnc|navigation|orientation|resourcesUnused|screenLayout|screenSize|smallestScreenSize|touchscreen|uiMode"
android:launchMode="singleTask"
android:taskAffinity=""
android:exported="true">
@@ -31,20 +34,34 @@
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="claw" android:host="comments" android:pathPrefix="/" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="claw" android:host="hottest" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="claw" android:host="newest" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="claw" android:host="saved" />
</intent-filter>
</activity>
<activity
android:name=".SearchActivity"
android:configChanges="colorMode|density|fontScale|keyboard|keyboardHidden|layoutDirection|locale|mcc|mnc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|touchscreen|uiMode"
android:exported="true"
android:label="Claw Search"
android:launchMode="singleTask"
android:taskAffinity=""
android:theme="@style/Theme.Claw">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
android:name=".glance.WidgetClickActivity"
android:exported="false"
android:theme="@android:style/Theme.NoDisplay"
android:excludeFromRecents="true"
android:taskAffinity="" />
<provider
android:name="androidx.startup.InitializationProvider"
@@ -55,14 +72,10 @@
android:name="androidx.work.WorkManagerInitializer"
android:value="androidx.startup"
tools:node="remove" />
<meta-data
android:name="com.deliveryhero.whetstone.worker.WhetstoneWorkerInitializer"
android:value="androidx.startup"
tools:node="remove" />
</provider>
<receiver
android:name=".glance.WidgetReceiver"
android:name=".glance.SavedPostsWidgetReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
@@ -72,6 +85,17 @@
android:resource="@xml/saved_posts_widget_info" />
</receiver>
<receiver
android:name=".glance.HottestPostsWidgetReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/hottest_posts_widget_info" />
</receiver>
<!-- Required: set your sentry.io project identifier (DSN) -->
<meta-data
android:name="io.sentry.dsn"
@@ -93,5 +117,6 @@
android:value="true" />
<!-- set the environment for this build -->
<meta-data android:name="io.sentry.environment" android:value="${sentryEnvironment}" />
<meta-data android:name="io.sentry.auto-init" android:value="false" />
</application>
</manifest>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,35 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android
import android.app.Application
import dev.zacsweers.metrox.android.MetroAppComponentFactory
/**
* Custom variant of [android.app.AppComponentFactory] that skips [ClawApplication] init when the
* app is running in a backup process for Android Auto Backup. In all normal cases, it will defer to
* [MetroAppComponentFactory].
*/
@Suppress("unused")
class BackupSafeAppComponentFactory : MetroAppComponentFactory() {
override fun instantiateApplicationCompat(cl: ClassLoader, className: String): Application {
if (isBackupAgentProcess()) {
return Application()
}
return super.instantiateApplicationCompat(cl, className)
}
private fun isBackupAgentProcess(): Boolean {
return try {
val processName = Application.getProcessName()
processName?.contains("backup") == true
} catch (_: Exception) {
false
}
}
}
@@ -1,70 +0,0 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android
import android.app.assist.AssistContent
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.ui.platform.LocalUriHandler
import com.deliveryhero.whetstone.Whetstone
import dev.msfjarvis.claw.android.viewmodel.ClawViewModel
import dev.msfjarvis.claw.common.comments.HTMLConverter
import dev.msfjarvis.claw.common.theme.LobstersTheme
import dev.msfjarvis.claw.common.urllauncher.UrlLauncher
import javax.inject.Inject
/** A base class that encapsulates all activities used by Claw. */
@Stable
abstract class BaseActivity : ComponentActivity() {
@Inject lateinit var urlLauncher: UrlLauncher
@Inject lateinit var htmlConverter: HTMLConverter
@Inject lateinit var viewModel: ClawViewModel
var webUri: String? = null
/** Entrypoint to show a [Composable] as this [ComponentActivity]'s view. */
@Composable abstract fun Content()
/** Overrideable method to call stuff in [onCreate]. */
open fun preLaunch() {}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
preLaunch()
Whetstone.inject(this)
enableEdgeToEdge(
statusBarStyle = SystemBarStyle.light(Color.TRANSPARENT, Color.TRANSPARENT),
navigationBarStyle = SystemBarStyle.light(Color.TRANSPARENT, Color.TRANSPARENT),
)
setContent {
LobstersTheme(
dynamicColor = true,
providedValues = arrayOf(LocalUriHandler provides urlLauncher),
) {
Content()
}
}
}
override fun onProvideAssistContent(outContent: AssistContent?) {
super.onProvideAssistContent(outContent)
if (outContent != null) {
if (webUri != null) {
outContent.webUri = Uri.parse(webUri)
} else {
outContent.webUri = null
}
}
}
}
@@ -7,43 +7,62 @@
package dev.msfjarvis.claw.android
import android.app.Application
import android.os.Build
import androidx.glance.appwidget.GlanceAppWidgetManager
import androidx.work.Constraints
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.NetworkType
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import com.deliveryhero.whetstone.Whetstone
import com.deliveryhero.whetstone.app.ApplicationComponentOwner
import com.deliveryhero.whetstone.app.ContributesAppInjector
import dev.msfjarvis.claw.android.glance.SavedPostsWidgetReceiver
import dev.msfjarvis.claw.android.injection.AppGraph
import dev.msfjarvis.claw.android.work.SavedPostUpdaterWorker
import dev.msfjarvis.claw.core.injection.AppPlugin
import dev.msfjarvis.claw.android.work.TagExpirationCleanupWorker
import dev.zacsweers.metro.createGraphFactory
import dev.zacsweers.metrox.android.MetroAppComponentProviders
import dev.zacsweers.metrox.android.MetroApplication
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@ContributesAppInjector(generateAppComponent = true)
class ClawApplication : Application(), ApplicationComponentOwner {
class ClawApplication : Application(), MetroApplication {
override val applicationComponent = GeneratedApplicationComponent.create(this)
val appGraph by lazy { createGraphFactory<AppGraph.Factory>().create(this) }
@Inject lateinit var plugins: Set<@JvmSuppressWildcards AppPlugin>
override val appComponentProviders: MetroAppComponentProviders
get() = appGraph
@OptIn(DelicateCoroutinesApi::class)
override fun onCreate() {
Whetstone.inject(this)
super.onCreate()
plugins.forEach { plugin -> plugin.apply(this) }
appGraph.plugins.forEach { plugin -> plugin.apply(this) }
val postUpdateWorkRequest =
PeriodicWorkRequestBuilder<SavedPostUpdaterWorker>(POST_REFRESH_PERIOD, TimeUnit.HOURS)
PeriodicWorkRequestBuilder<SavedPostUpdaterWorker>(24, TimeUnit.HOURS)
.setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
.build()
WorkManager.getInstance(this)
.enqueueUniquePeriodicWork(
"updateSavedPosts",
ExistingPeriodicWorkPolicy.KEEP,
postUpdateWorkRequest,
)
}
private companion object {
private const val POST_REFRESH_PERIOD = 24L // 24 hours
appGraph.workManager.enqueueUniquePeriodicWork(
uniqueWorkName = "updateSavedPosts",
existingPeriodicWorkPolicy = ExistingPeriodicWorkPolicy.KEEP,
request = postUpdateWorkRequest,
)
val tagCleanupWorkRequest =
PeriodicWorkRequestBuilder<TagExpirationCleanupWorker>(24, TimeUnit.HOURS).build()
appGraph.workManager.enqueueUniquePeriodicWork(
uniqueWorkName = "cleanupExpiredTags",
existingPeriodicWorkPolicy = ExistingPeriodicWorkPolicy.KEEP,
request = tagCleanupWorkRequest,
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
GlobalScope.launch(appGraph.mainDispatcher) {
try {
GlanceAppWidgetManager(applicationContext)
.setWidgetPreviews(SavedPostsWidgetReceiver::class)
} catch (_: IllegalArgumentException) {
// Workaround for Motorola Android 15 bug where AppWidgetServiceImpl incorrectly
// reports registered widgets as invalid when setting previews.
// See: https://claw.sentry.io/share/issue/f8ee09821d8840b9b86293d3ffb627d3/
}
}
}
}
}
@@ -6,51 +6,116 @@
*/
package dev.msfjarvis.claw.android
import androidx.compose.foundation.layout.fillMaxSize
import android.app.Activity
import android.app.assist.AssistContent
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.platform.UriHandler
import androidx.core.net.toUri
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import com.deliveryhero.whetstone.activity.ContributesActivityInjector
import androidx.lifecycle.ViewModelProvider
import androidx.navigation3.runtime.NavKey
import dev.msfjarvis.claw.android.ui.navigation.Comments
import dev.msfjarvis.claw.android.ui.navigation.Hottest
import dev.msfjarvis.claw.android.ui.navigation.Newest
import dev.msfjarvis.claw.android.ui.navigation.Saved
import dev.msfjarvis.claw.android.ui.screens.LobstersPostsScreen
import dev.msfjarvis.claw.android.ui.screens.TabletScreen
import dev.msfjarvis.claw.common.theme.LobstersTheme
import dev.msfjarvis.claw.core.injection.InjectedViewModelFactory
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesIntoMap
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.binding
import dev.zacsweers.metrox.android.ActivityKey
import dev.zacsweers.metrox.viewmodel.LocalMetroViewModelFactory
@ContributesActivityInjector
class MainActivity : BaseActivity() {
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
@ContributesIntoMap(AppScope::class, binding<Activity>())
@ActivityKey
@Inject
class MainActivity(
private val uriHandler: UriHandler,
private val viewModelFactory: InjectedViewModelFactory,
) : ComponentActivity() {
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
@Composable
override fun Content() {
val windowSizeClass = calculateWindowSizeClass(this)
override val defaultViewModelProviderFactory: ViewModelProvider.Factory
get() = viewModelFactory
when (windowSizeClass.widthSizeClass) {
WindowWidthSizeClass.Compact -> {
var webUri: String? = null
var deepLinkDestination by mutableStateOf<NavKey?>(null)
private set
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
installSplashScreen()
enableEdgeToEdge(
statusBarStyle = SystemBarStyle.light(Color.TRANSPARENT, Color.TRANSPARENT)
// Don't set navigation bar style, the default matches the platform behavior.
)
handleIntent(intent)
setContent {
LobstersTheme(
dynamicColor = true,
providedValues =
arrayOf(
LocalUriHandler provides uriHandler,
LocalMetroViewModelFactory provides viewModelFactory,
),
) {
val windowSizeClass = calculateWindowSizeClass(this)
LobstersPostsScreen(
urlLauncher = urlLauncher,
htmlConverter = htmlConverter,
uriHandler = uriHandler,
windowSizeClass = windowSizeClass,
setWebUri = { url -> webUri = url },
)
}
else -> {
TabletScreen(
urlLauncher = urlLauncher,
htmlConverter = htmlConverter,
modifier = Modifier.fillMaxSize(),
deepLinkDestination = deepLinkDestination,
clearDeepLink = { deepLinkDestination = null },
)
}
}
}
override fun preLaunch() {
super.preLaunch()
installSplashScreen()
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
handleIntent(intent)
}
companion object {
const val NAVIGATION_KEY = "postId"
private fun handleIntent(intent: Intent) {
val data = intent.data
if (data != null && data.scheme == BuildConfig.DEEPLINK_SCHEME) {
when (data.host) {
"comments" -> {
val postId = data.pathSegments.firstOrNull()
if (postId != null) {
deepLinkDestination = Comments(postId)
}
}
"newest" -> deepLinkDestination = Newest
"hottest" -> deepLinkDestination = Hottest
"saved" -> deepLinkDestination = Saved
}
}
}
override fun onProvideAssistContent(outContent: AssistContent?) {
super.onProvideAssistContent(outContent)
val uri = webUri
if (outContent != null) {
if (uri != null) {
outContent.webUri = uri.toUri()
} else {
outContent.webUri = null
}
}
}
}
@@ -1,24 +0,0 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android
import androidx.compose.runtime.Composable
import com.deliveryhero.whetstone.activity.ContributesActivityInjector
import dev.msfjarvis.claw.android.ui.screens.SearchScreen
@ContributesActivityInjector
class SearchActivity : BaseActivity() {
@Composable
override fun Content() {
SearchScreen(
urlLauncher = urlLauncher,
htmlConverter = htmlConverter,
setWebUri = { webUri = it },
viewModel = viewModel,
)
}
}
@@ -0,0 +1,157 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android.glance
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
import androidx.glance.GlanceId
import androidx.glance.GlanceModifier
import androidx.glance.GlanceTheme
import androidx.glance.LocalContext
import androidx.glance.action.clickable
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.action.actionStartActivity
import androidx.glance.appwidget.cornerRadius
import androidx.glance.appwidget.lazy.items
import androidx.glance.appwidget.provideContent
import androidx.glance.background
import androidx.glance.layout.Alignment
import androidx.glance.layout.Box
import androidx.glance.layout.fillMaxWidth
import androidx.glance.layout.padding
import androidx.glance.text.Text
import androidx.glance.text.TextAlign
import androidx.glance.text.TextStyle
import com.slack.eithernet.ApiResult
import dev.msfjarvis.claw.android.BuildConfig
import dev.msfjarvis.claw.android.ClawApplication
import dev.msfjarvis.claw.android.MainActivity
import dev.msfjarvis.claw.android.R
import dev.msfjarvis.claw.database.local.CachedRemotePost
import dev.msfjarvis.claw.model.LobstersPost
import dev.msfjarvis.claw.model.UIPost
import dev.msfjarvis.claw.model.fromCachedRemotePost
import dev.msfjarvis.claw.model.toUIPost
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.first
class HottestPostsWidget : GlanceAppWidget() {
override suspend fun provideGlance(context: Context, id: GlanceId) {
val appGraph = (context.applicationContext as ClawApplication).appGraph
val cachedRemotePostsRepository = appGraph.cachedRemotePostsRepository
val tagBlockRepository = appGraph.tagBlockRepository
val filteredTags =
try {
tagBlockRepository.getSavedTags().first()
} catch (_: Exception) {
emptySet()
}
val posts =
when (val postsResult = appGraph.lobstersApi.getHottestPosts(1)) {
is ApiResult.Success -> {
val uiPosts = postsResult.value.posts.map(LobstersPost::toUIPost)
// Cache the posts for future use when network is unavailable
// Using try-catch to prevent widget rendering failures if caching fails
try {
val cachedPosts = uiPosts.mapIndexed { index, post ->
CachedRemotePost(
shortId = post.shortId,
title = post.title,
url = post.url,
createdAt = post.createdAt,
commentCount = post.commentCount,
commentsUrl = post.commentsUrl,
submitterName = post.submitter,
tags = post.tags,
description = post.description,
userIsAuthor = post.userIsAuthor,
insertionOrder = index,
)
}
cachedRemotePostsRepository.savePosts(cachedPosts)
} catch (_: Exception) {
// Silently ignore caching failures - widget should still render
}
uiPosts.toImmutableList()
}
else -> {
// Fall back to cached posts when network fails
cachedRemotePostsRepository
.getCachedPosts()
.map(UIPost::fromCachedRemotePost)
.toImmutableList()
}
}
// Filter out posts with tags that are in the filtered tags set
val filteredPosts =
if (filteredTags.isEmpty()) {
posts
} else {
posts
.filter { post -> post.tags.none { tag -> filteredTags.contains(tag) } }
.toImmutableList()
}
provideContent { LobstersGlanceTheme { Content(context, filteredPosts) } }
}
override suspend fun providePreview(context: Context, widgetCategory: Int) {
provideContent { Content(context, samplePosts()) }
}
@SuppressLint("ComposeUnstableReceiver")
@Composable
private fun Content(
context: Context,
posts: ImmutableList<UIPost>,
modifier: GlanceModifier = GlanceModifier,
) {
WidgetContainer(
"Hottest posts",
listContent = {
items(posts) { post ->
Box(GlanceModifier.padding(horizontal = 16.dp, vertical = 4.dp)) { WidgetPostEntry(post) }
}
item {
Box(
contentAlignment = Alignment.Center,
modifier = GlanceModifier.fillMaxWidth().padding(16.dp),
) {
Box(
contentAlignment = Alignment.Center,
modifier =
GlanceModifier.background(GlanceTheme.colors.primary)
.cornerRadius(20.dp)
.padding(horizontal = 24.dp, vertical = 10.dp)
.clickable(
actionStartActivity(
Intent(Intent.ACTION_VIEW, "${BuildConfig.DEEPLINK_SCHEME}://hottest".toUri())
.apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
setClass(LocalContext.current, MainActivity::class.java)
}
)
),
) {
Text(
text = context.getString(R.string.see_more_posts),
style =
TextStyle(color = GlanceTheme.colors.onPrimary, textAlign = TextAlign.Center),
)
}
}
}
},
modifier = modifier,
)
}
}
@@ -6,25 +6,26 @@
*/
package dev.msfjarvis.claw.android.glance
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.GlanceAppWidgetReceiver
import com.deliveryhero.whetstone.Whetstone
import com.deliveryhero.whetstone.broadcastreceiver.ContributesBroadcastReceiverInjector
import dev.msfjarvis.claw.android.viewmodel.ClawViewModel
import javax.inject.Inject
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesIntoMap
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.binding
import dev.zacsweers.metrox.android.BroadcastReceiverKey
@ContributesBroadcastReceiverInjector
class WidgetReceiver : GlanceAppWidgetReceiver() {
@Inject lateinit var viewModel: ClawViewModel
@ContributesIntoMap(AppScope::class, binding<BroadcastReceiver>())
@BroadcastReceiverKey
@Inject
class HottestPostsWidgetReceiver() : GlanceAppWidgetReceiver() {
override val glanceAppWidget: GlanceAppWidget
get() = SavedPostsWidget(viewModel.savedPosts)
get() = HottestPostsWidget()
override fun onReceive(context: Context, intent: Intent) {
Whetstone.inject(this, context)
super.onReceive(context, intent)
}
}
@@ -6,69 +6,106 @@
*/
package dev.msfjarvis.claw.android.glance
import android.annotation.SuppressLint
import android.content.Context
import android.os.Build
import androidx.compose.material3.MaterialTheme
import android.content.Intent
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
import androidx.glance.GlanceComposable
import androidx.glance.GlanceId
import androidx.glance.GlanceModifier
import androidx.glance.GlanceTheme
import androidx.glance.LocalContext
import androidx.glance.action.clickable
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.appWidgetBackground
import androidx.glance.appwidget.lazy.LazyColumn
import androidx.glance.appwidget.action.actionStartActivity
import androidx.glance.appwidget.cornerRadius
import androidx.glance.appwidget.lazy.items
import androidx.glance.appwidget.provideContent
import androidx.glance.background
import androidx.glance.layout.Alignment
import androidx.glance.layout.fillMaxSize
import androidx.glance.layout.Box
import androidx.glance.layout.fillMaxWidth
import androidx.glance.layout.padding
import androidx.glance.material3.ColorProviders
import androidx.glance.preview.ExperimentalGlancePreviewApi
import androidx.glance.preview.Preview
import androidx.glance.text.Text
import androidx.glance.text.TextAlign
import androidx.glance.text.TextStyle
import dev.msfjarvis.claw.common.theme.DarkThemeColors
import dev.msfjarvis.claw.common.theme.LightThemeColors
import dev.msfjarvis.claw.android.BuildConfig
import dev.msfjarvis.claw.android.ClawApplication
import dev.msfjarvis.claw.android.MainActivity
import dev.msfjarvis.claw.android.R
import dev.msfjarvis.claw.model.UIPost
import dev.msfjarvis.claw.model.fromSavedPost
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
class SavedPostsWidget(private val posts: Flow<List<UIPost>>) : GlanceAppWidget() {
override suspend fun provideGlance(context: Context, id: GlanceId) {
val postWindow = posts.first().take(50).toImmutableList()
provideContent {
GlanceTheme(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) GlanceTheme.colors
else ColorProviders(light = LightThemeColors, dark = DarkThemeColors)
) {
WidgetHost(postWindow)
}
}
}
}
class SavedPostsWidget : GlanceAppWidget() {
@Composable
fun WidgetHost(posts: ImmutableList<UIPost>, modifier: GlanceModifier = GlanceModifier) {
LazyColumn(
modifier =
modifier.fillMaxSize().background(GlanceTheme.colors.background).appWidgetBackground(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
item {
val style = MaterialTheme.typography.titleLarge
Text(
text = "Saved posts",
style =
TextStyle(
color = GlanceTheme.colors.onBackground,
fontSize = style.fontSize,
fontWeight = style.fontWeight.toGlance(),
fontStyle = style.fontStyle.toGlance(),
),
modifier = GlanceModifier.padding(vertical = 8.dp),
)
}
items(posts) { post -> WidgetListEntry(post = post) }
override suspend fun provideGlance(context: Context, id: GlanceId) {
val appGraph = (context.applicationContext as ClawApplication).appGraph
val posts = appGraph.savedPostsRepository.getRecentPosts(50)
val postWindow = posts.first().map(UIPost::fromSavedPost).toImmutableList()
provideContent { LobstersGlanceTheme { Content(postWindow) } }
}
override suspend fun providePreview(context: Context, widgetCategory: Int) {
provideContent { Content(samplePosts()) }
}
@SuppressLint("ComposeUnstableReceiver")
@Composable
private fun Content(posts: ImmutableList<UIPost>, modifier: GlanceModifier = GlanceModifier) {
val context = LocalContext.current
WidgetContainer(
title = context.getString(R.string.saved_posts),
listContent = {
items(posts) { post ->
Box(GlanceModifier.padding(horizontal = 16.dp, vertical = 4.dp)) { WidgetPostEntry(post) }
}
item {
Box(
contentAlignment = Alignment.Center,
modifier = GlanceModifier.fillMaxWidth().padding(16.dp),
) {
Box(
contentAlignment = Alignment.Center,
modifier =
GlanceModifier.background(GlanceTheme.colors.primary)
.cornerRadius(20.dp)
.padding(horizontal = 24.dp, vertical = 10.dp)
.clickable(
actionStartActivity(
Intent(Intent.ACTION_VIEW, "${BuildConfig.DEEPLINK_SCHEME}://saved".toUri())
.apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
setClass(LocalContext.current, MainActivity::class.java)
}
)
),
) {
Text(
text = context.getString(R.string.see_more_posts),
style =
TextStyle(color = GlanceTheme.colors.onPrimary, textAlign = TextAlign.Center),
)
}
}
}
},
modifier = modifier,
)
}
@SuppressLint("ComposeUnstableReceiver")
@OptIn(ExperimentalGlancePreviewApi::class)
@Preview
@GlanceComposable
@Composable
private fun Preview() {
Content(samplePosts())
}
}
@@ -0,0 +1,31 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android.glance
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.GlanceAppWidgetReceiver
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesIntoMap
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.binding
import dev.zacsweers.metrox.android.BroadcastReceiverKey
@ContributesIntoMap(AppScope::class, binding<BroadcastReceiver>())
@BroadcastReceiverKey
@Inject
class SavedPostsWidgetReceiver() : GlanceAppWidgetReceiver() {
override val glanceAppWidget: GlanceAppWidget
get() = SavedPostsWidget()
override fun onReceive(context: Context, intent: Intent) {
super.onReceive(context, intent)
}
}
@@ -0,0 +1,96 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android.glance
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import androidx.core.net.toUri
import dev.msfjarvis.claw.android.BuildConfig
import dev.msfjarvis.claw.android.MainActivity
import io.sentry.Breadcrumb
import io.sentry.Sentry
import io.sentry.SentryLevel
/**
* Trampoline activity for handling widget clicks with explicit components.
*
* This activity is required to fix COMPOSE-LOBSTERS-9X/9P/8M. Glance's LazyColumn requires all
* actionStartActivity intents to have an explicit component set. For external URLs, we route
* through this activity which then launches the appropriate intent (either internal deep link or
* external browser).
*
* @see:
* https://developer.android.com/reference/kotlin/androidx/glance/appwidget/action/package-summary#actionStartActivity(android.content.Intent,androidx.glance.action.ActionParameters)
*/
class WidgetClickActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val url = intent.getStringExtra(EXTRA_URL)
val shortId = intent.getStringExtra(EXTRA_SHORT_ID)
val action = intent.action
when {
// Handle comments deep link
action == ACTION_OPEN_COMMENTS && shortId != null -> {
Sentry.addBreadcrumb(
Breadcrumb().apply {
category = "widget_click_debug"
message = "Comment deeplink"
level = SentryLevel.INFO
}
)
val commentsIntent =
Intent(Intent.ACTION_VIEW, "${BuildConfig.DEEPLINK_SCHEME}://comments/$shortId".toUri())
.apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
setClass(this@WidgetClickActivity, MainActivity::class.java)
}
startActivity(commentsIntent)
}
// Handle external URL
url != null && (url.startsWith("http://") || url.startsWith("https://")) -> {
Sentry.addBreadcrumb(
Breadcrumb().apply {
category = "widget_click_debug"
message = "External URL"
level = SentryLevel.INFO
}
)
val urlIntent = Intent(Intent.ACTION_VIEW, url.toUri())
startActivity(urlIntent)
}
// Handle internal/relative URL as comments link
url != null && (url.startsWith('/') || url.isEmpty()) -> {
Sentry.addBreadcrumb(
Breadcrumb().apply {
category = "widget_click_debug"
message = "Relative URL, parsed as a comment link"
level = SentryLevel.INFO
}
)
// For internal URLs without shortId, extract it from the URL or use comments action
val commentsIntent =
Intent(Intent.ACTION_VIEW, "${BuildConfig.DEEPLINK_SCHEME}://comments/$shortId".toUri())
.apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
setClass(this@WidgetClickActivity, MainActivity::class.java)
}
startActivity(commentsIntent)
}
}
finish()
}
companion object {
const val ACTION_OPEN_COMMENTS = "dev.msfjarvis.claw.OPEN_COMMENTS"
const val EXTRA_URL = "dev.msfjarvis.claw.EXTRA_URL"
const val EXTRA_SHORT_ID = "dev.msfjarvis.claw.EXTRA_SHORT_ID"
}
}
@@ -1,77 +0,0 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android.glance
import android.content.Intent
import android.net.Uri
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.dp
import androidx.glance.GlanceComposable
import androidx.glance.GlanceModifier
import androidx.glance.GlanceTheme
import androidx.glance.Image
import androidx.glance.ImageProvider
import androidx.glance.action.ActionParameters.Key
import androidx.glance.action.actionParametersOf
import androidx.glance.action.actionStartActivity
import androidx.glance.action.clickable
import androidx.glance.appwidget.action.actionStartActivity
import androidx.glance.appwidget.cornerRadius
import androidx.glance.background
import androidx.glance.layout.Alignment
import androidx.glance.layout.Box
import androidx.glance.layout.Row
import androidx.glance.layout.fillMaxWidth
import androidx.glance.layout.padding
import androidx.glance.text.Text
import androidx.glance.text.TextStyle
import dev.msfjarvis.claw.android.MainActivity
import dev.msfjarvis.claw.android.MainActivity.Companion.NAVIGATION_KEY
import dev.msfjarvis.claw.android.R
import dev.msfjarvis.claw.model.UIPost
private val destinationKey = Key<String>(NAVIGATION_KEY)
@Composable
@GlanceComposable
fun WidgetListEntry(post: UIPost, modifier: GlanceModifier = GlanceModifier) {
val titleStyle = MaterialTheme.typography.titleMedium
val commentsAction =
actionStartActivity<MainActivity>(actionParametersOf(destinationKey to post.shortId))
// If the URL starts with a /, it's a relative URL and we should open the comments page directly.
val postAction =
if (post.url.startsWith('/')) commentsAction
else actionStartActivity(Intent(Intent.ACTION_VIEW, Uri.parse(post.url)))
Box(modifier.padding(8.dp)) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier =
GlanceModifier.fillMaxWidth()
.background(GlanceTheme.colors.surfaceVariant)
.cornerRadius(4.dp)
.padding(8.dp),
) {
Text(
text = post.title,
modifier = GlanceModifier.defaultWeight().padding(start = 4.dp).clickable(postAction),
style =
TextStyle(
color = GlanceTheme.colors.onSurfaceVariant,
fontSize = titleStyle.fontSize,
fontWeight = titleStyle.fontWeight.toGlance(),
fontStyle = titleStyle.fontStyle.toGlance(),
),
)
Image(
provider = ImageProvider(R.drawable.ic_comment),
contentDescription = "${post.commentCount} comments",
modifier = GlanceModifier.padding(end = 4.dp).clickable(commentsAction),
)
}
}
}
@@ -0,0 +1,97 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android.glance
import android.annotation.SuppressLint
import android.content.Intent
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.dp
import androidx.glance.ColorFilter
import androidx.glance.GlanceComposable
import androidx.glance.GlanceModifier
import androidx.glance.GlanceTheme
import androidx.glance.Image
import androidx.glance.ImageProvider
import androidx.glance.LocalContext
import androidx.glance.action.clickable
import androidx.glance.appwidget.action.actionStartActivity
import androidx.glance.appwidget.cornerRadius
import androidx.glance.background
import androidx.glance.layout.Alignment
import androidx.glance.layout.Row
import androidx.glance.layout.fillMaxWidth
import androidx.glance.layout.padding
import androidx.glance.preview.ExperimentalGlancePreviewApi
import androidx.glance.preview.Preview
import androidx.glance.text.Text
import androidx.glance.text.TextStyle
import dev.msfjarvis.claw.android.R
import dev.msfjarvis.claw.model.UIPost
@SuppressLint("ArgInFormattedQuantityStringRes")
@Composable
@GlanceComposable
fun WidgetPostEntry(post: UIPost, modifier: GlanceModifier = GlanceModifier) {
val titleStyle = MaterialTheme.typography.bodyMedium
val context = LocalContext.current
val commentsAction =
actionStartActivity(
Intent(context, WidgetClickActivity::class.java).apply {
action = WidgetClickActivity.ACTION_OPEN_COMMENTS
putExtra(WidgetClickActivity.EXTRA_SHORT_ID, post.shortId)
}
)
val postAction =
if (post.url.startsWith('/') || post.url.isEmpty()) commentsAction
else
actionStartActivity(
Intent(context, WidgetClickActivity::class.java).apply {
putExtra(WidgetClickActivity.EXTRA_URL, post.url)
putExtra(WidgetClickActivity.EXTRA_SHORT_ID, post.shortId)
}
)
Row(
verticalAlignment = Alignment.CenterVertically,
modifier =
modifier.fillMaxWidth().cornerRadius(8.dp).background(GlanceTheme.colors.secondaryContainer),
) {
Text(
text = post.title,
modifier =
GlanceModifier.defaultWeight()
.clickable(postAction)
.padding(horizontal = 8.dp, vertical = 4.dp)
.padding(start = 4.dp),
maxLines = 1,
style =
TextStyle(color = GlanceTheme.colors.onSecondaryContainer, fontSize = titleStyle.fontSize),
)
Image(
provider = ImageProvider(R.drawable.ic_comment),
contentDescription =
context.resources.getQuantityString(
R.plurals.comments,
post.commentCount,
post.commentCount,
),
colorFilter = ColorFilter.tint(GlanceTheme.colors.onSecondaryContainer),
modifier =
GlanceModifier.clickable(commentsAction)
.padding(horizontal = 8.dp, vertical = 4.dp)
.padding(horizontal = 4.dp),
)
}
}
@OptIn(ExperimentalGlancePreviewApi::class)
@Preview
@GlanceComposable
@Composable
private fun WidgetPostEntryPreview() {
WidgetPostEntry(samplePosts(1).first())
}
@@ -6,22 +6,67 @@
*/
package dev.msfjarvis.claw.android.glance
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import android.annotation.SuppressLint
import android.os.Build
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.glance.GlanceComposable
import androidx.glance.GlanceModifier
import androidx.glance.GlanceTheme
import androidx.glance.appwidget.cornerRadius
import androidx.glance.appwidget.lazy.LazyColumn
import androidx.glance.appwidget.lazy.LazyListScope
import androidx.glance.background
import androidx.glance.layout.Alignment
import androidx.glance.layout.Column
import androidx.glance.layout.Row
import androidx.glance.layout.fillMaxSize
import androidx.glance.layout.fillMaxWidth
import androidx.glance.layout.padding
import androidx.glance.material3.ColorProviders
import androidx.glance.text.Text
import androidx.glance.text.TextStyle
import dev.msfjarvis.claw.common.posts.TEST_POST
import dev.msfjarvis.claw.common.theme.DarkThemeColors
import dev.msfjarvis.claw.common.theme.LightThemeColors
import dev.msfjarvis.claw.model.UIPost
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.toPersistentList
fun FontWeight?.toGlance(): androidx.glance.text.FontWeight? {
return when (this) {
FontWeight.Normal -> androidx.glance.text.FontWeight.Normal
FontWeight.Medium -> androidx.glance.text.FontWeight.Medium
FontWeight.Bold -> androidx.glance.text.FontWeight.Bold
else -> null
@Composable
fun LobstersGlanceTheme(content: @GlanceComposable @Composable () -> Unit) {
GlanceTheme(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) GlanceTheme.colors
else ColorProviders(light = LightThemeColors, dark = DarkThemeColors),
content,
)
}
@Composable
fun WidgetContainer(
title: String,
listContent: LazyListScope.() -> Unit,
modifier: GlanceModifier = GlanceModifier,
) {
Column(
modifier.fillMaxSize().background(GlanceTheme.colors.widgetBackground).cornerRadius(16.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = GlanceModifier.padding(16.dp).fillMaxWidth(),
) {
Text(text = title, style = TextStyle(fontSize = 18.sp, color = GlanceTheme.colors.onSurface))
}
LazyColumn(content = listContent)
}
}
fun FontStyle?.toGlance(): androidx.glance.text.FontStyle? {
return when (this) {
FontStyle.Normal -> androidx.glance.text.FontStyle.Normal
FontStyle.Italic -> androidx.glance.text.FontStyle.Italic
else -> null
}
@SuppressLint("VisibleForTests")
fun samplePosts(
count: Int = 5,
mutator: UIPost.(idx: Int) -> UIPost = { this },
): PersistentList<UIPost> {
return buildList(count) { repeat(count) { add(TEST_POST.mutator(it)) } }.toPersistentList()
}
@@ -0,0 +1,61 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android.injection
import android.content.Context
import androidx.work.ListenableWorker
import androidx.work.WorkManager
import dev.msfjarvis.claw.android.viewmodel.CachedRemotePostsRepository
import dev.msfjarvis.claw.android.viewmodel.SavedPostsRepository
import dev.msfjarvis.claw.api.LobstersApi
import dev.msfjarvis.claw.common.tags.TagBlockRepository
import dev.msfjarvis.claw.core.coroutines.MainDispatcher
import dev.msfjarvis.claw.core.injection.AppPlugin
import dev.msfjarvis.claw.core.injection.InjectedWorkerFactory
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.DependencyGraph
import dev.zacsweers.metro.Multibinds
import dev.zacsweers.metro.Provides
import dev.zacsweers.metro.SingleIn
import dev.zacsweers.metrox.android.MetroAppComponentProviders
import dev.zacsweers.metrox.viewmodel.ViewModelGraph
import kotlin.reflect.KClass
import kotlinx.coroutines.CoroutineDispatcher
@DependencyGraph(AppScope::class)
interface AppGraph : MetroAppComponentProviders, ViewModelGraph {
val plugins: Set<AppPlugin>
val workManager: WorkManager
@Provides
@SingleIn(AppScope::class)
fun providesWorkManager(application: Context): WorkManager {
return WorkManager.getInstance(application)
}
@Multibinds
val workerProviders:
Map<KClass<out ListenableWorker>, () -> InjectedWorkerFactory.WorkerInstanceFactory<*>>
val workerFactory: InjectedWorkerFactory
val savedPostsRepository: SavedPostsRepository
val cachedRemotePostsRepository: CachedRemotePostsRepository
val lobstersApi: LobstersApi
val tagBlockRepository: TagBlockRepository
@MainDispatcher val mainDispatcher: CoroutineDispatcher
@DependencyGraph.Factory
fun interface Factory {
fun create(@Provides context: Context): AppGraph
}
}
@@ -1,20 +0,0 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android.injection
import com.deliveryhero.whetstone.app.ApplicationScope
import com.squareup.anvil.annotations.ContributesTo
import dagger.Binds
import dagger.Module
import dev.msfjarvis.claw.android.ui.util.HTMLConverterImpl
import dev.msfjarvis.claw.common.comments.HTMLConverter
@Module
@ContributesTo(ApplicationScope::class)
interface HTMLConverterModule {
@Binds fun bindHTMLConverter(impl: HTMLConverterImpl): HTMLConverter
}
@@ -6,27 +6,27 @@
*/
package dev.msfjarvis.claw.android.injection
import com.deliveryhero.whetstone.app.ApplicationScope
import com.squareup.anvil.annotations.ContributesTo
import dagger.Module
import dagger.Provides
import io.github.aakira.napier.Napier
import android.util.Log
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.BindingContainer
import dev.zacsweers.metro.ContributesTo
import dev.zacsweers.metro.Provides
import me.saket.unfurl.UnfurlLogger
import me.saket.unfurl.Unfurler
import okhttp3.OkHttpClient
@Module
@ContributesTo(ApplicationScope::class)
@BindingContainer
@ContributesTo(AppScope::class)
object MetadataExtractorModule {
@Provides
fun provideUnfurlLogger(): UnfurlLogger {
return object : UnfurlLogger {
override fun log(message: String) {
Napier.d(tag = "Unfurler") { message }
Log.d("Unfurler", message)
}
override fun log(e: Throwable, message: String) {
Napier.e(tag = "Unfurler", throwable = e) { message }
Log.e("Unfurler", message, e)
}
}
}
@@ -0,0 +1,37 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android.injection
import android.app.Application
import dev.msfjarvis.claw.api.LobstersApi
import dev.msfjarvis.claw.core.injection.AppPlugin
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesIntoSet
import dev.zacsweers.metro.Inject
import io.sentry.SentryOptions
import io.sentry.TypeCheckHint.OKHTTP_REQUEST
import io.sentry.android.core.SentryAndroid
import okhttp3.Request
@ContributesIntoSet(AppScope::class)
@Inject
class SentryConfigurationPlugin : AppPlugin {
override fun apply(application: Application) {
SentryAndroid.init(application) { options ->
options.beforeSend = SentryOptions.BeforeSendCallback { event, hint ->
val request = hint.getAs(OKHTTP_REQUEST, Request::class.java)
// Drop all OkHttp errors that are not about Lobsters specifically.
if (request != null && !LobstersApi.BASE_URL.contains(request.url.host)) {
null
} else {
event
}
}
}
}
}
@@ -1,24 +0,0 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android.injection
import android.content.Context
import com.deliveryhero.whetstone.activity.ActivityScope
import com.squareup.anvil.annotations.ContributesTo
import com.squareup.anvil.annotations.optional.ForScope
import dagger.Module
import dagger.Provides
import dev.msfjarvis.claw.common.urllauncher.UrlLauncher
@Module
@ContributesTo(ActivityScope::class)
object UrlLauncherModule {
@Provides
fun provideUrlLauncher(@ForScope(ActivityScope::class) context: Context): UrlLauncher {
return UrlLauncher(context)
}
}
@@ -11,13 +11,14 @@ import android.util.Log
import androidx.work.Configuration
import androidx.work.WorkManager
import androidx.work.WorkerFactory
import com.deliveryhero.whetstone.app.ApplicationScope
import com.squareup.anvil.annotations.ContributesMultibinding
import dev.msfjarvis.claw.core.injection.AppPlugin
import javax.inject.Inject
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesIntoSet
import dev.zacsweers.metro.Inject
@ContributesMultibinding(ApplicationScope::class)
class WorkManagerPlugin @Inject constructor(private val workerFactory: WorkerFactory) : AppPlugin {
@ContributesIntoSet(AppScope::class)
@Inject
class WorkManagerPlugin(private val workerFactory: WorkerFactory) : AppPlugin {
override fun apply(application: Application) {
WorkManager.initialize(
application,
@@ -10,23 +10,22 @@ import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.slack.eithernet.ApiResult.Failure
import com.slack.eithernet.ApiResult.Success
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dev.msfjarvis.claw.android.ui.toError
import dev.msfjarvis.claw.core.injection.IODispatcher
import dev.msfjarvis.claw.api.toError
import dev.msfjarvis.claw.core.coroutines.IODispatcher
import dev.msfjarvis.claw.model.LobstersPost
import dev.msfjarvis.claw.model.UIPost
import dev.msfjarvis.claw.model.toUIPost
import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedFactory
import dev.zacsweers.metro.AssistedInject
import java.io.IOException
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
class LobstersPagingSource
@AssistedInject
constructor(
class LobstersPagingSource(
@Assisted private val remoteFetcher: RemoteFetcher<LobstersPost>,
@IODispatcher private val ioDispatcher: CoroutineDispatcher,
@param:IODispatcher private val ioDispatcher: CoroutineDispatcher,
) : PagingSource<Int, UIPost>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, UIPost> {
val page = params.key ?: STARTING_PAGE_INDEX
@@ -9,17 +9,17 @@ package dev.msfjarvis.claw.android.paging
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.slack.eithernet.ApiResult
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dev.msfjarvis.claw.android.paging.LobstersPagingSource.Companion.PAGE_SIZE
import dev.msfjarvis.claw.android.paging.LobstersPagingSource.Companion.STARTING_PAGE_INDEX
import dev.msfjarvis.claw.android.ui.toError
import dev.msfjarvis.claw.api.LobstersSearchApi
import dev.msfjarvis.claw.core.injection.IODispatcher
import dev.msfjarvis.claw.api.toError
import dev.msfjarvis.claw.core.coroutines.IODispatcher
import dev.msfjarvis.claw.model.LobstersPost
import dev.msfjarvis.claw.model.UIPost
import dev.msfjarvis.claw.model.toUIPost
import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedFactory
import dev.zacsweers.metro.AssistedInject
import java.io.IOException
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
@@ -30,12 +30,11 @@ import kotlinx.coroutines.withContext
* relevant parameters to short-circuit when there is no query specified in order to avoid calling
* the API at all.
*/
class SearchPagingSource
@AssistedInject
constructor(
class SearchPagingSource(
private val searchApi: LobstersSearchApi,
@Assisted private val queryProvider: () -> String,
@IODispatcher private val ioDispatcher: CoroutineDispatcher,
@param:IODispatcher private val ioDispatcher: CoroutineDispatcher,
) : PagingSource<Int, UIPost>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, UIPost> {
val searchQuery = queryProvider()
@@ -1,21 +0,0 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android.plugins
import android.app.Application
import com.deliveryhero.whetstone.app.ApplicationScope
import com.squareup.anvil.annotations.ContributesMultibinding
import dev.msfjarvis.claw.core.injection.AppPlugin
import io.sentry.android.core.SentryAndroid
import javax.inject.Inject
@ContributesMultibinding(ApplicationScope::class)
class SentryAndroidPlugin @Inject constructor() : AppPlugin {
override fun apply(application: Application) {
SentryAndroid.init(application)
}
}
@@ -0,0 +1,81 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android.ui.decorations
import android.app.Activity
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Tune
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.navigation3.runtime.NavKey
import dev.msfjarvis.claw.android.R
import dev.msfjarvis.claw.android.ui.navigation.Search
import dev.msfjarvis.claw.android.ui.navigation.Settings
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ClawAppBar(
activity: Activity?,
isTopLevel: Boolean,
navigateTo: (NavKey) -> Unit,
popBackStack: () -> NavKey?,
modifier: Modifier = Modifier,
) {
TopAppBar(
modifier = modifier.shadow(8.dp),
navigationIcon = {
if (!isTopLevel) {
IconButton(onClick = { if (popBackStack() == null) activity?.finish() }) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = stringResource(R.string.go_back_to_previous_screen),
)
}
} else {
Icon(
painter = painterResource(id = R.drawable.ic_launcher_foreground),
contentDescription = stringResource(R.string.the_app_icon_for_claw),
modifier = Modifier.size(48.dp),
)
}
},
title = {
if (isTopLevel) {
Text(text = stringResource(R.string.app_name), fontWeight = FontWeight.Bold)
}
},
actions = {
if (isTopLevel) {
IconButton(onClick = { navigateTo(Search) }) {
Icon(
imageVector = Icons.Filled.Search,
contentDescription = stringResource(R.string.search_posts),
)
}
IconButton(onClick = { navigateTo(Settings) }) {
Icon(
imageVector = Icons.Filled.Tune,
contentDescription = stringResource(R.string.settings),
)
}
}
},
)
}
@@ -26,15 +26,12 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import androidx.navigation.compose.currentBackStackEntryAsState
import dev.chrisbanes.haze.HazeDefaults
import androidx.navigation3.runtime.NavKey
import dev.chrisbanes.haze.HazeState
import dev.chrisbanes.haze.HazeStyle
import dev.chrisbanes.haze.blur.HazeBlurDefaults
import dev.chrisbanes.haze.blur.blurEffect
import dev.chrisbanes.haze.hazeEffect
import dev.msfjarvis.claw.android.ui.navigation.AppDestinations
import dev.msfjarvis.claw.android.ui.navigation.Destination
import dev.msfjarvis.claw.android.ui.navigation.matches
import dev.msfjarvis.claw.common.ui.FloatingNavigationBar
import kotlinx.collections.immutable.ImmutableList
@@ -42,8 +39,9 @@ const val AnimationDuration = 100
@Composable
fun ClawNavigationBar(
navController: NavController,
items: ImmutableList<NavigationItem>,
currentNavKey: NavKey?,
navigateTo: (NavKey) -> Unit,
isVisible: Boolean,
hazeState: HazeState,
modifier: Modifier = Modifier,
@@ -64,6 +62,7 @@ fun ClawNavigationBar(
),
label = "",
content = {
val surfaceColor = MaterialTheme.colorScheme.surface
FloatingNavigationBar(
tonalElevation = 16.dp,
shape = MaterialTheme.shapes.extraLarge,
@@ -72,23 +71,18 @@ fun ClawNavigationBar(
.padding(horizontal = 16.dp)
.navigationBarsPadding()
.clip(MaterialTheme.shapes.extraLarge)
.hazeEffect(
hazeState,
style =
HazeStyle(
backgroundColor = MaterialTheme.colorScheme.surface,
tints = emptyList(),
blurRadius = 24.dp,
noiseFactor = 0f,
),
),
containerColor =
if (HazeDefaults.blurEnabled()) Color.Transparent else MaterialTheme.colorScheme.surface,
.hazeEffect(hazeState) {
blurEffect {
backgroundColor = surfaceColor
colorEffects = emptyList()
blurRadius = 24.dp
noiseFactor = 0f
}
},
containerColor = if (HazeBlurDefaults.blurEnabled()) Color.Transparent else surfaceColor,
) {
val navBackStackEntry = navController.currentBackStackEntryAsState().value
val currentDestination = navBackStackEntry?.destination
items.forEach { navItem ->
val isSelected = currentDestination.matches(navItem.destination)
val isSelected = currentNavKey == navItem.navKey
NavigationBarItem(
icon = {
Crossfade(isSelected, label = "nav-label") {
@@ -104,11 +98,7 @@ fun ClawNavigationBar(
if (isSelected) {
navItem.listStateResetCallback()
} else {
navController.navigate(navItem.destination) {
popUpTo(navController.graph.startDestinationId) { saveState = true }
launchSingleTop = true
restoreState = true
}
navigateTo(navItem.navKey)
}
},
modifier = Modifier.testTag(navItem.label.uppercase()),
@@ -123,7 +113,7 @@ class NavigationItem
private constructor(
val icon: ImageVector,
val label: String,
val destination: Destination,
val navKey: NavKey,
val selectedIcon: ImageVector,
val listStateResetCallback: () -> Unit,
) {
@@ -133,7 +123,7 @@ private constructor(
) : this(
destination.icon,
destination.label,
destination.destination,
destination.navKey,
destination.selectedIcon,
listStateResetCallback,
) {}
@@ -21,15 +21,14 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.navigation.NavController
import androidx.navigation.compose.currentBackStackEntryAsState
import dev.msfjarvis.claw.android.ui.navigation.matches
import androidx.navigation3.runtime.NavKey
import kotlinx.collections.immutable.ImmutableList
@Composable
fun ClawNavigationRail(
navController: NavController,
items: ImmutableList<NavigationItem>,
currentNavKey: NavKey?,
navigateTo: (NavKey) -> Unit,
isVisible: Boolean,
modifier: Modifier = Modifier,
) {
@@ -50,11 +49,9 @@ fun ClawNavigationRail(
modifier = Modifier,
) {
NavigationRail(modifier = modifier) {
val navBackStackEntry = navController.currentBackStackEntryAsState().value
val currentDestination = navBackStackEntry?.destination
Spacer(Modifier.weight(1f))
items.forEach { navItem ->
val isSelected = currentDestination.matches(navItem.destination)
val isSelected = currentNavKey == navItem.navKey
NavigationRailItem(
icon = {
Crossfade(isSelected, label = "nav-label") {
@@ -70,11 +67,7 @@ fun ClawNavigationRail(
if (isSelected) {
navItem.listStateResetCallback()
} else {
navController.navigate(navItem.destination) {
popUpTo(navController.graph.startDestinationId) { saveState = true }
launchSingleTop = true
restoreState = true
}
navigateTo(navItem.navKey)
}
},
modifier = Modifier.testTag(navItem.label.uppercase()),
@@ -8,25 +8,22 @@ package dev.msfjarvis.claw.android.ui
import android.content.Context
import android.content.Intent
import com.slack.eithernet.ApiResult
import androidx.compose.ui.platform.UriHandler
import dev.msfjarvis.claw.android.viewmodel.ClawViewModel
import dev.msfjarvis.claw.common.posts.PostActions
import dev.msfjarvis.claw.common.urllauncher.UrlLauncher
import dev.msfjarvis.claw.model.LinkMetadata
import dev.msfjarvis.claw.model.UIPost
import java.io.IOException
import java.net.HttpURLConnection
fun PostActions(
context: Context,
urlLauncher: UrlLauncher,
uriHandler: UriHandler,
viewModel: ClawViewModel,
navigateToComments: (String) -> Unit,
): PostActions {
return object : PostActions {
override fun viewPost(postId: String, postUrl: String, commentsUrl: String) {
viewModel.markPostAsRead(postId)
urlLauncher.openUri(postUrl.ifEmpty { commentsUrl })
uriHandler.openUri(postUrl.ifEmpty { commentsUrl })
}
override fun viewComments(postId: String) {
@@ -35,7 +32,7 @@ fun PostActions(
}
override fun viewCommentsPage(post: UIPost) {
urlLauncher.openUri(post.commentsUrl)
uriHandler.openUri(post.commentsUrl)
}
override fun toggleSave(post: UIPost) {
@@ -58,26 +55,8 @@ fun PostActions(
override fun isPostSaved(post: UIPost): Boolean = viewModel.isPostSaved(post)
override suspend fun getComments(postId: String): UIPost {
return viewModel.getPostComments(postId)
}
override suspend fun getLinkMetadata(url: String): LinkMetadata {
return viewModel.getLinkMetadata(url)
}
}
}
/**
* Convert an [ApiResult.Failure.HttpFailure] to a scoped down error with a more useful user-facing
* message.
*/
@Suppress("NOTHING_TO_INLINE") // We inline this to eliminate the stacktrace frame.
inline fun <T : Any> ApiResult.Failure.HttpFailure<T>.toError(): Throwable =
when (code) {
HttpURLConnection.HTTP_NOT_FOUND -> IOException("Story was removed by moderator")
HttpURLConnection.HTTP_INTERNAL_ERROR,
HttpURLConnection.HTTP_BAD_GATEWAY,
HttpURLConnection.HTTP_UNAVAILABLE -> IOException("It appears lobste.rs is currently down")
else -> IOException("API returned an invalid response")
}
@@ -25,7 +25,9 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import dev.msfjarvis.claw.android.R
import dev.msfjarvis.claw.common.posts.PostActions
import dev.msfjarvis.claw.common.ui.decorations.MonthHeader
import dev.msfjarvis.claw.model.UIPost
@@ -46,17 +48,27 @@ fun DatabasePosts(
Column(modifier = Modifier.align(Alignment.Center)) {
Icon(
imageVector = Icons.Filled.Inbox,
contentDescription = "Empty inbox icon",
contentDescription = stringResource(R.string.empty_inbox_icon),
modifier = Modifier.align(Alignment.CenterHorizontally).size(36.dp),
)
Text(text = "No saved posts", style = MaterialTheme.typography.headlineSmall)
Text(
text = stringResource(R.string.no_saved_posts),
style = MaterialTheme.typography.headlineSmall,
)
}
} else {
LazyColumn(state = listState, contentPadding = contentPadding) {
items.forEach { (month, posts) ->
stickyHeader(contentType = "month-header") { MonthHeader(label = month) }
items(items = posts, key = { it.shortId }, contentType = { "LobstersItem" }) { item ->
LobstersListItem(item = item, postActions = postActions)
val isSaved = postActions.isPostSaved(item)
val isRead = postActions.isPostRead(item)
LobstersListItem(
item = item,
isSaved = isSaved,
isRead = isRead,
postActions = postActions,
)
HorizontalDivider()
}
}
@@ -13,14 +13,25 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.unit.dp
import dev.msfjarvis.claw.android.glance.samplePosts
import dev.msfjarvis.claw.common.posts.LobstersCard
import dev.msfjarvis.claw.common.posts.PostActions
import dev.msfjarvis.claw.common.posts.TEST_POST_ACTIONS
import dev.msfjarvis.claw.common.theme.LobstersTheme
import dev.msfjarvis.claw.common.ui.preview.ThemePreviews
import dev.msfjarvis.claw.model.UIPost
import me.saket.swipe.SwipeAction
import me.saket.swipe.SwipeableActionsBox
@Composable
fun LobstersListItem(item: UIPost, postActions: PostActions, modifier: Modifier = Modifier) {
fun LobstersListItem(
item: UIPost,
isSaved: Boolean,
isRead: Boolean,
postActions: PostActions,
modifier: Modifier = Modifier,
) {
val commentsAction =
SwipeAction(
icon = rememberVectorPainter(Icons.AutoMirrored.Filled.Reply),
@@ -33,7 +44,31 @@ fun LobstersListItem(item: UIPost, postActions: PostActions, modifier: Modifier
background = MaterialTheme.colorScheme.tertiary,
onSwipe = { postActions.share(item) },
)
SwipeableActionsBox(startActions = listOf(shareAction), endActions = listOf(commentsAction)) {
LobstersCard(post = item, postActions = postActions, modifier = modifier)
SwipeableActionsBox(
startActions = listOf(shareAction),
endActions = listOf(commentsAction),
swipeThreshold = 80.dp,
backgroundUntilSwipeThreshold = MaterialTheme.colorScheme.surfaceVariant,
) {
LobstersCard(
post = item,
isSaved = isSaved,
isRead = isRead,
postActions = postActions,
modifier = modifier,
)
}
}
@ThemePreviews
@Composable
private fun ItemPreview() {
LobstersTheme {
LobstersListItem(
item = samplePosts(1).first(),
isSaved = true,
isRead = true,
postActions = TEST_POST_ACTIONS,
)
}
}
@@ -31,15 +31,16 @@ import androidx.paging.PagingData
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.itemContentType
import androidx.paging.compose.itemKey
import dev.msfjarvis.claw.android.glance.samplePosts
import dev.msfjarvis.claw.common.posts.PostActions
import dev.msfjarvis.claw.common.posts.TEST_POST
import dev.msfjarvis.claw.common.posts.TEST_POST_ACTIONS
import dev.msfjarvis.claw.common.theme.LobstersTheme
import dev.msfjarvis.claw.common.ui.NetworkError
import dev.msfjarvis.claw.common.ui.ProgressBar
import dev.msfjarvis.claw.common.ui.preview.DevicePreviews
import dev.msfjarvis.claw.model.UIPost
import kotlinx.collections.immutable.ImmutableSet
import kotlinx.collections.immutable.persistentSetOf
import kotlinx.coroutines.flow.MutableStateFlow
@OptIn(ExperimentalMaterial3Api::class)
@@ -49,6 +50,7 @@ fun NetworkPosts(
listState: LazyListState,
postActions: PostActions,
contentPadding: PaddingValues,
filteredTags: ImmutableSet<String>,
modifier: Modifier = Modifier,
) {
ReportDrawnWhen { lazyPagingItems.itemCount > 0 }
@@ -77,13 +79,24 @@ fun NetworkPosts(
LazyColumn(contentPadding = contentPadding, state = listState) {
items(
count = lazyPagingItems.itemCount,
key = lazyPagingItems.itemKey { it.shortId },
// Intentionally keyed on just the index to avoid crashes
key = { index -> index },
contentType = lazyPagingItems.itemContentType { "LobstersItem" },
) { index ->
val item = lazyPagingItems[index]
if (item != null) {
LobstersListItem(item = item, postActions = postActions)
HorizontalDivider()
val shouldShowPost = item.tags.none { tag -> filteredTags.contains(tag) }
if (shouldShowPost) {
val isSaved = postActions.isPostSaved(item)
val isRead = postActions.isPostRead(item)
LobstersListItem(
item = item,
isSaved = isSaved,
isRead = isRead,
postActions = postActions,
)
HorizontalDivider()
}
}
}
if (lazyPagingItems.loadState.append == LoadState.Loading) {
@@ -104,7 +117,7 @@ fun NetworkPosts(
@DevicePreviews
@Composable
private fun ListPreview() {
val items = List(20) { TEST_POST.copy(shortId = "${TEST_POST.shortId}${it}") }
val items = samplePosts(20) { this.copy(shortId = "${this.shortId}${it}") }
val flow = MutableStateFlow(PagingData.from(items))
LobstersTheme {
NetworkPosts(
@@ -112,6 +125,7 @@ private fun ListPreview() {
listState = rememberLazyListState(),
postActions = TEST_POST_ACTIONS,
contentPadding = PaddingValues(),
filteredTags = persistentSetOf(),
)
}
}
@@ -10,19 +10,16 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.isTraversalGroup
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import androidx.paging.compose.LazyPagingItems
import dev.msfjarvis.claw.common.posts.PostActions
import dev.msfjarvis.claw.common.ui.SearchBar
import dev.msfjarvis.claw.model.UIPost
import kotlinx.collections.immutable.ImmutableSet
@Composable
fun SearchList(
@@ -32,16 +29,14 @@ fun SearchList(
searchQuery: String,
setSearchQuery: (String) -> Unit,
contentPadding: PaddingValues,
filteredTags: ImmutableSet<String>,
modifier: Modifier = Modifier,
) {
val triggerSearch = { query: String ->
setSearchQuery(query)
lazyPagingItems.refresh()
}
Column(
modifier =
modifier.systemBarsPadding().semantics { isTraversalGroup = true }.zIndex(1f).fillMaxWidth()
) {
Column(modifier.padding(contentPadding).fillMaxWidth()) {
SearchBar(
value = searchQuery,
onValueChange = setSearchQuery,
@@ -52,7 +47,8 @@ fun SearchList(
lazyPagingItems = lazyPagingItems,
listState = listState,
postActions = postActions,
contentPadding = contentPadding,
contentPadding = PaddingValues(0.dp),
filteredTags = filteredTags,
)
}
}
@@ -1,45 +0,0 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android.ui.login
import android.webkit.CookieManager
import android.webkit.WebView
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import com.google.accompanist.web.AccompanistWebViewClient
import com.google.accompanist.web.WebView
import com.google.accompanist.web.rememberWebViewState
import dev.msfjarvis.claw.api.LobstersApi
import io.github.aakira.napier.Napier
import okhttp3.HttpUrl.Companion.toHttpUrl
@Suppress("Unused")
@Composable
fun LobstersLoginWebView(modifier: Modifier = Modifier) {
val state = rememberWebViewState(url = "https://lobste.rs/login")
val webClient = remember {
object : AccompanistWebViewClient() {
override fun onPageFinished(view: WebView, url: String?) {
super.onPageFinished(view, url)
Napier.d(tag = "WebView") { "onPageFinished(url=$url)" }
if (url != null && url.toHttpUrl() == LobstersApi.BASE_URL.toHttpUrl()) {
val cookies = CookieManager.getInstance().getCookie(LobstersApi.BASE_URL)
val trap =
cookies
.split(";")
.associate {
val split = it.split("=")
split[0] to split[1]
}["lobsters_trap"]
Napier.d(tag = "WebView") { "lobsters_trap=${trap}" }
}
}
}
}
WebView(state = state, client = webClient, modifier = modifier, captureBackPresses = false)
}
@@ -15,49 +15,56 @@ import androidx.compose.material.icons.outlined.FavoriteBorder
import androidx.compose.material.icons.outlined.NewReleases
import androidx.compose.material.icons.outlined.Whatshot
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.navigation3.runtime.NavKey
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable
sealed interface Destination : Parcelable
interface TopLevelDestination
@Parcelize @Serializable data object Hottest : Destination
interface NonStackable
@Parcelize @Serializable data object Newest : Destination
@Parcelize @Serializable data object Hottest : NavKey, TopLevelDestination, Parcelable
@Parcelize @Serializable data object Saved : Destination
@Parcelize @Serializable data object Newest : NavKey, TopLevelDestination, Parcelable
@Parcelize @Serializable data class Comments(val postId: String) : Destination
@Parcelize @Serializable data object Saved : NavKey, TopLevelDestination, Parcelable
@Parcelize @Serializable data class User(val username: String) : Destination
@Parcelize @Serializable data class Comments(val postId: String) : NavKey, Parcelable, NonStackable
@Parcelize @Serializable data object Search : Destination
@Parcelize @Serializable data class User(val username: String) : NavKey, Parcelable, NonStackable
@Parcelize @Serializable data object Settings : Destination
@Parcelize @Serializable data object Search : NavKey, Parcelable
@Parcelize @Serializable data object AboutLibraries : Destination
@Parcelize @Serializable data object Settings : NavKey, Parcelable
@Parcelize @Serializable data object AboutLibraries : NavKey, Parcelable
@Parcelize @Serializable data object TagFiltering : NavKey, Parcelable
@Parcelize @Serializable data object Login : NavKey, Parcelable
enum class AppDestinations(
val icon: ImageVector,
val label: String,
val destination: Destination,
val navKey: NavKey,
val selectedIcon: ImageVector,
) {
HOTTEST(
icon = Icons.Outlined.Whatshot,
label = "Hottest",
destination = Hottest,
navKey = Hottest,
selectedIcon = Icons.Filled.Whatshot,
),
NEWEST(
icon = Icons.Outlined.NewReleases,
label = "Newest",
destination = Newest,
navKey = Newest,
selectedIcon = Icons.Filled.NewReleases,
),
SAVED(
icon = Icons.Outlined.FavoriteBorder,
label = "Saved",
destination = Saved,
navKey = Saved,
selectedIcon = Icons.Filled.Favorite,
),
}
@@ -1,30 +0,0 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android.ui.navigation
import androidx.navigation.NavDestination
import androidx.navigation.NavDestination.Companion.hasRoute
import androidx.navigation.NavDestination.Companion.hierarchy
import kotlinx.collections.immutable.ImmutableList
/**
* Walk through the [NavDestination]'s [hierarchy] to see if it has any destination that matches the
* route defined by [dest].
*/
fun NavDestination?.matches(dest: Destination): Boolean {
return this?.hierarchy?.any { it.hasRoute(dest::class) } == true
}
/** Check if this [NavDestination] [matches] any of the potential navigation [destinations]. */
fun NavDestination?.any(destinations: ImmutableList<Destination>): Boolean {
return destinations.any { this?.matches(it) == true }
}
/** Check if this [NavDestination] [matches] none of the potential navigation [destinations]. */
fun NavDestination?.none(destinations: ImmutableList<Destination>): Boolean {
return destinations.none { this?.matches(it) == true }
}
@@ -0,0 +1,241 @@
/** Taken from https://github.com/getsentry/sentry-java/pull/5023 */
@file:Suppress("UnstableApiUsage") // Sentry internal APIs are used by this module
package dev.msfjarvis.claw.android.ui.navigation
import android.annotation.SuppressLint
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.snapshotFlow
import io.sentry.Breadcrumb
import io.sentry.BuildConfig
import io.sentry.Hint
import io.sentry.IScopes
import io.sentry.ITransaction
import io.sentry.ScopesAdapter
import io.sentry.SentryIntegrationPackageStorage
import io.sentry.SentryLevel
import io.sentry.SentryOptions
import io.sentry.SpanStatus
import io.sentry.TransactionContext
import io.sentry.TransactionOptions
import io.sentry.TypeCheckHint
import io.sentry.protocol.TransactionNameSource
import io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
private const val TRACE_ORIGIN = "auto.navigation.navigation3"
private const val NAVIGATION_OP = "navigation"
/** Holder for the active navigation transaction in a composition scope. */
internal class NavigationTransactionHolder {
internal var activeTransaction: ITransaction? = null
}
/**
* A Composable that observes a back stack [List] and captures a [Breadcrumb] and starts an
* [ITransaction] for each navigation event, sending them to Sentry.
*
* This integration is designed for Android Navigation 3 which uses a back stack-based approach with
* Compose state observation instead of traditional listeners.
*
* Each invocation of this composable maintains independent transaction state, allowing multiple
* navigation instances (e.g., in split-screen or multi-pane layouts) to coexist without
* interference.
*
* @param T The type of keys in the back stack
* @param backStack The current back stack to observe for navigation changes
* @param enableNavigationBreadcrumbs Whether the integration should capture breadcrumbs for
* navigation events.
* @param enableNavigationTracing Whether the integration should start a new idle [ITransaction]
* with [SentryOptions.idleTimeout] for navigation events.
* @param keyToRoute A function to extract a route name from a back stack key. Defaults to
* [Any.toString].
* @param scopes The [IScopes] instance to use for capturing events. Defaults to the singleton
* instance.
*/
@Composable
fun <T> SentryNavigation3Traced(
@SuppressLint("ComposeUnstableCollections") backStack: List<T>,
enableNavigationBreadcrumbs: Boolean = true,
enableNavigationTracing: Boolean = true,
keyToRoute: (T) -> String? = { it.toString() },
scopes: IScopes = ScopesAdapter.getInstance(),
) {
val backStackSnapshot by rememberUpdatedState(backStack)
val enableBreadcrumbsSnapshot by rememberUpdatedState(enableNavigationBreadcrumbs)
val enableTracingSnapshot by rememberUpdatedState(enableNavigationTracing)
val keyToRouteSnapshot by rememberUpdatedState(keyToRoute)
val scopesSnapshot by rememberUpdatedState(scopes)
val transactionHolder = remember { NavigationTransactionHolder() }
DisposableEffect(Unit) {
addIntegrationToSdkVersion("Navigation3")
val observer =
SentryBackStackObserver(
enableNavigationBreadcrumbs = enableBreadcrumbsSnapshot,
enableNavigationTracing = enableTracingSnapshot,
keyToRoute = keyToRouteSnapshot,
scopes = scopesSnapshot,
transactionHolder = transactionHolder,
)
@SuppressLint("RawDispatchersUse")
val scope =
kotlinx.coroutines.CoroutineScope(
kotlinx.coroutines.Dispatchers.Main + kotlinx.coroutines.SupervisorJob()
)
@SuppressLint("DenyListedApi")
val job =
snapshotFlow { backStackSnapshot }
.drop(1) // Skip initial state
.onEach { currentStack ->
val currentKey = currentStack.lastOrNull()
val previousKey = observer.previousKey
if (currentKey != null && currentKey != previousKey) {
observer.handleNavigation(currentKey, currentStack)
observer.previousKey = currentKey
}
}
.launchIn(scope)
onDispose { job.cancel() }
}
}
/** Internal observer that monitors back stack changes and creates Sentry events. */
internal class SentryBackStackObserver<T>(
private val enableNavigationBreadcrumbs: Boolean,
private val enableNavigationTracing: Boolean,
private val keyToRoute: (T) -> String?,
private val scopes: IScopes,
private val transactionHolder: NavigationTransactionHolder,
) {
internal var previousKey: T? = null
private val isPerformanceEnabled: Boolean
get() = scopes.options.isTracingEnabled && enableNavigationTracing
init {
SentryIntegrationPackageStorage.getInstance()
.addPackage("maven:io.sentry:sentry-android-navigation3", BuildConfig.VERSION_NAME)
}
internal fun handleNavigation(currentKey: T, currentStack: List<T>) {
val currentRoute = keyToRoute(currentKey)
if (currentRoute != null) {
addBreadcrumb(currentRoute, currentStack)
if (scopes.options.isEnableScreenTracking) {
scopes.configureScope { it.screen = currentRoute }
}
startTracing(currentRoute, currentStack)
}
}
private fun addBreadcrumb(toRoute: String, currentStack: List<T>) {
if (!enableNavigationBreadcrumbs) {
return
}
val breadcrumb =
Breadcrumb().apply {
type = NAVIGATION_OP
category = NAVIGATION_OP
val fromKey = previousKey
if (fromKey != null) {
val fromRoute = keyToRoute(fromKey)
if (fromRoute != null) {
data["from"] = fromRoute
}
}
data["to"] = toRoute
// Capture back stack keys as a list
val backStackKeys = currentStack.mapNotNull { keyToRoute(it) }
if (backStackKeys.isNotEmpty()) {
data["back_stack"] = backStackKeys
}
level = SentryLevel.INFO
}
val hint = Hint()
hint.set(TypeCheckHint.ANDROID_NAV_DESTINATION, toRoute)
scopes.addBreadcrumb(breadcrumb, hint)
}
private fun startTracing(routeName: String, currentStack: List<T>) {
if (!isPerformanceEnabled) {
io.sentry.util.TracingUtils.startNewTrace(scopes)
return
}
// Finish previous transaction in this navigation scope
if (transactionHolder.activeTransaction != null) {
stopTracing()
}
val transactionOptions =
TransactionOptions().also {
it.isWaitForChildren = true
it.idleTimeout = scopes.options.idleTimeout
// Set deadline timeout based on configured option
val deadlineTimeoutMillis = scopes.options.deadlineTimeout
// No deadline when zero or negative value is set
it.deadlineTimeout = if (deadlineTimeoutMillis <= 0) null else deadlineTimeoutMillis
it.isTrimEnd = true
}
val transaction =
scopes.startTransaction(
TransactionContext(routeName, TransactionNameSource.ROUTE, NAVIGATION_OP),
transactionOptions,
)
transaction.spanContext.origin = TRACE_ORIGIN
// Capture back stack keys as data
val backStackKeys = currentStack.mapNotNull { keyToRoute(it) }
if (backStackKeys.isNotEmpty()) {
transaction.setData("back_stack", backStackKeys)
}
scopes.configureScope { scope ->
scope.withTransaction { tx ->
if (tx == null) {
scope.transaction = transaction
}
}
}
transactionHolder.activeTransaction = transaction
}
private fun stopTracing() {
val status = transactionHolder.activeTransaction?.status ?: SpanStatus.OK
transactionHolder.activeTransaction?.finish(status)
// clear transaction from scope so others can bind to it
scopes.configureScope { scope ->
scope.withTransaction { tx ->
if (tx == transactionHolder.activeTransaction) {
scope.clearTransaction()
}
}
}
transactionHolder.activeTransaction = null
}
}
@@ -6,28 +6,27 @@
*/
package dev.msfjarvis.claw.android.ui.screens
import android.content.Intent
import androidx.activity.compose.BackHandler
import androidx.activity.compose.LocalActivity
import androidx.annotation.VisibleForTesting
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Tune
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
import androidx.compose.material3.adaptive.navigation3.ListDetailSceneStrategy
import androidx.compose.material3.adaptive.navigation3.rememberListDetailSceneStrategy
import androidx.compose.material3.windowsizeclass.WindowSizeClass
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -35,29 +34,25 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.platform.UriHandler
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import androidx.navigation.toRoute
import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.runtime.rememberNavBackStack
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
import androidx.navigation3.ui.NavDisplay
import androidx.paging.compose.collectAsLazyPagingItems
import com.deliveryhero.whetstone.compose.injectedViewModel
import com.mikepenz.aboutlibraries.ui.compose.android.produceLibraries
import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer
import dev.chrisbanes.haze.HazeState
import dev.chrisbanes.haze.hazeSource
import dev.msfjarvis.claw.android.MainActivity
import dev.msfjarvis.claw.android.R
import dev.msfjarvis.claw.android.SearchActivity
import dev.msfjarvis.claw.android.ui.PostActions
import dev.msfjarvis.claw.android.ui.decorations.ClawAppBar
import dev.msfjarvis.claw.android.ui.decorations.ClawNavigationBar
import dev.msfjarvis.claw.android.ui.decorations.ClawNavigationRail
import dev.msfjarvis.claw.android.ui.decorations.NavigationItem
@@ -68,216 +63,308 @@ import dev.msfjarvis.claw.android.ui.navigation.AppDestinations
import dev.msfjarvis.claw.android.ui.navigation.ClawNavigationType
import dev.msfjarvis.claw.android.ui.navigation.Comments
import dev.msfjarvis.claw.android.ui.navigation.Hottest
import dev.msfjarvis.claw.android.ui.navigation.Login
import dev.msfjarvis.claw.android.ui.navigation.Newest
import dev.msfjarvis.claw.android.ui.navigation.NonStackable
import dev.msfjarvis.claw.android.ui.navigation.Saved
import dev.msfjarvis.claw.android.ui.navigation.Search
import dev.msfjarvis.claw.android.ui.navigation.SentryNavigation3Traced
import dev.msfjarvis.claw.android.ui.navigation.Settings
import dev.msfjarvis.claw.android.ui.navigation.TagFiltering
import dev.msfjarvis.claw.android.ui.navigation.TopLevelDestination
import dev.msfjarvis.claw.android.ui.navigation.User
import dev.msfjarvis.claw.android.ui.navigation.any
import dev.msfjarvis.claw.android.ui.navigation.none
import dev.msfjarvis.claw.android.viewmodel.ClawViewModel
import dev.msfjarvis.claw.android.viewmodel.SettingsViewModel
import dev.msfjarvis.claw.common.comments.CommentsPage
import dev.msfjarvis.claw.common.comments.HTMLConverter
import dev.msfjarvis.claw.common.urllauncher.UrlLauncher
import dev.msfjarvis.claw.common.tags.TagFilterViewModel
import dev.msfjarvis.claw.common.tags.TagList
import dev.msfjarvis.claw.common.user.UserProfile
import dev.zacsweers.metrox.viewmodel.metroViewModel
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toPersistentList
import kotlinx.collections.immutable.persistentSetOf
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun LobstersPostsScreen(
urlLauncher: UrlLauncher,
htmlConverter: HTMLConverter,
uriHandler: UriHandler,
windowSizeClass: WindowSizeClass,
setWebUri: (String?) -> Unit,
modifier: Modifier = Modifier,
viewModel: ClawViewModel = injectedViewModel(),
deepLinkDestination: NavKey? = null,
clearDeepLink: () -> Unit = {},
viewModel: ClawViewModel = metroViewModel(),
settingsViewModel: SettingsViewModel = metroViewModel(),
tagFilterViewModel: TagFilterViewModel = metroViewModel(key = "tag_filter"),
) {
val navigationType = ClawNavigationType.fromSize(windowSizeClass.widthSizeClass)
val libraries by produceLibraries()
val backStack = rememberNavBackStack(Hottest)
val listDetailStrategy = rememberListDetailSceneStrategy<NavKey>()
// region Pain
val context = LocalContext.current
val activity = LocalActivity.current
val hottestListState = rememberLazyListState()
val newestListState = rememberLazyListState()
val savedListState = rememberLazyListState()
val navController = rememberNavController()
val navBackStackEntry = navController.currentBackStackEntryAsState().value
val currentDestination = navBackStackEntry?.destination
val coroutineScope = rememberCoroutineScope()
val snackbarHostState = remember { SnackbarHostState() }
val postActions = remember {
PostActions(context, urlLauncher, viewModel) { navController.navigate(Comments(it)) }
}
val hazeState = remember { HazeState() }
val hottestPosts = viewModel.hottestPosts.collectAsLazyPagingItems()
val newestPosts = viewModel.newestPosts.collectAsLazyPagingItems()
val savedPosts by viewModel.savedPostsByMonth.collectAsStateWithLifecycle(persistentMapOf())
val savedPostsCount by viewModel.savedPostsCount.collectAsStateWithLifecycle(0L)
val navigationType = ClawNavigationType.fromSize(windowSizeClass.widthSizeClass)
val filteredTags by tagFilterViewModel.filteredTags.collectAsStateWithLifecycle(persistentSetOf())
val isLoggedIn by settingsViewModel.isLoggedIn.collectAsStateWithLifecycle(false)
val username by settingsViewModel.username.collectAsStateWithLifecycle(null)
val postIdOverride = activity?.intent?.extras?.getString(MainActivity.NAVIGATION_KEY)
LaunchedEffect(Unit) {
if (postIdOverride != null) {
navController.navigate(Comments(postIdOverride))
LaunchedEffect(deepLinkDestination) {
if (deepLinkDestination != null) {
navigateTo(backStack, deepLinkDestination, allowStacking = true)
clearDeepLink()
}
}
val navItems =
persistentListOf(
NavigationItem(AppDestinations.HOTTEST) {
coroutineScope.launch {
if (hottestPosts.itemCount > 0) hottestListState.animateScrollToItem(index = 0)
}
coroutineScope.launch { hottestListState.animateScrollToItem(index = 0) }
},
NavigationItem(AppDestinations.NEWEST) {
coroutineScope.launch {
if (newestPosts.itemCount > 0) newestListState.animateScrollToItem(index = 0)
}
coroutineScope.launch { newestListState.animateScrollToItem(index = 0) }
},
NavigationItem(AppDestinations.SAVED) {
coroutineScope.launch { if (savedPosts.isNotEmpty()) savedListState.scrollToItem(0) }
},
)
val navDestinations = navItems.map(NavigationItem::destination).toPersistentList()
// endregion
val postActions = remember {
PostActions(context, uriHandler, viewModel) {
navigateTo(backStack, Comments(it), allowStacking = false)
}
}
BackHandler(enabled = backStack.size > 1) { popBackStack(backStack) }
Scaffold(
topBar = {
TopAppBar(
modifier = Modifier.shadow(8.dp),
navigationIcon = {
if (
navController.previousBackStackEntry != null && currentDestination.none(navDestinations)
) {
IconButton(onClick = { if (!navController.popBackStack()) activity?.finish() }) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Go back to previous screen",
)
}
} else {
Icon(
painter = painterResource(id = R.drawable.ic_launcher_foreground),
contentDescription = "The app icon for Claw",
modifier = Modifier.size(48.dp),
)
}
},
title = {
if (currentDestination.any(navDestinations)) {
Text(text = stringResource(R.string.app_name), fontWeight = FontWeight.Bold)
}
},
actions = {
if (currentDestination.any(navDestinations)) {
IconButton(
onClick = { context.startActivity(Intent(context, SearchActivity::class.java)) }
) {
Icon(imageVector = Icons.Filled.Search, contentDescription = "Search posts")
}
IconButton(onClick = { navController.navigate(Settings) }) {
Icon(imageVector = Icons.Filled.Tune, contentDescription = "Settings")
}
}
},
ClawAppBar(
activity = activity,
isTopLevel = backStack.lastOrNull() is TopLevelDestination,
navigateTo = { destination -> navigateTo(backStack, destination) },
popBackStack = { popBackStack(backStack) },
)
},
bottomBar = {
val currentDestination = backStack.lastOrNull()
AnimatedVisibility(visible = navigationType == ClawNavigationType.BOTTOM_NAVIGATION) {
ClawNavigationBar(
navController = navController,
items = navItems,
isVisible = currentDestination.any(navDestinations),
currentNavKey = currentDestination,
navigateTo = { destination -> navigateTo(backStack, destination) },
isVisible = backStack.lastOrNull() is TopLevelDestination,
hazeState = hazeState,
)
}
},
snackbarHost = { SnackbarHost(snackbarHostState) },
modifier = modifier.semantics { testTagsAsResourceId = true },
modifier = Modifier.semantics { testTagsAsResourceId = true },
) { contentPadding ->
SentryNavigation3Traced(backStack = backStack)
Row {
AnimatedVisibility(visible = navigationType == ClawNavigationType.NAVIGATION_RAIL) {
val currentDestination = backStack.lastOrNull()
ClawNavigationRail(
navController = navController,
items = navItems,
isVisible = currentDestination.any(navDestinations),
currentNavKey = currentDestination,
navigateTo = { destination -> navigateTo(backStack, destination) },
isVisible = backStack.lastOrNull() is TopLevelDestination,
)
}
NavHost(
navController = navController,
startDestination = Hottest,
// Make animations 2x faster than default specs
enterTransition = { fadeIn(animationSpec = tween(350)) },
exitTransition = { fadeOut(animationSpec = tween(350)) },
modifier = Modifier.hazeSource(hazeState),
) {
composable<Hottest> {
setWebUri("https://lobste.rs/")
NetworkPosts(
lazyPagingItems = hottestPosts,
listState = hottestListState,
postActions = postActions,
contentPadding = contentPadding,
)
}
composable<Newest> {
setWebUri("https://lobste.rs/")
NetworkPosts(
lazyPagingItems = newestPosts,
listState = newestListState,
postActions = postActions,
contentPadding = contentPadding,
)
}
composable<Saved> {
setWebUri(null)
DatabasePosts(
items = savedPosts,
listState = savedListState,
postActions = postActions,
contentPadding = contentPadding,
)
}
composable<Comments> { backStackEntry ->
val postId = backStackEntry.toRoute<Comments>().postId
setWebUri("https://lobste.rs/s/$postId")
CommentsPage(
postId = postId,
postActions = postActions,
htmlConverter = htmlConverter,
getSeenComments = viewModel::getSeenComments,
markSeenComments = viewModel::markSeenComments,
contentPadding = contentPadding,
openUserProfile = { navController.navigate(User(it)) },
)
}
composable<User> { backStackEntry ->
val username = backStackEntry.toRoute<User>().username
setWebUri("https://lobste.rs/u/$username")
UserProfile(
username = username,
getProfile = viewModel::getUserProfile,
contentPadding = contentPadding,
openUserProfile = { navController.navigate(User(it)) },
)
}
composable<Settings> {
SettingsScreen(
openInputStream = context.contentResolver::openInputStream,
openOutputStream = context.contentResolver::openOutputStream,
openLibrariesScreen = { navController.navigate(AboutLibraries) },
importPosts = viewModel::importPosts,
exportPostsAsJson = viewModel::exportPostsAsJson,
exportPostsAsHtml = viewModel::exportPostsAsHtml,
snackbarHostState = snackbarHostState,
contentPadding = contentPadding,
modifier = Modifier.fillMaxSize(),
)
}
composable<AboutLibraries> {
LibrariesContainer(contentPadding = contentPadding, modifier = Modifier.fillMaxSize())
}
}
NavDisplay(
backStack = backStack,
modifier = modifier.hazeSource(hazeState),
sceneStrategies = listOf(listDetailStrategy),
entryDecorators = listOf(rememberSaveableStateHolderNavEntryDecorator()),
onBack = {
if (popBackStack(backStack) == null) {
activity?.finish()
}
},
predictivePopTransitionSpec = {
slideInHorizontally(initialOffsetX = { -it }, animationSpec = tween(200)) togetherWith
slideOutHorizontally(targetOffsetX = { it }, animationSpec = tween(200))
},
entryProvider =
entryProvider {
entry<Hottest>(
metadata = ListDetailSceneStrategy.listPane(detailPlaceholder = { Placeholder() })
) {
setWebUri("https://lobste.rs/")
val hottestPosts = viewModel.hottestPosts.collectAsLazyPagingItems()
NetworkPosts(
lazyPagingItems = hottestPosts,
listState = hottestListState,
postActions = postActions,
contentPadding = contentPadding,
filteredTags = filteredTags,
)
}
entry<Newest>(
metadata = ListDetailSceneStrategy.listPane(detailPlaceholder = { Placeholder() })
) {
setWebUri("https://lobste.rs/")
val newestPosts = viewModel.newestPosts.collectAsLazyPagingItems()
NetworkPosts(
lazyPagingItems = newestPosts,
listState = newestListState,
postActions = postActions,
contentPadding = contentPadding,
filteredTags = filteredTags,
)
}
entry<Saved>(
metadata = ListDetailSceneStrategy.listPane(detailPlaceholder = { Placeholder() })
) {
setWebUri(null)
DatabasePosts(
items = savedPosts,
listState = savedListState,
postActions = postActions,
contentPadding = contentPadding,
)
}
entry<Comments>(metadata = ListDetailSceneStrategy.detailPane()) { dest ->
val commentsPostActions = remember {
PostActions(context, uriHandler, viewModel) {
navigateTo(backStack, Comments(it), allowStacking = true)
}
}
CommentsPage(
postId = dest.postId,
postActions = commentsPostActions,
contentPadding = contentPadding,
openUserProfile = { navigateTo(backStack, User(it)) },
)
}
entry<User>(
metadata =
NavDisplay.transitionSpec {
slideInHorizontally(
initialOffsetX = { it },
animationSpec = tween(200),
) togetherWith ExitTransition.KeepUntilTransitionsFinished
} +
NavDisplay.popTransitionSpec {
// Slide old content down, revealing the new content in place underneath
EnterTransition.None togetherWith
slideOutHorizontally(targetOffsetX = { it }, animationSpec = tween(200))
} +
ListDetailSceneStrategy.extraPane()
) { dest ->
UserProfile(
username = dest.username,
contentPadding = contentPadding,
openUserProfile = { navigateTo(backStack, User(it)) },
)
}
entry<Settings>(metadata = ListDetailSceneStrategy.extraPane()) {
SettingsScreen(
openInputStream = context.contentResolver::openInputStream,
openOutputStream = context.contentResolver::openOutputStream,
openLibrariesScreen = { navigateTo(backStack, AboutLibraries) },
openRepository = {
uriHandler.openUri("https://github.com/msfjarvis/compose-lobsters")
},
openTagFiltering = { navigateTo(backStack, TagFiltering) },
openLoginScreen = { navigateTo(backStack, Login) },
onLogout = { settingsViewModel.logout() },
isLoggedIn = isLoggedIn,
username = username,
importPosts = viewModel::importPosts,
exportPostsAsJson = viewModel::exportPostsAsJson,
exportPostsAsHtml = viewModel::exportPostsAsHtml,
savedPostsCount = savedPostsCount,
snackbarHostState = snackbarHostState,
contentPadding = contentPadding,
modifier = Modifier.fillMaxSize(),
)
}
entry<Login>(metadata = ListDetailSceneStrategy.extraPane()) {
LoginScreen(
onLoginSuccess = settingsViewModel::saveCookie,
popBackStack = { popBackStack(backStack) },
modifier = Modifier.fillMaxSize(),
)
}
entry<Search>(metadata = ListDetailSceneStrategy.extraPane()) {
setWebUri("https://lobste.rs/search")
SearchScreen(postActions = postActions, contentPadding = contentPadding)
}
entry<AboutLibraries>(metadata = ListDetailSceneStrategy.extraPane()) {
LibrariesContainer(
libraries = libraries,
contentPadding = contentPadding,
modifier = Modifier.fillMaxSize(),
)
}
entry<TagFiltering> { TagList(contentPadding = contentPadding) }
},
)
}
}
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun navigateTo(
backStack: MutableList<NavKey>,
destination: NavKey,
allowStacking: Boolean = false,
) {
if (destination is TopLevelDestination) {
backStack.clear()
if (destination != Hottest) {
backStack.add(Hottest)
}
backStack.add(destination)
return
}
if (destination is NonStackable) {
if (allowStacking && destination is Comments) {
val lastItem = backStack.lastOrNull()
if (lastItem is Comments && lastItem.postId == destination.postId) {
return
}
backStack.add(destination)
return
}
val existingEntry = backStack.firstOrNull {
it is NonStackable && it::class.java.isAssignableFrom(destination::class.java)
}
if (existingEntry != null) {
backStack.remove(existingEntry)
}
}
backStack.add(destination)
}
private fun popBackStack(backStack: MutableList<NavKey>): NavKey? {
if (backStack.size <= 1) {
return null
}
return backStack.removeAt(backStack.lastIndex)
}
@Composable
private fun Placeholder(modifier: Modifier = Modifier) {
Text(text = stringResource(R.string.no_post_selected), modifier = modifier)
}
@@ -0,0 +1,131 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android.ui.screens
import android.annotation.SuppressLint
import android.graphics.Bitmap
import android.webkit.CookieManager
import android.webkit.WebResourceError
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
private const val LOGIN_URL = "https://lobste.rs/login"
private const val LOBSTERS_URL = "https://lobste.rs"
internal fun parseAuthenticatedUsername(usernameResult: String): String? {
return usernameResult.removeSurrounding("\"").takeUnless { it.isBlank() || it == "null" }
}
@Composable
fun LoginScreen(
onLoginSuccess: (String, String) -> Unit,
popBackStack: () -> Unit,
modifier: Modifier = Modifier,
) {
var isLoading by remember { mutableStateOf(true) }
var hasError by remember { mutableStateOf(false) }
var webViewRef by remember { mutableStateOf<WebView?>(null) }
Scaffold(modifier = modifier) { contentPadding ->
Box(modifier = Modifier.padding(contentPadding).fillMaxSize()) {
if (hasError) {
Column(
modifier = Modifier.align(Alignment.Center),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
Text("Failed to load login page.")
Spacer(modifier = Modifier.height(12.dp))
Button(
onClick = {
hasError = false
isLoading = true
webViewRef?.loadUrl(LOGIN_URL)
}
) {
Text("Retry")
}
}
} else {
AndroidView(
factory = { context ->
WebView(context)
.apply {
@SuppressLint("SetJavaScriptEnabled")
settings.javaScriptEnabled = true
webViewClient =
object : WebViewClient() {
override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) {
isLoading = true
}
override fun onPageFinished(view: WebView, url: String) {
isLoading = false
if (!url.contains("/login")) {
@SuppressLint("DeprecatedCall")
view.evaluateJavascript(
"document.body && document.body.getAttribute('data-username')"
) { usernameResult ->
val username = parseAuthenticatedUsername(usernameResult)
if (username != null) {
val cookie = CookieManager.getInstance().getCookie(LOBSTERS_URL)
if (cookie != null) {
onLoginSuccess(cookie, username)
popBackStack()
}
}
}
}
}
override fun onReceivedError(
view: WebView,
request: WebResourceRequest,
error: WebResourceError,
) {
if (request.isForMainFrame) {
isLoading = false
hasError = true
}
}
}
loadUrl(LOGIN_URL)
}
.also { webViewRef = it }
},
modifier = Modifier.fillMaxSize(),
)
}
if (isLoading && !hasError) {
LinearProgressIndicator(modifier = Modifier.fillMaxWidth().align(Alignment.TopCenter))
}
}
}
}
@@ -6,81 +6,40 @@
*/
package dev.msfjarvis.claw.android.ui.screens
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.toRoute
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.paging.compose.collectAsLazyPagingItems
import com.deliveryhero.whetstone.compose.injectedViewModel
import dev.msfjarvis.claw.android.ui.PostActions
import dev.msfjarvis.claw.android.ui.lists.SearchList
import dev.msfjarvis.claw.android.ui.navigation.Comments
import dev.msfjarvis.claw.android.ui.navigation.Search
import dev.msfjarvis.claw.android.ui.navigation.User
import dev.msfjarvis.claw.android.viewmodel.ClawViewModel
import dev.msfjarvis.claw.common.comments.CommentsPage
import dev.msfjarvis.claw.common.comments.HTMLConverter
import dev.msfjarvis.claw.common.urllauncher.UrlLauncher
import dev.msfjarvis.claw.common.user.UserProfile
import dev.msfjarvis.claw.common.posts.PostActions
import dev.msfjarvis.claw.common.tags.TagFilterViewModel
import dev.zacsweers.metrox.viewmodel.metroViewModel
import kotlinx.collections.immutable.toPersistentSet
@Composable
fun SearchScreen(
urlLauncher: UrlLauncher,
htmlConverter: HTMLConverter,
setWebUri: (String?) -> Unit,
postActions: PostActions,
contentPadding: PaddingValues,
modifier: Modifier = Modifier,
viewModel: ClawViewModel = injectedViewModel(),
viewModel: ClawViewModel = metroViewModel(),
tagFilterViewModel: TagFilterViewModel = metroViewModel(key = "tag_filter"),
) {
val context = LocalContext.current
val navController = rememberNavController()
val postActions = remember {
PostActions(context, urlLauncher, viewModel) { navController.navigate(Comments(it)) }
}
val listState = rememberLazyListState()
val searchResults = viewModel.searchResults.collectAsLazyPagingItems()
val filteredTags by tagFilterViewModel.filteredTags.collectAsStateWithLifecycle(emptySet())
Scaffold(modifier = modifier) { contentPadding ->
NavHost(navController = navController, startDestination = Search) {
composable<Search> {
setWebUri("https://lobste.rs/search")
SearchList(
lazyPagingItems = searchResults,
listState = listState,
postActions = postActions,
searchQuery = viewModel.searchQuery,
contentPadding = contentPadding,
setSearchQuery = { query -> viewModel.searchQuery = query },
)
}
composable<Comments> { backStackEntry ->
val postId = backStackEntry.toRoute<Comments>().postId
setWebUri("https://lobste.rs/s/$postId")
CommentsPage(
postId = postId,
postActions = postActions,
htmlConverter = htmlConverter,
getSeenComments = viewModel::getSeenComments,
markSeenComments = viewModel::markSeenComments,
openUserProfile = { navController.navigate(User(it)) },
contentPadding = contentPadding,
)
}
composable<User> { backStackEntry ->
val username = backStackEntry.toRoute<User>().username
setWebUri("https://lobste.rs/u/$username")
UserProfile(
username = username,
getProfile = viewModel::getUserProfile,
openUserProfile = { navController.navigate(User(it)) },
contentPadding = contentPadding,
)
}
}
}
SearchList(
lazyPagingItems = searchResults,
listState = listState,
postActions = postActions,
searchQuery = viewModel.searchQuery,
contentPadding = contentPadding,
setSearchQuery = { query -> viewModel.searchQuery = query },
filteredTags = filteredTags.toPersistentSet(),
modifier = modifier,
)
}
@@ -11,21 +11,22 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.LibraryBooks
import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material.icons.filled.Bookmarks
import androidx.compose.material.icons.filled.Code
import androidx.compose.material.icons.filled.FilterList
import androidx.compose.material.icons.filled.ImportExport
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
@@ -39,9 +40,13 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import dev.msfjarvis.claw.android.BuildConfig
import dev.msfjarvis.claw.android.R
import dev.msfjarvis.claw.common.theme.LobstersTheme
import dev.msfjarvis.claw.common.ui.preview.ThemePreviews
import io.sentry.Sentry
import java.io.InputStream
import java.io.OutputStream
import kotlinx.coroutines.CoroutineScope
@@ -50,67 +55,194 @@ import kotlinx.coroutines.launch
private const val JSON_MIME_TYPE = "application/json"
private const val HTML_MIME_TYPE = "application/html"
@Composable
fun SettingsScreen(
openLibrariesScreen: () -> Unit,
snackbarHostState: SnackbarHostState,
openInputStream: (Uri) -> InputStream?,
openOutputStream: (Uri) -> OutputStream?,
importPosts: suspend (InputStream) -> Unit,
exportPostsAsJson: suspend (OutputStream) -> Unit,
exportPostsAsHtml: suspend (OutputStream) -> Unit,
contentPadding: PaddingValues,
modifier: Modifier = Modifier,
) {
val coroutineScope = rememberCoroutineScope()
Box(modifier = modifier.padding(contentPadding)) {
Column {
ListItem(
headlineContent = { Text("Data transfer") },
leadingContent = {
Icon(
imageVector = Icons.Filled.ImportExport,
contentDescription = null,
modifier = Modifier.height(32.dp),
)
},
supportingContent = {
Row(
horizontalArrangement = Arrangement.spacedBy(32.dp),
modifier = Modifier.fillMaxWidth(),
) {
ImportPosts(openInputStream, coroutineScope, snackbarHostState, importPosts)
ExportPosts(
coroutineScope,
snackbarHostState,
openOutputStream,
exportPostsAsJson,
exportPostsAsHtml,
)
}
},
)
ListItem(
headlineContent = { Text("Libraries") },
leadingContent = {
Icon(
imageVector = Icons.AutoMirrored.Filled.LibraryBooks,
contentDescription = null,
modifier = Modifier.height(32.dp),
)
},
modifier = Modifier.clickable(onClick = openLibrariesScreen),
)
}
internal fun loggedInAccountText(username: String?): String {
return if (username.isNullOrBlank()) {
"Sign out of your lobste.rs account"
} else {
"Logged in as $username"
}
}
@Composable
private fun RowScope.ImportPosts(
fun SettingsScreen(
openLibrariesScreen: () -> Unit,
openRepository: () -> Unit,
openTagFiltering: () -> Unit,
openLoginScreen: () -> Unit,
onLogout: () -> Unit,
isLoggedIn: Boolean,
username: String?,
snackbarHostState: SnackbarHostState,
openInputStream: (Uri) -> InputStream?,
openOutputStream: (Uri) -> OutputStream?,
importPosts: suspend (InputStream) -> Result<Unit>,
exportPostsAsJson: suspend (OutputStream) -> Unit,
exportPostsAsHtml: suspend (OutputStream) -> Unit,
contentPadding: PaddingValues,
savedPostsCount: Long,
modifier: Modifier = Modifier,
) {
val coroutineScope = rememberCoroutineScope()
Column(modifier.padding(contentPadding)) {
// Account Section
SectionHeader(title = "Account")
if (isLoggedIn) {
ListItem(
headlineContent = { Text("Log out") },
supportingContent = { Text(loggedInAccountText(username)) },
leadingContent = {
Icon(
imageVector = Icons.Filled.AccountCircle,
contentDescription = null,
modifier = Modifier.height(32.dp),
)
},
modifier = Modifier.clickable(onClick = onLogout),
)
} else {
ListItem(
headlineContent = { Text("Log in with lobste.rs") },
supportingContent = { Text("Sign in to your lobste.rs account") },
leadingContent = {
Icon(
imageVector = Icons.Filled.AccountCircle,
contentDescription = null,
modifier = Modifier.height(32.dp),
)
},
modifier = Modifier.clickable(onClick = openLoginScreen),
)
}
Spacer(modifier = Modifier.height(24.dp))
// Data Management Section
SectionHeader(title = stringResource(R.string.data_management))
ListItem(
headlineContent = { Text(stringResource(R.string.data_transfer)) },
supportingContent = { Text(stringResource(R.string.import_and_export_your_saved_posts)) },
leadingContent = {
Icon(
imageVector = Icons.Filled.ImportExport,
contentDescription = null,
modifier = Modifier.height(32.dp),
)
},
trailingContent = {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
ImportPosts(openInputStream, coroutineScope, snackbarHostState, importPosts)
ExportPosts(
coroutineScope,
snackbarHostState,
openOutputStream,
exportPostsAsJson,
exportPostsAsHtml,
)
}
},
)
Spacer(modifier = Modifier.height(24.dp))
// App Information Section
SectionHeader(title = stringResource(R.string.app_information))
ListItem(
headlineContent = { Text(stringResource(R.string.version)) },
supportingContent = {
Text(
stringResource(
R.string.version_placeholder,
BuildConfig.VERSION_NAME,
BuildConfig.VERSION_CODE,
)
)
},
leadingContent = {
Icon(
imageVector = Icons.Filled.Code,
contentDescription = null,
modifier = Modifier.height(32.dp),
)
},
)
ListItem(
headlineContent = { Text(stringResource(R.string.saved_posts)) },
supportingContent = { Text(stringResource(R.string.posts_saved_locally, savedPostsCount)) },
leadingContent = {
Icon(
imageVector = Icons.Filled.Bookmarks,
contentDescription = null,
modifier = Modifier.height(32.dp),
)
},
)
ListItem(
headlineContent = { Text(stringResource(R.string.tag_filtering)) },
supportingContent = { Text(stringResource(R.string.filter_posts_by_tags)) },
leadingContent = {
Icon(
imageVector = Icons.Filled.FilterList,
contentDescription = null,
modifier = Modifier.height(32.dp),
)
},
modifier = Modifier.clickable(onClick = openTagFiltering),
)
Spacer(modifier = Modifier.height(24.dp))
// About Section
SectionHeader(title = stringResource(R.string.about))
ListItem(
headlineContent = { Text(stringResource(R.string.libraries)) },
supportingContent = {
Text(stringResource(R.string.view_open_source_libraries_used_in_this_))
},
leadingContent = {
Icon(
imageVector = Icons.AutoMirrored.Filled.LibraryBooks,
contentDescription = null,
modifier = Modifier.height(32.dp),
)
},
modifier = Modifier.clickable(onClick = openLibrariesScreen),
)
ListItem(
headlineContent = { Text(stringResource(R.string.source_code)) },
supportingContent = { Text(stringResource(R.string.view_the_source_code_on_github)) },
leadingContent = {
Icon(
imageVector = Icons.Filled.Code,
contentDescription = null,
modifier = Modifier.height(32.dp),
)
},
modifier = Modifier.clickable(onClick = openRepository),
)
}
}
@Composable
private fun SectionHeader(title: String, modifier: Modifier = Modifier) {
Column(modifier = modifier) {
Text(
text = title,
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
)
HorizontalDivider(
modifier = Modifier.padding(horizontal = 16.dp),
color = MaterialTheme.colorScheme.outlineVariant,
)
}
}
@Composable
private fun ImportPosts(
openInputStream: (Uri) -> InputStream?,
coroutineScope: CoroutineScope,
snackbarHostState: SnackbarHostState,
importPosts: suspend (InputStream) -> Unit,
importPosts: suspend (InputStream) -> Result<Unit>,
modifier: Modifier = Modifier,
) {
val importAction =
@@ -120,28 +252,39 @@ private fun RowScope.ImportPosts(
return@rememberLauncherForActivityResult
}
coroutineScope.launch {
openInputStream(uri)?.use { stream ->
importPosts(stream)
snackbarHostState.showSnackbarDismissing("Successfully imported posts")
try {
openInputStream(uri)?.use { stream ->
val result = importPosts(stream)
if (result.isSuccess) {
snackbarHostState.showSnackbarDismissing("Successfully imported posts")
} else {
snackbarHostState.showSnackbarDismissing("Failed to import posts")
}
} ?: snackbarHostState.showSnackbarDismissing("Unable to open selected file")
} catch (e: Exception) {
snackbarHostState.showSnackbarDismissing("Failed to open file: ${e.message}")
}
}
}
OutlinedButton(
onClick = { importAction.launch(JSON_MIME_TYPE) },
onClick = {
importAction.launch(JSON_MIME_TYPE)
Sentry.metrics().count("import", 1.0)
},
shape = MaterialTheme.shapes.extraSmall,
modifier = modifier.fillMaxWidth(0.30f).weight(0.5f),
modifier = modifier,
) {
Text(text = "Import")
Text(text = stringResource(R.string.import_action))
}
}
@Composable
private fun RowScope.ExportPosts(
private inline fun ExportPosts(
coroutineScope: CoroutineScope,
snackbarHostState: SnackbarHostState,
openOutputStream: (Uri) -> OutputStream?,
exportPostsAsJson: suspend (OutputStream) -> Unit,
exportPostsAsHtml: suspend (OutputStream) -> Unit,
crossinline openOutputStream: (Uri) -> OutputStream?,
crossinline exportPostsAsJson: suspend (OutputStream) -> Unit,
crossinline exportPostsAsHtml: suspend (OutputStream) -> Unit,
modifier: Modifier = Modifier,
) {
val jsonExportAction =
@@ -152,9 +295,19 @@ private fun RowScope.ExportPosts(
return@rememberLauncherForActivityResult
}
coroutineScope.launch {
openOutputStream(uri)?.use { stream ->
exportPostsAsJson(stream)
snackbarHostState.showSnackbarDismissing("Successfully exported posts")
try {
openOutputStream(uri)?.use { stream ->
exportPostsAsJson(stream)
snackbarHostState.showSnackbarDismissing("Successfully exported posts")
Sentry.metrics().count("json_export_success", 1.0)
}
?: run {
snackbarHostState.showSnackbarDismissing("Unable to open output file")
Sentry.metrics().count("json_export_failure", 1.0)
}
} catch (e: Exception) {
snackbarHostState.showSnackbarDismissing("Failed to export posts: ${e.message}")
Sentry.metrics().count("json_export_failure", 1.0)
}
}
}
@@ -166,9 +319,19 @@ private fun RowScope.ExportPosts(
return@rememberLauncherForActivityResult
}
coroutineScope.launch {
openOutputStream(uri)?.use { stream ->
exportPostsAsHtml(stream)
snackbarHostState.showSnackbarDismissing("Successfully exported posts")
try {
openOutputStream(uri)?.use { stream ->
exportPostsAsHtml(stream)
snackbarHostState.showSnackbarDismissing("Successfully exported posts")
Sentry.metrics().count("html_export_success", 1.0)
}
?: run {
snackbarHostState.showSnackbarDismissing("Unable to open output file")
Sentry.metrics().count("html_export_failure", 1.0)
}
} catch (e: Exception) {
snackbarHostState.showSnackbarDismissing("Failed to export posts: ${e.message}")
Sentry.metrics().count("html_export_failure", 1.0)
}
}
}
@@ -176,22 +339,25 @@ private fun RowScope.ExportPosts(
OutlinedButton(
onClick = { expanded = true },
shape = MaterialTheme.shapes.extraSmall,
modifier = modifier.fillMaxWidth(0.30f).weight(0.5f),
modifier = modifier,
) {
Text(text = "Export")
Text(text = stringResource(R.string.export))
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
DropdownMenuItem(
text = { Text("JSON") },
text = { Text(stringResource(R.string.json)) },
onClick = {
expanded = false
jsonExportAction.launch("claw-export.json")
},
leadingIcon = {
Icon(imageVector = Icons.Filled.Code, contentDescription = "Export as JSON")
Icon(
imageVector = Icons.Filled.Code,
contentDescription = stringResource(R.string.export_as_json),
)
},
)
DropdownMenuItem(
text = { Text("Bookmarks") },
text = { Text(stringResource(R.string.bookmarks)) },
onClick = {
expanded = false
htmlExportAction.launch("claw-export.html")
@@ -199,7 +365,7 @@ private fun RowScope.ExportPosts(
leadingIcon = {
Icon(
imageVector = Icons.Filled.Bookmarks,
contentDescription = "Export as browser bookmarks",
contentDescription = stringResource(R.string.export_as_browser_bookmarks),
)
},
)
@@ -208,7 +374,7 @@ private fun RowScope.ExportPosts(
}
/** Shows a Snackbar but dismisses any existing ones first. */
private suspend fun SnackbarHostState.showSnackbarDismissing(text: String) {
private suspend inline fun SnackbarHostState.showSnackbarDismissing(text: String) {
currentSnackbarData?.dismiss()
showSnackbar(text)
}
@@ -217,6 +383,22 @@ private suspend fun SnackbarHostState.showSnackbarDismissing(text: String) {
@Composable
private fun SettingsScreenPreview() {
LobstersTheme {
SettingsScreen({}, SnackbarHostState(), { null }, { null }, {}, {}, {}, PaddingValues())
SettingsScreen(
openLibrariesScreen = {},
openRepository = {},
openTagFiltering = {},
openLoginScreen = {},
onLogout = {},
isLoggedIn = false,
username = null,
snackbarHostState = SnackbarHostState(),
openInputStream = { null },
openOutputStream = { null },
importPosts = { Result.success(Unit) },
exportPostsAsJson = {},
exportPostsAsHtml = {},
contentPadding = PaddingValues(),
savedPostsCount = 42,
)
}
}
@@ -1,237 +0,0 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
@file:OptIn(ExperimentalMaterial3AdaptiveApi::class)
package dev.msfjarvis.claw.android.ui.screens
import androidx.activity.compose.BackHandler
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
import androidx.compose.material3.adaptive.layout.AnimatedPane
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole
import androidx.compose.material3.adaptive.layout.PaneAdaptedValue
import androidx.compose.material3.adaptive.navigation.BackNavigationBehavior
import androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator
import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.paging.compose.collectAsLazyPagingItems
import com.deliveryhero.whetstone.compose.injectedViewModel
import dev.msfjarvis.claw.android.R
import dev.msfjarvis.claw.android.ui.PostActions
import dev.msfjarvis.claw.android.ui.decorations.ClawNavigationRail
import dev.msfjarvis.claw.android.ui.decorations.NavigationItem
import dev.msfjarvis.claw.android.ui.lists.DatabasePosts
import dev.msfjarvis.claw.android.ui.lists.NetworkPosts
import dev.msfjarvis.claw.android.ui.navigation.AppDestinations
import dev.msfjarvis.claw.android.ui.navigation.Comments
import dev.msfjarvis.claw.android.ui.navigation.Hottest
import dev.msfjarvis.claw.android.ui.navigation.Newest
import dev.msfjarvis.claw.android.ui.navigation.Saved
import dev.msfjarvis.claw.android.ui.navigation.User
import dev.msfjarvis.claw.android.viewmodel.ClawViewModel
import dev.msfjarvis.claw.common.comments.CommentsPage
import dev.msfjarvis.claw.common.comments.HTMLConverter
import dev.msfjarvis.claw.common.urllauncher.UrlLauncher
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.coroutines.launch
private fun ThreePaneScaffoldNavigator<*>.isListExpanded() =
scaffoldValue[ListDetailPaneScaffoldRole.List] == PaneAdaptedValue.Expanded
private fun ThreePaneScaffoldNavigator<*>.isDetailExpanded() =
scaffoldValue[ListDetailPaneScaffoldRole.Detail] == PaneAdaptedValue.Expanded
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TabletScreen(
urlLauncher: UrlLauncher,
htmlConverter: HTMLConverter,
modifier: Modifier = Modifier,
viewModel: ClawViewModel = injectedViewModel(),
) {
val context = LocalContext.current
val hottestListState = rememberLazyListState()
val newestListState = rememberLazyListState()
val savedListState = rememberLazyListState()
val navController = rememberNavController()
val coroutineScope = rememberCoroutineScope()
val hottestPosts = viewModel.hottestPosts.collectAsLazyPagingItems()
val newestPosts = viewModel.newestPosts.collectAsLazyPagingItems()
val savedPosts by viewModel.savedPostsByMonth.collectAsStateWithLifecycle(persistentMapOf())
val navigator = rememberListDetailPaneScaffoldNavigator<Comments>()
val backBehavior =
if (navigator.isListExpanded() && navigator.isDetailExpanded()) {
BackNavigationBehavior.PopUntilContentChange
} else {
BackNavigationBehavior.PopUntilScaffoldValueChange
}
val postActions = remember {
PostActions(context, urlLauncher, viewModel) {
coroutineScope.launch {
navigator.navigateTo(pane = ListDetailPaneScaffoldRole.Detail, contentKey = Comments(it))
}
}
}
val navItems =
persistentListOf(
NavigationItem(AppDestinations.HOTTEST) {
coroutineScope.launch {
if (hottestPosts.itemCount > 0) hottestListState.animateScrollToItem(index = 0)
}
},
NavigationItem(AppDestinations.NEWEST) {
coroutineScope.launch {
if (newestPosts.itemCount > 0) newestListState.animateScrollToItem(index = 0)
}
},
NavigationItem(AppDestinations.SAVED) {
coroutineScope.launch {
if (savedPosts.isNotEmpty()) savedListState.animateScrollToItem(index = 0)
}
},
)
BackHandler(navigator.canNavigateBack(backBehavior)) {
coroutineScope.launch { navigator.navigateBack(backBehavior) }
}
Scaffold(
topBar = {
TopAppBar(
navigationIcon = {
if (navigator.canNavigateBack(backBehavior)) {
IconButton(
onClick = { coroutineScope.launch { navigator.navigateBack(backBehavior) } }
) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Go back to previous screen",
)
}
} else {
Icon(
painter = painterResource(id = R.drawable.ic_launcher_foreground),
contentDescription = "The app icon for Claw",
modifier = Modifier.size(48.dp),
)
}
},
title = {
if (!navigator.canNavigateBack(backBehavior)) {
Text(text = stringResource(R.string.app_name), fontWeight = FontWeight.Bold)
}
},
)
},
content = { paddingValues ->
Row {
ClawNavigationRail(navController = navController, items = navItems, isVisible = true)
ListDetailPaneScaffold(
modifier = modifier.padding(paddingValues),
directive = navigator.scaffoldDirective,
value = navigator.scaffoldValue,
listPane = {
AnimatedPane {
NavHost(
navController = navController,
startDestination = Hottest,
enterTransition = { fadeIn(tween(350)) },
exitTransition = { fadeOut(tween(350)) },
) {
composable<Hottest> {
NetworkPosts(
lazyPagingItems = hottestPosts,
listState = hottestListState,
postActions = postActions,
contentPadding = PaddingValues(),
)
}
composable<Newest> {
NetworkPosts(
lazyPagingItems = newestPosts,
listState = newestListState,
postActions = postActions,
contentPadding = PaddingValues(),
)
}
composable<Saved> {
DatabasePosts(
items = savedPosts,
listState = savedListState,
postActions = postActions,
contentPadding = PaddingValues(),
)
}
}
}
},
detailPane = {
AnimatedPane {
when (val contentKey = navigator.currentDestination?.contentKey) {
null -> {
Box(Modifier.fillMaxSize()) {
Text(
text = "Select a post to view comments",
modifier = Modifier.align(Alignment.Center),
)
}
}
else -> {
CommentsPage(
postId = contentKey.postId,
postActions = postActions,
htmlConverter = htmlConverter,
getSeenComments = viewModel::getSeenComments,
markSeenComments = viewModel::markSeenComments,
openUserProfile = { navController.navigate(User(it)) },
contentPadding = PaddingValues(),
modifier = Modifier.fillMaxSize(),
)
}
}
}
},
)
}
},
)
}
@@ -1,170 +0,0 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android.ui.screens
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.Icon
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.paging.compose.collectAsLazyPagingItems
import com.deliveryhero.whetstone.compose.injectedViewModel
import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer
import dev.msfjarvis.claw.android.ui.PostActions
import dev.msfjarvis.claw.android.ui.lists.DatabasePosts
import dev.msfjarvis.claw.android.ui.lists.NetworkPosts
import dev.msfjarvis.claw.android.ui.navigation.AboutLibraries
import dev.msfjarvis.claw.android.ui.navigation.AppDestinations
import dev.msfjarvis.claw.android.ui.navigation.Comments
import dev.msfjarvis.claw.android.ui.navigation.Hottest
import dev.msfjarvis.claw.android.ui.navigation.Newest
import dev.msfjarvis.claw.android.ui.navigation.Saved
import dev.msfjarvis.claw.android.ui.navigation.Settings
import dev.msfjarvis.claw.android.ui.navigation.User
import dev.msfjarvis.claw.android.viewmodel.ClawViewModel
import dev.msfjarvis.claw.common.comments.CommentsPage
import dev.msfjarvis.claw.common.comments.HTMLConverter
import dev.msfjarvis.claw.common.urllauncher.UrlLauncher
import dev.msfjarvis.claw.common.user.UserProfile
import kotlinx.collections.immutable.persistentMapOf
@Composable
fun TabletScreen2(
urlLauncher: UrlLauncher,
htmlConverter: HTMLConverter,
setWebUri: (String?) -> Unit,
modifier: Modifier = Modifier,
viewModel: ClawViewModel = injectedViewModel(),
) {
// TODO: Needs a custom Saver implementation, should probably be an ArrayDeque
val navigationBackStack = rememberSaveable {
mutableStateListOf(AppDestinations.HOTTEST.destination)
}
val context = LocalContext.current
val hottestListState = rememberLazyListState()
val newestListState = rememberLazyListState()
val savedListState = rememberLazyListState()
val snackbarHostState = remember { SnackbarHostState() }
val postActions = remember {
PostActions(context, urlLauncher, viewModel) { navigationBackStack.add(Comments(it)) }
}
val hottestPosts = viewModel.hottestPosts.collectAsLazyPagingItems()
val newestPosts = viewModel.newestPosts.collectAsLazyPagingItems()
val savedPosts by viewModel.savedPostsByMonth.collectAsStateWithLifecycle(persistentMapOf())
BackHandler(navigationBackStack.size > 1) {
navigationBackStack.removeAt(navigationBackStack.size - 1)
}
val contentPadding = PaddingValues()
NavigationSuiteScaffold(
navigationSuiteItems = {
AppDestinations.entries.forEach {
item(
icon = { Icon(imageVector = it.icon, contentDescription = it.label) },
selected = it.destination == navigationBackStack.first(),
onClick = { navigationBackStack.add(it.destination) },
)
}
},
modifier = modifier,
) {
when (navigationBackStack.first()) {
AboutLibraries -> {
LibrariesContainer(contentPadding = contentPadding, modifier = Modifier.fillMaxSize())
}
Hottest -> {
setWebUri("https://lobste.rs/")
NetworkPosts(
lazyPagingItems = hottestPosts,
listState = hottestListState,
postActions = postActions,
contentPadding = contentPadding,
)
}
Newest -> {
setWebUri("https://lobste.rs/")
NetworkPosts(
lazyPagingItems = newestPosts,
listState = newestListState,
postActions = postActions,
contentPadding = contentPadding,
)
}
Saved -> {
setWebUri(null)
DatabasePosts(
items = savedPosts,
listState = savedListState,
postActions = postActions,
contentPadding = contentPadding,
)
}
Settings -> {
setWebUri(null)
SettingsScreen(
openInputStream = context.contentResolver::openInputStream,
openOutputStream = context.contentResolver::openOutputStream,
openLibrariesScreen = { navigationBackStack.add(AboutLibraries) },
importPosts = viewModel::importPosts,
exportPostsAsJson = viewModel::exportPostsAsJson,
exportPostsAsHtml = viewModel::exportPostsAsHtml,
snackbarHostState = snackbarHostState,
contentPadding = contentPadding,
modifier = Modifier.fillMaxSize(),
)
}
is Comments -> {
val postId = (navigationBackStack.first() as Comments).postId
setWebUri("https://lobste.rs/s/$postId")
CommentsPage(
postId = postId,
postActions = postActions,
htmlConverter = htmlConverter,
getSeenComments = viewModel::getSeenComments,
markSeenComments = viewModel::markSeenComments,
contentPadding = contentPadding,
openUserProfile = { navigationBackStack.add(User(it)) },
)
}
is User -> {
val username = (navigationBackStack.first() as User).username
setWebUri("https://lobste.rs/u/$username")
UserProfile(
username = username,
getProfile = viewModel::getUserProfile,
contentPadding = contentPadding,
openUserProfile = { navigationBackStack.add(User(it)) },
)
}
else -> {
Box(Modifier.fillMaxSize()) {
Text(
text = "Unexpected destination: ${navigationBackStack.first()}, please report this bug",
modifier = Modifier.align(Alignment.Center),
)
}
}
}
}
}
@@ -1,21 +0,0 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android.ui.util
import androidx.compose.runtime.Stable
import dev.msfjarvis.claw.common.comments.HTMLConverter
import io.github.furstenheim.CopyDown
import javax.inject.Inject
@Stable
class HTMLConverterImpl @Inject constructor() : HTMLConverter {
private val copydown = CopyDown()
override fun convertHTMLToMarkdown(html: String): String {
return copydown.convert(html)
}
}
@@ -0,0 +1,51 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android.viewmodel
import android.util.Log
import app.cash.sqldelight.coroutines.asFlow
import app.cash.sqldelight.coroutines.mapToList
import dev.msfjarvis.claw.android.BuildConfig
import dev.msfjarvis.claw.core.coroutines.DatabaseReadDispatcher
import dev.msfjarvis.claw.core.coroutines.DatabaseWriteDispatcher
import dev.msfjarvis.claw.database.local.CachedRemotePost
import dev.msfjarvis.claw.database.local.CachedRemotePostQueries
import dev.zacsweers.metro.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
@Inject
class CachedRemotePostsRepository(
private val cachedRemotePostQueries: CachedRemotePostQueries,
@param:DatabaseReadDispatcher private val readDispatcher: CoroutineDispatcher,
@param:DatabaseWriteDispatcher private val writeDispatcher: CoroutineDispatcher,
) {
val cachedPosts = cachedRemotePostQueries.selectAllPosts().asFlow().mapToList(readDispatcher)
fun getRecentPosts(limit: Long) =
cachedRemotePostQueries.selectRecentPosts(limit).asFlow().mapToList(readDispatcher)
suspend fun getCachedPosts(): List<CachedRemotePost> {
return withContext(readDispatcher) { cachedRemotePostQueries.selectAllPosts().executeAsList() }
}
suspend fun savePosts(posts: List<CachedRemotePost>) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "Caching remote posts: ${posts.joinToString(",") { it.shortId }}")
}
withContext(writeDispatcher) {
cachedRemotePostQueries.transaction {
cachedRemotePostQueries.deleteAllPosts()
posts.forEach { post -> cachedRemotePostQueries.insertOrReplacePost(post) }
}
}
}
private companion object {
private const val TAG = "CachedRemotePostsRepository"
}
}
@@ -15,75 +15,92 @@ import androidx.compose.ui.text.capitalize
import androidx.compose.ui.text.intl.Locale
import androidx.glance.appwidget.updateAll
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.cachedIn
import com.deliveryhero.whetstone.app.ApplicationScope
import com.deliveryhero.whetstone.viewmodel.ContributesViewModel
import com.slack.eithernet.ApiResult.Failure
import com.slack.eithernet.ApiResult.Success
import com.squareup.anvil.annotations.optional.ForScope
import dev.msfjarvis.claw.android.glance.SavedPostsWidget
import dev.msfjarvis.claw.android.paging.LobstersPagingSource
import dev.msfjarvis.claw.android.paging.LobstersPagingSource.Companion.PAGE_SIZE
import dev.msfjarvis.claw.android.paging.LobstersPagingSource.Companion.STARTING_PAGE_INDEX
import dev.msfjarvis.claw.android.paging.SearchPagingSource
import dev.msfjarvis.claw.android.ui.toError
import dev.msfjarvis.claw.api.LobstersApi
import dev.msfjarvis.claw.core.injection.IODispatcher
import dev.msfjarvis.claw.core.injection.MainDispatcher
import dev.msfjarvis.claw.model.Comment
import dev.msfjarvis.claw.api.toPostsResult
import dev.msfjarvis.claw.core.coroutines.IODispatcher
import dev.msfjarvis.claw.core.coroutines.MainDispatcher
import dev.msfjarvis.claw.model.UIPost
import dev.msfjarvis.claw.model.fromSavedPost
import dev.msfjarvis.claw.model.toUIPost
import java.io.IOException
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesIntoMap
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.binding
import dev.zacsweers.metrox.viewmodel.ViewModelKey
import java.io.InputStream
import java.io.OutputStream
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import javax.inject.Inject
import kotlinx.collections.immutable.ImmutableMap
import kotlinx.collections.immutable.toImmutableMap
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ContributesViewModel
class ClawViewModel
@Inject
constructor(
@ViewModelKey
@ContributesIntoMap(scope = AppScope::class, binding = binding<ViewModel>())
class ClawViewModel(
context: Context,
private val api: LobstersApi,
private val commentsRepository: CommentsRepository,
private val readPostsRepository: ReadPostsRepository,
private val savedPostsRepository: SavedPostsRepository,
private val linkMetadataRepository: LinkMetadataRepository,
private val dataTransferRepository: DataTransferRepository,
private val pagingSourceFactory: LobstersPagingSource.Factory,
private val searchPagingSourceFactory: SearchPagingSource.Factory,
@IODispatcher private val ioDispatcher: CoroutineDispatcher,
@MainDispatcher private val mainDispatcher: CoroutineDispatcher,
@ForScope(ApplicationScope::class) context: Context,
@param:IODispatcher private val ioDispatcher: CoroutineDispatcher,
@param:MainDispatcher private val mainDispatcher: CoroutineDispatcher,
) : AndroidViewModel(context as Application) {
val hottestPosts =
Pager(
config = PagingConfig(pageSize = PAGE_SIZE),
initialKey = STARTING_PAGE_INDEX,
pagingSourceFactory = { pagingSourceFactory.create(api::getHottestPosts) },
)
.flow
.cachedIn(viewModelScope)
val newestPosts =
Pager(
config = PagingConfig(pageSize = PAGE_SIZE),
initialKey = STARTING_PAGE_INDEX,
pagingSourceFactory = { pagingSourceFactory.create(api::getNewestPosts) },
)
.flow
.cachedIn(viewModelScope)
private var _hottestPosts: Flow<PagingData<UIPost>>? = null
val hottestPosts: Flow<PagingData<UIPost>>
get() {
if (_hottestPosts == null) {
_hottestPosts =
Pager(
config = PagingConfig(pageSize = PAGE_SIZE),
initialKey = STARTING_PAGE_INDEX,
pagingSourceFactory = {
pagingSourceFactory.create { page -> api.getHottestPosts(page).toPostsResult() }
},
)
.flow
.cachedIn(viewModelScope)
}
return requireNotNull(_hottestPosts) { "_hottestPosts should be initialized here" }
}
private var _newestPosts: Flow<PagingData<UIPost>>? = null
val newestPosts: Flow<PagingData<UIPost>>
get() {
if (_newestPosts == null) {
_newestPosts =
Pager(
config = PagingConfig(pageSize = PAGE_SIZE),
initialKey = STARTING_PAGE_INDEX,
pagingSourceFactory = {
pagingSourceFactory.create { page -> api.getNewestPosts(page).toPostsResult() }
},
)
.flow
.cachedIn(viewModelScope)
}
return requireNotNull(_newestPosts) { "_newestPosts should be initialized here" }
}
val searchResults =
Pager(
PagingConfig(pageSize = PAGE_SIZE),
@@ -92,44 +109,37 @@ constructor(
)
.flow
val savedPosts = savedPostsRepository.savedPosts.map { it.map(UIPost.Companion::fromSavedPost) }
val savedPostsCount = savedPostsRepository.savedPosts.map { it.size.toLong() }
val savedPostsByMonth
get() = savedPosts.map(::groupSavedPosts)
get() =
savedPostsRepository.savedPostsSortedByDate.map { posts ->
posts
.map(UIPost.Companion::fromSavedPost)
.groupBy { post ->
val time = post.createdAt.toLocalDateTime()
"${time.month.name.lowercase().capitalize(Locale.current)} ${time.year}"
}
.toImmutableMap()
}
var searchQuery by mutableStateOf("")
private var _readPosts = emptyList<String>()
private var _savedPosts = emptyList<String>()
private var _readPosts by mutableStateOf(emptySet<String>())
private var _savedPosts by mutableStateOf(emptySet<String>())
init {
viewModelScope.launch { savedPosts.collectLatest { _savedPosts = it.map(UIPost::shortId) } }
viewModelScope.launch { readPostsRepository.readPosts.collectLatest { _readPosts = it } }
}
private fun groupSavedPosts(items: List<UIPost>): ImmutableMap<String, List<UIPost>> {
val sorted =
items.sortedWith { post1, post2 ->
val post1Date = post1.createdAt.toLocalDateTime()
val post2Date = post2.createdAt.toLocalDateTime()
if (post2Date.isBefore(post1Date)) {
-1
} else if (post2Date.isAfter(post1Date)) {
1
} else {
0
}
}
return sorted
.groupBy { post ->
val time = post.createdAt.toLocalDateTime()
"${time.month.name.lowercase().capitalize(Locale.current)} ${time.year}"
}
.toImmutableMap()
viewModelScope.launch {
savedPosts.collectLatest { _savedPosts = it.map(UIPost::shortId).toSet() }
}
viewModelScope.launch {
readPostsRepository.readPosts.collectLatest { _readPosts = it.toSet() }
}
}
fun toggleSave(post: UIPost) {
viewModelScope.launch {
savedPostsRepository.toggleSave(post)
withContext(mainDispatcher) { SavedPostsWidget(savedPosts).updateAll(getApplication()) }
withContext(mainDispatcher) { SavedPostsWidget().updateAll(getApplication()) }
}
}
@@ -141,38 +151,11 @@ constructor(
return _savedPosts.contains(post.shortId)
}
suspend fun getPostComments(postId: String) =
withContext(ioDispatcher) {
when (val result = api.getPostDetails(postId)) {
is Success -> result.value.toUIPost()
is Failure.NetworkFailure -> throw result.error
is Failure.UnknownFailure -> throw result.error
is Failure.HttpFailure -> throw result.toError()
is Failure.ApiFailure -> throw IOException("API returned an invalid response")
}
}
suspend fun getSeenComments(postId: String) = commentsRepository.getSeenComments(postId)
fun markSeenComments(postId: String, comments: List<Comment>) {
viewModelScope.launch { commentsRepository.markSeenComments(postId, comments) }
}
suspend fun getLinkMetadata(url: String) =
withContext(ioDispatcher) { linkMetadataRepository.getLinkMetadata(url) }
suspend fun getUserProfile(username: String) =
withContext(ioDispatcher) {
when (val result = api.getUser(username)) {
is Success -> result.value
is Failure.NetworkFailure -> throw result.error
is Failure.UnknownFailure -> throw result.error
is Failure.HttpFailure -> throw result.toError()
is Failure.ApiFailure -> throw IOException("API returned an invalid response")
}
}
suspend fun importPosts(input: InputStream) = dataTransferRepository.importPosts(input)
suspend fun importPosts(input: InputStream): Result<Unit> =
dataTransferRepository.importPosts(input)
suspend fun exportPostsAsJson(output: OutputStream) =
dataTransferRepository.exportPostsAsJson(output)
@@ -6,14 +6,14 @@
*/
package dev.msfjarvis.claw.android.viewmodel
import dev.msfjarvis.claw.core.injection.IODispatcher
import dev.msfjarvis.claw.core.coroutines.IODispatcher
import dev.msfjarvis.claw.database.SavedPostSerializer
import dev.msfjarvis.claw.database.local.SavedPost
import dev.zacsweers.metro.Inject
import java.io.InputStream
import java.io.OutputStream
import java.time.Instant
import java.time.format.DateTimeFormatter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
@@ -23,20 +23,24 @@ import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import kotlinx.serialization.json.encodeToStream
@OptIn(ExperimentalSerializationApi::class)
class DataTransferRepository
@Inject
constructor(
@OptIn(ExperimentalSerializationApi::class)
class DataTransferRepository(
private val json: Json,
private val savedPostsRepository: SavedPostsRepository,
@IODispatcher private val ioDispatcher: CoroutineDispatcher,
@param:IODispatcher private val ioDispatcher: CoroutineDispatcher,
) {
private val serializer = ListSerializer(SavedPostSerializer)
suspend fun importPosts(input: InputStream) {
val posts: List<SavedPost> =
withContext(ioDispatcher) { json.decodeFromStream(serializer, input) }
savedPostsRepository.savePosts(posts)
suspend fun importPosts(input: InputStream): Result<Unit> {
return try {
val posts: List<SavedPost> =
withContext(ioDispatcher) { json.decodeFromStream(serializer, input) }
savedPostsRepository.savePosts(posts)
Result.success(Unit)
} catch (t: Throwable) {
Result.failure(t)
}
}
suspend fun exportPostsAsJson(output: OutputStream) {
@@ -61,7 +65,7 @@ constructor(
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
<TITLE>Bookmarks</TITLE>
<H1>Bookmarks</H1>
"""
"""
.trimIndent()
val html = buildString {
append(header)
@@ -69,10 +73,9 @@ constructor(
for (post in posts) {
append(
"""
<DT><A HREF="${post.url.ifEmpty { post.commentsUrl }}" ADD_DATE="${computeTimestamp(post)}" PRIVATE="0" TAGS="${post.tags.joinToString(",")}">${post.title}</A>
<DD>${post.title}
"""
<DT><A HREF="${post.url.ifEmpty { post.commentsUrl }}" ADD_DATE="${computeTimestamp(post)}" PRIVATE="0" TAGS="${post.tags.joinToString(",")}">${post.title}</A>
<DD>${post.title}
"""
.trimIndent()
)
}
@@ -80,16 +83,11 @@ constructor(
"""
<DT><A HREF="https://example.com/" ADD_DATE="0" PRIVATE="0" TAGS="delete,me,pls">Padding post</A>
<DD>Linkding ignores the last entry so this pads the difference for imports
"""
"""
.trimIndent()
)
append("</DD></p>\n")
}
withContext(ioDispatcher) {
val writer = output.bufferedWriter()
writer.write(html)
writer.flush()
}
withContext(ioDispatcher) { output.bufferedWriter().use { writer -> writer.write(html) } }
}
}
@@ -6,18 +6,17 @@
*/
package dev.msfjarvis.claw.android.viewmodel
import dev.msfjarvis.claw.core.injection.IODispatcher
import dev.msfjarvis.claw.core.coroutines.IODispatcher
import dev.msfjarvis.claw.model.LinkMetadata
import javax.inject.Inject
import dev.zacsweers.metro.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
import me.saket.unfurl.Unfurler
class LinkMetadataRepository
@Inject
constructor(
class LinkMetadataRepository(
private val unfurler: Unfurler,
@IODispatcher private val dispatcher: CoroutineDispatcher,
@param:IODispatcher private val dispatcher: CoroutineDispatcher,
) {
suspend fun getLinkMetadata(url: String): LinkMetadata {
val result = withContext(dispatcher) { unfurler.unfurl(url) }
@@ -8,21 +8,22 @@ package dev.msfjarvis.claw.android.viewmodel
import app.cash.sqldelight.coroutines.asFlow
import app.cash.sqldelight.coroutines.mapToList
import dev.msfjarvis.claw.core.injection.DatabaseDispatcher
import dev.msfjarvis.claw.core.coroutines.DatabaseReadDispatcher
import dev.msfjarvis.claw.core.coroutines.DatabaseWriteDispatcher
import dev.msfjarvis.claw.database.local.ReadPostsQueries
import javax.inject.Inject
import dev.zacsweers.metro.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
class ReadPostsRepository
@Inject
constructor(
class ReadPostsRepository(
private val readPostsQueries: ReadPostsQueries,
@DatabaseDispatcher private val dbDispatcher: CoroutineDispatcher,
@param:DatabaseReadDispatcher private val readDispatcher: CoroutineDispatcher,
@param:DatabaseWriteDispatcher private val writeDispatcher: CoroutineDispatcher,
) {
val readPosts = readPostsQueries.selectAllPosts().asFlow().mapToList(dbDispatcher)
val readPosts = readPostsQueries.selectAllPosts().asFlow().mapToList(readDispatcher)
suspend fun markRead(postId: String) {
withContext(dbDispatcher) { readPostsQueries.markRead(postId) }
withContext(writeDispatcher) { readPostsQueries.markRead(postId) }
}
}
@@ -6,40 +6,57 @@
*/
package dev.msfjarvis.claw.android.viewmodel
import android.util.Log
import app.cash.sqldelight.coroutines.asFlow
import app.cash.sqldelight.coroutines.mapToList
import dev.msfjarvis.claw.core.injection.DatabaseDispatcher
import dev.msfjarvis.claw.android.BuildConfig
import dev.msfjarvis.claw.core.coroutines.DatabaseReadDispatcher
import dev.msfjarvis.claw.core.coroutines.DatabaseWriteDispatcher
import dev.msfjarvis.claw.database.local.SavedPost
import dev.msfjarvis.claw.database.local.SavedPostQueries
import dev.msfjarvis.claw.model.UIPost
import dev.msfjarvis.claw.model.toSavedPost
import io.github.aakira.napier.Napier
import javax.inject.Inject
import dev.zacsweers.metro.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.withContext
class SavedPostsRepository
@Inject
constructor(
class SavedPostsRepository(
private val savedPostQueries: SavedPostQueries,
@DatabaseDispatcher private val dbDispatcher: CoroutineDispatcher,
@param:DatabaseReadDispatcher private val readDispatcher: CoroutineDispatcher,
@param:DatabaseWriteDispatcher private val writeDispatcher: CoroutineDispatcher,
) {
val savedPosts = savedPostQueries.selectAllPosts().asFlow().mapToList(dbDispatcher)
val savedPosts = savedPostQueries.selectAllPosts().asFlow().mapToList(readDispatcher)
val savedPostsSortedByDate =
savedPostQueries.selectAllPostsSortedByDate().asFlow().mapToList(readDispatcher)
fun getPostsFromLastNDays(days: Long) =
savedPostQueries.selectPostsFromLastNDays(days.toString()).asFlow().mapToList(readDispatcher)
fun getRecentPosts(limit: Long) =
savedPostQueries.selectRecentPosts(limit).asFlow().mapToList(readDispatcher)
suspend fun toggleSave(post: UIPost) {
if (savedPosts.firstOrNull().orEmpty().any { it.shortId == post.shortId }) {
Napier.d(tag = TAG) { "Removing post: ${post.shortId}" }
withContext(dbDispatcher) { savedPostQueries.deletePost(post.shortId) }
val exists =
withContext(readDispatcher) { savedPostQueries.postExists(post.shortId).executeAsOne() }
if (exists) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "Removing post: ${post.shortId}")
}
withContext(writeDispatcher) { savedPostQueries.deletePost(post.shortId) }
} else {
Napier.d(tag = TAG) { "Saving post: ${post.shortId}" }
withContext(dbDispatcher) { savedPostQueries.insertOrReplacePost(post.toSavedPost()) }
if (BuildConfig.DEBUG) {
Log.d(TAG, "Saving post: ${post.shortId}")
}
withContext(writeDispatcher) { savedPostQueries.insertOrReplacePost(post.toSavedPost()) }
}
}
suspend fun savePosts(posts: List<SavedPost>) {
Napier.d(tag = TAG) { "Saving posts: ${posts.joinToString(",") { it.shortId }}" }
withContext(dbDispatcher) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "Saving posts: ${posts.joinToString(",") { it.shortId }}")
}
withContext(writeDispatcher) {
savedPostQueries.transaction {
posts.forEach { post -> savedPostQueries.insertOrReplacePost(post) }
}
@@ -0,0 +1,54 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dev.msfjarvis.claw.core.network.SessionCookieStore
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesIntoMap
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.binding
import dev.zacsweers.metrox.viewmodel.ViewModelKey
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.stateIn
@Inject
@ViewModelKey
@ContributesIntoMap(scope = AppScope::class, binding = binding<ViewModel>())
class SettingsViewModel(
private val sessionCookieStore: SessionCookieStore,
private val webViewCookieStore: WebViewCookieStore,
) : ViewModel() {
val isLoggedIn =
sessionCookieStore
.isLoggedIn()
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = false,
)
val username =
sessionCookieStore
.username()
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = null,
)
fun logout() {
sessionCookieStore.clear()
webViewCookieStore.clearLobstersCookies()
}
fun saveCookie(cookie: String, username: String) {
sessionCookieStore.set(cookie, username)
}
}
@@ -0,0 +1,25 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android.viewmodel
import android.webkit.CookieManager
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesBinding
import dev.zacsweers.metro.Inject
interface WebViewCookieStore {
fun clearLobstersCookies()
}
@Inject
@ContributesBinding(AppScope::class)
class AndroidWebViewCookieStore : WebViewCookieStore {
override fun clearLobstersCookies() {
CookieManager.getInstance().removeAllCookies(null)
CookieManager.getInstance().flush()
}
}
@@ -10,47 +10,81 @@ import android.content.Context
import androidx.glance.appwidget.updateAll
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.deliveryhero.whetstone.worker.ContributesWorker
import com.deliveryhero.whetstone.worker.WorkerScope
import com.slack.eithernet.ApiResult.Success
import com.squareup.anvil.annotations.optional.ForScope
import dev.msfjarvis.claw.android.glance.SavedPostsWidget
import dev.msfjarvis.claw.android.viewmodel.SavedPostsRepository
import dev.msfjarvis.claw.api.LobstersApi
import dev.msfjarvis.claw.model.LobstersPostDetails
import dev.msfjarvis.claw.model.UIPost
import dev.msfjarvis.claw.model.fromSavedPost
import dev.msfjarvis.claw.core.injection.InjectedWorkerFactory
import dev.msfjarvis.claw.core.injection.WorkerKey
import dev.msfjarvis.claw.database.local.SavedPost
import dev.msfjarvis.claw.model.toSavedPost
import javax.inject.Inject
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedFactory
import dev.zacsweers.metro.AssistedInject
import dev.zacsweers.metro.ContributesIntoMap
import dev.zacsweers.metro.binding
import kotlin.random.Random
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
/**
* WorkManager-backed [CoroutineWorker] that gets all the posts from [SavedPostsRepository], fetches
* their newest state using the [LobstersApi] and then writes them back to the database. This allows
* saved posts that were saved before comment counts were added to be able to show a comment count
* and for new-enough posts that are still getting comments to have an accurate one.
* WorkManager-backed [CoroutineWorker] that gets all the posts from [SavedPostsRepository] that
* were created in the last 30 days, fetches their newest state using the [LobstersApi] and then
* writes them back to the database. API calls are staggered with a configurable delay to avoid rate
* limiting. This allows saved posts to stay up-to-date with current comment counts and other
* metadata changes.
*/
@ContributesWorker
class SavedPostUpdaterWorker
@Inject
constructor(
@ForScope(WorkerScope::class) appContext: Context,
workerParams: WorkerParameters,
@AssistedInject
class SavedPostUpdaterWorker(
context: Context,
@Assisted params: WorkerParameters,
private val savedPostsRepository: SavedPostsRepository,
private val lobstersApi: LobstersApi,
) : CoroutineWorker(appContext, workerParams) {
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
savedPostsRepository.savedPosts
.first()
.map { post -> lobstersApi.getPostDetails(post.shortId) }
.filterIsInstance<Success<LobstersPostDetails>>()
.map { result -> result.value.toSavedPost() }
.let { savedPostsRepository.savePosts(it) }
SavedPostsWidget(
savedPostsRepository.savedPosts.map { it.map(UIPost.Companion::fromSavedPost) }
)
.updateAll(applicationContext)
val postsToUpdate = savedPostsRepository.getPostsFromLastNDays(DAYS_TO_UPDATE).first()
if (postsToUpdate.isEmpty()) {
return Result.success()
}
val updatedPosts = mutableListOf<SavedPost>()
for ((index, post) in postsToUpdate.withIndex()) {
when (val result = lobstersApi.getPostDetails(post.shortId)) {
is Success -> {
updatedPosts.add(result.value.toSavedPost())
}
else -> {}
}
if (index < postsToUpdate.lastIndex) {
// Add a random delay between API calls to avoid rate limiting
val delayMs = Random.nextLong(MIN_DELAY_MS, MAX_DELAY_MS)
delay(delayMs)
}
}
if (updatedPosts.isNotEmpty()) {
savedPostsRepository.savePosts(updatedPosts)
SavedPostsWidget().updateAll(applicationContext)
}
return Result.success()
}
private companion object {
const val DAYS_TO_UPDATE = 30L
const val MIN_DELAY_MS = 500L
const val MAX_DELAY_MS = 2000L
}
@WorkerKey(SavedPostUpdaterWorker::class)
@ContributesIntoMap(
AppScope::class,
binding = binding<InjectedWorkerFactory.WorkerInstanceFactory<*>>(),
)
@AssistedFactory
abstract class Factory : InjectedWorkerFactory.WorkerInstanceFactory<SavedPostUpdaterWorker>
}
@@ -0,0 +1,40 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android.work
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import dev.msfjarvis.claw.common.tags.TagBlockRepository
import dev.msfjarvis.claw.core.injection.InjectedWorkerFactory
import dev.msfjarvis.claw.core.injection.WorkerKey
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedFactory
import dev.zacsweers.metro.AssistedInject
import dev.zacsweers.metro.ContributesIntoMap
import dev.zacsweers.metro.binding
@AssistedInject
class TagExpirationCleanupWorker(
context: Context,
@Assisted params: WorkerParameters,
private val tagBlockRepository: TagBlockRepository,
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
tagBlockRepository.removeExpiredTags()
return Result.success()
}
@WorkerKey(TagExpirationCleanupWorker::class)
@ContributesIntoMap(
AppScope::class,
binding = binding<InjectedWorkerFactory.WorkerInstanceFactory<*>>(),
)
@AssistedFactory
abstract class Factory : InjectedWorkerFactory.WorkerInstanceFactory<TagExpirationCleanupWorker>
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

@@ -9,8 +9,7 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?android:colorControlNormal"
android:alpha="0.6"
android:tint="?android:attr/colorControlNormal"
android:autoMirrored="true">
<path
android:fillColor="@android:color/white"
+36 -1
View File
@@ -4,6 +4,41 @@
~ license that can be found in the LICENSE file or at
~ https://opensource.org/licenses/MIT.
-->
<resources>
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="app_name">Claw</string>
<string name="saved_posts_widget_description">View your saved Lobsters posts</string>
<string name="hottest_posts_widget_description">View the hottest posts on Lobsters</string>
<string name="saved_posts">Saved posts</string>
<string name="see_more_posts">See more posts</string>
<plurals name="comments">
<item quantity="one">%1$d comment</item>
<item quantity="other">%1$d comments</item>
</plurals>
<string name="no_post_selected">No post selected</string>
<string name="data_management">Data Management</string>
<string name="data_transfer">Data transfer</string>
<string name="import_and_export_your_saved_posts">Import and export your saved posts</string>
<string name="app_information">App Information</string>
<string name="version">Version</string>
<string name="version_placeholder">%1$s (%2$s)</string>
<string name="posts_saved_locally">%1$s posts saved locally</string>
<string name="tag_filtering">Tag filtering</string>
<string name="filter_posts_by_tags">Filter posts by tags</string>
<string name="about">About</string>
<string name="libraries">Libraries</string>
<string name="view_open_source_libraries_used_in_this_">View open source libraries used in this app</string>
<string name="source_code">Source code</string>
<string name="view_the_source_code_on_github">View the source code on GitHub</string>
<string name="import_action">Import</string>
<string name="export">Export</string>
<string name="json">JSON</string>
<string name="export_as_json">Export as JSON</string>
<string name="bookmarks">Bookmarks</string>
<string name="export_as_browser_bookmarks">Export as browser bookmarks</string>
<string name="go_back_to_previous_screen">Go back to previous screen</string>
<string name="the_app_icon_for_claw">The app icon for Claw</string>
<string name="search_posts">Search posts</string>
<string name="settings">Settings</string>
<string name="empty_inbox_icon">Empty inbox icon</string>
<string name="no_saved_posts">No saved posts</string>
</resources>
@@ -8,12 +8,12 @@
<cloud-backup>
<include
domain="database"
path="databases" />
path="." />
</cloud-backup>
<device-transfer>
<include
domain="database"
path="databases" />
path="." />
</device-transfer>
</data-extraction-rules>
@@ -7,5 +7,5 @@
<full-backup-content>
<include
domain="database"
path="databases" />
path="." />
</full-backup-content>
@@ -0,0 +1,17 @@
<!--
~ Copyright © Harsh Shandilya.
~ Use of this source code is governed by an MIT-style
~ license that can be found in the LICENSE file or at
~ https://opensource.org/licenses/MIT.
-->
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/hottest_posts_widget_description"
android:previewImage="@drawable/hottest_posts_widget_preview"
android:initialLayout="@layout/glance_default_loading_layout"
android:minWidth="349dp"
android:minHeight="110dp"
android:minResizeWidth="349dp"
android:minResizeHeight="110dp"
android:resizeMode="vertical|horizontal"
android:updatePeriodMillis="1800000"
android:widgetCategory="home_screen" />
@@ -5,6 +5,8 @@
~ https://opensource.org/licenses/MIT.
-->
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/saved_posts_widget_description"
android:previewImage="@drawable/saved_posts_widget_preview"
android:initialLayout="@layout/glance_default_loading_layout"
android:minWidth="349dp"
android:minHeight="110dp"
@@ -1,37 +0,0 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android.ui.lists
import android.annotation.SuppressLint
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.paging.PagingData
import androidx.paging.compose.collectAsLazyPagingItems
import dev.msfjarvis.claw.common.posts.TEST_POST
import dev.msfjarvis.claw.common.posts.TEST_POST_ACTIONS
import dev.msfjarvis.claw.common.theme.LobstersTheme
import dev.msfjarvis.claw.common.ui.preview.ThemePreviews
import kotlinx.coroutines.flow.MutableStateFlow
@SuppressLint("ComposePreviewPublic", "ComposeUnstableReceiver")
class NetworkPostsTest {
@ThemePreviews
@Composable
fun DefaultPreview() {
val items = List(20) { TEST_POST.copy(shortId = "${TEST_POST.shortId}${it}") }
val flow = MutableStateFlow(PagingData.from(items))
LobstersTheme {
NetworkPosts(
lazyPagingItems = flow.collectAsLazyPagingItems(),
listState = rememberLazyListState(),
postActions = TEST_POST_ACTIONS,
contentPadding = PaddingValues(),
)
}
}
}
@@ -0,0 +1,71 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android.ui.screens
import androidx.navigation3.runtime.NavKey
import com.google.common.truth.Truth.assertThat
import dev.msfjarvis.claw.android.ui.navigation.Comments
import dev.msfjarvis.claw.android.ui.navigation.Hottest
import dev.msfjarvis.claw.android.ui.navigation.Newest
import dev.msfjarvis.claw.android.ui.navigation.Saved
import org.junit.jupiter.api.Test
class BackStackBehaviorTest {
private fun makeBackStack(vararg keys: NavKey) = mutableListOf(*keys)
@Test
fun `navigating from between top level destinations prevents stacking`() {
val backStack = makeBackStack(Hottest)
navigateTo(backStack, Newest)
assertThat(backStack).containsExactly(Hottest, Newest).inOrder()
navigateTo(backStack, Saved)
assertThat(backStack).containsExactly(Hottest, Saved).inOrder()
}
@Test
fun `NonStackable destinations do not stack by default`() {
val backStack = makeBackStack(Hottest, Comments("abc123"))
navigateTo(backStack, Comments("def456"))
assertThat(backStack).containsExactly(Hottest, Comments("def456")).inOrder()
}
@Test
fun `NonStackable destinations stack when allowStacking = true`() {
val backStack = makeBackStack(Hottest, Comments("abc123"))
navigateTo(backStack, Comments("def456"), allowStacking = true)
assertThat(backStack).containsExactly(Hottest, Comments("abc123"), Comments("def456")).inOrder()
}
@Test
fun `Same destination cannot be stacked on itself`() {
val backStack = makeBackStack(Hottest, Comments("abc123"))
navigateTo(backStack, Comments("abc123"), allowStacking = true)
assertThat(backStack).containsExactly(Hottest, Comments("abc123")).inOrder()
}
@Test
fun `Same destination can be stacked with a gap in between`() {
val backStack = makeBackStack(Hottest, Comments("abc123"), Comments("def456"))
navigateTo(backStack, Comments("abc123"), allowStacking = true)
assertThat(backStack)
.containsExactly(Hottest, Comments("abc123"), Comments("def456"), Comments("abc123"))
.inOrder()
}
}
@@ -0,0 +1,72 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android.viewmodel
import com.google.common.truth.Truth.assertThat
import dev.msfjarvis.claw.core.network.SessionCookieStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import org.junit.jupiter.api.Test
class SettingsViewModelTest {
@Test
fun `logout clears persisted and webview sessions`() {
val sessionCookieStore = FakeSessionCookieStore()
val webViewCookieStore = FakeWebViewCookieStore()
val viewModel = SettingsViewModel(sessionCookieStore, webViewCookieStore)
viewModel.logout()
assertThat(sessionCookieStore.cleared).isTrue()
assertThat(webViewCookieStore.cleared).isTrue()
}
@Test
fun `saveCookie persists username`() {
val sessionCookieStore = FakeSessionCookieStore()
val viewModel = SettingsViewModel(sessionCookieStore, FakeWebViewCookieStore())
viewModel.saveCookie("cookie=value", "alice")
assertThat(sessionCookieStore.savedCookie).isEqualTo("cookie=value")
assertThat(sessionCookieStore.savedUsername).isEqualTo("alice")
}
private class FakeSessionCookieStore : SessionCookieStore {
var cleared = false
var savedCookie: String? = null
var savedUsername: String? = null
override fun get(): String? = savedCookie
override fun getUsername(): String? = savedUsername
override fun set(cookie: String, username: String) {
savedCookie = cookie
savedUsername = username
}
override fun clear() {
cleared = true
savedCookie = null
savedUsername = null
}
override fun isLoggedIn(): Flow<Boolean> = flowOf(savedUsername != null)
override fun username(): Flow<String?> = flowOf(savedUsername)
}
private class FakeWebViewCookieStore : WebViewCookieStore {
var cleared = false
override fun clearLobstersCookies() {
cleared = true
}
}
}
+2 -2
View File
@@ -1,5 +1,5 @@
#
# This file was automatically generated by 'versioning-plugin'. DO NOT EDIT MANUALLY.
#
versioning-plugin.versionCode=15400
versioning-plugin.versionName=1.54.0
versioning-plugin.versionCode=16500
versioning-plugin.versionName=1.65.0
+9 -14
View File
@@ -7,31 +7,26 @@
import dev.msfjarvis.claw.gradle.addTestDependencies
plugins {
id("dev.msfjarvis.claw.android-library")
id("dev.msfjarvis.claw.kotlin-android")
alias(libs.plugins.anvil)
alias(libs.plugins.whetstone)
id("dev.msfjarvis.claw.kotlin-jvm")
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.dependencyAnalysis)
alias(libs.plugins.metro)
}
android { namespace = "dev.msfjarvis.claw.api" }
anvil { generateDaggerFactories.set(true) }
dependencies {
api(libs.dagger)
api(libs.javax.inject)
api(libs.eithernet)
api(libs.kotlinx.serialization.core)
api(libs.okhttp.core)
api(libs.retrofit)
api(projects.model)
implementation(libs.eithernet)
implementation(libs.eithernet.integration.retrofit)
implementation(libs.jsoup)
implementation(libs.ksoup)
implementation(libs.kspoon)
implementation(libs.retrofit.kotlinxSerializationConverter)
testImplementation(libs.eithernet.test.fixtures)
testImplementation(libs.kotlin.reflect)
testImplementation(libs.kotlinx.coroutines.test)
testImplementation(libs.kotlinx.serialization.core)
testImplementation(libs.kotlinx.serialization.json)
addTestDependencies(project)
}
+1 -1
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.7.1" type="baseline" client="gradle" dependencies="true" name="AGP (8.7.1)" variant="all" version="8.7.1">
<issues format="6" by="lint 9.1.0" type="baseline" client="gradle" dependencies="true" name="AGP (9.1.0)" variant="all" version="9.1.0">
</issues>
@@ -0,0 +1,91 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.api
import com.slack.eithernet.ApiResult
import com.slack.eithernet.ApiResult.Failure
import com.slack.eithernet.ApiResult.Success
import dev.zacsweers.metro.Inject
import java.io.IOException
import okhttp3.MultipartBody
private const val XML_HTTP_REQUEST = "XMLHttpRequest"
@Inject
class AuthenticatedLobstersApi(private val api: LobstersApi) {
suspend fun upvoteComment(commentId: String): ApiResult<Unit, Unit> {
return withCSRFToken { csrfToken ->
api.upvoteComment(commentId, csrfToken, XML_HTTP_REQUEST, part("reason", ""))
}
}
suspend fun unvoteComment(commentId: String): ApiResult<Unit, Unit> {
return withCSRFToken { csrfToken ->
api.unvoteComment(commentId, csrfToken, XML_HTTP_REQUEST, part("reason", ""))
}
}
suspend fun reply(commentId: String, comment: String): ApiResult<Unit, Unit> {
return withCSRFToken { csrfToken ->
when (val formResponse = api.getReplyForm(commentId, csrfToken, XML_HTTP_REQUEST)) {
is Success -> {
val form = formResponse.value
if (!form.isValid()) {
ApiResult.unknownFailure(IOException("Lobsters reply form was missing required fields"))
} else {
api.postReply(
csrfToken = csrfToken,
requestedWith = XML_HTTP_REQUEST,
authenticityToken = part("authenticity_token", form.authenticityToken),
storyId = part("story_id", form.storyId),
method = part("_method", form.method),
parentCommentShortId = part("parent_comment_short_id", form.parentCommentShortId),
comment = part("comment", comment),
commit = part("commit", "Post"),
)
}
}
is Failure -> formResponse.toUnitFailure()
}
}
}
private suspend fun withCSRFToken(
block: suspend (String) -> ApiResult<Unit, Unit>
): ApiResult<Unit, Unit> {
return when (val token = api.getCSRFToken()) {
is Success -> {
val value = token.value.value
if (value.isBlank()) {
ApiResult.unknownFailure(IOException("Lobsters CSRF token was empty"))
} else {
block(value)
}
}
is Failure -> token.toUnitFailure()
}
}
private fun ReplyForm.isValid(): Boolean =
authenticityToken.isNotBlank() &&
storyId.isNotBlank() &&
method.isNotBlank() &&
parentCommentShortId.isNotBlank()
private fun part(name: String, value: String): MultipartBody.Part =
MultipartBody.Part.createFormData(name, value)
private fun Failure<*>.toUnitFailure(): Failure<Unit> =
when (this) {
is Failure.ApiFailure -> ApiResult.apiFailure(error as? Unit)
is Failure.HttpFailure -> ApiResult.httpFailure(code, error as? Unit)
is Failure.NetworkFailure -> ApiResult.networkFailure(error)
is Failure.UnknownFailure -> ApiResult.unknownFailure(error)
}
}
@@ -7,29 +7,91 @@
package dev.msfjarvis.claw.api
import com.slack.eithernet.ApiResult
import com.slack.eithernet.ApiResult.Failure
import com.slack.eithernet.ApiResult.Success
import dev.burnoo.kspoon.annotation.Selector
import dev.msfjarvis.claw.model.LobstersPost
import dev.msfjarvis.claw.model.LobstersPostDetails
import dev.msfjarvis.claw.model.Tag
import dev.msfjarvis.claw.model.User
import kotlinx.serialization.Serializable
import okhttp3.MultipartBody
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.Part
import retrofit2.http.Path
@Serializable class PostsPage(@Selector("li.story") val posts: List<LobstersPost> = emptyList())
@Serializable class TagsPage(@Selector("ol.category_tags > li") val tags: List<Tag> = emptyList())
fun ApiResult<PostsPage, Unit>.toPostsResult(): ApiResult<List<LobstersPost>, Unit> =
when (this) {
is Success -> ApiResult.success(value.posts)
is Failure.ApiFailure -> ApiResult.apiFailure(error)
is Failure.HttpFailure -> ApiResult.httpFailure(code, error)
is Failure.NetworkFailure -> ApiResult.networkFailure(error)
is Failure.UnknownFailure -> ApiResult.unknownFailure(error)
}
/** Simple interface defining an API for lobste.rs */
interface LobstersApi {
@GET("page/{page}.json")
suspend fun getHottestPosts(@Path("page") page: Int): ApiResult<List<LobstersPost>, Unit>
@GET("page/{page}")
suspend fun getHottestPosts(@Path("page") page: Int): ApiResult<PostsPage, Unit>
@GET("newest/page/{page}.json")
suspend fun getNewestPosts(@Path("page") page: Int): ApiResult<List<LobstersPost>, Unit>
@GET("newest/page/{page}")
suspend fun getNewestPosts(@Path("page") page: Int): ApiResult<PostsPage, Unit>
@GET("s/{postId}.json")
@GET("s/{postId}")
suspend fun getPostDetails(@Path("postId") postId: String): ApiResult<LobstersPostDetails, Unit>
@GET("~{username}.json")
suspend fun getUser(@Path("username") username: String): ApiResult<User, Unit>
@GET("~{username}") suspend fun getUser(@Path("username") username: String): ApiResult<User, Unit>
@GET("/") suspend fun getCSRFToken(): ApiResult<CSRFToken, Unit>
@GET("tags") suspend fun getTags(): ApiResult<TagsPage, Unit>
@Multipart
@POST("comments/{commentId}/upvote")
suspend fun upvoteComment(
@Path("commentId") commentId: String,
@Header("x-csrf-token") csrfToken: String,
@Header("x-requested-with") requestedWith: String,
@Part reason: MultipartBody.Part,
): ApiResult<Unit, Unit>
@Multipart
@POST("comments/{commentId}/unvote")
suspend fun unvoteComment(
@Path("commentId") commentId: String,
@Header("x-csrf-token") csrfToken: String,
@Header("x-requested-with") requestedWith: String,
@Part reason: MultipartBody.Part,
): ApiResult<Unit, Unit>
@GET("comments/{commentId}/reply")
suspend fun getReplyForm(
@Path("commentId") commentId: String,
@Header("x-csrf-token") csrfToken: String,
@Header("x-requested-with") requestedWith: String,
): ApiResult<ReplyForm, Unit>
@Multipart
@POST("comments")
suspend fun postReply(
@Header("x-csrf-token") csrfToken: String,
@Header("x-requested-with") requestedWith: String,
@Part authenticityToken: MultipartBody.Part,
@Part storyId: MultipartBody.Part,
@Part method: MultipartBody.Part,
@Part parentCommentShortId: MultipartBody.Part,
@Part comment: MultipartBody.Part,
@Part commit: MultipartBody.Part,
): ApiResult<Unit, Unit>
companion object {
const val BASE_URL = "https://lobste.rs"
}
@@ -0,0 +1,14 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.api
data class ReplyForm(
val authenticityToken: String,
val storyId: String,
val method: String,
val parentCommentShortId: String,
)
@@ -6,18 +6,18 @@
*/
package dev.msfjarvis.claw.api.converters
import com.fleeksoft.ksoup.Ksoup
import dev.msfjarvis.claw.api.CSRFToken
import dev.msfjarvis.claw.api.LobstersApi
import java.lang.reflect.Type
import okhttp3.ResponseBody
import org.jsoup.Jsoup
import retrofit2.Converter
import retrofit2.Retrofit
object CSRFTokenConverter : Converter<ResponseBody, CSRFToken> {
override fun convert(value: ResponseBody): CSRFToken {
val token =
Jsoup.parse(value.string(), LobstersApi.BASE_URL)
Ksoup.parse(value.string(), baseUri = LobstersApi.BASE_URL)
.select("meta[name=\"csrf-token\"]")
.firstOrNull()
?.attr("content")
@@ -30,8 +30,8 @@ object CSRFTokenConverter : Converter<ResponseBody, CSRFToken> {
type: Type,
annotations: Array<out Annotation>,
retrofit: Retrofit,
): Converter<ResponseBody, CSRFToken> {
return CSRFTokenConverter
): Converter<ResponseBody, *>? {
return if (type == CSRFToken::class.java) CSRFTokenConverter else null
}
}
}
@@ -0,0 +1,40 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.api.converters
import com.fleeksoft.ksoup.Ksoup
import dev.msfjarvis.claw.api.LobstersApi
import dev.msfjarvis.claw.api.ReplyForm
import java.lang.reflect.Type
import okhttp3.ResponseBody
import retrofit2.Converter
import retrofit2.Retrofit
object ReplyFormConverter : Converter<ResponseBody, ReplyForm> {
override fun convert(value: ResponseBody): ReplyForm {
val document = Ksoup.parse(value.string(), baseUri = LobstersApi.BASE_URL)
fun inputValue(name: String): String =
document.select("input[name=\"$name\"]").firstOrNull()?.attr("value").orEmpty()
return ReplyForm(
authenticityToken = inputValue("authenticity_token"),
storyId = inputValue("story_id"),
method = inputValue("_method"),
parentCommentShortId = inputValue("parent_comment_short_id"),
)
}
object Factory : Converter.Factory() {
override fun responseBodyConverter(
type: Type,
annotations: Array<out Annotation>,
retrofit: Retrofit,
): Converter<ResponseBody, *>? {
return if (type == ReplyForm::class.java) ReplyFormConverter else null
}
}
}
@@ -6,21 +6,21 @@
*/
package dev.msfjarvis.claw.api.converters
import com.fleeksoft.ksoup.Ksoup
import com.fleeksoft.ksoup.nodes.Element
import com.fleeksoft.ksoup.select.Elements
import dev.msfjarvis.claw.api.LobstersApi
import dev.msfjarvis.claw.model.LobstersPost
import java.lang.reflect.Type
import okhttp3.ResponseBody
import org.jsoup.Jsoup
import org.jsoup.nodes.Element
import org.jsoup.select.Elements
import retrofit2.Converter
import retrofit2.Retrofit
object SearchConverter : Converter<ResponseBody, List<LobstersPost>> {
override fun convert(value: ResponseBody): List<LobstersPost> {
val elements =
Jsoup.parse(value.string(), LobstersApi.BASE_URL).select("div.story_liner.h-entry")
return elements.map(::parsePost)
return Ksoup.parse(value.string(), baseUri = LobstersApi.BASE_URL)
.select("div.story_liner.h-entry")
.map(::parsePost)
}
private fun parsePost(elem: Element): LobstersPost {
@@ -31,7 +31,8 @@ object SearchConverter : Converter<ResponseBody, List<LobstersPost>> {
val url = titleElement.attr("href")
val tags = elem.select("span.tags > a").map(Element::text)
val (commentCount, commentsUrl) = getCommentsData(elem.select("span.comments_label"))
val submitter = elem.select("div.byline > a.u-author").text()
val submitter =
elem.select("div.byline > a[href^=/~]:not([tabindex]):not([aria-hidden=true])").text()
val userIsAuthor =
(elem.select("div.byline > span").first()?.text() ?: "").contains(
"authored",
@@ -0,0 +1,29 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.api.converters
import java.lang.reflect.Type
import kotlin.Unit
import okhttp3.ResponseBody
import retrofit2.Converter
import retrofit2.Retrofit
object UnitConverter : Converter<ResponseBody, Unit> {
override fun convert(value: ResponseBody) {
value.close()
}
object Factory : Converter.Factory() {
override fun responseBodyConverter(
type: Type,
annotations: Array<out Annotation>,
retrofit: Retrofit,
): Converter<ResponseBody, *>? {
return if (type == Unit::class.java) UnitConverter else null
}
}
}
@@ -6,24 +6,32 @@
*/
package dev.msfjarvis.claw.api.injection
import com.deliveryhero.whetstone.app.ApplicationScope
import com.fleeksoft.ksoup.Ksoup
import com.slack.eithernet.integration.retrofit.ApiResultCallAdapterFactory
import com.slack.eithernet.integration.retrofit.ApiResultConverterFactory
import com.squareup.anvil.annotations.ContributesTo
import dagger.Module
import dagger.Provides
import dagger.multibindings.IntKey
import dagger.multibindings.IntoMap
import dev.burnoo.kspoon.Kspoon
import dev.msfjarvis.claw.api.AuthenticatedLobstersApi
import dev.msfjarvis.claw.api.LobstersApi
import dev.msfjarvis.claw.api.LobstersSearchApi
import dev.msfjarvis.claw.api.converters.CSRFTokenConverter
import dev.msfjarvis.claw.api.converters.ReplyFormConverter
import dev.msfjarvis.claw.api.converters.SearchConverter
import javax.inject.Named
import javax.inject.Qualifier
import dev.msfjarvis.claw.api.converters.UnitConverter
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.BindingContainer
import dev.zacsweers.metro.ContributesTo
import dev.zacsweers.metro.IntKey
import dev.zacsweers.metro.IntoMap
import dev.zacsweers.metro.Named
import dev.zacsweers.metro.Provides
import dev.zacsweers.metro.Qualifier
import dev.zacsweers.metro.SingleIn
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import retrofit2.CallAdapter
import retrofit2.Converter
import retrofit2.Retrofit
import retrofit2.converter.kotlinx.serialization.asConverterFactory
import retrofit2.create
/**
@@ -33,14 +41,15 @@ import retrofit2.create
* uses [IntoMap] with [IntKey]s to fake the presence of a fixed order by sorting on the key of the
* injected [Map]s when injecting them into [Retrofit].
*/
@Module
@ContributesTo(ApplicationScope::class)
@BindingContainer
@ContributesTo(AppScope::class)
object RetrofitModule {
@Provides
@SingleIn(AppScope::class)
fun provideRetrofit(
client: OkHttpClient,
converterFactories: Map<Int, @JvmSuppressWildcards Converter.Factory>,
callAdapterFactories: Map<Int, @JvmSuppressWildcards CallAdapter.Factory>,
converterFactories: Map<Int, Converter.Factory>,
callAdapterFactories: Map<Int, CallAdapter.Factory>,
@Named("LobstersURL") baseUrl: String,
): Retrofit {
return Retrofit.Builder()
@@ -53,10 +62,11 @@ object RetrofitModule {
@Provides
@SearchApi
@SingleIn(AppScope::class)
fun provideSearchApiRetrofit(
client: OkHttpClient,
@SearchApi converterFactories: List<@JvmSuppressWildcards Converter.Factory>,
callAdapterFactories: Map<Int, @JvmSuppressWildcards CallAdapter.Factory>,
@SearchApi converterFactories: List<Converter.Factory>,
callAdapterFactories: Map<Int, CallAdapter.Factory>,
@Named("LobstersURL") baseUrl: String,
): Retrofit {
return Retrofit.Builder()
@@ -68,9 +78,17 @@ object RetrofitModule {
.build()
}
@Provides fun provideApi(retrofit: Retrofit): LobstersApi = retrofit.create()
@Provides
@SingleIn(AppScope::class)
fun provideApi(retrofit: Retrofit): LobstersApi = retrofit.create()
@Provides
@SingleIn(AppScope::class)
fun provideAuthenticatedApi(api: LobstersApi): AuthenticatedLobstersApi =
AuthenticatedLobstersApi(api)
@Provides
@SingleIn(AppScope::class)
fun provideSearchApi(@SearchApi retrofit: Retrofit): LobstersSearchApi = retrofit.create()
@Provides
@@ -78,19 +96,40 @@ object RetrofitModule {
@IntoMap
fun provideApiResultConverter(): Converter.Factory = ApiResultConverterFactory
@Provides
@IntKey(1)
@IntoMap
fun provideCSRFTokenConverter(): Converter.Factory = CSRFTokenConverter.Factory
@Provides
@IntKey(2)
@IntoMap
fun provideReplyFormConverter(): Converter.Factory = ReplyFormConverter.Factory
@Provides
@IntKey(3)
@IntoMap
fun provideUnitConverter(): Converter.Factory = UnitConverter.Factory
@Provides
@IntKey(4)
@IntoMap
fun provideKspoonConverter(): Converter.Factory =
Kspoon {
parse = { html -> Ksoup.parse(html, baseUri = LobstersApi.BASE_URL) }
coerceInputValues = true
}
.toFormat()
.asConverterFactory("text/html".toMediaType())
@Provides
@IntKey(0)
@IntoMap
fun provideApiResultCallAdapter(): CallAdapter.Factory = ApiResultCallAdapterFactory
@Provides
@IntKey(Int.MAX_VALUE)
@IntoMap
fun provideCSRFTokenConverter(): Converter.Factory = CSRFTokenConverter.Factory
@Provides
@SearchApi
fun provideConverters(): List<@JvmSuppressWildcards Converter.Factory> =
fun provideConverters(): List<Converter.Factory> =
listOf(ApiResultConverterFactory, SearchConverter.Factory)
@Provides @Named("LobstersURL") fun provideLobstersUrl(): String = LobstersApi.BASE_URL
@@ -0,0 +1,21 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.api
import com.slack.eithernet.ApiResult.Failure
import java.io.IOException
import java.net.HttpURLConnection
@Suppress("NOTHING_TO_INLINE") // We inline this to eliminate the stacktrace frame.
inline fun <T : Any> Failure.HttpFailure<T>.toError(): Throwable =
when (code) {
HttpURLConnection.HTTP_NOT_FOUND -> IOException("Story was removed by moderator")
HttpURLConnection.HTTP_INTERNAL_ERROR,
HttpURLConnection.HTTP_BAD_GATEWAY,
HttpURLConnection.HTTP_UNAVAILABLE -> IOException("It appears lobste.rs is currently down")
else -> IOException("API returned an invalid response")
}
@@ -6,13 +6,16 @@
*/
package dev.msfjarvis.claw.api
import com.fleeksoft.ksoup.Ksoup
import com.google.common.truth.Truth.assertThat
import com.slack.eithernet.ApiResult.Success
import com.slack.eithernet.test.newEitherNetController
import dev.msfjarvis.claw.model.LobstersPost
import dev.burnoo.kspoon.Kspoon
import dev.msfjarvis.claw.model.LobstersPostDetails
import dev.msfjarvis.claw.model.User
import dev.msfjarvis.claw.util.TestUtils.assertIs
import java.time.Instant
import java.time.format.DateTimeFormatter
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
@@ -24,23 +27,130 @@ class ApiTest {
@Test
fun `api gets correct number of items`() = runTest {
val posts = api.getHottestPosts(1)
assertIs<Success<List<LobstersPost>>>(posts)
assertThat(posts.value).hasSize(25)
assertIs<Success<PostsPage>>(posts)
assertThat(posts.value.posts).hasSize(25)
}
@Test
fun `posts with no urls`() = runTest {
val posts = api.getHottestPosts(1)
assertIs<Success<List<LobstersPost>>>(posts)
val commentsOnlyPosts = posts.value.asSequence().filter { it.url.isEmpty() }.toSet()
assertThat(commentsOnlyPosts).hasSize(1)
assertIs<Success<PostsPage>>(posts)
val commentsOnlyPosts = posts.value.posts.asSequence().filter { it.url.isEmpty() }.toSet()
assertThat(commentsOnlyPosts).hasSize(0)
}
@Test
fun `api parses hottest HTML fixture fields`() = runTest {
val posts = api.getHottestPosts(1)
assertIs<Success<PostsPage>>(posts)
val firstPost = posts.value.posts[0]
assertThat(firstPost.shortId).isEqualTo("jp3nva")
assertThat(firstPost.title).isEqualTo("You probably don't need Yocto, and that's fine")
assertThat(firstPost.submitter).isEqualTo("rw-rw-rw-")
assertThat(firstPost.commentCount).isEqualTo(9)
assertThat(firstPost.commentsUrl)
.isEqualTo("https://lobste.rs/s/jp3nva/you_probably_don_t_need_yocto_s_fine")
assertThat(firstPost.tags).containsExactly("linux")
assertThat(firstPost.userIsAuthor).isTrue()
assertThat(firstPost.createdAt).isEqualTo("2026-05-29T09:08:12Z")
DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(firstPost.createdAt)
val secondPost = posts.value.posts[1]
assertThat(secondPost.shortId).isEqualTo("lc26ar")
assertThat(secondPost.title).isEqualTo("SQLite Does Not Accept Agentic Code")
assertThat(secondPost.submitter).isEqualTo("hoistbypetard")
assertThat(secondPost.commentCount).isEqualTo(15)
assertThat(secondPost.commentsUrl)
.isEqualTo("https://lobste.rs/s/lc26ar/sqlite_does_not_accept_agentic_code")
assertThat(secondPost.tags).containsExactly("vibecoding")
assertThat(secondPost.userIsAuthor).isFalse()
val noCommentsPost = posts.value.posts.first { it.shortId == "1fkt8w" }
assertThat(noCommentsPost.title).isEqualTo("Patching my guitar amp's firmware")
assertThat(noCommentsPost.submitter).isEqualTo("mcf")
assertThat(noCommentsPost.commentCount).isEqualTo(0)
assertThat(noCommentsPost.commentsUrl)
.isEqualTo("https://lobste.rs/s/1fkt8w/patching_my_guitar_amp_s_firmware")
assertThat(noCommentsPost.tags).containsExactly("hardware", "reversing")
assertThat(noCommentsPost.userIsAuthor).isTrue()
}
@Test
fun `api gets newest posts`() = runTest {
val posts = api.getNewestPosts(1)
assertIs<Success<PostsPage>>(posts)
assertThat(posts.value.posts).hasSize(25)
}
@Test
fun `post details with comments`() = runTest {
val postDetails = api.getPostDetails("tdfoqh")
assertIs<Success<LobstersPostDetails>>(postDetails)
assertThat(postDetails.value.comments).hasSize(10)
val comments = postDetails.value.comments
assertThat(comments).hasSize(10)
assertThat(comments.first().user).isEqualTo("dpercy")
assertThat(comments.first { it.shortId == "pcvbcd" }.score).isEqualTo(2)
assertThat(comments.first().comment).contains("Maybe take the max, instead of the sum?")
assertThat(comments.first { it.shortId == "pcvbcd" }.parentComment).isEqualTo("m3wyu5")
assertThat(comments.first { it.shortId == "lqqn3a" }.parentComment).isEqualTo("owddle")
}
@Test
fun `comments without visible upvoter count have one point from the author`() {
val kspoon = Kspoon {
parse = { html -> Ksoup.parse(html, baseUri = LobstersApi.BASE_URL) }
coerceInputValues = true
}
val postDetails =
kspoon.parse<LobstersPostDetails>(
"""
<ol class="stories">
<li class="story" data-shortid="story1">
<span class="link h-cite"><a href="/s/story1/test">Test story</a></span>
<div class="byline">
<a class="u-author" href="/~/submitter">submitter</a>
<time data-at-unix="1710000000"></time>
</div>
</li>
</ol>
<ol class="comments">
<li class="comments_subtree">
<div class="comment" data-shortid="abc123">
<div class="voters"></div>
<div class="details">
<div class="byline">
<a href="/~/author">author</a>
<a href="/c/abc123"><time data-at-unix="1710000000"></time></a>
</div>
<div class="comment_text"><p>Hello</p></div>
</div>
</div>
</li>
</ol>
"""
.trimIndent()
)
assertThat(postDetails.comments.single().score).isEqualTo(1)
}
@Test
fun `edited comments expose a single timestamp and edited state`() = runTest {
val postDetails = api.getPostDetails("tdfoqh")
assertIs<Success<LobstersPostDetails>>(postDetails)
val editedComment = postDetails.value.comments.first { it.shortId == "pcvbcd" }
assertThat(editedComment.edited).isTrue()
assertThat(Instant.from(editedComment.timestamp).epochSecond).isEqualTo(1658588955)
}
@Test
fun `post details preserves upvoted comments`() = runTest {
val postDetails = wrapper.upvotedPostDetails
assertThat(postDetails.comments.filter { it.isUpvoted }.map { it.shortId })
.containsExactly("ncdsfc")
}
@Test
@@ -56,7 +166,27 @@ class ApiTest {
assertIs<Success<CSRFToken>>(token)
assertThat(token.value.value)
.isEqualTo(
"OZWykgFemPVeOSNmB53-ccKXe458X7xCInO1-qzFU6nk_9RCSrSQqS9OPmA5_pyy8qD3IYAIZ7XfAM3gdhJpkQ"
"dvJ8r_CkOImcHQ5ZLUWlJeQVoPEPQ3rK85DNgiZJcehafqwYP8jESW8AhMf0uQGLqqLbsarYiISCghnDaUd6wA"
)
}
@Test
fun `retrieve tags`() = runTest {
val tags = api.getTags()
assertIs<Success<TagsPage>>(tags)
assertThat(tags.value.tags).isNotEmpty()
val rubyTag = tags.value.tags.first { it.tag == "ruby" }
assertThat(rubyTag.description).isEqualTo("Ruby programming")
assertThat(rubyTag.privileged).isFalse()
assertThat(rubyTag.active).isTrue()
assertThat(rubyTag.category).isEmpty()
assertThat(rubyTag.isMedia).isFalse()
assertThat(rubyTag.hotnessMod).isEqualTo(0.0)
val newsTag = tags.value.tags.first { it.tag == "news" }
assertThat(newsTag.active).isFalse()
val videoTag = tags.value.tags.first { it.tag == "video" }
assertThat(videoTag.isMedia).isTrue()
}
}
@@ -6,44 +6,79 @@
*/
package dev.msfjarvis.claw.api
import com.fleeksoft.ksoup.Ksoup
import com.slack.eithernet.ApiResult.Companion.success
import com.slack.eithernet.test.EitherNetController
import com.slack.eithernet.test.enqueue
import dev.burnoo.kspoon.Kspoon
import dev.msfjarvis.claw.api.converters.CSRFTokenConverter
import dev.msfjarvis.claw.model.LobstersPost
import dev.msfjarvis.claw.model.LobstersPostDetails
import dev.msfjarvis.claw.model.User
import dev.msfjarvis.claw.util.TestUtils.getResource
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonNamingStrategy
import okhttp3.MediaType
import okhttp3.ResponseBody
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.ResponseBody.Companion.toResponseBody
@OptIn(ExperimentalSerializationApi::class)
class ApiWrapper(controller: EitherNetController<LobstersApi>) {
private val json = Json {
ignoreUnknownKeys = true
namingStrategy = JsonNamingStrategy.SnakeCase
private val kspoon = Kspoon {
parse = { html -> Ksoup.parse(html, baseUri = LobstersApi.BASE_URL) }
coerceInputValues = true
}
private val hottest: List<LobstersPost> = json.decodeFromString(getResource("hottest.json"))
private val postsPage: PostsPage = kspoon.parse<PostsPage>(getResource("hottest_page.html"))
private val postDetails: LobstersPostDetails =
json.decodeFromString(getResource("post_details_tdfoqh.json"))
private val user: User = json.decodeFromString(getResource("msfjarvis.json"))
kspoon.parse(getResource("post_details_tdfoqh.html"))
val upvotedPostDetails: LobstersPostDetails =
kspoon.parse(getResource("post_details_upvoted.html"))
private val user: User = kspoon.parse(getResource("msfjarvis.html"))
private val tags: TagsPage = kspoon.parse(getResource("tags.html"))
val api = controller.api
val authenticatedApi = AuthenticatedLobstersApi(api)
init {
controller.enqueue(LobstersApi::getHottestPosts) { success(hottest) }
controller.enqueue(LobstersApi::getHottestPosts) { success(hottest) }
controller.enqueue(LobstersApi::getHottestPosts) { success(postsPage) }
controller.enqueue(LobstersApi::getHottestPosts) { success(postsPage) }
controller.enqueue(LobstersApi::getHottestPosts) { success(postsPage) }
controller.enqueue(LobstersApi::getNewestPosts) { success(postsPage) }
controller.enqueue(LobstersApi::getPostDetails) { success(postDetails) }
controller.enqueue(LobstersApi::getUser) { success(user) }
controller.enqueue(LobstersApi::getTags) { success(tags) }
controller.enqueue(LobstersApi::getCSRFToken) {
success(
CSRFTokenConverter.convert(
ResponseBody.create(MediaType.get("text/html"), getResource("csrf_page.html"))
getResource("csrf_page.html").toResponseBody("text/html".toMediaType())
)
)
}
controller.enqueue(LobstersApi::getCSRFToken) {
success(
CSRFTokenConverter.convert(
getResource("csrf_page.html").toResponseBody("text/html".toMediaType())
)
)
}
controller.enqueue(LobstersApi::getCSRFToken) {
success(
CSRFTokenConverter.convert(
getResource("csrf_page.html").toResponseBody("text/html".toMediaType())
)
)
}
controller.enqueue(LobstersApi::getCSRFToken) {
success(
CSRFTokenConverter.convert(
getResource("csrf_page.html").toResponseBody("text/html".toMediaType())
)
)
}
controller.enqueue(LobstersApi::upvoteComment) { success(Unit) }
controller.enqueue(LobstersApi::unvoteComment) { success(Unit) }
controller.enqueue(LobstersApi::getReplyForm) {
success(
dev.msfjarvis.claw.api.converters.ReplyFormConverter.convert(
getResource("reply_form.html").toResponseBody("text/html".toMediaType())
)
)
}
controller.enqueue(LobstersApi::postReply) { success(Unit) }
}
}
@@ -0,0 +1,39 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.api
import com.slack.eithernet.ApiResult.Success
import com.slack.eithernet.test.newEitherNetController
import dev.msfjarvis.claw.util.TestUtils.assertIs
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
class AuthenticatedLobstersApiTest {
private val wrapper = ApiWrapper(newEitherNetController())
private val authenticatedApi = wrapper.authenticatedApi
@Test
fun `upvote comment succeeds`() = runTest {
val result = authenticatedApi.upvoteComment("0unzyg")
assertIs<Success<Unit>>(result)
}
@Test
fun `unvote comment succeeds`() = runTest {
val result = authenticatedApi.unvoteComment("0unzyg")
assertIs<Success<Unit>>(result)
}
@Test
fun `reply succeeds`() = runTest {
val result = authenticatedApi.reply("edtrox", "Thanks for the details!")
assertIs<Success<Unit>>(result)
}
}

Some files were not shown because too many files have changed in this diff Show More