Compare commits

..
196 Commits
Author SHA1 Message Date
GitHub Actions 35469825c7 feat(release): bump version 2023-11-20 07:33:30 +00:00
Harsh Shandilya f565bc41d7 chore: refresh feature graphic 2023-11-20 12:42:48 +05:30
Harsh Shandilya 9446f09f22 chore: adjust changelog to be under 500 chars 2023-11-20 12:39:03 +05:30
Harsh Shandilya 925158b149 feat(build): remove modulegraph plugin
I don't care
2023-11-20 12:32:51 +05:30
Harsh Shandilya 6f1c1b3629 fix(maestro): update flow for new search screen 2023-11-20 12:31:18 +05:30
Harsh Shandilya cb6e71c8bf fix(build): add minify switch 2023-11-20 12:31:18 +05:30
Harsh Shandilya 17258ea4d7 feat(android): refresh baseline profiles 2023-11-20 12:31:18 +05:30
Harsh Shandilya 035d33fcea chore(android): remove obsolete workaround 2023-11-20 00:55:18 +05:30
Harsh Shandilya de46f1b159 fix(gradle): remove unnecessary jvmargs setup 2023-11-19 21:12:07 +05:30
Harsh Shandilya 4b4d133e8b fix(build): target Kotlin 1.9 2023-11-19 21:12:07 +05:30
Harsh Shandilya 5a1cdc73c5 fix(android): refresh icon asset
dzuk said the asset had export errors.
2023-11-19 19:23:15 +05:30
Harsh Shandilya c5fe4f5325 fix(ci): add job to validate changelog 2023-11-18 16:25:05 +05:30
Harsh Shandilya e2fa605db6 Merge remote-tracking branches 'origin/renovate/actions-setup-python-4.x' and 'origin/renovate/pin-dependencies'
* origin/renovate/actions-setup-python-4.x:
  chore(deps): update actions/setup-python action to v4

* origin/renovate/pin-dependencies:
  chore(deps): pin dependencies
2023-11-18 15:37:19 +05:30
renovate[bot]andGitHub 55580bc51c chore(deps): update actions/setup-python action to v4 2023-11-18 10:02:51 +00:00
renovate[bot]andGitHub fe78f97f6a chore(deps): pin dependencies 2023-11-18 10:02:48 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
9d46e6f460 chore(deps): update mobsf/mobsfscan digest to b8503e0 (#417)
[![Mend Renovate logo
banner](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| MobSF/mobsfscan | action | digest | `a60d10a` -> `b8503e0` |

---

### 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:eyJjcmVhdGVkSW5WZXIiOiIzNy41OS44IiwidXBkYXRlZEluVmVyIjoiMzcuNTkuOCIsInRhcmdldEJyYW5jaCI6Im1haW4ifQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-18 09:59:28 +00:00
Harsh Shandilya 21f9f6f476 feat(ci): add MobSF code quality job 2023-11-18 15:26:19 +05:30
Harsh Shandilya 2ba8eb9430 chore: rejig changelog
Add missing stuff and move more interesting stuff higher up
2023-11-18 15:05:03 +05:30
Harsh Shandilya 6ea194dd0e Revert "fix(android): allow configuration changes to go through"
I don't think I want the behavior this creates.

This reverts commit 4218beac9d.
2023-11-18 15:05:03 +05:30
Harsh Shandilya 1d3d4f7455 fix(android): avoid two unnecessary API calls in search 2023-11-18 15:00:06 +05:30
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
fdb79d4a6d chore(deps): update dependency gradle to v8.5-rc-3 (#415)
[![Mend Renovate logo
banner](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [gradle](https://gradle.org)
([source](https://togithub.com/gradle/gradle)) | patch | `8.5-rc-2` ->
`8.5-rc-3` |

---

### Release Notes

<details>
<summary>gradle/gradle (gradle)</summary>

###
[`v8.5-rc-3`](https://togithub.com/gradle/gradle/compare/v8.5.0-RC2...v8.5.0-RC3)

[Compare
Source](https://togithub.com/gradle/gradle/compare/v8.5.0-RC2...v8.5.0-RC3)

</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:eyJjcmVhdGVkSW5WZXIiOiIzNy41OS44IiwidXBkYXRlZEluVmVyIjoiMzcuNTkuOCIsInRhcmdldEJyYW5jaCI6Im1haW4ifQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-17 14:37:46 +00:00
Harsh Shandilya 89fce2010c feat(common): add PasswordField 2023-11-17 19:56:25 +05:30
Harsh Shandilya fb878824e8 fix(build): address enableAndroidTest deprecation 2023-11-17 19:56:25 +05:30
renovate[bot]andGitHub aff5af4771 chore(deps): update agp to v8.3.0-alpha14 2023-11-17 01:26:14 +00:00
Harsh Shandilya dd06c93935 fix(api): migrate to new pagination API
The older one is supposedly performing very poorly on the lobsters
side and has been modified to serve only cached content for unauthenticated
users. Unfortunately the configuration for this caching behaviour has
a bug that ignores query parameters and thus every page returns
the same set of 20 posts. Cut us over to this new API path that
hopefully does not generate too much load for the service.

See: https://github.com/lobsters/lobsters/issues/1219
2023-11-16 22:42:23 +05:30
renovate[bot]andGitHub d9e664c440 chore(deps): update github/codeql-action action to v2.22.7 2023-11-16 13:29:24 +00:00
Harsh Shandilya 0a492a169e Merge remote-tracking branch 'origin/renovate/androidx'
* origin/renovate/androidx:
  fix(deps): update androidx
2023-11-16 03:03:19 +05:30
Harsh Shandilya 6ae71adbca feat(android): add app icon to top bar
I love it too much
2023-11-16 03:02:40 +05:30
Harsh Shandilya cbaa098005 fix(deps): upgrade compose-bom 2023-11-16 02:42:01 +05:30
Harsh Shandilya 741e82a425 fix(build): remove use of custom R8 build
In AGP we trust...?
2023-11-16 01:04:25 +05:30
renovate[bot]andGitHub c694b57e08 fix(deps): update androidx 2023-11-15 19:17:32 +00:00
renovate[bot]andGitHub 2c7bc2d668 fix(deps): update serialization to v1.6.1 2023-11-15 16:37:19 +00:00
renovate[bot]andGitHub 765f83a2ae fix(deps): update dependency org.testcontainers:testcontainers to v1.19.2 2023-11-15 04:12:08 +00:00
Harsh Shandilya 58f6607404 feat(android): add new icon by dzuk 2023-11-15 09:34:06 +05:30
Harsh Shandilya 9b27783886 fix(build): recognise vector as an XML tag 2023-11-15 09:10:57 +05:30
renovate[bot]andGitHub c6de087615 chore(deps): update dependency gradle to v8.5-rc-2 2023-11-14 16:25:51 +00:00
renovate[bot]andGitHub 86f7b54b87 chore(deps): update github/codeql-action action to v2.22.6 2023-11-14 13:24:37 +00:00
renovate[bot]andGitHub defe305ad7 chore(deps): update actions/dependency-review-action digest to 7bbfa03 2023-11-13 20:32:44 +00:00
Harsh Shandilya cc210a3597 chore(common): rename ext.kt -> Color.ext.kt 2023-11-11 23:40:11 +05:30
renovate[bot]andGitHub f7c7bb293b chore(deps): update agp to v8.3.0-alpha13 2023-11-09 22:57:33 +00:00
Harsh Shandilya ab1db27dc7 feat(common): request focus when search screen is opened 2023-11-08 18:06:57 +05:30
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
71a86fa9aa chore(deps): update actions/dependency-review-action digest to fde92ac (#411)
[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[actions/dependency-review-action](https://togithub.com/actions/dependency-review-action)
| action | digest | `9f45b24` -> `fde92ac` |

---

### 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:eyJjcmVhdGVkSW5WZXIiOiIzNy40Ni4wIiwidXBkYXRlZEluVmVyIjoiMzcuNDYuMCIsInRhcmdldEJyYW5jaCI6Im1haW4ifQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-08 11:49:31 +00:00
Harsh Shandilya fcc20d2f1e refactor(build): use a hack to expose catalog type-safe API 2023-11-08 17:04:35 +05:30
Harsh Shandilya 8922142ade Revert "feat(common): add swipe action for saving posts"
The post update flow is not synced enough for this to work.

This reverts commit 7a648a77b0.
2023-11-08 01:34:13 +05:30
Harsh Shandilya eb28fe0120 chore(build): upgrade to Gradle 8.5 RC1 2023-11-08 00:55:58 +05:30
renovate[bot]andGitHub d73a755cf2 fix(deps): update sentry to v6.33.1 2023-11-07 11:51:22 +00:00
Harsh Shandilya 7a648a77b0 feat(common): add swipe action for saving posts 2023-11-07 13:09:15 +05:30
Harsh Shandilya c721b8b1da fix(android): adjust default theme 2023-11-07 12:51:40 +05:30
Harsh Shandilya ce62671276 fix(common): use multipreview for SearchBar 2023-11-06 17:36:51 +05:30
Harsh Shandilya ba4cd1b0c6 fix(android): simplify SearchList component 2023-11-06 17:32:37 +05:30
renovate[bot]andGitHub a18ee3d6e5 chore(deps): update actions/dependency-review-action digest to 9f45b24 2023-11-06 08:39:10 +00:00
renovate[bot]andGitHub 15fa833d8e fix(deps): update junit5 monorepo to v5.10.1 2023-11-05 18:06:10 +00:00
Harsh Shandilya 06edbeb903 feat(android): fully flesh out search screen 2023-11-04 21:55:20 +05:30
Harsh Shandilya 082dc9cc82 refactor(android): LobstersApp -> LobstersPostsScreen 2023-11-04 20:52:37 +05:30
Harsh Shandilya 0b3286cd1e feat(android): revert search screen to old design
The Material3 SearchBar component is just not for me
2023-11-04 20:50:37 +05:30
Harsh Shandilya f17fb4487e fix(database): specify SQLite dialect
We use a new-enough version of SQLite for this
2023-11-04 20:16:20 +05:30
Harsh Shandilya 339162d595 refactor(android): ListItem -> LobstersListItem 2023-11-04 19:50:14 +05:30
Harsh Shandilya da6fefb1b0 fix(build): disable CC for Spotless 2023-11-04 19:45:29 +05:30
Harsh Shandilya 8d3ed24d65 fix(api): switch Retrofit URL to Named 2023-11-04 19:40:21 +05:30
Harsh Shandilya ba219af1ac fix(api): directly use HTTP annotation 2023-11-04 19:38:49 +05:30
Harsh Shandilya 4218beac9d fix(android): allow configuration changes to go through 2023-11-04 13:59:39 +05:30
renovate[bot]andGitHub d5d71c1c82 fix(deps): update dependency com.slack.eithernet:eithernet to v1.8.0 2023-11-03 21:44:50 +00:00
Harsh Shandilya 348b3a7c65 feat(android): move backup/restore and search to a dropdown menu 2023-11-04 02:43:03 +05:30
Harsh Shandilya f636b56ee3 feat(android): introduce a separate SearchActivity 2023-11-04 02:42:21 +05:30
Harsh Shandilya ed81ceaa94 chore: reformat manifest file 2023-11-04 02:29:56 +05:30
renovate[bot]andGitHub 7a78c77b6a fix(deps): update dependency eu.bambooapps:compose-material3-pullrefresh to v1.0.1 2023-11-03 13:11:18 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
337cd259a6 chore(deps): update agp to v8.3.0-alpha12 (#409)
[![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.3.0-alpha11` -> `8.3.0-alpha12` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/com.android.test/8.3.0-alpha12?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/com.android.test/8.3.0-alpha12?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/com.android.test/8.3.0-alpha11/8.3.0-alpha12?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.android.test/8.3.0-alpha11/8.3.0-alpha12?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.3.0-alpha11` -> `8.3.0-alpha12` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/com.android.tools.build:gradle/8.3.0-alpha12?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/com.android.tools.build:gradle/8.3.0-alpha12?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/com.android.tools.build:gradle/8.3.0-alpha11/8.3.0-alpha12?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.android.tools.build:gradle/8.3.0-alpha11/8.3.0-alpha12?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:eyJjcmVhdGVkSW5WZXIiOiIzNy4zMS41IiwidXBkYXRlZEluVmVyIjoiMzcuMzEuNSIsInRhcmdldEJyYW5jaCI6Im1haW4ifQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-03 03:26:46 +00:00
renovate[bot]andGitHub e7781517aa fix(deps): update dependency com.slack.eithernet:eithernet to v1.7.0 2023-11-02 17:32:43 +00:00
renovate[bot]andGitHub ce3411e956 fix(deps): update sentry to v6.33.0 2023-11-02 12:32:40 +00:00
renovate[bot]andGitHub a58ca2c419 fix(deps): update androidx 2023-11-01 19:16:37 +00:00
Harsh Shandilya 608d855cae fix(android): make TopAppBar color match BottomNavigation 2023-11-01 22:19:46 +05:30
Harsh Shandilya eb529be286 fix(deps): upgrade Compose Compiler and KSP 2023-11-01 00:49:17 +05:30
renovate[bot]andGitHub 68dbe97442 chore(deps): update kotlin and compose to v1.9.20 2023-10-31 19:17:22 +00:00
Harsh Shandilya 5374410c67 fix(renovate): include KSP in Kotlin group 2023-11-01 00:46:59 +05:30
Harsh Shandilya cbb697216c fix(build): make google repo non-exclusive
This allows the Compose Compiler artifact to be retried against
the secondary snapshots repository as well, since we sometimes
use builds from there.
2023-11-01 00:45:33 +05:30
Harsh Shandilya 25a11bde59 refactor(api): use ApiResult in Shiori and wire into DI 2023-10-31 12:14:00 +05:30
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
62bffc33fd fix(deps): update konvert to v2.4.0 (#408)
[![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) | `2.3.1`
-> `2.4.0` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/io.mcarle:konvert/2.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/io.mcarle:konvert/2.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/io.mcarle:konvert/2.3.1/2.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/io.mcarle:konvert/2.3.1/2.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [io.mcarle:konvert-api](https://togithub.com/mcarleio/konvert) |
`2.3.1` -> `2.4.0` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/io.mcarle:konvert-api/2.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/io.mcarle:konvert-api/2.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/io.mcarle:konvert-api/2.3.1/2.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/io.mcarle:konvert-api/2.3.1/2.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

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

###
[`v2.4.0`](https://togithub.com/mcarleio/konvert/blob/HEAD/CHANGELOG.md#240)

##### New features

- allow functions with multiple parameters in `@Konverter` annotated
interfaces if one is defined as source with `@Konverter.Source`
[#&#8203;28](https://togithub.com/mcarleio/konvert/issues/28)
    ```kotlin
    @&#8203;Konverter
    interface Mapper {
fun toDto(@&#8203;Konverter.Source source: Source, otherProperty: Int):
Target
    }
    data class Source(val property: String)
    data class Target(val property: String, val otherProperty: Int)
    ```

##### Bug fixes:

- ignore private and extension functions in `@Konverter` annotated
interfaces [#&#8203;30](https://togithub.com/mcarleio/konvert/issues/30)

</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:eyJjcmVhdGVkSW5WZXIiOiIzNy4zMS41IiwidXBkYXRlZEluVmVyIjoiMzcuMzEuNSIsInRhcmdldEJyYW5jaCI6Im1haW4ifQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-31 05:58:53 +00:00
Harsh Shandilya 45f384d048 fix(api): boot up a fresh container for each test 2023-10-31 10:56:17 +05:30
Harsh Shandilya b9d92ecefa Revert "Revert "fix(api): title and url are required for bookmark edits""
This reverts commit 2273768556.
2023-10-31 10:45:24 +05:30
renovate[bot]andGitHub 01fd8bee0c fix(deps): update coil to v2.5.0 2023-10-30 23:08:29 +00:00
Harsh Shandilya 9048d253e6 chore: update changelog 2023-10-28 14:30:33 +05:30
renovate[bot]andGitHub 16b660ad0e fix(deps): update dependency com.slack.lint:slack-lint-checks to v0.7.0 2023-10-27 21:15:39 +00:00
Harsh Shandilya 2273768556 Revert "fix(api): title and url are required for bookmark edits"
Seems to still fail in CI????????????????

This reverts commit cce12fb138.
2023-10-28 00:34:07 +05:30
Harsh Shandilya ab01554b82 fix(ci): expand test report glob to cover all modules 2023-10-28 00:13:16 +05:30
Harsh Shandilya cce12fb138 fix(api): title and url are required for bookmark edits 2023-10-27 22:34:07 +05:30
Harsh Shandilya 6f4c1a0fbd fix(build): disable slack-lint DeprecatedCall 2023-10-27 22:34:07 +05:30
Harsh Shandilya 3663ca1ec1 feat(api): add integration tests for Shiori API 2023-10-27 22:34:07 +05:30
Harsh Shandilya f39bf0b043 feat(api/model): initial Shiori API 2023-10-27 22:34:07 +05:30
renovate[bot]andGitHub f55d89b17e chore(deps): update github/codeql-action action to v2.22.5 2023-10-27 10:10:26 +00:00
renovate[bot]andGitHub f6185a3dc5 chore(deps): update agp to v8.3.0-alpha11 2023-10-26 21:44:48 +00:00
renovate[bot]andGitHub be7bd38341 fix(deps): update dependency org.gradle.android.cache-fix:org.gradle.android.cache-fix.gradle.plugin to v3 2023-10-25 19:03:15 +00:00
Harsh Shandilya b3277e2367 chore: refresh module graph 2023-10-25 02:44:46 +05:30
renovate[bot]andGitHub f560bb6fbf chore(deps): update agp to v8.3.0-alpha10 2023-10-24 17:57:12 +00:00
renovate[bot]andGitHub 013138afb8 chore(deps): update github/codeql-action action to v2.22.4 2023-10-20 19:46:38 +00:00
renovate[bot]andGitHub d6d639d059 fix(deps): update dependency org.jsoup:jsoup to v1.16.2 2023-10-20 07:12:22 +00:00
renovate[bot]andGitHub 31b2d66b9b chore(deps): update dependency com.android.test to v8.3.0-alpha09 2023-10-20 01:10:06 +00:00
renovate[bot]andGitHub 4b278d68e6 chore(deps): update agp to v8.3.0-alpha08 2023-10-19 19:04:37 +00:00
renovate[bot]andGitHub e81687bf53 fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v3.14.0 2023-10-18 22:19:40 +00:00
Harsh Shandilya a0ea6d6e1c fix(build): disable GradleDependency lint
I have better ways to deal with this
2023-10-19 01:39:10 +05:30
Harsh Shandilya 17eb9a4c45 chore: refresh lint baselines 2023-10-19 01:38:13 +05:30
Harsh Shandilya 2e8ad58979 Revert "Revert "fix(renovate): disable compose-bom updates""
Still broken :(

This reverts commit 680d43f78c.
2023-10-19 01:31:42 +05:30
Harsh Shandilya 680d43f78c Revert "fix(renovate): disable compose-bom updates"
Let's try this again, shall we?

This reverts commit f844062980.
2023-10-19 01:18:42 +05:30
renovate[bot]andGitHub 0d1fb26b52 fix(deps): update sentry to v6.32.0 2023-10-18 19:37:29 +00:00
renovate[bot]andGitHub 7540430e1d fix(deps): update androidx 2023-10-18 17:45:06 +00:00
renovate[bot]andGitHub 095ec1ed04 chore(deps): update actions/checkout action to v4.1.1 2023-10-17 17:38:42 +00:00
renovate[bot]andGitHub ce62a7b1fc fix(deps): update dependency com.squareup.okhttp3:okhttp-bom to v4.12.0 2023-10-17 04:13:01 +00:00
renovate[bot]andGitHub 4b89775f8a fix(deps): update konvert to v2.3.1 2023-10-15 09:16:58 +00:00
Harsh Shandilya a5af23e330 refactor(android): make ClawViewModel#isPostSaved synchronous
Fixes #403
2023-10-15 01:44:11 +05:30
Harsh Shandilya 46cc0910bf feat(database): enable query logging 2023-10-15 01:44:11 +05:30
renovate[bot]andGitHub 37e0fdb0a7 chore(deps): update github/codeql-action action to v2.22.3 2023-10-13 14:23:08 +00:00
Harsh Shandilya a3e7e76931 fix(deps): update gradle-versions-plugin to 0.49.0 2023-10-13 01:35:32 +05:30
renovate[bot]andGitHub cfdab8ddcd fix(deps): update sentry to v6.31.0 2023-10-12 19:47:10 +00:00
renovate[bot]andGitHub 389924a544 chore(deps): update github/codeql-action action to v2.22.2 2023-10-12 14:10:43 +00:00
renovate[bot]andGitHub b3634a0762 fix(deps): update dependency com.slack.lint:slack-lint-checks to v0.6.1 2023-10-09 21:38:21 +00:00
renovate[bot]andGitHub 985bc1361a chore(deps): update github/codeql-action action to v2.22.1 2023-10-09 12:49:26 +00:00
Harsh Shandilya d3d3a6af09 refactor(android): simplify system bars color setup 2023-10-09 01:32:45 +05:30
renovate[bot]andGitHub 3883160234 chore(deps): update androidx to v1.2.0-rc02 2023-10-06 18:35:29 +00:00
renovate[bot]andGitHub d04ad16aee chore(deps): update github/codeql-action action to v2.22.0 2023-10-06 11:02:51 +00:00
GitHub Actions 0067418ffb feat(release): start next development iteration 2023-10-06 08:51:44 +00:00
GitHub Actions deb900f8f8 feat(release): bump version 2023-10-06 08:47:17 +00:00
Harsh Shandilya e6f6ed75f1 chore: update changelog 2023-10-06 14:03:59 +05:30
Harsh Shandilya 6226c3b0c8 fix(common): prevent crashing when parsing comment trees
Fixes COMPOSE-LOBSTERS-1Z
2023-10-06 13:51:18 +05:30
Harsh Shandilya 1a525d6e5d fix(android): avoid violating sorting contract
Fixes COMPOSE-LOBSTERS-1J
2023-10-06 13:47:58 +05:30
Harsh Shandilya bc213fe3c9 chore: make rgignore common 2023-10-06 12:21:10 +05:30
Harsh Shandilya 731666936c fix(android): prevent crashes when <50 saved posts exist
Fixes COMPOSE-LOBSTERS-25
2023-10-06 12:21:10 +05:30
renovate[bot]andGitHub 67ec7e92b5 fix(deps): update dependency org.jetbrains.kotlinx:kotlinx-collections-immutable to v0.3.6 2023-10-06 01:24:19 +00:00
renovate[bot]andGitHub 40bfcb07b6 chore(deps): update agp to v8.3.0-alpha07 2023-10-05 22:19:05 +00:00
renovate[bot]andGitHub e5f09a7aef chore(deps): update dependency gradle to v8.4 2023-10-04 23:27:29 +00:00
renovate[bot]andGitHub 341bdb0eda fix(deps): update androidx 2023-10-04 17:56:34 +00:00
Harsh ShandilyaandGitHub 27d68f2774 fix(deps): update compose-bom to v2023.11.00-alpha01
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
2023-10-04 23:26:16 +05:30
GitHub Actions d259cd8eb5 feat(release): start next development iteration 2023-10-04 13:34:35 +00:00
GitHub Actions c9e5ded43a feat(release): bump version 2023-10-04 13:30:30 +00:00
renovate[bot]andGitHub a8e03e22bf fix(deps): update dagger to v2.48.1 2023-10-03 22:31:04 +00:00
Harsh Shandilya 7094016e7e chore: update changelog 2023-10-03 23:37:32 +05:30
Harsh Shandilya 1bfa1f3981 Revert "Revert "fix(deps): use Swipe snapshots""
Maybe fixed by new Compose BOM?

This reverts commit 3cf6b22d95.
2023-10-03 22:43:34 +05:30
Harsh Shandilya 655745e049 fix(deps): upgrade to Compose Compiler 1.5.3 2023-10-03 22:43:34 +05:30
Harsh Shandilya f83a22b310 fix(deps): update compose-bom to 2023.10.00-alpha02 2023-10-03 22:43:34 +05:30
Harsh Shandilya 025c24a9d9 feat(android): switch to M3-compatible pull to refresh 2023-10-03 22:43:34 +05:30
renovate[bot]andGitHub c8c3e83e7b chore(deps): update gradle/gradle-build-action digest to 842c587 2023-10-01 01:43:33 +00:00
renovate[bot]andGitHub 7d8327af18 chore(deps): update gradle/gradle-build-action action to v2.9.0 2023-09-29 22:36:21 +00:00
Harsh Shandilya 58bab2f538 refactor(android): DRY 2023-09-30 02:45:43 +05:30
Harsh Shandilya cf93eda6c1 chore: update changelog 2023-09-30 02:43:56 +05:30
Harsh Shandilya a3b7af0f79 feat(android): add separate touch actions for widget title and comment count 2023-09-30 02:43:56 +05:30
Harsh Shandilya 91bf5e5555 fix(android): sort saved posts in view model 2023-09-30 02:03:20 +05:30
Harsh Shandilya 61a72b330d fix(android): rollback widget size to 5x2 2023-09-30 02:03:20 +05:30
Harsh Shandilya 6a1164363c feat(android): fix saved posts widget layout 2023-09-30 02:03:20 +05:30
Harsh Shandilya df9a82482c fix(android): dispatch widget update when toggling save state 2023-09-30 01:51:57 +05:30
Harsh Shandilya 778d98c7f2 fix(android): limit widget to 50 posts 2023-09-30 01:51:57 +05:30
Harsh Shandilya cae759a09e Merge remote-tracking branches 'origin/renovate/sentry' and 'origin/renovate/org.gradle.android.cache-fix-org.gradle.android.cache-fix.gradle.plugin-2.x'
* origin/renovate/sentry:
  fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v3.13.0

* origin/renovate/org.gradle.android.cache-fix-org.gradle.android.cache-fix.gradle.plugin-2.x:
  fix(deps): update dependency org.gradle.android.cache-fix:org.gradle.android.cache-fix.gradle.plugin to v2.8.1
2023-09-29 23:19:35 +05:30
renovate[bot]andGitHub 52126b2248 fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v3.13.0 2023-09-29 17:13:29 +00:00
renovate[bot]andGitHub 5782521824 fix(deps): update dependency org.gradle.android.cache-fix:org.gradle.android.cache-fix.gradle.plugin to v2.8.1 2023-09-29 17:13:26 +00:00
Harsh Shandilya 3522dc529d fix(build): prevent invalid lints failing build 2023-09-29 12:06:52 +05:30
Harsh Shandilya 8602e68b60 Merge remote-tracking branches 'origin/renovate/agp' and 'origin/renovate/com.diffplug.spotless-spotless-plugin-gradle-6.x'
* origin/renovate/agp:
  chore(deps): update dependency com.android.test to v8.3.0-alpha06

* origin/renovate/com.diffplug.spotless-spotless-plugin-gradle-6.x:
  fix(deps): update dependency com.diffplug.spotless:spotless-plugin-gradle to v6.22.0
2023-09-29 11:54:16 +05:30
renovate[bot]andGitHub fb4514d70e fix(deps): update dependency com.diffplug.spotless:spotless-plugin-gradle to v6.22.0 2023-09-29 00:41:48 +00:00
renovate[bot]andGitHub 35d86bff11 chore(deps): update dependency com.android.test to v8.3.0-alpha06 2023-09-28 21:25:13 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
193730e16c fix(deps): update sentry to v6.30.0 (#400)
[![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.sentry:sentry-android-okhttp](https://togithub.com/getsentry/sentry-java)
| `6.29.0` -> `6.30.0` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/io.sentry:sentry-android-okhttp/6.30.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/io.sentry:sentry-android-okhttp/6.30.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/io.sentry:sentry-android-okhttp/6.29.0/6.30.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/io.sentry:sentry-android-okhttp/6.29.0/6.30.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [io.sentry:sentry-bom](https://togithub.com/getsentry/sentry-java) |
`6.29.0` -> `6.30.0` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/io.sentry:sentry-bom/6.30.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/io.sentry:sentry-bom/6.30.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/io.sentry:sentry-bom/6.29.0/6.30.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/io.sentry:sentry-bom/6.29.0/6.30.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

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

###
[`v6.30.0`](https://togithub.com/getsentry/sentry-java/blob/HEAD/CHANGELOG.md#6300)

[Compare
Source](https://togithub.com/getsentry/sentry-java/compare/6.29.0...6.30.0)

##### Features

- Add `sendModules` option for disable sending modules
([#&#8203;2926](https://togithub.com/getsentry/sentry-java/pull/2926))
- Send `db.system` and `db.name` in span data for androidx.sqlite spans
([#&#8203;2928](https://togithub.com/getsentry/sentry-java/pull/2928))
- Check-ins (CRONS) support
([#&#8203;2952](https://togithub.com/getsentry/sentry-java/pull/2952))
- Add API for sending check-ins (CRONS) manually
([#&#8203;2935](https://togithub.com/getsentry/sentry-java/pull/2935))
- Support check-ins (CRONS) for Quartz
([#&#8203;2940](https://togithub.com/getsentry/sentry-java/pull/2940))
- `@SentryCheckIn` annotation and advice config for Spring
([#&#8203;2946](https://togithub.com/getsentry/sentry-java/pull/2946))
- Add option for ignoring certain monitor slugs
([#&#8203;2943](https://togithub.com/getsentry/sentry-java/pull/2943))

##### Fixes

- Always send memory stats for transactions
([#&#8203;2936](https://togithub.com/getsentry/sentry-java/pull/2936))
- This makes it possible to query transactions by the `device.class` tag
on Sentry
- Add `sentry.enable-aot-compatibility` property to SpringBoot Jakarta
`SentryAutoConfiguration` to enable building for GraalVM
([#&#8203;2915](https://togithub.com/getsentry/sentry-java/pull/2915))

##### Dependencies

- Bump Gradle from v8.2.1 to v8.3.0
([#&#8203;2900](https://togithub.com/getsentry/sentry-java/pull/2900))
- \[changelog]\(https://github.com/gradle/gradle/blob/master
release-test/CHANGELOG.md#v830)
- [diff](https://togithub.com/gradle/gradle/compare/v8.2.1...v8.3.0)

</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:eyJjcmVhdGVkSW5WZXIiOiIzNi4xMDcuMiIsInVwZGF0ZWRJblZlciI6IjM2LjEwNy4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiJ9-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-28 09:50:08 +00:00
Harsh Shandilya ed32186984 refactor(build): adopt Slack lints 2023-09-28 15:14:36 +05:30
renovate[bot]andGitHub eb449740eb chore(deps): update github/codeql-action action to v2.21.9 2023-09-27 16:36:31 +00:00
renovate[bot]andGitHub 327fd43dd6 fix(deps): update dependency com.slack.eithernet:eithernet to v1.6.0 2023-09-27 00:12:38 +00:00
renovate[bot]andGitHub bc2345f415 chore(deps): update plugin licensee to v1.8.0 2023-09-26 21:30:38 +00:00
Harsh Shandilya fd1bdc79f8 feat(README): use theme-context for feature graphics 2023-09-26 17:17:45 +05:30
Harsh Shandilya 22a2419185 feat(scripts): generate dark and light feature graphics 2023-09-26 17:16:46 +05:30
Harsh Shandilya de921bf786 chore(scripts): run shfmt 2023-09-26 17:12:02 +05:30
Harsh Shandilya 3cf6b22d95 Revert "fix(deps): use Swipe snapshots"
Somehow breaks R8, don't really care to figure it out.

This reverts commit d77db45f37.
2023-09-26 16:51:54 +05:30
Harsh Shandilya 82ce45b8e4 refactor(database): move SavedPostSerializer to core 2023-09-26 16:37:28 +05:30
Harsh Shandilya dfe95a646e chore: add database refactor to blame ignore revs 2023-09-26 16:36:42 +05:30
Harsh Shandilya 7b0b206905 refactor(database): split out JVM and Android parts 2023-09-26 16:35:50 +05:30
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
1b1984064c chore(deps): update gradle/gradle-build-action action to v2.8.1 (#399)
[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[gradle/gradle-build-action](https://togithub.com/gradle/gradle-build-action)
| action | patch | `v2.8.0` -> `v2.8.1` |

---

### Release Notes

<details>
<summary>gradle/gradle-build-action
(gradle/gradle-build-action)</summary>

###
[`v2.8.1`](https://togithub.com/gradle/gradle-build-action/compare/v2.8.0...v2.8.1)

[Compare
Source](https://togithub.com/gradle/gradle-build-action/compare/v2.8.0...v2.8.1)

</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:eyJjcmVhdGVkSW5WZXIiOiIzNi45Ny4xIiwidXBkYXRlZEluVmVyIjoiMzYuOTcuMSIsInRhcmdldEJyYW5jaCI6Im1haW4ifQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-25 19:09:16 +00:00
renovate[bot]andGitHub 7e03e2571f chore(deps): update dependency dev.msfjarvis.whetstone to v0.8.0 2023-09-23 19:28:34 +00:00
renovate[bot]andGitHub 23f0b32634 chore(deps): update actions/checkout action to v4.1.0 2023-09-23 05:22:37 +00:00
Harsh Shandilya f844062980 fix(renovate): disable compose-bom updates
The current alphas have a regression causing crashes when
expanding/collapsing comments.
2023-09-23 10:49:10 +05:30
Harsh Shandilya 84f982b20e Revert "fix(deps): update dependency dev.chrisbanes.compose:compose-bom to v2023.10.00-alpha02"
Temporary test to see if it fixes the crashes I've seen

This reverts commit 45c7dbb3f7.
2023-09-23 10:33:47 +05:30
Harsh Shandilya c6739487a4 fix(android): update widget with a more usable UI 2023-09-23 01:19:18 +05:30
Harsh Shandilya be0f46371e fix(android): update widget when saved post update worker runs 2023-09-23 01:19:18 +05:30
Harsh Shandilya 85351d5b1c fix(android): increase widget size to 5x3 2023-09-23 01:15:41 +05:30
Harsh Shandilya 23366e4536 feat(android): navigate to a specific comments page via intent 2023-09-23 01:15:02 +05:30
Harsh Shandilya 0d1f69d36e refactor(android): expose 'plain' SavedPosts list in VM 2023-09-23 01:14:06 +05:30
Harsh Shandilya d77db45f37 fix(deps): use Swipe snapshots
Fixes COMPOSE-LOBSTERS-1N
2023-09-22 10:11:43 +05:30
renovate[bot]andGitHub 20c5b6c4a5 chore(deps): update agp to v8.3.0-alpha05 2023-09-21 22:30:07 +00:00
Harsh Shandilya 62f512e319 feat(android): basic widget implementation
This needs a Glance version of LobstersCard to function
2023-09-22 00:58:49 +05:30
Harsh Shandilya e740ddb7cc fix(build): recognize appwidget-provider as a valid XML tag 2023-09-22 00:58:07 +05:30
Harsh Shandilya 68924d6f35 chore(gradle): add androix.glance dependencies 2023-09-22 00:58:01 +05:30
Harsh Shandilya 94db512518 fix(deps): upgrade to Whetstone 0.8.0-SNAPSHOT
For BroadcastReceiver injection support
2023-09-22 00:58:01 +05:30
Harsh Shandilya f39e9889f3 feat(android): delete unused DataStore code 2023-09-21 16:11:45 +05:30
Harsh Shandilya b0ffbcd07d refactor(build): fix debug builds and groups to collection tasks 2023-09-21 13:57:12 +05:30
Harsh Shandilya 19412cc963 fix(renovate): also group Compose BOM updates with AndroidX 2023-09-21 00:45:31 +05:30
renovate[bot]andGitHub 45c7dbb3f7 fix(deps): update dependency dev.chrisbanes.compose:compose-bom to v2023.10.00-alpha02 2023-09-20 18:50:55 +00:00
renovate[bot]andGitHub 3b0d0e55dc fix(deps): update androidx 2023-09-20 18:20:45 +00:00
renovate[bot]andGitHub ae21ebe80d chore(deps): update actions/setup-java action to v3.13.0 2023-09-20 12:36:56 +00:00
Harsh Shandilya cca14765ce chore(README): refresh module graph graphic 2023-09-20 09:05:52 +05:30
renovate[bot]andGitHub 725718d18c fix(deps): update konvert to v2.3.0 2023-09-20 01:27:39 +00:00
renovate[bot]andHarsh Shandilya 61b000dc48 chore(deps): update dependency com.facebook:ktfmt to v0.46 2023-09-20 00:04:33 +05:30
renovate[bot]andGitHub cf850071e5 chore(deps): update github/codeql-action action to v2.21.8 2023-09-19 11:24:32 +00:00
Harsh Shandilya fe473e70a3 fix(ci): adjust CodeQL build task 2023-09-19 16:54:06 +05:30
Harsh Shandilya ce6540a6f3 fix(scripts): allow running on Darwin hosts 2023-09-19 16:06:56 +05:30
GitHub Actions ac3e60053a feat(release): start next development iteration 2023-09-19 09:54:05 +00:00
141 changed files with 51670 additions and 2826 deletions
+3
View File
@@ -6,3 +6,6 @@ cbdfa67d075ec1f8ceec0f3559498cbca01783a0
# Renamed Renovate config file
f304304bc5b64edf189aaa473b05df912067a8bd
# Refactored database module into core and impl
7b0b206905b8dc1f824a2d2a10e3d94c6b9dd11c
Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 KiB

+3 -1
View File
@@ -22,6 +22,7 @@
{
"matchPackageNames": [
"androidx.compose.compiler:compiler",
"com.google.devtools.ksp",
"dev.drewhamilton.poko",
"org.jetbrains.kotlin:kotlin-gradle-plugin",
"org.jetbrains.kotlin.plugin.serialization",
@@ -32,6 +33,8 @@
"matchPackagePatterns": [
"^androidx.*",
],
"matchPackageNames": [
],
"groupName": "androidx"
},
{
@@ -43,7 +46,6 @@
},
{
"matchPackageNames": [
"com.android.tools:r8",
"com.squareup.anvil",
],
"enabled": false,
+22 -9
View File
@@ -18,18 +18,18 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
fetch-depth: 0
- name: Set up JDK
uses: actions/setup-java@cd89f46ac9d01407894225f350157564c9c7cee2 # v3.12.0
uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0
with:
distribution: temurin
java-version: 18
- name: Run unit tests
uses: gradle/gradle-build-action@ef76a971e2fa3f867b617efd72f2fbd72cf6f8bc # v2.8.0
uses: gradle/gradle-build-action@842c587ad8aa4c68eeba24c396e15af4c2e9f30a # v2.9.0
with:
arguments: --no-configuration-cache --stacktrace check -PslimTests
gradle-home-cache-cleanup: true
@@ -41,7 +41,7 @@ jobs:
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with:
name: Test report
path: android/build/reports
path: '**/build/reports/tests/**'
dependency-review:
runs-on: ubuntu-latest
@@ -49,26 +49,39 @@ jobs:
- check
steps:
- name: Checkout repository
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Dependency Review
uses: actions/dependency-review-action@6c5ccdad469c9f8a2996bfecaec55a631a347034 # v3
uses: actions/dependency-review-action@7bbfa034e752445ea40215fff1c3bf9597993d3f # v3
with:
base-ref: refs/heads/main
head-ref: ${{ github.ref }}
validate-changelog:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Check changelog format
uses: mindsers/changelog-reader-action@b97ce03a10d9bdbb07beb491c76a5a01d78cd3ef # v2.2.2
with:
path: ./CHANGELOG.md
validation_level: error
deploy-release-snapshot:
runs-on: ubuntu-latest
if: "github.event_name == 'push' && github.event.ref == 'refs/heads/main'"
needs:
- check
- validate-changelog
- dependency-review
steps:
- name: Checkout repository
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Set up JDK
uses: actions/setup-java@cd89f46ac9d01407894225f350157564c9c7cee2 # v3.12.0
uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0
with:
distribution: temurin
java-version: 18
@@ -81,7 +94,7 @@ jobs:
AGE_SECRET_KEY: ${{ secrets.AGE_SECRET_KEY }}
- name: Build release app
uses: gradle/gradle-build-action@ef76a971e2fa3f867b617efd72f2fbd72cf6f8bc # v2.8.0
uses: gradle/gradle-build-action@842c587ad8aa4c68eeba24c396e15af4c2e9f30a # v2.9.0
env:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
with:
@@ -0,0 +1,87 @@
name: Code quality analysis
on:
push:
branches:
- main
- renovate/**
schedule:
- cron: '31 7 * * 3'
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
permissions:
contents: read
security-events: write
jobs:
codeql:
name: CodeQL
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Set up JDK
uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0
with:
distribution: temurin
java-version: 18
- name: Setup Gradle caching
uses: gradle/gradle-build-action@842c587ad8aa4c68eeba24c396e15af4c2e9f30a # v2.9.0
with:
gradle-home-cache-cleanup: true
cache-read-only: true
- name: Initialize CodeQL
uses: github/codeql-action/init@66b90a5db151a8042fa97405c6cf843bbe433f7b # v2.22.7
with:
languages: java
tools: latest
queries: +security-extended
- name: Build project
shell: bash
run: |
./gradlew assembleDebug assembleRelease
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@66b90a5db151a8042fa97405c6cf843bbe433f7b # v2.22.7
with:
category: "/language:java"
mobsfscan:
name: MobSF
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Set up JDK
uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0
with:
distribution: temurin
java-version: 18
- name: Setup Gradle caching
uses: gradle/gradle-build-action@842c587ad8aa4c68eeba24c396e15af4c2e9f30a # v2.9.0
with:
gradle-home-cache-cleanup: true
cache-read-only: true
- name: Setup python
uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4
with:
python-version: 3.8
- name: Run mobsfscan
uses: MobSF/mobsfscan@b8503e0027d134f55cc3306582efbbbeaa96c7ba
with:
args: . --sarif --output results.sarif || true
- name: Upload mobsfscan report
uses: github/codeql-action/upload-sarif@66b90a5db151a8042fa97405c6cf843bbe433f7b # v2
with:
sarif_file: results.sarif
-53
View File
@@ -1,53 +0,0 @@
name: CodeQL
on:
push:
branches:
- renovate/**
workflow_dispatch:
schedule:
- cron: '31 7 * * 3'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
- name: Set up JDK
uses: actions/setup-java@cd89f46ac9d01407894225f350157564c9c7cee2 # v3.12.0
with:
distribution: temurin
java-version: 18
- name: Setup Gradle caching
uses: gradle/gradle-build-action@ef76a971e2fa3f867b617efd72f2fbd72cf6f8bc # v2.8.0
with:
gradle-home-cache-cleanup: true
cache-read-only: true
- name: Initialize CodeQL
uses: github/codeql-action/init@04daf014b50eaf774287bf3f0f1869d4b4c4b913 # v2.21.7
with:
languages: java
tools: latest
queries: +security-extended
- name: Build project
shell: bash
run: |
./gradlew assemble -PslimTests
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@04daf014b50eaf774287bf3f0f1869d4b4c4b913 # v2.21.7
with:
category: "/language:java"
+3 -3
View File
@@ -16,13 +16,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
token: ${{ secrets.POST_RELEASE_GH_TOKEN }}
fetch-depth: 0
- name: Set up JDK
uses: actions/setup-java@cd89f46ac9d01407894225f350157564c9c7cee2 # v3.12.0
uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0
with:
distribution: temurin
java-version: 18
@@ -34,7 +34,7 @@ jobs:
git config user.email noreply@github.com
- name: Setup Gradle caching
uses: gradle/gradle-build-action@ef76a971e2fa3f867b617efd72f2fbd72cf6f8bc # v2.8.0
uses: gradle/gradle-build-action@842c587ad8aa4c68eeba24c396e15af4c2e9f30a # v2.9.0
with:
gradle-home-cache-cleanup: true
cache-read-only: true
+1
View File
@@ -1,3 +1,4 @@
*.json
baseline-prof.txt
startup-prof.txt
**/test/resources/**
+40 -1
View File
@@ -7,6 +7,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [1.38.0] - 2023-11-20
### Changed
* Add brand new icon by dzuk
* Fix a bug that caused the app to fetch the same 20 posts over and over
* Significantly improve scrolling performance when there are a lot of saved posts
* Move search feature to its own screen
* Make top app and bottom system bars use the same color
* Reduce unnecessary API calls in search screen
* Upgrade to Compose November releases
* Enable logging of SQLite queries
## [1.37.0] - 2023-10-06
### Changed
* Fixed a crash that infrequently happened in the comments page
* Fixed a crash when trying to list saved posts
* Fixed a crash in home screen widget when user had less than 50 saved posts
* Upgraded to Compose 1.6.0-alpha07
## [1.36.0] - 2023-10-04
### Added
* Introduce an initial attempt at a home screen widget
### Changed
* Fixed a crash triggered when swiping items
* Adjusted pull to refresh component to match Material You theme
## [1.35.0] - 2023-09-19
### Changed
@@ -301,7 +334,13 @@ 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.35.0...HEAD
[Unreleased]: https://github.com/msfjarvis/compose-lobsters/compare/v1.38.0...HEAD
[1.38.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.37.0...v1.38.0
[1.37.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.36.0...v1.37.0
[1.36.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.35.0...v1.36.0
[1.35.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.34.0...v1.35.0
+4 -24
View File
@@ -10,34 +10,14 @@ Unofficial Android app for read-only access to [lobste.rs](https://lobste.rs), b
> Snapshots from the development branch can be obtained [here](https://github.com/msfjarvis/compose-lobsters/releases/tag/nightly).
<img src="https://github.com/msfjarvis/compose-lobsters/blob/main/.github/readme_feature.webp"
<img src="https://github.com/msfjarvis/compose-lobsters/blob/main/.github/readme_feature_light.webp#gh-light-mode-only"
alt="A grid of screenshots from the app, in clockwise order: the main screen, the comments page, the search page and the saved posts page"
width="550" />
## Dependency Diagram
<img src="https://github.com/msfjarvis/compose-lobsters/blob/main/.github/readme_feature_dark.webp#gh-dark-mode-only"
alt="A grid of screenshots from the app, in clockwise order: the main screen, the comments page, the search page and the saved posts page"
width="550" />
```mermaid
%%{
init: {
'theme': 'dark'
}
}%%
graph TB
benchmark --> android
android --> api
android --> common
android --> core
android --> database
android --> model
android --> benchmark
api --> model
common --> core
common --> database
common --> model
```
## License
See [LICENSE](LICENSE)
+7 -3
View File
@@ -46,6 +46,9 @@ licensee {
allow("MIT")
ignoreDependencies("org.commonmark") { because("Commonmark is BSD licensed") }
allowUrl("https://jsoup.org/license") { because("Jsoup is MIT licensed") }
allowUrl("https://opensource.org/licenses/isc-license.txt") {
because("kotlin-result is ISC licensed")
}
}
moduleGraphAssert {
@@ -65,12 +68,12 @@ dependencies {
implementation(platform(libs.androidx.compose.bom))
implementation(platform(libs.okhttp.bom))
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.compose.material)
implementation(libs.androidx.compose.glance)
implementation(libs.androidx.compose.glance.m3)
implementation(libs.androidx.compose.material.icons.extended)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.material3.window.size)
implementation(libs.androidx.core.splashscreen)
implementation(libs.androidx.datastore.preferences)
implementation(libs.androidx.lifecycle.compose)
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.paging.compose)
@@ -82,6 +85,7 @@ dependencies {
implementation(libs.jsoup)
implementation(libs.kotlinx.collections.immutable)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.material3.pulltorefresh)
implementation(libs.napier)
implementation(libs.sqldelight.extensions.coroutines)
implementation(libs.swipe)
@@ -89,7 +93,7 @@ dependencies {
implementation(projects.api)
implementation(projects.common)
implementation(projects.core)
implementation(projects.database)
implementation(projects.database.impl)
implementation(projects.model)
kapt(libs.dagger.compiler)
+12 -1
View File
@@ -1,4 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.3.0-alpha02" type="baseline" client="gradle" dependencies="true" name="AGP (8.3.0-alpha02)" variant="all" version="8.3.0-alpha02">
<issues format="6" by="lint 8.3.0-alpha14" type="baseline" client="gradle" dependencies="true" name="AGP (8.3.0-alpha14)" variant="all" version="8.3.0-alpha14">
<issue
id="VectorPath"
message="Very long vector path (918 characters), which is bad for performance. Considering reducing precision, removing minor details or rasterizing vector."
errorLine1=" android:pathData=&quot;M431.3,341.2C435.9,337 580.3,236 710.5,223.8C747.3,220.3 790.7,254.7 833.2,312.5C875.9,370.7 892.9,433.4 875.9,443C863.2,450.2 838.6,407.4 758.8,396.3C743.4,394.2 730.4,393.4 719.4,393.5C738.5,423.4 745.6,451.1 737.2,458C733.1,461.4 673.6,438.7 612.9,436.5C570.1,435 535.2,440.9 506.7,451.3C496.9,454.9 486,453.8 477.2,448.3C444.5,427.9 424.2,406.2 418.4,376.2C415.8,362.8 420.9,349.4 431.3,341.2ZM274.7,394.8C285.8,370.1 312.2,325.9 346.3,305.4C383.6,282.9 335,390.3 427.3,489.8C519.5,589.3 586.5,623.8 655.1,581.5C723.7,539.2 746.5,518.1 753,518.7C764,519.6 772.9,536.1 764.4,589.5C763,598.2 761.3,606.5 759.3,614.2C828.7,575 871.7,516.3 889,520C912,524.8 918.3,617.8 852.1,721C811.8,784 768.8,830.7 741.4,857.5C724.3,874 699.7,880 677,873.1C512,822.6 315.5,733.8 202.9,652C193.8,645.2 186.8,635.9 183,625.1C171.8,593.7 161.3,548.1 168.5,495.3C179.5,414.2 222.7,352.9 245.2,335.9C261.9,323.2 267.1,362.4 274.7,394.8Z&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/drawable/ic_launcher_foreground.xml"
line="17"
column="27"/>
</issue>
</issues>
+31 -9
View File
@@ -132,6 +132,17 @@
<data android:scheme="https" />
</intent-filter>
</activity>
<activity
android:name=".SearchActivity"
android:configChanges="colorMode|density|fontScale|keyboard|keyboardHidden|layoutDirection|locale|mcc|mnc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|touchscreen|uiMode"
android:exported="true"
android:label="Claw Search"
android:theme="@style/Theme.Claw">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
@@ -146,23 +157,34 @@
android:value="androidx.startup"
tools:node="remove" />
</provider>
<!-- https://issuetracker.google.com/issues/258619948 -->
<receiver
android:name="androidx.profileinstaller.ProfileInstallReceiver"
android:permission="android.permission.DUMP"
android:name=".glance.WidgetReceiver"
android:exported="true">
<intent-filter>
<action android:name="androidx.profileinstaller.action.BENCHMARK_OPERATION" />
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/saved_posts_widget_info" />
</receiver>
<!-- Required: set your sentry.io project identifier (DSN) -->
<meta-data android:name="io.sentry.dsn" android:value="${sentryDsn}" />
<meta-data
android:name="io.sentry.dsn"
android:value="${sentryDsn}" />
<!-- enable automatic breadcrumbs for user interactions (clicks, swipes, scrolls) -->
<meta-data android:name="io.sentry.traces.user-interaction.enable" android:value="${enableSentry}" />
<meta-data android:name="io.sentry.breadcrumbs.user-interaction" android:value="${enableSentry}" />
<meta-data
android:name="io.sentry.traces.user-interaction.enable"
android:value="${enableSentry}" />
<meta-data
android:name="io.sentry.breadcrumbs.user-interaction"
android:value="${enableSentry}" />
<!-- enable screenshot for crashes -->
<meta-data android:name="io.sentry.attach-screenshot" android:value="${enableSentry}" />
<meta-data
android:name="io.sentry.attach-screenshot"
android:value="${enableSentry}" />
<!-- enable view hierarchy for crashes -->
<meta-data android:name="io.sentry.attach-view-hierarchy" android:value="${enableSentry}" />
<meta-data
android:name="io.sentry.attach-view-hierarchy"
android:value="${enableSentry}" />
</application>
</manifest>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

@@ -7,9 +7,11 @@
package dev.msfjarvis.claw.android
import android.app.assist.AssistContent
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
@@ -18,7 +20,7 @@ import androidx.compose.ui.platform.LocalUriHandler
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import com.deliveryhero.whetstone.Whetstone
import com.deliveryhero.whetstone.activity.ContributesActivityInjector
import dev.msfjarvis.claw.android.ui.LobstersApp
import dev.msfjarvis.claw.android.ui.screens.LobstersPostsScreen
import dev.msfjarvis.claw.common.comments.HTMLConverter
import dev.msfjarvis.claw.common.theme.LobstersTheme
import dev.msfjarvis.claw.common.urllauncher.UrlLauncher
@@ -36,14 +38,25 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
installSplashScreen()
Whetstone.inject(this)
enableEdgeToEdge()
enableEdgeToEdge(
statusBarStyle =
SystemBarStyle.light(
Color.TRANSPARENT,
Color.TRANSPARENT,
),
navigationBarStyle =
SystemBarStyle.light(
Color.TRANSPARENT,
Color.TRANSPARENT,
),
)
setContent {
val windowSizeClass = calculateWindowSizeClass(this)
LobstersTheme(
dynamicColor = true,
providedValues = arrayOf(LocalUriHandler provides urlLauncher),
) {
LobstersApp(
LobstersPostsScreen(
urlLauncher = urlLauncher,
htmlConverter = htmlConverter,
windowSizeClass = windowSizeClass,
@@ -63,4 +76,8 @@ class MainActivity : ComponentActivity() {
}
}
}
companion object {
const val NAVIGATION_KEY = "postId"
}
}
@@ -0,0 +1,70 @@
/*
* Copyright © 2021-2023 Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android
import android.app.assist.AssistContent
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.ui.platform.LocalUriHandler
import com.deliveryhero.whetstone.Whetstone
import com.deliveryhero.whetstone.activity.ContributesActivityInjector
import dev.msfjarvis.claw.android.ui.screens.SearchScreen
import dev.msfjarvis.claw.android.viewmodel.ClawViewModel
import dev.msfjarvis.claw.common.comments.HTMLConverter
import dev.msfjarvis.claw.common.theme.LobstersTheme
import dev.msfjarvis.claw.common.urllauncher.UrlLauncher
import javax.inject.Inject
@ContributesActivityInjector
class SearchActivity : ComponentActivity() {
@Inject lateinit var urlLauncher: UrlLauncher
@Inject lateinit var htmlConverter: HTMLConverter
@Inject lateinit var viewModel: ClawViewModel
private var webUri: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Whetstone.inject(this)
enableEdgeToEdge(
statusBarStyle =
SystemBarStyle.light(
Color.TRANSPARENT,
Color.TRANSPARENT,
),
navigationBarStyle =
SystemBarStyle.light(
Color.TRANSPARENT,
Color.TRANSPARENT,
),
)
setContent {
LobstersTheme(
dynamicColor = true,
providedValues = arrayOf(LocalUriHandler provides urlLauncher),
) {
SearchScreen(urlLauncher, htmlConverter, { webUri = it })
}
}
}
override fun onProvideAssistContent(outContent: AssistContent?) {
super.onProvideAssistContent(outContent)
if (outContent != null) {
if (webUri != null) {
outContent.webUri = Uri.parse(webUri)
} else {
outContent.webUri = null
}
}
}
}
@@ -0,0 +1,73 @@
/*
* Copyright © 2023 Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android.glance
import android.content.Context
import android.os.Build
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.dp
import androidx.glance.GlanceId
import androidx.glance.GlanceModifier
import androidx.glance.GlanceTheme
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.appWidgetBackground
import androidx.glance.appwidget.lazy.LazyColumn
import androidx.glance.appwidget.lazy.items
import androidx.glance.appwidget.provideContent
import androidx.glance.background
import androidx.glance.layout.Alignment
import androidx.glance.layout.fillMaxSize
import androidx.glance.layout.padding
import androidx.glance.material3.ColorProviders
import androidx.glance.text.Text
import androidx.glance.text.TextStyle
import dev.msfjarvis.claw.common.theme.DarkThemeColors
import dev.msfjarvis.claw.common.theme.LightThemeColors
import dev.msfjarvis.claw.database.local.SavedPost
class SavedPostsWidget(
private val posts: List<SavedPost>,
) : GlanceAppWidget() {
override suspend fun provideGlance(context: Context, id: GlanceId) {
provideContent {
GlanceTheme(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) GlanceTheme.colors
else ColorProviders(light = LightThemeColors, dark = DarkThemeColors)
) {
WidgetHost()
}
}
}
@Composable
private fun WidgetHost() {
LazyColumn(
modifier =
GlanceModifier.fillMaxSize()
.background(GlanceTheme.colors.background)
.appWidgetBackground(),
horizontalAlignment = Alignment.CenterHorizontally
) {
item {
val style = MaterialTheme.typography.titleLarge
Text(
text = "Saved posts",
style =
TextStyle(
color = GlanceTheme.colors.onBackground,
fontSize = style.fontSize,
fontWeight = style.fontWeight.toGlance(),
fontStyle = style.fontStyle.toGlance(),
),
modifier = GlanceModifier.padding(vertical = 8.dp),
)
}
items(posts) { post -> WidgetListEntry(post = post) }
}
}
}
@@ -0,0 +1,89 @@
/*
* Copyright © 2023 Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android.glance
import android.content.Intent
import android.net.Uri
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.dp
import androidx.glance.GlanceComposable
import androidx.glance.GlanceModifier
import androidx.glance.GlanceTheme
import androidx.glance.action.ActionParameters.Key
import androidx.glance.action.actionParametersOf
import androidx.glance.action.actionStartActivity
import androidx.glance.action.clickable
import androidx.glance.appwidget.action.actionStartActivity
import androidx.glance.appwidget.cornerRadius
import androidx.glance.background
import androidx.glance.layout.Alignment
import androidx.glance.layout.Box
import androidx.glance.layout.Row
import androidx.glance.layout.fillMaxWidth
import androidx.glance.layout.padding
import androidx.glance.layout.wrapContentWidth
import androidx.glance.text.Text
import androidx.glance.text.TextAlign
import androidx.glance.text.TextStyle
import dev.msfjarvis.claw.android.MainActivity
import dev.msfjarvis.claw.android.MainActivity.Companion.NAVIGATION_KEY
import dev.msfjarvis.claw.database.local.SavedPost
private val destinationKey = Key<String>(NAVIGATION_KEY)
@Composable
@GlanceComposable
fun WidgetListEntry(
post: SavedPost,
modifier: GlanceModifier = GlanceModifier,
) {
val titleStyle = MaterialTheme.typography.titleMedium
val subtitleStyle = MaterialTheme.typography.labelLarge
val commentsAction =
actionStartActivity<MainActivity>(actionParametersOf(destinationKey to post.shortId))
val postAction =
if (post.url.isNotEmpty()) actionStartActivity(Intent(Intent.ACTION_VIEW, Uri.parse(post.url)))
else commentsAction
Box(modifier.padding(8.dp)) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier =
GlanceModifier.fillMaxWidth()
.background(GlanceTheme.colors.surfaceVariant)
.cornerRadius(16.dp)
.padding(8.dp),
) {
Text(
text = post.title,
modifier = GlanceModifier.defaultWeight().padding(start = 4.dp).clickable(postAction),
style =
TextStyle(
color = GlanceTheme.colors.onSurfaceVariant,
fontSize = titleStyle.fontSize,
fontWeight = titleStyle.fontWeight.toGlance(),
fontStyle = titleStyle.fontStyle.toGlance(),
)
)
post.commentCount?.let { count ->
Text(
text = "$count comments",
modifier =
GlanceModifier.wrapContentWidth().padding(end = 4.dp).clickable(commentsAction),
style =
TextStyle(
color = GlanceTheme.colors.onSurfaceVariant,
fontSize = subtitleStyle.fontSize,
fontWeight = subtitleStyle.fontWeight.toGlance(),
fontStyle = subtitleStyle.fontStyle.toGlance(),
textAlign = TextAlign.End,
)
)
}
}
}
}
@@ -0,0 +1,32 @@
/*
* Copyright © 2023 Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android.glance
import android.content.Context
import android.content.Intent
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.GlanceAppWidgetReceiver
import com.deliveryhero.whetstone.Whetstone
import com.deliveryhero.whetstone.broadcastreceiver.ContributesBroadcastReceiverInjector
import dev.msfjarvis.claw.android.viewmodel.ClawViewModel
import javax.inject.Inject
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
@ContributesBroadcastReceiverInjector
class WidgetReceiver : GlanceAppWidgetReceiver() {
@Inject lateinit var viewModel: ClawViewModel
override val glanceAppWidget: GlanceAppWidget
get() = SavedPostsWidget(runBlocking { viewModel.savedPosts.first().take(50) })
override fun onReceive(context: Context, intent: Intent) {
Whetstone.inject(this, context)
super.onReceive(context, intent)
}
}
@@ -0,0 +1,27 @@
/*
* Copyright © 2023 Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android.glance
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
fun FontWeight?.toGlance(): androidx.glance.text.FontWeight? {
return when (this) {
FontWeight.Normal -> androidx.glance.text.FontWeight.Normal
FontWeight.Medium -> androidx.glance.text.FontWeight.Medium
FontWeight.Bold -> androidx.glance.text.FontWeight.Bold
else -> null
}
}
fun FontStyle?.toGlance(): androidx.glance.text.FontStyle? {
return when (this) {
FontStyle.Normal -> androidx.glance.text.FontStyle.Normal
FontStyle.Italic -> androidx.glance.text.FontStyle.Italic
else -> null
}
}
@@ -1,42 +0,0 @@
/*
* Copyright © 2023 Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android.injection
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStoreFile
import com.deliveryhero.whetstone.app.ApplicationScope
import com.squareup.anvil.annotations.ContributesTo
import com.squareup.anvil.annotations.optional.ForScope
import dagger.Module
import dagger.Provides
import dev.msfjarvis.claw.core.coroutines.DispatcherProvider
import javax.inject.Singleton
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
@Module
@ContributesTo(ApplicationScope::class)
class DataStoreModule {
@Singleton
@Provides
fun provideLoginDatastore(
@ForScope(ApplicationScope::class) context: Context,
dispatcherProvider: DispatcherProvider,
): DataStore<Preferences> {
return PreferenceDataStoreFactory.create(
corruptionHandler = null,
migrations = emptyList(),
scope = CoroutineScope(dispatcherProvider.io() + SupervisorJob()),
) {
context.preferencesDataStoreFile("login")
}
}
}
@@ -14,6 +14,7 @@ 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.api.LobstersSearchApi
import dev.msfjarvis.claw.core.injection.IODispatcher
import dev.msfjarvis.claw.model.LobstersPost
import java.io.IOException
@@ -21,15 +22,16 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
/**
* Duplicate of [LobstersPagingSource] with an additional optimization to not continue loading
* further pages if nothing is found in the current one. This is required to prevent the app from
* hammering the search endpoint with up to 70 consecutive requests for the search results of a
* blank query.
* Specialized variant of [LobstersPagingSource] to prevent hammering the Lobsters search endpoint
* with unnecessary requests. Rather than abstract out the API call this paging source takes in the
* relevant parameters to short-circuit when there is no query specified in order to avoid calling
* the API at all.
*/
class SearchPagingSource
@AssistedInject
constructor(
@Assisted private val remoteFetcher: RemoteFetcher<LobstersPost>,
private val searchApi: LobstersSearchApi,
@Assisted private val queryProvider: () -> String,
@IODispatcher private val ioDispatcher: CoroutineDispatcher,
) : PagingSource<Int, LobstersPost>() {
override fun getRefreshKey(state: PagingState<Int, LobstersPost>): Int? {
@@ -39,8 +41,14 @@ constructor(
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, LobstersPost> {
val searchQuery = queryProvider()
// If there is no query, we don't need to call the API at all.
if (searchQuery.isEmpty()) {
return LoadResult.Page(itemsBefore = 0, data = emptyList(), prevKey = null, nextKey = null)
}
val page = params.key ?: STARTING_PAGE_INDEX
return when (val result = withContext(ioDispatcher) { remoteFetcher.getItemsAtPage(page) }) {
val result = withContext(ioDispatcher) { searchApi.searchPosts(searchQuery, page) }
return when (result) {
is ApiResult.Success -> {
// Optimization: prevent fetching more pages if we found no items, this means
// there is no active search query.
@@ -62,6 +70,6 @@ constructor(
@AssistedFactory
interface Factory {
fun create(remoteFetcher: RemoteFetcher<LobstersPost>): SearchPagingSource
fun create(queryProvider: () -> String): SearchPagingSource
}
}
@@ -1,23 +0,0 @@
/*
* Copyright © 2023 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.settings
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.stringPreferencesKey
import javax.inject.Inject
class LoginSettings
@Inject
constructor(
val dataStore: DataStore<Preferences>,
) {
companion object {
val CSRF_TOKEN = stringPreferencesKey("csrf_token")
val LOGIN_COOKIE = stringPreferencesKey("login_cookie")
}
}
@@ -8,6 +8,7 @@ package dev.msfjarvis.claw.android.ui.decorations
import androidx.compose.foundation.layout.RowScope
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
@@ -19,6 +20,7 @@ import androidx.compose.ui.unit.dp
import dev.msfjarvis.claw.common.theme.LobstersTheme
import dev.msfjarvis.claw.common.ui.preview.DevicePreviews
import dev.msfjarvis.claw.common.ui.preview.ThemePreviews
import dev.msfjarvis.claw.common.ui.surfaceColorAtNavigationBarElevation
@Composable
@OptIn(ExperimentalMaterial3Api::class)
@@ -31,7 +33,10 @@ fun ClawAppBar(
TopAppBar(
title = title,
modifier = modifier.shadow(8.dp),
colors = TopAppBarDefaults.topAppBarColors(),
colors =
TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.surfaceColorAtNavigationBarElevation()
),
navigationIcon = navigationIcon,
actions = actions,
)
@@ -59,7 +59,7 @@ fun DatabasePosts(
key = { it.shortId },
contentType = { "LobstersItem" },
) { item ->
ListItem(
LobstersListItem(
item = item,
isSaved = { true },
isRead = { false },
@@ -21,14 +21,13 @@ import me.saket.swipe.SwipeAction
import me.saket.swipe.SwipeableActionsBox
@Composable
fun ListItem(
fun LobstersListItem(
item: SavedPost,
isSaved: suspend (SavedPost) -> Boolean,
isSaved: (SavedPost) -> Boolean,
isRead: suspend (String) -> Boolean,
postActions: PostActions,
modifier: Modifier = Modifier,
) {
val saved by produceState(false, item) { value = isSaved(item) }
val read by produceState(false, item.shortId) { value = isRead(item.shortId) }
val commentsAction =
SwipeAction(
@@ -41,7 +40,7 @@ fun ListItem(
) {
LobstersCard(
post = item,
isSaved = saved,
isSaved = isSaved(item),
isRead = read,
postActions = postActions,
modifier = modifier,
@@ -14,13 +14,8 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -35,13 +30,16 @@ import dev.msfjarvis.claw.common.ui.ProgressBar
import dev.msfjarvis.claw.database.local.SavedPost
import dev.msfjarvis.claw.model.LobstersPost
import dev.msfjarvis.claw.model.toSavedPost
import eu.bambooapps.material3.pullrefresh.PullRefreshIndicator
import eu.bambooapps.material3.pullrefresh.pullRefresh
import eu.bambooapps.material3.pullrefresh.rememberPullRefreshState
@OptIn(ExperimentalMaterialApi::class)
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NetworkPosts(
lazyPagingItems: LazyPagingItems<LobstersPost>,
listState: LazyListState,
isPostSaved: suspend (SavedPost) -> Boolean,
isPostSaved: (SavedPost) -> Boolean,
isPostRead: suspend (String) -> Boolean,
postActions: PostActions,
modifier: Modifier = Modifier,
@@ -69,7 +67,7 @@ fun NetworkPosts(
val item = lazyPagingItems[index]
if (item != null) {
val dbModel = item.toSavedPost()
ListItem(
LobstersListItem(
item = dbModel,
isSaved = isPostSaved,
isRead = isPostRead,
@@ -95,8 +93,7 @@ fun NetworkPosts(
refreshing = isRefreshing,
state = pullRefreshState,
modifier = Modifier.align(Alignment.TopCenter),
backgroundColor = MaterialTheme.colorScheme.surface,
contentColor = contentColorFor(MaterialTheme.colorScheme.surface),
shadowElevation = 6.dp, // From M2 implementation
)
}
}
@@ -6,25 +6,11 @@
*/
package dev.msfjarvis.claw.android.ui.lists
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.SearchOff
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
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.testTag
import androidx.compose.ui.semantics.isTraversalGroup
@@ -43,7 +29,7 @@ import kotlinx.coroutines.flow.Flow
fun SearchList(
items: Flow<PagingData<LobstersPost>>,
listState: LazyListState,
isPostSaved: suspend (SavedPost) -> Boolean,
isPostSaved: (SavedPost) -> Boolean,
postActions: PostActions,
searchQuery: String,
setSearchQuery: (String) -> Unit,
@@ -54,42 +40,19 @@ fun SearchList(
setSearchQuery(query)
lazyPagingItems.refresh()
}
DisposableEffect(true) {
// Clear search field when navigating away
onDispose { setSearchQuery("") }
}
var searchActive by remember { mutableStateOf(false) }
Column(modifier = Modifier.semantics { isTraversalGroup = true }.zIndex(1f).fillMaxWidth()) {
Column(modifier = modifier.semantics { isTraversalGroup = true }.zIndex(1f).fillMaxWidth()) {
SearchBar(
value = searchQuery,
onValueChange = setSearchQuery,
onSearch = {
triggerSearch(it)
searchActive = true
},
onSearch = { triggerSearch(it) },
modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp).testTag("search_bar"),
)
if (searchActive) {
NetworkPosts(
lazyPagingItems = lazyPagingItems,
listState = listState,
isPostSaved = isPostSaved,
isPostRead = { false },
postActions = postActions,
modifier = modifier,
)
} else {
Box(modifier = Modifier.fillMaxSize()) {
Column(modifier = Modifier.align(Alignment.Center)) {
Icon(
imageVector = Icons.Filled.SearchOff,
contentDescription = "No search results",
modifier = Modifier.align(Alignment.CenterHorizontally).size(36.dp),
)
Text(text = "Nothing to see here", style = MaterialTheme.typography.headlineSmall)
}
}
}
NetworkPosts(
lazyPagingItems = lazyPagingItems,
listState = listState,
isPostSaved = isPostSaved,
isPostRead = { false },
postActions = postActions,
)
}
}
@@ -21,10 +21,6 @@ sealed class Destinations {
override val route = "saved"
}
object Search : Destinations() {
override val route = "search"
}
object Comments : Destinations() {
const val placeholder = "{postId}"
override val route = "comments/$placeholder"
@@ -39,6 +35,10 @@ sealed class Destinations {
override val route: String = "datatransfer"
}
object Search : Destinations() {
override val route: String = "search"
}
companion object {
val startDestination
get() = Hottest
@@ -4,45 +4,58 @@
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android.ui
package dev.msfjarvis.claw.android.ui.screens
import android.content.Intent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.ImportExport
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.NewReleases
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Whatshot
import androidx.compose.material.icons.outlined.FavoriteBorder
import androidx.compose.material.icons.outlined.NewReleases
import androidx.compose.material.icons.outlined.Search
import androidx.compose.material.icons.outlined.Whatshot
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.windowsizeclass.WindowSizeClass
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
@@ -52,17 +65,20 @@ import androidx.navigation.navArgument
import androidx.navigation.navDeepLink
import androidx.paging.compose.collectAsLazyPagingItems
import com.deliveryhero.whetstone.compose.injectedViewModel
import dev.msfjarvis.claw.android.MainActivity
import dev.msfjarvis.claw.android.R
import dev.msfjarvis.claw.android.SearchActivity
import dev.msfjarvis.claw.android.ui.datatransfer.DataTransferScreen
import dev.msfjarvis.claw.android.ui.decorations.ClawAppBar
import dev.msfjarvis.claw.android.ui.decorations.ClawNavigationBar
import dev.msfjarvis.claw.android.ui.decorations.ClawNavigationRail
import dev.msfjarvis.claw.android.ui.decorations.NavigationItem
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.lists.SearchList
import dev.msfjarvis.claw.android.ui.navigation.ClawNavigationType
import dev.msfjarvis.claw.android.ui.navigation.Destinations
import dev.msfjarvis.claw.android.ui.rememberPostActions
import dev.msfjarvis.claw.android.viewmodel.ClawViewModel
import dev.msfjarvis.claw.api.LobstersApi
import dev.msfjarvis.claw.common.comments.CommentsPage
@@ -75,7 +91,7 @@ import kotlinx.coroutines.launch
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun LobstersApp(
fun LobstersPostsScreen(
urlLauncher: UrlLauncher,
htmlConverter: HTMLConverter,
windowSizeClass: WindowSizeClass,
@@ -86,7 +102,6 @@ fun LobstersApp(
val hottestListState = rememberLazyListState()
val newestListState = rememberLazyListState()
val savedListState = rememberLazyListState()
val searchListState = rememberLazyListState()
val navController = rememberNavController()
val coroutineScope = rememberCoroutineScope()
val snackbarHostState = remember { SnackbarHostState() }
@@ -97,10 +112,19 @@ fun LobstersApp(
val hottestPosts = viewModel.hottestPosts.collectAsLazyPagingItems()
val newestPosts = viewModel.newestPosts.collectAsLazyPagingItems()
val savedPosts by viewModel.savedPosts.collectAsState(persistentMapOf())
val savedPosts by viewModel.savedPostsByMonth.collectAsState(persistentMapOf())
val navigationType = ClawNavigationType.fromSize(windowSizeClass.widthSizeClass)
val postIdOverride = context.getActivity()?.intent?.extras?.getString(MainActivity.NAVIGATION_KEY)
LaunchedEffect(false) {
if (postIdOverride != null) {
navController.navigate(
Destinations.Comments.route.replace(Destinations.Comments.placeholder, postIdOverride)
)
}
}
val navItems =
persistentListOf(
NavigationItem(
@@ -127,52 +151,76 @@ fun LobstersApp(
) {
coroutineScope.launch { savedListState.animateScrollToItem(index = 0) }
},
NavigationItem(
label = "Search",
route = Destinations.Search.route,
icon = Icons.Outlined.Search,
selectedIcon = Icons.Filled.Search,
) {
coroutineScope.launch { searchListState.animateScrollToItem(index = 0) }
},
)
Scaffold(
topBar = {
if (currentDestination != Destinations.Search.route) {
ClawAppBar(
navigationIcon = {
if (
navController.previousBackStackEntry != null &&
navItems.none { it.route == currentDestination }
ClawAppBar(
navigationIcon = {
if (
navController.previousBackStackEntry != null &&
navItems.none { it.route == currentDestination }
) {
IconButton(
onClick = { if (!navController.popBackStack()) context.getActivity()?.finish() },
) {
IconButton(
onClick = { if (!navController.popBackStack()) context.getActivity()?.finish() },
) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Go back to previous screen",
)
}
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Go back to previous screen",
)
}
},
title = {
if (navItems.any { it.route == currentDestination }) {
}
},
title = {
if (navItems.any { it.route == currentDestination }) {
Row(verticalAlignment = Alignment.CenterVertically) {
Image(
painter = painterResource(id = R.drawable.ic_launcher_foreground),
contentDescription = null,
modifier = Modifier.size(48.dp),
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface),
)
Text(text = stringResource(R.string.app_name), fontWeight = FontWeight.Bold)
}
},
actions = {
if (navItems.any { it.route == currentDestination }) {
IconButton(onClick = { navController.navigate(Destinations.DataTransfer.route) }) {
Icon(
imageVector = Icons.Filled.ImportExport,
contentDescription = "Data transfer options",
)
}
}
},
actions = {
var expanded by remember { mutableStateOf(false) }
if (navItems.any { it.route == currentDestination }) {
IconButton(onClick = { expanded = true }) {
Icon(Icons.Default.MoreVert, contentDescription = "Extra options")
}
},
)
}
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
DropdownMenuItem(
text = { Text("Backup and restore") },
onClick = {
navController.navigate(Destinations.DataTransfer.route)
expanded = false
},
leadingIcon = {
Icon(
imageVector = Icons.Filled.ImportExport,
contentDescription = "Data transfer options",
)
}
)
DropdownMenuItem(
text = { Text("Search posts") },
onClick = {
context.startActivity(Intent(context, SearchActivity::class.java))
expanded = false
},
leadingIcon = {
Icon(
imageVector = Icons.Filled.Search,
contentDescription = "Search posts",
)
}
)
}
}
},
)
},
bottomBar = {
AnimatedVisibility(visible = navigationType == ClawNavigationType.BOTTOM_NAVIGATION) {
@@ -231,17 +279,6 @@ fun LobstersApp(
postActions = postActions,
)
}
composable(Destinations.Search.route) {
setWebUri("https://lobste.rs/search")
SearchList(
items = viewModel.searchResults,
listState = searchListState,
isPostSaved = viewModel::isPostSaved,
postActions = postActions,
searchQuery = viewModel.searchQuery,
setSearchQuery = { query -> viewModel.searchQuery = query },
)
}
composable(
route = Destinations.Comments.route,
arguments = listOf(navArgument("postId") { type = NavType.StringType }),
@@ -251,7 +288,10 @@ fun LobstersApp(
navDeepLink { uriPattern = "$uri/s/${Destinations.Comments.placeholder}" },
),
) { backStackEntry ->
val postId = requireNotNull(backStackEntry.arguments?.getString("postId"))
val postId =
requireNotNull(backStackEntry.arguments?.getString("postId")) {
"Navigating to ${Destinations.Comments.route} without necessary 'postId' argument"
}
setWebUri("https://lobste.rs/s/$postId")
CommentsPage(
postId = postId,
@@ -267,7 +307,10 @@ fun LobstersApp(
deepLinks =
listOf(navDeepLink { uriPattern = "$uri/u/${Destinations.User.placeholder}" }),
) { backStackEntry ->
val username = requireNotNull(backStackEntry.arguments?.getString("username"))
val username =
requireNotNull(backStackEntry.arguments?.getString("username")) {
"Navigating to ${Destinations.User.route} without necessary 'username' argument"
}
setWebUri("https://lobste.rs/u/$username")
UserProfile(
username = username,
@@ -0,0 +1,75 @@
/*
* Copyright © 2023 Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android.ui.screens
import androidx.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.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
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.rememberPostActions
import dev.msfjarvis.claw.android.viewmodel.ClawViewModel
import dev.msfjarvis.claw.common.comments.CommentsPage
import dev.msfjarvis.claw.common.comments.HTMLConverter
import dev.msfjarvis.claw.common.urllauncher.UrlLauncher
@Composable
fun SearchScreen(
urlLauncher: UrlLauncher,
htmlConverter: HTMLConverter,
setWebUri: (String?) -> Unit,
modifier: Modifier = Modifier,
viewModel: ClawViewModel = injectedViewModel(),
) {
val navController = rememberNavController()
val postActions = rememberPostActions(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) {
setWebUri("https://lobste.rs/search")
SearchList(
items = viewModel.searchResults,
listState = listState,
isPostSaved = viewModel::isPostSaved,
postActions = postActions,
searchQuery = viewModel.searchQuery,
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"
}
setWebUri("https://lobste.rs/s/$postId")
CommentsPage(
postId = postId,
postActions = postActions,
htmlConverter = htmlConverter,
getSeenComments = viewModel::getSeenComments,
markSeenComments = viewModel::markSeenComments,
)
}
}
}
}
@@ -6,23 +6,29 @@
*/
package dev.msfjarvis.claw.android.viewmodel
import android.app.Application
import android.content.Context
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.glance.appwidget.updateAll
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import com.deliveryhero.whetstone.app.ApplicationScope
import com.deliveryhero.whetstone.viewmodel.ContributesViewModel
import com.slack.eithernet.ApiResult.Failure
import com.slack.eithernet.ApiResult.Success
import com.squareup.anvil.annotations.optional.ForScope
import dev.msfjarvis.claw.android.glance.SavedPostsWidget
import dev.msfjarvis.claw.android.paging.LobstersPagingSource
import dev.msfjarvis.claw.android.paging.LobstersPagingSource.Companion.PAGE_SIZE
import dev.msfjarvis.claw.android.paging.LobstersPagingSource.Companion.STARTING_PAGE_INDEX
import dev.msfjarvis.claw.android.paging.SearchPagingSource
import dev.msfjarvis.claw.api.LobstersApi
import dev.msfjarvis.claw.api.LobstersSearchApi
import dev.msfjarvis.claw.core.injection.IODispatcher
import dev.msfjarvis.claw.core.injection.MainDispatcher
import dev.msfjarvis.claw.database.local.SavedPost
import dev.msfjarvis.claw.model.Comment
import java.io.IOException
@@ -37,9 +43,12 @@ import javax.inject.Inject
import kotlinx.collections.immutable.ImmutableMap
import kotlinx.collections.immutable.toImmutableMap
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
@ContributesViewModel
@@ -47,7 +56,6 @@ class ClawViewModel
@Inject
constructor(
private val api: LobstersApi,
private val searchApi: LobstersSearchApi,
private val commentsRepository: CommentsRepository,
private val readPostsRepository: ReadPostsRepository,
private val savedPostsRepository: SavedPostsRepository,
@@ -56,7 +64,9 @@ constructor(
private val pagingSourceFactory: LobstersPagingSource.Factory,
private val searchPagingSourceFactory: SearchPagingSource.Factory,
@IODispatcher private val ioDispatcher: CoroutineDispatcher,
) : ViewModel() {
@MainDispatcher private val mainDispatcher: CoroutineDispatcher,
@ForScope(ApplicationScope::class) context: Context,
) : AndroidViewModel(context as Application) {
private val hottestPostsPager =
Pager(PagingConfig(pageSize = PAGE_SIZE), initialKey = STARTING_PAGE_INDEX) {
pagingSourceFactory.create(api::getHottestPosts)
@@ -67,7 +77,7 @@ constructor(
}
private val searchResultsPager =
Pager(PagingConfig(pageSize = PAGE_SIZE), initialKey = STARTING_PAGE_INDEX) {
searchPagingSourceFactory.create { searchApi.searchPosts(searchQuery, it) }
searchPagingSourceFactory.create { searchQuery }
}
val hottestPosts
@@ -76,24 +86,41 @@ constructor(
val newestPosts
get() = newestPostsPager.flow
private val savedPostsFlow
get() = savedPostsRepository.savedPosts
val savedPosts
get() = savedPostsFlow.map(::mapSavedPosts)
get() =
savedPostsRepository.savedPosts.map {
it.sortedByDescending { post -> post.createdAt.toLocalDateTime() }
}
val savedPostsByMonth
get() = savedPosts.map(::mapSavedPosts)
val searchResults
get() = searchResultsPager.flow
var searchQuery by mutableStateOf("")
private val _savedPostsMutex = Mutex()
private var _savedPosts = emptyList<String>()
init {
viewModelScope.launch {
savedPosts.collectLatest {
_savedPostsMutex.withLock { _savedPosts = it.map(SavedPost::shortId) }
}
}
}
private fun mapSavedPosts(items: List<SavedPost>): ImmutableMap<Month, List<SavedPost>> {
val sorted = items.sortedByDescending { post -> post.createdAt.toLocalDateTime() }
val sorted =
items.sortedWith { post1, post2 ->
post2.createdAt.toLocalDateTime().compareTo(post1.createdAt.toLocalDateTime())
}
return sorted.groupBy { post -> post.createdAt.toLocalDateTime().month }.toImmutableMap()
}
suspend fun isPostSaved(post: SavedPost): Boolean {
return savedPostsFlow.first().any { savedPost -> savedPost.shortId == post.shortId }
fun isPostSaved(post: SavedPost): Boolean {
return _savedPosts.contains(post.shortId)
}
fun toggleSave(post: SavedPost) {
@@ -104,6 +131,10 @@ constructor(
} else {
savedPostsRepository.savePost(post)
}
val newPosts = savedPosts.first()
withContext(mainDispatcher) {
SavedPostsWidget(newPosts.take(50)).updateAll(getApplication())
}
}
}
@@ -7,12 +7,14 @@
package dev.msfjarvis.claw.android.work
import android.content.Context
import androidx.glance.appwidget.updateAll
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.deliveryhero.whetstone.worker.ContributesWorker
import com.deliveryhero.whetstone.worker.WorkerScope
import com.slack.eithernet.ApiResult.Success
import com.squareup.anvil.annotations.optional.ForScope
import dev.msfjarvis.claw.android.glance.SavedPostsWidget
import dev.msfjarvis.claw.android.viewmodel.SavedPostsRepository
import dev.msfjarvis.claw.api.LobstersApi
import dev.msfjarvis.claw.model.LobstersPostDetails
@@ -42,6 +44,7 @@ constructor(
.filterIsInstance<Success<LobstersPostDetails>>()
.map { result -> result.value.toSavedPost() }
.let { savedPostsRepository.savePosts(it) }
SavedPostsWidget(savedPostsRepository.savedPosts.first().take(50)).updateAll(applicationContext)
return Result.success()
}
}
@@ -0,0 +1,20 @@
<!--
~ Copyright © 2023 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="1080"
android:viewportHeight="1080">
<group android:scaleX="0.7"
android:scaleY="0.7"
android:translateX="162"
android:translateY="162">
<path
android:pathData="M431.3,341.2C435.9,337 580.3,236 710.5,223.8C747.3,220.3 790.7,254.7 833.2,312.5C875.9,370.7 892.9,433.4 875.9,443C863.2,450.2 838.6,407.4 758.8,396.3C743.4,394.2 730.4,393.4 719.4,393.5C738.5,423.4 745.6,451.1 737.2,458C733.1,461.4 673.6,438.7 612.9,436.5C570.1,435 535.2,440.9 506.7,451.3C496.9,454.9 486,453.8 477.2,448.3C444.5,427.9 424.2,406.2 418.4,376.2C415.8,362.8 420.9,349.4 431.3,341.2ZM274.7,394.8C285.8,370.1 312.2,325.9 346.3,305.4C383.6,282.9 335,390.3 427.3,489.8C519.5,589.3 586.5,623.8 655.1,581.5C723.7,539.2 746.5,518.1 753,518.7C764,519.6 772.9,536.1 764.4,589.5C763,598.2 761.3,606.5 759.3,614.2C828.7,575 871.7,516.3 889,520C912,524.8 918.3,617.8 852.1,721C811.8,784 768.8,830.7 741.4,857.5C724.3,874 699.7,880 677,873.1C512,822.6 315.5,733.8 202.9,652C193.8,645.2 186.8,635.9 183,625.1C171.8,593.7 161.3,548.1 168.5,495.3C179.5,414.2 222.7,352.9 245.2,335.9C261.9,323.2 267.1,362.4 274.7,394.8Z"
android:fillColor="#FF4529"/>
</group>
</vector>
@@ -5,7 +5,7 @@
~ https://opensource.org/licenses/MIT.
-->
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@mipmap/ic_launcher_foreground"/>
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

@@ -0,0 +1,9 @@
<!--
~ Copyright © 2021-2023 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.
-->
<resources>
<color name="ic_launcher_background">#540C00</color>
</resources>
+3 -3
View File
@@ -7,13 +7,13 @@
<resources>
<style name="Theme.Claw.SplashScreen" parent="Theme.SplashScreen">
<item name="windowSplashScreenBackground">#FF6C0000</item>
<item name="windowSplashScreenAnimatedIcon">@mipmap/ic_launcher_foreground</item>
<item name="windowSplashScreenBackground">@color/ic_launcher_background</item>
<item name="windowSplashScreenAnimatedIcon">@drawable/ic_launcher_foreground</item>
<item name="windowSplashScreenAnimationDuration">1000</item>
<item name="postSplashScreenTheme">@style/Theme.Claw</item>
</style>
<style name="Platform.Theme.Claw" parent="android:Theme.Material.Light.NoActionBar" />
<style name="Platform.Theme.Claw" parent="android:Theme.Material.NoActionBar" />
<style name="Base.Theme.Claw" parent="Platform.Theme.Claw">
<item name="android:windowActionModeOverlay">true</item>
@@ -0,0 +1,15 @@
<!--
~ Copyright © 2023 Harsh Shandilya.
~ Use of this source code is governed by an MIT-style
~ license that can be found in the LICENSE file or at
~ https://opensource.org/licenses/MIT.
-->
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/glance_default_loading_layout"
android:minWidth="349dp"
android:minHeight="110dp"
android:minResizeWidth="349dp"
android:minResizeHeight="110dp"
android:resizeMode="vertical|horizontal"
android:updatePeriodMillis="1800000"
android:widgetCategory="home_screen" />
+2 -2
View File
@@ -1,5 +1,5 @@
#
# This file was automatically generated by 'versioning-plugin'. DO NOT EDIT MANUALLY.
#
versioning-plugin.versionCode=13500
versioning-plugin.versionName=1.35.0
versioning-plugin.versionCode=13800
versioning-plugin.versionName=1.38.0
+2
View File
@@ -27,7 +27,9 @@ dependencies {
implementation(libs.jsoup)
testImplementation(testFixtures(libs.eithernet))
testImplementation(libs.testcontainers)
testImplementation(libs.kotlinx.coroutines.test)
testImplementation(libs.kotlinx.serialization.json)
testImplementation(libs.retrofit.kotlinxSerializationConverter)
addTestDependencies(project)
}
+1 -1
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.3.0-alpha02" type="baseline" client="gradle" dependencies="true" name="AGP (8.3.0-alpha02)" variant="all" version="8.3.0-alpha02">
<issues format="6" by="lint 8.3.0-alpha14" type="baseline" client="gradle" dependencies="true" name="AGP (8.3.0-alpha14)" variant="all" version="8.3.0-alpha14">
</issues>
@@ -12,16 +12,15 @@ import dev.msfjarvis.claw.model.LobstersPostDetails
import dev.msfjarvis.claw.model.User
import retrofit2.http.GET
import retrofit2.http.Path
import retrofit2.http.Query
/** Simple interface defining an API for lobste.rs */
interface LobstersApi {
@GET("hottest.json")
suspend fun getHottestPosts(@Query("page") page: Int): ApiResult<List<LobstersPost>, Unit>
@GET("page/{page}.json")
suspend fun getHottestPosts(@Path("page") page: Int): ApiResult<List<LobstersPost>, Unit>
@GET("newest.json")
suspend fun getNewestPosts(@Query("page") page: Int): ApiResult<List<LobstersPost>, Unit>
@GET("newest/page/{page}.json")
suspend fun getNewestPosts(@Path("page") page: Int): ApiResult<List<LobstersPost>, Unit>
@GET("s/{postId}.json")
suspend fun getPostDetails(@Path("postId") postId: String): ApiResult<LobstersPostDetails, Unit>
@@ -0,0 +1,53 @@
/*
* Copyright © 2023 Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.api
import com.slack.eithernet.ApiResult
import dev.msfjarvis.claw.model.shiori.AuthRequest
import dev.msfjarvis.claw.model.shiori.AuthResponse
import dev.msfjarvis.claw.model.shiori.Bookmark
import dev.msfjarvis.claw.model.shiori.BookmarkRequest
import dev.msfjarvis.claw.model.shiori.BookmarksResponse
import dev.msfjarvis.claw.model.shiori.EditedBookmark
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.HTTP
import retrofit2.http.Header
import retrofit2.http.POST
import retrofit2.http.PUT
private const val SESSION_ID_HEADER = "X-Session-Id"
interface ShioriApi {
@POST("/api/login") suspend fun login(@Body body: AuthRequest): ApiResult<AuthResponse, Unit>
@HTTP(method = "POST", path = "/api/logout", hasBody = false)
suspend fun logout(@Header(SESSION_ID_HEADER) sessionId: String): ApiResult<Unit, Unit>
@GET("/api/bookmarks")
suspend fun getBookmarks(
@Header(SESSION_ID_HEADER) sessionId: String,
): ApiResult<BookmarksResponse, Unit>
@POST("/api/bookmarks")
suspend fun addBookmark(
@Header(SESSION_ID_HEADER) sessionId: String,
@Body bookmarkRequest: BookmarkRequest,
): ApiResult<Bookmark, Unit>
@PUT("/api/bookmarks")
suspend fun editBookmark(
@Header(SESSION_ID_HEADER) sessionId: String,
@Body bookmark: EditedBookmark,
): ApiResult<Bookmark, Unit>
@HTTP(method = "DELETE", path = "/api/bookmarks", hasBody = true)
suspend fun deleteBookmark(
@Header(SESSION_ID_HEADER) sessionId: String,
@Body ids: List<Int>,
): ApiResult<Int, Unit>
}
@@ -16,8 +16,10 @@ import dagger.multibindings.IntKey
import dagger.multibindings.IntoMap
import dev.msfjarvis.claw.api.LobstersApi
import dev.msfjarvis.claw.api.LobstersSearchApi
import dev.msfjarvis.claw.api.ShioriApi
import dev.msfjarvis.claw.api.converters.CSRFTokenConverter
import dev.msfjarvis.claw.api.converters.SearchConverter
import javax.inject.Named
import javax.inject.Qualifier
import okhttp3.OkHttpClient
import retrofit2.CallAdapter
@@ -40,7 +42,7 @@ object RetrofitModule {
client: OkHttpClient,
converterFactories: Map<Int, @JvmSuppressWildcards Converter.Factory>,
callAdapterFactories: Map<Int, @JvmSuppressWildcards CallAdapter.Factory>,
@BaseUrl baseUrl: String,
@Named("LobstersURL") baseUrl: String,
): Retrofit {
return Retrofit.Builder()
.client(client)
@@ -56,7 +58,7 @@ object RetrofitModule {
client: OkHttpClient,
@SearchApi converterFactories: List<@JvmSuppressWildcards Converter.Factory>,
callAdapterFactories: Map<Int, @JvmSuppressWildcards CallAdapter.Factory>,
@BaseUrl baseUrl: String,
@Named("LobstersURL") baseUrl: String,
): Retrofit {
return Retrofit.Builder()
.client(client)
@@ -72,6 +74,8 @@ object RetrofitModule {
@Provides
fun provideSearchApi(@SearchApi retrofit: Retrofit): LobstersSearchApi = retrofit.create()
@Provides fun provideShioriApi(retrofit: Retrofit): ShioriApi = retrofit.create()
@Provides
@IntKey(0)
@IntoMap
@@ -95,10 +99,8 @@ object RetrofitModule {
SearchConverter.Factory,
)
@Provides @BaseUrl fun provideBaseUrl(): String = LobstersApi.BASE_URL
@Provides @Named("LobstersURL") fun provideLobstersUrl(): String = LobstersApi.BASE_URL
}
@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class BaseUrl
/** Internal Dagger [Qualifier] to identify dependencies exclusive to [LobstersSearchApi]. */
@Qualifier @Retention(AnnotationRetention.RUNTIME) private annotation class SearchApi
@@ -0,0 +1,173 @@
/*
* Copyright © 2023 Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.api
import com.google.common.truth.Truth.assertThat
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import com.slack.eithernet.ApiResultCallAdapterFactory
import com.slack.eithernet.ApiResultConverterFactory
import com.slack.eithernet.successOrNull
import dev.msfjarvis.claw.model.shiori.AuthRequest
import dev.msfjarvis.claw.model.shiori.AuthResponse
import dev.msfjarvis.claw.model.shiori.Bookmark
import dev.msfjarvis.claw.model.shiori.BookmarkRequest
import dev.msfjarvis.claw.model.shiori.BookmarksResponse
import dev.msfjarvis.claw.model.shiori.EditedBookmark
import dev.msfjarvis.claw.model.shiori.Tag
import dev.msfjarvis.claw.util.TestUtils.assertIs
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import kotlinx.serialization.json.Json
import okhttp3.MediaType
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.testcontainers.containers.GenericContainer
import retrofit2.Retrofit
import retrofit2.create
class ShioriApiTest {
private lateinit var credentials: AuthResponse
@BeforeEach
fun setUp() {
container.start()
runBlocking {
credentials =
requireNotNull(api.login(AuthRequest(USER, PASSWORD)).successOrNull()) { "Login failed" }
}
}
@AfterEach
fun tearDown() {
container.stop()
}
@Test
fun getBookmarks() = runTest {
val response = api.getBookmarks(credentials.session).successOrNull()
assertIs<BookmarksResponse>(response)
assertThat(response.page).isEqualTo(1)
assertThat(response.bookmarks).isEmpty()
}
@Test
fun addBookmark() = runTest {
val response =
api
.addBookmark(
credentials.session,
BookmarkRequest(
"https://example.com",
false,
0,
emptyList(),
"Example Domain",
"""
This domain is for use in illustrative examples in documents.
You may use this domain in literature without prior coordination or asking for permission.
"""
.trimIndent(),
)
)
.successOrNull()
assertIs<Bookmark>(response)
assertThat(response.url).isEqualTo("https://example.com")
assertThat(response.title).isEqualTo("Example Domain")
assertThat(response.excerpt)
.isEqualTo(
"""
This domain is for use in illustrative examples in documents.
You may use this domain in literature without prior coordination or asking for permission.
"""
.trimIndent()
)
assertThat(response.id).isAtLeast(0)
}
@Test
fun editBookmark() = runTest {
val response =
api
.addBookmark(
credentials.session,
BookmarkRequest(
"https://example.com",
false,
0,
emptyList(),
"Example Domain",
"""
This domain is for use in illustrative examples in documents.
You may use this domain in literature without prior coordination or asking for permission.
"""
.trimIndent(),
)
)
.successOrNull()
assertIs<Bookmark>(response)
assertThat(response.tags).isEmpty()
val newBookmark =
EditedBookmark(
id = response.id,
url = response.url,
title = response.title,
tags = listOf(Tag("examples")),
)
val edited = api.editBookmark(credentials.session, newBookmark).successOrNull()
assertIs<Bookmark>(edited)
assertThat(edited.tags).isNotEmpty()
assertThat(edited.tags).containsExactly(Tag("examples"))
}
@Test
fun deleteBookmark() = runTest {
val response =
api
.addBookmark(
credentials.session,
BookmarkRequest(
"https://example.com",
false,
0,
emptyList(),
"Example Domain",
"""
This domain is for use in illustrative examples in documents.
You may use this domain in literature without prior coordination or asking for permission.
"""
.trimIndent(),
)
)
.successOrNull()
assertIs<Bookmark>(response)
val count = api.deleteBookmark(credentials.session, listOf(response.id)).successOrNull()
assertThat(count).isEqualTo(1)
}
companion object {
// Default settings for the container
private const val USER = "shiori"
private const val PASSWORD = "gopher"
private val container =
GenericContainer("ghcr.io/go-shiori/shiori:v1.5.5").withExposedPorts(8080)
private val json = Json { ignoreUnknownKeys = true }
private val api
get() =
Retrofit.Builder()
.baseUrl("http://${container.host}:${container.firstMappedPort}")
.addConverterFactory(ApiResultConverterFactory)
.addConverterFactory(json.asConverterFactory(MediaType.get("application/json")))
.addCallAdapterFactory(ApiResultCallAdapterFactory)
.build()
.create<ShioriApi>()
}
}
+3 -1
View File
@@ -65,10 +65,12 @@ dependencies {
implementation(libs.build.agp)
implementation(libs.build.cachefix)
implementation(libs.build.kotlin.gradle)
implementation(libs.build.r8)
implementation(libs.build.semver)
implementation(libs.build.sentry)
implementation(libs.build.spotless)
implementation(libs.build.vcu)
implementation(libs.build.versions)
// Expose the generated version catalog API to the plugin.
implementation(files(libs::class.java.superclass.protectionDomain.codeSource.location))
}
-4
View File
@@ -43,10 +43,6 @@ dependencyResolutionManagement {
includeModule("gradle.plugin.org.gradle.android", "android-cache-fix-gradle-plugin")
}
}
exclusiveContent {
forRepository { maven("https://storage.googleapis.com/r8-releases/raw/main") }
filter { includeModule("com.android.tools", "r8") }
}
mavenCentral()
}
versionCatalogs { create("libs") { from(files("../gradle/libs.versions.toml")) } }
@@ -15,14 +15,13 @@ import com.android.build.api.variant.HasUnitTestBuilder
import com.android.build.api.variant.LibraryAndroidComponentsExtension
import com.android.build.gradle.BaseExtension
import dev.msfjarvis.claw.gradle.LintConfig.configureLint
import org.gradle.accessors.dm.LibrariesForLibs
import org.gradle.android.AndroidCacheFixPlugin
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.findByType
import org.gradle.kotlin.dsl.getByType
class AndroidCommonPlugin : Plugin<Project> {
@@ -59,9 +58,9 @@ class AndroidCommonPlugin : Plugin<Project> {
}
project.extensions.findByType<ApplicationExtension>()?.lint?.configureLint(project)
project.extensions.findByType<LibraryExtension>()?.lint?.configureLint(project)
val catalog = project.extensions.getByType<VersionCatalogsExtension>()
val libs = catalog.named("libs")
project.dependencies.addProvider("lintChecks", libs.findLibrary("slack-compose-lints").get())
val libs = project.extensions.getByName("libs") as LibrariesForLibs
project.dependencies.addProvider("lintChecks", libs.slack.compose.lints)
project.dependencies.addProvider("lintChecks", libs.slack.lints)
}
private fun Project.configureSlimTests() {
@@ -73,7 +72,7 @@ class AndroidCommonPlugin : Plugin<Project> {
beforeVariants(selector().all()) {
if (it.name == "debug") return@beforeVariants
(it as HasUnitTestBuilder).enableUnitTest = false
it.enableAndroidTest = false
it.androidTest.enable = false
}
}
@@ -82,7 +81,7 @@ class AndroidCommonPlugin : Plugin<Project> {
beforeVariants(selector().all()) {
if (it.name == "debug") return@beforeVariants
(it as HasUnitTestBuilder).enableUnitTest = false
it.enableAndroidTest = false
it.androidTest.enable = false
}
}
}
@@ -43,7 +43,9 @@ class ApplicationPlugin : Plugin<Project> {
)
)
}
named("release") { isMinifyEnabled = true }
named("release") {
isMinifyEnabled = !project.providers.environmentVariable("DISABLE_MINIFY").isPresent
}
named("debug") {
applicationIdSuffix = ".debug"
versionNameSuffix = "-debug"
@@ -31,7 +31,7 @@ class KotlinCommonPlugin : Plugin<Project> {
compilerOptions {
allWarningsAsErrors.set(true)
freeCompilerArgs.addAll(ADDITIONAL_COMPILER_ARGS)
languageVersion.set(KotlinVersion.KOTLIN_1_8)
languageVersion.set(KotlinVersion.KOTLIN_1_9)
}
}
withType<Test>().configureEach {
@@ -31,10 +31,15 @@ object LintConfig {
if (!isJVM) {
enable += "ComposeM2Api"
error += "ComposeM2Api"
// False-positives in the TestContainers library
disable += "DeprecatedCall"
}
baseline = project.file("lint-baseline.xml")
// This is extremely annoying
disable += "NewerVersionAvailable"
disable += "GradleDependency"
// Jetpack Compose lints are incompatible with AGP 8.3.0-alpha06
disable += "ObsoleteLintCustomCheck"
}
fun configureRootProject(project: Project) {
@@ -10,20 +10,18 @@ import com.android.build.api.variant.ApplicationAndroidComponentsExtension
import io.sentry.android.gradle.extensions.InstrumentationFeature
import io.sentry.android.gradle.extensions.SentryPluginExtension
import java.util.EnumSet
import org.gradle.accessors.dm.LibrariesForLibs
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.getByType
@Suppress("Unused")
class SentryPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.pluginManager.withPlugin("com.android.application") {
val catalog = project.extensions.getByType<VersionCatalogsExtension>()
val libs = catalog.named("libs")
val libs = project.extensions.getByName("libs") as LibrariesForLibs
project.extensions.configure<ApplicationAndroidComponentsExtension> {
onVariants(selector().all()) { variant ->
val sentryDsn = project.providers.environmentVariable(SENTRY_DSN_PROPERTY)
@@ -52,13 +50,11 @@ class SentryPlugin : Plugin<Project> {
experimentalGuardsquareSupport.set(false)
autoInstallation {
enabled.set(true)
sentryVersion.set(libs.findVersion("sentry-sdk").get().requiredVersion)
sentryVersion.set(libs.versions.sentry.sdk)
}
includeDependenciesReport.set(true)
}
with(project.dependencies) {
addProvider("implementation", platform(libs.findLibrary("sentry-bom").get()))
}
with(project.dependencies) { addProvider("implementation", platform(libs.sentry.bom)) }
}
}
@@ -8,11 +8,13 @@ package dev.msfjarvis.claw.gradle
import com.diffplug.gradle.spotless.SpotlessExtension
import com.diffplug.gradle.spotless.SpotlessPlugin
import com.diffplug.gradle.spotless.SpotlessTask
import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.withType
@Suppress("Unused")
class SpotlessPlugin : Plugin<Project> {
@@ -43,13 +45,16 @@ class SpotlessPlugin : Plugin<Project> {
endWithNewline()
licenseHeaderFile(
project.file("spotless/license.xml"),
"<manifest|<resources|<full-backup-content|<data-extraction-rules|<adaptive-icon"
"<(adaptive-icon|appwidget-provider|data-extraction-rules|full-backup-content|manifest|vector|resources)"
)
}
}
project.tasks.withType<SpotlessTask>().configureEach {
notCompatibleWithConfigurationCache("https://github.com/diffplug/spotless/issues/987")
}
}
private companion object {
private const val KTFMT_VERSION = "0.45"
private const val KTFMT_VERSION = "0.46"
}
}
@@ -1,5 +1,5 @@
/*
* Copyright © 2022 Harsh Shandilya.
* Copyright © 2022-2023 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.
@@ -12,6 +12,7 @@ import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardCopyOption
import kotlin.io.path.createDirectory
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
@@ -21,6 +22,7 @@ import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
@@ -33,6 +35,7 @@ abstract class CollectApksTask : DefaultTask() {
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
@get:Optional
abstract val mappingFile: RegularFileProperty
@get:Input abstract val variantName: Property<String>
@@ -41,27 +44,30 @@ abstract class CollectApksTask : DefaultTask() {
@get:OutputDirectory abstract val outputDirectory: DirectoryProperty
override fun getGroup(): String {
return "artifact collection"
}
@TaskAction
fun run() {
val outputDir = outputDirectory.asFile.get()
val outputDirStream =
Files.walk(outputDir.toPath()).sorted(Comparator.reverseOrder()).map(Path::toFile)
outputDirStream.forEach(File::delete)
outputDirStream.close()
outputDir.mkdirs()
val outputDir = outputDirectory.asFile.get().toPath()
Files.walk(outputDir).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete)
outputDir.createDirectory()
val builtArtifacts =
builtArtifactsLoader.get().load(apkFolder.get()) ?: error("Cannot load APKs")
builtArtifacts.elements.forEach { artifact ->
Files.copy(
Paths.get(artifact.outputFile),
outputDir.resolve("Claw-${variantName.get()}-${artifact.versionName}.apk").toPath(),
outputDir.resolve("Claw-${variantName.get()}-${artifact.versionName}.apk"),
StandardCopyOption.REPLACE_EXISTING,
)
}
mappingFile.orNull?.apply {
Files.copy(
asFile.toPath(),
outputDir.resolve("mapping.txt"),
StandardCopyOption.REPLACE_EXISTING,
)
}
Files.copy(
mappingFile.get().asFile.toPath(),
outputDir.resolve("mapping.txt").toPath(),
StandardCopyOption.REPLACE_EXISTING,
)
}
}
@@ -1,5 +1,5 @@
/*
* Copyright © 2022 Harsh Shandilya.
* Copyright © 2022-2023 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.
@@ -10,19 +10,28 @@ import java.io.File
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardCopyOption
import kotlin.io.path.createDirectory
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
abstract class CollectBundleTask : DefaultTask() {
@get:InputFile abstract val bundleFile: RegularFileProperty
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val bundleFile: RegularFileProperty
@get:InputFile abstract val mappingFile: RegularFileProperty
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
@get:Optional
abstract val mappingFile: RegularFileProperty
@get:Input abstract val variantName: Property<String>
@@ -30,23 +39,26 @@ abstract class CollectBundleTask : DefaultTask() {
@get:OutputDirectory abstract val outputDirectory: DirectoryProperty
override fun getGroup(): String {
return "artifact collection"
}
@TaskAction
fun taskAction() {
val outputDir = outputDirectory.asFile.get()
val outputDirStream =
Files.walk(outputDir.toPath()).sorted(Comparator.reverseOrder()).map(Path::toFile)
outputDirStream.forEach(File::delete)
outputDirStream.close()
outputDir.mkdirs()
val outputDir = outputDirectory.asFile.get().toPath()
Files.walk(outputDir).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete)
outputDir.createDirectory()
Files.copy(
bundleFile.get().asFile.toPath(),
outputDir.resolve("Claw-${variantName.get()}-${versionName.get()}.aab").toPath(),
StandardCopyOption.REPLACE_EXISTING,
)
Files.copy(
mappingFile.get().asFile.toPath(),
outputDir.resolve("mapping.txt").toPath(),
outputDir.resolve("Claw-${variantName.get()}-${versionName.get()}.aab"),
StandardCopyOption.REPLACE_EXISTING,
)
mappingFile.orNull?.apply {
Files.copy(
asFile.toPath(),
outputDir.resolve("mapping.txt"),
StandardCopyOption.REPLACE_EXISTING,
)
}
}
}
@@ -6,29 +6,27 @@
*/
package dev.msfjarvis.claw.gradle
import org.gradle.accessors.dm.LibrariesForLibs
import org.gradle.api.Project
import org.gradle.api.artifacts.ExternalModuleDependency
import org.gradle.api.artifacts.MinimalExternalModuleDependency
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.kotlin.dsl.DependencyHandlerScope
import org.gradle.kotlin.dsl.exclude
import org.gradle.kotlin.dsl.getByType
/** Extension function to configure JUnit5 dependencies with the Truth assertion library. */
fun DependencyHandlerScope.addTestDependencies(project: Project) {
val catalog = project.extensions.getByType<VersionCatalogsExtension>()
val libs = catalog.named("libs")
addProvider("testImplementation", libs.findLibrary("junit-jupiter-api").get())
val libs = project.extensions.getByName("libs") as LibrariesForLibs
addProvider("testImplementation", libs.junit.jupiter.api)
addProvider<MinimalExternalModuleDependency, ExternalModuleDependency>(
"testImplementation",
libs.findLibrary("truth").get(),
libs.truth,
) {
exclude(group = "junit", module = "junit")
}
addProvider("testRuntimeOnly", libs.findLibrary("junit-jupiter-engine").get())
addProvider("testRuntimeOnly", libs.junit.jupiter.engine)
addProvider<MinimalExternalModuleDependency, ExternalModuleDependency>(
"testRuntimeOnly",
libs.findLibrary("junit-legacy").get(),
libs.junit.legacy,
) {
// See https://github.com/google/truth/issues/333
because("Truth needs it")
-11
View File
@@ -4,19 +4,8 @@
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
import dev.iurysouza.modulegraph.Orientation
import dev.iurysouza.modulegraph.Theme
plugins {
id("dev.msfjarvis.claw.spotless")
id("dev.msfjarvis.claw.versions")
id("dev.msfjarvis.claw.kotlin-common")
alias(libs.plugins.modulegraph)
}
moduleGraphConfig {
heading.set("## Dependency Diagram")
orientation.set(Orientation.TOP_TO_BOTTOM)
readmePath.set(project.layout.projectDirectory.file("README.md").asFile.absolutePath)
theme.set(Theme.DARK)
}
+2 -1
View File
@@ -33,7 +33,7 @@ anvil { generateDaggerFactories.set(true) }
dependencies {
api(projects.core)
api(projects.database)
api(projects.database.core)
api(projects.model)
implementation(platform(libs.androidx.compose.bom))
@@ -50,6 +50,7 @@ dependencies {
implementation(libs.compose.richtext.ui)
implementation(libs.kotlinx.collections.immutable)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinResult.coroutines)
implementation(libs.napier)
compileOnly(libs.androidx.compose.ui.tooling.preview)
+1 -1
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.3.0-alpha02" type="baseline" client="gradle" dependencies="true" name="AGP (8.3.0-alpha02)" variant="all" version="8.3.0-alpha02">
<issues format="6" by="lint 8.3.0-alpha14" type="baseline" client="gradle" dependencies="true" name="AGP (8.3.0-alpha14)" variant="all" version="8.3.0-alpha14">
</issues>
@@ -37,6 +37,8 @@ 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
import dev.msfjarvis.claw.common.posts.PostActions
import dev.msfjarvis.claw.common.posts.PostTitle
import dev.msfjarvis.claw.common.posts.Submitter
@@ -61,7 +63,7 @@ internal fun CommentsHeader(
produceState(
initialValue = LinkMetadata(postDetails.url, null),
) {
runCatching { postActions.getLinkMetadata(postDetails.url) }
runSuspendCatching { postActions.getLinkMetadata(postDetails.url) }
.onSuccess { metadata -> value = metadata }
}
@@ -25,7 +25,7 @@ internal data class CommentNode(
child.parent = this
} else {
child.indentLevel += 1
children.last().addChild(child)
children.lastOrNull()?.addChild(child)
}
}
}
@@ -47,7 +47,7 @@ internal fun createListNode(
)
)
} else {
commentNodes.last().let {
commentNodes.lastOrNull()?.let {
it.addChild(
CommentNode(
comment = comments[i],
@@ -25,6 +25,8 @@ 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
@@ -109,10 +111,10 @@ fun CommentsPage(
) {
val postDetails by
produceState<NetworkState>(Loading) {
runCatching { postActions.getComments(postId) }
runSuspendCatching { postActions.getComments(postId) }
.fold(
onSuccess = { details -> value = Success(details) },
onFailure = { value = Error(error = it, description = "Failed to load comments") }
success = { details -> value = Success(details) },
failure = { value = Error(error = it, description = "Failed to load comments") },
)
}
val commentState by
@@ -16,6 +16,7 @@ import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.absoluteOffset
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
@@ -30,7 +31,6 @@ import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.FavoriteBorder
import androidx.compose.material3.Badge
import androidx.compose.material3.BadgedBox
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
@@ -186,7 +186,6 @@ private fun SaveButton(
}
@Composable
@OptIn(ExperimentalMaterial3Api::class)
private fun CommentsButton(
commentCount: Int?,
modifier: Modifier = Modifier,
@@ -197,6 +196,8 @@ private fun CommentsButton(
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(),
@@ -6,6 +6,7 @@
*/
package dev.msfjarvis.claw.common.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
@@ -16,10 +17,15 @@ import androidx.compose.material3.lightColorScheme
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
import com.halilibo.richtext.ui.material3.SetupMaterial3RichText
private val LightThemeColors =
val LightThemeColors =
lightColorScheme(
primary = md_theme_light_primary,
onPrimary = md_theme_light_onPrimary,
@@ -48,7 +54,7 @@ private val LightThemeColors =
inverseSurface = md_theme_light_inverseSurface,
)
private val DarkThemeColors =
val DarkThemeColors =
darkColorScheme(
primary = md_theme_dark_primary,
onPrimary = md_theme_dark_onPrimary,
@@ -97,6 +103,14 @@ fun LobstersTheme(
}
else -> if (darkTheme) DarkThemeColors else LightThemeColors
}
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
window.statusBarColor = Color.Transparent.toArgb()
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme
}
}
CompositionLocalProvider(*providedValues) {
MaterialTheme(colorScheme = colorScheme, typography = AppTypography) {
SetupMaterial3RichText { content() }
@@ -1,5 +1,5 @@
/*
* Copyright © 2022 Harsh Shandilya.
* Copyright © 2022-2023 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.
@@ -0,0 +1,66 @@
/*
* Copyright © 2023 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.clickable
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import dev.msfjarvis.claw.common.theme.LobstersTheme
import dev.msfjarvis.claw.common.ui.preview.ThemePreviews
@Composable
fun PasswordField(
value: String,
label: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier,
) {
var isPasswordVisible by remember { mutableStateOf(false) }
TextField(
value = value,
onValueChange = onValueChange,
label = { Text(label) },
visualTransformation =
if (isPasswordVisible) VisualTransformation.None else PasswordVisualTransformation(),
trailingIcon = {
Icon(
painter =
if (isPasswordVisible) rememberVectorPainter(Icons.Filled.VisibilityOff)
else rememberVectorPainter(Icons.Filled.Visibility),
contentDescription = null,
modifier = Modifier.clickable { isPasswordVisible = !isPasswordVisible }
)
},
modifier = modifier,
)
}
@ThemePreviews
@Composable
private fun PasswordFieldPreview() {
LobstersTheme {
var value by remember { mutableStateOf("") }
PasswordField(
value = value,
label = "Password",
onValueChange = { value = it },
)
}
}
@@ -22,17 +22,21 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
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.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import dev.msfjarvis.claw.common.theme.LobstersTheme
import dev.msfjarvis.claw.common.ui.preview.DevicePreviews
import dev.msfjarvis.claw.common.ui.preview.ThemePreviews
@Composable
fun SearchBar(
@@ -41,6 +45,7 @@ fun SearchBar(
onSearch: (String) -> Unit,
modifier: Modifier = Modifier,
) {
val focusRequester = remember { FocusRequester() }
TextField(
value = value,
onValueChange = onValueChange,
@@ -59,11 +64,14 @@ fun SearchBar(
imeAction = ImeAction.Search,
),
singleLine = true,
modifier = modifier.focusable(),
modifier = modifier.focusable().focusRequester(focusRequester),
)
LaunchedEffect(Unit) { focusRequester.requestFocus() }
}
@Preview
@DevicePreviews
@ThemePreviews
@Composable
fun SearchBarPreview() {
LobstersTheme {
@@ -73,7 +81,7 @@ fun SearchBarPreview() {
value = value,
onValueChange = { value = it },
onSearch = {},
modifier = Modifier.align(Alignment.TopCenter)
modifier = Modifier.align(Alignment.TopCenter).fillMaxWidth()
)
}
}
@@ -25,6 +25,8 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
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
@@ -44,10 +46,10 @@ fun UserProfile(
) {
val user by
produceState<NetworkState>(Loading) {
runCatching { getProfile(username) }
runSuspendCatching { getProfile(username) }
.fold(
onSuccess = { profile -> value = Success(profile) },
onFailure = {
success = { profile -> value = Success(profile) },
failure = {
value =
Error(
error = it,
+1 -1
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.3.0-alpha02" type="baseline" client="gradle" dependencies="true" name="AGP (8.3.0-alpha02)" variant="all" version="8.3.0-alpha02">
<issues format="6" by="lint 8.3.0-alpha14" type="baseline" client="gradle" dependencies="true" name="AGP (8.3.0-alpha14)" variant="all" version="8.3.0-alpha14">
</issues>
@@ -4,7 +4,8 @@
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
@file:Suppress("InjectDispatcher") // False-positive
@file:Suppress("RawDispatchersUse") // False-positive
package dev.msfjarvis.claw.core.coroutines
import javax.inject.Inject
@@ -28,7 +28,7 @@ import kotlinx.coroutines.CoroutineDispatcher
@ContributesTo(ApplicationScope::class)
interface CoroutineDispatcherModule {
@Binds fun DefaultDispatcherProvider.bind(): DispatcherProvider
@Binds fun bindDispatcherProvider(impl: DefaultDispatcherProvider): DispatcherProvider
companion object {
@[Provides IODispatcher]
+30
View File
@@ -0,0 +1,30 @@
/*
* Copyright © 2021-2023 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.kotlin-jvm")
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.sqldelight)
}
sqldelight {
databases {
create("LobstersDatabase") {
packageName.set("dev.msfjarvis.claw.database")
schemaOutputDirectory.set(file("src/main/sqldelight/databases"))
verifyMigrations.set(true)
dialect(libs.sqldelight.dialect338)
}
}
}
dependencies {
implementation(libs.kotlinx.serialization.core)
testImplementation(libs.kotlinx.serialization.json)
addTestDependencies(project)
}
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.3.0-alpha14" type="baseline" client="gradle" dependencies="true" name="AGP (8.3.0-alpha14)" variant="all" version="8.3.0-alpha14">
</issues>

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