Compare commits

...
563 Commits
Author SHA1 Message Date
GitHub Actions 80736d6538 feat(release): bump version 2023-07-31 14:47:02 +00:00
Harsh Shandilya cb77a3ae85 chore: update changelog 2023-07-31 19:11:34 +05:30
Harsh Shandilya 80ab034493 fix(android): use SoLoader to load sqlite native library 2023-07-31 19:10:03 +05:30
Harsh Shandilya 3bb5dfffbe refactor(build): migrate to Baseline Profile Gradle Plugin 2023-07-31 16:48:09 +05:30
GitHub Actions d9ab751dbd chore(android): refresh baseline profile 2023-07-31 06:58:01 +00:00
Harsh Shandilya e3627ca89d fix: prevent upgrading to Anvil k1.8 build 2023-07-29 20:22:24 +05:30
Harsh Shandilya 543edf36e8 chore(deps): update anvil to v2.4.7 2023-07-29 12:12:37 +05:30
GitHub Actions fb5721beda chore(android): refresh baseline profile 2023-07-29 01:33:18 +00:00
renovate[bot]andGitHub 6e67a3de5f chore(deps): update github/codeql-action action to v2.21.2 2023-07-28 15:40:09 +00:00
GitHub Actions 6d944967f0 chore(android): refresh baseline profile 2023-07-28 01:40:03 +00:00
Harsh Shandilya 2628bfc2cd py: scripts/deploy-snapshot 2023-07-27 15:20:31 +00:00
Harsh Shandilya d77fa13903 py: scripts/boot-emulator 2023-07-27 16:55:38 +05:30
GitHub Actions f71aa07949 feat(release): start next development iteration 2023-07-27 08:10:24 +00:00
GitHub Actions a41b465648 feat(release): bump version 2023-07-27 08:06:08 +00:00
Harsh Shandilya a7038600b3 fix(release): update signing key secret in publish workflow
Fixes: ca6b941cbb ("fix(release): update secrets handling scripts")
2023-07-27 13:34:22 +05:30
Harsh Shandilya 3143556c63 fix: re-roll Lint baseline 2023-07-27 12:04:50 +05:30
GitHub Actions 5e2d9a2355 chore(android): refresh baseline profile 2023-07-27 01:38:58 +00:00
Harsh Shandilya b4adfede90 fix(android): avoid crash from empty creation time
Fixes COMPOSE-LOBSTERS-17
2023-07-27 02:57:35 +05:30
Harsh Shandilya ed85ba905d fix(deps): update dev.chrisbanes.compose:compose-bom to 2023.07.00-alpha02 2023-07-27 02:36:28 +05:30
Harsh Shandilya c29e4065d6 chore: update changelog 2023-07-27 02:03:48 +05:30
Harsh Shandilya bcf4f02a01 Revert "Revert "Revert "fix(common): make text in comments screen selectable"""
Still causes crashes :(

This reverts commit c5ec6cd680.

Fixes COMPOSE-LOBSTERS-16
2023-07-27 01:28:21 +05:30
renovate[bot]andGitHub 72b9409383 chore(deps): update github/codeql-action action to v2.21.1 2023-07-26 19:31:22 +00:00
renovate[bot]andGitHub 2c762428af chore(deps): update androidx 2023-07-26 18:27:51 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
58e427c13a chore(deps): update sqldelight to v2.0.0 (#391)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-26 23:57:37 +05:30
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
974f03ee89 fix(deps): update dependency androidx.paging:paging-compose to v3 (#392)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-26 23:57:28 +05:30
renovate[bot]andGitHub f5e6b25ec6 chore(deps): update dependency gradle to v8.3-rc-2 2023-07-26 16:46:13 +00:00
renovate[bot]andGitHub be5f073acf fix(deps): update sentry to v6.27.0 2023-07-26 08:41:17 +00:00
Harsh Shandilya ca6b941cbb fix(release): update secrets handling scripts 2023-07-26 14:10:49 +05:30
GitHub Actions 1e5f830652 chore(android): refresh baseline profile 2023-07-26 06:39:23 +00:00
renovate[bot]andGitHub e72a5fda5c chore(deps): update agp to v8.2.0-alpha14 2023-07-25 21:59:32 +00:00
renovate[bot]andGitHub 5c2fe6e630 fix(deps): update coroutines to v1.7.3 2023-07-25 19:04:26 +00:00
Harsh ShandilyaandGitHub 5907c2a09a fix(scripts): make boot_emulator naming consistent 2023-07-25 21:52:32 +05:30
Harsh Shandilya e3b381c665 chore: refresh feature graphic 2023-07-25 20:25:21 +05:30
Harsh Shandilya f1d30e59b0 feat: add Search flow to Maestro 2023-07-25 20:17:27 +05:30
Harsh Shandilya 7669a85f43 chore: refresh README graphic 2023-07-25 19:25:35 +05:30
GitHub Actions c05db765c8 feat(release): start next development iteration 2023-07-25 10:42:52 +00:00
GitHub Actions 4c8e30bcc9 feat(release): bump version 2023-07-25 10:38:59 +00:00
GitHub Actions d32819b1ff chore(android): refresh baseline profile 2023-07-25 06:39:00 +00:00
Harsh Shandilya a2d046bd0b Revert "fix(benchmark): upgrade to API level 33"
This reverts commit 1defffa504.
2023-07-25 11:46:47 +05:30
Harsh Shandilya 3ad4ff1c63 fix(build): use Whetstone from Maven Central 2023-07-25 11:40:02 +05:30
Harsh Shandilya 1defffa504 fix(benchmark): upgrade to API level 33 2023-07-25 01:31:01 +05:30
Harsh Shandilya 0d78cb4112 fix(benchmark): rework baseline profile benchmark 2023-07-25 01:31:01 +05:30
Harsh Shandilya 7c59130af9 feat(benchmark): restore full UI exploration 2023-07-25 01:31:01 +05:30
Harsh Shandilya 4137d73539 chore: update changelog 2023-07-25 00:56:07 +05:30
renovate[bot]andGitHub 0f8a649e3b chore(deps): update gradle/gradle-build-action action to v2.7.0 2023-07-24 16:23:53 +00:00
Harsh ShandilyaandGitHub 7e7a4bec83 chore(renovate): re-enable Kotlin upgrades 2023-07-24 21:49:52 +05:30
Harsh Shandilya b442b14cc1 fix(deps): upgrade to Kotlin 1.9.0 2023-07-24 21:29:24 +05:30
renovate[bot]andGitHub 97886f6ece chore(deps): update actions/setup-java action to v3.12.0 2023-07-24 14:03:55 +00:00
GitHub Actions 7b9245e62c chore(android): refresh baseline profile 2023-07-24 01:25:48 +00:00
renovate[bot]andGitHub 99aa62d0e4 fix(deps): update junit5 monorepo to v5.10.0 2023-07-23 13:39:47 +00:00
GitHub Actions c3cf8a8a39 chore(android): refresh baseline profile 2023-07-23 01:27:03 +00:00
GitHub Actions 340655f18a chore(android): refresh baseline profile 2023-07-22 01:27:26 +00:00
renovate[bot]andGitHub 10fa84b09e fix(deps): update richtext to v0.17.0 2023-07-21 05:01:52 +00:00
GitHub Actions f21a03b20b chore(android): refresh baseline profile 2023-07-21 01:29:17 +00:00
GitHub Actions 56394fc275 chore(android): refresh baseline profile 2023-07-20 01:27:18 +00:00
Harsh Shandilya 9fd2d9f17b fix(android): avoid triggering another useless search API call 2023-07-20 01:11:45 +05:30
Harsh Shandilya 0c0506b6d7 fix(android): fix content description of search page icon 2023-07-20 01:06:13 +05:30
Harsh Shandilya f94741144c refactor(android): make search page manage localized state 2023-07-19 22:24:43 +05:30
Harsh Shandilya 29c0369d43 fix(build): set proguard files for all build types
Some cursed day you might be enabling R8 on debug builds
2023-07-19 22:24:23 +05:30
Harsh Shandilya e700987d14 chore(deps): update dev.chrisbanes.compose:compose-bom to 2023.07.00-alpha01 2023-07-19 22:15:33 +05:30
Harsh Shandilya c5ec6cd680 Revert "Revert "fix(common): make text in comments screen selectable""
This reverts commit 401214794d.
2023-07-19 21:58:08 +05:30
renovate[bot]andGitHub e48a6d41d5 chore(deps): update github/codeql-action action to v2.21.0 2023-07-19 16:02:19 +00:00
Harsh Shandilya 5870af4017 fix(core): remove unused DispatcherProvider#unconfined 2023-07-19 21:31:43 +05:30
Harsh Shandilya cb4c85b41c chore(build): upgrade to Gradle 8.3 RC1 2023-07-19 21:31:43 +05:30
renovate[bot]andGitHub cd6a04c5fd fix(deps): update sentry to v6.26.0 2023-07-19 13:37:30 +00:00
Harsh Shandilya 401214794d Revert "fix(common): make text in comments screen selectable"
Causes frequent crashes

This reverts commit 778be597dd.
2023-07-19 10:03:04 +00:00
Harsh Shandilya fdd2f7008c Revert "fix(common): use SystemClock for getting current millis"
This broke relative time accounting

This reverts commit 42cc852a20.
2023-07-19 10:02:20 +00:00
renovate[bot]andGitHub 18ef9c63b9 chore(deps): update dependency com.android.test to v8.2.0-alpha13 2023-07-18 21:20:15 +00:00
Harsh Shandilya d343f3db2b feat(api): remove tag search endpoints 2023-07-19 00:50:10 +05:30
Harsh Shandilya 42cc852a20 fix(common): use SystemClock for getting current millis 2023-07-19 00:41:33 +05:30
Harsh Shandilya 44be6eacbb chore: update changelog 2023-07-19 00:04:25 +05:30
Harsh Shandilya 778be597dd fix(common): make text in comments screen selectable
With this change you can now select
- The post title
- The post description if it exists, and
- Each individual comment

Fixes #385
2023-07-18 23:23:24 +05:30
Harsh Shandilya 255439e2b4 feat(common): add a clickable icon to SearchBar 2023-07-18 23:23:24 +05:30
renovate[bot]andGitHub 63c3e26f5c fix(deps): update dependency nl.littlerobots.version-catalog-update:nl.littlerobots.version-catalog-update.gradle.plugin to v0.8.1 2023-07-18 17:38:40 +00:00
Harsh Shandilya d3c3e3ca08 feat(android): wire up search UI into navigation graph 2023-07-18 23:08:07 +05:30
Harsh Shandilya 8d6aba9865 feat(android): add search UI 2023-07-18 23:08:07 +05:30
Harsh Shandilya 5e91e20c51 feat(android): add specialized PagingSource for search endpoint 2023-07-18 23:08:07 +05:30
Harsh Shandilya 84c9dd3762 feat(common): add a simple SearchBar component
Material 3's SearchBar has way too much "interaction" for my liking
2023-07-18 23:08:07 +05:30
renovate[bot]andGitHub 24ef11b315 fix(deps): update dependency androidx.benchmark:benchmark-macro-junit4 to v1.2.0-beta01 2023-07-18 17:34:11 +00:00
Harsh Shandilya bdf218cd85 refactor(api): rework Retrofit DI and expose LobstersSearchApi 2023-07-18 17:53:36 +05:30
Harsh Shandilya b3ff40b12b refactor(core): RetrofitModule -> JsonModule 2023-07-18 17:11:52 +05:30
Harsh Shandilya bad5bf5457 refactor(api): make SearchConverter a singleton 2023-07-18 17:11:06 +05:30
Harsh Shandilya cf3ca27966 fix(api): correct usage of @Query feature in search API 2023-07-18 17:04:50 +05:30
renovate[bot]andHarsh Shandilya 127a0674a2 chore(deps): update gradle/gradle-build-action action to v2.6.1 2023-07-18 17:01:52 +05:30
renovate[bot]andHarsh Shandilya b1a18c5dec fix(deps): update dependency com.diffplug.spotless:spotless-plugin-gradle to v6.20.0 2023-07-18 17:01:52 +05:30
GitHub ActionsandHarsh Shandilya 0a39f56775 chore(android): refresh baseline profile 2023-07-18 17:01:52 +05:30
Harsh Shandilya 5daeb8d580 feat(common): log errors from NetworkError composable
Having the errors in the logcat stream makes debugging quite a lot faster.
2023-07-18 17:01:52 +05:30
Harsh Shandilya 415ddae9f7 refactor(android): enforce DI encapsulation 2023-07-18 17:01:52 +05:30
Harsh Shandilya 4c95680f3b fix(renovate): remove now-unnecessary sentry-sdk regex manager 2023-07-17 16:03:15 +05:30
Harsh Shandilya b6cb9a2077 fix(renovate): consolidate and cleanup disabled packages 2023-07-17 16:01:36 +05:30
Harsh Shandilya 13eefbba2a chore(gradle): ensure all custom repositories are named 2023-07-17 15:43:58 +05:30
Harsh Shandilya 6548f7d654 fix(renovate): drop Poko from Renovate list
This is tied to the Kotlin version
2023-07-17 15:12:09 +05:30
Harsh Shandilya 1e4217598f fix(build): make slimTests skip debug rather than select release 2023-07-17 15:08:52 +05:30
Harsh Shandilya 0359860b82 refactor(android): move link metadata extraction to Unfurl
Crux is doing major changes for no good reason
2023-07-17 15:03:55 +05:30
Harsh Shandilya 37f42dc107 feat(api): add a Retrofit service for search 2023-07-17 08:26:56 +00:00
Harsh Shandilya 7eb4b45ab1 fix(model): ensure all classes have a readable toString 2023-07-17 13:36:42 +05:30
GitHub Actions 3706fdd284 chore(android): refresh baseline profile 2023-07-17 01:28:06 +00:00
renovate[bot]andGitHub f1fa96f8ca fix(deps): update dependency com.chimbori.crux:crux to v5 2023-07-16 10:22:42 +00:00
renovate[bot]andGitHub 36cadcdd78 fix(deps): update dependency com.chimbori.crux:crux to v4 2023-07-16 03:55:22 +00:00
renovate[bot]andGitHub cf3ac329f6 fix(deps): update dependency com.chimbori.crux:crux to v3.13.0 2023-07-16 00:26:09 +00:00
Harsh Shandilya 0581cedfb0 fix: remove useless get 2023-07-16 00:49:16 +05:30
GitHub Actions 0fc5adc197 chore(android): refresh baseline profile 2023-07-15 01:25:21 +00:00
renovate[bot]andGitHub 0680585b77 chore(deps): update github/codeql-action action to v2.20.4 2023-07-14 15:04:14 +00:00
Harsh Shandilya 2a3514e3b5 Revert "fix(android): remove explicit item keys"
This causes subtle UI artifacts that make me more annoyed than the occasional crash.

This reverts commit 2a97383e7d.
2023-07-14 19:08:32 +05:30
renovate[bot]andGitHub fd8b65be72 chore(deps): update sentry to v6.25.2 2023-07-14 10:57:40 +00:00
renovate[bot]andGitHub 50870b5fb5 fix(deps): update dagger to v2.47 2023-07-14 05:07:17 +00:00
GitHub Actions 7c5d935801 chore(android): refresh baseline profile 2023-07-14 01:31:32 +00:00
GitHub Actions 2862ada772 chore(android): refresh baseline profile 2023-07-13 01:25:54 +00:00
renovate[bot]andGitHub 65d1457386 chore(deps): update sentry to v6.25.1 2023-07-12 10:33:55 +00:00
GitHub Actions 0f63611c2d chore(android): refresh baseline profile 2023-07-12 01:25:10 +00:00
renovate[bot]andGitHub 7767f1c6d7 chore(deps): update agp to v8.2.0-alpha12 2023-07-11 19:08:20 +00:00
GitHub Actions f351b60073 chore(android): refresh baseline profile 2023-07-11 01:27:20 +00:00
Harsh Shandilya 752dee8ed6 fix(ci): submit generated dependency graph from running tests 2023-07-11 06:28:33 +05:30
Harsh Shandilya 0573e8cb2d fix(ci): remove build-tools workaround 2023-07-11 06:14:04 +05:30
Harsh Shandilya 75fd41a0d8 fix(ci): make CodeQL only run on schedule 2023-07-11 06:14:04 +05:30
renovate[bot]andGitHub 59f8577d1f chore(deps): update plugin org.gradle.toolchains.foojay-resolver-convention to v0.6.0 2023-07-10 18:46:20 +00:00
renovate[bot]andGitHub 85d4b50d28 chore(deps): update gradle/gradle-build-action action to v2.6.0 2023-07-10 18:32:28 +00:00
renovate[bot]andGitHub c3499d434b chore(deps): update dependency gradle to v8.2.1 2023-07-10 15:18:37 +00:00
GitHub Actions 701e0d7fcc chore(android): refresh baseline profile 2023-07-10 01:28:01 +00:00
Harsh Shandilya 9a5a417418 feat(build): add copyright license headers to XML files 2023-07-09 22:59:58 +05:30
renovate[bot]andHarsh Shandilya 1221992d26 chore(deps): update agp to v8.2.0-alpha11 2023-07-09 22:56:37 +05:30
renovate[bot]andHarsh Shandilya 144cdb5e50 chore(deps): update github/codeql-action action to v2.20.3 2023-07-09 22:56:37 +05:30
GitHub Actions 8133b2c091 chore(android): refresh baseline profile 2023-07-05 01:24:08 +00:00
renovate[bot]andGitHub 869cc650e3 chore(deps): update sentry to v6.25.0 2023-07-04 17:23:45 +00:00
GitHub Actions 7a3262338d chore(android): refresh baseline profile 2023-07-04 01:26:35 +00:00
renovate[bot]andGitHub 85ff15e8ca chore(deps): update github/codeql-action action to v2.20.2 2023-07-03 11:17:13 +00:00
GitHub Actions 3d34c4f466 chore(android): refresh baseline profile 2023-07-03 06:43:06 +00:00
GitHub Actions 6da6364a55 feat(release): start next development iteration 2023-07-02 18:43:07 +00:00
GitHub Actions 26b04cce16 feat(release): bump version 2023-07-02 18:39:14 +00:00
GitHub Actions 9ab2400e1f chore(android): refresh baseline profile 2023-07-02 17:01:00 +00:00
GitHub Actions c3b30cc86c chore(android): refresh baseline profile 2023-07-02 01:25:14 +00:00
Harsh Shandilya 2a97383e7d fix(android): remove explicit item keys
Hail mary fix for Sentry issue COMPOSE-LOBSTERS-2
2023-07-01 18:59:17 +05:30
Harsh Shandilya dd20885270 fix(deps): bump coroutines to 1.7.2 2023-07-01 18:57:26 +05:30
Harsh Shandilya fe752f839a feat(build): migrate to Gradle JVM Toolchains 2023-07-01 18:21:10 +05:30
GitHub ActionsandHarsh Shandilya be9a888816 chore(android): refresh baseline profile 2023-07-01 18:21:10 +05:30
renovate[bot]andGitHub 648e7b31cf chore(deps): update dependency gradle to v8.2 2023-06-30 21:58:31 +00:00
GitHub Actions 21b520f786 chore(android): refresh baseline profile 2023-06-30 01:27:40 +00:00
renovate[bot]andGitHub 8bbcabc8f6 chore(deps): update gradle/gradle-build-action action to v2.5.1 2023-06-29 19:44:16 +00:00
Harsh Shandilya 0e4e344a2f fix(deps): upgrade workmanager to 2.9.0-alpha01 2023-06-29 18:12:26 +05:30
GitHub Actions 7b25b6aa85 chore(android): refresh baseline profile 2023-06-29 01:25:13 +00:00
renovate[bot]andGitHub 5343168b8a chore(deps): update gradle/gradle-build-action action to v2.5.0 2023-06-29 00:05:15 +00:00
renovate[bot]andGitHub 9b39fcf1fc fix(deps): update dependency androidx.navigation:navigation-compose to v2.7.0-beta02 2023-06-28 22:59:50 +00:00
renovate[bot]andGitHub 8ce7ac96d0 chore(deps): update dependency gradle to v8.2-rc-3 2023-06-28 10:44:19 +00:00
Harsh Shandilya b5c57500b1 feat: revert back to old text pipeline
Things have been entirely too slow on the lobste.rs front and I don't care much to continue pushing for it.

Fixes #382
Fixes #383
2023-06-28 10:58:39 +05:30
Harsh Shandilya c089e5ae5a fix(build): make slim tests optional 2023-06-28 10:46:59 +05:30
Harsh Shandilya d8a76cf1e7 Revert "fix(build): temporarily revert to AGP 8.0.x"
This reverts commit 1e888494ee.
2023-06-28 10:46:59 +05:30
renovate[bot]andGitHub 92fce55feb fix(deps): update dependency org.gradle.android.cache-fix:org.gradle.android.cache-fix.gradle.plugin to v2.7.2 2023-06-28 03:22:11 +00:00
GitHub Actions 60a430baa4 chore(android): refresh baseline profile 2023-06-28 01:35:58 +00:00
Harsh Shandilya b0c8de888c chore(deps): bump sqldelight to 2.0.0-rc02 2023-06-28 00:18:54 +05:30
renovate[bot]andGitHub 73ab2dd1d3 fix(deps): update dependency androidx.test:runner to v1.6.0-alpha03 2023-06-27 16:48:14 +00:00
GitHub Actions 7c4f843f42 chore(android): refresh baseline profile 2023-06-27 01:33:28 +00:00
GitHub Actions 123ec904e3 chore(android): refresh baseline profile 2023-06-26 01:32:43 +00:00
GitHub Actions 80c9211d37 chore(android): refresh baseline profile 2023-06-25 01:32:29 +00:00
GitHub Actions b030694f63 chore(android): refresh baseline profile 2023-06-24 01:35:33 +00:00
GitHub Actions e40e196354 chore(android): refresh baseline profile 2023-06-23 01:32:30 +00:00
renovate[bot]andGitHub b7abef3f8f chore(deps): update sentry to v6.24.0 2023-06-22 16:00:38 +00:00
Harsh ShandilyaandGitHub 5bde6cd9cb chore(deps): bump Compose to June alpha release 2023-06-22 21:29:15 +05:30
Harsh Shandilya cb8c90ace9 fix(renovate): include JitPack repository 2023-06-22 12:09:21 +05:30
Harsh Shandilya 9c2c238e1e fix(build): raise target SDK to 34 2023-06-22 11:27:42 +05:30
renovate[bot]andHarsh Shandilya 21a7baa309 fix(deps): update dependency androidx.benchmark:benchmark-macro-junit4 to v1.2.0-alpha16 2023-06-22 11:23:58 +05:30
GitHub Actions a9d75148fd chore(android): refresh baseline profile 2023-06-22 01:35:13 +00:00
renovate[bot]andGitHub 3209b5b1fe chore(deps): update github/codeql-action action to v2.20.1 2023-06-21 13:16:25 +00:00
renovate[bot]andGitHub c749a97ded fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v3.11.1 2023-06-21 09:28:39 +00:00
GitHub Actions f1ab468ceb chore(android): refresh baseline profile 2023-06-21 01:30:06 +00:00
renovate[bot]andGitHub ebd0e5cb5d chore(deps): update plugin modulegraphassert to v2.4.1 2023-06-19 22:42:11 +00:00
GitHub Actions 19bf81ac0e chore(android): refresh baseline profile 2023-06-19 01:35:03 +00:00
renovate[bot]andGitHub 569d5af8fb fix(deps): update dependency com.google.truth:truth to v1.1.5 2023-06-18 16:26:46 +00:00
GitHub Actions dc754dabb3 chore(android): refresh baseline profile 2023-06-18 01:37:26 +00:00
GitHub Actions ed42532416 chore(android): refresh baseline profile 2023-06-17 01:38:56 +00:00
GitHub Actions 211f39da52 chore(android): refresh baseline profile 2023-06-16 01:34:00 +00:00
renovate[bot]andHarsh Shandilya fefac25781 chore(deps): update sentry 2023-06-15 23:27:40 +05:30
Harsh Shandilya e0c9c88ca9 chore(common): prettify HTMLText preview 2023-06-15 23:27:40 +05:30
Harsh Shandilya ea7f7b6306 fix(common): add some visual distinction for blockquotes 2023-06-15 23:27:40 +05:30
GitHub Actions 8ceca6c5ed chore(android): refresh baseline profile 2023-06-15 01:36:06 +00:00
GitHub Actions e9ff85ad44 chore(android): refresh baseline profile 2023-06-14 01:35:35 +00:00
renovate[bot]andGitHub b8e6d4d3fe chore(deps): update github/codeql-action action to v2.20.0 2023-06-13 22:15:55 +00:00
GitHub Actions e7fdfcab69 chore(android): refresh baseline profile 2023-06-13 01:38:12 +00:00
Harsh Shandilya ad8c830b17 fix(common): make HTMLText use M3 Text component 2023-06-13 02:47:17 +05:30
renovate[bot]andHarsh Shandilya 25d2d4ddb7 chore(deps): update actions/checkout action to v3.5.3 2023-06-13 01:44:31 +05:30
Harsh Shandilya 22d1755fd2 fix(android): use SoLoader to load libraries 2023-06-13 01:44:31 +05:30
Harsh Shandilya d397680d33 chore: add changelog entry 2023-06-09 20:09:44 +05:30
Harsh Shandilya 40cd7758dc chore: remove unused Markdown dependencies 2023-06-09 20:09:44 +05:30
Harsh Shandilya 2dc8eae861 refactor: migrate to HTMLText 2023-06-09 20:09:44 +05:30
Harsh Shandilya 2d23ceee83 feat(common): add HTMLText composable
This allows rendering raw HTML text directly without roundtripping it as Markdown
2023-06-09 20:09:44 +05:30
renovate[bot]andGitHub cccc6fd64b fix(deps): update dependency com.github.ben-manes:gradle-versions-plugin to v0.47.0 2023-06-09 11:04:49 +00:00
GitHub Actions aced95c542 chore(android): refresh baseline profile 2023-06-09 01:30:17 +00:00
Harsh Shandilya 90edeefd6e fix: remove useless toList 2023-06-09 02:04:58 +05:30
GitHub Actions a5b90851c8 feat(release): start next development iteration 2023-06-08 15:03:41 +00:00
GitHub Actions 7d0c6972ef feat(release): bump version 2023-06-08 15:02:17 +00:00
Harsh Shandilya 18cb5dc07c chore: refresh feature graphic 2023-06-08 20:18:09 +05:30
Harsh Shandilya 5343e13215 fix(scripts): ensure demo mode is correctly enabled 2023-06-08 19:55:55 +05:30
renovate[bot]andGitHub 4d06a2722c fix(deps): update dependency dev.chrisbanes.compose:compose-bom to v2023.04.00-beta02 2023-06-08 12:14:43 +00:00
Harsh Shandilya b9f5bee35e fix(scripts): make setup-age architecture agnostic 2023-06-08 11:53:55 +00:00
Harsh Shandilya 72078b2768 fix(scripts): bump age to v1.1.1 2023-06-08 11:53:55 +00:00
GitHub Actions 465191609b chore(android): refresh baseline profile 2023-06-08 11:48:51 +00:00
Harsh ShandilyaandGitHub 6e1858a55e fix(benchmark): adjust for API changes 2023-06-08 07:03:28 +00:00
renovate[bot]andGitHub 3eeb947914 fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v3.10.0 2023-06-08 00:02:53 +00:00
renovate[bot]andGitHub d2c99fa8b7 fix(deps): update androidx 2023-06-07 19:06:35 +00:00
renovate[bot]andGitHub dbba96426d chore(deps): update sentry 2023-06-07 15:43:33 +00:00
Harsh Shandilya a5be35a667 feat(core): manually wire in Sentry OkHttp integration 2023-06-07 21:04:24 +05:30
Harsh Shandilya 6e291a8df1 refactor(scripts): rework how Maestro flows are run 2023-06-07 18:07:18 +05:30
Harsh Shandilya 24898cf9fb chore: refresh feature graphic 2023-06-07 17:19:43 +05:30
Harsh Shandilya 66e64ac305 chore: update changelog 2023-06-07 16:24:08 +05:30
Harsh Shandilya ee6bcc864f feat: update iconography for consistency 2023-06-07 16:24:02 +05:30
Harsh Shandilya 8de49d670b fix(android): add visual indication when there are no saved posts 2023-06-07 16:11:18 +05:30
GitHub Actions 13b1c699f6 chore(android): refresh baseline profile 2023-06-07 01:34:08 +00:00
Harsh Shandilya 86a70ffb48 feat(common): remove animation for comment text 2023-06-07 02:42:15 +05:30
Harsh Shandilya 2bb71238bc fix(android): dismiss existing snackbars when showing a new one 2023-06-07 02:37:43 +05:30
Harsh Shandilya 7d491de86c fix(android): prevent comments page from stacking when spammed 2023-06-07 02:19:54 +05:30
Harsh Shandilya 02575a917b fix(android): check for previous back stack entry 2023-06-07 02:05:14 +05:30
renovate[bot]andGitHub 5206851b22 chore(deps): update dependency gradle to v8.2-rc-2 2023-06-06 19:03:10 +00:00
GitHub Actions 74c36f27ae chore(android): refresh baseline profile 2023-06-06 01:36:55 +00:00
Harsh Shandilya 2048a2bb9b chore: add test resources to .rgignore 2023-06-06 00:45:13 +05:30
renovate[bot]andGitHub ea48cdb4f3 chore(deps): update plugin modulegraphassert to v2.4.0 2023-06-05 16:10:44 +00:00
Harsh Shandilya 942b9f3fb4 fix(android): report fully drawn status 2023-06-05 16:50:29 +05:30
Harsh Shandilya 5f6d635c7a fix(android): inline useless function 2023-06-05 14:58:28 +05:30
Harsh Shandilya 47a398f518 refactor: move metadata-extractor to android 2023-06-05 13:24:22 +05:30
Harsh Shandilya 9fdd72220f feat(scripts): add a quick way to refresh the README graphic 2023-06-05 13:24:22 +05:30
Harsh Shandilya 0a1913e056 feat(build): add module-graph-assert plugin 2023-06-05 10:04:41 +05:30
Harsh Shandilya 68db3dcda6 refactor: move coroutine-utils into core 2023-06-05 09:52:12 +05:30
Harsh Shandilya c50c9c8528 chore: refresh module graph 2023-06-05 09:46:09 +05:30
Harsh Shandilya 8a418f6649 refactor: make SavedPostSerializer an object 2023-06-05 09:41:52 +05:30
Harsh Shandilya acdc924047 chore: cleanup redundant @OptIn annotations 2023-06-05 09:34:48 +05:30
GitHub Actions 2fa1d3f06f chore(android): refresh baseline profile 2023-06-05 01:37:40 +00:00
Harsh Shandilya 7ffa225487 feat(database): add test for SavedPostSerializer 2023-06-05 02:51:53 +05:30
Harsh Shandilya c1659eb2dd chore(gitignore): add claw-export.json 2023-06-05 02:18:09 +05:30
Harsh Shandilya 1e888494ee fix(build): temporarily revert to AGP 8.0.x
Android Studio's canary version is going through some trouble recognizing test sources
2023-06-05 02:16:57 +05:30
Harsh Shandilya 054ba13dc3 fix(ci): enforce concurrency for all workflows 2023-06-05 02:00:15 +05:30
Harsh Shandilya 301760c39f refactor(android): make String.toLocalDateTime private 2023-06-05 01:59:17 +05:30
Harsh Shandilya 79fd814c14 fix(android): add Snackbars to data transfer flow 2023-06-05 01:50:23 +05:30
Harsh Shandilya a82d0e7063 fix(android): only show data transfer action on home screen 2023-06-05 00:56:20 +05:30
Harsh Shandilya c6ea1182e0 refactor: remove explicit calls to rememberVectorPainter 2023-06-04 17:14:09 +00:00
Harsh Shandilya 5e7b79eb89 fix(android): hide data transfer action on data transfer screen 2023-06-04 21:19:22 +05:30
Harsh Shandilya 3738d94789 feat(android): wire up import/export settings to UI 2023-06-04 18:45:14 +05:30
Harsh Shandilya fb0e372917 feat(android): add DataTransferRepository 2023-06-04 18:45:14 +05:30
Harsh Shandilya 8945f9e33f feat(database): build a custom serializer for SavedPost 2023-06-04 18:45:14 +05:30
Harsh Shandilya 2895e7ffe7 refactor(android): split out comments functionality 2023-06-04 18:45:14 +05:30
GitHub Actions f102166fae chore(android): refresh baseline profile 2023-06-04 01:28:30 +00:00
Harsh Shandilya c51e28b8e1 fix: touch up version catalog 2023-06-04 01:33:25 +05:30
GitHub Actions a9923eb8c0 feat(release): start next development iteration 2023-06-03 18:50:53 +00:00
GitHub Actions de2f3964e0 feat(release): bump version 2023-06-03 18:46:20 +00:00
Harsh Shandilya 7c5ccbf63b fix: make nav transitions run at 2x of defaults 2023-06-04 00:03:38 +05:30
Harsh Shandilya b4b96222ed chore: reorder manifest attributes 2023-06-04 00:03:38 +05:30
Harsh Shandilya a920f899fb fix(android): automatically extract native libraries 2023-06-04 00:03:38 +05:30
GitHub Actions 31b39a49d6 chore(android): refresh baseline profile 2023-06-03 01:33:42 +00:00
renovate[bot]andGitHub bfb4e9741a chore(deps): update msfjarvis/keep-a-changelog-new-release action to v2.1.0 2023-06-02 22:59:13 +00:00
GitHub Actions bc5c5a13b4 chore(android): refresh baseline profile 2023-06-02 01:22:03 +00:00
renovate[bot]andGitHub 19c50f40cd chore(deps): update github/codeql-action action to v2.3.6 2023-06-01 19:54:14 +00:00
renovate[bot]andGitHub bb0a987d80 fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v3.8.0 2023-06-01 10:22:25 +00:00
Harsh Shandilya dba380e4c4 fix(gradle): remove default-enabled feature prop 2023-06-01 12:46:32 +05:30
Harsh Shandilya 4a52524212 fix(gradle): correct property name for CC 2023-06-01 12:46:32 +05:30
Harsh Shandilya 1a94ba5a40 fix(gradle): upgrade to 8.2-rc-1 2023-06-01 12:38:05 +05:30
GitHub Actions 2022255200 chore(android): refresh baseline profile 2023-06-01 01:33:44 +00:00
renovate[bot]andGitHub ab1951f9c5 fix(deps): update dependency com.slack.eithernet:eithernet to v1.4.1 2023-05-31 19:31:40 +00:00
renovate[bot]andGitHub a9176b8508 chore(deps): update dependency io.sentry:sentry-android to v6.21.0 2023-05-31 08:54:31 +00:00
GitHub Actions 02ad998b06 chore(android): refresh baseline profile 2023-05-31 01:23:45 +00:00
GitHub Actions 31059abf23 feat(release): start next development iteration 2023-05-31 01:11:15 +00:00
GitHub Actions fc70931c2c feat(release): bump version 2023-05-31 01:07:01 +00:00
renovate[bot]andGitHub af4a6c4672 chore(deps): update agp to v8.2.0-alpha06 2023-05-31 00:40:15 +00:00
Harsh Shandilya 3347224018 feat(database): use Requery's repackaged SQLite for databases 2023-05-31 06:09:44 +05:30
Harsh Shandilya 558062a915 Revert "feat: add some navigation transitions"
This was fun but good lord it is ugly

This reverts commit 4f4b480a13.
2023-05-31 05:57:25 +05:30
Harsh Shandilya a7ab941543 chore(deps): update sqldelight to v2.0.0-rc01 2023-05-31 05:54:11 +05:30
renovate[bot]andGitHub 57b5f33720 fix(deps): update dependency com.google.truth:truth to v1.1.4 2023-05-30 17:18:20 +00:00
GitHub Actions f36d0ef5e6 chore(android): refresh baseline profile 2023-05-30 01:24:00 +00:00
GitHub Actions 0d9b0fec6b chore(android): refresh baseline profile 2023-05-29 06:19:36 +00:00
Harsh Shandilya d1b83410d0 fix(ci): adjust baseline profile path 2023-05-29 11:12:39 +05:30
Harsh Shandilya 990bb26276 fix(deps): update accompanist to 0.31.3-beta 2023-05-29 11:05:56 +05:30
Harsh Shandilya 4f4b480a13 feat: add some navigation transitions 2023-05-28 18:14:11 +05:30
Harsh Shandilya b2490cd014 fix: wrap DatabasePosts content in a Box 2023-05-28 18:13:53 +05:30
Harsh Shandilya 9d85563cf2 fix: remove deeplink for lobste.rs
It makes logging in to the website from a browser fairly annoying for minimal gain
2023-05-28 17:25:04 +05:30
Harsh Shandilya ac573d9311 chore: add changelog entry for adaptive nav 2023-05-28 17:22:52 +05:30
Harsh Shandilya 1e5ae4048c fix: make nav rail animate horizontally 2023-05-28 14:01:04 +05:30
Harsh Shandilya 3db05fb3b1 fix: remove dubious return 2023-05-28 14:01:04 +05:30
Harsh ShandilyaandGitHub 5a098e125a Apply suggestions from code review 2023-05-28 13:39:39 +05:30
Yash-Garg 78b77fd4c1 feat: add adaptive navigation for different screen sizes 2023-05-28 13:17:55 +05:30
renovate[bot]andGitHub b2470ca89b chore(deps): pin actions/cache action to 88522ab 2023-05-27 23:56:18 +00:00
Harsh Shandilya 4764698583 fix(ci): manually cache Android SDK build-tools
This is necessary to avoid the extremely slow re-download on each run
2023-05-28 03:19:10 +05:30
Harsh Shandilya c91a34aae5 chore: update changelog 2023-05-27 01:36:06 +05:30
Harsh Shandilya 8dc76f6ce5 fix(renovate): avoid R8 updates 2023-05-26 19:21:49 +05:30
Harsh Shandilya 0ebe949da6 Revert "feat(renovate): keep R8 up-to-date with ToT"
This reverts commit 25c78eed91.
2023-05-26 19:19:18 +05:30
Harsh Shandilya 6c1a40d614 fix(deps): update dependency dev.chrisbanes.compose:compose-bom to v2023.04.00-beta01.1 2023-05-26 19:18:29 +05:30
renovate[bot]andGitHub 9a81d06e2d chore(deps): update dependency io.sentry:sentry-android to v6.20.0 2023-05-26 12:40:11 +00:00
renovate[bot]andGitHub 687cd210af chore(deps): update r8 digest to c765114 2023-05-26 09:54:10 +00:00
Harsh ShandilyaandGitHub 87135c0e1b fix(renovate): stop treating R8 as a Maven dependency 2023-05-26 15:23:23 +05:30
Harsh Shandilya 48cfa6b7f1 fix(deps): update AndroidX Navigation and kotlinx.serialization 2023-05-26 01:55:49 +05:30
Harsh Shandilya 25c78eed91 feat(renovate): keep R8 up-to-date with ToT 2023-05-26 01:20:08 +05:30
renovate[bot]andGitHub 6a50387c39 chore(deps): update agp to v8.2.0-alpha05 2023-05-25 19:41:42 +00:00
renovate[bot]andHarsh Shandilya bac6f65dd0 chore(deps): update github/codeql-action action to v2.3.5 2023-05-26 01:08:19 +05:30
Harsh Shandilya 5ec34fea3b fix(ci): accept all SDK licenses 2023-05-26 01:08:19 +05:30
Harsh Shandilya 539e53c6b1 fix: add Retrofit R8 rules 2023-05-26 01:08:19 +05:30
Harsh Shandilya dfe108d13e fix(deps): use an unreleased R8 build with verifier crash fix 2023-05-26 01:08:19 +05:30
Harsh Shandilya 37e6c87046 feat: move up to AGP 8.2.x 2023-05-25 22:19:58 +05:30
renovate[bot]andGitHub e92929910d chore(deps): update github/codeql-action action to v2.3.4 2023-05-25 04:40:10 +00:00
GitHub Actions 7488afdbb3 chore(android): refresh baseline profile 2023-05-25 01:27:30 +00:00
renovate[bot]andGitHub 2dbc99a27a chore(deps): update agp to v8.0.2 2023-05-24 21:57:29 +00:00
renovate[bot]andGitHub 1dd26feebb fix(deps): update androidx 2023-05-24 18:55:21 +00:00
renovate[bot]andGitHub 203847dcd7 fix(deps): update dependency dev.chrisbanes.compose:compose-bom to v2023.04.00-beta01 2023-05-24 18:06:07 +00:00
renovate[bot]andGitHub a4a3620f71 fix(deps): update dependency com.diffplug.spotless:spotless-plugin-gradle to v6.19.0 2023-05-24 04:02:03 +00:00
GitHub Actions 5fba9bfe7b chore(android): refresh baseline profile 2023-05-24 01:27:21 +00:00
renovate[bot]andGitHub c05aa75f1e fix(deps): update dependency com.google.testparameterinjector:test-parameter-injector to v1.12 2023-05-23 11:44:26 +00:00
GitHub Actions 2f378aaeec chore(android): refresh baseline profile 2023-05-22 01:27:24 +00:00
renovate[bot]andGitHub e92279634d fix(deps): update coil to v2.4.0 2023-05-21 19:07:33 +00:00
Harsh Shandilya 1bc15b7b7c fix(renovate): disable Coil updates
Coil 2.4.0 uses Kotlin 1.8.21 that we can't use yet
2023-05-21 20:41:20 +05:30
GitHub Actions 7d8083b268 chore(android): refresh baseline profile 2023-05-21 01:26:45 +00:00
GitHub Actions 7035159fa6 chore(android): refresh baseline profile 2023-05-20 01:23:53 +00:00
renovate[bot]andGitHub a2a9446e3b fix(deps): update dependency com.slack.eithernet:eithernet to v1.4.0 2023-05-19 19:34:10 +00:00
renovate[bot]andGitHub 5c40fedde2 chore(deps): update sentry to v6.19.1 2023-05-19 12:45:15 +00:00
GitHub Actions a44ca13e3a chore(android): refresh baseline profile 2023-05-19 01:22:06 +00:00
GitHub Actions 67f3595d33 chore(android): refresh baseline profile 2023-05-18 01:27:06 +00:00
Harsh Shandilya ae10d433e6 refactor: switch to Material Icons
Our previous system resulted in leaner code but was way too annoying to add
new icons to.
2023-05-18 02:03:17 +05:30
Harsh Shandilya e33768d9ca fix(android): set assist URI for profile page 2023-05-18 02:03:17 +05:30
renovate[bot]andGitHub 7624fa204d fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v3.7.0 2023-05-17 12:31:46 +00:00
renovate[bot]andGitHub 4738674f7f chore(deps): update sentry to v6.19.0 2023-05-16 13:05:10 +00:00
Harsh Shandilya 7cbc96161e feat(build): raise target bytecode to Java 17 2023-05-16 16:14:10 +05:30
Harsh Shandilya d940ba252e chore: expand changelog 2023-05-16 13:32:46 +05:30
GitHub Actions 60507cc817 chore(android): refresh baseline profile 2023-05-15 01:20:52 +00:00
GitHub Actions 3a99c28233 chore(android): refresh baseline profile 2023-05-14 01:23:15 +00:00
Harsh Shandilya 7e91628339 feat: switch over to Compose alphas 2023-05-13 19:27:28 +05:30
GitHub Actions eb053eb5bf chore(android): refresh baseline profile 2023-05-13 01:24:08 +00:00
renovate[bot]andGitHub 7462b1a2f7 fix(deps): update dagger to v2.46.1 2023-05-12 22:00:05 +00:00
Harsh Shandilya 8d557c3898 fix(deps): update coroutines to 1.7.1 2023-05-13 02:49:04 +05:30
Harsh Shandilya 6b1c53c31a chore(gradle): sort dependencies 2023-05-12 13:15:36 +05:30
GitHub Actions 5e27937358 chore(android): refresh baseline profile 2023-05-12 01:20:49 +00:00
renovate[bot]andGitHub a8aad809ba fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v3.6.0 2023-05-11 17:18:55 +00:00
Harsh ShandilyaandGitHub d94ca0e9c6 fix(deps): remove unused kotest dependencies (#375) 2023-05-11 04:19:58 +00:00
GitHub Actions aa51535e65 chore(android): refresh baseline profile 2023-05-11 01:27:50 +00:00
Harsh Shandilya 7737c82db9 fix: switch TopAppBar to non-deprecated APIs 2023-05-11 01:30:36 +05:30
renovate[bot]andGitHub 7ee64e0ead fix(deps): update androidx 2023-05-10 19:51:26 +00:00
Harsh Shandilya 987990704c fix(renovate): group all AndroidX updates 2023-05-11 01:20:53 +05:30
GitHub Actions e8975e70f5 chore(android): refresh baseline profile 2023-05-09 01:23:53 +00:00
GitHub Actions 72409a0b73 chore(android): refresh baseline profile 2023-05-08 01:25:09 +00:00
Harsh Shandilya 3dfd2d7862 chore: rework dependency declaration 2023-05-07 11:16:19 +00:00
Harsh ShandilyaandGitHub c93d2f73a9 fix(maestro): correct tap index
I initially wrote this to tap the 1st and 3rd save button but failed to account for the fact that once the first button was pressed, its text property would no longer be "Add to saved posts" which results in index=2 actually pointing to the 4th absolute position in the list.
2023-05-06 12:25:08 +05:30
Harsh Shandilya 66d638f952 fix(benchmark): raise min SDK to align with API requirements 2023-05-06 02:24:37 +05:30
Harsh Shandilya 815a8f186b chore: rejig README 2023-05-06 02:23:39 +05:30
Harsh Shandilya ae745b917e chore: refresh README feature image 2023-05-06 02:22:04 +05:30
Harsh Shandilya bd759c53cd chore(deps): update coroutines to 1.7.0 2023-05-06 02:02:34 +05:30
renovate[bot]andGitHub 8dca324f71 chore(deps): update plugin modulegraph to v0.4.0 2023-05-05 19:49:51 +00:00
Harsh Shandilya dd7a7d9977 feat: use Maestro to automatically generate Play Store screenshots 2023-05-05 21:01:31 +05:30
Harsh Shandilya a7c81a77f2 fix(android): make nav content description less shouty 2023-05-05 20:42:27 +05:30
Harsh Shandilya 15b1e68611 Revert "feat: add gradle-dependencies-sort plugin"
The lexer/parser on this is very sensitive to custom APIs being used in the
`dependencies {}` DSL.

This reverts commit 069c5a4389.
2023-05-05 20:31:29 +05:30
Harsh Shandilya a5e25e2eb5 refactor(build): simplify test dependency setup 2023-05-05 20:31:29 +05:30
renovate[bot]andGitHub c10ec27b51 chore(deps): update github/codeql-action action to v2.3.3 2023-05-04 20:26:50 +00:00
Harsh Shandilya 9c86a9124d fix: sort dependencies 2023-05-04 15:16:31 +05:30
Harsh Shandilya 069c5a4389 feat: add gradle-dependencies-sort plugin 2023-05-04 15:09:41 +05:30
Harsh Shandilya b498cf4b62 feat: fix Sentry disabling logic again 2023-05-04 14:09:05 +05:30
GitHub Actions 70ee848a41 chore(android): refresh baseline profile 2023-05-04 01:28:37 +00:00
GitHub Actions 60fd6a4af4 feat(release): start next development iteration 2023-05-03 22:38:06 +00:00
GitHub Actions 04ed687dbb feat(release): bump version 2023-05-03 22:37:02 +00:00
Harsh Shandilya d694415a4a chore: re-sync Lint baseline 2023-05-04 03:53:36 +05:30
Harsh Shandilya 3f2a9c999b chore: sync changelog 2023-05-04 03:52:18 +05:30
Harsh Shandilya ea40650214 feat(android): migrate away from kotest 2023-05-04 03:38:23 +05:30
Harsh Shandilya ae66aced83 feat(api): migrate away from kotest 2023-05-04 03:33:47 +05:30
Harsh Shandilya 5d2eebf093 fix(api): add test util for type assertion 2023-05-04 03:33:47 +05:30
Harsh Shandilya 5c8057d71f fix(model): implement contains operator fun for Tags 2023-05-04 03:33:47 +05:30
Harsh Shandilya 4d78a73602 feat(database): migrate away from kotest 2023-05-04 03:33:47 +05:30
Harsh Shandilya 7c5a9eb683 feat(build): add JUnit 5 and Truth deps 2023-05-04 03:33:47 +05:30
renovate[bot]andGitHub df423d28d6 fix(deps): update dependency androidx.profileinstaller:profileinstaller to v1.3.1 2023-05-03 20:29:20 +00:00
renovate[bot]andHarsh Shandilya a92616c5a3 fix(deps): update dependency androidx.benchmark:benchmark-macro-junit4 to v1.2.0-alpha14 2023-05-04 01:58:30 +05:30
renovate[bot]andHarsh Shandilya 2fc29f65ca fix(deps): update dependency androidx.compose:compose-bom to v2023.05.00 2023-05-04 01:58:30 +05:30
Harsh Shandilya aab712e680 fix(android): migrate to new paging-compose APIs 2023-05-04 01:58:30 +05:30
renovate[bot]andGitHub 4bca86d91e fix(deps): update dependency androidx.paging:paging-compose to v1.0.0-alpha19 2023-05-03 18:41:29 +00:00
GitHub Actions 63cca3eba2 chore(android): refresh baseline profile 2023-05-03 01:28:04 +00:00
Harsh Shandilya cba2ed5944 fix: strip out invalid R8 rules 2023-05-03 01:58:22 +05:30
Harsh Shandilya ee5886c9b9 chore(benchmark): use method reference 2023-05-02 19:45:28 +05:30
Harsh Shandilya e96dadae34 fix(scripts): adjust emulator bootup routines 2023-05-02 19:45:28 +05:30
Harsh Shandilya a23ad82b60 fix(scripts): migrate away from deprecated ANDROID_SDK_ROOT 2023-05-02 19:45:28 +05:30
GitHub Actions f816580886 chore(android): refresh baseline profile 2023-05-02 01:28:03 +00:00
renovate[bot]andGitHub a5f8f1a4c2 chore(deps): update agp to v8.0.1 2023-05-01 18:30:20 +00:00
GitHub Actions 0d47f06be3 chore(android): refresh baseline profile 2023-05-01 01:31:57 +00:00
GitHub Actions c4eb2d2116 chore(android): refresh baseline profile 2023-04-30 01:32:24 +00:00
renovate[bot]andGitHub 8c1dc62211 fix(deps): update dependency org.jsoup:jsoup to v1.16.1 2023-04-29 08:41:46 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>Harsh Shandilya
b1a66bb8dd chore(deps): update dependency com.facebook:ktfmt to v0.44 (#374)
* chore(deps): update dependency com.facebook:ktfmt to v0.44

* chore: reformat

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Harsh Shandilya <me@msfjarvis.dev>
2023-04-29 09:48:04 +05:30
GitHub Actions 61a2cefcc3 chore(android): refresh baseline profile 2023-04-29 01:27:42 +00:00
renovate[bot]andGitHub ad775bf8e1 fix(deps): update dagger to v2.46 2023-04-28 22:48:14 +00:00
renovate[bot]andGitHub a80e8bb298 fix(deps): update dependency io.sentry:sentry-bom to v6.18.1 2023-04-28 19:06:41 +00:00
renovate[bot]andGitHub 2db51746ac chore(deps): update github/codeql-action action to v2.3.2 2023-04-28 05:12:08 +00:00
GitHub Actions 3d7e6d7f4c chore(android): refresh baseline profile 2023-04-28 01:20:55 +00:00
Harsh Shandilya 6918cf5f76 fix(core): adjust OkHttp config 2023-04-27 18:52:21 +05:30
renovate[bot]andGitHub cd05ed4da9 chore(deps): update sentry to v6.18.0 2023-04-27 12:49:45 +00:00
renovate[bot]andGitHub b669900c75 chore(deps): update github/codeql-action action to v2.3.1 2023-04-27 03:05:16 +00:00
GitHub Actions 3a9bf04a4d chore(android): refresh baseline profile 2023-04-27 01:23:43 +00:00
Harsh Shandilya 0cd4257e73 Revert "feat(build): upgrade to AGP 8.1.0-beta01"
Newer AGP releases cause incorrect desugaring of the Crux dependency

This reverts commit 68a20e0d11.
2023-04-26 14:37:03 +00:00
Harsh ShandilyaandGitHub 6f55da754c fix(deps): de-commonize AndroidX Test versioning 2023-04-25 21:11:50 +05:30
Harsh ShandilyaandGitHub 31adb45152 fix(deps): rollback AndroidX Test version to 1.6.0-alpha01
alpha02 doesn't exist
2023-04-25 20:03:31 +05:30
Harsh Shandilya 68a20e0d11 feat(build): upgrade to AGP 8.1.0-beta01 2023-04-25 18:42:05 +05:30
renovate[bot]andGitHub 52657b0c08 fix(deps): update dependency androidx.test:runner to v1.6.0-alpha02 2023-04-25 05:06:38 +00:00
Harsh Shandilya a2123b7a5d chore: remove unnecessary suppress 2023-04-25 01:23:06 +05:30
Harsh Shandilya 207f98ca17 feat(common): add a preview for LobstersCard 2023-04-25 01:20:43 +05:30
Harsh Shandilya 58170b6072 feat: disable Sentry performance reporting and profiling
These features are going paid and I do not care enough to foot the bill
2023-04-24 23:36:47 +05:30
GitHub Actions 744955bf11 chore(android): refresh baseline profile 2023-04-24 01:21:34 +00:00
renovate[bot]andGitHub cd6d53d19e fix(deps): update dependency com.squareup.okhttp3:okhttp-bom to v4.11.0 2023-04-23 02:00:02 +00:00
GitHub Actions 50e03fbd50 chore(android): refresh baseline profile 2023-04-22 01:19:16 +00:00
renovate[bot]andGitHub 6dd219ee05 chore(deps): update github/codeql-action action to v2.3.0 2023-04-21 20:06:55 +00:00
renovate[bot]andGitHub 2a6cd96c8c chore(deps): update dependency gradle to v8.1.1 2023-04-21 15:30:29 +00:00
Harsh Shandilya 1323668fe6 feat(build): add a task to collect Lint SARIF reports 2023-04-20 18:00:01 +05:30
Harsh Shandilya 24b747eb38 fix(build): correct plugin application ordering 2023-04-20 18:00:01 +05:30
Harsh Shandilya ed31d1fefb chore(lint): update lint baselines 2023-04-20 18:00:01 +05:30
Harsh Shandilya 237a06f1cf feat(build): apply Android Lint plugin to JVM modules 2023-04-20 18:00:01 +05:30
renovate[bot]andGitHub 06528e7504 fix(deps): update dependency androidx.test.uiautomator:uiautomator to v2.3.0-alpha03 2023-04-20 06:00:10 +00:00
renovate[bot]andGitHub 387f9a2601 fix(deps): update dependency androidx.navigation:navigation-compose to v2.6.0-beta01 2023-04-20 05:17:28 +00:00
renovate[bot]andGitHub 4bcfd083d6 fix(deps): update dependency androidx.core:core-splashscreen to v1.0.1 2023-04-20 01:49:30 +00:00
renovate[bot]andGitHub 0e922176fe fix(deps): update dependency androidx.activity:activity-compose to v1.7.1 2023-04-19 22:03:59 +00:00
Harsh Shandilya 250e7c20e5 feat(common): use FlowRow from Compose Foundation 2023-04-20 01:44:43 +05:30
Harsh Shandilya c3de960966 feat(deps): upgrade to Compose 2023.04.01 2023-04-20 01:19:27 +05:30
renovate[bot]andGitHub 866995b2d3 fix(deps): update dependency com.slack.lint.compose:compose-lint-checks to v1.2.0 2023-04-19 16:53:22 +00:00
Harsh Shandilya 825ee23afe chore: convert launcher assets from PNG to WebP 2023-04-19 17:25:09 +05:30
Harsh Shandilya 66ce35927a refactor(build): extract out Lint config 2023-04-19 17:16:06 +05:30
Harsh Shandilya 468d363c65 feat(build): rework Lint config
- Export SARIF output
- Expand enabled ruleset
- Make HTML reports more detailed
2023-04-19 15:19:15 +05:30
Harsh Shandilya 009744cb47 fix(ci): ensure full history is cloned 2023-04-19 14:51:37 +05:30
Harsh Shandilya 2d63a1e79f chore: sync up copyright headers 2023-04-19 12:38:36 +05:30
Harsh Shandilya 5452c55e86 chore(build): enable Spotless license header sync by default 2023-04-19 12:37:44 +05:30
Harsh Shandilya 6b164f84d9 refactor(build): migrate to Kotlin compilerOptions DSL 2023-04-19 12:36:03 +05:30
Harsh Shandilya bf104a1d85 chore(build): reorder DSL declarations 2023-04-19 12:29:27 +05:30
renovate[bot]andGitHub 1d32ec810f fix(deps): update dependency com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter to v1 2023-04-19 05:18:18 +00:00
GitHub Actions 1f7b9dbbc4 chore(android): refresh baseline profile 2023-04-19 01:22:24 +00:00
renovate[bot]andGitHub 8c0bfc2143 fix(deps): update kotest to v5.6.1 2023-04-18 20:18:21 +00:00
GitHub Actions 279ddd9974 feat(release): start next development iteration 2023-04-18 08:50:27 +00:00
GitHub Actions 71b29c0b56 feat(release): bump version 2023-04-18 08:48:54 +00:00
Harsh Shandilya b331a85a2a chore: remove Qodana configurations 2023-04-18 14:06:49 +05:30
Harsh Shandilya e8037e8d60 chore(codeql): run analysis on pushes to main branch 2023-04-18 13:57:29 +05:30
Harsh Shandilya ea0dd4cb78 feat(core): implement certificate pinning for lobste.rs 2023-04-18 13:44:31 +05:30
GitHub ActionsandHarsh Shandilya 4734ab4a22 chore(android): refresh baseline profile 2023-04-18 13:44:31 +05:30
Harsh Shandilya 4df97acd73 fix(codeql): manually build project 2023-04-18 13:44:31 +05:30
GitHub ActionsandHarsh Shandilya 8ec233d761 chore(android): refresh baseline profile 2023-04-18 13:44:31 +05:30
renovate[bot]andHarsh Shandilya 135a1fc5ed fix(deps): update kotest to v5.6.0 2023-04-18 13:44:31 +05:30
GitHub ActionsandHarsh Shandilya 4cb5b09001 chore(android): refresh baseline profile 2023-04-18 13:44:31 +05:30
Harsh Shandilya 3bb8b51cb5 chore: update changelog 2023-04-18 13:44:31 +05:30
GitHub ActionsandHarsh Shandilya 1ee31873ab chore(android): refresh baseline profile 2023-04-18 13:44:31 +05:30
Harsh Shandilya 79fac06a4c fix(benchmark): adjust baseline profile iterations 2023-04-18 13:44:31 +05:30
Harsh Shandilya 553bbedc95 feat(benchmark): migrate to Gradle Managed Devices 2023-04-18 13:44:31 +05:30
Harsh Shandilya d31fe53f78 chore: change benchmark package name 2023-04-18 13:44:18 +05:30
Harsh Shandilya be74e52156 fix: adjust baseline profile emulator options 2023-04-18 13:44:18 +05:30
Harsh Shandilya a2157ae618 feat: rework Sentry disabling logic for benchmark builds 2023-04-18 13:44:18 +05:30
Harsh Shandilya 8b7bcaa92f fix(renovate): disable Kotlin updates
Renovate can't understand how to stop
2023-04-18 13:44:18 +05:30
renovate[bot]andHarsh Shandilya 157998ddf5 fix(deps): update dependency org.gradle.android.cache-fix:org.gradle.android.cache-fix.gradle.plugin to v2.7.1 2023-04-18 13:44:18 +05:30
renovate[bot]andHarsh Shandilya cd302bb1bb chore(deps): update gradle/gradle-build-action action to v2.4.2 2023-04-18 13:44:18 +05:30
GitHub ActionsandHarsh Shandilya 52341757e4 chore(android): refresh baseline profile 2023-04-18 13:44:18 +05:30
GitHub ActionsandHarsh Shandilya a74633df54 chore(android): refresh baseline profile 2023-04-18 13:44:18 +05:30
Harsh Shandilya b936b54132 feat: rework system bar colors 2023-04-18 13:44:18 +05:30
Harsh Shandilya cf111f7852 fix: include Anvil updates in Kotlin bundle 2023-04-18 13:44:18 +05:30
renovate[bot]andHarsh Shandilya f23f9498cd chore(deps): update github/codeql-action action to v2.2.12 2023-04-18 13:44:18 +05:30
Harsh Shandilya 7c019353df feat(deps): upgrade AGP to 8.0.0 2023-04-18 13:44:18 +05:30
renovate[bot]andGitHub 3954e63141 chore(deps): update actions/checkout action to v3.5.2 2023-04-13 15:11:26 +00:00
renovate[bot]andGitHub d04d638b27 chore(deps): update gradle/gradle-build-action action to v2.4.1 2023-04-13 08:23:27 +00:00
renovate[bot]andGitHub 3ad414dadd chore(deps): update actions/checkout action to v3.5.1 2023-04-13 02:43:26 +00:00
GitHub Actions 8bc9bd4dbb chore(android): refresh baseline profile 2023-04-13 01:30:13 +00:00
renovate[bot]andGitHub b0c3066adc chore(deps): update dependency gradle to v8.1 2023-04-12 15:06:23 +00:00
GitHub Actions c5dfd4fffe chore(android): refresh baseline profile 2023-04-12 01:30:16 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
7de7b4f843 chore(deps): update dependency gradle to v8.1-rc-4 (#369)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-12 00:30:14 +05:30
GitHub Actions 7079c5a6f1 chore(android): refresh baseline profile 2023-04-09 01:24:44 +00:00
GitHub Actions 46ebda2673 chore(android): refresh baseline profile 2023-04-08 01:20:21 +00:00
GitHub Actions eee9df7d8a chore(android): refresh baseline profile 2023-04-07 01:30:51 +00:00
renovate[bot]andGitHub f8be557e6e chore(deps): update github/codeql-action action to v2.2.11 2023-04-06 17:43:27 +00:00
renovate[bot]andGitHub d6f776e12e fix(deps): update dependency com.diffplug.spotless:spotless-plugin-gradle to v6.18.0 2023-04-06 05:46:57 +00:00
GitHub Actions 6fd3f32f7e chore(android): refresh baseline profile 2023-04-06 01:25:12 +00:00
renovate[bot]andHarsh Shandilya adbb400728 fix(deps): update dependency androidx.navigation:navigation-compose to v2.6.0-alpha09
(cherry picked from commit 4a0fa59c9189eb18eff966c47d2307a5591cfbc5)
2023-04-06 01:33:59 +05:30
renovate[bot]andHarsh Shandilya 8f6a46aa2e fix(deps): update dependency androidx.datastore:datastore-preferences to v1.1.0-alpha04
(cherry picked from commit 8a78f2bd470adfad7130826b7b550d0c20e4a4e7)
2023-04-06 01:33:59 +05:30
renovate[bot]andHarsh Shandilya 0ec52b84fe fix(deps): update dependency androidx.compose:compose-bom to v2023.04.00
(cherry picked from commit c67e4d2923965a671e3043a9f503c3a0ea88013e)
2023-04-06 01:33:59 +05:30
renovate[bot]andHarsh Shandilya b436f10e18 chore(deps): update github/codeql-action action to v2.2.10
(cherry picked from commit 5fed8bd9d309fb18f4b24b09458e859fd939d9f5)
2023-04-06 01:06:50 +05:30
renovate[bot]andHarsh Shandilya 75ebf57c68 fix(deps): update dependency androidx.benchmark:benchmark-macro-junit4 to v1.2.0-alpha13
(cherry picked from commit 8293dccef8722344993e3cc55e4e6806e16db804)
2023-04-06 01:06:50 +05:30
GitHub Actions b3cea28687 chore(android): refresh baseline profile 2023-04-05 01:30:38 +00:00
renovate[bot]andGitHub 878b51fa30 fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v3.5.0 2023-04-04 19:19:14 +00:00
renovate[bot]andGitHub 81f024d197 chore(deps): update dependency gradle to v8.1-rc-3 2023-04-04 12:41:24 +00:00
renovate[bot]andHarsh Shandilya a21d665f5d chore(deps): update plugin modulegraph to v0.3.0
(cherry picked from commit d600239ba264490346e4fcaef321c50d8883119e)
2023-04-04 11:24:46 +05:30
renovate[bot]andGitHub 954bc9710b fix(deps): update accompanist to v0.30.1 2023-04-04 04:05:45 +00:00
GitHub Actions 5faeef190d chore(android): refresh baseline profile 2023-04-04 01:22:37 +00:00
GitHub Actions 43c5f93b60 chore(android): refresh baseline profile 2023-04-03 01:28:56 +00:00
Harsh Shandilya adfac94094 feat: add a module graph to the README 2023-04-02 23:44:54 +05:30
GitHub Actions bc60929fe9 chore(android): refresh baseline profile 2023-04-02 01:25:34 +00:00
GitHub Actions fdf2564f15 chore(android): refresh baseline profile 2023-04-01 01:22:47 +00:00
renovate[bot]andGitHub 8b6d422d96 chore(deps): update sentry to v6.17.0 2023-03-31 11:01:58 +00:00
GitHub Actions d2c0fea75f chore(android): refresh baseline profile 2023-03-31 01:23:47 +00:00
GitHub Actions efa5afb441 chore(android): refresh baseline profile 2023-03-30 01:28:39 +00:00
Harsh Shandilya 830144d5b1 fix(gradle): CC is now stable 2023-03-29 15:18:15 +05:30
Harsh Shandilya f6b684ceb0 chore(build): upgrade to Gradle 8.1 RC2 2023-03-29 15:18:00 +05:30
GitHub Actions cd3dfc065b chore(android): refresh baseline profile 2023-03-29 01:25:25 +00:00
Harsh Shandilya 72c1529bb9 chore: sort dependencies with gradle-dependencies-sorter 2023-03-28 12:15:18 +05:30
GitHub Actions 1b17da668d chore(android): refresh baseline profile 2023-03-28 01:32:21 +00:00
renovate[bot]andGitHub 3e7f50f4bf chore(deps): update actions/setup-java action to v3.11.0 2023-03-27 16:11:28 +00:00
renovate[bot]andGitHub 12bc6a06fb chore(deps): update github/codeql-action action to v2.2.9 2023-03-27 15:47:54 +00:00
GitHub Actions c3f146723a chore(android): refresh baseline profile 2023-03-27 01:26:13 +00:00
renovate[bot]andGitHub f64254ccef fix(deps): update coil to v2.3.0 2023-03-26 03:37:24 +00:00
GitHub Actions 0a43d9c9f6 chore(android): refresh baseline profile 2023-03-26 01:25:07 +00:00
GitHub Actions dcb9bfeb18 chore(android): refresh baseline profile 2023-03-25 01:27:05 +00:00
renovate[bot]andGitHub d6f5ea444d fix(deps): update dependency androidx.datastore:datastore-preferences to v1.1.0-alpha03 2023-03-24 19:06:44 +00:00
renovate[bot]andGitHub 3a3e02a937 fix(deps): update dependency com.google.testparameterinjector:test-parameter-injector to v1.11 2023-03-24 14:03:24 +00:00
GitHub Actions b5109fc872 feat(release): start next development iteration 2023-03-24 09:56:53 +00:00
GitHub Actions d9ed25478f feat(release): bump version 2023-03-24 09:55:40 +00:00
Harsh Shandilya f5c9da6aa0 chore: update changelog 2023-03-24 15:24:23 +05:30
Harsh Shandilya 293c04b2ad fix(build): remove DSL_SCOPE_VIOLATION suppression 2023-03-24 14:59:34 +05:30
Harsh Shandilya e91b344bea fix(build): remove no longer needed SAM conversion flag 2023-03-24 14:59:34 +05:30
Harsh Shandilya 491d8a6e71 fix(build): remove no longer needed afterEvaluate 2023-03-24 14:37:01 +05:30
Harsh Shandilya 04054914f5 chore(build): upgrade to Gradle 8.1 RC1 2023-03-24 14:35:56 +05:30
Harsh Shandilya d273c6f7b5 fix(ci): use exact versions 2023-03-24 14:27:03 +05:30
renovate[bot]andGitHub 9cf134e916 chore(deps): update actions/checkout action to v3.5.0 2023-03-24 06:43:57 +00:00
renovate[bot]andGitHub abd1e73d82 chore(deps): update actions/checkout digest to 8f4b7f8 2023-03-24 06:04:09 +00:00
GitHub Actions 5625aebb0c chore(android): refresh baseline profile 2023-03-24 01:25:53 +00:00
renovate[bot]andGitHub b84393c0d6 fix(deps): update accompanist to v0.30.0 2023-03-23 03:06:25 +00:00
GitHub Actions c0021ff5dc chore(android): refresh baseline profile 2023-03-23 01:28:23 +00:00
renovate[bot]andGitHub 9e1893612c chore(deps): update github/codeql-action digest to 67a35a0 2023-03-22 21:09:02 +00:00
Harsh Shandilya 3c2c577628 fix(deps): upgrade AndroidX dependencies 2023-03-23 01:00:07 +05:30
Harsh Shandilya 190b7b4026 fix(gradle): remove unused version declaration 2023-03-23 01:00:07 +05:30
renovate[bot]andGitHub 6fa03bad66 chore(deps): update dependency androidx.compose.compiler:compiler to v1.4.4 2023-03-22 18:49:52 +00:00
GitHub Actions 0a84659b70 chore(android): refresh baseline profile 2023-03-22 01:21:36 +00:00
GitHub Actions dec5873682 chore(android): refresh baseline profile 2023-03-21 01:30:49 +00:00
renovate[bot]andGitHub 8fc18f8324 fix(deps): update dependency io.sentry.android.gradle:io.sentry.android.gradle.gradle.plugin to v3.4.3 2023-03-20 21:00:07 +00:00
GitHub Actions 590240b283 chore(android): refresh baseline profile 2023-03-20 01:24:09 +00:00
GitHub Actions 4b90ff49fd chore(android): refresh baseline profile 2023-03-19 01:27:20 +00:00
renovate[bot]andGitHub 2223cea395 chore(deps): update reactivecircus/android-emulator-runner digest to d94c3fb 2023-03-18 11:39:49 +00:00
GitHub Actions ad692b73ba chore(android): refresh baseline profile 2023-03-18 01:23:26 +00:00
renovate[bot]andGitHub de57d089eb chore(deps): update sentry to v6.16.0 2023-03-17 10:57:52 +00:00
GitHub Actions 303a706d8a chore(android): refresh baseline profile 2023-03-17 01:23:23 +00:00
GitHub Actions 108bbbda87 chore(android): refresh baseline profile 2023-03-16 01:24:39 +00:00
renovate[bot]andGitHub 65f060a8d2 chore(deps): update actions/checkout action to v3.4.0 2023-03-15 23:04:10 +00:00
renovate[bot]andGitHub f35f0af6d8 chore(deps): update actions/checkout digest to 24cb908 2023-03-15 20:05:42 +00:00
renovate[bot]andGitHub 6c30892def chore(deps): update github/codeql-action digest to 168b99b 2023-03-15 16:58:44 +00:00
GitHub Actions 1016c85e3c chore(android): refresh baseline profile 2023-03-15 01:27:15 +00:00
renovate[bot]andGitHub 887c05c2b9 fix(deps): update dependency nl.littlerobots.version-catalog-update:nl.littlerobots.version-catalog-update.gradle.plugin to v0.8.0 2023-03-14 10:50:38 +00:00
renovate[bot]andHarsh Shandilya 98cb50fab7 chore(deps): update actions/cache action to v3.3.1
(cherry picked from commit 5d965a3cf41e5ef781451c232c5965800b2d0a7b)
2023-03-14 16:19:03 +05:30
renovate[bot]andHarsh Shandilya 38d75b0e51 fix(deps): update dependency com.diffplug.spotless:spotless-plugin-gradle to v6.17.0
(cherry picked from commit f693de3a5ef4c2c90b6e715d9221b2cc6e7a3e0a)
2023-03-14 16:19:03 +05:30
Harsh Shandilya 20a2389eb0 feat(android): set up DataStore 2023-03-14 16:19:03 +05:30
GitHub Actions 9cbc754574 chore(android): refresh baseline profile 2023-03-12 01:19:11 +00:00
GitHub Actions b564283419 chore(android): refresh baseline profile 2023-03-11 01:21:57 +00:00
renovate[bot]andGitHub 32dd218131 chore(deps): update github/codeql-action digest to 16964e9 2023-03-10 19:28:32 +00:00
GitHub Actions 09a36aa4c6 chore(android): refresh baseline profile 2023-03-10 01:29:02 +00:00
Harsh Shandilya c1f1d67bfa feat(android): add a repository for extracting CSRF token 2023-03-10 01:29:04 +05:30
renovate[bot]andGitHub 93d2397572 chore(deps): update actions/cache action to v3.3.0 2023-03-09 14:07:15 +00:00
renovate[bot]andGitHub 4b89dc202e fix(deps): update dependency androidx.profileinstaller:profileinstaller to v1.3.0-rc01 2023-03-09 08:02:47 +00:00
renovate[bot]andGitHub 0718e18fb0 fix(deps): update dependency androidx.navigation:navigation-compose to v2.6.0-alpha07 2023-03-09 04:31:28 +00:00
renovate[bot]andGitHub 90483889c4 fix(deps): update dependency androidx.lifecycle:lifecycle-viewmodel-compose to v2.6.0 2023-03-09 01:50:37 +00:00
GitHub Actions b7ef4cb2d6 chore(android): refresh baseline profile 2023-03-09 01:23:10 +00:00
renovate[bot]andGitHub b73261a339 fix(deps): update dependency androidx.benchmark:benchmark-macro-junit4 to v1.2.0-alpha11 2023-03-08 22:06:44 +00:00
renovate[bot]andGitHub 0b4fd240cc fix(deps): update dependency androidx.activity:activity-compose to v1.7.0-rc01 2023-03-08 19:58:16 +00:00
Harsh Shandilya b1af5d2349 chore: fix OkHttp capitalisation in changelog 2023-03-09 01:09:39 +05:30
Harsh Shandilya 456205dece chore(deps): bump compose-lint-checks 2023-03-09 01:08:21 +05:30
GitHub Actions 8d092a761a chore(android): refresh baseline profile 2023-03-08 01:28:52 +00:00
Harsh Shandilya 05e9c25742 fix: update incorrect comment 2023-03-08 02:50:17 +05:30
Harsh Shandilya 998171af9e chore: update changelog 2023-03-08 02:43:11 +05:30
Harsh Shandilya 3c62dd9542 fix: address Lint warnings in icon resources 2023-03-08 02:27:51 +05:30
Harsh Shandilya 55cc397333 chore: add labels to Crossfade elements 2023-03-08 02:27:51 +05:30
Harsh Shandilya b969b5f0bf refactor: use immutable collections where flagged by Lint 2023-03-08 02:27:51 +05:30
Harsh Shandilya d7cc55ed53 chore(build): make Lint errors fatal 2023-03-08 02:27:51 +05:30
Harsh Shandilya 9ab8a3e149 fix(api): avoid unnecessary build warning 2023-03-08 02:27:51 +05:30
Harsh Shandilya 0ae48dee33 feat(build): remove Detekt and switch to Slack's Compose lints 2023-03-08 02:27:51 +05:30
Harsh ShandilyaandGitHub d612ed44d9 chore: update changelog 2023-03-07 13:00:52 +05:30
Harsh Shandilya cb798cf011 feat: upgrade to OkHttp 4.10.0 2023-03-07 12:53:51 +05:30
GitHub Actions 8392841554 chore(android): refresh baseline profile 2023-03-07 01:35:08 +00:00
Harsh Shandilya 24d4d3279a fix: remove unused version catalog key 2023-03-07 01:00:07 +05:30
Harsh Shandilya 84bdcb340b feat: add Play Store feature graphic 2023-03-06 10:51:16 +05:30
Harsh Shandilya e4a28684ef feat: refresh icon assets 2023-03-06 10:49:58 +05:30
GitHub Actions 0f5698bb58 chore(android): refresh baseline profile 2023-03-06 01:24:49 +00:00
GitHub Actions ca2da81af5 chore(android): refresh baseline profile 2023-03-05 22:05:24 +00:00
Harsh Shandilya a26fd0862f fix(build): skip Sentry setup for CI benchmarks 2023-03-06 03:17:49 +05:30
Harsh Shandilya ab905d4061 fix(build): set a default for sentryDsn 2023-03-06 02:45:26 +05:30
renovate[bot]andGitHub 7c27e26da7 chore(deps): update jetbrains/qodana-jvm-android docker tag to v2023 2023-03-04 09:56:19 +00:00
renovate[bot]andGitHub 7f49333c42 chore(deps): update dependency gradle to v8.0.2 2023-03-04 05:58:54 +00:00
GitHub Actions 50d2af7be4 feat(release): start next development iteration 2023-03-02 21:52:11 +00:00
GitHub Actions 5746bbebf8 feat(release): bump version 2023-03-02 21:47:23 +00:00
Harsh Shandilya 94551a8c71 fix(release): provide SENTRY_AUTH_TOKEN during release asset build 2023-03-03 03:13:07 +05:30
Harsh Shandilya 50ed66414c chore: add missing sentry.properties 2023-03-03 03:07:00 +05:30
Harsh Shandilya 905c27fe84 feat: set up Sentry 2023-03-03 02:42:01 +05:30
GitHub Actions 62dd421cf4 feat(release): start next development iteration 2023-03-02 08:37:46 +00:00
202 changed files with 43677 additions and 40042 deletions
+1
View File
@@ -2,3 +2,4 @@
**/dependencies/** linguist-generated
**/clawicons/** linguist-generated
android/src/main/baseline-prof.txt linguist-generated -diff
android/src/main/generated/** linguist-generated -diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 KiB

After

Width:  |  Height:  |  Size: 294 KiB

+24 -34
View File
@@ -1,4 +1,5 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base",
":dependencyDashboard",
@@ -15,27 +16,37 @@
"https://maven.google.com/",
"https://repo1.maven.org/maven2",
"https://plugins.gradle.org/m2/",
"https://jitpack.io",
],
},
{
"matchPackageNames": [
"androidx.compose.compiler:compiler",
"dev.drewhamilton.poko",
"org.jetbrains.kotlin:kotlin-gradle-plugin",
"org.jetbrains.kotlin.plugin.serialization",
],
"groupName": "Kotlin and Compose",
},
{
"matchPackagePatterns": [
"^kotlin",
"^org.jetbrains.kotlin",
"^androidx.compose.compiler"
"^androidx.*",
],
"groupName": "kotlin"
"groupName": "androidx"
},
{
"managers": [
"gradle"
"matchPackagePatterns": [
"^io.sentry",
"^sentry",
],
"packagePatterns": [
// We want to stay in sync with Retrofit
"^com.squareup.okhttp3",
// Renovate refuses to stop resolving this from the snapshots repo
"^app.cash.sqldelight",
"groupName": "sentry"
},
{
"matchPackageNames": [
"com.android.tools:r8",
"com.squareup.anvil",
],
"enabled": false
"enabled": false,
},
],
"regexManagers": [
@@ -50,17 +61,6 @@
"depNameTemplate": "com.facebook:ktfmt",
"registryUrlTemplate": "https://repo1.maven.org/maven2/",
},
{
"fileMatch": [
"Detekt\.kt$"
],
"matchStrings": [
"COMPOSE_RULES_VERSION = \"(?<currentValue>.*)\""
],
"datasourceTemplate": "maven",
"depNameTemplate": "io.nlopez.compose.rules:detekt",
"registryUrlTemplate": "https://repo1.maven.org/maven2/",
},
{
"fileMatch": [
"gradle/libs.versions.toml"
@@ -72,15 +72,5 @@
"depNameTemplate": "androidx.compose.compiler:compiler",
"registryUrlTemplate": "https://maven.google.com",
},
{
"fileMatch": [
"qodana.yaml"
],
"matchStrings": [
"jetbrains/qodana-jvm-android:(?<currentValue>.*)"
],
"datasourceTemplate": "docker",
"depNameTemplate": "jetbrains/qodana-jvm-android",
}
]
],
}
+18 -65
View File
@@ -6,9 +6,8 @@ on:
- cron: '43 0 * * *'
workflow_dispatch:
env:
AVD_API_LEVEL: 31
AVD_ARCH: x86_64
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
baseline-profile:
@@ -19,13 +18,13 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
with:
token: ${{ secrets.POST_RELEASE_GH_TOKEN }}
fetch-depth: 0
- name: Set up JDK
uses: actions/setup-java@3f07048e3d294f56e9b90ac5ea2c6f74e9ad0f98 # v3.10.0
uses: actions/setup-java@cd89f46ac9d01407894225f350157564c9c7cee2 # v3.12.0
with:
distribution: temurin
java-version: 18
@@ -37,19 +36,10 @@ jobs:
git config user.email noreply@github.com
- name: Setup Gradle caching
uses: gradle/gradle-build-action@6095a76664413da4c8c134ee32e8a8ae900f0f1f # v2.4.0
uses: gradle/gradle-build-action@a4cf152f482c7ca97ef56ead29bf08bcd953284c # v2.7.0
with:
gradle-home-cache-cleanup: true
# Restore AVD from cache
- name: Cache AVD
uses: actions/cache@69d9d449aced6a2ede0bc19182fadc3a0a42d2b0 # v3.2.6
id: avd-cache
with:
path: |
~/.android/avd/*
~/.android/adb*
key: ${{ runner.os }}-avd-${{ env.AVD_API_LEVEL }}-${{ env.AVD_ARCH }}
cache-read-only: true
- name: Decrypt secrets
run: |
@@ -61,64 +51,27 @@ jobs:
# This allows us to build most of what we need without the emulator running
# and using resources
- name: Build app and benchmark
run: ./gradlew :benchmark:assembleBenchmark :android:assembleBenchmark
run: ./gradlew :benchmark:assembleNonMinifiedBenchmark :android:assembleNonMinifiedRelease
# Now use reactivecircus/android-emulator-runner to spin up an emulator. We're gonna use it again
# to actually run the test once the emulator has booted.
- name: Create AVD snapshot for caching
if: steps.avd-cache.outputs.cache-hit != 'true'
uses: reactivecircus/android-emulator-runner@50986b1464923454c95e261820bc626f38490ec0 # v2
with:
api-level: ${{ env.AVD_API_LEVEL }}
arch: ${{ env.AVD_ARCH }}
channel: canary
target: aosp_atd
force-avd-creation: false
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: false
disk-size: 8G
script: echo "Generated AVD snapshot for caching."
- name: Setup emulator for benchmark
run: ./gradlew :benchmark:api31Setup
# Run our baseline profile generator. We need to manually pull the baseline profiles off the
# emulator when using the GA runner
- name: Run benchmark
uses: reactivecircus/android-emulator-runner@50986b1464923454c95e261820bc626f38490ec0 # v2
with:
api-level: ${{ env.AVD_API_LEVEL }}
arch: ${{ env.AVD_ARCH }}
channel: canary
target: aosp_atd
disable-animations: true
disable-spellchecker: true
disk-size: 8G
script: |
# Run our benchmark, enabling only tests using BaselineProfile
./gradlew connectedBenchmarkAndroidTest -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile --stacktrace
# Need to manually pull the generated profiles from the emulator
adb pull /sdcard/Android/media/dev.msfjarvis.claw.benchmark benchmark/build/outputs/baseline-prof/
- name: Run benchmark on Gradle Managed Device
run: |
./gradlew cleanManagedDevices
./gradlew generateBaselineProfile \
-Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect"
# If we're on main branch, copy over the baseline profile and
# commit it to the repository (if changed)
# If we're on main branch, commit the baseline profile to the repository (if changed)
- name: Commit baseline profile into main
if: github.ref == 'refs/heads/main'
run: |
# Pull down any changes which may have been committed while this workflow has been running
git pull
# Sort the baseline profile, saving it to app/
sort -o android/src/main/baseline-prof.txt benchmark/build/outputs/baseline-prof/BaselineProfileGenerator_startup-baseline-prof.txt
# If the baseline profile has changed, commit it
if [[ $(git diff --stat android/src/main/baseline-prof.txt) != '' ]]; then
git add android/src/main/baseline-prof.txt
if [[ $(git diff --stat android/src/main/generated/baselineProfiles/baseline-prof.txt) != '' ]]; then
git add android/src/main/generated/baselineProfiles/baseline-prof.txt
git commit -m "chore(android): refresh baseline profile" && git push
fi
# Upload the entire generated folder and attach it to the CI run
- name: Attach baseline profile
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with:
name: Baseline profile output
path: benchmark/build/outputs/baseline-prof
- name: Clean secrets
if: always()
run: scripts/signing-cleanup.sh
+21 -17
View File
@@ -18,27 +18,23 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
with:
fetch-depth: 0
- name: Set up JDK
uses: actions/setup-java@3f07048e3d294f56e9b90ac5ea2c6f74e9ad0f98 # v3.10.0
uses: actions/setup-java@cd89f46ac9d01407894225f350157564c9c7cee2 # v3.12.0
with:
distribution: temurin
java-version: 18
- name: Run unit tests
uses: gradle/gradle-build-action@6095a76664413da4c8c134ee32e8a8ae900f0f1f # v2.4.0
uses: gradle/gradle-build-action@a4cf152f482c7ca97ef56ead29bf08bcd953284c # v2.7.0
with:
arguments: --no-configuration-cache --stacktrace check
gradle-home-cache-cleanup: true
cache-read-only: ${{ github.ref != 'refs/heads/main' }}
- name: Run Detekt
uses: gradle/gradle-build-action@6095a76664413da4c8c134ee32e8a8ae900f0f1f # v2.4.0
with:
arguments: --no-configuration-cache --stacktrace detektMain
arguments: --no-configuration-cache --stacktrace check -PslimTests
gradle-home-cache-cleanup: true
cache-read-only: ${{ github.ref != 'refs/heads/main' }}
dependency-graph: generate-and-submit
- name: (Fail-only) Upload test report
if: failure()
@@ -53,23 +49,31 @@ jobs:
needs: [ "check" ]
steps:
- name: Checkout repository
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Set up JDK
uses: actions/setup-java@3f07048e3d294f56e9b90ac5ea2c6f74e9ad0f98 # v3.10.0
uses: actions/setup-java@cd89f46ac9d01407894225f350157564c9c7cee2 # v3.12.0
with:
distribution: temurin
java-version: 18
- name: Set up Git author
shell: bash
run: |
git config user.name "GitHub Actions"
git config user.email noreply@github.com
- name: Decrypt secrets
run: |
./scripts/setup-age.sh
./scripts/signing-setup.sh "$ENCRYPT_KEY"
./scripts/signing-setup.sh "$AGE_SECRET_KEY"
env:
ENCRYPT_KEY: ${{ secrets.ENCRYPT_KEY }}
AGE_SECRET_KEY: ${{ secrets.AGE_SECRET_KEY }}
- name: Build release app
uses: gradle/gradle-build-action@6095a76664413da4c8c134ee32e8a8ae900f0f1f # v2.4.0
uses: gradle/gradle-build-action@a4cf152f482c7ca97ef56ead29bf08bcd953284c # v2.7.0
env:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
with:
arguments: --no-configuration-cache --stacktrace collectReleaseApks
gradle-home-cache-cleanup: true
@@ -79,6 +83,6 @@ jobs:
run: scripts/signing-cleanup.sh
- name: Deploy snapshot
run: scripts/deploy-snapshot.sh
run: scripts/deploy-snapshot.py
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+30 -6
View File
@@ -1,7 +1,12 @@
name: "CodeQL"
name: CodeQL
on:
workflow_dispatch:
schedule:
- cron: '31 7 * * 3'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
analyze:
@@ -13,14 +18,33 @@ jobs:
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- 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@a4cf152f482c7ca97ef56ead29bf08bcd953284c # v2.7.0
with:
gradle-home-cache-cleanup: true
cache-read-only: true
- name: Initialize CodeQL
uses: github/codeql-action/init@32dc499307d133bb5085bae78498c0ac2cf762d5 # v2
uses: github/codeql-action/init@0ba4244466797eb048eb91a6cd43d5c03ca8bd05 # v2.21.2
with:
languages: java
- name: Autobuild
uses: github/codeql-action/autobuild@32dc499307d133bb5085bae78498c0ac2cf762d5 # v2
tools: latest
queries: +security-extended
- name: Build project
shell: bash
run: |
./gradlew assemble -PslimTests
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@32dc499307d133bb5085bae78498c0ac2cf762d5 # v2
uses: github/codeql-action/analyze@0ba4244466797eb048eb91a6cd43d5c03ca8bd05 # v2.21.2
with:
category: "/language:java"
-16
View File
@@ -1,16 +0,0 @@
name: Qodana
on:
workflow_dispatch:
jobs:
qodana:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
with:
fetch-depth: 0
- name: 'Qodana Scan'
uses: JetBrains/qodana-action@7afb26c0c2f325c0d5c21ea1f617c79c7f899337 # v2022.3.4
env:
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
+13 -7
View File
@@ -2,18 +2,21 @@ name: Release to Google Play
on:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
publish-google-play-release:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
with:
token: ${{ secrets.POST_RELEASE_GH_TOKEN }}
fetch-depth: 0
- name: Set up JDK
uses: actions/setup-java@3f07048e3d294f56e9b90ac5ea2c6f74e9ad0f98 # v3.10.0
uses: actions/setup-java@cd89f46ac9d01407894225f350157564c9c7cee2 # v3.12.0
with:
distribution: temurin
java-version: 18
@@ -25,7 +28,7 @@ jobs:
git config user.email noreply@github.com
- name: Setup Gradle caching
uses: gradle/gradle-build-action@6095a76664413da4c8c134ee32e8a8ae900f0f1f # v2.4.0
uses: gradle/gradle-build-action@a4cf152f482c7ca97ef56ead29bf08bcd953284c # v2.7.0
with:
gradle-home-cache-cleanup: true
cache-read-only: true
@@ -33,9 +36,9 @@ jobs:
- name: Decrypt secrets
run: |
./scripts/setup-age.sh
./scripts/signing-setup.sh "$ENCRYPT_KEY"
./scripts/signing-setup.sh "$AGE_SECRET_KEY"
env:
ENCRYPT_KEY: ${{ secrets.ENCRYPT_KEY }}
AGE_SECRET_KEY: ${{ secrets.AGE_SECRET_KEY }}
- name: Get current version
shell: bash
@@ -45,7 +48,7 @@ jobs:
echo VERSION="${VERSION}" >> $GITHUB_ENV
- name: Update changelog
uses: msfjarvis/keep-a-changelog-new-release@ce8769801992253adb0209055c236a0cd192500b # 2.0.0
uses: msfjarvis/keep-a-changelog-new-release@bf43ae42f4e42f137572950af426efcf6e777207 # 2.1.0
with:
tag: v${{ env.VERSION }}
version: ${{ env.VERSION }}
@@ -57,8 +60,11 @@ jobs:
- name: Build release assets
shell: bash
env:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
run: |
./gradlew --no-configuration-cache collectReleaseApks collectReleaseBundle
./gradlew --no-configuration-cache collectReleaseApks collectReleaseBundle -PsentryUploadMappings
- name: Clean secrets
run: scripts/signing-cleanup.sh
+2
View File
@@ -245,3 +245,5 @@ hs_err_pid*
keystore.properties
distribution/
screenshots/
claw-export.json
+1
View File
@@ -1,2 +1,3 @@
*.json
baseline-prof.txt
**/test/resources/**
+123 -1
View File
@@ -7,6 +7,106 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [1.33.0] - 2023-07-31
### Changed
* Yet another attempt at fixing native library crashes
* Rework how baseline profiles are generated
## [1.32.0] - 2023-07-27
### Changed
* Upgrade to AndroidX July 26 release
* Revert selectable text feature due to sporadic crashes
## [1.31.0] - 2023-07-25
### Added
* Added the ability to search for posts
* Text in the comments page is now selectable
### Changed
* Upgrade to Compose July release
* Upgrade to Kotlin 1.9.0
## [1.30.0] - 2023-07-02
### Changed
* Added another workaround for native library loading crash
## [1.29.0] - 2023-06-08
### Added
* Backup and restore options for saved posts
### Fixed
* Spamming the comments button no longer causes it to be opened multiple times
* Saved posts screen now has a visual indication when you have nothing saved
* Iconography has been updated across the board to be more consistent
## [1.28.0] - 2023-06-03
### Changed
* Navigation transitions have been slightly sped up
### Fixed
* Add workaround for a native library loading crash observed on some devices
## [1.27.0] - 2023-05-31
### Changed
* Small accessibility improvements
* Slightly tweak the layout for story items to take less vertical space
* Upgrade to Compose `1.5.0-beta01` release
* Set accessibility web URI for profile screen
* Directly use Material Icons from upstream artifacts
* Upgrade dependencies
* Add adaptive navigation support (thanks @Yash-Garg)
* Use latest SQLite for backing databases
## [1.26.0] - 2023-05-03
### Changed
* Upgrade to Compose May release
* Disable Sentry performance reporting
* Migrate to Compose Foundation `FlowRow` in `LobstersCard`
## [1.25.0] - 2023-04-18
### Changed
* Make the app draw edge to edge
* Simplify Top App Bar color scheme
* Rework how baseline profiles are generated
* Upgrade to Compose April release
## [1.24.0] - 2023-03-24
### Changed
* Rebuild app icon assets to align better with Material guidelines
* Adopt Slack's Compose lints and address issues found by it
* Upgrade to OkHttp 4.x
* Upgrade to Compose March release
* Start work on support for logging in with lobste.rs
## [1.23.0] - 2023-03-02
### Added
* Introduce [Sentry](https://sentry.io) for error reporting and performance monitoring
## [1.22.0] - 2023-03-02
### Changed
@@ -176,7 +276,29 @@ 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.22.0...HEAD
[Unreleased]: https://github.com/msfjarvis/compose-lobsters/compare/v1.33.0...HEAD
[1.33.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.32.0...v1.33.0
[1.32.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.31.0...v1.32.0
[1.31.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.30.0...v1.31.0
[1.30.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.29.0...v1.30.0
[1.29.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.28.0...v1.29.0
[1.28.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.27.0...v1.28.0
[1.27.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.26.0...v1.27.0
[1.26.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.25.0...v1.26.0
[1.25.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.24.0...v1.25.0
[1.24.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.23.0...v1.24.0
[1.23.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.22.0...v1.23.0
[1.22.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.21.0...v1.22.0
+26 -3
View File
@@ -8,12 +8,35 @@ Unofficial Android app for read-only access to [lobste.rs](https://lobste.rs), b
height="80" />
</a>
> 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"
alt="Side by side screenshots of the app's main page in dark and light UI modes"
height="550" />
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" />
Snapshots from the development branch can be obtained [here](https://github.com/msfjarvis/compose-lobsters/releases/tag/nightly).
## Dependency Diagram
```mermaid
%%{
init: {
'theme': 'neutral'
}
}%%
graph LR
benchmark --> android
android --> api
android --> common
android --> core
android --> database
android --> model
api --> model
common --> core
common --> database
common --> model
```
## License
See [LICENSE](LICENSE)
-1
View File
@@ -1 +0,0 @@
-dontobfuscate
+48 -25
View File
@@ -4,23 +4,21 @@
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
@file:Suppress("DSL_SCOPE_VIOLATION", "UnstableApiUsage")
@file:Suppress("UnstableApiUsage")
import dev.msfjarvis.claw.gradle.addTestDependencies
plugins {
id("dev.msfjarvis.claw.android-application")
id("dev.msfjarvis.claw.rename-artifacts")
id("dev.msfjarvis.claw.kotlin-android")
id("dev.msfjarvis.claw.kotlin-kapt")
id("dev.msfjarvis.claw.sentry")
id("dev.msfjarvis.claw.versioning-plugin")
alias(libs.plugins.anvil)
alias(libs.plugins.modulegraphassert)
alias(libs.plugins.whetstone)
}
whetstone {
addOns {
compose.set(true)
workManager.set(true)
}
alias(libs.plugins.baselineprofile)
}
android {
@@ -31,39 +29,64 @@ android {
useLiveLiterals = false
kotlinCompilerExtensionVersion = libs.versions.composeCompiler.get()
}
buildTypes {
create("benchmark") {
signingConfig = signingConfigs["debug"]
matchingFallbacks += "release"
isDebuggable = false
proguardFile("benchmark-rules.pro")
}
packaging { jniLibs { useLegacyPackaging = true } }
}
baselineProfile {
dexLayoutOptimization = true
mergeIntoMain = true
saveInSrc = true
from(projects.benchmark.dependencyProject)
}
moduleGraphAssert {
assertOnAnyBuild = true
maxHeight = 4
restricted = arrayOf(":core -X> :.*")
}
whetstone {
addOns {
compose.set(true)
workManager.set(true)
}
}
dependencies {
kapt(libs.dagger.compiler)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.dagger)
implementation(projects.api)
implementation(projects.common)
implementation(projects.core)
implementation(projects.coroutineUtils)
implementation(projects.database)
implementation(projects.metadataExtractor)
implementation(projects.model)
implementation(platform(libs.okhttp.bom))
implementation(libs.accompanist.sysuicontroller)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.compose.material)
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)
implementation(libs.androidx.profileinstaller)
implementation(libs.androidx.work.runtime.ktx)
implementation(libs.androidx.work.runtime)
implementation(libs.coil)
implementation(libs.copydown)
implementation(libs.dagger)
implementation(libs.jsoup)
implementation(libs.kotlinx.collections.immutable)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.napier)
implementation(libs.soloader)
implementation(libs.sqldelight.extensions.coroutines)
implementation(libs.unfurl)
implementation(projects.api)
implementation(projects.common)
implementation(projects.core)
implementation(projects.database)
implementation(projects.model)
kapt(libs.dagger.compiler)
testImplementation(libs.kotlinx.coroutines.test)
testImplementation(libs.okhttp.mockwebserver)
addTestDependencies(project)
}
+23 -1
View File
@@ -1,4 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 7.4.0-alpha09" type="baseline" client="gradle" dependencies="false" name="AGP (7.4.0-alpha09)" variant="all" version="7.4.0-alpha09">
<issues format="6" by="lint 8.2.0-alpha14" type="baseline" client="gradle" dependencies="true" name="AGP (8.2.0-alpha14)" variant="all" version="8.2.0-alpha14">
<issue
id="NewerVersionAvailable"
message="A newer version of com.squareup.anvil than 2.4.7 is available: 2.4.7-1-8"
errorLine1="anvil = &quot;com.squareup.anvil:2.4.7&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="../gradle/libs.versions.toml"
line="100"
column="9"/>
</issue>
<issue
id="UsingMaterialAndMaterial3Libraries"
message="Using a material import while also using the material3 library"
errorLine1="import androidx.compose.material.ExperimentalMaterialApi"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/NetworkPosts.kt"
line="17"
column="8"/>
</issue>
</issues>
Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

+7 -10
View File
@@ -1,22 +1,19 @@
-allowaccessmodification
-dontpreverify
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-verbose
-keepattributes *Annotation*
-keepclasseswithmembernames class * {
native <methods>;
native <methods>;
}
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keepclassmembers class * implements android.os.Parcelable {
public static final ** CREATOR;
public static final ** CREATOR;
}
-keep class androidx.annotation.Keep
@@ -24,13 +21,13 @@
-keep @androidx.annotation.Keep class * {*;}
-keepclasseswithmembers class * {
@androidx.annotation.Keep <methods>;
@androidx.annotation.Keep <methods>;
}
-keepclasseswithmembers class * {
@androidx.annotation.Keep <fields>;
@androidx.annotation.Keep <fields>;
}
-keepclasseswithmembers class * {
@androidx.annotation.Keep <init>(...);
@androidx.annotation.Keep <init>(...);
}
+20
View File
@@ -1,2 +1,22 @@
-dontobfuscate
-keepattributes SourceFile, LineNumberTable
# From https://github.com/square/retrofit/blob/master/retrofit/src/main/resources/META-INF/proguard/retrofit2.pro
# With R8 full mode, it sees no subtypes of Retrofit interfaces since they are created with a Proxy
# and replaces all potential values with null. Explicitly keeping the interfaces prevents this.
-if interface * { @retrofit2.http.* <methods>; }
-keep,allowobfuscation interface <1>
# Keep inherited services.
-if interface * { @retrofit2.http.* <methods>; }
-keep,allowobfuscation interface * extends <1>
# With R8 full mode generic signatures are stripped for classes that are not
# kept. Suspend functions are wrapped in continuations where the type argument
# is used.
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
# R8 full mode strips generic signatures from return types if not kept.
-if interface * { @retrofit2.http.* public *** *(...); }
-keep,allowoptimization,allowshrinking,allowobfuscation class <3>
+6 -1
View File
@@ -1,4 +1,9 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
~ 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>
<string name="app_name">Claw β</string>
</resources>
+16 -15
View File
@@ -1,4 +1,9 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
~ 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
@@ -6,13 +11,13 @@
<application
android:name=".ClawApplication"
android:dataExtractionRules="@xml/data_extraction_rules"
android:enableOnBackInvokedCallback="true"
android:fullBackupContent="@xml/full_backup_content"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/Theme.Claw.SplashScreen"
android:enableOnBackInvokedCallback="true"
tools:targetApi="tiramisu">
<profileable
android:shell="true"
@@ -25,19 +30,6 @@
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:ssp="//lobste.rs" />
<data android:host="lobste.rs" />
<data android:path="/" />
<data android:scheme="https" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
@@ -163,5 +155,14 @@
<action android:name="androidx.profileinstaller.action.BENCHMARK_OPERATION" />
</intent-filter>
</receiver>
<!-- Required: set your sentry.io project identifier (DSN) -->
<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}" />
<!-- enable screenshot for crashes -->
<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}" />
</application>
</manifest>
-35819
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,5 +1,5 @@
/*
* Copyright © 2021-2022 Harsh Shandilya.
* 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.
@@ -15,6 +15,7 @@ import androidx.work.WorkManager
import com.deliveryhero.whetstone.Whetstone
import com.deliveryhero.whetstone.app.ApplicationComponentOwner
import com.deliveryhero.whetstone.app.ContributesAppInjector
import com.facebook.soloader.SoLoader
import dev.msfjarvis.claw.android.work.SavedPostUpdaterWorker
import dev.msfjarvis.claw.core.injection.AppPlugin
import java.util.concurrent.TimeUnit
@@ -31,6 +32,8 @@ class ClawApplication : Application(), ApplicationComponentOwner {
override fun onCreate() {
Whetstone.inject(this)
super.onCreate()
SoLoader.init(this, false)
SoLoader.loadLibrary("sqlite3x")
plugins.forEach { plugin -> plugin.apply(this) }
val postUpdateWorkRequest =
PeriodicWorkRequestBuilder<SavedPostUpdaterWorker>(POST_REFRESH_PERIOD, TimeUnit.HOURS)
@@ -1,5 +1,5 @@
/*
* Copyright © 2021-2022 Harsh Shandilya.
* 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.
@@ -11,7 +11,10 @@ import android.net.Uri
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.WindowCompat
import com.deliveryhero.whetstone.Whetstone
import com.deliveryhero.whetstone.activity.ContributesActivityInjector
import dev.msfjarvis.claw.android.ui.LobstersApp
@@ -26,14 +29,19 @@ class MainActivity : ComponentActivity() {
@Inject lateinit var htmlConverter: HTMLConverter
private var webUri: String? = null
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
installSplashScreen()
Whetstone.inject(this)
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
val windowSizeClass = calculateWindowSizeClass(this)
LobstersApp(
urlLauncher = urlLauncher,
htmlConverter = htmlConverter,
windowSizeClass = windowSizeClass,
setWebUri = { url -> webUri = url },
)
}
@@ -0,0 +1,42 @@
/*
* 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.ForScope
import com.deliveryhero.whetstone.app.ApplicationScope
import com.squareup.anvil.annotations.ContributesTo
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")
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright © 2021-2022 Harsh Shandilya.
* 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.
@@ -1,25 +1,44 @@
/*
* Copyright © 2021-2022 Harsh Shandilya.
* 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.injection
import com.chimbori.crux.Crux
import com.deliveryhero.whetstone.app.ApplicationScope
import com.squareup.anvil.annotations.ContributesTo
import dagger.Module
import dagger.Provides
import io.github.aakira.napier.Napier
import me.saket.unfurl.UnfurlLogger
import me.saket.unfurl.Unfurler
import okhttp3.OkHttpClient
@Module
@ContributesTo(ApplicationScope::class)
object MetadataExtractorModule {
@Provides
fun provideCrux(
fun provideUnfurlLogger(): UnfurlLogger {
return object : UnfurlLogger {
override fun log(message: String) {
Napier.d { message }
}
override fun log(e: Throwable, message: String) {
Napier.e(e) { message }
}
}
}
@Provides
fun provideUnfurler(
okHttpClient: OkHttpClient,
): Crux {
return Crux(okHttpClient = okHttpClient)
logger: UnfurlLogger,
): Unfurler {
return Unfurler(
httpClient = okHttpClient,
logger = logger,
)
}
}
@@ -1,15 +0,0 @@
/*
* Copyright © 2022 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 javax.inject.Qualifier
@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class DatabaseDispatcher
@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class MainDispatcher
@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class IODispatcher
@@ -1,5 +1,5 @@
/*
* Copyright © 2021-2022 Harsh Shandilya.
* 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.
@@ -13,7 +13,7 @@ import com.slack.eithernet.ApiResult.Success
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dev.msfjarvis.claw.android.injection.IODispatcher
import dev.msfjarvis.claw.core.injection.IODispatcher
import dev.msfjarvis.claw.model.LobstersPost
import java.io.IOException
import kotlinx.coroutines.CoroutineDispatcher
@@ -0,0 +1,67 @@
/*
* 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.paging
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.slack.eithernet.ApiResult
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dev.msfjarvis.claw.android.paging.LobstersPagingSource.Companion.PAGE_SIZE
import dev.msfjarvis.claw.android.paging.LobstersPagingSource.Companion.STARTING_PAGE_INDEX
import dev.msfjarvis.claw.core.injection.IODispatcher
import dev.msfjarvis.claw.model.LobstersPost
import java.io.IOException
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.
*/
class SearchPagingSource
@AssistedInject
constructor(
@Assisted private val remoteFetcher: RemoteFetcher<LobstersPost>,
@IODispatcher private val ioDispatcher: CoroutineDispatcher,
) : PagingSource<Int, LobstersPost>() {
override fun getRefreshKey(state: PagingState<Int, LobstersPost>): Int? {
return state.anchorPosition?.let { anchorPosition ->
(anchorPosition / PAGE_SIZE).coerceAtLeast(STARTING_PAGE_INDEX)
}
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, LobstersPost> {
val page = params.key ?: STARTING_PAGE_INDEX
return when (val result = withContext(ioDispatcher) { remoteFetcher.getItemsAtPage(page) }) {
is ApiResult.Success -> {
// Optimization: prevent fetching more pages if we found no items, this means
// there is no active search query.
val nextKey = if (result.value.isEmpty()) null else page + 1
LoadResult.Page(
itemsBefore = (page - 1) * PAGE_SIZE,
data = result.value,
prevKey = if (page == STARTING_PAGE_INDEX) null else page - 1,
nextKey = nextKey,
)
}
is ApiResult.Failure.NetworkFailure -> LoadResult.Error(result.error)
is ApiResult.Failure.UnknownFailure -> LoadResult.Error(result.error)
is ApiResult.Failure.HttpFailure,
is ApiResult.Failure.ApiFailure ->
LoadResult.Error(IOException("API returned an invalid response"))
}
}
@AssistedFactory
interface Factory {
fun create(remoteFetcher: RemoteFetcher<LobstersPost>): SearchPagingSource
}
}
@@ -0,0 +1,23 @@
/*
* 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")
}
}
@@ -6,23 +6,38 @@
*/
package dev.msfjarvis.claw.android.ui
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.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.ImportExport
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.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.SideEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
@@ -38,43 +53,46 @@ import androidx.navigation.navArgument
import androidx.navigation.navDeepLink
import androidx.paging.compose.collectAsLazyPagingItems
import com.deliveryhero.whetstone.compose.injectedViewModel
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import dev.msfjarvis.claw.android.R
import dev.msfjarvis.claw.android.ui.datatransfer.DataTransferScreen
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.decorations.TransparentSystemBars
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.viewmodel.ClawViewModel
import dev.msfjarvis.claw.api.LobstersApi
import dev.msfjarvis.claw.common.comments.CommentsPage
import dev.msfjarvis.claw.common.comments.HTMLConverter
import dev.msfjarvis.claw.common.res.ClawIcons
import dev.msfjarvis.claw.common.theme.LobstersTheme
import dev.msfjarvis.claw.common.ui.decorations.ClawAppBar
import dev.msfjarvis.claw.common.ui.surfaceColorAtNavigationBarElevation
import dev.msfjarvis.claw.common.urllauncher.UrlLauncher
import dev.msfjarvis.claw.common.user.UserProfile
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.coroutines.launch
@OptIn(
ExperimentalComposeUiApi::class,
ExperimentalMaterial3Api::class,
)
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun LobstersApp(
urlLauncher: UrlLauncher,
htmlConverter: HTMLConverter,
windowSizeClass: WindowSizeClass,
setWebUri: (String?) -> Unit,
modifier: Modifier = Modifier,
viewModel: ClawViewModel = injectedViewModel(),
) {
val systemUiController = rememberSystemUiController()
val hottestListState = rememberLazyListState()
val newestListState = rememberLazyListState()
val savedListState = rememberLazyListState()
val searchListState = rememberLazyListState()
val navController = rememberNavController()
val coroutineScope = rememberCoroutineScope()
val snackbarHostState = remember { SnackbarHostState() }
val postActions = rememberPostActions(urlLauncher, navController, viewModel)
val backStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = backStackEntry?.destination?.route
@@ -82,156 +100,195 @@ fun LobstersApp(
val hottestPosts = viewModel.hottestPosts.collectAsLazyPagingItems()
val newestPosts = viewModel.newestPosts.collectAsLazyPagingItems()
val savedPosts by viewModel.savedPosts.collectAsState(emptyMap())
val savedPosts by viewModel.savedPosts.collectAsState(persistentMapOf())
val navigationType = ClawNavigationType.fromSize(windowSizeClass.widthSizeClass)
LobstersTheme(
dynamicColor = true,
providedValues = arrayOf(LocalUriHandler provides urlLauncher),
) {
val currentUiMode = LocalConfiguration.current.uiMode
val systemBarsColor = MaterialTheme.colorScheme.surfaceColorAtNavigationBarElevation()
val backgroundColor = MaterialTheme.colorScheme.background
val navItems =
listOf(
persistentListOf(
NavigationItem(
label = "Hottest",
route = Destinations.Hottest.route,
icon = ClawIcons.Flame,
selectedIcon = ClawIcons.FlameFilled,
icon = Icons.Outlined.Whatshot,
selectedIcon = Icons.Filled.Whatshot,
) {
coroutineScope.launch { hottestListState.animateScrollToItem(index = 0) }
},
NavigationItem(
label = "Newest",
route = Destinations.Newest.route,
icon = ClawIcons.New,
selectedIcon = ClawIcons.NewFilled,
icon = Icons.Outlined.NewReleases,
selectedIcon = Icons.Filled.NewReleases,
) {
coroutineScope.launch { newestListState.animateScrollToItem(index = 0) }
},
NavigationItem(
label = "Saved",
route = Destinations.Saved.route,
icon = ClawIcons.HeartBorder,
selectedIcon = ClawIcons.Heart,
icon = Icons.Outlined.FavoriteBorder,
selectedIcon = Icons.Filled.Favorite,
) {
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) }
},
)
SideEffect { systemUiController.setStatusBarColor(color = systemBarsColor) }
// Track UI mode as a key to force a navbar color update when dark theme is toggled
LaunchedEffect(currentDestination, currentUiMode) {
val color =
if (navItems.none { item -> item.route == currentDestination }) {
backgroundColor
} else {
systemBarsColor
}
systemUiController.setNavigationBarColor(color = color)
}
TransparentSystemBars()
Scaffold(
topBar = {
ClawAppBar(
backgroundColor = systemBarsColor,
navigationIcon = {
if (navItems.none { it.route == currentDestination }) {
IconButton(
onClick = { if (!navController.popBackStack()) context.getActivity()?.finish() }
if (currentDestination != Destinations.Search.route) {
ClawAppBar(
navigationIcon = {
if (
navController.previousBackStackEntry != null &&
navItems.none { it.route == currentDestination }
) {
Icon(
painter = ClawIcons.ArrowBack,
contentDescription = "Go back to previous screen",
)
IconButton(
onClick = { if (!navController.popBackStack()) context.getActivity()?.finish() },
) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = "Go back to previous screen",
)
}
}
}
},
title = {
if (navItems.any { it.route == currentDestination }) {
Text(text = stringResource(R.string.app_name), fontWeight = FontWeight.Bold)
}
},
)
},
title = {
if (navItems.any { it.route == currentDestination }) {
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",
)
}
}
},
)
}
},
bottomBar = {
ClawNavigationBar(
navController = navController,
items = navItems,
isVisible = navItems.any { it.route == currentDestination },
)
AnimatedVisibility(visible = navigationType == ClawNavigationType.BOTTOM_NAVIGATION) {
ClawNavigationBar(
navController = navController,
items = navItems,
isVisible = navItems.any { it.route == currentDestination },
)
}
},
snackbarHost = { SnackbarHost(snackbarHostState) },
modifier = modifier.semantics { testTagsAsResourceId = true },
) { paddingValues ->
NavHost(
navController = navController,
startDestination = Destinations.startDestination.route,
modifier = Modifier.padding(paddingValues),
) {
val uri = LobstersApi.BASE_URL
composable(
route = Destinations.Hottest.route,
deepLinks =
listOf(navDeepLink { uriPattern = uri }, navDeepLink { uriPattern = "$uri/" }),
Row(modifier = Modifier.padding(paddingValues)) {
AnimatedVisibility(visible = navigationType == ClawNavigationType.NAVIGATION_RAIL) {
ClawNavigationRail(
navController = navController,
items = navItems,
isVisible = navItems.any { it.route == currentDestination },
)
}
NavHost(
navController = navController,
startDestination = Destinations.startDestination.route,
// Make animations 2x faster than default specs
enterTransition = { fadeIn(animationSpec = tween(350)) },
exitTransition = { fadeOut(animationSpec = tween(350)) },
) {
setWebUri("https://lobste.rs/")
NetworkPosts(
lazyPagingItems = hottestPosts,
listState = hottestListState,
isPostSaved = viewModel::isPostSaved,
postActions = postActions,
)
}
composable(
route = Destinations.Newest.route,
) {
setWebUri("https://lobste.rs/")
NetworkPosts(
lazyPagingItems = newestPosts,
listState = newestListState,
isPostSaved = viewModel::isPostSaved,
postActions = postActions,
)
}
composable(Destinations.Saved.route) {
setWebUri(null)
DatabasePosts(
items = savedPosts,
listState = savedListState,
postActions = postActions,
)
}
composable(
route = Destinations.Comments.route,
arguments = listOf(navArgument("postId") { type = NavType.StringType }),
deepLinks =
listOf(
navDeepLink { uriPattern = "$uri/s/${Destinations.Comments.placeholder}/.*" },
navDeepLink { uriPattern = "$uri/s/${Destinations.Comments.placeholder}" },
),
) { backStackEntry ->
val postId = requireNotNull(backStackEntry.arguments?.getString("postId"))
setWebUri("https://lobste.rs/s/$postId")
CommentsPage(
postId = postId,
postActions = postActions,
htmlConverter = htmlConverter,
getSeenComments = viewModel::getSeenComments,
markSeenComments = viewModel::markSeenComments,
)
}
composable(
route = Destinations.User.route,
arguments = listOf(navArgument("username") { type = NavType.StringType }),
deepLinks =
listOf(navDeepLink { uriPattern = "$uri/u/${Destinations.User.placeholder}" }),
) { backStackEntry ->
val username = requireNotNull(backStackEntry.arguments?.getString("username"))
UserProfile(
username = username,
getProfile = viewModel::getUserProfile,
)
val uri = LobstersApi.BASE_URL
composable(route = Destinations.Hottest.route) {
setWebUri("https://lobste.rs/")
NetworkPosts(
lazyPagingItems = hottestPosts,
listState = hottestListState,
isPostSaved = viewModel::isPostSaved,
postActions = postActions,
)
}
composable(route = Destinations.Newest.route) {
setWebUri("https://lobste.rs/")
NetworkPosts(
lazyPagingItems = newestPosts,
listState = newestListState,
isPostSaved = viewModel::isPostSaved,
postActions = postActions,
)
}
composable(route = Destinations.Saved.route) {
setWebUri(null)
DatabasePosts(
items = savedPosts,
listState = savedListState,
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 }),
deepLinks =
listOf(
navDeepLink { uriPattern = "$uri/s/${Destinations.Comments.placeholder}/.*" },
navDeepLink { uriPattern = "$uri/s/${Destinations.Comments.placeholder}" },
),
) { backStackEntry ->
val postId = requireNotNull(backStackEntry.arguments?.getString("postId"))
setWebUri("https://lobste.rs/s/$postId")
CommentsPage(
postId = postId,
postActions = postActions,
htmlConverter = htmlConverter,
getSeenComments = viewModel::getSeenComments,
markSeenComments = viewModel::markSeenComments,
)
}
composable(
route = Destinations.User.route,
arguments = listOf(navArgument("username") { type = NavType.StringType }),
deepLinks =
listOf(navDeepLink { uriPattern = "$uri/u/${Destinations.User.placeholder}" }),
) { backStackEntry ->
val username = requireNotNull(backStackEntry.arguments?.getString("username"))
setWebUri("https://lobste.rs/u/$username")
UserProfile(
username = username,
getProfile = viewModel::getUserProfile,
)
}
composable(route = Destinations.DataTransfer.route) {
DataTransferScreen(
context = context,
importPosts = viewModel::importPosts,
exportPosts = viewModel::exportPosts,
snackbarHostState = snackbarHostState,
)
}
}
}
}
@@ -0,0 +1,138 @@
/*
* 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.datatransfer
import android.content.Context
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument
import androidx.activity.result.contract.ActivityResultContracts.GetContent
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.height
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Download
import androidx.compose.material.icons.filled.Upload
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
import java.io.InputStream
import java.io.OutputStream
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
private const val MIME_TYPE = "application/json"
@Composable
fun DataTransferScreen(
context: Context,
importPosts: suspend (InputStream) -> Unit,
exportPosts: suspend (OutputStream) -> Unit,
snackbarHostState: SnackbarHostState,
modifier: Modifier = Modifier,
) {
val coroutineScope = rememberCoroutineScope()
Column(modifier = modifier) {
ImportOption(context, coroutineScope, importPosts, snackbarHostState)
ExportOption(context, coroutineScope, exportPosts, snackbarHostState)
}
}
@Composable
private fun ImportOption(
context: Context,
coroutineScope: CoroutineScope,
importPosts: suspend (InputStream) -> Unit,
snackbarHostState: SnackbarHostState,
) {
val importAction =
rememberLauncherForActivityResult(GetContent()) { uri ->
if (uri == null) {
coroutineScope.launch { snackbarHostState.showSnackbarDismissing("No file selected") }
return@rememberLauncherForActivityResult
}
coroutineScope.launch {
context.contentResolver.openInputStream(uri)?.use { stream ->
importPosts(stream)
snackbarHostState.showSnackbarDismissing("Successfully imported posts")
}
}
}
SettingsActionItem(
title = "Import saved posts",
description = "Import saved posts from a previously generated export",
icon = Icons.Filled.Download,
) {
importAction.launch(MIME_TYPE)
}
}
@Composable
private fun ExportOption(
context: Context,
coroutineScope: CoroutineScope,
exportPosts: suspend (OutputStream) -> Unit,
snackbarHostState: SnackbarHostState,
) {
val exportAction =
rememberLauncherForActivityResult(CreateDocument(MIME_TYPE)) { uri ->
if (uri == null) {
coroutineScope.launch { snackbarHostState.showSnackbarDismissing("No file selected") }
return@rememberLauncherForActivityResult
}
coroutineScope.launch {
context.contentResolver.openOutputStream(uri)?.use { stream ->
exportPosts(stream)
snackbarHostState.showSnackbarDismissing("Successfully exported posts")
}
}
}
SettingsActionItem(
title = "Export posts to file",
description = "Write all saved posts into a JSON file that can be imported at a later date",
icon = Icons.Filled.Upload,
) {
exportAction.launch("claw-export.json")
}
}
@Composable
private fun SettingsActionItem(
title: String,
modifier: Modifier = Modifier,
description: String? = null,
icon: ImageVector? = null,
onClick: (() -> Unit)? = null,
) {
ListItem(
headlineContent = { Text(title) },
supportingContent = { description?.let { Text(it) } },
leadingContent = {
icon?.let {
Icon(
imageVector = icon,
contentDescription = null,
modifier = Modifier.height(32.dp),
)
}
},
modifier = modifier.clickable { onClick?.invoke() },
)
}
/** Shows a Snackbar but dismisses any existing ones first. */
private suspend fun SnackbarHostState.showSnackbarDismissing(
text: String,
) {
currentSnackbarData?.dismiss()
showSnackbar(text)
}
@@ -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.
@@ -19,17 +19,18 @@ import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.testTag
import androidx.navigation.NavController
import dev.msfjarvis.claw.android.ui.navigation.Destinations
import kotlinx.collections.immutable.ImmutableList
private const val AnimationDuration = 100
const val AnimationDuration = 100
@Composable
fun ClawNavigationBar(
navController: NavController,
items: List<NavigationItem>,
items: ImmutableList<NavigationItem>,
isVisible: Boolean,
modifier: Modifier = Modifier,
) {
@@ -54,10 +55,10 @@ fun ClawNavigationBar(
val isCurrentDestination = navController.currentDestination?.route == navItem.route
NavigationBarItem(
icon = {
Crossfade(isCurrentDestination) {
Crossfade(isCurrentDestination, label = "nav-label") {
Icon(
painter = if (it) navItem.selectedIcon else navItem.icon,
contentDescription = navItem.label.uppercase()
imageVector = if (it) navItem.selectedIcon else navItem.icon,
contentDescription = navItem.label.replaceFirstChar(Char::uppercase),
)
}
},
@@ -66,13 +67,13 @@ fun ClawNavigationBar(
onClick = {
if (isCurrentDestination) {
navItem.listStateResetCallback()
return@NavigationBarItem
}
navController.graph.startDestinationRoute?.let { startDestination ->
navController.popBackStack(startDestination, false)
}
if (navItem.route != Destinations.startDestination.route) {
navController.navigate(navItem.route)
} else {
navController.graph.startDestinationRoute?.let { startDestination ->
navController.popBackStack(startDestination, false)
}
if (navItem.route != Destinations.startDestination.route) {
navController.navigate(navItem.route)
}
}
},
modifier = Modifier.testTag(navItem.label.uppercase()),
@@ -85,7 +86,7 @@ fun ClawNavigationBar(
class NavigationItem(
val label: String,
val route: String,
val icon: Painter,
val selectedIcon: Painter,
val icon: ImageVector,
val selectedIcon: ImageVector,
val listStateResetCallback: () -> Unit,
)
@@ -0,0 +1,84 @@
/*
* 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.
*/
package dev.msfjarvis.claw.android.ui.decorations
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.FastOutLinearInEasing
import androidx.compose.animation.core.LinearOutSlowInEasing
import androidx.compose.animation.core.tween
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.layout.Spacer
import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationRail
import androidx.compose.material3.NavigationRailItem
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.navigation.NavController
import dev.msfjarvis.claw.android.ui.navigation.Destinations
import kotlinx.collections.immutable.ImmutableList
@Composable
fun ClawNavigationRail(
navController: NavController,
items: ImmutableList<NavigationItem>,
isVisible: Boolean,
modifier: Modifier = Modifier,
) {
AnimatedVisibility(
visible = isVisible,
enter =
slideInHorizontally(
// Enters by sliding in from offset -fullWidth to 0.
initialOffsetX = { fullWidth -> -fullWidth },
animationSpec = tween(durationMillis = AnimationDuration, easing = LinearOutSlowInEasing),
),
exit =
slideOutHorizontally(
// Exits by sliding out from offset 0 to -fullWidth.
targetOffsetX = { fullWidth -> -fullWidth },
animationSpec = tween(durationMillis = AnimationDuration, easing = FastOutLinearInEasing),
),
modifier = Modifier,
) {
NavigationRail(modifier = modifier) {
Spacer(Modifier.weight(1f))
items.forEach { navItem ->
val isCurrentDestination = navController.currentDestination?.route == navItem.route
NavigationRailItem(
icon = {
Crossfade(isCurrentDestination, label = "nav-label") {
Icon(
imageVector = if (it) navItem.selectedIcon else navItem.icon,
contentDescription = navItem.label.replaceFirstChar(Char::uppercase),
)
}
},
label = { Text(text = navItem.label) },
selected = isCurrentDestination,
onClick = {
if (isCurrentDestination) {
navItem.listStateResetCallback()
} else {
navController.graph.startDestinationRoute?.let { startDestination ->
navController.popBackStack(startDestination, false)
}
if (navItem.route != Destinations.startDestination.route) {
navController.navigate(navItem.route)
}
}
},
modifier = Modifier.testTag(navItem.label.uppercase()),
)
}
Spacer(Modifier.weight(1f))
}
}
}
@@ -0,0 +1,25 @@
/*
* 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.decorations
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.graphics.Color
import com.google.accompanist.systemuicontroller.rememberSystemUiController
@Composable
fun TransparentSystemBars() {
val systemUiController = rememberSystemUiController()
val useDarkIcons = !isSystemInDarkTheme()
DisposableEffect(systemUiController, useDarkIcons) {
systemUiController.setSystemBarsColor(color = Color.Transparent, darkIcons = useDarkIcons)
onDispose {}
}
}
@@ -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.
@@ -19,8 +19,6 @@ import dev.msfjarvis.claw.common.urllauncher.UrlLauncher
import dev.msfjarvis.claw.database.local.SavedPost
import dev.msfjarvis.claw.model.LinkMetadata
import dev.msfjarvis.claw.model.LobstersPostDetails
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
fun Context.getActivity(): ComponentActivity? {
return when (this) {
@@ -30,14 +28,6 @@ fun Context.getActivity(): ComponentActivity? {
}
}
/**
* Parses a given [String] into a [LocalDateTime]. This method is only intended to be used for dates
* in the format returned by the Lobsters API, and is not a general purpose parsing solution.
*/
fun String.toLocalDateTime(): LocalDateTime {
return LocalDateTime.from(DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(this))
}
@Composable
fun rememberPostActions(
urlLauncher: UrlLauncher,
@@ -51,9 +41,10 @@ fun rememberPostActions(
}
override fun viewComments(postId: String) {
navController.navigate(
val currentRoute = navController.currentDestination?.route
val newRoute =
Destinations.Comments.route.replace(Destinations.Comments.placeholder, postId)
)
if (currentRoute != Destinations.Comments.route) navController.navigate(newRoute)
}
override fun viewCommentsPage(commentsUrl: String) {
@@ -6,40 +6,67 @@
*/
package dev.msfjarvis.claw.android.ui.lists
import androidx.activity.compose.ReportDrawn
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Divider
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Inbox
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import dev.msfjarvis.claw.common.posts.PostActions
import dev.msfjarvis.claw.common.ui.decorations.MonthHeader
import dev.msfjarvis.claw.database.local.SavedPost
import java.time.Month
import kotlinx.collections.immutable.ImmutableMap
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun DatabasePosts(
items: Map<Month, List<SavedPost>>,
items: ImmutableMap<Month, List<SavedPost>>,
listState: LazyListState,
postActions: PostActions,
modifier: Modifier = Modifier,
) {
LazyColumn(
state = listState,
modifier = modifier,
) {
items.forEach { (month, posts) ->
stickyHeader { MonthHeader(month = month) }
items(posts, { it.shortId }) { item ->
ListItem(
item = item,
isSaved = { true },
postActions = postActions,
ReportDrawn()
Box(modifier = modifier.fillMaxSize()) {
if (items.isEmpty()) {
Column(modifier = Modifier.align(Alignment.Center)) {
Icon(
imageVector = Icons.Filled.Inbox,
contentDescription = "Empty inbox icon",
modifier = Modifier.align(Alignment.CenterHorizontally).size(36.dp),
)
Divider()
Text(text = "No saved posts", style = MaterialTheme.typography.headlineSmall)
}
} else {
LazyColumn(state = listState) {
items.forEach { (month, posts) ->
stickyHeader(contentType = "month-header") { MonthHeader(month = month) }
items(
items = posts,
key = { it.shortId },
contentType = { "LobstersItem" },
) { item ->
ListItem(
item = item,
isSaved = { true },
postActions = postActions,
)
HorizontalDivider()
}
}
}
}
}
@@ -1,11 +1,12 @@
/*
* Copyright © 2021-2022 Harsh Shandilya.
* 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.ui.lists
import androidx.activity.compose.ReportDrawnWhen
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -17,7 +18,7 @@ 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.Divider
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
@@ -26,7 +27,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.paging.LoadState
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.items
import androidx.paging.compose.itemContentType
import androidx.paging.compose.itemKey
import dev.msfjarvis.claw.common.posts.PostActions
import dev.msfjarvis.claw.common.posts.toDbModel
import dev.msfjarvis.claw.common.ui.NetworkError
@@ -43,6 +45,7 @@ fun NetworkPosts(
postActions: PostActions,
modifier: Modifier = Modifier,
) {
ReportDrawnWhen { lazyPagingItems.itemCount > 0 }
val refreshLoadState = lazyPagingItems.loadState.refresh
val isRefreshing = refreshLoadState == LoadState.Loading
val pullRefreshState = rememberPullRefreshState(isRefreshing, lazyPagingItems::refresh)
@@ -57,7 +60,12 @@ fun NetworkPosts(
LazyColumn(
state = listState,
) {
items(items = lazyPagingItems, key = { it.shortId }) { item ->
items(
count = lazyPagingItems.itemCount,
key = lazyPagingItems.itemKey { it.shortId },
contentType = lazyPagingItems.itemContentType { "LobstersItem" },
) { index ->
val item = lazyPagingItems[index]
if (item != null) {
val dbModel = item.toDbModel()
ListItem(
@@ -66,7 +74,7 @@ fun NetworkPosts(
postActions = postActions,
)
Divider()
HorizontalDivider()
}
}
if (lazyPagingItems.loadState.append == LoadState.Loading) {
@@ -0,0 +1,94 @@
/*
* 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.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
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import androidx.paging.PagingData
import androidx.paging.compose.collectAsLazyPagingItems
import dev.msfjarvis.claw.common.posts.PostActions
import dev.msfjarvis.claw.common.ui.SearchBar
import dev.msfjarvis.claw.database.local.SavedPost
import dev.msfjarvis.claw.model.LobstersPost
import kotlinx.coroutines.flow.Flow
@Composable
fun SearchList(
items: Flow<PagingData<LobstersPost>>,
listState: LazyListState,
isPostSaved: suspend (SavedPost) -> Boolean,
postActions: PostActions,
searchQuery: String,
setSearchQuery: (String) -> Unit,
modifier: Modifier = Modifier,
) {
val lazyPagingItems = items.collectAsLazyPagingItems()
val triggerSearch = { query: String ->
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()) {
SearchBar(
value = searchQuery,
onValueChange = setSearchQuery,
onSearch = {
triggerSearch(it)
searchActive = true
},
modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp).testTag("search_bar"),
)
if (searchActive) {
NetworkPosts(
lazyPagingItems = lazyPagingItems,
listState = listState,
isPostSaved = isPostSaved,
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)
}
}
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright © 2021-2022 Harsh Shandilya.
* 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.
@@ -21,6 +21,10 @@ 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"
@@ -31,6 +35,10 @@ sealed class Destinations {
override val route = "user/$placeholder"
}
object DataTransfer : Destinations() {
override val route: String = "datatransfer"
}
companion object {
val startDestination
get() = Hottest
@@ -0,0 +1,24 @@
/*
* 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.navigation
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
enum class ClawNavigationType {
BOTTOM_NAVIGATION,
NAVIGATION_RAIL;
companion object {
fun fromSize(windowSize: WindowWidthSizeClass): ClawNavigationType {
return when (windowSize) {
WindowWidthSizeClass.Medium,
WindowWidthSizeClass.Expanded -> NAVIGATION_RAIL
else -> BOTTOM_NAVIGATION
}
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright © 2022 Harsh Shandilya.
* 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.
@@ -0,0 +1,36 @@
/*
* 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.viewmodel
import dev.msfjarvis.claw.api.injection.BaseUrl
import dev.msfjarvis.claw.core.injection.IODispatcher
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
import org.jsoup.Jsoup
/** Helper for extracting CSRF token for authenticated requests to https://lobste.rs. */
class CSRFRepository
@Inject
constructor(
private val okHttpClient: OkHttpClient,
@IODispatcher private val dispatcher: CoroutineDispatcher,
@BaseUrl private val url: String,
) {
suspend fun extractToken(): String? {
val request = Request.Builder().url(url).build()
return withContext(dispatcher) {
okHttpClient.newCall(request).execute().use { response ->
val doc = Jsoup.parse(response.body?.string() ?: return@use null)
val element = doc.select("meta[name=\"csrf-token\"]").first() ?: return@use null
return@use element.attr("content")
}
}
}
}
@@ -6,6 +6,9 @@
*/
package dev.msfjarvis.claw.android.viewmodel
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
@@ -13,18 +16,26 @@ import androidx.paging.PagingConfig
import com.deliveryhero.whetstone.viewmodel.ContributesViewModel
import com.slack.eithernet.ApiResult.Failure
import com.slack.eithernet.ApiResult.Success
import dev.msfjarvis.claw.android.injection.IODispatcher
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.ui.toLocalDateTime
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.database.local.SavedPost
import dev.msfjarvis.claw.model.Comment
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.net.HttpURLConnection
import java.time.LocalDateTime
import java.time.Month
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import javax.inject.Inject
import kotlinx.collections.immutable.ImmutableMap
import kotlinx.collections.immutable.toImmutableMap
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
@@ -36,9 +47,13 @@ class ClawViewModel
@Inject
constructor(
private val api: LobstersApi,
private val searchApi: LobstersSearchApi,
private val savedPostsRepository: SavedPostsRepository,
private val commentsRepository: CommentsRepository,
private val linkMetadataRepository: LinkMetadataRepository,
private val dataTransferRepository: DataTransferRepository,
private val pagingSourceFactory: LobstersPagingSource.Factory,
private val searchPagingSourceFactory: SearchPagingSource.Factory,
@IODispatcher private val ioDispatcher: CoroutineDispatcher,
) : ViewModel() {
private val hottestPostsPager =
@@ -49,6 +64,10 @@ constructor(
Pager(PagingConfig(pageSize = PAGE_SIZE), initialKey = STARTING_PAGE_INDEX) {
pagingSourceFactory.create(api::getNewestPosts)
}
private val searchResultsPager =
Pager(PagingConfig(pageSize = PAGE_SIZE), initialKey = STARTING_PAGE_INDEX) {
searchPagingSourceFactory.create { searchApi.searchPosts(searchQuery, it) }
}
val hottestPosts
get() = hottestPostsPager.flow
@@ -62,9 +81,14 @@ constructor(
val savedPosts
get() = savedPostsFlow.map(::mapSavedPosts)
private fun mapSavedPosts(items: List<SavedPost>): Map<Month, List<SavedPost>> {
val searchResults
get() = searchResultsPager.flow
var searchQuery by mutableStateOf("")
private fun mapSavedPosts(items: List<SavedPost>): ImmutableMap<Month, List<SavedPost>> {
val sorted = items.sortedByDescending { post -> post.createdAt.toLocalDateTime() }
return sorted.groupBy { post -> post.createdAt.toLocalDateTime().month }
return sorted.groupBy { post -> post.createdAt.toLocalDateTime().month }.toImmutableMap()
}
suspend fun isPostSaved(post: SavedPost): Boolean {
@@ -99,10 +123,10 @@ constructor(
}
}
suspend fun getSeenComments(postId: String) = savedPostsRepository.getSeenComments(postId)
suspend fun getSeenComments(postId: String) = commentsRepository.getSeenComments(postId)
fun markSeenComments(postId: String, comments: List<Comment>) {
viewModelScope.launch { savedPostsRepository.markSeenComments(postId, comments) }
viewModelScope.launch { commentsRepository.markSeenComments(postId, comments) }
}
suspend fun getLinkMetadata(url: String) =
@@ -118,4 +142,18 @@ constructor(
is Failure.ApiFailure -> throw IOException("API returned an invalid response")
}
}
suspend fun importPosts(input: InputStream) = dataTransferRepository.importPosts(input)
suspend fun exportPosts(output: OutputStream) = dataTransferRepository.exportPosts(output)
/**
* Parses a given [String] into a [LocalDateTime]. This method is only intended to be used for
* dates in the format returned by the Lobsters API, and is not a general purpose parsing
* solution.
*/
private fun String.toLocalDateTime(): LocalDateTime {
if (isEmpty()) return LocalDateTime.now(ZoneId.systemDefault())
return LocalDateTime.from(DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(this))
}
}
@@ -0,0 +1,33 @@
/*
* 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.viewmodel
import dev.msfjarvis.claw.core.injection.DatabaseDispatcher
import dev.msfjarvis.claw.database.LobstersDatabase
import dev.msfjarvis.claw.database.local.PostComments
import dev.msfjarvis.claw.model.Comment
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
class CommentsRepository
@Inject
constructor(
database: LobstersDatabase,
@DatabaseDispatcher private val dbDispatcher: CoroutineDispatcher,
) {
private val postCommentsQueries = database.postCommentsQueries
suspend fun getSeenComments(postId: String) =
withContext(dbDispatcher) { postCommentsQueries.getCommentIds(postId).executeAsOneOrNull() }
suspend fun markSeenComments(postId: String, comments: List<Comment>) {
withContext(dbDispatcher) {
postCommentsQueries.rememberComments(PostComments(postId, comments.map { it.shortId }))
}
}
}
@@ -0,0 +1,54 @@
/*
* 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.viewmodel
import dev.msfjarvis.claw.core.injection.DatabaseDispatcher
import dev.msfjarvis.claw.core.injection.IODispatcher
import dev.msfjarvis.claw.database.LobstersDatabase
import dev.msfjarvis.claw.database.SavedPostSerializer
import dev.msfjarvis.claw.database.local.SavedPost
import java.io.InputStream
import java.io.OutputStream
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import kotlinx.serialization.json.encodeToStream
@OptIn(ExperimentalSerializationApi::class)
class DataTransferRepository
@Inject
constructor(
private val database: LobstersDatabase,
private val json: Json,
@DatabaseDispatcher private val dbDispatcher: CoroutineDispatcher,
@IODispatcher private val ioDispatcher: CoroutineDispatcher,
) {
private val serializer = ListSerializer(SavedPostSerializer)
private val savedPostQueries = database.savedPostQueries
suspend fun importPosts(input: InputStream) {
val posts: List<SavedPost> =
withContext(ioDispatcher) {
json.decodeFromStream(
serializer,
input,
)
}
withContext(dbDispatcher) {
database.transaction { posts.forEach(savedPostQueries::insertOrReplacePost) }
}
}
suspend fun exportPosts(output: OutputStream) {
val posts = withContext(dbDispatcher) { savedPostQueries.selectAllPosts().executeAsList() }
withContext(ioDispatcher) { json.encodeToStream(serializer, posts, output) }
}
}
@@ -1,21 +1,26 @@
/*
* 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.
*/
package dev.msfjarvis.claw.android.viewmodel
import dev.msfjarvis.claw.metadata.MetadataExtractor
import dev.msfjarvis.claw.core.injection.IODispatcher
import dev.msfjarvis.claw.model.LinkMetadata
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
import me.saket.unfurl.Unfurler
class LinkMetadataRepository
@Inject
constructor(
private val metadataExtractor: MetadataExtractor,
private val unfurler: Unfurler,
@IODispatcher private val dispatcher: CoroutineDispatcher,
) {
suspend fun getLinkMetadata(url: String): LinkMetadata {
return metadataExtractor.getExtractedMetadata(url)
val result = withContext(dispatcher) { unfurler.unfurl(url) }
return LinkMetadata(url, result?.favicon?.toString())
}
}
@@ -8,11 +8,9 @@ package dev.msfjarvis.claw.android.viewmodel
import app.cash.sqldelight.coroutines.asFlow
import app.cash.sqldelight.coroutines.mapToList
import dev.msfjarvis.claw.android.injection.DatabaseDispatcher
import dev.msfjarvis.claw.core.injection.DatabaseDispatcher
import dev.msfjarvis.claw.database.LobstersDatabase
import dev.msfjarvis.claw.database.local.PostComments
import dev.msfjarvis.claw.database.local.SavedPost
import dev.msfjarvis.claw.model.Comment
import io.github.aakira.napier.Napier
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -25,7 +23,6 @@ constructor(
@DatabaseDispatcher private val dbDispatcher: CoroutineDispatcher,
) {
private val savedPostQueries = database.savedPostQueries
private val postCommentsQueries = database.postCommentsQueries
val savedPosts = savedPostQueries.selectAllPosts().asFlow().mapToList(dbDispatcher)
suspend fun savePost(post: SavedPost) {
@@ -47,15 +44,6 @@ constructor(
withContext(dbDispatcher) { savedPostQueries.deletePost(post.shortId) }
}
suspend fun getSeenComments(postId: String) =
withContext(dbDispatcher) { postCommentsQueries.getCommentIds(postId).executeAsOneOrNull() }
suspend fun markSeenComments(postId: String, comments: List<Comment>) {
withContext(dbDispatcher) {
postCommentsQueries.rememberComments(PostComments(postId, comments.map { it.shortId }))
}
}
private companion object {
private const val TAG = "SavedPostsRepository"
}
@@ -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.
@@ -41,7 +41,6 @@ constructor(
.map { post -> lobstersApi.getPostDetails(post.shortId) }
.filterIsInstance<Success<LobstersPostDetails>>()
.map { result -> result.value.toDbModel() }
.toList()
.let { savedPostsRepository.savePosts(it) }
return Result.success()
}
@@ -1,6 +1,11 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
~ 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.
-->
<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="@mipmap/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 B

After

Width:  |  Height:  |  Size: 44 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 B

After

Width:  |  Height:  |  Size: 44 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 B

After

Width:  |  Height:  |  Size: 46 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 B

After

Width:  |  Height:  |  Size: 52 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

+6 -1
View File
@@ -1,4 +1,9 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
~ 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>
<bool name="use_light_status_bar">false</bool>
</resources>
+6 -1
View File
@@ -1,4 +1,9 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
~ 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>
<bool name="use_light_status_bar">true</bool>
</resources>
+6 -1
View File
@@ -1,4 +1,9 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
~ 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>
<string name="app_name">Claw</string>
</resources>
+6 -1
View File
@@ -1,4 +1,9 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
~ 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>
<style name="Theme.Claw.SplashScreen" parent="Theme.SplashScreen">
@@ -1,4 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<data-extraction-rules>
<cloud-backup>
<include
@@ -1,4 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<full-backup-content>
<include
domain="database"
@@ -0,0 +1,61 @@
/*
* 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.viewmodel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.runTest
import okhttp3.OkHttpClient
import okhttp3.mockwebserver.Dispatcher
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import okhttp3.mockwebserver.RecordedRequest
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
class CSRFRepositoryTest {
@Test
fun `correctly extracts CSRF token`() = runTest {
val repo =
CSRFRepository(
OkHttpClient.Builder().build(),
Dispatchers.Default,
server.url("/").toString(),
)
assertThat(repo.extractToken())
.isEqualTo(
"OZWykgFemPVeOSNmB53-ccKXe458X7xCInO1-qzFU6nk_9RCSrSQqS9OPmA5_pyy8qD3IYAIZ7XfAM3gdhJpkQ"
)
}
companion object {
private val server = MockWebServer()
@JvmStatic
@BeforeAll
fun setup() {
val dispatcher =
object : Dispatcher() {
override fun dispatch(request: RecordedRequest): MockResponse {
return when (val path = request.path) {
"/" ->
MockResponse()
.setResponseCode(200)
.setBody(
javaClass.classLoader!!
.getResourceAsStream("csrf_page.html")
.readAllBytes()
.decodeToString(),
)
else -> error("Invalid path: $path")
}
}
}
server.dispatcher = dispatcher
}
}
}
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -1,5 +1,5 @@
#
# This file was automatically generated by 'versioning-plugin'. DO NOT EDIT MANUALLY.
#
versioning-plugin.versionCode=12200
versioning-plugin.versionName=1.22.0
versioning-plugin.versionCode=13300
versioning-plugin.versionName=1.33.0
+10 -8
View File
@@ -1,14 +1,14 @@
/*
* Copyright © 2021-2022 Harsh Shandilya.
* 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.
*/
@file:Suppress("DSL_SCOPE_VIOLATION", "UnstableApiUsage")
import dev.msfjarvis.claw.gradle.addTestDependencies
plugins {
id("dev.msfjarvis.claw.kotlin-android")
id("dev.msfjarvis.claw.android-library")
id("dev.msfjarvis.claw.kotlin-android")
alias(libs.plugins.anvil)
alias(libs.plugins.whetstone)
}
@@ -18,14 +18,16 @@ android { namespace = "dev.msfjarvis.claw.api" }
anvil { generateDaggerFactories.set(true) }
dependencies {
api(projects.model)
api(libs.retrofit)
api(libs.eithernet)
api(libs.retrofit)
api(projects.model)
implementation(libs.dagger)
implementation(libs.javax.inject)
implementation(libs.jsoup)
testImplementation(testFixtures(libs.eithernet))
testImplementation(libs.kotest.assertions.core)
testImplementation(libs.kotest.runner.junit5)
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.0.0-alpha08" type="baseline" client="gradle" dependencies="false" name="AGP (8.0.0-alpha08)" variant="all" version="8.0.0-alpha08">
<issues format="6" by="lint 8.2.0-alpha14" type="baseline" client="gradle" dependencies="true" name="AGP (8.2.0-alpha14)" variant="all" version="8.2.0-alpha14">
</issues>
@@ -9,7 +9,6 @@ package dev.msfjarvis.claw.api
import com.slack.eithernet.ApiResult
import dev.msfjarvis.claw.model.LobstersPost
import dev.msfjarvis.claw.model.LobstersPostDetails
import dev.msfjarvis.claw.model.Tags
import dev.msfjarvis.claw.model.User
import retrofit2.http.GET
import retrofit2.http.Path
@@ -30,12 +29,6 @@ interface LobstersApi {
@GET("u/{username}.json")
suspend fun getUser(@Path("username") username: String): ApiResult<User, Unit>
@GET("t/{tags}.json")
suspend fun getPostsByTags(
@Path("tags") tag: Tags,
@Query("page") page: Int,
): ApiResult<List<LobstersPost>, Unit>
companion object {
const val BASE_URL = "https://lobste.rs"
}
@@ -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.
*/
package dev.msfjarvis.claw.api
import com.slack.eithernet.ApiResult
import dev.msfjarvis.claw.model.LobstersPost
import retrofit2.http.GET
import retrofit2.http.Query
interface LobstersSearchApi {
@GET("/search?what=stories&order=newest")
suspend fun searchPosts(
@Query("q") searchQuery: String,
@Query("page") page: Int,
): ApiResult<List<LobstersPost>, Unit>
}
@@ -0,0 +1,85 @@
/*
* 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.converters
import dev.msfjarvis.claw.api.LobstersApi
import dev.msfjarvis.claw.model.LobstersPost
import dev.msfjarvis.claw.model.User
import java.lang.reflect.Type
import okhttp3.ResponseBody
import org.jsoup.Jsoup
import org.jsoup.nodes.Element
import org.jsoup.select.Elements
import retrofit2.Converter
import retrofit2.Retrofit
object SearchConverter : Converter<ResponseBody, List<LobstersPost>> {
override fun convert(value: ResponseBody): List<LobstersPost> {
val elements =
Jsoup.parse(value.string(), LobstersApi.BASE_URL).select("div.story_liner.h-entry")
return elements.map(::parsePost)
}
private fun parsePost(elem: Element): LobstersPost {
val parent = elem.parent() ?: error("$elem must have a parent")
val shortId = parent.attr("data-shortid")
val titleElement = elem.select("span.link.h-cite > a")
val title = titleElement.text()
val url = titleElement.attr("href")
val tags = elem.select("span.tags > a").map(Element::text)
val (commentCount, commentsUrl) = getCommentsData(elem.select("span.comments_label"))
val submitter =
getSubmitter(elem.select("div.byline").first() ?: error("No byline element found"))
return LobstersPost(
shortId = shortId,
title = title,
url = url,
commentCount = commentCount,
commentsUrl = commentsUrl,
tags = tags,
submitter = submitter,
// The value of these fields is irrelevant for our use case
createdAt = "",
description = "",
)
}
private fun getCommentsData(elem: Elements): Pair<Int, String> {
val linkElement = elem.select("a")
val countString = linkElement.text().trimStart().substringBefore(" ")
val commentsUrl = linkElement.attr("href")
return (countString.toIntOrNull() ?: 0) to commentsUrl
}
/**
* Make a bare-bones [User] object given a byline [elem]. We only need this to be usable for
* displaying in a list.
*/
private fun getSubmitter(elem: Element): User {
val userElement = elem.select("a.u-author")
val avatarElement = elem.select("img.avatar")
val username = userElement.text()
val avatarUrl = avatarElement.attr("src")
return User(
username = username,
about = "",
invitedBy = null,
avatarUrl = avatarUrl,
createdAt = "",
)
}
object Factory : Converter.Factory() {
override fun responseBodyConverter(
type: Type,
annotations: Array<out Annotation>,
retrofit: Retrofit
): Converter<ResponseBody, List<LobstersPost>> {
return SearchConverter
}
}
}
@@ -1,42 +0,0 @@
/*
* Copyright © 2021-2022 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.injection
import com.deliveryhero.whetstone.app.ApplicationScope
import com.slack.eithernet.ApiResultCallAdapterFactory
import com.slack.eithernet.ApiResultConverterFactory
import com.squareup.anvil.annotations.ContributesTo
import dagger.Module
import dagger.Provides
import dev.msfjarvis.claw.api.LobstersApi
import okhttp3.OkHttpClient
import retrofit2.Converter
import retrofit2.Retrofit
import retrofit2.create
@Module
@ContributesTo(ApplicationScope::class)
object ApiModule {
@Provides
fun provideRetrofit(
client: OkHttpClient,
converterFactories: Set<@JvmSuppressWildcards Converter.Factory>,
): Retrofit {
return Retrofit.Builder()
.client(client)
.baseUrl(LobstersApi.BASE_URL)
.addConverterFactory(ApiResultConverterFactory)
.addCallAdapterFactory(ApiResultCallAdapterFactory)
.apply { converterFactories.forEach(this::addConverterFactory) }
.build()
}
@Provides
fun provideApi(retrofit: Retrofit): LobstersApi {
return retrofit.create()
}
}
@@ -0,0 +1,98 @@
/*
* 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.injection
import com.deliveryhero.whetstone.app.ApplicationScope
import com.slack.eithernet.ApiResultCallAdapterFactory
import com.slack.eithernet.ApiResultConverterFactory
import com.squareup.anvil.annotations.ContributesTo
import dagger.Module
import dagger.Provides
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.converters.SearchConverter
import javax.inject.Qualifier
import okhttp3.OkHttpClient
import retrofit2.CallAdapter
import retrofit2.Converter
import retrofit2.Retrofit
import retrofit2.create
/**
* Ideally the multibindings used here would only use [dagger.multibindings.IntoSet], but its lack
* of ordering guarantees means that we roll a die on each app launch that [Converter]s and
* [CallAdapter]s are in the correct order to be able to deserialize responses. Thus, the module
* uses [IntoMap] with [IntKey]s to fake the presence of a fixed order by sorting on the key of the
* injected [Map]s when injecting them into [Retrofit].
*/
@Module
@ContributesTo(ApplicationScope::class)
object RetrofitModule {
@Provides
fun provideRetrofit(
client: OkHttpClient,
converterFactories: Map<Int, @JvmSuppressWildcards Converter.Factory>,
callAdapterFactories: Map<Int, @JvmSuppressWildcards CallAdapter.Factory>,
@BaseUrl baseUrl: String,
): Retrofit {
return Retrofit.Builder()
.client(client)
.baseUrl(baseUrl)
.apply { converterFactories.toSortedMap().values.forEach(this::addConverterFactory) }
.apply { callAdapterFactories.toSortedMap().values.forEach(this::addCallAdapterFactory) }
.build()
}
@Provides
@SearchApi
fun provideSearchApiRetrofit(
client: OkHttpClient,
@SearchApi converterFactories: List<@JvmSuppressWildcards Converter.Factory>,
callAdapterFactories: Map<Int, @JvmSuppressWildcards CallAdapter.Factory>,
@BaseUrl baseUrl: String,
): Retrofit {
return Retrofit.Builder()
.client(client)
.baseUrl(baseUrl)
.apply { converterFactories.forEach(this::addConverterFactory) }
.apply { callAdapterFactories.toSortedMap().values.forEach(this::addCallAdapterFactory) }
.addConverterFactory(SearchConverter.Factory)
.build()
}
@Provides fun provideApi(retrofit: Retrofit): LobstersApi = retrofit.create()
@Provides
fun provideSearchApi(@SearchApi retrofit: Retrofit): LobstersSearchApi = retrofit.create()
@Provides
@IntKey(0)
@IntoMap
fun provideApiResultConverter(): Converter.Factory = ApiResultConverterFactory
@Provides
@IntKey(0)
@IntoMap
fun provideApiResultCallAdapter(): CallAdapter.Factory = ApiResultCallAdapterFactory
@Provides
@SearchApi
fun provideConverters(): List<@JvmSuppressWildcards Converter.Factory> =
listOf(
ApiResultConverterFactory,
SearchConverter.Factory,
)
@Provides @BaseUrl fun provideBaseUrl(): 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
@@ -6,65 +6,47 @@
*/
package dev.msfjarvis.claw.api
import com.google.common.truth.Truth.assertThat
import com.slack.eithernet.ApiResult.Success
import com.slack.eithernet.test.newEitherNetController
import dev.msfjarvis.claw.model.LobstersPost
import dev.msfjarvis.claw.model.LobstersPostDetails
import dev.msfjarvis.claw.model.Tags
import dev.msfjarvis.claw.model.User
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.collections.shouldHaveSize
import io.kotest.matchers.shouldBe
import io.kotest.matchers.types.shouldBeTypeOf
import dev.msfjarvis.claw.util.TestUtils.assertIs
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
class ApiTest : FunSpec() {
class ApiTest {
private val wrapper = ApiWrapper(newEitherNetController())
private val api
get() = wrapper.api
init {
test("api gets correct number of items") {
val posts = api.getHottestPosts(1)
posts.shouldBeTypeOf<Success<List<LobstersPost>>>()
posts.value shouldHaveSize 25
}
@Test
fun `api gets correct number of items`() = runTest {
val posts = api.getHottestPosts(1)
assertIs<Success<List<LobstersPost>>>(posts)
assertThat(posts.value).hasSize(25)
}
test("posts with no urls") {
val posts = api.getHottestPosts(1)
posts.shouldBeTypeOf<Success<List<LobstersPost>>>()
val commentsOnlyPosts = posts.value.asSequence().filter { it.url.isEmpty() }.toSet()
commentsOnlyPosts shouldHaveSize 2
}
@Test
fun `posts with no urls`() = runTest {
val posts = api.getHottestPosts(1)
assertIs<Success<List<LobstersPost>>>(posts)
val commentsOnlyPosts = posts.value.asSequence().filter { it.url.isEmpty() }.toSet()
assertThat(commentsOnlyPosts).hasSize(2)
}
test("post details with comments") {
val postDetails = api.getPostDetails("tdfoqh")
postDetails.shouldBeTypeOf<Success<LobstersPostDetails>>()
postDetails.value.comments shouldHaveSize 7
}
@Test
fun `post details with comments`() = runTest {
val postDetails = api.getPostDetails("tdfoqh")
assertIs<Success<LobstersPostDetails>>(postDetails)
assertThat(postDetails.value.comments).hasSize(7)
}
test("get user details") {
val user = api.getUser("msfjarvis")
user.shouldBeTypeOf<Success<User>>()
user.value.username shouldBe "msfjarvis"
}
test("get posts by single tag") {
var tags = Tags()
tags.addTag("meta")
val posts = api.getPostsByTags(tags, 1)
posts.shouldBeTypeOf<Success<List<LobstersPost>>>()
posts.value shouldHaveSize 25
posts.value[0].tags.contains("meta")
}
test("get posts by multiple tags") {
var tags = Tags()
tags.addTag("programming")
tags.addTag("rust")
val posts = api.getPostsByTags(tags, 1)
posts.shouldBeTypeOf<Success<List<LobstersPost>>>()
posts.value shouldHaveSize 25
posts.value[0].tags.contains("programming") or posts.value[0].tags.contains("rust")
}
@Test
fun `get user details`() = runTest {
val user = api.getUser("msfjarvis")
assertIs<Success<User>>(user)
assertThat(user.value.username).isEqualTo("msfjarvis")
}
}
@@ -12,9 +12,8 @@ import com.slack.eithernet.test.enqueue
import dev.msfjarvis.claw.model.LobstersPost
import dev.msfjarvis.claw.model.LobstersPostDetails
import dev.msfjarvis.claw.model.User
import dev.msfjarvis.claw.util.TestUtils.getJson
import dev.msfjarvis.claw.util.TestUtils.getResource
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonNamingStrategy
@@ -24,13 +23,10 @@ class ApiWrapper(controller: EitherNetController<LobstersApi>) {
ignoreUnknownKeys = true
namingStrategy = JsonNamingStrategy.SnakeCase
}
private val hottest: List<LobstersPost> = json.decodeFromString(getJson("hottest.json"))
private val hottest: List<LobstersPost> = json.decodeFromString(getResource("hottest.json"))
private val postDetails: LobstersPostDetails =
json.decodeFromString(getJson("post_details_tdfoqh.json"))
private val user: User = json.decodeFromString(getJson("msfjarvis.json"))
private val metaPosts: List<LobstersPost> = json.decodeFromString(getJson("meta.json"))
private val programmingRustPosts: List<LobstersPost> =
json.decodeFromString(getJson("programming_rust.json"))
json.decodeFromString(getResource("post_details_tdfoqh.json"))
private val user: User = json.decodeFromString(getResource("msfjarvis.json"))
val api = controller.api
@@ -39,7 +35,5 @@ class ApiWrapper(controller: EitherNetController<LobstersApi>) {
controller.enqueue(LobstersApi::getHottestPosts) { success(hottest) }
controller.enqueue(LobstersApi::getPostDetails) { success(postDetails) }
controller.enqueue(LobstersApi::getUser) { success(user) }
controller.enqueue(LobstersApi::getPostsByTags) { success(metaPosts) }
controller.enqueue(LobstersApi::getPostsByTags) { success(programmingRustPosts) }
}
}
@@ -0,0 +1,59 @@
/*
* 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.slack.eithernet.ApiResult
import com.slack.eithernet.test.newEitherNetController
import dev.msfjarvis.claw.model.LobstersPost
import dev.msfjarvis.claw.model.User
import dev.msfjarvis.claw.util.TestUtils.assertIs
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
class SearchApiTest {
private val wrapper = SearchApiWrapper(newEitherNetController())
private val api
get() = wrapper.api
@Test
fun `search is able to parse HTML`() = runTest {
val posts = api.searchPosts("ChatGPT", 1)
assertIs<ApiResult.Success<List<LobstersPost>>>(posts)
assertThat(posts.value).containsAtLeastElementsIn(results).inOrder()
assertThat(posts.value).hasSize(20)
}
private companion object {
private val results =
listOf(
LobstersPost(
shortId = "gjlsdg",
title = "ChatGPT visits the Emacs doctor",
url = "https://xenodium.com/chatgpt-visits-the-emacs-doctor/",
createdAt = "",
commentCount = 3,
commentsUrl = "/s/gjlsdg/chatgpt_visits_emacs_doctor",
submitter = User("xenodium", "", null, "/avatars/xenodium-16.png", ""),
tags = listOf("ai", "emacs"),
description = "",
),
LobstersPost(
shortId = "astcqf",
title =
"Implementing a question-answering system for PDF documents using ChatGPT and Redis",
url = "https://mstack.nl/blog/20230623-chatgpt-question-pdf-document/",
createdAt = "",
commentCount = 0,
commentsUrl = "/s/astcqf/implementing_question_answering_system",
submitter = User("asteroid", "", null, "/avatars/asteroid-16.png", ""),
tags = listOf("ai"),
description = "",
),
)
}
}
@@ -0,0 +1,29 @@
/*
* 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.Companion.success
import com.slack.eithernet.test.EitherNetController
import com.slack.eithernet.test.enqueue
import dev.msfjarvis.claw.api.converters.SearchConverter
import dev.msfjarvis.claw.util.TestUtils.getResource
import okhttp3.MediaType
import okhttp3.ResponseBody
class SearchApiWrapper(controller: EitherNetController<LobstersSearchApi>) {
val api = controller.api
init {
controller.enqueue(LobstersSearchApi::searchPosts) {
success(
SearchConverter.convert(
ResponseBody.create(MediaType.get("text/html"), getResource("search_chatgpt_page.html"))
)
)
}
}
}
@@ -1,18 +1,28 @@
/*
* Copyright © 2021-2022 Harsh Shandilya.
* 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.util
import com.google.common.truth.Truth.assertThat
import java.io.File
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
@OptIn(ExperimentalContracts::class)
object TestUtils {
fun getJson(path: String): String {
fun getResource(path: String): String {
// Load the JSON response
val uri = javaClass.classLoader.getResource(path)
val uri = javaClass.classLoader!!.getResource(path)
val file = File(uri.path)
return String(file.readBytes())
}
inline fun <reified T> assertIs(value: Any?): T {
contract { returns() implies (value is T) }
assertThat(value).isInstanceOf(T::class.java)
return value as T
}
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+25 -7
View File
@@ -1,21 +1,27 @@
/*
* 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.
*/
@file:Suppress("DSL_SCOPE_VIOLATION", "UnstableApiUsage")
@file:Suppress("UnstableApiUsage")
import com.android.build.api.dsl.ManagedVirtualDevice
plugins {
alias(libs.plugins.android.test)
id("dev.msfjarvis.claw.android-common")
id("dev.msfjarvis.claw.kotlin-android")
alias(libs.plugins.baselineprofile)
}
android {
namespace = "dev.msfjarvis.claw.benchmark"
defaultConfig { testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" }
compileSdk = 34
defaultConfig {
minSdk = 28
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
create("benchmark") {
@@ -25,8 +31,22 @@ android {
}
}
testOptions.managedDevices.devices {
create<ManagedVirtualDevice>("api31") {
device = "Pixel 6"
apiLevel = 31
systemImageSource = "aosp"
}
}
targetProjectPath = ":android"
experimentalProperties["android.experimental.self-instrumenting"] = true
experimentalProperties["android.experimental.r8.dex-startup-optimization"] = true
}
baselineProfile {
managedDevices += "api31"
useConnectedDevices = false
enableEmulatorDisplay = false
}
dependencies {
@@ -39,5 +59,3 @@ dependencies {
implementation(libs.androidx.test.runner)
implementation(libs.androidx.test.uiautomator)
}
androidComponents { beforeVariants(selector().all()) { it.enable = it.buildType == "benchmark" } }
+6 -1
View File
@@ -1,4 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<queries>
<package android:name="dev.msfjarvis.claw.android" />
@@ -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.
@@ -8,6 +8,7 @@ package dev.msfjarvis.claw.benchmark
import androidx.benchmark.macro.BaselineProfileMode
import androidx.benchmark.macro.CompilationMode
import androidx.benchmark.macro.FrameTimingMetric
import androidx.benchmark.macro.StartupMode
import androidx.benchmark.macro.StartupTimingMetric
import androidx.benchmark.macro.junit4.MacrobenchmarkRule
@@ -28,29 +29,39 @@ class BaselineProfileBenchmark {
}
@Test
fun startupNoCompilation() {
startup(CompilationMode.None())
fun noCompilation() {
exploreUI(CompilationMode.None())
}
@Test
fun startupBaselineFullyCompiled() {
startup(CompilationMode.Full())
fun fullyCompiled() {
exploreUI(CompilationMode.Full())
}
@Test
fun startupBaselineProfile() {
startup(CompilationMode.Partial(baselineProfileMode = BaselineProfileMode.Require))
fun baselineProfile() {
exploreUI(CompilationMode.Partial(baselineProfileMode = BaselineProfileMode.Require))
}
private fun startup(compilationMode: CompilationMode) {
private fun exploreUI(compilationMode: CompilationMode) {
benchmarkRule.measureRepeated(
packageName = PACKAGE_NAME,
metrics = listOf(StartupTimingMetric()),
iterations = 10,
metrics = listOf(FrameTimingMetric(), StartupTimingMetric()),
compilationMode = compilationMode,
startupMode = StartupMode.COLD,
compilationMode = compilationMode
iterations = 10,
) {
device.executeShellCommand("pm clear $PACKAGE_NAME")
startActivityAndWait()
device.waitForIdle()
device.savePosts()
device.exploreScreens()
device.returnToHottestScreen()
}
}
}
@@ -6,7 +6,6 @@
*/
package dev.msfjarvis.claw.benchmark
import androidx.benchmark.macro.ExperimentalStableBaselineProfilesApi
import androidx.benchmark.macro.junit4.BaselineProfileRule
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
@@ -14,7 +13,6 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
@OptIn(ExperimentalStableBaselineProfilesApi::class)
class BaselineProfileGenerator {
@get:Rule val baselineProfileRule = BaselineProfileRule()
private lateinit var device: UiDevice
@@ -26,11 +24,22 @@ class BaselineProfileGenerator {
}
@Test
fun startup() =
baselineProfileRule.collectStableBaselineProfile(
fun generateBaselineProfile() =
baselineProfileRule.collect(
packageName = PACKAGE_NAME,
maxIterations = 15,
maxIterations = 8,
includeInStartupProfile = true,
) {
device.executeShellCommand("pm clear $PACKAGE_NAME")
startActivityAndWait()
device.waitForIdle()
device.savePosts()
device.exploreScreens()
device.returnToHottestScreen()
}
}
@@ -1,60 +1,43 @@
/*
* 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.
*/
package dev.msfjarvis.claw.benchmark
import androidx.benchmark.macro.MacrobenchmarkScope
import androidx.test.uiautomator.By
import androidx.test.uiautomator.BySelector
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
const val PACKAGE_NAME = "dev.msfjarvis.claw.android"
private const val AWAIT_TIMEOUT = 10_000L
private const val SAVE_BUTTON_DESC = "Add to saved posts"
private const val SAVE_BUTTON_ID = "save_button"
private const val NAV_ID_HOTTEST = "HOTTEST"
private const val NAV_ID_NEWEST = "NEWEST"
private const val NAV_ID_SAVED = "SAVED"
private const val COMMENT_BUTTON_DESC = "Open comments"
@Suppress("Unused")
fun MacrobenchmarkScope.exploreUI(device: UiDevice) {
startActivityAndWait()
device.run {
savePosts()
exploreScreens()
returnToHottestScreen()
openCommentsScreen()
}
fun UiDevice.savePosts() {
waitForObject(By.res(SAVE_BUTTON_ID)).click()
}
private fun UiDevice.waitForSubmitterName() {
wait(Until.hasObject(By.textContains("Submitted by")), AWAIT_TIMEOUT)
}
private fun UiDevice.savePosts() {
waitForSubmitterName()
findObjects(By.desc(SAVE_BUTTON_DESC)).forEach { btn -> btn.click() }
}
private fun UiDevice.exploreScreens() {
fun UiDevice.exploreScreens() {
listOf(NAV_ID_HOTTEST, NAV_ID_NEWEST, NAV_ID_SAVED).forEach { tag ->
findObject(By.res(tag)).click()
waitForSubmitterName()
waitForObject(By.res(tag)).click()
waitForIdle()
}
}
private fun UiDevice.returnToHottestScreen() {
findObject(By.res(NAV_ID_HOTTEST)).click()
waitForSubmitterName()
fun UiDevice.returnToHottestScreen() {
waitForObject(By.res(NAV_ID_HOTTEST)).click()
}
private fun UiDevice.openCommentsScreen() {
waitForSubmitterName()
findObjects(By.desc(COMMENT_BUTTON_DESC)).first().click()
private fun UiDevice.waitForObject(selector: BySelector, timeout: Long = AWAIT_TIMEOUT): UiObject2 {
if (wait(Until.hasObject(selector), timeout)) {
return findObject(selector)
}
error("Object with selector [$selector] not found")
}
+8 -20
View File
@@ -1,29 +1,12 @@
/*
* 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.
*/
import org.gradle.api.JavaVersion
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.kotlin.dsl.withType
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins { `kotlin-dsl` }
afterEvaluate {
tasks.withType<JavaCompile>().configureEach {
sourceCompatibility = JavaVersion.VERSION_11.toString()
targetCompatibility = JavaVersion.VERSION_11.toString()
}
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
freeCompilerArgs = freeCompilerArgs + "-Xsam-conversions=class"
}
}
}
kotlin.jvmToolchain { languageVersion.set(JavaLanguageVersion.of(17)) }
gradlePlugin {
plugins {
@@ -59,6 +42,10 @@ gradlePlugin {
id = "dev.msfjarvis.claw.rename-artifacts"
implementationClass = "dev.msfjarvis.claw.gradle.RenameArtifactsPlugin"
}
register("sentry") {
id = "dev.msfjarvis.claw.sentry"
implementationClass = "dev.msfjarvis.claw.gradle.SentryPlugin"
}
register("spotless") {
id = "dev.msfjarvis.claw.spotless"
implementationClass = "dev.msfjarvis.claw.gradle.SpotlessPlugin"
@@ -77,9 +64,10 @@ gradlePlugin {
dependencies {
implementation(libs.build.agp)
implementation(libs.build.cachefix)
implementation(libs.build.detekt)
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)
+14 -2
View File
@@ -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.
@@ -15,7 +15,6 @@ dependencyResolutionManagement {
filter {
includeGroup("androidx.databinding")
includeGroup("com.android")
includeGroup("com.android.tools")
includeGroup("com.android.tools.analytics-library")
includeGroup("com.android.tools.build")
includeGroup("com.android.tools.build.jetifier")
@@ -25,6 +24,15 @@ dependencyResolutionManagement {
includeGroup("com.android.tools.lint")
includeGroup("com.android.tools.utp")
includeGroup("com.google.testing.platform")
includeModule("com.android.tools", "annotations")
includeModule("com.android.tools", "common")
includeModule("com.android.tools", "desugar_jdk_libs")
includeModule("com.android.tools", "desugar_jdk_libs_configuration")
includeModule("com.android.tools", "dvlib")
includeModule("com.android.tools", "play-sdk-proto")
includeModule("com.android.tools", "repository")
includeModule("com.android.tools", "sdklib")
includeModule("com.android.tools", "sdk-common")
}
}
exclusiveContent {
@@ -35,6 +43,10 @@ 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")) } }
@@ -10,24 +10,27 @@ package dev.msfjarvis.claw.gradle
import com.android.build.api.dsl.ApplicationExtension
import com.android.build.api.dsl.LibraryExtension
import com.android.build.api.dsl.Lint
import com.android.build.api.variant.ApplicationAndroidComponentsExtension
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.android.AndroidCacheFixPlugin
import org.gradle.api.JavaVersion
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> {
private companion object {
const val COMPILE_SDK = 33
const val COMPILE_SDK = 34
const val MIN_SDK = 26
const val TARGET_SDK = 33
const val TARGET_SDK = 34
const val SLIM_TESTS_PROPERTY = "slimTests"
}
override fun apply(project: Project) {
@@ -49,43 +52,38 @@ class AndroidCommonPlugin : Plugin<Project> {
resources.excludes.add("**/META-INF/LGPL2.1")
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
testOptions {
animationsDisabled = true
unitTests.isReturnDefaultValues = true
}
}
project.extensions.findByType<ApplicationExtension>()?.run { lint.configureLint(project) }
project.extensions.findByType<LibraryExtension>()?.run { lint.configureLint(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())
}
}
private fun Lint.configureLint(project: Project) {
abortOnError = false
checkReleaseBuilds = false
warningsAsErrors = false
disable.add("DialogFragmentCallbacksDetector")
baseline = project.file("lint-baseline.xml")
}
private fun Project.configureSlimTests() {
// Disable unit test tasks on the release build type for Android Library projects
extensions.findByType<LibraryAndroidComponentsExtension>()?.run {
beforeVariants(selector().withBuildType("release")) {
it.enableUnitTest = false
it.enableAndroidTest = false
private fun Project.configureSlimTests() {
if (!providers.gradleProperty(SLIM_TESTS_PROPERTY).isPresent) {
return
}
// Disable unit test tasks on the release build type for Android Library projects
extensions.findByType<LibraryAndroidComponentsExtension>()?.run {
beforeVariants(selector().all()) {
if (it.name == "debug") return@beforeVariants
(it as HasUnitTestBuilder).enableUnitTest = false
it.enableAndroidTest = false
}
}
}
// Disable unit test tasks on the release build type for Android Application projects.
extensions.findByType<ApplicationAndroidComponentsExtension>()?.run {
beforeVariants(selector().withBuildType("release")) {
it.enableUnitTest = false
it.enableAndroidTest = false
// Disable unit test tasks on the release build type for Android Application projects.
extensions.findByType<ApplicationAndroidComponentsExtension>()?.run {
beforeVariants(selector().all()) {
if (it.name == "debug") return@beforeVariants
(it as HasUnitTestBuilder).enableUnitTest = false
it.enableAndroidTest = false
}
}
}
}
@@ -34,8 +34,7 @@ class ApplicationPlugin : Plugin<Project> {
}
buildTypes {
named("release") {
isMinifyEnabled = true
all {
setProguardFiles(
listOf(
"proguard-android-optimize.pro",
@@ -44,6 +43,7 @@ class ApplicationPlugin : Plugin<Project> {
)
)
}
named("release") { isMinifyEnabled = true }
named("debug") {
applicationIdSuffix = ".debug"
versionNameSuffix = "-debug"
@@ -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.
@@ -22,13 +22,7 @@ class DependencyUpdatesPlugin : Plugin<Project> {
project.pluginManager.apply(VersionsPlugin::class)
project.pluginManager.apply(VersionCatalogUpdatePlugin::class)
project.tasks.withType<DependencyUpdatesTask>().configureEach {
rejectVersionIf {
when (candidate.group) {
"com.squareup.okhttp3",
"org.jetbrains.kotlin" -> true
else -> isNonStable(candidate.version) && !isNonStable(currentVersion)
}
}
rejectVersionIf { isNonStable(candidate.version) && !isNonStable(currentVersion) }
checkConstraints = true
checkBuildEnvironmentConstraints = true
checkForGradleUpdate = true

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