Compare commits

...
212 Commits
Author SHA1 Message Date
GitHub Actions edfe72f12a feat(release): bump version 2024-08-30 14:30:24 +00:00
renovate[bot]andGitHub 4ae70e2129 chore(deps): update agp to v8.6.0 2024-08-30 01:26:51 +00:00
renovate[bot]andGitHub 54c1d4438c chore(deps): update plugin dependencyanalysis to v2.0.1 2024-08-29 21:51:58 +00:00
renovate[bot]andGitHub 3e673b09b6 chore(deps): update actions/setup-python digest to f677139 2024-08-29 18:40:39 +00:00
Harsh Shandilya 77439a5799 fix(common): correctly size spacer to match system bars 2024-08-29 23:13:26 +05:30
renovate[bot]andGitHub adf1af691f chore(deps): update github/codeql-action action to v3.26.6 2024-08-29 14:03:24 +00:00
Harsh Shandilya 1d60fca7eb chore: refresh changelog 2024-08-29 16:54:32 +05:30
Harsh Shandilya 8224b71465 fix(common): manually roll equals/hashCode for CommentNode
Excludes the parent field for comparison since it can trigger cycles in some situations

Fixes COMPOSE-LOBSTERS-5G
2024-08-29 16:47:48 +05:30
Harsh Shandilya bf52966772 fix(common): re-add spacer to comments page 2024-08-29 16:34:43 +05:30
Harsh Shandilya dc06410fc4 fix(common): set item keys in comments page 2024-08-29 16:34:15 +05:30
Harsh Shandilya 7b78438385 chore: refresh screenshots 2024-08-29 12:42:22 +05:30
Harsh Shandilya 077c855ef3 fix(android): give the right padding to SearchBar
Fixes #644
2024-08-29 12:42:22 +05:30
GitHub Actions f7f3f55261 feat(release): start next development iteration 2024-08-29 03:49:12 +00:00
GitHub Actions 74af4ad5b6 feat(release): bump version 2024-08-29 03:45:26 +00:00
renovate[bot]andGitHub 6988b5d412 fix(deps): update serialization to v1.7.2 2024-08-28 21:23:08 +00:00
renovate[bot]andGitHub ecc7b79720 chore(deps): update plugin modulegraphassert to v2.7.1 2024-08-28 20:29:15 +00:00
Harsh Shandilya 1de4916c9c feat: redesign bottom navigation bar 2024-08-28 15:17:38 +05:30
Harsh Shandilya 4f3bafc051 fix(android): migrate to Material3 pull refresh indicator 2024-08-28 13:42:36 +05:30
Harsh Shandilya 8651a4f66b refactor: lazily query saved and read state in UI
Having this always be read from the UI avoids values going stale inside data models

Fixes #641
2024-08-28 13:12:31 +05:30
Harsh Shandilya 6f424ae2d5 fix(android): avoid triggering a refresh on saving posts
The underlying issue is still not resolved since work on paging has stalled out in
Store5, but this trades the bad UX of an unwanted refresh for the slightly less
bad UX of the saved state sometimes being stale.

Fixes #558
2024-08-28 12:37:57 +05:30
Harsh Shandilya c8d1491196 refactor(android): inline refresh load state 2024-08-28 12:18:58 +05:30
Harsh Shandilya e68af9be16 chore: enable ktfmt in IDEA
It's more annoying to have code be incorrectly reformatted during editing than the occasional jumpy cursor
2024-08-28 12:08:50 +05:30
renovate[bot]andGitHub 482258a162 chore(deps): update plugin dependencyanalysis to v2 2024-08-28 00:37:41 +00:00
renovate[bot]andGitHub ee44d99f94 chore(deps): update plugin modulegraphassert to v2.7.0 2024-08-27 23:05:29 +00:00
renovate[bot]andGitHub 6a7de43b15 chore(deps): update gradle/actions digest to 16bf8bc 2024-08-27 01:31:32 +00:00
renovate[bot]andGitHub 89b490d841 fix(deps): update dependency com.diffplug.spotless:spotless-plugin-gradle to v7.0.0.beta2 2024-08-25 23:13:20 +00:00
Harsh Shandilya 1cbd3b5bd0 fix(common): reintroduce handling for first views for comment state 2024-08-24 16:02:56 +05:30
Harsh Shandilya 1dfcda4a64 feat(android): enable Sentry session replay 2024-08-24 16:02:56 +05:30
renovate[bot]andGitHub 18f6689c47 chore(deps): update github/codeql-action action to v3.26.5 2024-08-23 23:37:08 +00:00
renovate[bot]andGitHub 022e05696b chore(deps): update aboutlibraries to v11.2.3 2024-08-23 11:17:01 +00:00
renovate[bot]andGitHub e2bbe9c264 chore(deps): update plugin poko to v0.17.0 2024-08-23 03:23:10 +00:00
renovate[bot]andGitHub d0762b5cae chore(deps): update plugin ksp to v2.0.20-1.0.24 2024-08-22 21:43:00 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>Harsh Shandilya
f452833f6d chore(deps): update kotlin and compose to v2.0.20 (#635)
[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [org.jetbrains.kotlin.plugin.serialization](https://kotlinlang.org/)
([source](https://togithub.com/JetBrains/kotlin)) | `2.0.10` -> `2.0.20`
|
[![age](https://developer.mend.io/api/mc/badges/age/maven/org.jetbrains.kotlin.plugin.serialization/2.0.20?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/org.jetbrains.kotlin.plugin.serialization/2.0.20?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/org.jetbrains.kotlin.plugin.serialization/2.0.10/2.0.20?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/org.jetbrains.kotlin.plugin.serialization/2.0.10/2.0.20?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [org.jetbrains.kotlin.plugin.compose](https://kotlinlang.org/)
([source](https://togithub.com/JetBrains/kotlin)) | `2.0.10` -> `2.0.20`
|
[![age](https://developer.mend.io/api/mc/badges/age/maven/org.jetbrains.kotlin.plugin.compose/2.0.20?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/org.jetbrains.kotlin.plugin.compose/2.0.20?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/org.jetbrains.kotlin.plugin.compose/2.0.10/2.0.20?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/org.jetbrains.kotlin.plugin.compose/2.0.10/2.0.20?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [org.jetbrains.kotlin:kotlin-gradle-plugin](https://kotlinlang.org/)
([source](https://togithub.com/JetBrains/kotlin)) | `2.0.10` -> `2.0.20`
|
[![age](https://developer.mend.io/api/mc/badges/age/maven/org.jetbrains.kotlin:kotlin-gradle-plugin/2.0.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.0.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.0.10/2.0.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.0.10/2.0.20?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

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

###
[`v2.0.20`](https://togithub.com/JetBrains/kotlin/compare/v2.0.10...v2.0.20)

</details>

---

### Configuration

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

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, 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://www.mend.io/free-developer-tools/renovate/). View the
[repository job
log](https://developer.mend.io/github/msfjarvis/compose-lobsters).

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

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Harsh Shandilya <me@msfjarvis.dev>
2024-08-22 18:38:00 +00:00
Harsh Shandilya eb32c84b18 fix(renovate): update Compose compiler dep name 2024-08-22 22:03:18 +05:30
renovate[bot]andGitHub 8f19f794a1 fix(deps): update dependency dev.chrisbanes.compose:compose-bom to v2024.08.00-alpha02 2024-08-22 01:13:40 +00:00
renovate[bot]andGitHub 6bb5994a37 chore(deps): update github/codeql-action action to v3.26.4 2024-08-21 21:56:35 +00:00
renovate[bot]andGitHub 26ccc0d687 fix(deps): update androidx 2024-08-21 19:07:24 +00:00
Harsh Shandilya 39e3d783cd refactor: make PostComments non-nullable 2024-08-21 13:37:48 +05:30
Harsh Shandilya c50790e4f9 refactor(common): get rid of interior mutability in CommentNode 2024-08-21 13:13:38 +05:30
Harsh Shandilya d55ff24add refactor(common): cleanup unused stuff from tree view change
Follows up from changes in 6223198191
2024-08-21 13:13:38 +05:30
Harsh Shandilya 9b2a1cba64 chore: disable ktfmt in the IDE
The auto-format option messes up Compose code
2024-08-21 13:13:38 +05:30
Harsh Shandilya 327cce9075 fix(deps): upgrade Spotless to 7.0.0.BETA1 2024-08-21 13:13:38 +05:30
renovate[bot]andGitHub 466ec7c79f fix(deps): update dependency me.saket.unfurl:unfurl to v2.2.0 2024-08-20 21:25:32 +00:00
Harsh Shandilya 06c9fa1477 chore: refresh changelog 2024-08-21 01:31:55 +05:30
Harsh Shandilya 6223198191 refactor(common): use a simpler tree view for comments 2024-08-21 01:31:54 +05:30
renovate[bot]andGitHub 789e4a124d chore(deps): update dependency com.facebook:ktfmt to v0.52 2024-08-20 11:52:10 +00:00
renovate[bot]andGitHub e3745d0c58 chore(deps): update github/codeql-action action to v3.26.3 2024-08-19 19:03:49 +00:00
renovate[bot]andGitHub 26b35d4dd8 chore(deps): update plugin com.gradle.develocity to v3.18 2024-08-19 12:58:50 +00:00
renovate[bot]andGitHub 691a18b889 fix(deps): update dependency me.saket.unfurl:unfurl to v2 2024-08-15 06:38:31 +00:00
Harsh Shandilya 789339eacb Revert "fix(renovate): avoid Unfurl upgrades for now"
This reverts commit 995caae174.
2024-08-15 12:05:49 +05:30
renovate[bot]andGitHub 78a470dfdd fix(deps): update dependency androidx.test:runner to v1.6.2 2024-08-14 23:49:24 +00:00
renovate[bot]andGitHub eb3185d883 chore(deps): update dependency gradle to v8.10 2024-08-14 19:58:32 +00:00
renovate[bot]andGitHub bf3d507990 chore(deps): update github/codeql-action action to v3.26.2 2024-08-14 17:03:51 +00:00
renovate[bot]andGitHub faa79bcb12 chore(deps): update dependency de.mannodermaus.android-junit5 to v1.11.0.0 2024-08-14 14:54:07 +00:00
renovate[bot]andGitHub e8dcb0dfba fix(deps): update junit5 monorepo to v5.11.0 2024-08-14 10:29:54 +00:00
renovate[bot]andGitHub d502716652 chore(deps): update github/codeql-action action to v3.26.1 2024-08-13 21:03:56 +00:00
renovate[bot]andGitHub bb29924822 fix(deps): update coroutines to v1.9.0-rc.2 2024-08-13 11:55:57 +00:00
renovate[bot]andGitHub 101cd8894f fix(deps): update sentry to v7.14.0 2024-08-13 09:50:01 +00:00
Harsh Shandilya 995caae174 fix(renovate): avoid Unfurl upgrades for now 2024-08-12 15:40:50 +05:30
Harsh Shandilya 1a7b1fbdd1 Revert "fix(deps): update dependency me.saket.unfurl:unfurl to v2"
This reverts commit 7875c63bde.
2024-08-12 15:40:11 +05:30
Harsh Shandilya 9c6008b36e refactor(common): rename Comments.kt 2024-08-12 00:54:18 +05:30
renovate[bot]andGitHub 7875c63bde fix(deps): update dependency me.saket.unfurl:unfurl to v2 2024-08-09 11:23:26 +00:00
renovate[bot]andGitHub 68fe458f7d chore(deps): update agp to v8.5.2 2024-08-09 05:10:22 +00:00
renovate[bot]andGitHub f611b9aa25 fix(deps): update androidx 2024-08-08 01:09:29 +00:00
Harsh Shandilya 67b253ba62 fix(android): reorder settings items 2024-08-08 03:44:03 +05:30
Harsh Shandilya a8f559fe06 fix(build): reduce Gradle heap size 2024-08-08 03:44:03 +05:30
Harsh Shandilya 45dab6d513 fix(build): compile against SDK 35 2024-08-08 03:44:03 +05:30
Harsh Shandilya 13f6467a16 fix(deps): upgrade dev.chrisbanes.compose:compose-bom to 2024.08.00-alpha01 2024-08-08 02:45:40 +05:30
renovate[bot]andGitHub 871abf7873 fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v4.11.0 2024-08-07 11:55:06 +00:00
renovate[bot]andGitHub 866b487256 chore(deps): update plugin ksp to v2.0.10-1.0.24 2024-08-06 20:32:09 +00:00
renovate[bot]andGitHub 63adacbea8 chore(deps): update github/codeql-action action to v3.26.0 2024-08-06 18:22:54 +00:00
renovate[bot]andGitHub 65b10a0071 chore(deps): update actions/upload-artifact action to v4.3.6 2024-08-06 16:16:33 +00:00
renovate[bot]andGitHub e20af353ac chore(deps): update dependency org.jetbrains.kotlin.plugin.compose to v2.0.10 2024-08-06 12:11:25 +00:00
renovate[bot]andGitHub ba0f62febe fix(deps): update dagger to v2.52 2024-08-05 20:13:37 +00:00
renovate[bot]andGitHub 3a1012288d chore(deps): update actions/setup-java action to v4.2.2 2024-08-05 16:59:32 +00:00
renovate[bot]andGitHub 8424798257 chore(deps): update gradle/actions action to v4 2024-08-04 01:01:10 +00:00
renovate[bot]andGitHub a7e5ede51a chore(deps): update actions/upload-artifact action to v4.3.5 2024-08-02 15:13:54 +00:00
Harsh Shandilya 1197e7fd7f fix(build): remove unused libraries 2024-08-01 22:45:49 +05:30
renovate[bot]andGitHub 72a684dbd1 fix(deps): update sentry to v7.13.0 2024-07-31 10:07:24 +00:00
renovate[bot]andGitHub ba47885490 chore(deps): update plugin anvil to v2.5.0-beta11 2024-07-30 03:02:55 +00:00
renovate[bot]andGitHub 9f153d62ba chore(deps): update plugin ksp to v2.0.0-1.0.24 2024-07-30 00:46:37 +00:00
renovate[bot]andGitHub 9bceb5c680 chore(deps): update plugin dependencyanalysis to v1.33.0 2024-07-27 19:58:13 +00:00
renovate[bot]andGitHub 99d69f1bef chore(deps): update github/codeql-action action to v3.25.15 2024-07-26 14:31:04 +00:00
renovate[bot]andGitHub 1a72d2cbbe fix(deps): update sentry to v7.12.1 2024-07-25 21:46:41 +00:00
renovate[bot]andGitHub e22ecb51d3 fix(deps): update dependency dev.chrisbanes.compose:compose-bom to v2024.07.00-alpha02 2024-07-25 18:08:09 +00:00
renovate[bot]andGitHub 68f779c395 chore(deps): update dependency de.mannodermaus.android-junit5 to v1.10.2.0 2024-07-25 14:31:08 +00:00
renovate[bot]andGitHub 954341c0fb chore(deps): update github/codeql-action action to v3.25.14 2024-07-25 08:57:30 +00:00
renovate[bot]andGitHub 798ffb8bb6 fix(deps): update androidx 2024-07-24 17:36:50 +00:00
renovate[bot]andGitHub a137900ac7 chore(deps): update plugin modulegraphassert to v2.6.0 2024-07-24 11:39:18 +00:00
renovate[bot]andGitHub fdd4f3d1dc chore(deps): update plugin anvil to v2.5.0-beta10 2024-07-23 00:04:50 +00:00
renovate[bot]andGitHub 4c975471fd chore(deps): update plugin com.gradle.develocity to v3.17.6 2024-07-22 10:30:48 +00:00
renovate[bot]andGitHub e2d530635d chore(deps): update github/codeql-action action to v3.25.13 2024-07-19 19:33:18 +00:00
Harsh Shandilya 371780a1fd fixup! feat(common): visually distinguish between read and unread comments 2024-07-18 16:23:20 +05:30
renovate[bot]andGitHub b1d6cbcb39 fix(deps): update coil to v2.7.0 2024-07-17 18:56:26 +00:00
renovate[bot]andGitHub a07c55a430 fix(deps): update sentry 2024-07-16 01:31:16 +00:00
renovate[bot]andGitHub 8415c46492 chore(deps): update gradle/actions digest to d9c87d4 2024-07-15 22:21:24 +00:00
renovate[bot]andGitHub 771892bfb1 fix(deps): update dependency com.google.truth:truth to v1.4.4 2024-07-12 22:31:07 +00:00
renovate[bot]andGitHub 66e69a333c chore(deps): update github/codeql-action action to v3.25.12 2024-07-12 17:13:51 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
67dccf949c chore(deps): update actions/dependency-review-action digest to 5a2ce3f (#611)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-12 20:10:19 +05:30
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
f411460841 chore(deps): update agp to v8.5.1 (#608)
[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

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.5.0` -> `8.5.1` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/com.android.test/8.5.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/com.android.test/8.5.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/com.android.test/8.5.0/8.5.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.android.test/8.5.0/8.5.1?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.5.0` -> `8.5.1` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/com.android.lint/8.5.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/com.android.lint/8.5.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/com.android.lint/8.5.0/8.5.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.android.lint/8.5.0/8.5.1?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.5.0` -> `8.5.1` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/com.android.tools.build:gradle/8.5.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/com.android.tools.build:gradle/8.5.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/com.android.tools.build:gradle/8.5.0/8.5.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.android.tools.build:gradle/8.5.0/8.5.1?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 becomes conflicted, 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 has been generated by [Mend
Renovate](https://www.mend.io/free-developer-tools/renovate/). View
repository job log
[here](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy40MjUuMSIsInVwZGF0ZWRJblZlciI6IjM3LjQyNS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-12 10:01:27 +00:00
renovate[bot]andGitHub 17bd98d96d chore(deps): update plugin ksp to v2.0.0-1.0.23 2024-07-12 07:02:25 +00:00
Harsh Shandilya fa2c0bc047 fix(deps): update dev.chrisbanes.compose:compose-bom to 2024.07.00-alpha01 2024-07-12 02:08:34 +05:30
renovate[bot]andGitHub 3dbca71de3 chore(deps): update dependency gradle to v8.9 2024-07-11 17:15:05 +00:00
renovate[bot]andGitHub 2c4f0fa4e2 chore(deps): update androidx 2024-07-10 18:00:45 +00:00
renovate[bot]andGitHub ce7e9db135 chore(deps): update actions/setup-python digest to 39cd149 2024-07-10 15:53:17 +00:00
renovate[bot]andGitHub babd40109b fix(deps): update dependency org.jsoup:jsoup to v1.18.1 2024-07-10 08:23:50 +00:00
Harsh Shandilya f1fcaf53f4 feat(android): link up bottombar visibility with scroll 2024-07-09 23:43:31 +05:30
renovate[bot]andGitHub d678843230 fix(deps): update dependency org.mobilenativefoundation.store:store5 to v5.1.0-alpha04 2024-07-07 22:40:08 +00:00
renovate[bot]andGitHub f7d9447ca0 fix(deps): update dependency org.mobilenativefoundation.store:store5 to v5.1.0-alpha03 2024-07-07 18:25:34 +00:00
Harsh Shandilya 03b0097ae1 chore: brush up changelog 2024-07-07 23:53:33 +05:30
Harsh Shandilya a53af77705 feat(common): visually distinguish between read and unread comments 2024-07-07 23:42:19 +05:30
Harsh Shandilya 05a8b9bc67 fix(build): only enable -Werror in CI 2024-07-07 23:18:38 +05:30
renovate[bot]andGitHub 9815ef1910 chore(deps): update dependency gradle to v8.9-rc-2 2024-07-05 18:02:15 +00:00
renovate[bot]andGitHub 7c9f61eb5e chore(deps): update actions/upload-artifact action to v4.3.4 2024-07-05 17:13:10 +00:00
Harsh Shandilya 8f69490c11 Reapply "refactor(android): migrate to navigation safe-args"
This reverts commit 6296afe51a.
2024-07-03 14:03:48 +05:30
renovate[bot]andGitHub a7dd2c52f5 fix(deps): update konvert to v3.2.2 2024-07-03 01:08:28 +00:00
renovate[bot]andGitHub 100387c759 fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v4.9.0 2024-07-02 13:24:02 +00:00
renovate[bot]andGitHub ce8d79f08c chore(deps): update mobsf/mobsfscan digest to 25184ed 2024-07-01 20:11:50 +00:00
renovate[bot]andGitHub 4a75ea99a3 fix(deps): update sentry to v7.11.0 2024-07-01 17:20:17 +00:00
renovate[bot]andGitHub bd8148b74b fix(deps): update dependency androidx.lifecycle:lifecycle-viewmodel-compose to v2.8.3 2024-07-01 17:04:59 +00:00
renovate[bot]andGitHub 2b9810c5d4 chore(deps): update plugin invert to v0.0.1-dev 2024-07-01 04:53:54 +00:00
renovate[bot]andGitHub 0e5052a5bb chore(deps): update aboutlibraries to v11.2.2 2024-06-29 14:02:17 +00:00
renovate[bot]andGitHub ffe40d41fd chore(deps): update github/codeql-action action to v3.25.11 2024-06-28 16:07:17 +00:00
renovate[bot]andGitHub 46cf368b99 fix(deps): update junit5 monorepo to v5.10.3 2024-06-27 15:38:30 +00:00
renovate[bot]andGitHub 1d0386f332 fix(deps): update dependency com.google.truth:truth to v1.4.3 2024-06-27 15:28:57 +00:00
Harsh Shandilya 4a621fa120 fix(deps): update dev.chrisbanes.compose:compose-bom to 2024.06.00-alpha01 2024-06-27 20:14:15 +05:30
renovate[bot]andGitHub a53dece4d1 fix(deps): update androidx 2024-06-27 00:50:09 +00:00
renovate[bot]andGitHub a2c6fb40bf fix(deps): update dependency androidx.navigation:navigation-compose to v2.8.0-beta04 2024-06-26 18:48:41 +00:00
Harsh Shandilya b132410282 feat: show "Authored by" on posts when applicable 2024-06-26 21:29:41 +05:30
Harsh Shandilya 4ebb99bf7d fix(build): update to Gradle 8.9-rc-1 2024-06-26 21:11:16 +05:30
Harsh Shandilya 3a15eea2fd Revert "fix(common): temporarily disable collapsible comments feature"
Doesn't fix the bug.

This reverts commit 7ae94ee2e6.
2024-06-26 12:01:16 +05:30
Harsh Shandilya 3a6f7be808 fix(android): surface upstream downtime more prominently 2024-06-26 11:58:12 +05:30
renovate[bot]andGitHub 1cb978aa92 fix(deps): update serialization to v1.7.1 2024-06-25 16:46:06 +00:00
renovate[bot]andGitHub e0de9c97de fix(deps): update androidx 2024-06-24 23:25:23 +00:00
Harsh Shandilya 7ae94ee2e6 fix(common): temporarily disable collapsible comments feature 2024-06-22 19:01:34 +05:30
renovate[bot]andGitHub 5d8467bc67 fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v4.8.0 2024-06-19 19:51:56 +00:00
renovate[bot]andGitHub 378f4e413a chore(deps): update gradle/actions digest to dbbdc27 2024-06-17 22:50:23 +00:00
renovate[bot]andGitHub 6727424092 chore(deps): update gradle/actions digest to 31ae356 2024-06-15 08:10:21 +00:00
renovate[bot]andGitHub 9594a4dc9e chore(deps): update dependency com.facebook:ktfmt to v0.51 2024-06-14 15:33:25 +00:00
renovate[bot]andGitHub f7fb007f85 chore(deps): update gradle/actions digest to d9336da 2024-06-13 21:13:45 +00:00
renovate[bot]andGitHub 710d2d2c35 chore(deps): update agp to v8.5.0 2024-06-13 18:20:45 +00:00
renovate[bot]andGitHub c3139bfc9d chore(deps): update github/codeql-action action to v3.25.10 2024-06-13 17:50:28 +00:00
Harsh Shandilya e2bcabe4e5 chore: refresh Lint baseline 2024-06-13 20:56:45 +05:30
Harsh Shandilya 9610241489 fix(common): tweak LobstersCard layout to improve touch targets 2024-06-13 20:46:01 +05:30
Harsh Shandilya cef05306ba fix(android): update LeakCanary API usage
Fixes 9a1f42764e
2024-06-13 20:46:01 +05:30
Harsh Shandilya c4aad66c03 refactor(common): move setExpanded to CommentNode 2024-06-13 20:46:01 +05:30
renovate[bot]andGitHub 7f2b052df5 fix(deps): update androidx 2024-06-13 00:38:31 +00:00
renovate[bot]andGitHub 0a3bf1e06a chore(deps): update actions/checkout action to v4.1.7 2024-06-12 21:15:14 +00:00
renovate[bot]andGitHub a779bee74c chore(deps): update plugin com.gradle.develocity to v3.17.5 2024-06-12 18:06:08 +00:00
renovate[bot]andGitHub 7ac87046ae chore(deps): update github/codeql-action action to v3.25.9 2024-06-12 17:04:50 +00:00
Harsh Shandilya 742a30bfdd Revert "fix(android): attempt to workaround duplicate item keys bug"
Doesn't seem to have resolved the issue.

This reverts commit 9c07169b1b.
2024-06-11 19:45:58 +05:30
renovate[bot]andGitHub ecad03e749 chore(deps): update agp to v8.4.2 2024-06-10 16:52:14 +00:00
Harsh Shandilya d2289da184 feat(build): add Invert plugin 2024-06-08 12:57:19 +05:30
Harsh Shandilya 2e91db6908 feat(store): init module
Fixes PP-83
2024-06-07 17:50:03 +05:30
Harsh Shandilya 679e177d47 chore: adjust Renovate config 2024-06-07 10:40:18 +05:30
renovate[bot]andGitHub feb4aa7b17 chore(deps): update dependency com.facebook:ktfmt to v0.50 2024-06-07 03:10:05 +00:00
renovate[bot]andGitHub c6bae7d92a fix(deps): update serialization to v1.7.0 2024-06-06 14:31:42 +00:00
renovate[bot]andGitHub 0277b987da fix(deps): update dependency io.sentry:sentry-bom to v7.10.0 2024-06-06 13:33:28 +00:00
renovate[bot]andGitHub 5024bfa160 fix(deps): update dependency com.slack.eithernet:eithernet to v1.9.0 2024-06-06 04:57:02 +00:00
renovate[bot]andGitHub 60c3d91087 chore(deps): update plugin ksp to v2.0.0-1.0.22 2024-06-06 01:12:50 +00:00
renovate[bot]andGitHub 559cfbb223 chore(deps): update actions/dependency-review-action digest to 72eb03d 2024-06-05 21:47:19 +00:00
renovate[bot]andGitHub 8e1e63b573 fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v4.7.1 2024-06-05 14:30:59 +00:00
GitHub Actions b0c6806226 feat(release): start next development iteration 2024-06-05 07:52:29 +00:00
GitHub Actions 6e29d61799 feat(release): bump version 2024-06-05 07:49:10 +00:00
Harsh Shandilya 1ee9d92b40 chore: update changelog 2024-06-05 13:12:57 +05:30
Harsh Shandilya 9c07169b1b fix(android): attempt to workaround duplicate item keys bug
Fixes COMPOSE-LOBSTERS-2E
2024-06-05 13:12:52 +05:30
renovate[bot]andGitHub 81f4d35f93 fix(deps): update dependency com.squareup.leakcanary:leakcanary-android-test to v3.0-alpha-8 2024-06-04 23:13:29 +00:00
renovate[bot]andGitHub 361082ded4 chore(deps): update github/codeql-action action to v3.25.8 2024-06-04 16:15:41 +00:00
renovate[bot]andGitHub 4fe43dbdc9 fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v4.7.0 2024-06-04 08:22:24 +00:00
renovate[bot]andGitHub cef45bd3e5 chore(deps): update dependency gradle to v8.8 2024-05-31 22:51:30 +00:00
renovate[bot]andGitHub c45dd76fc4 chore(deps): update github/codeql-action action to v3.25.7 2024-05-31 10:52:15 +00:00
renovate[bot]andGitHub 9a1f42764e fix(deps): update dependency com.squareup.leakcanary:leakcanary-android-test to v3.0-alpha-7 2024-05-31 02:01:11 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
e62a0eaa84 fix(deps): update androidx (#577)
[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [androidx.test:runner](https://developer.android.com/testing) |
`1.6.0-beta01` -> `1.6.0-rc01` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.test:runner/1.6.0-rc01?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/androidx.test:runner/1.6.0-rc01?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/androidx.test:runner/1.6.0-beta01/1.6.0-rc01?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.test:runner/1.6.0-beta01/1.6.0-rc01?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [androidx.test:rules](https://developer.android.com/testing) |
`1.6.0-beta01` -> `1.6.0-rc01` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.test:rules/1.6.0-rc01?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/androidx.test:rules/1.6.0-rc01?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/androidx.test:rules/1.6.0-beta01/1.6.0-rc01?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.test:rules/1.6.0-beta01/1.6.0-rc01?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [androidx.test.ext:junit](https://developer.android.com/testing) |
`1.2.0-beta01` -> `1.2.0-rc01` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.test.ext:junit/1.2.0-rc01?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/androidx.test.ext:junit/1.2.0-rc01?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/androidx.test.ext:junit/1.2.0-beta01/1.2.0-rc01?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.test.ext:junit/1.2.0-beta01/1.2.0-rc01?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
|
[androidx.test.espresso:espresso-core](https://developer.android.com/testing)
| `3.6.0-beta01` -> `3.6.0-rc01` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.test.espresso:espresso-core/3.6.0-rc01?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/androidx.test.espresso:espresso-core/3.6.0-rc01?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/androidx.test.espresso:espresso-core/3.6.0-beta01/3.6.0-rc01?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.test.espresso:espresso-core/3.6.0-beta01/3.6.0-rc01?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [androidx.test:core](https://developer.android.com/testing) |
`1.6.0-beta01` -> `1.6.0-rc01` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/androidx.test:core/1.6.0-rc01?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/androidx.test:core/1.6.0-rc01?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/androidx.test:core/1.6.0-beta01/1.6.0-rc01?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/androidx.test:core/1.6.0-beta01/1.6.0-rc01?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 becomes conflicted, or you tick the
rebase/retry checkbox.

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

---

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

---

This PR has been generated by [Mend
Renovate](https://www.mend.io/free-developer-tools/renovate/). View
repository job log
[here](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy4zNzcuOCIsInVwZGF0ZWRJblZlciI6IjM3LjM3Ny44IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-30 21:13:05 +00:00
Harsh Shandilya 6296afe51a Revert "refactor(android): migrate to navigation safe-args"
This reverts commit cf390de486 and bb3cef2f5b.
2024-05-31 01:05:14 +05:30
Harsh Shandilya cf390de486 refactor(android): move navigation extensions to their own file 2024-05-30 18:07:23 +05:30
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
99a45ab5df chore(deps): update aboutlibraries to v11.2.1 (#574)
[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

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

---

### Release Notes

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

###
[`v11.2.1`](https://togithub.com/mikepenz/AboutLibraries/releases/tag/11.2.1)

[Compare
Source](https://togithub.com/mikepenz/AboutLibraries/compare/11.2.0...11.2.1)

#### 💬 Other

-   Reduce noise of AmbiguousArtifactVariantsException logs even more
- PR:
[#&#8203;990](https://togithub.com/mikepenz/AboutLibraries/issues/990)
    -   Thanks [@&#8203;sebokopter](https://togithub.com/sebokopter)

</details>

---

### Configuration

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

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, 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 has been generated by [Mend
Renovate](https://www.mend.io/free-developer-tools/renovate/). View
repository job log
[here](https://developer.mend.io/github/msfjarvis/compose-lobsters).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy4zNzcuOCIsInVwZGF0ZWRJblZlciI6IjM3LjM3Ny44IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-30 10:57:41 +00:00
Harsh Shandilya 5257d0876a fix(ci): explicitly run assemble task
Seems like the Sentry Gradle plugin doesn't run otherwise
2024-05-30 14:17:39 +05:30
Harsh Shandilya bb3cef2f5b refactor(android): migrate to navigation safe-args
(cherry picked from commit 127a69249e)

Fixes PP-77
2024-05-30 14:03:42 +05:30
Harsh Shandilya a791588423 fix(android): work around weird scroll issue with saved posts 2024-05-30 13:07:51 +05:30
Harsh Shandilya 535624bf22 fix(deps): upgrade to Compose 1.7 betas 2024-05-30 13:04:02 +05:30
Harsh Shandilya b5a7225bd8 chore: update gitignore 2024-05-30 03:19:12 +05:30
Harsh Shandilya 8c0734a19a fix(build): enable Compose strong skipping mode 2024-05-30 02:02:12 +05:30
renovate[bot]andGitHub 71afc634b9 fix(deps): update androidx 2024-05-29 17:28:26 +00:00
renovate[bot]andGitHub fbaede8215 chore(deps): update plugin poko to v0.16.0 2024-05-28 23:03:26 +00:00
Harsh Shandilya 948aea7ea8 fix(deps): upgrade coroutines to 1.9.0-RC 2024-05-29 01:16:44 +05:30
renovate[bot]andGitHub 6acab19b28 chore(deps): update mobsf/mobsfscan digest to 849b749 2024-05-27 22:55:17 +00:00
renovate[bot]andGitHub 2555140ca1 chore(deps): update dependency gradle to v8.8-rc-2 2024-05-27 09:48:31 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
4a5980fd62 fix(deps): update konvert to v3.2.1 (#569)
[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [io.mcarle:konvert](https://togithub.com/mcarleio/konvert) | `3.2.0`
-> `3.2.1` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/io.mcarle:konvert/3.2.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/io.mcarle:konvert/3.2.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/io.mcarle:konvert/3.2.0/3.2.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/io.mcarle:konvert/3.2.0/3.2.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [io.mcarle:konvert-api](https://togithub.com/mcarleio/konvert) |
`3.2.0` -> `3.2.1` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/io.mcarle:konvert-api/3.2.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/io.mcarle:konvert-api/3.2.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/io.mcarle:konvert-api/3.2.0/3.2.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/io.mcarle:konvert-api/3.2.0/3.2.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [io.mcarle:konvert-annotations](https://togithub.com/mcarleio/konvert)
| `3.2.0` -> `3.2.1` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/io.mcarle:konvert-annotations/3.2.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/io.mcarle:konvert-annotations/3.2.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/io.mcarle:konvert-annotations/3.2.0/3.2.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/io.mcarle:konvert-annotations/3.2.0/3.2.1?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 becomes conflicted, 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 has been generated by [Mend
Renovate](https://www.mend.io/free-developer-tools/renovate/). View
repository job log
[here](https://developer.mend.io/github/msfjarvis/compose-lobsters).

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-24 09:04:05 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
215afd208e chore(deps): update plugin dependencyanalysis to v1.32.0 (#568)
[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

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

---

### Release Notes

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

###
[`v1.32.0`](https://togithub.com/autonomousapps/dependency-analysis-android-gradle-plugin/blob/HEAD/CHANGELOG.md#Version-1320)

- \[New]: Output dominator tree results in JSON format including size
and total size of deps.
-   \[New]: Allow to force app behavior for pure Java projects.
-   \[New]: `generateProjectGraph` task.
-   \[New]: `reason` works for multi-capabilities.
-   \[New]: Print build file path in `projectHealth` console report.
- \[Fix]: Enhance logging (more) when `ConstantPoolParser` throws
exception.
- \[Fix]: Do not dotty for path matching and remove prefix and suffix
from binary class name.
-   \[Fix]: Fix Windows file separator incompatibility.
- \[Fix]: Don't suggest adding `testImplementation` dependency on self.
-   \[Fix]: DAGP variants have a `Category` of 'dependency-analysis'.
-   \[Fix]: Sort an input map for better reproducibility.

</details>

---

### Configuration

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

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, 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 has been generated by [Mend
Renovate](https://www.mend.io/free-developer-tools/renovate/). View
repository job log
[here](https://developer.mend.io/github/msfjarvis/compose-lobsters).

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-24 04:41:12 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
806ef65e08 fix(deps): update dependency com.squareup.leakcanary:leakcanary-android-test to v3.0-alpha-6 (#566)
[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
|
[com.squareup.leakcanary:leakcanary-android-test](https://togithub.com/square/leakcanary)
| `3.0-alpha-5` -> `3.0-alpha-6` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/com.squareup.leakcanary:leakcanary-android-test/3.0-alpha-6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/com.squareup.leakcanary:leakcanary-android-test/3.0-alpha-6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/com.squareup.leakcanary:leakcanary-android-test/3.0-alpha-5/3.0-alpha-6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.squareup.leakcanary:leakcanary-android-test/3.0-alpha-5/3.0-alpha-6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>square/leakcanary
(com.squareup.leakcanary:leakcanary-android-test)</summary>

###
[`v3.0-alpha-6`](https://togithub.com/square/leakcanary/releases/tag/v3.0-alpha-6)

See [Change Log](https://square.github.io/leakcanary/changelog)

</details>

---

### Configuration

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

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR becomes conflicted, 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 has been generated by [Mend
Renovate](https://www.mend.io/free-developer-tools/renovate/). View
repository job log
[here](https://developer.mend.io/github/msfjarvis/compose-lobsters).

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-22 16:43:47 +00:00
Harsh Shandilya 35e143a458 chore: update changelog 2024-05-22 14:29:21 +05:30
Harsh Shandilya 5b4d1fc48e Merge remote-tracking branches 'origin/renovate/aboutlibraries' and 'origin/renovate/com.slack.lint-slack-lint-checks-0.x'
* origin/renovate/aboutlibraries:
  chore(deps): update aboutlibraries to v11.2.0

* origin/renovate/com.slack.lint-slack-lint-checks-0.x:
  fix(deps): update dependency com.slack.lint:slack-lint-checks to v0.7.4
2024-05-22 14:14:05 +05:30
renovate[bot]andGitHub c9098bbd44 chore(deps): update aboutlibraries to v11.2.0 2024-05-21 21:11:40 +00:00
renovate[bot]andGitHub b7eecd10b4 fix(deps): update dependency com.slack.lint:slack-lint-checks to v0.7.4 2024-05-21 21:11:36 +00:00
Harsh Shandilya c471fee4ee fix(build): cut over to upstream Compose Compiler 2024-05-22 02:40:24 +05:30
Harsh Shandilya e3ed059fde fix(deps): upgrade ksp and poko 2024-05-22 02:39:02 +05:30
renovate[bot]andGitHub 44355de333 chore(deps): update kotlin and compose to v2 2024-05-21 14:31:31 +00:00
renovate[bot]andGitHub 92dc6d9e4c fix(deps): update dependency com.squareup.leakcanary:leakcanary-android-test to v3.0-alpha-5 2024-05-21 05:58:36 +00:00
renovate[bot]andGitHub 68ca569b9d chore(deps): update agp to v8.4.1 2024-05-20 23:29:23 +00:00
renovate[bot]andGitHub 105a33e335 chore(deps): update github/codeql-action action to v3.25.6 2024-05-20 15:03:52 +00:00
renovate[bot]andGitHub 7862b4b5c8 chore(deps): update plugin poko to v0.15.3 2024-05-20 00:10:30 +00:00
Harsh Shandilya b12b5c800f feat(android): add share action 2024-05-20 01:37:00 +05:30
Harsh Shandilya b56de40b12 fix(build): downgrade to stable AGP 2024-05-20 01:29:22 +05:30
renovate[bot]andGitHub 8243b8998c chore(deps): update plugin com.gradle.develocity to v3.17.4 2024-05-17 17:50:12 +00:00
renovate[bot]andGitHub 195b50f919 fix(deps): update androidx 2024-05-17 02:28:27 +00:00
Harsh Shandilya 2d7903efc1 Merge remote-tracking branches 'origin/renovate/actions-checkout-4.x', 'origin/renovate/agp' and 'origin/renovate/sentry'
* origin/renovate/actions-checkout-4.x:
  chore(deps): update actions/checkout action to v4.1.6

* origin/renovate/agp:
  chore(deps): update agp to v8.5.0-beta02

* origin/renovate/sentry:
  fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v4.6.0
2024-05-17 00:53:33 +05:30
renovate[bot]andGitHub e2ab469ecc fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v4.6.0 2024-05-16 19:18:13 +00:00
renovate[bot]andGitHub 4ccf8959d9 chore(deps): update agp to v8.5.0-beta02 2024-05-16 19:18:08 +00:00
renovate[bot]andGitHub b73a758501 chore(deps): update actions/checkout action to v4.1.6 2024-05-16 19:18:05 +00:00
Harsh Shandilya cfaa1c17a0 fix(deps): update Compose Compiler to 1.5.14 2024-05-17 00:43:56 +05:30
renovate[bot]andGitHub 47332c22ec fix(deps): update androidx 2024-05-14 17:29:24 +00:00
GitHub Actions ce64454cec feat(release): start next development iteration 2024-05-14 07:49:51 +00:00
73 changed files with 1099 additions and 877 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 313 KiB

After

Width:  |  Height:  |  Size: 319 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 311 KiB

After

Width:  |  Height:  |  Size: 315 KiB

+2 -2
View File
@@ -25,11 +25,11 @@
],
},
{
"matchPackageNames": [
"androidx.compose.compiler:compiler",
"matchDepNames": [
"com.google.devtools.ksp",
"dev.drewhamilton.poko",
"org.jetbrains.kotlin:kotlin-gradle-plugin",
"org.jetbrains.kotlin.plugin.compose",
"org.jetbrains.kotlin.plugin.serialization",
],
"groupName": "Kotlin and Compose",
@@ -21,19 +21,19 @@ runs:
using: "composite"
steps:
- name: Checkout repository
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
fetch-depth: ${{ inputs.fetch-depth }}
token: ${{ inputs.token }}
- name: Set up JDK
uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
uses: actions/setup-java@6a0805fcefea3d4657a47ac4c165951e33482018 # v4.2.2
with:
distribution: temurin
java-version: 22
- name: Setup Gradle
uses: gradle/actions/setup-gradle@db19848a5fa7950289d3668fb053140cf3028d43 # v3
uses: gradle/actions/setup-gradle@16bf8bc8fe830fa669c3c9f914d3eb147c629707 # v4
with:
gradle-home-cache-cleanup: true
cache-read-only: ${{ inputs.cache-read-only }}
+3 -3
View File
@@ -36,7 +36,7 @@ jobs:
- name: (Fail-only) Upload test report
if: failure()
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6
with:
name: Test report
path: '**/build/reports/tests/**'
@@ -48,10 +48,10 @@ jobs:
- check
steps:
- name: Checkout repository
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Dependency Review
uses: actions/dependency-review-action@0c155c5e8556a497adf53f2c18edabf945ed8e70 # v4
uses: actions/dependency-review-action@5a2ce3f5b92ee19cbb1541a4984c76d921601d7c # v4
with:
base-ref: refs/heads/main
head-ref: ${{ github.ref }}
+6 -6
View File
@@ -29,7 +29,7 @@ jobs:
cache-read-only: true
- name: Initialize CodeQL
uses: github/codeql-action/init@b7cec7526559c32f1616476ff32d17ba4c59b2d6 # v3.25.5
uses: github/codeql-action/init@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6
with:
languages: java
tools: latest
@@ -41,7 +41,7 @@ jobs:
./gradlew assembleDebug assembleInternal
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@b7cec7526559c32f1616476ff32d17ba4c59b2d6 # v3.25.5
uses: github/codeql-action/analyze@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6
with:
category: "/language:java"
mobsfscan:
@@ -49,19 +49,19 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Setup python
uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5
uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5
with:
python-version: 3.8
- name: Run mobsfscan
uses: MobSF/mobsfscan@35b2016602af9d6ac52f296becca11be3fe004e8
uses: MobSF/mobsfscan@25184ed98afa315d90f982d10a44e42c065dcebe
with:
args: . --sarif --output results.sarif || true
- name: Upload mobsfscan report
uses: github/codeql-action/upload-sarif@b7cec7526559c32f1616476ff32d17ba4c59b2d6 # v3.25.5
uses: github/codeql-action/upload-sarif@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6
with:
sarif_file: results.sarif
+2 -2
View File
@@ -5,7 +5,7 @@ on:
inAppUpdatePriority:
description: 'In app update priority (0-5)'
type: number
default: "0"
default: 0
required: false
concurrency:
@@ -59,7 +59,7 @@ jobs:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
run: |
./gradlew --no-configuration-cache collectReleaseApks collectReleaseBundle -PenableSentry
./gradlew --no-configuration-cache assembleRelease collectReleaseApks collectReleaseBundle -PenableSentry
- name: Clean secrets
run: scripts/signing-cleanup.sh
+2 -2
View File
@@ -25,7 +25,7 @@ jobs:
run: |
./scripts/setup-age.sh
./scripts/signing-setup.sh "$AGE_SECRET_KEY"
./gradlew --no-configuration-cache --stacktrace -PenableSentry collectReleaseBundle
./gradlew --no-configuration-cache --stacktrace assembleRelease collectReleaseBundle -PenableSentry
./scripts/signing-cleanup.sh
env:
AGE_SECRET_KEY: ${{ secrets.AGE_SECRET_KEY }}
@@ -35,7 +35,7 @@ jobs:
ORG_GRADLE_PROJECT_VERSION_CODE: ${{ github.event.inputs.versionCode }}
- name: Upload app bundle
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6
with:
name: release-aab
path: android/build/outputs/bundle/release/android-release.aab
+1
View File
@@ -247,3 +247,4 @@ keystore.properties
distribution/
screenshots/
claw-export.json
.kotlin/
+1
View File
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KtfmtSettings">
<option name="enableKtfmt" value="Enabled" />
<option name="enabled" value="true" />
<option name="uiFormatterStyle" value="Google (internal)" />
</component>
+43 -1
View File
@@ -7,6 +7,45 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [1.50.0] - 2024-08-30
### Fixed
- Search bar draws under the status bar when hiding keyboard
- Bottom of the comments page is cut off by the navigation bar
- Rare crash in comments page
## [1.49.0] - 2024-08-29
### Added
- Opening posts you have previously seen will show the number of new comments since last visit
### Fixed
- Saving posts no longer triggers a page refresh that invalidates scroll position
### Changed
- Change submitter text to 'authored' when applicable
- Unread comments now have a brighter background rather than a text badge
- Bottom navigation bar has been redesigned
## [1.48.0] - 2024-06-05
### Added
- Swiping a post from left to right now offers a share action
### Fixed
- Try to workaround an infrequent crash when scrolling hottest/newest posts
### Changed
- Upgrade to Kotlin 2.0.0
- Upgrade to Compose May beta releases
## [1.47.0] - 2024-05-14
### Changed
@@ -423,7 +462,10 @@ 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.47.0...HEAD
[Unreleased]: https://github.com/msfjarvis/compose-lobsters/compare/v1.50.0...HEAD
[1.50.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.49.0...v1.50.0
[1.49.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.48.0...v1.49.0
[1.48.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.47.0...v1.48.0
[1.47.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.46.0...v1.47.0
[1.46.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.45.0...v1.46.0
[1.45.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.44.0...v1.45.0
+6 -5
View File
@@ -23,6 +23,9 @@ plugins {
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)
}
android {
@@ -30,10 +33,7 @@ android {
defaultConfig.applicationId = "dev.msfjarvis.claw.android"
defaultConfig.testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
buildFeatures.compose = true
composeOptions {
useLiveLiterals = false
kotlinCompilerExtensionVersion = libs.versions.composeCompiler.get()
}
composeOptions { useLiveLiterals = false }
buildTypes.create("internal") {
matchingFallbacks += "release"
signingConfig = signingConfigs["debug"]
@@ -95,12 +95,13 @@ dependencies {
implementation(libs.copydown)
implementation(libs.dagger)
implementation(libs.eithernet)
implementation(libs.haze)
implementation(libs.haze.materials)
implementation(libs.javax.inject)
implementation(libs.kotlinx.collections.immutable)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.serialization.core)
implementation(libs.kotlinx.serialization.json)
implementation(libs.material3.pulltorefresh)
implementation(libs.napier)
implementation(libs.okhttp.core)
implementation(libs.okhttp.loggingInterceptor)
+1 -1
View File
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.3.0" type="baseline" client="gradle" dependencies="true" name="AGP (8.3.0)" variant="all" version="8.3.0">
<issues format="6" by="lint 8.4.2" type="baseline" client="gradle" dependencies="true" name="AGP (8.4.2)" variant="all" version="8.4.2">
<issue
id="VectorPath"
@@ -18,14 +18,14 @@ import leakcanary.repeatingAndroidInProcessScenario
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.RegisterExtension
import shark.ObjectGrowthDetector
import shark.forAndroidHeap
import shark.HeapDiff
class HeapGrowthCheck {
@JvmField
@RegisterExtension
@Suppress("Unused")
val scenarioExtension = ActivityScenarioExtension.launch<MainActivity>()
private val detector = ObjectGrowthDetector.forAndroidHeap().repeatingAndroidInProcessScenario()
private val detector = HeapDiff.repeatingAndroidInProcessScenario()
private lateinit var device: UiDevice
@BeforeEach
@@ -13,16 +13,13 @@ import com.slack.eithernet.ApiResult.Success
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dev.msfjarvis.claw.android.viewmodel.ReadPostsRepository
import dev.msfjarvis.claw.android.viewmodel.SavedPostsRepository
import dev.msfjarvis.claw.android.ui.toError
import dev.msfjarvis.claw.core.injection.IODispatcher
import dev.msfjarvis.claw.database.local.SavedPost
import dev.msfjarvis.claw.model.LobstersPost
import dev.msfjarvis.claw.model.UIPost
import dev.msfjarvis.claw.model.toUIPost
import java.io.IOException
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
class LobstersPagingSource
@@ -30,39 +27,20 @@ class LobstersPagingSource
constructor(
@Assisted private val remoteFetcher: RemoteFetcher<LobstersPost>,
@IODispatcher private val ioDispatcher: CoroutineDispatcher,
private val savedPostsRepository: SavedPostsRepository,
private val readPostsRepository: ReadPostsRepository,
) : PagingSource<Int, UIPost>() {
private lateinit var savedPosts: List<String>
private lateinit var readPosts: List<String>
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, UIPost> {
if (!::savedPosts.isInitialized) {
savedPosts = savedPostsRepository.savedPosts.first().map(SavedPost::shortId)
}
if (!::readPosts.isInitialized) {
readPosts = readPostsRepository.readPosts.first()
}
val page = params.key ?: STARTING_PAGE_INDEX
return when (val result = withContext(ioDispatcher) { remoteFetcher.getItemsAtPage(page) }) {
is Success ->
LoadResult.Page(
itemsBefore = (page - 1) * PAGE_SIZE,
data =
result.value.map {
it
.toUIPost()
.copy(
isSaved = savedPosts.contains(it.shortId),
isRead = readPosts.contains(it.shortId),
)
},
data = result.value.map(LobstersPost::toUIPost),
prevKey = if (page == STARTING_PAGE_INDEX) null else page - 1,
nextKey = page + 1,
)
is Failure.NetworkFailure -> LoadResult.Error(result.error)
is Failure.UnknownFailure -> LoadResult.Error(result.error)
is Failure.HttpFailure,
is Failure.HttpFailure -> LoadResult.Error(result.toError())
is Failure.ApiFailure -> LoadResult.Error(IOException("API returned an invalid response"))
}
}
@@ -14,16 +14,14 @@ 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.viewmodel.ReadPostsRepository
import dev.msfjarvis.claw.android.viewmodel.SavedPostsRepository
import dev.msfjarvis.claw.android.ui.toError
import dev.msfjarvis.claw.api.LobstersSearchApi
import dev.msfjarvis.claw.core.injection.IODispatcher
import dev.msfjarvis.claw.database.local.SavedPost
import dev.msfjarvis.claw.model.LobstersPost
import dev.msfjarvis.claw.model.UIPost
import dev.msfjarvis.claw.model.toUIPost
import java.io.IOException
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
/**
@@ -38,19 +36,8 @@ constructor(
private val searchApi: LobstersSearchApi,
@Assisted private val queryProvider: () -> String,
@IODispatcher private val ioDispatcher: CoroutineDispatcher,
private val savedPostsRepository: SavedPostsRepository,
private val readPostsRepository: ReadPostsRepository,
) : PagingSource<Int, UIPost>() {
private lateinit var savedPosts: List<String>
private lateinit var readPosts: List<String>
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, UIPost> {
if (!::savedPosts.isInitialized) {
savedPosts = savedPostsRepository.savedPosts.first().map(SavedPost::shortId)
}
if (!::readPosts.isInitialized) {
readPosts = readPostsRepository.readPosts.first()
}
val searchQuery = queryProvider()
// If there is no query, we don't need to call the API at all.
if (searchQuery.isEmpty()) {
@@ -65,22 +52,14 @@ constructor(
val nextKey = if (result.value.isEmpty()) null else page + 1
LoadResult.Page(
itemsBefore = (page - 1) * PAGE_SIZE,
data =
result.value.map {
it
.toUIPost()
.copy(
isSaved = savedPosts.contains(it.shortId),
isRead = readPosts.contains(it.shortId),
)
},
data = result.value.map(LobstersPost::toUIPost),
prevKey = if (page == STARTING_PAGE_INDEX) null else page - 1,
nextKey = nextKey,
)
}
is ApiResult.Failure.NetworkFailure -> LoadResult.Error(result.error)
is ApiResult.Failure.UnknownFailure -> LoadResult.Error(result.error)
is ApiResult.Failure.HttpFailure,
is ApiResult.Failure.HttpFailure -> LoadResult.Error(result.toError())
is ApiResult.Failure.ApiFailure ->
LoadResult.Error(IOException("API returned an invalid response"))
}
@@ -0,0 +1,24 @@
/*
* Copyright © 2022-2024 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) { options ->
options.experimental.sessionReplay.errorSampleRate = 1.0
options.experimental.sessionReplay.sessionSampleRate = 0.1
}
}
}
@@ -13,16 +13,27 @@ import androidx.compose.animation.core.LinearOutSlowInEasing
import androidx.compose.animation.core.tween
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
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 dev.msfjarvis.claw.android.ui.navigation.Destinations
import androidx.navigation.compose.currentBackStackEntryAsState
import dev.chrisbanes.haze.HazeState
import dev.chrisbanes.haze.HazeStyle
import dev.chrisbanes.haze.hazeChild
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
const val AnimationDuration = 100
@@ -32,6 +43,7 @@ fun ClawNavigationBar(
navController: NavController,
items: ImmutableList<NavigationItem>,
isVisible: Boolean,
hazeState: HazeState,
modifier: Modifier = Modifier,
) {
AnimatedVisibility(
@@ -48,44 +60,65 @@ fun ClawNavigationBar(
targetOffsetY = { fullHeight -> fullHeight },
animationSpec = tween(durationMillis = AnimationDuration, easing = FastOutLinearInEasing),
),
modifier = Modifier,
) {
NavigationBar(modifier = modifier) {
items.forEach { navItem ->
val isCurrentDestination = navController.currentDestination?.route == navItem.route
NavigationBarItem(
icon = {
Crossfade(isCurrentDestination, label = "nav-label") {
Icon(
imageVector = if (it) navItem.selectedIcon else navItem.icon,
contentDescription = navItem.label.replaceFirstChar(Char::uppercase),
)
}
},
label = { Text(text = navItem.label) },
selected = isCurrentDestination,
onClick = {
if (isCurrentDestination) {
navItem.listStateResetCallback()
} else {
navController.graph.startDestinationRoute?.let { startDestination ->
navController.popBackStack(startDestination, false)
label = "",
content = {
FloatingNavigationBar(
tonalElevation = 16.dp,
shape = MaterialTheme.shapes.extraLarge,
modifier =
modifier
.padding(horizontal = 16.dp)
.navigationBarsPadding()
.clip(MaterialTheme.shapes.extraLarge)
.hazeChild(
hazeState,
style =
HazeStyle(
backgroundColor = MaterialTheme.colorScheme.surface,
tints = emptyList(),
blurRadius = 24.dp,
noiseFactor = 0f,
),
),
containerColor = Color.Transparent,
) {
val navBackStackEntry = navController.currentBackStackEntryAsState().value
val currentDestination = navBackStackEntry?.destination
items.forEach { navItem ->
val isSelected = currentDestination.matches(navItem.destination)
NavigationBarItem(
icon = {
Crossfade(isSelected, label = "nav-label") {
Icon(
imageVector = if (it) navItem.selectedIcon else navItem.icon,
contentDescription = navItem.label.replaceFirstChar(Char::uppercase),
)
}
if (navItem.route != Destinations.startDestination.route) {
navController.navigate(navItem.route)
},
label = { Text(text = navItem.label) },
selected = isSelected,
onClick = {
if (isSelected) {
navItem.listStateResetCallback()
} else {
navController.navigate(navItem.destination) {
popUpTo(navController.graph.startDestinationId) { saveState = true }
launchSingleTop = true
restoreState = true
}
}
}
},
modifier = Modifier.testTag(navItem.label.uppercase()),
)
},
modifier = Modifier.testTag(navItem.label.uppercase()),
)
}
}
}
}
},
)
}
class NavigationItem(
val label: String,
val route: String,
val destination: Destination,
val icon: ImageVector,
val selectedIcon: ImageVector,
val listStateResetCallback: () -> Unit,
@@ -22,7 +22,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.navigation.NavController
import dev.msfjarvis.claw.android.ui.navigation.Destinations
import androidx.navigation.compose.currentBackStackEntryAsState
import dev.msfjarvis.claw.android.ui.navigation.matches
import kotlinx.collections.immutable.ImmutableList
@Composable
@@ -49,12 +50,14 @@ fun ClawNavigationRail(
modifier = Modifier,
) {
NavigationRail(modifier = modifier) {
val navBackStackEntry = navController.currentBackStackEntryAsState().value
val currentDestination = navBackStackEntry?.destination
Spacer(Modifier.weight(1f))
items.forEach { navItem ->
val isCurrentDestination = navController.currentDestination?.route == navItem.route
val isSelected = currentDestination.matches(navItem.destination)
NavigationRailItem(
icon = {
Crossfade(isCurrentDestination, label = "nav-label") {
Crossfade(isSelected, label = "nav-label") {
Icon(
imageVector = if (it) navItem.selectedIcon else navItem.icon,
contentDescription = navItem.label.replaceFirstChar(Char::uppercase),
@@ -62,16 +65,15 @@ fun ClawNavigationRail(
}
},
label = { Text(text = navItem.label) },
selected = isCurrentDestination,
selected = isSelected,
onClick = {
if (isCurrentDestination) {
if (isSelected) {
navItem.listStateResetCallback()
} else {
navController.graph.startDestinationRoute?.let { startDestination ->
navController.popBackStack(startDestination, false)
}
if (navItem.route != Destinations.startDestination.route) {
navController.navigate(navItem.route)
navController.navigate(navItem.destination) {
popUpTo(navController.graph.startDestinationId) { saveState = true }
launchSingleTop = true
restoreState = true
}
}
},
@@ -8,16 +8,20 @@ package dev.msfjarvis.claw.android.ui
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import androidx.activity.ComponentActivity
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.navigation.NavController
import dev.msfjarvis.claw.android.ui.navigation.Destinations
import com.slack.eithernet.ApiResult
import dev.msfjarvis.claw.android.ui.navigation.Comments
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 Context.getActivity(): ComponentActivity? {
return when (this) {
@@ -29,6 +33,7 @@ fun Context.getActivity(): ComponentActivity? {
@Composable
fun rememberPostActions(
context: Context,
urlLauncher: UrlLauncher,
navController: NavController,
viewModel: ClawViewModel,
@@ -42,10 +47,7 @@ fun rememberPostActions(
override fun viewComments(postId: String) {
viewModel.markPostAsRead(postId)
val currentRoute = navController.currentDestination?.route
val newRoute =
Destinations.Comments.route.replace(Destinations.Comments.PLACEHOLDER, postId)
if (currentRoute != Destinations.Comments.route) navController.navigate(newRoute)
navController.navigate(Comments(postId))
}
override fun viewCommentsPage(post: UIPost) {
@@ -56,6 +58,22 @@ fun rememberPostActions(
viewModel.toggleSave(post)
}
override fun share(post: UIPost) {
val sendIntent: Intent =
Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_TEXT, post.url.ifEmpty { post.commentsUrl })
putExtra(Intent.EXTRA_TITLE, post.title)
type = "text/plain"
}
val shareIntent = Intent.createChooser(sendIntent, null)
context.startActivity(shareIntent)
}
override fun isPostRead(post: UIPost): Boolean = viewModel.isPostRead(post)
override fun isPostSaved(post: UIPost): Boolean = viewModel.isPostSaved(post)
override suspend fun getComments(postId: String): UIPost {
return viewModel.getPostComments(postId)
}
@@ -66,3 +84,17 @@ fun rememberPostActions(
}
}
}
/**
* 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")
}
@@ -10,6 +10,7 @@ import androidx.activity.compose.ReportDrawn
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
@@ -36,6 +37,7 @@ fun DatabasePosts(
items: ImmutableMap<String, List<UIPost>>,
listState: LazyListState,
postActions: PostActions,
contentPadding: PaddingValues,
modifier: Modifier = Modifier,
) {
ReportDrawn()
@@ -50,11 +52,11 @@ fun DatabasePosts(
Text(text = "No saved posts", style = MaterialTheme.typography.headlineSmall)
}
} else {
LazyColumn(state = listState) {
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, refresh = {}, postActions = postActions)
LobstersListItem(item = item, postActions = postActions)
HorizontalDivider()
}
}
@@ -8,6 +8,7 @@ package dev.msfjarvis.claw.android.ui.lists
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Reply
import androidx.compose.material.icons.filled.Share
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@@ -19,19 +20,20 @@ import me.saket.swipe.SwipeAction
import me.saket.swipe.SwipeableActionsBox
@Composable
fun LobstersListItem(
item: UIPost,
postActions: PostActions,
refresh: () -> Unit,
modifier: Modifier = Modifier,
) {
fun LobstersListItem(item: UIPost, postActions: PostActions, modifier: Modifier = Modifier) {
val commentsAction =
SwipeAction(
icon = rememberVectorPainter(Icons.AutoMirrored.Filled.Reply),
background = MaterialTheme.colorScheme.tertiary,
onSwipe = { postActions.viewCommentsPage(item) },
)
SwipeableActionsBox(endActions = listOf(commentsAction)) {
LobstersCard(post = item, postActions = postActions, refresh = refresh, modifier = modifier)
val shareAction =
SwipeAction(
icon = rememberVectorPainter(Icons.Filled.Share),
background = MaterialTheme.colorScheme.tertiary,
onSwipe = { postActions.share(item) },
)
SwipeableActionsBox(startActions = listOf(shareAction), endActions = listOf(commentsAction)) {
LobstersCard(post = item, postActions = postActions, modifier = modifier)
}
}
@@ -7,7 +7,7 @@
package dev.msfjarvis.claw.android.ui.lists
import androidx.activity.compose.ReportDrawnWhen
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
@@ -17,7 +17,12 @@ import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@@ -35,9 +40,6 @@ 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 eu.bambooapps.material3.pullrefresh.PullRefreshIndicator
import eu.bambooapps.material3.pullrefresh.pullRefresh
import eu.bambooapps.material3.pullrefresh.rememberPullRefreshState
import kotlinx.coroutines.flow.MutableStateFlow
@OptIn(ExperimentalMaterial3Api::class)
@@ -46,21 +48,33 @@ fun NetworkPosts(
lazyPagingItems: LazyPagingItems<UIPost>,
listState: LazyListState,
postActions: PostActions,
contentPadding: PaddingValues,
modifier: Modifier = Modifier,
) {
ReportDrawnWhen { lazyPagingItems.itemCount > 0 }
val refreshLoadState = lazyPagingItems.loadState.refresh
val isRefreshing = refreshLoadState == LoadState.Loading && lazyPagingItems.itemCount == 0
val pullRefreshState = rememberPullRefreshState(isRefreshing, lazyPagingItems::refresh)
Box(modifier = modifier.fillMaxSize().pullRefresh(pullRefreshState)) {
val refreshLoadState by rememberUpdatedState(lazyPagingItems.loadState.refresh)
val state = rememberPullToRefreshState()
PullToRefreshBox(
modifier = modifier.fillMaxSize(),
isRefreshing = refreshLoadState == LoadState.Loading,
state = state,
onRefresh = { lazyPagingItems.refresh() },
indicator = {
PullToRefreshDefaults.Indicator(
state = state,
isRefreshing = refreshLoadState == LoadState.Loading,
modifier = Modifier.align(Alignment.TopCenter),
)
},
) {
if (lazyPagingItems.itemCount == 0 && refreshLoadState is LoadState.Error) {
NetworkError(
label = "Failed to load posts",
error = refreshLoadState.error,
error = (refreshLoadState as LoadState.Error).error,
modifier = Modifier.align(Alignment.Center),
)
} else {
LazyColumn(state = listState) {
LazyColumn(contentPadding = contentPadding, state = listState) {
items(
count = lazyPagingItems.itemCount,
key = lazyPagingItems.itemKey { it.shortId },
@@ -68,11 +82,7 @@ fun NetworkPosts(
) { index ->
val item = lazyPagingItems[index]
if (item != null) {
LobstersListItem(
item = item,
postActions = postActions,
refresh = { lazyPagingItems.refresh() },
)
LobstersListItem(item = item, postActions = postActions)
HorizontalDivider()
}
}
@@ -88,12 +98,6 @@ fun NetworkPosts(
}
}
}
PullRefreshIndicator(
refreshing = isRefreshing,
state = pullRefreshState,
modifier = Modifier.align(Alignment.TopCenter),
shadowElevation = 6.dp, // From M2 implementation
)
}
}
@@ -107,6 +111,7 @@ private fun ListPreview() {
lazyPagingItems = flow.collectAsLazyPagingItems(),
listState = rememberLazyListState(),
postActions = TEST_POST_ACTIONS,
contentPadding = PaddingValues(),
)
}
}
@@ -7,8 +7,10 @@
package dev.msfjarvis.claw.android.ui.lists
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
@@ -31,6 +33,7 @@ fun SearchList(
postActions: PostActions,
searchQuery: String,
setSearchQuery: (String) -> Unit,
contentPadding: PaddingValues,
modifier: Modifier = Modifier,
) {
val lazyPagingItems = items.collectAsLazyPagingItems()
@@ -38,7 +41,10 @@ fun SearchList(
setSearchQuery(query)
lazyPagingItems.refresh()
}
Column(modifier = modifier.semantics { isTraversalGroup = true }.zIndex(1f).fillMaxWidth()) {
Column(
modifier =
modifier.systemBarsPadding().semantics { isTraversalGroup = true }.zIndex(1f).fillMaxWidth()
) {
SearchBar(
value = searchQuery,
onValueChange = setSearchQuery,
@@ -49,6 +55,7 @@ fun SearchList(
lazyPagingItems = lazyPagingItems,
listState = listState,
postActions = postActions,
contentPadding = contentPadding,
)
}
}
@@ -0,0 +1,27 @@
/*
* Copyright © 2024 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 kotlinx.serialization.Serializable
sealed interface Destination
@Serializable data object Hottest : Destination
@Serializable data object Newest : Destination
@Serializable data object Saved : Destination
@Serializable data class Comments(val postId: String) : Destination
@Serializable data class User(val username: String) : Destination
@Serializable data object Search : Destination
@Serializable data object Settings : Destination
@Serializable data object AboutLibraries : Destination
@@ -1,50 +0,0 @@
/*
* Copyright © 2024 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
sealed class Destinations {
abstract val route: String
data object Hottest : Destinations() {
override val route = "hottest"
}
data object Newest : Destinations() {
override val route = "newest"
}
data object Saved : Destinations() {
override val route = "saved"
}
data object Comments : Destinations() {
const val PLACEHOLDER = "{postId}"
override val route = "comments/$PLACEHOLDER"
}
data object User : Destinations() {
const val PLACEHOLDER = "{username}"
override val route = "user/$PLACEHOLDER"
}
data object Search : Destinations() {
override val route = "search"
}
data object Settings : Destinations() {
override val route = "settings"
}
data object AboutLibraries : Destinations() {
override val route = "about_libraries"
}
companion object {
val startDestination
get() = Hottest
}
}
@@ -0,0 +1,30 @@
/*
* Copyright © 2024 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 }
}
@@ -13,7 +13,6 @@ import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
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
@@ -51,15 +50,16 @@ 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.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import androidx.navigation.toRoute
import androidx.paging.compose.collectAsLazyPagingItems
import com.deliveryhero.whetstone.compose.injectedViewModel
import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer
import dev.chrisbanes.haze.HazeState
import dev.chrisbanes.haze.haze
import dev.msfjarvis.claw.android.MainActivity
import dev.msfjarvis.claw.android.R
import dev.msfjarvis.claw.android.SearchActivity
@@ -69,8 +69,16 @@ import dev.msfjarvis.claw.android.ui.decorations.NavigationItem
import dev.msfjarvis.claw.android.ui.getActivity
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.ClawNavigationType
import dev.msfjarvis.claw.android.ui.navigation.Destinations
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.ui.navigation.any
import dev.msfjarvis.claw.android.ui.navigation.none
import dev.msfjarvis.claw.android.ui.rememberPostActions
import dev.msfjarvis.claw.android.viewmodel.ClawViewModel
import dev.msfjarvis.claw.common.comments.CommentsPage
@@ -79,6 +87,7 @@ import dev.msfjarvis.claw.common.urllauncher.UrlLauncher
import dev.msfjarvis.claw.common.user.UserProfile
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.launch
@OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterial3Api::class)
@@ -91,16 +100,17 @@ fun LobstersPostsScreen(
modifier: Modifier = Modifier,
viewModel: ClawViewModel = injectedViewModel(),
) {
val context = LocalContext.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 = rememberPostActions(urlLauncher, navController, viewModel)
val backStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = backStackEntry?.destination?.route
val context = LocalContext.current
val postActions = rememberPostActions(context, urlLauncher, navController, viewModel)
val hazeState = remember { HazeState() }
val hottestPosts = viewModel.hottestPosts.collectAsLazyPagingItems()
val newestPosts = viewModel.newestPosts.collectAsLazyPagingItems()
@@ -111,9 +121,7 @@ fun LobstersPostsScreen(
val postIdOverride = context.getActivity()?.intent?.extras?.getString(MainActivity.NAVIGATION_KEY)
LaunchedEffect(Unit) {
if (postIdOverride != null) {
navController.navigate(
Destinations.Comments.route.replace(Destinations.Comments.PLACEHOLDER, postIdOverride)
)
navController.navigate(Comments(postIdOverride))
}
}
@@ -121,7 +129,7 @@ fun LobstersPostsScreen(
persistentListOf(
NavigationItem(
label = "Hottest",
route = Destinations.Hottest.route,
destination = Hottest,
icon = Icons.Outlined.Whatshot,
selectedIcon = Icons.Filled.Whatshot,
) {
@@ -131,7 +139,7 @@ fun LobstersPostsScreen(
},
NavigationItem(
label = "Newest",
route = Destinations.Newest.route,
destination = Newest,
icon = Icons.Outlined.NewReleases,
selectedIcon = Icons.Filled.NewReleases,
) {
@@ -141,15 +149,14 @@ fun LobstersPostsScreen(
},
NavigationItem(
label = "Saved",
route = Destinations.Saved.route,
destination = Saved,
icon = Icons.Outlined.FavoriteBorder,
selectedIcon = Icons.Filled.Favorite,
) {
coroutineScope.launch {
if (savedPosts.isNotEmpty()) savedListState.animateScrollToItem(index = 0)
}
coroutineScope.launch { if (savedPosts.isNotEmpty()) savedListState.scrollToItem(0) }
},
)
val navDestinations = navItems.map(NavigationItem::destination).toPersistentList()
Scaffold(
topBar = {
@@ -157,8 +164,7 @@ fun LobstersPostsScreen(
modifier = Modifier.shadow(8.dp),
navigationIcon = {
if (
navController.previousBackStackEntry != null &&
navItems.none { it.route == currentDestination }
navController.previousBackStackEntry != null && currentDestination.none(navDestinations)
) {
IconButton(
onClick = { if (!navController.popBackStack()) context.getActivity()?.finish() }
@@ -177,18 +183,18 @@ fun LobstersPostsScreen(
}
},
title = {
if (navItems.any { it.route == currentDestination }) {
if (currentDestination.any(navDestinations)) {
Text(text = stringResource(R.string.app_name), fontWeight = FontWeight.Bold)
}
},
actions = {
if (navItems.any { it.route == currentDestination }) {
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(Destinations.Settings.route) }) {
IconButton(onClick = { navController.navigate(Settings) }) {
Icon(imageVector = Icons.Filled.Tune, contentDescription = "Settings")
}
}
@@ -200,57 +206,60 @@ fun LobstersPostsScreen(
ClawNavigationBar(
navController = navController,
items = navItems,
isVisible = navItems.any { it.route == currentDestination },
isVisible = currentDestination.any(navDestinations),
hazeState = hazeState,
)
}
},
snackbarHost = { SnackbarHost(snackbarHostState) },
modifier = modifier.semantics { testTagsAsResourceId = true },
) { paddingValues ->
Row(modifier = Modifier.padding(paddingValues)) {
) { contentPadding ->
Row {
AnimatedVisibility(visible = navigationType == ClawNavigationType.NAVIGATION_RAIL) {
ClawNavigationRail(
navController = navController,
items = navItems,
isVisible = navItems.any { it.route == currentDestination },
isVisible = currentDestination.any(navDestinations),
)
}
NavHost(
navController = navController,
startDestination = Destinations.startDestination.route,
startDestination = Hottest,
// Make animations 2x faster than default specs
enterTransition = { fadeIn(animationSpec = tween(350)) },
exitTransition = { fadeOut(animationSpec = tween(350)) },
modifier = Modifier.haze(hazeState),
) {
composable(route = Destinations.Hottest.route) {
composable<Hottest> {
setWebUri("https://lobste.rs/")
NetworkPosts(
lazyPagingItems = hottestPosts,
listState = hottestListState,
postActions = postActions,
contentPadding = contentPadding,
)
}
composable(route = Destinations.Newest.route) {
composable<Newest> {
setWebUri("https://lobste.rs/")
NetworkPosts(
lazyPagingItems = newestPosts,
listState = newestListState,
postActions = postActions,
contentPadding = contentPadding,
)
}
composable(route = Destinations.Saved.route) {
composable<Saved> {
setWebUri(null)
DatabasePosts(items = savedPosts, listState = savedListState, postActions = postActions)
DatabasePosts(
items = savedPosts,
listState = savedListState,
postActions = postActions,
contentPadding = contentPadding,
)
}
composable(
route = Destinations.Comments.route,
arguments = listOf(navArgument("postId") { type = NavType.StringType }),
) { backStackEntry ->
val postId =
requireNotNull(backStackEntry.arguments?.getString("postId")) {
"Navigating to ${Destinations.Comments.route} without necessary 'postId' argument"
}
composable<Comments> { backStackEntry ->
val postId = backStackEntry.toRoute<Comments>().postId
setWebUri("https://lobste.rs/s/$postId")
CommentsPage(
postId = postId,
@@ -258,44 +267,33 @@ fun LobstersPostsScreen(
htmlConverter = htmlConverter,
getSeenComments = viewModel::getSeenComments,
markSeenComments = viewModel::markSeenComments,
openUserProfile = {
navController.navigate(
Destinations.User.route.replace(Destinations.User.PLACEHOLDER, it)
)
},
contentPadding = contentPadding,
openUserProfile = { navController.navigate(User(it)) },
)
}
composable(
route = Destinations.User.route,
arguments = listOf(navArgument("username") { type = NavType.StringType }),
) { backStackEntry ->
val username =
requireNotNull(backStackEntry.arguments?.getString("username")) {
"Navigating to ${Destinations.User.route} without necessary 'username' argument"
}
composable<User> { backStackEntry ->
val username = backStackEntry.toRoute<User>().username
setWebUri("https://lobste.rs/u/$username")
UserProfile(
username = username,
getProfile = viewModel::getUserProfile,
openUserProfile = {
navController.navigate(
Destinations.User.route.replace(Destinations.User.PLACEHOLDER, it)
)
},
contentPadding = contentPadding,
openUserProfile = { navController.navigate(User(it)) },
)
}
composable(route = Destinations.Settings.route) {
composable<Settings> {
SettingsScreen(
context = context,
openLibrariesScreen = { navController.navigate(Destinations.AboutLibraries.route) },
openLibrariesScreen = { navController.navigate(AboutLibraries) },
importPosts = viewModel::importPosts,
exportPostsAsJson = viewModel::exportPostsAsJson,
exportPostsAsHtml = viewModel::exportPostsAsHtml,
snackbarHostState = snackbarHostState,
contentPadding = contentPadding,
)
}
composable(route = Destinations.AboutLibraries.route) {
LibrariesContainer(modifier = Modifier.fillMaxSize())
composable<AboutLibraries> {
LibrariesContainer(contentPadding = contentPadding, modifier = Modifier.fillMaxSize())
}
}
}
@@ -6,19 +6,20 @@
*/
package dev.msfjarvis.claw.android.ui.screens
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.navigation.NavType
import androidx.compose.ui.platform.LocalContext
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import androidx.navigation.toRoute
import com.deliveryhero.whetstone.compose.injectedViewModel
import dev.msfjarvis.claw.android.ui.lists.SearchList
import dev.msfjarvis.claw.android.ui.navigation.Destinations
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.ui.rememberPostActions
import dev.msfjarvis.claw.android.viewmodel.ClawViewModel
import dev.msfjarvis.claw.common.comments.CommentsPage
@@ -35,32 +36,23 @@ fun SearchScreen(
viewModel: ClawViewModel = injectedViewModel(),
) {
val navController = rememberNavController()
val postActions = rememberPostActions(urlLauncher, navController, viewModel)
val postActions = rememberPostActions(LocalContext.current, urlLauncher, navController, viewModel)
val listState = rememberLazyListState()
Scaffold(modifier = modifier) { paddingValues ->
NavHost(
navController = navController,
startDestination = Destinations.Search.route,
modifier = Modifier.padding(paddingValues),
) {
composable(route = Destinations.Search.route) {
Scaffold(modifier = modifier) { contentPadding ->
NavHost(navController = navController, startDestination = Search) {
composable<Search> {
setWebUri("https://lobste.rs/search")
SearchList(
items = viewModel.searchResults,
listState = listState,
postActions = postActions,
searchQuery = viewModel.searchQuery,
contentPadding = contentPadding,
setSearchQuery = { query -> viewModel.searchQuery = query },
)
}
composable(
route = Destinations.Comments.route,
arguments = listOf(navArgument("postId") { type = NavType.StringType }),
) { backStackEntry ->
val postId =
requireNotNull(backStackEntry.arguments?.getString("postId")) {
"Navigating to ${Destinations.Comments.route} without necessary 'postId' argument"
}
composable<Comments> { backStackEntry ->
val postId = backStackEntry.toRoute<Comments>().postId
setWebUri("https://lobste.rs/s/$postId")
CommentsPage(
postId = postId,
@@ -68,30 +60,18 @@ fun SearchScreen(
htmlConverter = htmlConverter,
getSeenComments = viewModel::getSeenComments,
markSeenComments = viewModel::markSeenComments,
openUserProfile = { username: String ->
navController.navigate(
Destinations.User.route.replace(Destinations.User.PLACEHOLDER, username)
)
},
openUserProfile = { navController.navigate(User(it)) },
contentPadding = contentPadding,
)
}
composable(
route = Destinations.User.route,
arguments = listOf(navArgument("username") { type = NavType.StringType }),
) { backStackEntry ->
val username =
requireNotNull(backStackEntry.arguments?.getString("username")) {
"Navigating to ${Destinations.User.route} without necessary 'username' argument"
}
composable<User> { backStackEntry ->
val username = backStackEntry.toRoute<User>().username
setWebUri("https://lobste.rs/u/$username")
UserProfile(
username = username,
getProfile = viewModel::getUserProfile,
openUserProfile = {
navController.navigate(
Destinations.User.route.replace(Destinations.User.PLACEHOLDER, it)
)
},
openUserProfile = { navController.navigate(User(it)) },
contentPadding = contentPadding,
)
}
}
@@ -13,11 +13,13 @@ 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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
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.Bookmarks
@@ -55,22 +57,12 @@ fun SettingsScreen(
importPosts: suspend (InputStream) -> Unit,
exportPostsAsJson: suspend (OutputStream) -> Unit,
exportPostsAsHtml: suspend (OutputStream) -> Unit,
contentPadding: PaddingValues,
modifier: Modifier = Modifier,
) {
val coroutineScope = rememberCoroutineScope()
Box(modifier = modifier.fillMaxSize()) {
Box(modifier = modifier.padding(contentPadding).fillMaxSize()) {
Column {
ListItem(
headlineContent = { Text("Libraries") },
leadingContent = {
Icon(
imageVector = Icons.AutoMirrored.Filled.LibraryBooks,
contentDescription = null,
modifier = Modifier.height(32.dp),
)
},
modifier = Modifier.clickable(onClick = openLibrariesScreen),
)
ListItem(
headlineContent = { Text("Data transfer") },
leadingContent = {
@@ -96,6 +88,17 @@ fun SettingsScreen(
}
},
)
ListItem(
headlineContent = { Text("Libraries") },
leadingContent = {
Icon(
imageVector = Icons.AutoMirrored.Filled.LibraryBooks,
contentDescription = null,
modifier = Modifier.height(32.dp),
)
},
modifier = Modifier.clickable(onClick = openLibrariesScreen),
)
}
}
}
@@ -29,6 +29,7 @@ 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
@@ -39,7 +40,6 @@ import dev.msfjarvis.claw.model.toUIPost
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.net.HttpURLConnection
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter
@@ -133,19 +133,21 @@ constructor(
}
}
fun isPostRead(post: UIPost): Boolean {
return _readPosts.contains(post.shortId)
}
fun isPostSaved(post: UIPost): Boolean {
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 -> {
if (result.code == HttpURLConnection.HTTP_NOT_FOUND) {
throw IOException("Story was removed by moderator")
} else {
throw IOException("API returned an invalid response")
}
}
is Failure.HttpFailure -> throw result.toError()
is Failure.ApiFailure -> throw IOException("API returned an invalid response")
}
}
@@ -165,7 +167,7 @@ constructor(
is Success -> result.value
is Failure.NetworkFailure -> throw result.error
is Failure.UnknownFailure -> throw result.error
is Failure.HttpFailure,
is Failure.HttpFailure -> throw result.toError()
is Failure.ApiFailure -> throw IOException("API returned an invalid response")
}
}
@@ -1,5 +1,5 @@
/*
* Copyright © 2021-2023 Harsh Shandilya.
* Copyright © 2021-2024 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.
@@ -22,7 +22,10 @@ constructor(
) {
suspend fun getSeenComments(postId: String) =
withContext(dbDispatcher) { postCommentsQueries.getCommentIds(postId).executeAsOneOrNull() }
withContext(dbDispatcher) {
postCommentsQueries.getCommentIds(postId).executeAsOneOrNull()
?: PostComments(postId = postId, commentIds = emptyList())
}
suspend fun markSeenComments(postId: String, comments: List<Comment>) {
withContext(dbDispatcher) {
@@ -16,6 +16,7 @@ import dev.msfjarvis.claw.model.toSavedPost
import io.github.aakira.napier.Napier
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.withContext
class SavedPostsRepository
@@ -27,7 +28,7 @@ constructor(
val savedPosts = savedPostQueries.selectAllPosts().asFlow().mapToList(dbDispatcher)
suspend fun toggleSave(post: UIPost) {
if (post.isSaved) {
if (savedPosts.firstOrNull().orEmpty().any { it.shortId == post.shortId }) {
Napier.d(tag = TAG) { "Removing post: ${post.shortId}" }
withContext(dbDispatcher) { savedPostQueries.deletePost(post.shortId) }
} else {
+2 -2
View File
@@ -1,5 +1,5 @@
#
# This file was automatically generated by 'versioning-plugin'. DO NOT EDIT MANUALLY.
#
versioning-plugin.versionCode=14700
versioning-plugin.versionName=1.47.0
versioning-plugin.versionCode=15000
versioning-plugin.versionName=1.50.0
+1 -1
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.3.0" type="baseline" client="gradle" dependencies="true" name="AGP (8.3.0)" variant="all" version="8.3.0">
<issues format="6" by="lint 8.4.2" type="baseline" client="gradle" dependencies="true" name="AGP (8.4.2)" variant="all" version="8.4.2">
</issues>
@@ -32,6 +32,11 @@ object SearchConverter : Converter<ResponseBody, List<LobstersPost>> {
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 userIsAuthor =
(elem.select("div.byline > span").first()?.text() ?: "").contains(
"authored",
ignoreCase = true,
)
return LobstersPost(
shortId = shortId,
title = title,
@@ -43,6 +48,7 @@ object SearchConverter : Converter<ResponseBody, List<LobstersPost>> {
// The value of these fields is irrelevant for our use case
createdAt = "",
description = "",
userIsAuthor = userIsAuthor,
)
}
@@ -40,6 +40,7 @@ class SearchApiTest {
submitter = "xenodium",
tags = listOf("ai", "emacs"),
description = "",
userIsAuthor = true,
),
LobstersPost(
shortId = "astcqf",
@@ -52,6 +53,7 @@ class SearchApiTest {
submitter = "asteroid",
tags = listOf("ai"),
description = "",
userIsAuthor = false,
),
)
}
+1 -1
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.3.0" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0)" variant="all" version="8.3.0">
<issues format="6" by="lint 8.4.2" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.2)" variant="all" version="8.4.2">
</issues>
@@ -1,5 +1,5 @@
/*
* Copyright © 2022-2023 Harsh Shandilya.
* Copyright © 2022-2024 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.
@@ -26,7 +26,7 @@ import org.gradle.kotlin.dsl.findByType
class AndroidCommonPlugin : Plugin<Project> {
private companion object {
const val COMPILE_SDK = 34
const val COMPILE_SDK = 35
const val MIN_SDK = 26
const val TARGET_SDK = 34
const val SLIM_TESTS_PROPERTY = "slimTests"
@@ -7,15 +7,12 @@
package dev.msfjarvis.claw.gradle
import dev.msfjarvis.claw.gradle.KotlinCommonPlugin.Companion.JVM_TOOLCHAIN_ACTION
import org.gradle.accessors.dm.LibrariesForLibs
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.getByType
import org.gradle.kotlin.dsl.withType
import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinAndroidPluginWrapper
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
@Suppress("Unused")
class KotlinAndroidPlugin : Plugin<Project> {
@@ -26,29 +23,5 @@ class KotlinAndroidPlugin : Plugin<Project> {
apply(KotlinCommonPlugin::class)
}
project.extensions.getByType<KotlinProjectExtension>().jvmToolchain(JVM_TOOLCHAIN_ACTION)
val libs = project.extensions.getByName("libs") as LibrariesForLibs
val composeCompilerVersion = libs.versions.composeCompiler.get()
val kotlinVersion = libs.versions.kotlin.get()
val matches = COMPOSE_COMPILER_VERSION_REGEX.find(composeCompilerVersion)
project.tasks.withType<KotlinCompile>().configureEach {
compilerOptions.freeCompilerArgs.addAll(
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:featureFlag=StrongSkipping",
)
if (matches != null) {
val (compilerKotlinVersion) = matches.destructured
if (compilerKotlinVersion != kotlinVersion) {
compilerOptions.freeCompilerArgs.addAll(
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:suppressKotlinVersionCompatibilityCheck=$kotlinVersion",
)
}
}
}
}
private companion object {
// Matches against 1.5.0-dev-k1.9.0-6a60475e07f
val COMPOSE_COMPILER_VERSION_REGEX = "\\d.\\d.\\d-dev-k(\\d.\\d.\\d+)-[a-z0-9]+".toRegex()
}
}
@@ -29,7 +29,7 @@ class KotlinCommonPlugin : Plugin<Project> {
project.tasks.run {
withType<KotlinCompile>().configureEach {
compilerOptions {
allWarningsAsErrors.set(true)
allWarningsAsErrors.set(project.providers.environmentVariable("CI").isPresent)
freeCompilerArgs.addAll(ADDITIONAL_COMPILER_ARGS)
languageVersion.set(KotlinVersion.KOTLIN_1_9)
}
@@ -32,7 +32,7 @@ class KotlinKaptPlugin : Plugin<Project> {
// https://github.com/google/dagger/commit/0d2505a727b54f47b8677f42dd4fc5c1924e37f5
option("-Adagger.experimentalDaggerErrorMessages=enabled")
// KAPT nests errors causing real issues to be suppressed in CI logs
option("-Xmaxerrs", 500)
option("-Xmaxerrs", "500")
// Enables per-module validation for faster error detection
// https://github.com/google/dagger/commit/325b516ac6a53d3fc973d247b5231fafda9870a2
option("-Adagger.moduleBindingValidation=ERROR")
@@ -55,6 +55,6 @@ class SpotlessPlugin : Plugin<Project> {
}
private companion object {
private const val KTFMT_VERSION = "0.49"
private const val KTFMT_VERSION = "0.52"
}
}
+1
View File
@@ -9,4 +9,5 @@ plugins {
id("dev.msfjarvis.claw.versions")
id("dev.msfjarvis.claw.kotlin-common")
alias(libs.plugins.dependencyAnalysis)
alias(libs.plugins.invert)
}
+2 -4
View File
@@ -13,6 +13,7 @@ plugins {
id("dev.msfjarvis.claw.kotlin-android")
alias(libs.plugins.anvil)
alias(libs.plugins.whetstone)
alias(libs.plugins.kotlin.composeCompiler)
}
android {
@@ -20,10 +21,7 @@ android {
androidResources = true
compose = true
}
composeOptions {
useLiveLiterals = false
kotlinCompilerExtensionVersion = libs.versions.composeCompiler.get()
}
composeOptions { useLiveLiterals = false }
namespace = "dev.msfjarvis.claw.common"
}
+1 -1
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.3.0" type="baseline" client="gradle" dependencies="true" name="AGP (8.3.0)" variant="all" version="8.3.0">
<issues format="6" by="lint 8.4.2" type="baseline" client="gradle" dependencies="true" name="AGP (8.4.2)" variant="all" version="8.4.2">
</issues>
@@ -6,7 +6,6 @@
*/
package dev.msfjarvis.claw.common.comments
import android.text.format.DateUtils
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
@@ -27,14 +26,9 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import com.github.michaelbull.result.coroutines.runSuspendCatching
import com.github.michaelbull.result.onSuccess
@@ -46,8 +40,6 @@ import dev.msfjarvis.claw.common.ui.NetworkImage
import dev.msfjarvis.claw.common.ui.ThemedRichText
import dev.msfjarvis.claw.model.LinkMetadata
import dev.msfjarvis.claw.model.UIPost
import java.time.Instant
import java.time.temporal.TemporalAccessor
import kotlinx.collections.immutable.toImmutableList
@Composable
@@ -123,105 +115,3 @@ private fun PostLink(linkMetadata: LinkMetadata, modifier: Modifier = Modifier)
}
}
}
private val CommentEntryPadding = 16f.dp
@Composable
internal fun CommentEntry(
commentNode: CommentNode,
htmlConverter: HTMLConverter,
toggleExpanded: (CommentNode) -> Unit,
openUserProfile: (String) -> Unit,
modifier: Modifier = Modifier,
) {
val comment = commentNode.comment
Box(
modifier =
modifier
.fillMaxWidth()
.clickable { toggleExpanded(commentNode) }
.background(MaterialTheme.colorScheme.background)
.padding(
start = CommentEntryPadding * commentNode.indentLevel,
end = CommentEntryPadding,
top = CommentEntryPadding,
bottom = CommentEntryPadding,
)
) {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
Submitter(
text =
buildCommenterString(
commenterName = comment.user,
score = comment.score,
isUnread = commentNode.isUnread,
createdAt = comment.createdAt,
updatedAt = comment.updatedAt,
),
avatarUrl = "https://lobste.rs/avatars/${comment.user}-100.png",
contentDescription = "User avatar for ${comment.user}",
modifier = Modifier.clickable { openUserProfile(comment.user) },
)
if (commentNode.isExpanded) {
ThemedRichText(
text = htmlConverter.convertHTMLToMarkdown(comment.comment),
modifier = Modifier.padding(top = 8.dp),
)
}
}
}
}
@Composable
fun buildCommenterString(
commenterName: String,
score: Int,
isUnread: Boolean,
createdAt: TemporalAccessor,
updatedAt: TemporalAccessor,
): AnnotatedString {
val now = System.currentTimeMillis()
val createdRelative =
remember(createdAt) {
DateUtils.getRelativeTimeSpanString(
Instant.from(createdAt).toEpochMilli(),
now,
DateUtils.MINUTE_IN_MILLIS,
)
}
val updatedRelative =
remember(updatedAt) {
DateUtils.getRelativeTimeSpanString(
Instant.from(updatedAt).toEpochMilli(),
now,
DateUtils.MINUTE_IN_MILLIS,
)
}
return buildAnnotatedString {
append(commenterName)
append(' ')
append('•')
append(' ')
append("$score points")
append(' ')
append('•')
append(' ')
append(createdRelative.toString())
if (updatedRelative != createdRelative) {
append(' ')
append('(')
append("Updated")
append(' ')
append(updatedRelative.toString())
append(')')
}
if (isUnread) {
append(' ')
withStyle(
style = SpanStyle(fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.error)
) {
append("(unread)")
}
}
}
}
@@ -6,36 +6,73 @@
*/
package dev.msfjarvis.claw.common.comments
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.material3.HorizontalDivider
import dev.msfjarvis.claw.database.local.PostComments
import dev.msfjarvis.claw.model.Comment
internal data class CommentNode(
internal class CommentNode(
val comment: Comment,
var parent: CommentNode? = null,
private var parent: CommentNode? = null,
val children: MutableList<CommentNode> = mutableListOf(),
val isUnread: Boolean = false,
var isExpanded: Boolean = true,
var indentLevel: Int,
val indentLevel: Int,
) {
fun addChild(child: CommentNode) {
if (comment.shortId == child.comment.parentComment) {
children.add(child)
child.parent = this
} else {
child.indentLevel += 1
children.lastOrNull()?.addChild(child)
children
.lastOrNull()
?.addChild(
CommentNode(
comment = child.comment,
parent = child.parent,
isUnread = child.isUnread,
indentLevel = child.indentLevel + 1,
)
)
}
}
/**
* [CommentNode.equals] and [CommentNode.hashCode] are hand-rolled to drop the
* [CommentNode.parent] field from the comparison since it's possible for there to be cycles in
* this comparison check. For our purposes we're fine with foregoing the field.
*/
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as CommentNode
if (comment != other.comment) return false
if (children != other.children) return false
if (isUnread != other.isUnread) return false
if (indentLevel != other.indentLevel) return false
return true
}
override fun hashCode(): Int {
var result = comment.hashCode()
result = 31 * result + children.hashCode()
result = 31 * result + isUnread.hashCode()
result = 31 * result + indentLevel
return result
}
}
internal fun createListNode(
comments: List<Comment>,
commentState: PostComments?,
commentState: PostComments,
): MutableList<CommentNode> {
// Only check unread state if there any comments read in the first place. This somewhat restores
// the first view behavior from when `commentState` was nullable.
val hasUnread = commentState.commentIds.isNotEmpty()
val isUnread =
if (hasUnread) ({ id: String -> !commentState.commentIds.contains(id) }) else { _ -> false }
val commentNodes = mutableListOf<CommentNode>()
val isUnread = { id: String -> commentState?.commentIds?.contains(id) == false }
for (i in comments.indices) {
if (comments[i].parentComment == null) {
@@ -47,80 +84,16 @@ internal fun createListNode(
)
)
} else {
commentNodes.lastOrNull()?.let {
it.addChild(
commentNodes.lastOrNull()?.let { commentNode ->
commentNode.addChild(
CommentNode(
comment = comments[i],
isUnread = isUnread(comments[i].shortId),
indentLevel = it.indentLevel + 1,
indentLevel = commentNode.indentLevel + 1,
)
)
}
}
}
return commentNodes
}
internal fun setExpanded(commentNode: CommentNode, expanded: Boolean): CommentNode {
commentNode.isExpanded = expanded
if (commentNode.children.isNotEmpty()) {
commentNode.children.forEach { setExpanded(it, expanded) }
}
return commentNode
}
internal tailrec fun findTopMostParent(node: CommentNode): CommentNode {
val parent = node.parent
return if (parent != null) {
findTopMostParent(parent)
} else {
node
}
}
internal fun LazyListScope.nodes(
nodes: List<CommentNode>,
htmlConverter: HTMLConverter,
toggleExpanded: (CommentNode) -> Unit,
openUserProfile: (String) -> Unit,
) {
nodes.forEach { node ->
node(
node = node,
htmlConverter = htmlConverter,
toggleExpanded = toggleExpanded,
openUserProfile = openUserProfile,
)
}
}
private fun LazyListScope.node(
node: CommentNode,
htmlConverter: HTMLConverter,
toggleExpanded: (CommentNode) -> Unit,
openUserProfile: (String) -> Unit,
) {
// Skip the node if neither the node nor its parent is expanded
if (!node.isExpanded && node.parent?.isExpanded == false) {
return
}
item(key = node.comment.shortId) {
CommentEntry(
commentNode = node,
htmlConverter = htmlConverter,
toggleExpanded = toggleExpanded,
openUserProfile = openUserProfile,
)
HorizontalDivider()
}
if (node.children.isNotEmpty()) {
nodes(
node.children,
htmlConverter = htmlConverter,
toggleExpanded = toggleExpanded,
openUserProfile = openUserProfile,
)
}
}
@@ -1,155 +0,0 @@
/*
* Copyright © 2021-2024 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.common.comments
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState
import androidx.compose.runtime.toMutableStateList
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.github.michaelbull.result.coroutines.runSuspendCatching
import com.github.michaelbull.result.fold
import dev.msfjarvis.claw.common.NetworkState
import dev.msfjarvis.claw.common.NetworkState.Error
import dev.msfjarvis.claw.common.NetworkState.Loading
import dev.msfjarvis.claw.common.NetworkState.Success
import dev.msfjarvis.claw.common.posts.PostActions
import dev.msfjarvis.claw.common.ui.NetworkError
import dev.msfjarvis.claw.common.ui.ProgressBar
import dev.msfjarvis.claw.database.local.PostComments
import dev.msfjarvis.claw.model.Comment
import dev.msfjarvis.claw.model.UIPost
@Suppress("LongParameterList")
@Composable
private fun CommentsPageInternal(
details: UIPost,
postActions: PostActions,
htmlConverter: HTMLConverter,
commentState: PostComments?,
markSeenComments: (String, List<Comment>) -> Unit,
openUserProfile: (String) -> Unit,
modifier: Modifier = Modifier,
) {
val commentNodes = createListNode(details.comments, commentState).toMutableStateList()
LaunchedEffect(key1 = commentNodes) { markSeenComments(details.shortId, details.comments) }
Surface(color = MaterialTheme.colorScheme.surfaceVariant) {
LazyColumn(modifier = modifier, contentPadding = PaddingValues(bottom = 24.dp)) {
item {
CommentsHeader(
post = details,
postActions = postActions,
htmlConverter = htmlConverter,
openUserProfile = openUserProfile,
)
}
if (commentNodes.isNotEmpty()) {
item {
Text(
text = "Comments",
style = MaterialTheme.typography.labelLarge,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp),
)
}
nodes(
nodes = commentNodes,
htmlConverter = htmlConverter,
toggleExpanded = { node ->
val newNode = setExpanded(node, !node.isExpanded)
val parent = findTopMostParent(newNode)
val index =
commentNodes.indexOf(commentNodes.find { it.comment.url == parent.comment.url })
if (index != -1) {
commentNodes.removeAt(index)
commentNodes.add(index, parent)
}
},
openUserProfile = openUserProfile,
)
} else {
item {
Text(
text = "No Comments",
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.fillMaxWidth().padding(16.dp),
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center,
)
}
}
}
}
}
@Suppress("UNCHECKED_CAST", "LongParameterList")
@Composable
fun CommentsPage(
postId: String,
postActions: PostActions,
htmlConverter: HTMLConverter,
getSeenComments: suspend (String) -> PostComments?,
markSeenComments: (String, List<Comment>) -> Unit,
modifier: Modifier = Modifier,
openUserProfile: (String) -> Unit,
) {
val postDetails by
produceState<NetworkState>(Loading) {
runSuspendCatching { postActions.getComments(postId) }
.fold(
success = { details -> value = Success(details) },
failure = { value = Error(error = it, description = "Failed to load comments") },
)
}
val commentState by
produceState<PostComments?>(initialValue = null) { value = getSeenComments(postId) }
when (postDetails) {
is Success<*> -> {
CommentsPageInternal(
details = (postDetails as Success<UIPost>).data,
postActions = postActions,
htmlConverter = htmlConverter,
commentState = commentState,
markSeenComments = markSeenComments,
openUserProfile = openUserProfile,
modifier = modifier.fillMaxSize(),
)
}
is Error -> {
val error = postDetails as Error
Box(modifier = Modifier.fillMaxSize()) {
NetworkError(
label = error.description,
error = error.error,
modifier = Modifier.align(Alignment.Center),
)
}
}
Loading -> {
Box(modifier = Modifier.fillMaxSize()) {
ProgressBar(modifier = Modifier.align(Alignment.Center))
}
}
}
}
@@ -0,0 +1,84 @@
/*
* Copyright © 2021-2024 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.common.comments
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.github.michaelbull.result.coroutines.runSuspendCatching
import com.github.michaelbull.result.fold
import dev.msfjarvis.claw.common.NetworkState
import dev.msfjarvis.claw.common.NetworkState.Error
import dev.msfjarvis.claw.common.NetworkState.Loading
import dev.msfjarvis.claw.common.NetworkState.Success
import dev.msfjarvis.claw.common.posts.PostActions
import dev.msfjarvis.claw.common.ui.NetworkError
import dev.msfjarvis.claw.common.ui.ProgressBar
import dev.msfjarvis.claw.database.local.PostComments
import dev.msfjarvis.claw.model.Comment
import dev.msfjarvis.claw.model.UIPost
@Suppress("UNCHECKED_CAST")
@Composable
fun CommentsPage(
postId: String,
postActions: PostActions,
htmlConverter: HTMLConverter,
getSeenComments: suspend (String) -> PostComments,
markSeenComments: (String, List<Comment>) -> Unit,
contentPadding: PaddingValues,
modifier: Modifier = Modifier,
openUserProfile: (String) -> Unit,
) {
val postDetails by
produceState<NetworkState>(Loading) {
runSuspendCatching { postActions.getComments(postId) }
.fold(
success = { details -> value = Success(details) },
failure = { value = Error(error = it, description = "Failed to load comments") },
)
}
val commentState by
produceState(initialValue = PostComments(postId, emptyList())) {
value = getSeenComments(postId)
}
when (postDetails) {
is Success<*> -> {
CommentsPageInternal(
details = (postDetails as Success<UIPost>).data,
postActions = postActions,
htmlConverter = htmlConverter,
commentState = commentState,
markSeenComments = markSeenComments,
openUserProfile = openUserProfile,
contentPadding = contentPadding,
modifier = modifier.fillMaxSize(),
)
}
is Error -> {
val error = postDetails as Error
Box(modifier = Modifier.fillMaxSize()) {
NetworkError(
label = error.description,
error = error.error,
modifier = Modifier.align(Alignment.Center),
)
}
}
Loading -> {
Box(modifier = Modifier.fillMaxSize()) {
ProgressBar(modifier = Modifier.align(Alignment.Center))
}
}
}
}
@@ -0,0 +1,246 @@
/*
* Copyright © 2021-2024 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.common.comments
import android.text.format.DateUtils
import android.widget.Toast
import androidx.compose.foundation.background
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.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.windowInsetsBottomHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
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.platform.LocalContext
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import dev.msfjarvis.claw.common.posts.PostActions
import dev.msfjarvis.claw.common.posts.Submitter
import dev.msfjarvis.claw.common.ui.ThemedRichText
import dev.msfjarvis.claw.database.local.PostComments
import dev.msfjarvis.claw.model.Comment
import dev.msfjarvis.claw.model.UIPost
import java.time.Instant
import java.time.temporal.TemporalAccessor
@Composable
internal fun CommentsPageInternal(
details: UIPost,
postActions: PostActions,
htmlConverter: HTMLConverter,
commentState: PostComments,
markSeenComments: (String, List<Comment>) -> Unit,
openUserProfile: (String) -> Unit,
contentPadding: PaddingValues,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
val commentNodes = createListNode(details.comments, commentState)
LaunchedEffect(key1 = commentNodes) {
if (details.comments.isNotEmpty() && commentState.commentIds.isNotEmpty()) {
val unreadCount = details.comments.size - commentState.commentIds.size
if (unreadCount > 0) {
val text = "$unreadCount unread comments"
Toast.makeText(context, text, Toast.LENGTH_SHORT).show()
}
}
markSeenComments(details.shortId, details.comments)
}
Surface(color = MaterialTheme.colorScheme.surfaceVariant) {
LazyColumn(modifier = modifier, contentPadding = contentPadding) {
item {
CommentsHeader(
post = details,
postActions = postActions,
htmlConverter = htmlConverter,
openUserProfile = openUserProfile,
)
}
if (commentNodes.isNotEmpty()) {
item(key = "comments_header") {
Text(
text = "Comments",
style = MaterialTheme.typography.labelLarge,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp),
)
}
commentNodes.forEach { node ->
item(key = node.comment.shortId) { Node(node, htmlConverter, openUserProfile) }
}
item(key = "bottom_spacer") {
Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.systemBars))
}
} else {
item {
Text(
text = "No Comments",
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.fillMaxWidth().padding(16.dp),
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center,
)
}
}
}
}
}
/**
* Simple tree view implementation by Anton Shilov who was smarter in 2020 than I am today
* https://gist.github.com/antonshilov/ef8cd0a360a5cc0f823b2a4e85084720
*/
@Composable
private fun Node(
node: CommentNode,
htmlConverter: HTMLConverter,
openUserProfile: (String) -> Unit,
modifier: Modifier = Modifier,
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
var isChildrenShown by remember { mutableStateOf(true) }
NodeBox(
node = node,
isExpanded = isChildrenShown,
htmlConverter = htmlConverter,
openUserProfile,
modifier = modifier.clickable(onClick = { isChildrenShown = !isChildrenShown }),
)
if (isChildrenShown) {
Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) {
node.children.forEach { model -> Node(model, htmlConverter, openUserProfile) }
}
}
}
}
@Composable
private fun NodeBox(
node: CommentNode,
isExpanded: Boolean,
htmlConverter: HTMLConverter,
openUserProfile: (String) -> Unit,
modifier: Modifier = Modifier,
) {
CommentEntry(isExpanded, node, htmlConverter, openUserProfile, modifier)
HorizontalDivider()
}
private val CommentEntryPadding = 16f.dp
@Composable
private fun CommentEntry(
isExpanded: Boolean,
commentNode: CommentNode,
htmlConverter: HTMLConverter,
openUserProfile: (String) -> Unit,
modifier: Modifier = Modifier,
) {
val comment = commentNode.comment
Box(
modifier =
modifier
.fillMaxWidth()
.background(
if (commentNode.isUnread) MaterialTheme.colorScheme.surfaceContainerHigh
else MaterialTheme.colorScheme.background
)
.padding(
start = CommentEntryPadding * commentNode.indentLevel,
end = CommentEntryPadding,
top = CommentEntryPadding,
bottom = CommentEntryPadding,
)
) {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
Submitter(
text =
buildCommenterString(
commenterName = comment.user,
score = comment.score,
createdAt = comment.createdAt,
updatedAt = comment.updatedAt,
),
avatarUrl = "https://lobste.rs/avatars/${comment.user}-100.png",
contentDescription = "User avatar for ${comment.user}",
modifier = Modifier.clickable { openUserProfile(comment.user) },
)
if (isExpanded) {
ThemedRichText(
text = htmlConverter.convertHTMLToMarkdown(comment.comment),
modifier = Modifier.padding(top = 8.dp),
)
}
}
}
}
private fun buildCommenterString(
commenterName: String,
score: Int,
createdAt: TemporalAccessor,
updatedAt: TemporalAccessor,
): AnnotatedString {
val now = System.currentTimeMillis()
val createdRelative =
DateUtils.getRelativeTimeSpanString(
Instant.from(createdAt).toEpochMilli(),
now,
DateUtils.MINUTE_IN_MILLIS,
)
val updatedRelative =
DateUtils.getRelativeTimeSpanString(
Instant.from(updatedAt).toEpochMilli(),
now,
DateUtils.MINUTE_IN_MILLIS,
)
return buildAnnotatedString {
append(commenterName)
append(' ')
append('•')
append(' ')
append("$score points")
append(' ')
append('•')
append(' ')
append(createdRelative.toString())
if (updatedRelative != createdRelative) {
append(' ')
append('(')
append("Updated")
append(' ')
append(updatedRelative.toString())
append(')')
}
}
}
@@ -36,7 +36,9 @@ import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.minimumInteractiveComponentSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -59,25 +61,16 @@ import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
@Composable
fun LobstersCard(
post: UIPost,
postActions: PostActions,
refresh: () -> Unit,
modifier: Modifier = Modifier,
) {
var localReadState by remember(post) { mutableStateOf(post.isRead) }
var localSavedState by remember(post) { mutableStateOf(post.isSaved) }
fun LobstersCard(post: UIPost, postActions: PostActions, modifier: Modifier = Modifier) {
val readState by remember(post.shortId) { derivedStateOf { postActions.isPostRead(post) } }
val savedState by remember(post.shortId) { derivedStateOf { postActions.isPostSaved(post) } }
Box(
modifier =
modifier
.fillMaxWidth()
.clickable {
postActions.viewPost(post.shortId, post.url, post.commentsUrl)
localReadState = true
refresh()
}
.clickable { postActions.viewPost(post.shortId, post.url, post.commentsUrl) }
.background(MaterialTheme.colorScheme.background)
.padding(start = 8.dp)
.padding(start = 8.dp, top = 2.dp, bottom = 2.dp)
) {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
@@ -85,35 +78,22 @@ fun LobstersCard(
) {
PostDetails(
post = post,
isRead = localReadState,
isRead = { readState },
singleLineTitle = true,
modifier = Modifier.weight(1f),
)
Column(
modifier = Modifier.wrapContentHeight(),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
SaveButton(
isSaved = localSavedState,
modifier =
Modifier.clickable(role = Role.Button) {
localSavedState = !localSavedState
postActions.toggleSave(post)
refresh()
},
)
SaveButton(isSaved = { savedState }, onClick = { postActions.toggleSave(post) })
HorizontalDivider(modifier = Modifier.width(48.dp))
CommentsButton(
commentCount = post.commentCount,
modifier =
Modifier.clickable(
role = Role.Button,
onClick = {
postActions.viewComments(post.shortId)
localReadState = true
refresh()
},
onClick = { postActions.viewComments(post.shortId) },
),
)
}
@@ -124,16 +104,19 @@ fun LobstersCard(
@Composable
fun PostDetails(
post: UIPost,
isRead: Boolean,
isRead: () -> Boolean,
singleLineTitle: Boolean,
modifier: Modifier = Modifier,
) {
Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(8.dp)) {
PostTitle(title = post.title, isRead = isRead, singleLineTitle = singleLineTitle)
PostTitle(title = post.title, isRead = isRead(), singleLineTitle = singleLineTitle)
TagRow(tags = post.tags.toImmutableList())
Spacer(Modifier.height(4.dp))
Submitter(
text = AnnotatedString("Submitted by ${post.submitter}"),
text =
AnnotatedString(
"${if (post.userIsAuthor) "Authored" else "Submitted"} by ${post.submitter}"
),
avatarUrl = "https://lobste.rs/avatars/${post.submitter}-100.png",
contentDescription = "User avatar for ${post.submitter}",
)
@@ -187,36 +170,38 @@ internal fun Submitter(
}
@Composable
private fun SaveButton(isSaved: Boolean, modifier: Modifier = Modifier) {
Crossfade(targetState = isSaved, label = "save-button") { saved ->
Box(modifier = modifier.padding(vertical = 12.dp)) {
private fun SaveButton(isSaved: () -> Boolean, onClick: () -> Unit, modifier: Modifier = Modifier) {
var localSavedState by remember { mutableStateOf(isSaved()) }
Crossfade(targetState = localSavedState, label = "save-button") { saved ->
Box(modifier = modifier.minimumInteractiveComponentSize()) {
Icon(
imageVector = if (saved) Icons.Filled.Favorite else Icons.Filled.FavoriteBorder,
tint = MaterialTheme.colorScheme.secondary,
contentDescription = if (saved) "Remove from saved posts" else "Add to saved posts",
modifier = Modifier.align(Alignment.Center).testTag("save_button"),
modifier =
Modifier.align(Alignment.Center).testTag("save_button").clickable(role = Role.Button) {
onClick()
localSavedState = !localSavedState
},
)
}
}
}
@Composable
private fun CommentsButton(commentCount: Int?, modifier: Modifier = Modifier) {
private fun CommentsButton(commentCount: Int, modifier: Modifier = Modifier) {
BadgedBox(
modifier = modifier.padding(vertical = 12.dp),
modifier = modifier.minimumInteractiveComponentSize(),
badge = {
if (commentCount != null) {
Badge(
containerColor = MaterialTheme.colorScheme.tertiaryContainer,
// Required to make the badge label look visually okay
modifier = Modifier.absoluteOffset(y = (-8).dp),
) {
Text(
text = commentCount.toString(),
color = MaterialTheme.colorScheme.onTertiaryContainer,
style = MaterialTheme.typography.labelMedium,
)
}
Badge(
containerColor = MaterialTheme.colorScheme.tertiaryContainer,
modifier = Modifier.absoluteOffset(y = (-8).dp),
) {
Text(
text = commentCount.toString(),
color = MaterialTheme.colorScheme.onTertiaryContainer,
style = MaterialTheme.typography.labelMedium,
)
}
},
) {
@@ -265,6 +250,16 @@ val TEST_POST_ACTIONS =
override fun toggleSave(post: UIPost) {}
override fun share(post: UIPost) {}
override fun isPostRead(post: UIPost): Boolean {
return true
}
override fun isPostSaved(post: UIPost): Boolean {
return true
}
override suspend fun getComments(postId: String): UIPost {
return UIPost(
shortId = "ooga",
@@ -297,12 +292,10 @@ val TEST_POST =
submitter = "Haki",
tags = listOf("databases", "apis"),
description = "",
isSaved = true,
isRead = true,
)
@ThemePreviews
@Composable
private fun LobstersCardPreview() {
LobstersTheme { LobstersCard(post = TEST_POST, postActions = TEST_POST_ACTIONS, refresh = {}) }
LobstersTheme { LobstersCard(post = TEST_POST, postActions = TEST_POST_ACTIONS) }
}
@@ -20,6 +20,12 @@ interface PostActions {
fun toggleSave(post: UIPost)
fun share(post: UIPost)
fun isPostRead(post: UIPost): Boolean
fun isPostSaved(post: UIPost): Boolean
suspend fun getComments(postId: String): UIPost
suspend fun getLinkMetadata(url: String): LinkMetadata
@@ -18,8 +18,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.ProvidedValue
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
@@ -106,7 +104,6 @@ fun LobstersTheme(
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
window.statusBarColor = Color.Transparent.toArgb()
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme
}
}
@@ -0,0 +1,94 @@
/*
* Copyright © 2024 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.common.ui
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.selection.selectableGroup
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.BarChart
import androidx.compose.material.icons.filled.BrokenImage
import androidx.compose.material.icons.filled.HeartBroken
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBarDefaults
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Surface
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
// Taken from Chris Banes' amazing app Tivi
// https://github.com/chrisbanes/tivi/blob/836d596d74959f4235ca2395b5bbfdd6fd9c9a9e/ui/root/src/commonMain/kotlin/app/tivi/home/Home.kt#L173
@Composable
fun FloatingNavigationBar(
modifier: Modifier = Modifier,
shape: Shape = MaterialTheme.shapes.extraLarge,
containerColor: Color = NavigationBarDefaults.containerColor,
contentColor: Color = MaterialTheme.colorScheme.contentColorFor(containerColor),
tonalElevation: Dp = NavigationBarDefaults.Elevation,
content: @Composable RowScope.() -> Unit,
) {
Surface(
color = containerColor,
contentColor = contentColor,
tonalElevation = tonalElevation,
shape = shape,
border =
BorderStroke(
width = 0.5.dp,
brush =
Brush.verticalGradient(
colors =
listOf(
MaterialTheme.colorScheme.surfaceVariant,
MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f),
)
),
),
modifier = modifier,
) {
Row(
modifier = Modifier.padding(horizontal = 8.dp).fillMaxWidth().height(80.dp).selectableGroup(),
horizontalArrangement = Arrangement.spacedBy(8.dp),
content = content,
)
}
}
@Preview
@Composable
private fun FloatingNavigationBarPreview() {
FloatingNavigationBar {
NavigationBarItem(
selected = true,
onClick = {},
icon = { Icon(imageVector = Icons.Filled.HeartBroken, contentDescription = "Home") },
)
NavigationBarItem(
selected = true,
onClick = {},
icon = { Icon(imageVector = Icons.Filled.BarChart, contentDescription = "Home") },
)
NavigationBarItem(
selected = true,
onClick = {},
icon = { Icon(imageVector = Icons.Filled.BrokenImage, contentDescription = "Home") },
)
}
}
@@ -9,15 +9,13 @@ package dev.msfjarvis.claw.common.user
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.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.text.ClickableText
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
@@ -27,11 +25,8 @@ import androidx.compose.runtime.produceState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.LinkAnnotation
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import com.github.michaelbull.result.coroutines.runSuspendCatching
import com.github.michaelbull.result.fold
@@ -51,6 +46,7 @@ fun UserProfile(
username: String,
getProfile: suspend (username: String) -> User,
openUserProfile: (String) -> Unit,
contentPadding: PaddingValues,
modifier: Modifier = Modifier,
) {
val user by
@@ -73,7 +69,7 @@ fun UserProfile(
}
is Error -> {
val error = user as Error
Box(modifier = Modifier.fillMaxSize()) {
Box(modifier = Modifier.padding(contentPadding).fillMaxSize()) {
NetworkError(
label = error.description,
error = error.error,
@@ -110,47 +106,18 @@ private fun UserProfileInternal(
Text(text = user.username, style = MaterialTheme.typography.displaySmall)
ThemedRichText(text = user.about)
user.invitedBy?.let { invitedBy ->
val linkTextStyle =
with(LocalTextStyle.current) {
SpanStyle(
LocalContentColor.current,
fontSize,
fontWeight,
fontStyle,
fontSynthesis,
fontFamily,
fontFeatureSettings,
letterSpacing,
baselineShift,
textGeometricTransform,
localeList,
background,
textDecoration,
shadow,
)
}
val text = buildAnnotatedString {
withStyle(linkTextStyle) { append("Invited by ") }
pushStringAnnotation(tag = "URL", annotation = invitedBy)
withStyle(
linkTextStyle.copy(
textDecoration = TextDecoration.Underline,
fontWeight = FontWeight.Bold,
)
) {
append(invitedBy)
}
pop()
}
ClickableText(
text = text,
onClick = { offset ->
text
.getStringAnnotations(tag = "URL", start = offset, end = offset)
.firstOrNull()
?.let { annotation -> openUserProfile(annotation.item) }
},
Text(
text =
buildAnnotatedString {
append("Invited by ")
pushLink(
LinkAnnotation.Clickable(
tag = "username",
linkInteractionListener = { openUserProfile(invitedBy) },
)
)
append(invitedBy)
}
)
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.3.0" type="baseline" client="gradle" dependencies="true" name="AGP (8.3.0)" variant="all" version="8.3.0">
<issues format="6" by="lint 8.4.2" type="baseline" client="gradle" dependencies="true" name="AGP (8.4.2)" variant="all" version="8.4.2">
</issues>
+1 -1
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.3.0" type="baseline" client="gradle" dependencies="true" name="AGP (8.3.0)" variant="all" version="8.3.0">
<issues format="6" by="lint 8.4.2" type="baseline" client="gradle" dependencies="true" name="AGP (8.4.2)" variant="all" version="8.4.2">
</issues>
+1 -1
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.3.0" type="baseline" client="gradle" dependencies="true" name="AGP (8.3.0)" variant="all" version="8.3.0">
<issues format="6" by="lint 8.4.2" type="baseline" client="gradle" dependencies="true" name="AGP (8.4.2)" variant="all" version="8.4.2">
</issues>
+3 -2
View File
@@ -13,8 +13,8 @@ org.gradle.configuration-cache=true
org.gradle.configuration-cache.problems=warn
org.gradle.configuration-cache.max-problems=2
# Set JVM heap size to 4G
org.gradle.jvmargs=-Xmx4096m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# Set JVM heap size to 3G
org.gradle.jvmargs=-Xmx3072m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options=-Xmx1500m,Xms500m
# Enable Kotlin incremental compilation
kotlin.incremental=true
@@ -50,6 +50,7 @@ android.disableEarlyManifestParsing=true
# Disable warnings about unsupported features
android.suppressUnsupportedOptionWarnings=android.dependencyResolutionAtConfigurationTime.disallow,android.disableEarlyManifestParsing,android.suppressUnsupportedOptionWarnings
android.suppressUnsupportedCompileSdk=35
# Make Spotless default to keeping license headers years in sync
spotlessSetLicenseHeaderYearsFromGitHistory=true
+44 -42
View File
@@ -1,35 +1,35 @@
[versions]
aboutLibraries = "11.1.4"
agp = "8.5.0-beta01"
android-junit5 = "1.10.0.0"
benchmark = "1.3.0-alpha04"
coil = "2.6.0"
# @keep used for kotlinCompilerExtensionVersion
composeCompiler = "1.5.14-dev-k1.9.24-50022def4af"
coroutines = "1.8.1"
dagger = "2.51.1"
aboutLibraries = "11.2.3"
agp = "8.6.0"
android-junit5 = "1.11.0.0"
benchmark = "1.3.0"
coil = "2.7.0"
coroutines = "1.9.0-RC.2"
dagger = "2.52"
glance = "1.0.0"
junit = "5.10.2"
konvert = "3.2.0"
kotlin = "1.9.24"
junit = "5.11.0"
konvert = "3.2.2"
kotlin = "2.0.20"
kotlinResult = "2.0.0"
leakcanary = "3.0-alpha-4"
lifecycle = "2.8.0-rc01"
leakcanary = "3.0-alpha-8"
lifecycle = "2.8.4"
navigation = "2.8.0-rc01"
retrofit = "2.11.0"
richtext = "1.0.0-alpha01"
sentry-sdk = "7.9.0"
serialization = "1.6.3"
sentry-sdk = "7.14.0"
serialization = "1.7.2"
store = "5.1.0-alpha04"
sqldelight = "2.0.2"
whetstone = "0.9.0-beta02"
workmanager = "2.10.0-alpha02"
[libraries]
aboutLibraries-m3 = { module = "com.mikepenz:aboutlibraries-compose-m3", version.ref = "aboutLibraries" }
androidx-activity-compose = "androidx.activity:activity-compose:1.9.0"
androidx-activity-compose = "androidx.activity:activity-compose:1.9.1"
androidx-benchmark-macro-junit4 = { module = "androidx.benchmark:benchmark-macro-junit4", version.ref = "benchmark" }
androidx-browser = "androidx.browser:browser:1.8.0"
androidx-compose-animation = { module = "androidx.compose.animation:animation" }
androidx-compose-bom = "androidx.compose:compose-bom:2024.05.00"
androidx-compose-bom = "dev.chrisbanes.compose:compose-bom:2024.08.00-alpha02"
androidx-compose-foundation = { module = "androidx.compose.foundation:foundation" }
androidx-compose-glance = { module = "androidx.glance:glance-appwidget", version.ref = "glance" }
androidx-compose-glance-m3 = { module = "androidx.glance:glance-material3", version.ref = "glance" }
@@ -45,26 +45,24 @@ androidx-compose-ui-unit = { module = "androidx.compose.ui:ui-unit" }
androidx-compose-ui-util = { module = "androidx.compose.ui:ui-util" }
androidx-core = "androidx.core:core:1.13.1"
androidx-core-splashscreen = "androidx.core:core-splashscreen:1.2.0-alpha01"
androidx-lifecycle-common = { module = "androidx.lifecycle:lifecycle-common", version.ref = "lifecycle" }
androidx-lifecycle-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycle" }
androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime", version.ref = "lifecycle" }
androidx-lint = "androidx.lint:lint-gradle:1.0.0-alpha01"
androidx-navigation-compose = "androidx.navigation:navigation-compose:2.8.0-alpha08"
androidx-paging-compose = "androidx.paging:paging-compose:3.3.0-rc01"
androidx-profileinstaller = "androidx.profileinstaller:profileinstaller:1.4.0-alpha01"
androidx-test-core = "androidx.test:core:1.6.0-alpha06"
androidx-test-espresso-core = "androidx.test.espresso:espresso-core:3.6.0-alpha04"
androidx-test-ext-junit = "androidx.test.ext:junit:1.2.0-alpha04"
androidx-test-rules = "androidx.test:rules:1.6.0-alpha04"
androidx-test-runner = "androidx.test:runner:1.6.0-alpha07"
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigation" }
androidx-paging-compose = "androidx.paging:paging-compose:3.3.2"
androidx-profileinstaller = "androidx.profileinstaller:profileinstaller:1.4.0-beta01"
androidx-test-core = "androidx.test:core:1.6.1"
androidx-test-espresso-core = "androidx.test.espresso:espresso-core:3.6.1"
androidx-test-ext-junit = "androidx.test.ext:junit:1.2.1"
androidx-test-rules = "androidx.test:rules:1.6.1"
androidx-test-runner = "androidx.test:runner:1.6.2"
androidx-test-uiautomator = "androidx.test.uiautomator:uiautomator:2.3.0"
androidx-work-runtime = { module = "androidx.work:work-runtime", version.ref = "workmanager" }
build-agp = { module = "com.android.tools.build:gradle", version.ref = "agp" }
build-cachefix = "org.gradle.android.cache-fix:org.gradle.android.cache-fix.gradle.plugin:3.0.1"
build-kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
build-semver = "com.github.zafarkhaja:java-semver:0.10.2"
build-sentry = "io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin:4.5.1"
build-spotless = "com.diffplug.spotless:spotless-plugin-gradle:6.25.0"
build-sentry = "io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin:4.11.0"
build-spotless = "com.diffplug.spotless:spotless-plugin-gradle:7.0.0.BETA2"
build-vcu = "nl.littlerobots.version-catalog-update:nl.littlerobots.version-catalog-update.gradle.plugin:0.8.4"
coil = { module = "io.coil-kt:coil", version.ref = "coil" }
coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" }
@@ -74,9 +72,11 @@ compose-richtext-ui = { module = "com.halilibo.compose-richtext:richtext-ui", ve
copydown = "io.github.furstenheim:copy_down:1.1"
dagger = { module = "com.google.dagger:dagger", version.ref = "dagger" }
dagger-compiler = { module = "com.google.dagger:dagger-compiler", version.ref = "dagger" }
eithernet = "com.slack.eithernet:eithernet:1.8.1"
eithernet = "com.slack.eithernet:eithernet:1.9.0"
haze = "dev.chrisbanes.haze:haze:0.9.0-alpha08"
haze-materials = "dev.chrisbanes.haze:haze-materials:0.9.0-alpha08"
javax-inject = "javax.inject:javax.inject:1"
jsoup = "org.jsoup:jsoup:1.17.2"
jsoup = "org.jsoup:jsoup:1.18.1"
junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" }
junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" }
junit-legacy = "junit:junit:4.13.2"
@@ -92,41 +92,43 @@ kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-t
kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "serialization" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" }
leakcanary-android-test = { module = "com.squareup.leakcanary:leakcanary-android-test", version.ref = "leakcanary" }
material3-pulltorefresh = "eu.bambooapps:compose-material3-pullrefresh:1.1.1"
napier = "io.github.aakira:napier:2.7.1"
okhttp-bom = "com.squareup.okhttp3:okhttp-bom:4.12.0"
okhttp-core = { module = "com.squareup.okhttp3:okhttp" }
okhttp-loggingInterceptor = { module = "com.squareup.okhttp3:logging-interceptor" }
okhttp-mockwebserver = { module = "com.squareup.okhttp3:mockwebserver" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
retrofit-kotlinxSerializationConverter = { module = "com.squareup.retrofit2:converter-kotlinx-serialization", version.ref = "retrofit" }
sentry-android = { module = "io.sentry:sentry-android", version.ref = "sentry-sdk" }
sentry-bom = { module = "io.sentry:sentry-bom", version.ref = "sentry-sdk" }
slack-compose-lints = "com.slack.lint.compose:compose-lint-checks:1.3.1"
slack-lints = "com.slack.lint:slack-lint-checks:0.7.3"
slack-lints = "com.slack.lint:slack-lint-checks:0.7.4"
sqldelight-androidDriver = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" }
sqldelight-dialect338 = { module = "app.cash.sqldelight:sqlite-3-38-dialect", version.ref = "sqldelight" }
sqldelight-extensions-coroutines = { module = "app.cash.sqldelight:coroutines-extensions-jvm", version.ref = "sqldelight" }
sqldelight-jvmDriver = { module = "app.cash.sqldelight:sqlite-driver", version.ref = "sqldelight" }
sqldelight-primitiveAdapters = { module = "app.cash.sqldelight:primitive-adapters", version.ref = "sqldelight" }
sqlite-android = "com.github.requery:sqlite-android:3.45.0"
store = { module = "org.mobilenativefoundation.store:store5", version.ref = "store" }
swipe = "me.saket.swipe:swipe:1.3.0"
truth = "com.google.truth:truth:1.4.2"
unfurl = "me.saket.unfurl:unfurl:1.7.0"
truth = "com.google.truth:truth:1.4.4"
unfurl = "me.saket.unfurl:unfurl:2.2.0"
[plugins]
aboutlibraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "aboutLibraries" }
android-lint = { id = "com.android.lint", version.ref = "agp" }
android-junit5 = { id = "de.mannodermaus.android-junit5", version.ref = "android-junit5" }
android-test = { id = "com.android.test", version.ref = "agp" }
anvil = "com.squareup.anvil:2.5.0-beta09"
anvil = "com.squareup.anvil:2.5.0-beta11"
baselineprofile = { id = "androidx.baselineprofile", version.ref = "benchmark" }
dependencyAnalysis = "com.autonomousapps.dependency-analysis:1.31.0"
dependencyAnalysis = "com.autonomousapps.dependency-analysis:2.0.1"
invert = "com.squareup.invert:0.0.1-dev"
kotlin-composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
ksp = "com.google.devtools.ksp:1.9.24-1.0.20"
ksp = "com.google.devtools.ksp:2.0.20-1.0.24"
licensee = "app.cash.licensee:1.11.0"
modulegraphassert = "com.jraska.module.graph.assertion:2.5.0"
poko = "dev.drewhamilton.poko:0.15.2"
modulegraphassert = "com.jraska.module.graph.assertion:2.7.1"
navigation-safeargs = { id = "androidx.navigation.safeargs.kotlin", version.ref = "navigation" }
poko = "dev.drewhamilton.poko:0.17.0"
sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" }
tracelog = "dev.msfjarvis.tracelog:0.1.3"
whetstone = { id = "dev.msfjarvis.whetstone", version.ref = "whetstone" }
Binary file not shown.
+2 -2
View File
@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=a2e1cfee7ffdeee86015b85b2dd2a435032c40eedc01d8172285556c7d8fea13
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-rc-1-bin.zip
distributionSha256Sum=5b9c5eb3f9fc2c94abaea57d90bd78747ca117ddbbf96c859d3741181a12bf2a
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+4 -1
View File
@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@@ -84,7 +86,8 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
Vendored
+2
View File
@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
+1 -1
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.3.0" type="baseline" client="gradle" dependencies="true" name="AGP (8.3.0)" variant="all" version="8.3.0">
<issues format="6" by="lint 8.4.2" type="baseline" client="gradle" dependencies="true" name="AGP (8.4.2)" variant="all" version="8.4.2">
</issues>
@@ -29,5 +29,6 @@ class LobstersPost(
val commentCount: Int,
val commentsUrl: String,
@SerialName("submitter_user") val submitter: String,
@SerialName("user_is_author") val userIsAuthor: Boolean,
val tags: List<String>,
)
@@ -27,8 +27,7 @@ data class UIPost(
@SerialName("submitter_user") val submitter: String,
val tags: List<String>,
val comments: List<Comment> = emptyList(),
val isSaved: Boolean = false,
val isRead: Boolean = false,
val userIsAuthor: Boolean = false,
) {
@KonvertFrom(
value = SavedPost::class,
@@ -36,7 +35,6 @@ data class UIPost(
[
Mapping(target = "submitter", expression = "it.submitterName"),
Mapping(target = "commentCount", expression = "it.commentCount ?: 0"),
Mapping(target = "isSaved", expression = "true"),
],
)
companion object
+10 -1
View File
@@ -14,6 +14,7 @@ pluginManagement {
includeGroup("androidx.baselineprofile")
includeGroup("androidx.benchmark")
includeGroup("androidx.databinding")
includeGroupAndSubgroups("androidx.navigation")
includeGroup("com.google.testing.platform")
includeGroupAndSubgroups("com.android")
}
@@ -49,6 +50,12 @@ pluginManagement {
maven("https://oss.sonatype.org/content/repositories/snapshots/") {
name = "Sonatype Snapshots"
content { includeGroup("dev.msfjarvis.whetstone") }
content { includeGroup("dev.drewhamilton.poko") }
mavenContent { snapshotsOnly() }
}
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") {
name = "Sonatype S01 Snapshots"
content { includeGroup("com.squareup.invert") }
mavenContent { snapshotsOnly() }
}
mavenCentral { mavenContent { releasesOnly() } }
@@ -57,7 +64,7 @@ pluginManagement {
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
id("com.gradle.develocity") version "3.17.3"
id("com.gradle.develocity") version "3.18"
}
develocity {
@@ -91,6 +98,7 @@ dependencyResolutionManagement {
maven("https://oss.sonatype.org/content/repositories/snapshots/") {
name = "Sonatype Snapshots"
content { includeGroup("dev.msfjarvis.whetstone") }
content { includeGroup("dev.drewhamilton.poko") }
mavenContent { snapshotsOnly() }
}
mavenCentral { mavenContent { releasesOnly() } }
@@ -110,5 +118,6 @@ include(
"database:core",
"database:impl",
"model",
"store",
"web",
)
+24
View File
@@ -0,0 +1,24 @@
/*
* Copyright © 2021-2024 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.
*/
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)
}
android { namespace = "dev.msfjarvis.claw.data.store" }
anvil { generateDaggerFactories.set(true) }
dependencies {
implementation(libs.store)
addTestDependencies(project)
}
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.4.2" type="baseline" client="gradle" dependencies="true" name="AGP (8.4.2)" variant="all" version="8.4.2">
</issues>
+1 -1
View File
@@ -9,12 +9,12 @@
plugins {
id("dev.msfjarvis.claw.android-library")
id("dev.msfjarvis.claw.kotlin-android")
alias(libs.plugins.kotlin.composeCompiler)
}
android {
namespace = "com.google.accompanist.web"
buildFeatures.compose = true
composeOptions.kotlinCompilerExtensionVersion = libs.versions.composeCompiler.get()
kotlin.explicitApi()
// Don't quite care
lint.disable += "DeprecatedCall"
+1 -1
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.3.0" type="baseline" client="gradle" dependencies="true" name="AGP (8.3.0)" variant="all" version="8.3.0">
<issues format="6" by="lint 8.4.2" type="baseline" client="gradle" dependencies="true" name="AGP (8.4.2)" variant="all" version="8.4.2">
</issues>