Compare commits

...
537 Commits
Author SHA1 Message Date
GitHub Actions f4dc74c795 feat(release): bump version 2023-02-09 10:07:16 +00:00
renovate[bot]andGitHub 15d5913637 chore(deps): update github/codeql-action digest to 8775e86 2023-02-09 00:16:32 +00:00
renovate[bot]andHarsh Shandilya b0a10b37e6 fix(deps): update dependency androidx.work:work-runtime-ktx to v2.8.0 2023-02-09 01:02:01 +05:30
renovate[bot]andHarsh Shandilya f572e1255a fix(deps): update dependency androidx.profileinstaller:profileinstaller to v1.3.0-beta01 2023-02-09 01:02:00 +05:30
renovate[bot]andHarsh Shandilya 5a491447fe fix(deps): update dependency androidx.paging:paging-compose to v1.0.0-alpha18 2023-02-09 01:01:59 +05:30
renovate[bot]andHarsh Shandilya 81457f6c5b fix(deps): update dependency androidx.navigation:navigation-compose to v2.6.0-alpha05 2023-02-09 01:01:55 +05:30
renovate[bot]andHarsh Shandilya cda5c2d2d8 fix(deps): update dependency androidx.lifecycle:lifecycle-viewmodel-compose to v2.6.0-beta01 2023-02-09 01:01:34 +05:30
renovate[bot]andGitHub f9f11e51c8 fix(deps): update dependency androidx.browser:browser to v1.5.0 2023-02-08 18:50:44 +00:00
renovate[bot]andGitHub 19638658ec fix(deps): update dependency androidx.activity:activity-compose to v1.7.0-beta01 2023-02-08 18:46:46 +00:00
Harsh Shandilya f8d220083a fix(ci): allow CI workflow to run in parallel across branches 2023-02-09 00:15:50 +05:30
Harsh Shandilya 648f4502e6 fix(renovate): set branch limit to 15 2023-02-09 00:15:35 +05:30
Harsh Shandilya 5e2c2cbdde chore(deps): use Compose Compiler from gMaven 2023-02-09 00:02:08 +05:30
renovate[bot]andGitHub 52035cde13 chore(deps): update dependency androidx.compose.compiler:compiler to v1.4.2 2023-02-08 18:25:02 +00:00
renovate[bot]andGitHub ea57889959 chore(deps): update jetbrains/qodana-action action to v2022.3.3 2023-02-08 13:20:10 +00:00
Harsh Shandilya 038ded5abd chore(deps): upgrade to Gradle 8.1.0-alpha03 2023-02-08 11:37:55 +05:30
GitHub Actions 1b5d9a87e6 chore(android): refresh baseline profile 2023-02-08 01:27:13 +00:00
renovate[bot]andGitHub 6f625a0f6f chore(deps): update actions/setup-java action to v3.10.0 2023-02-07 21:08:02 +00:00
renovate[bot]andGitHub da1948c0e6 chore(deps): update dependency com.facebook:ktfmt to v0.43 2023-02-07 16:35:32 +00:00
Harsh ShandilyaandGitHub 177d0b7162 fix(renovate): adjust regex manager 2023-02-07 22:04:39 +05:30
renovate[bot]andGitHub 5c350628ff chore(deps): update github/codeql-action digest to 39d8d7e 2023-02-07 02:33:28 +00:00
GitHub Actions cd014f2ba6 chore(android): refresh baseline profile 2023-02-07 01:27:51 +00:00
renovate[bot]andGitHub 08ec5c267c chore(deps): update dependency gradle to v8.0-rc-3 2023-02-06 13:00:38 +00:00
GitHub Actions d930450a6f chore(android): refresh baseline profile 2023-02-06 01:30:00 +00:00
renovate[bot]andHarsh Shandilya d09b5cb306 fix(deps): update kotest to v5.5.5
(cherry picked from commit 7632747f9ba345988dd0ac515c71aaab25f9a9de)
2023-02-06 03:38:22 +05:30
renovate[bot]andGitHub 89dde6e050 fix(deps): update dependency com.diffplug.spotless:spotless-plugin-gradle to v6.14.1 2023-02-05 20:29:34 +00:00
GitHub Actions 4b99abbfa4 chore(android): refresh baseline profile 2023-02-05 01:30:01 +00:00
renovate[bot]andGitHub edc3a017be fix(deps): update dependency org.gradle.android.cache-fix:org.gradle.android.cache-fix.gradle.plugin to v2.6.3 2023-02-04 15:01:56 +00:00
renovate[bot]andGitHub ff69ccb384 fix(deps): update dependency com.github.ben-manes:gradle-versions-plugin to v0.45.0 2023-02-04 11:41:04 +00:00
Harsh ShandilyaandGitHub 09b3bd4e74 chore(deps): update Compose Compiler to 1.4.1-dev-k1.8.10-c312d77f4cb 2023-02-04 16:41:36 +05:30
Harsh ShandilyaandGitHub de8dd8a3cf fix(renovate): set maven URLs 2023-02-04 15:34:05 +05:30
renovate[bot]andGitHub 928878928a chore(deps): update kotlin to v1.8.10 2023-02-04 01:49:30 +00:00
GitHub Actions 58c5cfa5b6 chore(android): refresh baseline profile 2023-02-04 01:25:51 +00:00
GitHub Actions 3bf0849f1c chore(android): refresh baseline profile 2023-02-02 01:26:51 +00:00
renovate[bot]andGitHub 85e65fed92 chore(deps): update agp to v7.4.1 2023-02-01 19:17:39 +00:00
GitHub Actions 499fd23342 feat(release): start next development iteration 2023-02-01 13:52:30 +00:00
GitHub Actions 595dbd96e9 feat(release): bump version 2023-02-01 13:51:21 +00:00
Harsh Shandilya b62bc8dc89 chore: update changelog 2023-02-01 15:29:29 +05:30
GitHub Actions a909f1274d chore(android): refresh baseline profile 2023-02-01 07:46:45 +00:00
GitHub Actions 94a7ad788b chore(android): refresh baseline profile 2023-02-01 07:25:57 +00:00
Harsh Shandilya cfa8bc4971 fix(benchmark): enforce profile stability 2023-02-01 11:17:40 +05:30
GitHub Actions a4ad701aff chore(android): refresh baseline profile 2023-01-31 01:20:11 +00:00
renovate[bot]andGitHub 00e94eb1cb chore(deps): update actions/cache action to v3.2.4 2023-01-30 12:14:02 +00:00
Harsh Shandilya ff478b3365 fix(renovate): specify explicit registry URLs for regex Maven deps 2023-01-30 10:03:51 +05:30
GitHub Actions 7bf599ce19 chore(android): refresh baseline profile 2023-01-30 01:17:26 +00:00
GitHub Actions a4b9e227bf chore(android): refresh baseline profile 2023-01-29 01:17:30 +00:00
GitHub Actions cb9039e927 chore(android): refresh baseline profile 2023-01-28 01:17:50 +00:00
renovate[bot]andGitHub bf65027105 chore(deps): update github/codeql-action digest to 3ebbd71 2023-01-27 11:23:21 +00:00
GitHub Actions 28d655efc2 chore(android): refresh baseline profile 2023-01-27 09:47:51 +00:00
renovate[bot]andGitHub e711781eb0 fix(deps): update dependency com.diffplug.spotless:spotless-plugin-gradle to v6.14.0 2023-01-27 04:47:56 +00:00
renovate[bot]andGitHub 8576bad580 chore(deps): update jetbrains/qodana-action action to v2022.3.2 2023-01-27 01:49:31 +00:00
GitHub Actions 56e089ffa9 chore(android): refresh baseline profile 2023-01-27 01:29:13 +00:00
renovate[bot]andGitHub 6625ebcfc7 chore(deps): update github/codeql-action digest to 436dbd9 2023-01-26 18:02:20 +00:00
Harsh Shandilya 540ea7ba43 feat: upgrade kotlinx.serialization to 1.5.0-RC 2023-01-26 21:49:29 +05:30
Harsh Shandilya dea2d8b468 fix(android): raise target API suppression in manifest 2023-01-26 11:49:08 +05:30
renovate[bot]andHarsh Shandilya ba292bf709 fix(deps): update dependency androidx.lifecycle:lifecycle-viewmodel-compose to v2.6.0-alpha05
(cherry picked from commit 073a8d00dc6bfb10654b040e3d79dc296b01aab2)
2023-01-26 10:35:22 +05:30
renovate[bot]andHarsh Shandilya 2ce4d1de2e fix(deps): update dependency androidx.browser:browser to v1.5.0-rc01
(cherry picked from commit ea010bf8e1c5dfb1497c2a2478054cf0927dc3db)
2023-01-26 10:35:21 +05:30
renovate[bot]andHarsh Shandilya 9d6712d94b fix(deps): update dependency androidx.activity:activity-compose to v1.7.0-alpha04
(cherry picked from commit ef7b1062eebea83c935e57a39477bc6378619596)
2023-01-26 10:35:21 +05:30
Harsh Shandilya 4e920328e1 fix(gradle): allow resolution of androidx.resourceinspection
Newer AppCompat releases depend on this
2023-01-26 10:35:11 +05:30
GitHub Actions 1865c62db8 chore(android): refresh baseline profile 2023-01-24 01:18:43 +00:00
GitHub Actions d61206a7d2 chore(android): refresh baseline profile 2023-01-23 01:19:01 +00:00
GitHub Actions a94c48028d chore(android): refresh baseline profile 2023-01-22 01:22:12 +00:00
GitHub Actions 909c615ae6 feat(release): start next development iteration 2023-01-21 07:26:31 +00:00
GitHub Actions 5d87bb077f feat(release): bump version 2023-01-21 07:25:25 +00:00
GitHub Actions 72b5b2c795 chore(android): refresh baseline profile 2023-01-21 07:16:07 +00:00
Harsh Shandilya ce91650788 fix(deps): downgrade to Compose 2023.01.00
There seems to be a regression with `produceState` in this release

Also reverts fb4beb08ee
2023-01-21 12:32:13 +05:30
GitHub Actions 1b2534eca6 chore(android): refresh baseline profile 2023-01-21 01:23:34 +00:00
Harsh Shandilya 5e3356e1d4 fix(android): set a key for database items 2023-01-21 01:50:00 +05:30
Harsh Shandilya ddc7a7598d fix(android): throw a different error for removed stories 2023-01-21 01:46:59 +05:30
Harsh Shandilya 2cfe9d73a2 chore(deps): update app.cash.sqldelight to 2.0.0-alpha05 2023-01-21 00:07:20 +05:30
Harsh Shandilya dbed41bdbd fix(renovate): disable updates for SQLDelight 2023-01-20 23:57:54 +05:30
GitHub Actions a0296f5d5a chore(android): refresh baseline profile 2023-01-20 01:19:27 +00:00
GitHub Actions 39cca12708 chore(android): refresh baseline profile 2023-01-19 01:21:48 +00:00
renovate[bot]andGitHub 0c4e3db59c chore(deps): update github/codeql-action digest to a34ca99 2023-01-18 20:27:54 +00:00
Harsh ShandilyaandGitHub 8dea17cbf2 chore(deps): update androidx.compose.compiler to 1.4.0 2023-01-18 14:32:52 +05:30
GitHub Actions b428c73602 chore(android): refresh baseline profile 2023-01-18 01:22:44 +00:00
Harsh ShandilyaandGitHub 8c9ce29a7a chore: add Renovate config rename to blame ignore revs 2023-01-17 22:14:22 +05:30
Harsh ShandilyaandGitHub f304304bc5 fix(renovate): switch config to JSON5 2023-01-17 22:13:41 +05:30
Harsh Shandilya f14941cf27 chore(deps): upgrade to Gradle 8.0-RC-2 2023-01-17 18:54:46 +05:30
GitHub Actions c83afb3cd7 chore(android): refresh baseline profile 2023-01-17 01:18:23 +00:00
GitHub Actions dfc6d80127 chore(android): refresh baseline profile 2023-01-16 01:18:43 +00:00
GitHub Actions 3ad1b07ff8 chore(android): refresh baseline profile 2023-01-15 01:23:13 +00:00
Harsh Shandilya d3c2861939 fix(renovate): adapt to Qodana linter switch 2023-01-14 19:30:27 +05:30
renovate[bot]andGitHub 8632f8fd7a fix(deps): update dependency com.diffplug.spotless:spotless-plugin-gradle to v6.13.0 2023-01-14 02:22:57 +00:00
GitHub Actions 83585b4a8a chore(android): refresh baseline profile 2023-01-14 01:24:57 +00:00
Harsh Shandilya e2e3186bb4 chore(gradle): update to 8.0-20230104085204+0000 nightly 2023-01-13 15:16:18 +05:30
Harsh Shandilya 29765f0024 fix(gradle): remove unnecessary properties 2023-01-13 11:12:55 +05:30
Harsh Shandilya 43edeaff04 fix(gradle): remove obsolete CC workaround 2023-01-13 11:11:33 +05:30
Harsh Shandilya 2b7cf7d3cb fix(renovate): remove AGP version clamp 2023-01-13 10:55:23 +05:30
Harsh Shandilya fdf13fd201 chore(deps): bump com.squareup:anvil to v2.4.4 2023-01-13 10:54:39 +05:30
renovate[bot]andGitHub 92ba77a838 chore(deps): update agp to v7.4.0 2023-01-13 02:07:17 +00:00
GitHub Actions d910810dd4 chore(android): refresh baseline profile 2023-01-13 01:22:45 +00:00
Harsh Shandilya 5f8853c69d fix(renovate): fix Compose BOM resolution 2023-01-13 00:09:08 +05:30
Harsh Shandilya 0f9b35efb1 fix(renovate): remove obsoleted Compose group 2023-01-12 22:39:47 +05:30
Harsh Shandilya 7096e62948 fix(deps): switch to Chris Banes' Compose alpha BOM 2023-01-12 22:37:10 +05:30
renovate[bot]andGitHub acb7c0e670 chore(deps): update github/codeql-action digest to 515828d 2023-01-12 14:00:44 +00:00
Harsh ShandilyaandGitHub bb91e6e083 fix(deps): update accompanist to 0.29.0-alpha 2023-01-12 19:02:26 +05:30
Harsh ShandilyaandGitHub 67a922362d chore(deps): bump accompanist to 0.29.0 2023-01-12 17:39:39 +05:30
GitHub Actions 56be44b918 feat(release): start next development iteration 2023-01-12 07:38:31 +00:00
GitHub Actions 15493aec1a feat(release): bump version 2023-01-12 07:35:33 +00:00
Harsh Shandilya 9aef7582bd chore: update README 2023-01-12 12:52:29 +05:30
Harsh Shandilya 5378ec6854 fix(ci): disable scheduled code analysis jobs
They both fail for stupid reasons 🤷
2023-01-12 12:42:00 +05:30
Harsh Shandilya 79dbb64275 chore(gradle): reformat version catalog 2023-01-12 11:27:54 +05:30
GitHub Actions b8912e747a chore(android): refresh baseline profile 2023-01-12 01:24:00 +00:00
renovate[bot]andHarsh Shandilya a9dcd0157a fix(deps): update dependency androidx.lifecycle:lifecycle-viewmodel-compose to v2.6.0-alpha04 2023-01-12 02:55:54 +05:30
renovate[bot]andHarsh Shandilya 45e8993d75 fix(deps): update dependency androidx.test.uiautomator:uiautomator to v2.3.0-alpha02 2023-01-12 02:55:54 +05:30
renovate[bot]andHarsh Shandilya 39bd1fa2eb fix(deps): update dependency androidx.profileinstaller:profileinstaller to v1.3.0-alpha03 2023-01-12 02:55:54 +05:30
renovate[bot]andHarsh Shandilya 5b33fbc20b fix(deps): update dependency androidx.browser:browser to v1.5.0-beta01 2023-01-12 02:55:53 +05:30
renovate[bot]andHarsh Shandilya 196195d867 fix(deps): update dependency androidx.benchmark:benchmark-macro-junit4 to v1.2.0-alpha09 2023-01-12 02:55:28 +05:30
renovate[bot]andHarsh Shandilya 20b8076235 fix(deps): update dependency androidx.activity:activity-compose to v1.7.0-alpha03 2023-01-12 02:54:44 +05:30
Harsh Shandilya 5f49c8149a fix(renovate): batch Compose updates together 2023-01-12 02:51:01 +05:30
Harsh Shandilya fb4beb08ee fix(common): adopt new Compose APIs 2023-01-12 02:40:37 +05:30
Harsh Shandilya 6750eff9fa chore(deps): upgrade to Compose 2023-01-12 alpha release 2023-01-12 02:31:00 +05:30
Harsh Shandilya 692cc8ce67 chore: update changelog 2023-01-12 02:20:09 +05:30
Harsh Shandilya a59b6411b1 feat: implement support for persisting read comments
Fixes #358
2023-01-12 02:20:09 +05:30
Harsh Shandilya 1345709450 feat(android): wire up PostCommentsQueries 2023-01-12 01:06:34 +05:30
Harsh Shandilya 8433f38f6e fix(model): add Comment#shortId 2023-01-12 01:06:34 +05:30
Harsh Shandilya 91c3d0f9a6 feat(common): add support for unread comments to UI 2023-01-12 01:06:34 +05:30
Harsh Shandilya 579a2ffe2e fix(database): add a migration to generate an empty PostComments table 2023-01-12 00:58:07 +05:30
Harsh Shandilya 918f28feab fix(renovate): prevent Anvil from being queried in the snapshots repo
Renovate incorrectly picks up the 2.4.4-1-8-0-SNAPSHOT version
2023-01-11 18:04:51 +05:30
Harsh Shandilya 7b13d40797 chore(deps): upgrade to Kotlin 1.8.0 2023-01-11 17:41:04 +05:30
GitHub Actions 002588a45a chore(android): refresh baseline profile 2023-01-11 01:20:37 +00:00
Harsh Shandilya 1ae0cbb95d feat(database): add a table for post comments 2023-01-11 03:05:36 +05:30
Harsh Shandilya 3e2d32e4c9 chore: refresh license headers 2023-01-11 02:40:44 +05:30
Harsh Shandilya 403f7d47b1 refactor(database): rename test class 2023-01-11 02:39:11 +05:30
GitHub Actions d1fcebdf4c chore(android): refresh baseline profile 2023-01-10 01:17:58 +00:00
renovate[bot]andGitHub a983bd729c chore(deps): update actions/cache action to v3.2.3 2023-01-09 05:49:54 +00:00
GitHub Actions 8d311a1193 chore(android): refresh baseline profile 2023-01-09 01:20:48 +00:00
GitHub Actions 6bbd2c73b5 chore(android): refresh baseline profile 2023-01-08 01:16:53 +00:00
GitHub Actions f851e2d626 chore(android): refresh baseline profile 2023-01-07 01:25:39 +00:00
renovate[bot]andGitHub 97aafc764f chore(deps): update agp to v7.4.0-rc03 2023-01-06 19:21:43 +00:00
Harsh Shandilya 183e10dba8 chore(renovate): prevent AGP from being upgraded past 7.4.x 2023-01-07 00:47:48 +05:30
Harsh Shandilya f5deaaa8ff fix(deps): downgrade AGP to 7.4.0-beta02 2023-01-07 00:44:38 +05:30
Harsh Shandilya 55537ea191 fix(deps): update dependency org.gradle.android.cache-fix:org.gradle.android.cache-fix.gradle.plugin to v2.6.1 2023-01-07 00:36:03 +05:30
Harsh Shandilya 98818023e9 chore: update changelog 2023-01-07 00:36:03 +05:30
Harsh Shandilya e67df253de Revert "feat(android): use enter always scroll behavior for TopAppBar"
This reverts commit eeef2c347d.
2023-01-07 00:36:03 +05:30
renovate[bot]andGitHub 53524c8ef8 chore(deps): update actions/upload-artifact action to v3.1.2 2023-01-06 15:46:07 +00:00
Harsh Shandilya bc90a8941a chore(github): allow running analysis actions manually 2023-01-06 20:37:42 +05:30
Harsh Shandilya 5c413d1f12 chore(qodana): switch to the Android linter 2023-01-06 20:37:42 +05:30
renovate[bot]andGitHub 8b5ab33730 chore(deps): update github/codeql-action digest to 959cbb7 2023-01-06 03:58:04 +00:00
GitHub Actions d4d1288900 chore(android): refresh baseline profile 2023-01-06 01:31:11 +00:00
renovate[bot]andGitHub f4f07d0ce3 chore(deps): update actions/checkout action to v3.3.0 2023-01-05 22:08:46 +00:00
renovate[bot]andGitHub 6282f4d6e0 chore(deps): update actions/checkout digest to ac59398 2023-01-05 18:54:06 +00:00
Harsh Shandilya 19f5f8a0ca chore: hide baseline-prof from diffs
Having this pollute regular diffs doesn't provide much value if any
at all, since it is generated by an automated process and never
touched by hand.
2023-01-05 21:08:55 +05:30
Harsh Shandilya a296d2b827 sync-backend: delete
Realistically this will be much more enjoyable to do in Rust or Go *shudder*
so let's cut out the unused code until we can tackle the backend.
2023-01-05 21:07:07 +05:30
Harsh Shandilya 45872f1653 chore: re-enable code quality workflows to run on a schedule 2023-01-05 21:00:06 +05:30
GitHub Actions 074420056b chore(android): refresh baseline profile 2023-01-05 01:23:05 +00:00
renovate[bot]andHarsh Shandilya 9f7e05d79a chore(deps): update agp to v8.0.0-alpha11 2023-01-05 00:30:58 +05:30
Harsh Shandilya 955299bd89 chore(renovate): re-enable AGP updates 2023-01-04 23:58:57 +05:30
renovate[bot]andHarsh Shandilya 6520758722 fix(deps): update dependency androidx.test.espresso:espresso-core to v3.5.1 2023-01-04 13:06:36 +05:30
renovate[bot]andGitHub 583137aa25 fix(deps): update dependency androidx.test.ext:junit to v1.1.5 2023-01-04 04:14:16 +00:00
GitHub Actions 8afce7706c chore(android): refresh baseline profile 2023-01-04 01:19:47 +00:00
renovate[bot]andGitHub 7ae0a0bae6 fix(deps): update dependency androidx.test:runner to v1.5.2 2023-01-03 21:54:08 +00:00
renovate[bot]andGitHub f34d6c5222 fix(deps): update ktor to v2.2.2 2023-01-03 18:18:52 +00:00
GitHub Actions 002f4f9309 chore(android): refresh baseline profile 2023-01-03 01:18:09 +00:00
renovate[bot]andGitHub bf77a9aaed fix(deps): update dependency com.diffplug.spotless:spotless-plugin-gradle to v6.12.1 2023-01-02 05:09:06 +00:00
GitHub Actions d17c178eaa chore(android): refresh baseline profile 2023-01-02 01:22:20 +00:00
GitHub Actions 2c0ba0fae9 chore(android): refresh baseline profile 2023-01-01 01:24:21 +00:00
renovate[bot]andGitHub 0396a488cd fix(deps): update dependency com.slack.eithernet:eithernet to v1.3.1 2022-12-31 18:53:39 +00:00
GitHub ActionsandHarsh Shandilya f5ef483cd9 chore(android): refresh baseline profile 2022-12-31 05:38:40 +00:00
Harsh Shandilya 78960f6d4e feat(deps): upgrade to Gradle 8.0-rc-1 2022-12-31 05:38:40 +00:00
GitHub Actions 2be9408560 chore(android): refresh baseline profile 2022-12-30 01:16:41 +00:00
GitHub Actions 46dc6fbe91 chore(android): refresh baseline profile 2022-12-29 01:15:22 +00:00
GitHub Actions e411cb3d7f chore(android): refresh baseline profile 2022-12-28 01:18:43 +00:00
renovate[bot]andGitHub 00535dc859 chore(deps): update actions/cache action to v3.2.2 2022-12-27 13:49:21 +00:00
GitHub Actions 465e6931ad chore(android): refresh baseline profile 2022-12-27 01:15:23 +00:00
GitHub Actions 7e64100491 chore(android): refresh baseline profile 2022-12-26 01:19:10 +00:00
GitHub Actions e199f00ca0 chore(android): refresh baseline profile 2022-12-25 01:18:18 +00:00
renovate[bot]andGitHub 8722636b13 fix(deps): update dependency ch.qos.logback:logback-classic to v1.4.5 2022-12-24 15:42:59 +00:00
Harsh Shandilya 5db4a1ee8d sync-backend: initialize barebones module
This is simply an import of the project generated by start.ktor.io cleaned of
its ungodly wildcard import fetish and better integrated into our existing build
system.
2022-12-24 14:17:59 +00:00
GitHub Actions b0bae12e6b feat(release): start next development iteration 2022-12-24 02:26:25 +00:00
GitHub Actions 585d42c6aa feat(release): bump version 2022-12-24 02:25:00 +00:00
GitHub Actions 47448616dd chore(android): refresh baseline profile 2022-12-24 01:19:28 +00:00
GitHub Actions ccd64b46e3 chore(android): refresh baseline profile 2022-12-23 20:38:40 +00:00
Harsh Shandilya bce227357f chore(gradle): run versionCatalogFormat 2022-12-24 01:43:30 +05:30
Harsh Shandilya 2ec1633c51 fix(gradle): remove unnecessary repository declarations 2022-12-24 01:38:04 +05:30
Harsh Shandilya ed8528aea4 fix(build-logic): tweak DependencyUpdates configuration 2022-12-24 01:36:29 +05:30
Harsh Shandilya 3cc74f9bcf fix(android): stupid copypasta :clown:
Fixes 2afbc73bd4
2022-12-24 01:22:38 +05:30
renovate[bot]andGitHub 015c9929b3 chore(deps): update actions/cache action to v3.2.1 2022-12-23 07:53:38 +00:00
Harsh Shandilya 76737b0eed chore(gradle): remove Compose Compiler metrics opt-in
This doesn't seem worth generating for each build

Revert "fix(gradle): address list reassignment lints"

This reverts commit 1d2da7c2f8.

Revert "chore: enable Compose Compiler metrics"

This reverts commit 0d668ac3aa.
2022-12-22 11:25:06 +05:30
Harsh Shandilya 28c454261f fix(deps): return to AGP 8.x 2022-12-22 11:19:05 +05:30
GitHub Actions 327ec9b6a5 chore(android): refresh baseline profile 2022-12-22 01:14:47 +00:00
GitHub Actions fc617a5dfa chore(android): refresh baseline profile 2022-12-21 19:16:21 +00:00
renovate[bot]andGitHub c6289908c4 chore(deps): update actions/cache action to v3.2.0 2022-12-21 15:13:13 +00:00
Harsh ShandilyaandGitHub ca0706f94b fix(ci): fix baseline profile path 2022-12-21 16:44:18 +05:30
Harsh Shandilya 350a64d479 fix(benchmark): simplify 2022-12-21 15:03:05 +05:30
Harsh Shandilya 058561e4d6 chore(ci): tweak baseline profile execution environment 2022-12-21 14:31:36 +05:30
Harsh Shandilya a4bc5476a4 feat(ci): rework benchmark workflow to improve perf 2022-12-21 14:13:23 +05:30
Harsh Shandilya 1fe559ff04 chore(benchmark): remove test filter annotations 2022-12-21 13:55:15 +05:30
Harsh Shandilya 78f050f115 Revert "fix(benchmark): remove startup benchmarks"
This reverts commit fab5cb45c4.
2022-12-21 13:36:54 +05:30
Harsh Shandilya 0d964c4a5d fix(ci): use a more granular test filter 2022-12-21 13:33:06 +05:30
Harsh Shandilya c24c433c0e chore(ci): log stacktrace on failure 2022-12-21 13:18:07 +05:30
Harsh Shandilya fd9010d468 chore(android): refresh baseline profile 2022-12-21 13:11:08 +05:30
Harsh Shandilya 90f40075e8 fix(ci): fix incorrect baseline profile destination 2022-12-21 13:10:13 +05:30
GitHub Actions f621f785f8 feat(release): start next development iteration 2022-12-20 19:17:34 +00:00
GitHub Actions 76d62906c8 feat(release): bump version 2022-12-20 19:16:18 +00:00
Harsh ShandilyaandGitHub dbeafbb569 fix(benchmark): upgrade baseline profile generator size
This takes long enough to be a large test
2022-12-21 00:05:24 +05:30
Harsh ShandilyaandGitHub fab5cb45c4 fix(benchmark): remove startup benchmarks
These just always fail and it's not worth pursuing a fix
2022-12-20 23:28:59 +05:30
Harsh Shandilya 7b593806eb fix: prevent unwanted list jumping during paging refresh
Fixes #347

Fixes SDP-34
2022-12-20 22:47:53 +05:30
Harsh ShandilyaandGitHub f6064616b0 chore: reword changelog entries 2022-12-20 16:57:24 +05:30
Harsh ShandilyaandGitHub 36d999ebb4 chore(build-logic): use kotlin DSL's apply extension 2022-12-20 16:40:20 +05:30
Harsh Shandilya 79920f6180 chore: align copyright headers with Spotless expectations 2022-12-20 16:01:44 +05:30
Harsh Shandilya 36ab72226c refactor(build-logic): simplify plugin setup 2022-12-20 16:01:18 +05:30
renovate[bot]andGitHub 7cc81047d4 chore(deps): update plugin anvil to v2.4.3 2022-12-17 00:47:20 +00:00
Harsh Shandilya 82252dbe2d chore: remove unused Nix shell config 2022-12-16 18:19:16 +05:30
renovate[bot]andGitHub da1825498a chore(deps): update actions/setup-java action to v3.9.0 2022-12-14 16:13:19 +00:00
Harsh Shandilya 947a04d82d chore: update changelog 2022-12-14 19:41:14 +05:30
Harsh Shandilya 75ab508dc3 refactor: rework ProgressBar usage 2022-12-14 18:32:28 +05:30
Harsh Shandilya 1d2da7c2f8 fix(gradle): address list reassignment lints 2022-12-14 18:27:01 +05:30
Harsh Shandilya dd019279ec fix(comments): ensure load failure screen renders correctly
Fixes SDP-21
2022-12-14 17:56:16 +05:30
Harsh Shandilya 485282a48d feat(common): change progress indicator to linear
Fixes SDP-20
2022-12-14 17:46:42 +05:30
Harsh Shandilya eeef2c347d feat(android): use enter always scroll behavior for TopAppBar 2022-12-13 17:03:04 +00:00
Harsh Shandilya b7229c6d15 chore: add changelog entry for expanded state fix 2022-12-13 11:43:46 +05:30
Harsh Shandilya da80605ae1 chore(common): updateAllExpanded -> setExpanded 2022-12-13 11:37:41 +05:30
Anunay Maheshwari 4066a86ad6 comments: Update isExpanded based on the parent node being toggled (Fixes #352) 2022-12-13 10:56:28 +05:30
renovate[bot]andGitHub 0f595f7b02 chore(deps): update actions/checkout action to v3.2.0 2022-12-12 19:52:23 +00:00
GitHub Actions 42f3068b01 feat(release): start next development iteration 2022-12-12 08:02:05 +00:00
GitHub Actions 0312c3f431 feat(release): bump version 2022-12-12 08:01:02 +00:00
Harsh Shandilya 7d324ddb8c refactor(common): tweak visibility of module members 2022-12-12 12:47:23 +05:30
Harsh Shandilya c7b7a786f3 chore: update changelog 2022-12-12 12:02:59 +05:30
Harsh Shandilya 26f69c2027 feat(common): match node collapse behaviour with the lobste.rs site 2022-12-12 12:02:59 +05:30
Harsh Shandilya 450601f26b feat(common): use a simplified search algorithm 2022-12-12 11:26:29 +05:30
Harsh Shandilya 2ab69de686 fix(common): do not compute a new node if it cannot be used 2022-12-12 10:44:00 +05:30
Harsh Shandilya 0083aa69b5 refactor(common): optimize comment entry display 2022-12-12 10:44:00 +05:30
Anunay MaheshwariandHarsh Shandilya 18d82a5eb3 refactor(comments): convert internal representation to a tree 2022-12-12 10:44:00 +05:30
Harsh Shandilya 0d668ac3aa chore: enable Compose Compiler metrics 2022-12-12 02:41:12 +05:30
Harsh Shandilya b1a32496ba refactor: improve skippability of Composables 2022-12-12 02:41:12 +05:30
Harsh Shandilya 4db59db7a1 fix(scripts): set a trap to delete temp file 2022-12-12 02:10:58 +05:30
Harsh Shandilya 0843352514 fix(ci): fix benchmark assemble tasks 2022-12-12 02:10:42 +05:30
renovate[bot]andGitHub aa50d6449d chore(deps): pin reactivecircus/android-emulator-runner action to 50986b1 2022-12-11 15:02:50 +00:00
Harsh Shandilya 6a41b966c5 feat(ci): disable lint workflows
This hasn't brought much value but eats up too much CI time
2022-12-11 19:09:44 +05:30
Harsh Shandilya 943b9bd091 chore: re-encrypt secrets with age 2022-12-11 19:09:44 +05:30
Harsh Shandilya 010f69031a feat(ci): add a job to regenerate baseline profile 2022-12-11 17:22:07 +05:30
Harsh Shandilya 168271a8f4 chore: add changelog entry for post update worker tweaks 2022-12-10 20:16:04 +05:30
Harsh Shandilya ea92d64972 fix(android): do not force re-enqueue post update job on each start 2022-12-10 20:16:04 +05:30
Harsh Shandilya 766f14cd22 refactor(android): run post updates in a batch transaction 2022-12-10 20:16:04 +05:30
Harsh Shandilya 33210319bb fix(android): manually set up WorkManager 2022-12-10 19:31:22 +05:30
Harsh Shandilya 7ef0a8fc51 fix(database): ensure schema migrations can run 2022-12-10 18:27:12 +05:30
Harsh Shandilya 1f850435b1 feat(database): add description to the saved post table 2022-12-10 18:27:12 +05:30
GitHub Actions 9d8fd77a08 feat(release): start next development iteration 2022-12-10 10:21:13 +00:00
GitHub Actions e2c760e7f3 feat(release): bump version 2022-12-10 10:18:13 +00:00
Harsh Shandilya 51a8538d6a fix: users can exist without inviters 2022-12-10 10:14:35 +00:00
renovate[bot]andGitHub 4b9b148519 chore(deps): update jetbrains/qodana-jvm-community docker tag to v2022.3 2022-12-09 16:13:12 +00:00
Harsh Shandilya ced16c3794 feat(renovate): add a regex manager for Qodana linters 2022-12-09 21:39:16 +05:30
renovate[bot]andHarsh Shandilya e824294ea0 chore(deps): update jetbrains/qodana-action action to v2022.3.0 2022-12-09 21:30:48 +05:30
renovate[bot]andHarsh Shandilya ffce7c2b3e chore(deps): pin dependencies 2022-12-09 21:30:23 +05:30
Harsh Shandilya f0d615bd44 feat(qodana): check-in configuration 2022-12-09 21:20:42 +05:30
Harsh Shandilya df546947bb chore(changelog): fix diff links 2022-12-09 21:19:01 +05:30
Harsh Shandilya 3e0544279c fix(release): ensure changelog links are valid 2022-12-09 21:18:20 +05:30
GitHub Actions 25bfed3c71 feat(release): start next development iteration 2022-12-09 15:40:46 +00:00
GitHub Actions 6c88ecf5d5 feat(release): bump version 2022-12-09 15:37:20 +00:00
Harsh Shandilya 4f60db29bd Revert "refactor(android): move post save state logic to a Paging transform"
This reverts commit 3829abeb8f.
2022-12-09 21:02:24 +05:30
Harsh Shandilya e6eaadbaa1 chore: add CHANGELOG entry for d36bcd6b26 2022-12-09 21:01:34 +05:30
Harsh Shandilya 30fa04db56 fix(android): only queue post update worker on Application init 2022-12-09 20:58:42 +05:30
Harsh Shandilya 74684fe115 fix(common): reduce space between favicon and URL 2022-12-09 18:19:15 +05:30
Harsh Shandilya d36bcd6b26 fix(common): simplify favicon display 2022-12-09 18:19:10 +05:30
Harsh Shandilya a2f3faad0f chore: add an editorconfig file to approximate ktfmt formatting
This avoids having to reconfigure IntelliJ again and again
2022-12-09 16:08:36 +05:30
Harsh Shandilya 9b25cd0fea feat(ci): add Qodana scan workflow 2022-12-09 16:08:11 +05:30
renovate[bot]andGitHub db75d1edfe chore(deps): update github/codeql-action digest to a669cc5 2022-12-08 22:27:59 +00:00
GitHub Actions ac98ebda3f feat(release): start next development iteration 2022-12-07 21:51:44 +00:00
GitHub Actions c8d03c515a feat(release): bump version 2022-12-07 21:47:57 +00:00
Harsh Shandilya a77f098822 chore: update changelog 2022-12-08 03:13:50 +05:30
Harsh Shandilya 608720ac35 chore(android): refresh baseline profile 2022-12-08 03:12:59 +05:30
renovate[bot]andHarsh Shandilya f1b68b238c fix(deps): update richtext to v0.16.0 2022-12-08 03:08:58 +05:30
renovate[bot]andGitHub 977e3cbc8e fix(deps): update dependency androidx.compose:compose-bom to v2022.12.00 2022-12-07 21:38:17 +00:00
renovate[bot]andHarsh Shandilya fe2c3f630b fix(deps): update dependency androidx.work:work-runtime-ktx to v2.8.0-rc01 2022-12-08 03:06:00 +05:30
renovate[bot]andGitHub 4fac861656 chore(deps): pin dependencies 2022-12-07 21:33:05 +00:00
Harsh Shandilya 0ec03fc979 fix(benchmark): re-enable BaselineProfileBenchmark 2022-12-08 02:59:58 +05:30
renovate[bot]andHarsh Shandilya 46a9658dcd fix(deps): update dependency androidx.browser:browser to v1.5.0-alpha02 2022-12-08 02:48:05 +05:30
Harsh Shandilya 387881181a feat(benchmark): switch to new BaselineProfileRule.collectStableBaselineProfile API 2022-12-08 02:46:55 +05:30
renovate[bot]andHarsh Shandilya cb619ca6a9 fix(deps): update dependency androidx.benchmark:benchmark-macro-junit4 to v1.2.0-alpha08 2022-12-08 02:40:33 +05:30
Harsh Shandilya 37fc528053 fix(benchmark): wut 2022-12-08 02:39:33 +05:30
Harsh Shandilya b19617f8c7 feat(ci): configure CodeQL analysis 2022-12-08 01:40:44 +05:30
Harsh Shandilya 3829abeb8f refactor(android): move post save state logic to a Paging transform
Fixes #349
2022-12-08 01:32:04 +05:30
renovate[bot]andGitHub bd5cf0f167 chore(deps): update actions/setup-java action to v3.8.0 2022-12-06 16:56:08 +00:00
Harsh Shandilya a8ba0ed3d9 refactor(build-logic): massage around some types and extensions 2022-12-04 20:24:33 +05:30
Harsh Shandilya 1267cf81e3 refactor(build-logic): make everything top-level 2022-12-04 20:13:08 +05:30
Harsh Shandilya f48ef0206f fix(gradle): remove unused exclusiveContent block 2022-12-03 20:22:54 +05:30
renovate[bot]andGitHub 7b31dc740b chore(deps): update actions/setup-java action to v3.7.0 2022-12-01 14:38:07 +00:00
Harsh Shandilya 69c76a06c2 fix(gradle): use internal kotlin-android plugin 2022-12-01 12:18:45 +05:30
GitHub Actions 83e01c7cbc feat(release): start next development iteration 2022-11-30 14:38:50 +00:00
GitHub Actions b6421b01ba feat(release): bump version 2022-11-30 14:37:38 +00:00
Harsh Shandilya 9bc4a8c81b fix(readme): use an HTML image to downsize featured graphic 2022-11-30 15:01:20 +05:30
Harsh Shandilya 8cae135784 chore: refresh AGP experimental features opt-in 2022-11-30 14:35:56 +05:30
Harsh Shandilya eba5d64998 refactor: migrate all tests to kotest 2022-11-30 01:47:17 +05:30
Harsh Shandilya 8eec09e832 feat(gradle): add kotest deps 2022-11-30 01:32:15 +05:30
Harsh Shandilya 69537da95b chore: mark baseline profile as generated 2022-11-30 01:04:38 +05:30
Harsh Shandilya a679b88497 chore: update changelog 2022-11-30 01:04:38 +05:30
Harsh Shandilya e570b9d88f feat(android): refresh baseline profile 2022-11-30 01:04:38 +05:30
Harsh Shandilya ce37523e11 fix(benchmark): ensure UiAutomator is able to correctly find UI elements 2022-11-30 01:04:38 +05:30
Harsh Shandilya 3601c190ac fix(benchmark): disable baseline profile benchmark 2022-11-29 21:26:55 +05:30
Harsh Shandilya b0044dd0fc chore(deps): override ProfileInstaller version 2022-11-29 21:22:39 +05:30
Harsh Shandilya b2c2be45bf refactor: cleanup and simplify AndroidCommonPlugin 2022-11-29 21:22:39 +05:30
Harsh Shandilya 6f219b6da2 fix(benchmark): ensure benchmark build type is not obfuscated 2022-11-29 21:22:39 +05:30
Harsh Shandilya 8d6c567c84 refactor: simplify build type configuration 2022-11-29 21:22:39 +05:30
Harsh Shandilya 2b2010a22c fix(ci): only run one instance of snapshot workflow 2022-11-29 18:15:25 +05:30
Harsh Shandilya 27af06e180 chore: try out a full size image 2022-11-29 18:11:46 +05:30
Harsh Shandilya 499088cf43 chore: re-do README screenshot 2022-11-29 18:09:01 +05:30
renovate[bot]andGitHub ce579f21f9 fix(deps): update accompanist to v0.28.0 2022-11-29 04:51:00 +00:00
Harsh Shandilya 5a4643dc57 refactor: start consuming whetstone through Sonatype snapshots repository
It was much easier to set up automatic publishing than to constantly debug weird build errors from included builds
2022-11-29 03:29:06 +05:30
Harsh Shandilya 2a65dc4a9e chore(deps): bump whetstone tag 2022-11-29 02:17:04 +05:30
Harsh Shandilya d08aa25f76 fix: add changelog entry 2022-11-29 01:53:48 +05:30
Harsh Shandilya 410c7ccd1d fix: add missing DI annotation to CoilAppPlugin 2022-11-29 01:53:48 +05:30
Harsh Shandilya 5443cc1486 refactor: simplify swipe to refresh 2022-11-29 01:25:20 +05:30
Harsh Shandilya 683cb0cfbb fix: adjust capitalisation of CI steps 2022-11-28 19:56:59 +05:30
GitHub Actions cbf89b4158 feat(release): start next development iteration 2022-11-28 14:18:53 +00:00
GitHub Actions bbf315fecc feat(release): bump version 2022-11-28 14:16:35 +00:00
Harsh Shandilya 238d410245 Revert "fix: cache pager flows in ViewModel scope"
Trying to see if this fixes anything

This reverts commit 6b05a2991d.
2022-11-28 11:53:12 +00:00
Harsh Shandilya 41adf7566e Revert "Revert "chore(deps): update dependency gradle to v7.6""
After some painful wiping of local caches this seems to not be a problem anymore

This reverts commit 44733a05cd.
2022-11-28 12:57:23 +05:30
Harsh Shandilya 9f6d231912 refactor: add actions parameter to ClawAppBar 2022-11-28 12:57:17 +05:30
Harsh Shandilya 84e09fd5e1 refactor: remove unnecessary clickable configuration 2022-11-28 12:57:02 +05:30
Harsh Shandilya e12fc5ae6a fix(detekt): update config for 1.22.0 deprecations 2022-11-27 21:18:12 +05:30
Harsh Shandilya 9aa50637e0 fix(renovate): also disable Gradle updates 2022-11-27 21:18:12 +05:30
Harsh Shandilya 8e4330aea3 chore: update changelog 2022-11-27 21:18:12 +05:30
Harsh Shandilya 6b05a2991d fix: cache pager flows in ViewModel scope 2022-11-27 21:18:12 +05:30
Harsh Shandilya 6cd57b72b7 feat: add a loading indicator at the bottom of list 2022-11-27 21:18:12 +05:30
Harsh Shandilya a03ff1a60b chore: rename paging-related parameters and variables 2022-11-27 21:18:12 +05:30
Harsh Shandilya d59a151944 fix(deps): temporarily downgrade AGP to 7.3.1
Let's explain the decision with the help of a table

 AGP Version  | Has known issues | Has Studio available
--------------|------------------|---------------------
8.0.0-alpha07 |      No          |        No
8.0.0-alpha08 |      Yes         |        Yes
    7.3.1     |      No          |        Yes

And thus, we settle on 7.3.1
2022-11-27 19:28:56 +05:30
Harsh Shandilya 44733a05cd Revert "chore(deps): update dependency gradle to v7.6"
Seems to be causing hard-to-debug compilation errors

This reverts commit 98c489a32a.
2022-11-27 18:44:04 +05:30
renovate[bot]andGitHub 98c489a32a chore(deps): update dependency gradle to v7.6 2022-11-25 15:27:57 +00:00
renovate[bot]andGitHub f5b731a6e1 fix(deps): update dependency com.diffplug.spotless:spotless-plugin-gradle to v6.12.0 2022-11-24 23:53:18 +00:00
renovate[bot]andGitHub 180aafc71b chore(deps): update mindsers/changelog-reader-action action to v2.2.2 2022-11-23 15:26:47 +00:00
renovate[bot]andGitHub 2320917c46 chore(deps): update dependency com.twitter.compose.rules:detekt to v0.0.26 2022-11-21 19:05:13 +00:00
Harsh Shandilya 22081227c0 fixup! chore: disable AGP updates 2022-11-21 18:17:25 +05:30
Harsh Shandilya b893aff077 fix(release): add another newline after the changelog header 2022-11-21 18:06:40 +05:30
Harsh Shandilya 1558bc769d fix(release): checkout repository with PAT 2022-11-21 18:05:03 +05:30
Harsh Shandilya 7abe629744 chore: disable AGP updates 2022-11-21 18:03:29 +05:30
Harsh Shandilya f31d388c04 feat(release): start next development iteration 2022-11-21 18:03:29 +05:30
GitHub Actions 8e9ec3ab16 feat(release): bump version 2022-11-21 11:45:25 +00:00
Harsh Shandilya 86dd862469 fix: update whetstone checkout tag
The AGP fix needed to be pushed to whetstone as well
2022-11-21 17:03:10 +05:30
Harsh Shandilya c90e5b2fe5 Revert "fix: don't use markdown heading for PS release notes"
This is mostly meaniningless

This reverts commit 20d527246a.
2022-11-21 16:58:40 +05:30
Harsh Shandilya 9f5722a170 chore: add changelog entry for baseline profiles fix 2022-11-21 16:58:40 +05:30
Harsh Shandilya 5d8a08de2f Revert "chore(deps): update agp to v8.0.0-alpha08"
Workaround for https://issuetracker.google.com/259556213

This reverts commit 79778466b9.
2022-11-21 16:58:40 +05:30
renovate[bot]andGitHub 4b2382692e fix(deps): update dependency io.gitlab.arturbosch.detekt:detekt-gradle-plugin to v1.22.0 2022-11-21 00:02:14 +00:00
Harsh Shandilya 20d527246a fix: don't use markdown heading for PS release notes 2022-11-20 16:05:47 +05:30
Harsh Shandilya e4499ab8d3 fix(release): make push command branch agnostic 2022-11-20 14:03:40 +05:30
Harsh Shandilya eaf6d55108 feat: rework release pipeline to use centralized changelog 2022-11-20 14:03:40 +05:30
Harsh Shandilya 9b2da12451 chore: remove distribution changelog file 2022-11-20 14:01:53 +05:30
Harsh Shandilya 9cccf2f42e chore: add changelog 2022-11-20 14:01:53 +05:30
Harsh Shandilya c437d32b51 chore: add keystore.properties to gitignore 2022-11-20 11:20:02 +05:30
Harsh Shandilya 6249364c5e feat(release): start next development iteration 2022-11-20 01:27:35 +05:30
Harsh Shandilya b3b896694f fix(release): use --notes-file flag 2022-11-20 01:27:35 +05:30
GitHub Actions 00f164e347 feat(release): bump version 2022-11-19 17:20:52 +00:00
Harsh Shandilya e6ebb10bff refactor: add a Modifier parameter to LobstersApp 2022-11-19 20:13:46 +05:30
Harsh Shandilya 960222c489 refactor: remove navigation animations 2022-11-19 20:13:46 +05:30
Harsh Shandilya b82f93b459 chore(ci): show stacktrace on failure 2022-11-19 18:10:32 +05:30
Harsh Shandilya d3a6a82b50 Revert "Revert "fix(ci): disable configuration cache""
This reverts commit f7b4f4383c.
2022-11-19 18:10:10 +05:30
Harsh Shandilya f429faf8b8 feat(metadata-extractor): don't inspect canonical URLs
Changing the URL feels a bit icky when lobste.rs is a
_link_ aggregator at core. Let's trust the users are making
the right choices.
2022-11-19 18:07:56 +05:30
Harsh Shandilya 15d18414df chore: update changelog 2022-11-19 18:01:18 +05:30
Harsh Shandilya f7b2b0f9bc feat(android): replace Accompanist swiperefresh with Compose Material 2022-11-19 18:00:23 +05:30
Harsh Shandilya 2c1fdd51f8 fix(gradle): use a tagged checkout for whetstone 2022-11-19 17:32:30 +05:30
Harsh Shandilya a10ba38950 Revert "fix(gradle): change checkouts directory"
This reverts commit 6a8893a5aa.
2022-11-19 17:32:06 +05:30
Harsh Shandilya f7b4f4383c Revert "fix(ci): disable configuration cache"
This reverts commit fa62d871f8.
2022-11-19 17:30:16 +05:30
renovate[bot]andGitHub fb8bbf85c9 fix(deps): update accompanist to v0.27.1 2022-11-17 10:55:04 +00:00
renovate[bot]andGitHub ee80a5a52e fix(deps): update dependency com.google.testparameterinjector:test-parameter-injector to v1.10 2022-11-15 14:18:35 +00:00
renovate[bot]andGitHub 8a5a898be3 fix(deps): update dependency com.google.dagger:dagger to v2.44.2 2022-11-14 18:59:59 +00:00
Harsh Shandilya ed23fab7d9 fix(release): push the tag directly 2022-11-14 19:04:42 +05:30
Harsh Shandilya 0eba2aeaf4 feat(release): start next development iteration 2022-11-14 19:03:27 +05:30
Harsh Shandilya b728103b13 feat(release): bump version 2022-11-14 19:00:39 +05:30
Harsh ShandilyaandGitHub abe8ec77cc fix(github): use a PAT for the release workflow 2022-11-14 17:24:49 +05:30
Harsh Shandilya fa62d871f8 fix(ci): disable configuration cache 2022-11-14 15:24:13 +05:30
Harsh Shandilya aaf67de8d4 chore(deps): remove unused napt plugin declaration 2022-11-14 14:55:13 +05:30
Harsh Shandilya 4ac8882bec chore: update changelog 2022-11-14 14:54:10 +05:30
Harsh Shandilya 6a8893a5aa fix(gradle): change checkouts directory 2022-11-14 14:30:40 +05:30
Harsh Shandilya 839773e48e refactor(gradle): configure Lint for all modules 2022-11-14 13:56:56 +05:30
Harsh Shandilya aa2a109587 fix: prevent warnings in CI 2022-11-14 12:59:01 +05:30
Harsh Shandilya 0232a4179a refactor(di): replace Tangle with Whetstone 2022-11-14 12:59:01 +05:30
Harsh Shandilya 4a87682b68 refactor(di): migrate to Whetstone ApplicationScope 2022-11-14 12:59:01 +05:30
Harsh Shandilya 52562afbd1 feat(deps): configure Whetstone dependencies 2022-11-14 12:59:01 +05:30
Harsh Shandilya 15abd31b03 fix(core): reduce HTTP logging level 2022-11-13 21:15:03 +05:30
Harsh Shandilya 157fe2c221 refactor(di): move out Coil configuration to an AppPlugin 2022-11-13 21:10:57 +05:30
Harsh Shandilya 93db17e6d7 refactor(android): turn off StrictMode 2022-11-13 21:01:00 +05:30
Harsh Shandilya c75d1959a6 feat(android): integrate AppPlugin 2022-11-13 20:35:18 +05:30
Harsh Shandilya 72acf9c850 feat(android): add a WorkManager plugin 2022-11-13 20:35:18 +05:30
Harsh Shandilya 77af00229f feat(core): add a Napier plugin 2022-11-13 20:35:18 +05:30
Harsh Shandilya 0390336d50 feat(core): introduce the AppPlugin interface 2022-11-13 20:35:18 +05:30
Harsh Shandilya 0bf60812e0 refactor(di): workaround unstable IntoSet ordering
EitherNet's ApiResultConverterFactory needs to go before any other
(de)serialization factories, but multibindings do not have ordering
guarantees. To fix this, I've changed the module to handle the EitherNet
configuration within api and let core continue supplying everything else.
2022-11-13 18:11:50 +05:30
Harsh Shandilya 8a4fee5f7c chore(deps): remove unused dependencies from android module 2022-11-13 17:17:01 +05:30
Harsh Shandilya 005dc9ab83 refactor(di): move ApiModule to core and api 2022-11-13 17:17:01 +05:30
Harsh Shandilya 9d41b7e819 refactor(di): migrate OkHttp module to core 2022-11-13 16:27:33 +05:30
Harsh Shandilya b8965902ba chore: cleanup Hilt remnant 2022-11-13 15:15:01 +05:30
Harsh Shandilya ef5242f115 chore: fix Detekt build 2022-11-13 15:07:30 +05:30
Harsh Shandilya 0dd2a3be6f fix(benchmark): remove outdated annotation 2022-11-13 13:52:50 +05:30
Harsh Shandilya 10eecce01c fix(deps): re-align androidx.test versioning 2022-11-13 13:49:24 +05:30
Harsh Shandilya 4fe7c64e42 refactor(android): migrate to Anvil
Fixes #261
2022-11-13 13:15:53 +05:30
Harsh Shandilya 6d88335e13 refactor(metadata-extractor): break Hilt dependency 2022-11-13 13:14:55 +05:30
Harsh Shandilya e3e14edb5d refactor(coroutine-utils): break Hilt dependency 2022-11-13 13:14:44 +05:30
Harsh Shandilya 56d47d1f32 refactor(di): move database injection to its module 2022-11-13 13:13:59 +05:30
Harsh Shandilya ce37903ea0 feat(di): kick off Anvil infra 2022-11-13 13:13:03 +05:30
Harsh Shandilya 3c0a8dd392 feat(gradle): add Anvil and Tangle deps 2022-11-13 13:12:32 +05:30
Harsh Shandilya e7228d35df Revert "feat(build-logic): remove kapt plugin"
This reverts commit e0f68d05e9.
2022-11-13 13:11:35 +05:30
Harsh Shandilya 28c6f62b81 refactor(android): simplify SavedPostUpdaterWorker 2022-11-13 12:32:09 +05:30
Harsh Shandilya 4a48a2ba4c fix(deps): bump Compose Compiler to 1.4.0-alpha02 2022-11-13 01:31:12 +05:30
renovate[bot]andGitHub 18ed012154 fix(deps): update kotlin to v1.7.21 2022-11-12 19:57:41 +00:00
Harsh Shandilya 653787c946 fix(renovate): reformat config with Boop 2022-11-13 01:23:50 +05:30
Harsh Shandilya e90c2f5a7d feat(renovate): configure repositories for Compose Compiler 2022-11-13 01:23:04 +05:30
Harsh Shandilya 5a175c162c feat(renovate): add a regex manager for Compose Compiler 2022-11-13 01:07:45 +05:30
Harsh Shandilya 1373198df0 feat(renovate): enable and batch Kotlin + Compose Compiler updates 2022-11-13 01:03:29 +05:30
renovate[bot]andGitHub 0ab452f943 chore(deps): update dependency com.twitter.compose.rules:detekt to v0.0.24 2022-11-12 10:36:00 +00:00
renovate[bot]andGitHub 07e0de1b0d fix(deps): update dependency com.github.ben-manes:gradle-versions-plugin to v0.44.0 2022-11-12 09:05:32 +00:00
renovate[bot]andGitHub f92fb48752 chore(deps): update dagger to v2.44.1 2022-11-11 18:02:02 +00:00
Harsh Shandilya dc7ef55461 fix(release): add GITHUB_TOKEN env variable for gh CLI 2022-11-10 22:24:44 +05:30
Harsh Shandilya 6581e56c31 refactor(scripts): remove now unused release script 2022-11-10 22:17:19 +05:30
Harsh Shandilya da40cf3892 feat(release): start next development iteration 2022-11-10 22:15:16 +05:30
Harsh Shandilya 2eb0a6a31c feat(release): bump version 2022-11-10 22:12:04 +05:30
Harsh Shandilya 2d44ad2055 feat(github): make release workflow self-contained 2022-11-10 22:09:16 +05:30
Harsh Shandilya 8af4bddfe1 feat(release): add changelog entry for Compose bump 2022-11-10 21:20:17 +05:30
renovate[bot]andGitHub 0be0952e04 fix(deps): update dependency androidx.work:work-runtime-ktx to v2.8.0-beta02 2022-11-10 10:06:12 +00:00
renovate[bot]andGitHub 8b2ba790bc fix(deps): update dependency androidx.test:runner to v1.5.1 2022-11-10 08:07:26 +00:00
Harsh Shandilya 6ed377af94 fix(github): adjust when cache is written to 2022-11-10 13:30:49 +05:30
renovate[bot]andGitHub cfc479a9ee fix(deps): update dependency androidx.compose:compose-bom to v2022.11.00 2022-11-10 05:31:33 +00:00
renovate[bot]andGitHub 7552fb1c36 fix(deps): update dependency androidx.profileinstaller:profileinstaller to v1.3.0-alpha02 2022-11-10 02:24:42 +00:00
renovate[bot]andGitHub bcd8a412b5 fix(deps): update dependency androidx.navigation:navigation-compose to v2.6.0-alpha04 2022-11-09 23:38:19 +00:00
renovate[bot]andGitHub defe4914df fix(deps): update dependency androidx.benchmark:benchmark-macro-junit4 to v1.2.0-alpha07 2022-11-09 20:43:05 +00:00
renovate[bot]andGitHub 621e93939b chore(deps): update plugin napt to v1.18 2022-11-09 17:50:41 +00:00
renovate[bot]andGitHub 247949f92e chore(deps): update dependency com.twitter.compose.rules:detekt to v0.0.23 2022-11-09 14:36:56 +00:00
renovate[bot]andGitHub 5700b2150e fix(deps): update dependency androidx.test.ext:junit to v1.1.4 2022-11-09 03:13:19 +00:00
renovate[bot]andGitHub 530ad44b27 fix(deps): update dependency androidx.test.espresso:espresso-core to v3.5.0 2022-11-08 23:46:03 +00:00
renovate[bot]andGitHub 51c7306cfa fix(deps): update dependency androidx.test:runner to v1.5.0 2022-11-08 20:15:58 +00:00
renovate[bot]andGitHub 79778466b9 chore(deps): update agp to v8.0.0-alpha08 2022-11-07 19:09:52 +00:00
renovate[bot]andHarsh Shandilya 7d7989d5c0 chore(deps): pin dependencies 2022-11-05 16:45:25 +05:30
Harsh Shandilya 923361ee1b fix(renovate): pin actions via SHA 2022-11-05 16:45:14 +05:30
renovate[bot]andGitHub 6adf339e08 chore(deps): update agp to v8.0.0-alpha07 2022-11-02 17:55:42 +00:00
Harsh Shandilya 9e37246810 fix(release): add status parameter explicitly 2022-11-02 14:45:27 +05:30
Harsh Shandilya 7be1157f22 Revert "fix(release): rework Play Store release workflow"
This reverts commit cd7d82d378.
2022-11-02 14:44:48 +05:30
Harsh Shandilya cd7d82d378 fix(release): rework Play Store release workflow 2022-11-02 14:37:26 +05:30
Harsh Shandilya b30425d97d feat(release): start next development iteration 2022-11-02 14:17:58 +05:30
Harsh Shandilya c89589adcb feat(release): bump version
Release to Google Play / publish-google-play-release (push) Canceled after 0s
2022-11-02 14:17:05 +05:30
Harsh Shandilya 6523b85f07 fix(deps): reformat catalog 2022-11-02 12:57:56 +05:30
Harsh Shandilya 33f8b73938 chore: fill out changelog 2022-11-02 12:25:13 +05:30
Harsh Shandilya bb47119c84 fix: remove inaccurate copyright header
Same reasoning as this change https://github.com/rust-lang/rust/commit/2a8807e889a43c6b89eb6f2736907afa87ae592f
2022-11-02 12:23:09 +05:30
Harsh Shandilya 6adb45ff7c fix(metadata-extractor): migrate away from removed APIs 2022-11-01 19:06:27 +05:30
renovate[bot]andGitHub a87b1cf38c fix(deps): update dependency com.chimbori.crux:crux to v3.11.0 2022-11-01 10:50:53 +00:00
renovate[bot]andGitHub b112585533 fix(deps): update material_motion to v0.10.3 2022-10-30 09:52:46 +00:00
renovate[bot]andGitHub 5750bbebd3 fix(deps): update dependency androidx.test.ext:junit to v1.1.4-rc01 2022-10-27 05:20:30 +00:00
renovate[bot]andGitHub 067b762a31 fix(deps): update dependency androidx.test.espresso:espresso-core to v3.5.0-rc01 2022-10-27 02:48:26 +00:00
renovate[bot]andGitHub 658a2f36fd fix(deps): update androidx.test to v1.5.0-rc01 2022-10-26 23:46:41 +00:00
Harsh Shandilya 6a4d0c2d3a refactor: add license headers across the board 2022-10-26 01:55:50 +05:30
Harsh Shandilya 5319669a8e feat: configure Spotless license headers 2022-10-26 01:54:38 +05:30
Harsh Shandilya 720f946f41 fix(gradle): only allow Spotless plugin to be applied to the root project 2022-10-26 01:30:33 +05:30
renovate[bot]andGitHub a1390a0f05 fix(deps): update accompanist to v0.27.0 2022-10-25 03:32:37 +00:00
Harsh Shandilya 655b20e496 fix(github): bump r0adkll/upload-google-play
The new maintainer has not been bumping the v1 tag
2022-10-24 23:20:39 +05:30
Harsh Shandilya 8508a8646f feat(release): start next development iteration 2022-10-24 23:11:30 +05:30
Harsh Shandilya 790874e1a7 feat(release): bump version
Release to Google Play / publish-google-play-release (push) Canceled after 0s
2022-10-24 23:09:41 +05:30
renovate[bot]andGitHub 0ac7c9876e fix(deps): update dependency androidx.test:runner to v1.5.0-beta02 2022-10-24 17:37:20 +00:00
renovate[bot]andGitHub daba852e6d fix(deps): update dependency androidx.test.espresso:espresso-core to v3.5.0-beta02 2022-10-24 17:32:03 +00:00
Harsh Shandilya 47f7f97aa2 fix(renovate): use exact file matches for regex managers 2022-10-24 23:01:22 +05:30
Harsh Shandilya 80bfb34e5b refactor(deps): switch Compose dependencies to BOM 2022-10-24 22:49:58 +05:30
Harsh Shandilya 42310a6a33 fix(ci): ensure signing is setup for release workflow 2022-10-24 22:22:30 +05:30
Harsh Shandilya c554459191 fix(scripts): make GitHub release non-interactive 2022-10-24 22:20:13 +05:30
Harsh Shandilya 1560574e15 fix: delete artifact directories before copying to them 2022-10-24 22:04:51 +05:30
Harsh Shandilya a1f3950042 chore(deps): realign androidx.compose.ui:ui version 2022-10-24 21:21:17 +05:30
Harsh Shandilya d7bafbddbc fix(scripts): automatically put release notes as tag message 2022-10-24 21:17:39 +05:30
renovate[bot]andGitHub ef5e4a952e fix(deps): update dependency androidx.compose.ui:ui to v1.3.0 2022-10-24 15:02:52 +00:00
Harsh Shandilya 5711bab007 fix(scripts): clear changelog in make-release 2022-10-24 20:27:22 +05:30
Harsh Shandilya b1fca935a8 chore(deps): update AndroidX dependencies 2022-10-24 20:24:45 +05:30
Harsh Shandilya 2780f7b204 fix(scripts): fix snapshot deployment 2022-10-24 19:23:22 +05:30
Harsh Shandilya 22231606cb feat(ci): add workflow to publish releases to Google Play 2022-10-24 19:17:54 +05:30
Harsh Shandilya c797853055 feat: init release notes 2022-10-24 19:17:54 +05:30
Harsh Shandilya b003f0c283 fix(scripts): bring make-release up-to-date 2022-10-24 19:17:54 +05:30
Harsh Shandilya cc55687292 feat(rename-artifacts): add support for obfuscation mapping files 2022-10-24 19:17:54 +05:30
Harsh Shandilya 531234e036 fix(scripts): add executable bit to make-release.sh 2022-10-24 18:09:48 +05:30
renovate[bot]andGitHub ed5bbdd571 chore(deps): update gradle/gradle-build-action action to v2.3.3 2022-10-22 16:27:13 +00:00
renovate[bot]andGitHub 23cd91a97a fix(deps): update dependency com.chimbori.crux:crux to v3.10.0 2022-10-22 02:33:32 +00:00
renovate[bot]andGitHub 6761659c7f chore(deps): update actions/upload-artifact action to v3.1.1 2022-10-21 20:52:43 +00:00
renovate[bot]andGitHub 6bd3dd81f0 fix(deps): update richtext to v0.15.0 2022-10-21 00:24:14 +00:00
renovate[bot]andGitHub 765a7f6f0b chore(deps): update dependency com.twitter.compose.rules:detekt to v0.0.22 2022-10-19 20:33:12 +00:00
renovate[bot]andGitHub 46f4d9a397 chore(deps): update dependency com.twitter.compose.rules:detekt to v0.0.21 2022-10-19 12:59:44 +00:00
renovate[bot]andGitHub 2cc9e38e77 chore(deps): update agp to v8.0.0-alpha05 2022-10-18 15:35:44 +00:00
renovate[bot]andGitHub dc954c7ba5 chore(deps): update actions/setup-java action to v3.6.0 2022-10-18 12:58:55 +00:00
Harsh Shandilya 15dc9c3dc7 fix(common): prevent link metadata resolution from creating fatal errors 2022-10-18 12:08:49 +05:30
Harsh Shandilya a4be2d4bf2 fix(deps): update dependency org.jetbrains.kotlin:kotlin-serialization to v1.4.1 2022-10-17 18:05:55 +05:30
Harsh Shandilya 5329b6e3aa feat(release): add script to make a release 2022-10-17 12:27:15 +05:30
Harsh Shandilya 204a3c0bb5 feat(release): start next development iteration 2022-10-17 11:25:46 +05:30
Harsh Shandilya 69eed8a23a feat(release): bump version 2022-10-17 11:25:21 +05:30
renovate[bot]andGitHub 3c741a7fe1 fix(deps): update dependency com.github.ben-manes:gradle-versions-plugin to v0.43.0 2022-10-17 05:52:56 +00:00
Harsh Shandilya ccfcd48438 refactor: simplify argument passing 2022-10-16 12:44:07 +05:30
Harsh Shandilya 79aba9a187 feat: load link metadata lazily 2022-10-16 12:21:19 +05:30
renovate[bot]andGitHub 4337a19abc fix(deps): update dependency org.gradle.android.cache-fix:org.gradle.android.cache-fix.gradle.plugin to v2.6.0 2022-10-15 03:40:04 +00:00
renovate[bot]andGitHub a53e9f0065 chore(deps): update agp to v8.0.0-alpha04 2022-10-15 00:23:38 +00:00
renovate[bot]andGitHub e6ca8365ca fix(deps): update dependency nl.littlerobots.version-catalog-update:nl.littlerobots.version-catalog-update.gradle.plugin to v0.7.0 2022-10-14 12:12:01 +00:00
renovate[bot]andGitHub f1805396e5 chore(deps): update dependency com.twitter.compose.rules:detekt to v0.0.20 2022-10-11 17:04:16 +00:00
renovate[bot]andGitHub 6d70d997a3 chore(deps): update dependency com.twitter.compose.rules:detekt to v0.0.19 2022-10-10 13:01:32 +00:00
Harsh Shandilya 4e872bfa4a fix(gradle): re-enable Test state tracking 2022-10-10 01:29:32 +05:30
Harsh Shandilya fdc9ae8968 fix(gradle): raise Kotlin language version to 1.7 2022-10-10 01:29:32 +05:30
Harsh Shandilya f31debc685 fix(gradle): enable allWarningsAsErrors on CI 2022-10-10 01:29:32 +05:30
Harsh Shandilya 74c265538d fix(api): cleanup dependencies 2022-10-10 01:29:32 +05:30
Harsh Shandilya 4607a68de0 refactor(api): update tests for EitherNet 2022-10-10 01:29:32 +05:30
Harsh Shandilya 8652d4ceaa refactor(api): adopt EitherNet 2022-10-10 01:29:32 +05:30
Harsh Shandilya 484fac5779 Revert "Revert "android: enable support for back navigation animation""
This reverts commit f0a7ffb4b4.
2022-10-09 13:11:54 +05:30
Harsh Shandilya 0d7a7a0e22 fix(common): add shadow to ClawAppBar 2022-10-07 21:13:29 +05:30
renovate[bot]andGitHub 8470e95f6b fix(deps): update dependency androidx.test.ext:junit to v1.1.4-beta01 2022-10-07 11:54:17 +00:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
ad72c80130 fix(deps): update dependency androidx.test.espresso:espresso-core to v3.5.0-beta01 (#340)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-07 11:53:41 +00:00
renovate[bot]andGitHub be7840125f fix(deps): update androidx.test 2022-10-07 06:21:40 +00:00
renovate[bot]andGitHub cf3549f09a chore(deps): update agp to v8.0.0-alpha03 2022-10-06 22:52:03 +00:00
Harsh Shandilya 6ee00f6a27 refactor(build-logic): cleanup Detekt issues 2022-10-06 11:43:54 +05:30
Harsh Shandilya d963fd8ae4 chore(deps): commonize compose.ui version 2022-10-06 10:33:27 +05:30
renovate[bot]andGitHub 32ca4f2115 fix(deps): update dependency androidx.compose.ui:ui to v1.3.0-rc01 2022-10-05 18:18:11 +00:00
Harsh Shandilya 95cbe8dfe4 chore(deps): bump AndroidX dependencies 2022-10-05 23:23:43 +05:30
Harsh Shandilya 7e7e00f06e fix(database): remove unnecessary experimental annotation 2022-10-05 12:38:01 +05:30
Harsh Shandilya 8eee42ae0f feat(metadata-extractor): allow de-AMP plugin to change our target URL 2022-10-05 12:38:01 +05:30
Harsh Shandilya 8060c347cc refactor(android): resolve Detekt-reported issues 2022-10-05 12:23:30 +05:30
Harsh Shandilya 8851071d58 refactor(common): resolve Detekt-reported issues 2022-10-05 12:23:30 +05:30
Harsh Shandilya 54c4b01411 chore(deps): upgrade to Compose Compiler 1.3.2 2022-10-05 00:08:50 +05:30
renovate[bot]andGitHub 176bc9b022 chore(deps): update actions/checkout action to v3.1.0 2022-10-04 11:26:24 +00:00
Harsh Shandilya 245130ef20 chore(deps): bump sqldelight to 2.0.0-alpha04 2022-10-04 12:36:43 +05:30
Harsh Shandilya 394558d634 fix(ci): run Detekt separately 2022-10-04 00:14:13 +05:30
Harsh Shandilya 0904740980 feat(detekt): enable PreviewNaming lint and re-baseline 2022-10-04 00:02:15 +05:30
renovate[bot]andGitHub 4212ca41cf chore(deps): update dependency com.twitter.compose.rules:detekt to v0.0.18 2022-10-03 18:01:00 +00:00
Harsh Shandilya 33b8503f19 feat(detekt): add a flag to turn on debug input 2022-10-03 22:48:23 +05:30
Harsh Shandilya eb8be2f909 feat(detekt): try running type resolution enabled task if available 2022-10-03 22:45:21 +05:30
Harsh Shandilya e1d16e9f36 refactor(coroutine-utils): suppress false-positive InjectDispatcher lint 2022-10-03 22:40:59 +05:30
Harsh Shandilya dd76c7bed7 refactor(android): remove unnecessary non-null assertion 2022-10-03 22:40:33 +05:30
Harsh Shandilya a1ab32c43e refactor(common): fix Modifier re-use 2022-10-03 22:39:44 +05:30
Harsh Shandilya 069d7e5e05 refactor(common): workaround false-positive MagicNumber lint 2022-10-03 12:57:39 +05:30
Harsh Shandilya 8f927613f3 refactor(common): correctly use modifier in TagText 2022-10-03 12:57:39 +05:30
Harsh Shandilya f5de95af03 refactor(common): remove unused code 2022-10-03 12:57:39 +05:30
Harsh Shandilya 535c8a5808 refactor(common): pass HTMLConverter down as a parameter 2022-10-03 12:57:39 +05:30
Harsh Shandilya 02416a3606 feat(common): delete unused Paparazzi tests 2022-10-03 12:57:39 +05:30
Harsh Shandilya 293d12454f refactor(common): add a message to preview throwable 2022-10-03 12:57:39 +05:30
Harsh Shandilya d178bebebd refactor(common): ensure exceptions in UrlLauncher are logged 2022-10-03 12:57:38 +05:30
Harsh Shandilya 38f1fdb6a3 refactor(common): add missing Modifier parameters 2022-10-03 12:57:38 +05:30
Harsh Shandilya 46bfafd681 refactor(common): pass Modifier to UserProfileInternal 2022-10-03 11:33:47 +05:30
Harsh Shandilya 999f2dfb01 refactor(android): simplify Destinations 2022-10-03 11:32:45 +05:30
Harsh Shandilya 52ac8346b3 refactor: resolve Detekt issues found in metadata-extractor 2022-10-02 16:38:12 +05:30
Harsh Shandilya 1d761b1f6e refactor: suppress LongParameterList in model module 2022-10-02 16:38:12 +05:30
Harsh Shandilya 16404e6654 feat(detekt): sync baseline 2022-10-02 16:38:12 +05:30
Harsh Shandilya 0f684a3728 feat(detekt): add Twitter's Compose rules 2022-10-02 16:38:12 +05:30
Harsh Shandilya 2685111c70 refactor(build): extract detekt configuration 2022-10-02 16:38:12 +05:30
Harsh Shandilya b867804bdb refactor(detekt): adjust for Compose 2022-10-02 16:38:12 +05:30
Harsh Shandilya 37bb6e1d36 feat(detekt): add default config 2022-10-02 16:38:12 +05:30
Harsh Shandilya 78c321d269 chore(common): suppress warnings in generated code 2022-10-02 16:38:12 +05:30
Harsh Shandilya cab3f74ce0 feat(detekt): add baselines 2022-10-02 16:38:12 +05:30
Harsh Shandilya f0cff549b4 feat(build): add Detekt 2022-10-02 16:38:12 +05:30
Harsh Shandilya 5ab0774689 refactor(android): extract annotations to their own file 2022-10-02 16:38:12 +05:30
Harsh Shandilya 8835e07ac6 chore: reformat proguard file 2022-10-02 15:17:39 +05:30
renovate[bot]andGitHub 85de847692 fix(deps): update coil to v2.2.2 2022-10-02 03:54:47 +00:00
Harsh Shandilya ccc14dee4f feat(release): prepare next development revision 2022-09-30 15:19:00 +05:30
181 changed files with 23117 additions and 6257 deletions
+93
View File
@@ -0,0 +1,93 @@
# This .editorconfig section approximates ktfmt's formatting rules. You can include it in an
# existing .editorconfig file or use it standalone by copying it to <project root>/.editorconfig
# and making sure your editor is set to read settings from .editorconfig files.
#
# It includes editor-specific config options for IntelliJ IDEA.
#
# If any option is wrong, PR are welcome
[{*.kt,*.kts}]
indent_style = space
insert_final_newline = true
max_line_length = 100
indent_size = 2
ij_continuation_indent_size = 2
ij_java_names_count_to_use_import_on_demand = 9999
ij_kotlin_align_in_columns_case_branch = false
ij_kotlin_align_multiline_binary_operation = false
ij_kotlin_align_multiline_extends_list = false
ij_kotlin_align_multiline_method_parentheses = false
ij_kotlin_align_multiline_parameters = true
ij_kotlin_align_multiline_parameters_in_calls = false
ij_kotlin_allow_trailing_comma = true
ij_kotlin_allow_trailing_comma_on_call_site = true
ij_kotlin_assignment_wrap = normal
ij_kotlin_blank_lines_after_class_header = 0
ij_kotlin_blank_lines_around_block_when_branches = 0
ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1
ij_kotlin_block_comment_at_first_column = true
ij_kotlin_call_parameters_new_line_after_left_paren = true
ij_kotlin_call_parameters_right_paren_on_new_line = false
ij_kotlin_call_parameters_wrap = on_every_item
ij_kotlin_catch_on_new_line = false
ij_kotlin_class_annotation_wrap = split_into_lines
ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL
ij_kotlin_continuation_indent_for_chained_calls = true
ij_kotlin_continuation_indent_for_expression_bodies = true
ij_kotlin_continuation_indent_in_argument_lists = true
ij_kotlin_continuation_indent_in_elvis = false
ij_kotlin_continuation_indent_in_if_conditions = false
ij_kotlin_continuation_indent_in_parameter_lists = false
ij_kotlin_continuation_indent_in_supertype_lists = false
ij_kotlin_else_on_new_line = false
ij_kotlin_enum_constants_wrap = off
ij_kotlin_extends_list_wrap = normal
ij_kotlin_field_annotation_wrap = split_into_lines
ij_kotlin_finally_on_new_line = false
ij_kotlin_if_rparen_on_new_line = false
ij_kotlin_import_nested_classes = false
ij_kotlin_insert_whitespaces_in_simple_one_line_method = true
ij_kotlin_keep_blank_lines_before_right_brace = 2
ij_kotlin_keep_blank_lines_in_code = 2
ij_kotlin_keep_blank_lines_in_declarations = 2
ij_kotlin_keep_first_column_comment = true
ij_kotlin_keep_indents_on_empty_lines = false
ij_kotlin_keep_line_breaks = true
ij_kotlin_lbrace_on_next_line = false
ij_kotlin_line_comment_add_space = false
ij_kotlin_line_comment_at_first_column = true
ij_kotlin_method_annotation_wrap = split_into_lines
ij_kotlin_method_call_chain_wrap = normal
ij_kotlin_method_parameters_new_line_after_left_paren = true
ij_kotlin_method_parameters_right_paren_on_new_line = true
ij_kotlin_method_parameters_wrap = on_every_item
ij_kotlin_name_count_to_use_star_import = 9999
ij_kotlin_name_count_to_use_star_import_for_members = 9999
ij_kotlin_parameter_annotation_wrap = off
ij_kotlin_space_after_comma = true
ij_kotlin_space_after_extend_colon = true
ij_kotlin_space_after_type_colon = true
ij_kotlin_space_before_catch_parentheses = true
ij_kotlin_space_before_comma = false
ij_kotlin_space_before_extend_colon = true
ij_kotlin_space_before_for_parentheses = true
ij_kotlin_space_before_if_parentheses = true
ij_kotlin_space_before_lambda_arrow = true
ij_kotlin_space_before_type_colon = false
ij_kotlin_space_before_when_parentheses = true
ij_kotlin_space_before_while_parentheses = true
ij_kotlin_spaces_around_additive_operators = true
ij_kotlin_spaces_around_assignment_operators = true
ij_kotlin_spaces_around_equality_operators = true
ij_kotlin_spaces_around_function_type_arrow = true
ij_kotlin_spaces_around_logical_operators = true
ij_kotlin_spaces_around_multiplicative_operators = true
ij_kotlin_spaces_around_range = false
ij_kotlin_spaces_around_relational_operators = true
ij_kotlin_spaces_around_unary_operator = false
ij_kotlin_spaces_around_when_arrow = true
ij_kotlin_variable_annotation_wrap = off
ij_kotlin_while_on_new_line = false
ij_kotlin_wrap_elvis_expressions = 1
ij_kotlin_wrap_expression_body_functions = 1
ij_kotlin_wrap_first_method_in_call_chain = false
+3
View File
@@ -3,3 +3,6 @@
e7f4c8a7ebe4e907f65af46e6dddfd3a542415c0
ab2713154f846b3db6d44625d1022bf421b275d0
cbdfa67d075ec1f8ceec0f3559498cbca01783a0
# Renamed Renovate config file
f304304bc5b64edf189aaa473b05df912067a8bd
+1
View File
@@ -1,3 +1,4 @@
* text=auto eol=lf
**/dependencies/** linguist-generated
**/clawicons/** linguist-generated
android/src/main/baseline-prof.txt linguist-generated -diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

-31
View File
@@ -1,31 +0,0 @@
{
"extends": [
"config:base",
":dependencyDashboard",
"github>msfjarvis/shared-workflows//renovate/automerge"
],
"packageRules": [
{
"managers": [
"gradle"
],
"packagePatterns": [
"^org.jetbrains.kotlin",
"^com.squareup.okhttp3"
],
"enabled": false
}
],
"regexManagers": [
{
"fileMatch": [
".kt$"
],
"matchStrings": [
"KTFMT_VERSION = \"(?<currentValue>.*)\""
],
"datasourceTemplate": "maven",
"depNameTemplate": "com.facebook:ktfmt"
}
]
}
+86
View File
@@ -0,0 +1,86 @@
{
"extends": [
"config:base",
":dependencyDashboard",
"helpers:pinGitHubActionDigests",
"github>msfjarvis/shared-workflows//renovate/automerge"
],
"branchConcurrentLimit": 15,
"packageRules": [
{
matchDatasources: [
"maven"
],
registryUrls: [
"https://maven.google.com/",
"https://repo1.maven.org/maven2",
"https://plugins.gradle.org/m2/",
],
},
{
"matchPackagePatterns": [
"^kotlin",
"^org.jetbrains.kotlin",
"^androidx.compose.compiler"
],
"groupName": "kotlin"
},
{
"managers": [
"gradle"
],
"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",
],
"enabled": false
},
],
"regexManagers": [
{
"fileMatch": [
"SpotlessPlugin\.kt$"
],
"matchStrings": [
"KTFMT_VERSION = \"(?<currentValue>.*)\""
],
"datasourceTemplate": "maven",
"depNameTemplate": "com.facebook:ktfmt",
"registryUrlTemplate": "https://repo1.maven.org/maven2/",
},
{
"fileMatch": [
"Detekt\.kt$"
],
"matchStrings": [
"TWITTER_RULES_VERSION = \"(?<currentValue>.*)\""
],
"datasourceTemplate": "maven",
"depNameTemplate": "com.twitter.compose.rules:detekt",
"registryUrlTemplate": "https://repo1.maven.org/maven2/",
},
{
"fileMatch": [
"gradle/libs.versions.toml"
],
"matchStrings": [
"composeCompiler = \"(?<currentValue>.*)\""
],
"datasourceTemplate": "maven",
"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",
}
]
}
+124
View File
@@ -0,0 +1,124 @@
name: Baseline profile generation
on:
# every day at 00:43
schedule:
- cron: '43 0 * * *'
workflow_dispatch:
env:
AVD_API_LEVEL: 31
AVD_ARCH: x86_64
jobs:
baseline-profile:
runs-on: macos-latest
timeout-minutes: 45
env:
TERM: dumb
steps:
- name: Checkout repository
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
with:
token: ${{ secrets.POST_RELEASE_GH_TOKEN }}
fetch-depth: 0
- name: Set up JDK
uses: actions/setup-java@3f07048e3d294f56e9b90ac5ea2c6f74e9ad0f98 # v3.10.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: Setup Gradle caching
uses: gradle/gradle-build-action@3fbe033aaae657f011f88f29be9e65ed26bd29ef # tag=v2.3.3
with:
gradle-home-cache-cleanup: true
# Restore AVD from cache
- name: Cache AVD
uses: actions/cache@627f0f41f6904a5b1efbaed9f96d9eb58e92e920 # v3.2.4
id: avd-cache
with:
path: |
~/.android/avd/*
~/.android/adb*
key: ${{ runner.os }}-avd-${{ env.AVD_API_LEVEL }}-${{ env.AVD_ARCH }}
- name: Decrypt secrets
run: |
./scripts/setup-age.sh
./scripts/signing-setup.sh "$ENCRYPT_KEY"
env:
ENCRYPT_KEY: ${{ secrets.ENCRYPT_KEY }}
# 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
# 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."
# 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/
# If we're on main branch, copy over the baseline profile and
# commit it 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
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
+24 -10
View File
@@ -10,28 +10,39 @@ on:
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
check:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3.0.2
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
- name: Set up JDK
uses: actions/setup-java@v3.5.1
uses: actions/setup-java@3f07048e3d294f56e9b90ac5ea2c6f74e9ad0f98 # v3.10.0
with:
distribution: temurin
java-version: 18
- name: Run unit tests
uses: gradle/gradle-build-action@v2.3.2
uses: gradle/gradle-build-action@3fbe033aaae657f011f88f29be9e65ed26bd29ef # tag=v2.3.3
with:
arguments: check -PslimTests
arguments: --no-configuration-cache --stacktrace check -PslimTests
gradle-home-cache-cleanup: true
cache-read-only: ${{ github.ref != 'refs/heads/main' }}
- name: Run Detekt
uses: gradle/gradle-build-action@3fbe033aaae657f011f88f29be9e65ed26bd29ef # tag=v2.3.3
with:
arguments: --no-configuration-cache --stacktrace detektMain
gradle-home-cache-cleanup: true
cache-read-only: ${{ github.ref != 'refs/heads/main' }}
- name: (Fail-only) Upload test report
if: failure()
uses: actions/upload-artifact@v3.1.0
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with:
name: Test report
path: android/build/reports
@@ -42,24 +53,27 @@ jobs:
needs: [ "check" ]
steps:
- name: Checkout repository
uses: actions/checkout@v3.0.2
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
- name: Set up JDK
uses: actions/setup-java@v3.5.1
uses: actions/setup-java@3f07048e3d294f56e9b90ac5ea2c6f74e9ad0f98 # v3.10.0
with:
distribution: temurin
java-version: 18
- name: Decrypt secrets
run: scripts/signing-setup.sh "$ENCRYPT_KEY"
run: |
./scripts/setup-age.sh
./scripts/signing-setup.sh "$ENCRYPT_KEY"
env:
ENCRYPT_KEY: ${{ secrets.ENCRYPT_KEY }}
- name: Build release app
uses: gradle/gradle-build-action@v2.3.2
uses: gradle/gradle-build-action@3fbe033aaae657f011f88f29be9e65ed26bd29ef # tag=v2.3.3
with:
arguments: collectReleaseApks
arguments: --no-configuration-cache --stacktrace collectReleaseApks
gradle-home-cache-cleanup: true
cache-read-only: ${{ github.ref != 'refs/heads/main' }}
- name: Clean secrets
run: scripts/signing-cleanup.sh
+26
View File
@@ -0,0 +1,26 @@
name: "CodeQL"
on:
workflow_dispatch:
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
- name: Initialize CodeQL
uses: github/codeql-action/init@8775e868027fa230df8586bdf502bbd9b618a477 # v2
with:
languages: java
- name: Autobuild
uses: github/codeql-action/autobuild@8775e868027fa230df8586bdf502bbd9b618a477 # v2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@8775e868027fa230df8586bdf502bbd9b618a477 # v2
with:
category: "/language:java"
+16
View File
@@ -0,0 +1,16 @@
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@584b706efdfc1149cfb5234066b3bbf73d48249e # v2022.3.3
env:
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
+111
View File
@@ -0,0 +1,111 @@
name: Release to Google Play
on:
workflow_dispatch:
jobs:
publish-google-play-release:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
with:
token: ${{ secrets.POST_RELEASE_GH_TOKEN }}
fetch-depth: 0
- name: Set up JDK
uses: actions/setup-java@3f07048e3d294f56e9b90ac5ea2c6f74e9ad0f98 # v3.10.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: Setup Gradle caching
uses: gradle/gradle-build-action@3fbe033aaae657f011f88f29be9e65ed26bd29ef # tag=v2.3.3
with:
gradle-home-cache-cleanup: true
cache-read-only: true
- name: Decrypt secrets
run: |
./scripts/setup-age.sh
./scripts/signing-setup.sh "$ENCRYPT_KEY"
env:
ENCRYPT_KEY: ${{ secrets.ENCRYPT_KEY }}
- name: Get current version
shell: bash
run: |
./gradlew -q --no-configuration-cache clearPreRelease
VERSION="$(tail -n1 android/version.properties | cut -d = -f 2)"
echo VERSION="${VERSION}" >> $GITHUB_ENV
- name: Update changelog
uses: thomaseizinger/keep-a-changelog-new-release@5bc232893483441c5d6cd6c9dcb1e48abf9a2bae # 1.3.0
with:
tag: v${{ env.VERSION }}
version: ${{ env.VERSION }}
- name: Commit changes
shell: bash
run: |
git commit -am 'feat(release): bump version'
- name: Build release assets
shell: bash
run: |
./gradlew --no-configuration-cache collectReleaseApks collectReleaseBundle
- name: Clean secrets
run: scripts/signing-cleanup.sh
- name: Get changelog entry
id: changelog_reader
uses: mindsers/changelog-reader-action@b97ce03a10d9bdbb07beb491c76a5a01d78cd3ef # v2.2.2
with:
version: ${{ env.VERSION }}
path: ./CHANGELOG.md
- name: Write release notes file
shell: bash
env:
CHANGELOG: ${{ steps.changelog_reader.outputs.changes }}
run: |
mkdir -p distribution/whatsnew
printf '## Changelog\n\n' > distribution/whatsnew/whatsnew-en-GB
printf '%s' "${CHANGELOG}" >> distribution/whatsnew/whatsnew-en-GB
- name: Publish bundle to Google Play
uses: r0adkll/upload-google-play@78c9e796b1035c94169c101d8e46cb880194bfc3 # tag=v1.0.19
with:
mappingFile: android/bundle/mapping.txt
packageName: dev.msfjarvis.claw.android
releaseFiles: android/bundle/*.aab
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
status: completed
track: production
whatsNewDirectory: distribution/whatsnew
- name: Post-release work
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.POST_RELEASE_GH_TOKEN }}
run: |
# Tag the current version
git tag "v${VERSION}" -F distribution/whatsnew/whatsnew-en-GB
# Push the tag to GitHub
git push origin "v${VERSION}"
# Create a GitHub release
gh release create "v${VERSION}" --notes-file distribution/whatsnew/whatsnew-en-GB --title "v${VERSION}" ./android/apk/*.apk ./android/bundle/*.aab
# Start the next development iteration
./gradlew -q --no-configuration-cache bumpSnapshot
truncate -s 0 distribution/whatsnew/whatsnew-en-GB # Clear changelog
git commit -am 'feat(release): start next development iteration'
git push -u origin
+3
View File
@@ -242,3 +242,6 @@ hs_err_pid*
!/gradle/wrapper/gradle-wrapper.jar
# End of https://www.toptal.com/developers/gitignore/api/androidstudio,gradle,intellij+all
keystore.properties
distribution/
+204
View File
@@ -0,0 +1,204 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [1.20.0] - 2023-02-09
## [1.19.0] - 2023-02-01
### Changed
- Upgrade dependencies
- Optimize packaged baseline profiles
## [1.18.0] - 2023-01-21
### Changed
- Special-case deleted stories in comments API
- Fix regression where save button had the incorrect visual state
## [1.17.0] - 2023-01-12
### Added
- The app will now mark new comments as unread when returning to posts
### Changed
- Upgrade to Kotlin 1.8.0
- Revert questionable app bar scrolling behaviour change
## [1.16.0] - 2022-12-24
### Changed
- Fix bug where newest posts tab actually showed hottest posts instead
## [1.15.0] - 2022-12-20
### Changed
- Fix a case where collapsing the parent of an already collapsed comment caused it to become expanded
- Tweak progress bar UI to use a linear indicator
- Allow app bar to hide when scrolling down
- Fix bug where refreshing a list of posts caused it to jump around multiple times
## [1.14.0] - 2022-12-12
### Changed
- Rework how often saved posts are updated
- Collapsing a comment now collapses all comments under it similar to how it works on the website
## [1.13.0] - 2022-12-10
### Changed
- Fix crash when viewing [jcs](https://lobste.rs/u/jcs)' comments
## [1.12.0] - 2022-12-09
### Changed
- Do not schedule post update job every time the app starts
- Improve favicon loading to reduce unnecessary redraws
- Fix post lists being reloaded unnecessarily
## [1.11.0] - 2022-12-07
### Changed
- Upgrade to Compose 2022.12.00 release
- Refresh packaged baseline profile
- Improve how saved state of posts is queried
## [1.10.0] - 2022-11-30
### Changed
- Configure Coil image loader with better caching settings
- Refresh packaged baseline profile
## [1.9.0] - 2022-11-28
### Changed
- A loading indicator is added to the bottom of the post list to identify when new posts are being fetched
## [1.8.0] - 2022-11-21
### Changed
- The release pipeline now automatically generates Play Store release notes from the changelog file
### Fixed
- Downgrade AGP to 8.0.0-alpha07 to fix Baseline Profiles not being packaged
## [1.7.0] - 2022-11-19
### Changed
- Switch to Compose Material's swipe refresh feature
- Change metadata extractor logic to no longer prefer canonical URLs. The submitted URL will be retained as-is now.
- Remove navigation transition animations
## [1.6.0] - 2022-11-14
### Changed
- Migrate dependency injection from Hilt to Anvil + Whetstone
## [1.5.0] - 2022-11-10
### Changed
- Upgrade to Compose 2022.11.00 release
## [1.4.0] - 2022-11-02
### Changed
- Misc fixes to link metadata extractor
## [1.3.0] - 2022-10-24
### Changed
- Prevent errors during link metadata retrieval from crashing the app
- Update Jetpack libraries to 2022-10-24 release
## [1.2.0] - 2022-10-17
### Changed
- Lazily load link metadata to improve comment page loading speed
- Upgrade to latest Compose release
- Refactor code to align with Twitter's public Compose guidelines
## [1.1.0] - 2022-09-30
### Added
- Add score and relative time to comments
### Changed
- Update Jetpack Compose
- Declare data backup and transfer rules
- Improve legibility of links in comments and user profiles
- Commonize and improve how network errors are displayed
## [1.0.0] - 2022-09-20
- Initial Play Store release
[Unreleased]: https://github.com/msfjarvis/compose-lobsters/compare/v1.20.0...HEAD
[1.20.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.19.0...v1.20.0
[1.19.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.18.0...v1.19.0
[1.18.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.17.0...v1.18.0
[1.17.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.16.0...v1.17.0
[1.16.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.15.0...v1.16.0
[1.15.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.14.0...v1.15.0
[1.14.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.13.0...v1.14.0
[1.13.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.12.0...v1.13.0
[1.12.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.11.0...v1.12.0
[1.11.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.10.0...v1.11.0
[1.10.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.9.0...v1.10.0
[1.9.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.8.0...v1.9.0
[1.8.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.7.0...1.8.0
[1.7.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.6.0...v1.7.0
[1.6.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.5.0...v1.6.0
[1.5.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.4.0...v1.5.0
[1.4.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.3.0...v1.4.0
[1.3.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.2.0...v1.3.0
[1.2.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/msfjarvis/compose-lobsters/compare/29c374859b17c5fcef03585b8a01c00070de9097...v1.0.0
+23
View File
@@ -0,0 +1,23 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+5 -31
View File
@@ -2,44 +2,18 @@
Unofficial Android app for read-only access to [lobste.rs](https://lobste.rs), built with [Jetpack Compose](https://developer.android.com/jetpack/compose).
![Screenshots](./.github/readme_feature.png)
## Download
<a href="https://play.google.com/store/apps/details?id=dev.msfjarvis.claw.android">
<img src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png"
alt="Get it on Google Play"
height="80" />
</a>
<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" />
Snapshots from the development branch can be obtained [here](https://github.com/msfjarvis/compose-lobsters/releases/tag/nightly).
## License
```
Copyright (c) 2021 Harsh Shandilya
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
```
See [LICENSE](LICENSE)
+2
View File
@@ -0,0 +1,2 @@
/apk/
/bundle/
+1
View File
@@ -0,0 +1 @@
-dontobfuscate
+24 -25
View File
@@ -1,18 +1,27 @@
/*
* 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.
*/
@file:Suppress("DSL_SCOPE_VIOLATION", "UnstableApiUsage")
import dagger.hilt.android.plugin.HiltExtension
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.versioning-plugin")
alias(libs.plugins.hilt)
alias(libs.plugins.napt)
alias(libs.plugins.anvil)
alias(libs.plugins.whetstone)
}
// Hilt's aggregating task fails with NAPT
extensions.getByType<HiltExtension>().enableAggregatingTask = false
whetstone {
addOns {
compose.set(true)
workManager.set(true)
}
}
android {
namespace = "dev.msfjarvis.claw.android"
@@ -24,47 +33,37 @@ android {
}
buildTypes {
create("benchmark") {
signingConfig = signingConfigs.getByName("debug")
matchingFallbacks += listOf("release")
signingConfig = signingConfigs["debug"]
matchingFallbacks += "release"
isDebuggable = false
proguardFile("benchmark-rules.pro")
}
}
lint {
abortOnError = false
checkReleaseBuilds = false
warningsAsErrors = false
disable.add("DialogFragmentCallbacksDetector")
baseline = file("lint-baseline.xml")
}
}
dependencies {
annotationProcessor(libs.androidx.hilt.compiler)
annotationProcessor(libs.dagger.hilt.compiler)
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(libs.accompanist.swiperefresh)
implementation(libs.accompanist.sysuicontroller)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.compose.material)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.core.splashscreen)
implementation(libs.androidx.hilt.work)
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.coil)
implementation(libs.copydown)
implementation(libs.dagger.hilt.android)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.serialization.json)
implementation(libs.material.motion.core)
implementation(libs.material.motion.navigation)
implementation(libs.okhttp.loggingInterceptor)
implementation(libs.retrofit.kotlinxSerializationConverter)
implementation(libs.sqldelight.extensions.coroutines)
}
+1 -1
View File
@@ -16,7 +16,7 @@
}
-keepclassmembers class * implements android.os.Parcelable {
public static final ** CREATOR;
public static final ** CREATOR;
}
-keep class androidx.annotation.Keep
-27
View File
@@ -1,29 +1,2 @@
# Keep `Companion` object fields of serializable classes.
# This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects.
-if @kotlinx.serialization.Serializable class **
-keepclassmembers class <1> {
static <1>$Companion Companion;
}
# Keep `serializer()` on companion objects (both default and named) of serializable classes.
-if @kotlinx.serialization.Serializable class ** {
static **$* *;
}
-keepclassmembers class <2>$<3> {
kotlinx.serialization.KSerializer serializer(...);
}
# Keep `INSTANCE.serializer()` of serializable objects.
-if @kotlinx.serialization.Serializable class ** {
public static ** INSTANCE;
}
-keepclassmembers class <1> {
public static <1> INSTANCE;
kotlinx.serialization.KSerializer serializer(...);
}
# @Serializable and @Polymorphic are used at runtime for polymorphic serialization.
-keepattributes RuntimeVisibleAnnotations,AnnotationDefault
-dontobfuscate
-keepattributes SourceFile, LineNumberTable
+15 -1
View File
@@ -12,7 +12,8 @@
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/Theme.Claw.SplashScreen"
tools:targetApi="s">
android:enableOnBackInvokedCallback="true"
tools:targetApi="tiramisu">
<profileable
android:shell="true"
tools:targetApi="29" />
@@ -148,6 +149,19 @@
android:name="androidx.work.WorkManagerInitializer"
android:value="androidx.startup"
tools:node="remove" />
<meta-data
android:name="com.deliveryhero.whetstone.worker.WhetstoneWorkerInitializer"
android:value="androidx.startup"
tools:node="remove" />
</provider>
<!-- https://issuetracker.google.com/issues/258619948 -->
<receiver
android:name="androidx.profileinstaller.ProfileInstallReceiver"
android:permission="android.permission.DUMP"
android:exported="true">
<intent-filter>
<action android:name="androidx.profileinstaller.action.BENCHMARK_OPERATION" />
</intent-filter>
</receiver>
</application>
</manifest>
+19135 -4848
View File
File diff suppressed because it is too large Load Diff
@@ -1,48 +1,57 @@
/*
* 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.android
import android.app.Application
import android.os.StrictMode
import android.util.Log
import androidx.hilt.work.HiltWorkerFactory
import androidx.work.Configuration
import coil.ImageLoader
import coil.ImageLoaderFactory
import coil.disk.DiskCache
import coil.memory.MemoryCache
import dagger.hilt.android.HiltAndroidApp
import io.github.aakira.napier.DebugAntilog
import io.github.aakira.napier.Napier
import androidx.work.Constraints
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.NetworkType
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import com.deliveryhero.whetstone.Whetstone
import com.deliveryhero.whetstone.app.ApplicationComponentOwner
import com.deliveryhero.whetstone.app.ContributesAppInjector
import dev.msfjarvis.claw.android.work.SavedPostUpdaterWorker
import dev.msfjarvis.claw.core.injection.AppPlugin
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@HiltAndroidApp
class ClawApplication : Application(), Configuration.Provider, ImageLoaderFactory {
@Inject lateinit var workerFactory: HiltWorkerFactory
@ContributesAppInjector(generateAppComponent = true)
class ClawApplication : Application(), ApplicationComponentOwner {
override val applicationComponent by
lazy(LazyThreadSafetyMode.NONE) { GeneratedApplicationComponent.create(this) }
@Inject lateinit var plugins: Set<@JvmSuppressWildcards AppPlugin>
override fun onCreate() {
Whetstone.inject(this)
super.onCreate()
StrictMode.setVmPolicy(StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build())
StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build())
Napier.base(DebugAntilog())
plugins.forEach { plugin -> plugin.apply(this) }
val postUpdateWorkRequest =
PeriodicWorkRequestBuilder<SavedPostUpdaterWorker>(POST_REFRESH_PERIOD, TimeUnit.HOURS)
.setConstraints(
Constraints.Builder()
.setRequiresCharging(false)
.setRequiresBatteryNotLow(true)
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresDeviceIdle(true)
.build()
)
.build()
WorkManager.getInstance(this)
.enqueueUniquePeriodicWork(
"updateSavedPosts",
ExistingPeriodicWorkPolicy.UPDATE,
postUpdateWorkRequest,
)
}
override fun getWorkManagerConfiguration(): Configuration {
return Configuration.Builder()
.setMinimumLoggingLevel(Log.DEBUG)
.setWorkerFactory(workerFactory)
.build()
}
override fun newImageLoader(): ImageLoader {
return ImageLoader.Builder(this)
.memoryCache { MemoryCache.Builder(this).maxSizePercent(0.25).build() }
.diskCache {
DiskCache.Builder()
.directory(cacheDir.resolve("image_cache"))
.maxSizeBytes(25L * 1024 * 1024) // 25 MB
.build()
}
// Show a short crossfade when loading images asynchronously.
.crossfade(true)
.build()
private companion object {
private const val POST_REFRESH_PERIOD = 24L // 24 hours
}
}
@@ -1,3 +1,9 @@
/*
* 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.android
import android.app.assist.AssistContent
@@ -6,20 +12,14 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.work.Constraints
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.NetworkType
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import dagger.hilt.android.AndroidEntryPoint
import com.deliveryhero.whetstone.Whetstone
import com.deliveryhero.whetstone.activity.ContributesActivityInjector
import dev.msfjarvis.claw.android.ui.LobstersApp
import dev.msfjarvis.claw.android.work.SavedPostUpdaterWorker
import dev.msfjarvis.claw.common.comments.HTMLConverter
import dev.msfjarvis.claw.common.urllauncher.UrlLauncher
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@AndroidEntryPoint
@ContributesActivityInjector
class MainActivity : ComponentActivity() {
@Inject lateinit var urlLauncher: UrlLauncher
@@ -29,26 +29,14 @@ class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
installSplashScreen()
Whetstone.inject(this)
setContent {
LobstersApp(urlLauncher = urlLauncher, htmlConverter = htmlConverter) { url -> webUri = url }
}
val postUpdateWorkRequest =
PeriodicWorkRequestBuilder<SavedPostUpdaterWorker>(24, TimeUnit.HOURS)
.setConstraints(
Constraints.Builder()
.setRequiresCharging(false)
.setRequiresBatteryNotLow(true)
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresDeviceIdle(true)
.build()
)
.build()
WorkManager.getInstance(this@MainActivity)
.enqueueUniquePeriodicWork(
"updateSavedPosts",
ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE,
postUpdateWorkRequest,
LobstersApp(
urlLauncher = urlLauncher,
htmlConverter = htmlConverter,
setWebUri = { url -> webUri = url },
)
}
}
override fun onProvideAssistContent(outContent: AssistContent?) {
@@ -1,42 +0,0 @@
package dev.msfjarvis.claw.android.injection
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dev.msfjarvis.claw.api.LobstersApi
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.create
@OptIn(ExperimentalSerializationApi::class)
@Module
@InstallIn(SingletonComponent::class)
object ApiModule {
@Provides
fun provideRetrofit(
client: OkHttpClient,
json: Json,
): Retrofit {
val contentType = "application/json".toMediaType()
return Retrofit.Builder()
.client(client)
.baseUrl(LobstersApi.BASE_URL)
.addConverterFactory(json.asConverterFactory(contentType))
.build()
}
@Provides
fun provideApi(retrofit: Retrofit): LobstersApi {
return retrofit.create()
}
@Provides
fun provideJsonSerializer(): Json {
return Json { ignoreUnknownKeys = true }
}
}
@@ -1,20 +1,25 @@
/*
* 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 com.deliveryhero.whetstone.app.ApplicationScope
import com.squareup.anvil.annotations.ContributesTo
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dev.msfjarvis.claw.util.coroutines.DefaultDispatcherProvider
import dev.msfjarvis.claw.util.coroutines.DispatcherProvider
import javax.inject.Qualifier
import kotlinx.coroutines.CoroutineDispatcher
@Module
@InstallIn(SingletonComponent::class)
abstract class CoroutineDispatcherModule {
@ContributesTo(ApplicationScope::class)
interface CoroutineDispatcherModule {
@Binds abstract fun DefaultDispatcherProvider.bind(): DispatcherProvider
@Binds fun DefaultDispatcherProvider.bind(): DispatcherProvider
companion object {
@[Provides IODispatcher]
@@ -33,9 +38,3 @@ abstract class CoroutineDispatcherModule {
}
}
}
@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class DatabaseDispatcher
@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class MainDispatcher
@Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class IODispatcher
@@ -1,20 +0,0 @@
package dev.msfjarvis.claw.android.injection
import android.content.Context
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import dev.msfjarvis.claw.database.LobstersDatabase
import dev.msfjarvis.claw.database.local.createDatabase
@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {
@Provides
fun provideDatabase(@ApplicationContext context: Context): LobstersDatabase {
return createDatabase(context)
}
}
@@ -1,23 +1,20 @@
/*
* 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.android.injection
import com.deliveryhero.whetstone.app.ApplicationScope
import com.squareup.anvil.annotations.ContributesTo
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dev.msfjarvis.claw.android.ui.util.HTMLConverterImpl
import dev.msfjarvis.claw.common.comments.HTMLConverter
import io.github.furstenheim.CopyDown
@Module
@InstallIn(SingletonComponent::class)
object HTMLConverterModule {
@Provides
fun provideHTMLConverter() =
object : HTMLConverter {
private val copydown = CopyDown()
override fun convertHTMLToMarkdown(html: String): String {
return copydown.convert(html)
}
}
@ContributesTo(ApplicationScope::class)
interface HTMLConverterModule {
@Binds fun HTMLConverterImpl.bind(): HTMLConverter
}
@@ -1,14 +1,20 @@
/*
* 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.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 dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import okhttp3.OkHttpClient
@Module
@InstallIn(SingletonComponent::class)
@ContributesTo(ApplicationScope::class)
object MetadataExtractorModule {
@Provides
fun provideCrux(
@@ -1,18 +1,24 @@
/*
* 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.android.injection
import android.content.Context
import com.deliveryhero.whetstone.ForScope
import com.deliveryhero.whetstone.activity.ActivityScope
import com.squareup.anvil.annotations.ContributesTo
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent
import dagger.hilt.android.qualifiers.ActivityContext
import dev.msfjarvis.claw.common.urllauncher.UrlLauncher
@Module
@InstallIn(ActivityComponent::class)
@ContributesTo(ActivityScope::class)
object UrlLauncherModule {
@Provides
fun provideUrlLauncher(@ActivityContext context: Context): UrlLauncher {
fun provideUrlLauncher(@ForScope(ActivityScope::class) context: Context): UrlLauncher {
return UrlLauncher(context)
}
}
@@ -0,0 +1,34 @@
/*
* 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 android.app.Application
import android.util.Log
import androidx.work.Configuration
import androidx.work.WorkManager
import androidx.work.WorkerFactory
import com.deliveryhero.whetstone.app.ApplicationScope
import com.squareup.anvil.annotations.ContributesMultibinding
import dev.msfjarvis.claw.core.injection.AppPlugin
import javax.inject.Inject
@ContributesMultibinding(ApplicationScope::class)
class WorkManagerPlugin
@Inject
constructor(
private val workerFactory: WorkerFactory,
) : AppPlugin {
override fun apply(application: Application) {
WorkManager.initialize(
application,
Configuration.Builder()
.setWorkerFactory(workerFactory)
.setMinimumLoggingLevel(Log.DEBUG)
.build()
)
}
}
@@ -0,0 +1,15 @@
/*
* 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,19 +0,0 @@
package dev.msfjarvis.claw.android.network
import dev.msfjarvis.claw.android.BuildConfig
import javax.inject.Inject
import okhttp3.Interceptor
import okhttp3.Response
/** An OkHttp [Interceptor] that adds a recognizable User-Agent header to all network requests. */
class UserAgentInterceptor @Inject constructor() : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
return chain.proceed(
chain
.request()
.newBuilder()
.header("User-Agent", "Claw-Android/${BuildConfig.VERSION_NAME}/msfjarvis")
.build()
)
}
}
@@ -1,12 +1,21 @@
/*
* 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.android.paging
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.slack.eithernet.ApiResult.Failure
import com.slack.eithernet.ApiResult.Success
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dev.msfjarvis.claw.android.injection.IODispatcher
import dev.msfjarvis.claw.model.LobstersPost
import java.io.IOException
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
@@ -18,26 +27,35 @@ constructor(
) : PagingSource<Int, LobstersPost>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, LobstersPost> {
return try {
val page = params.key ?: 1
val posts = withContext(ioDispatcher) { remoteFetcher.getItemsAtPage(page) }
LoadResult.Page(
data = posts,
prevKey = if (page == 1) null else page - 1,
nextKey = page.plus(1)
)
} catch (e: Exception) {
LoadResult.Error(e)
val page = params.key ?: STARTING_PAGE_INDEX
return when (val result = withContext(ioDispatcher) { remoteFetcher.getItemsAtPage(page) }) {
is Success ->
LoadResult.Page(
itemsBefore = (page - 1) * PAGE_SIZE,
data = result.value,
prevKey = if (page == STARTING_PAGE_INDEX) null else page - 1,
nextKey = page + 1,
)
is Failure.NetworkFailure -> LoadResult.Error(result.error)
is Failure.UnknownFailure -> LoadResult.Error(result.error)
is Failure.HttpFailure,
is Failure.ApiFailure -> LoadResult.Error(IOException("API returned an invalid response"))
}
}
override fun getRefreshKey(state: PagingState<Int, LobstersPost>): Int {
return state.pages.size + 1
override fun getRefreshKey(state: PagingState<Int, LobstersPost>): Int? {
return state.anchorPosition?.let { anchorPosition ->
(anchorPosition / PAGE_SIZE).coerceAtLeast(STARTING_PAGE_INDEX)
}
}
@AssistedFactory
interface Factory {
fun create(remoteFetcher: RemoteFetcher<LobstersPost>): LobstersPagingSource
}
companion object {
const val PAGE_SIZE = 25
const val STARTING_PAGE_INDEX = 1
}
}
@@ -1,6 +1,14 @@
/*
* 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.paging
import com.slack.eithernet.ApiResult
/** SAM interface to abstract over a remote API that fetches paginated content. */
fun interface RemoteFetcher<T> {
suspend fun getItemsAtPage(page: Int): List<T>
suspend fun getItemsAtPage(page: Int): ApiResult<List<T>, Unit>
}
@@ -1,6 +1,11 @@
/*
* 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
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.ExperimentalMaterial3Api
@@ -15,18 +20,24 @@ import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
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
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.compose.ui.text.font.FontWeight
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import androidx.navigation.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.decorations.ClawNavigationBar
@@ -38,7 +49,6 @@ 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.comments.LocalHTMLConverter
import dev.msfjarvis.claw.common.res.ClawIcons
import dev.msfjarvis.claw.common.theme.LobstersTheme
import dev.msfjarvis.claw.common.ui.decorations.ClawAppBar
@@ -46,23 +56,24 @@ import dev.msfjarvis.claw.common.ui.surfaceColorAtNavigationBarElevation
import dev.msfjarvis.claw.common.urllauncher.UrlLauncher
import dev.msfjarvis.claw.common.user.UserProfile
import kotlinx.coroutines.launch
import soup.compose.material.motion.navigation.MaterialMotionNavHost
import soup.compose.material.motion.navigation.composable
import soup.compose.material.motion.navigation.rememberMaterialMotionNavController
@OptIn(ExperimentalMaterial3Api::class, ExperimentalAnimationApi::class)
@OptIn(
ExperimentalComposeUiApi::class,
ExperimentalMaterial3Api::class,
)
@Composable
fun LobstersApp(
viewModel: ClawViewModel = viewModel(),
urlLauncher: UrlLauncher,
htmlConverter: HTMLConverter,
setWebUri: (String?) -> Unit,
modifier: Modifier = Modifier,
viewModel: ClawViewModel = injectedViewModel(),
) {
val systemUiController = rememberSystemUiController()
val hottestListState = rememberLazyListState()
val newestListState = rememberLazyListState()
val savedListState = rememberLazyListState()
val navController = rememberMaterialMotionNavController()
val navController = rememberNavController()
val coroutineScope = rememberCoroutineScope()
val postActions = rememberPostActions(urlLauncher, navController, viewModel)
val backStackEntry by navController.currentBackStackEntryAsState()
@@ -75,11 +86,7 @@ fun LobstersApp(
LobstersTheme(
dynamicColor = true,
providedValues =
arrayOf(
LocalUriHandler provides urlLauncher,
LocalHTMLConverter provides htmlConverter,
),
providedValues = arrayOf(LocalUriHandler provides urlLauncher),
) {
val currentUiMode = LocalConfiguration.current.uiMode
val systemBarsColor = MaterialTheme.colorScheme.surfaceColorAtNavigationBarElevation()
@@ -88,7 +95,7 @@ fun LobstersApp(
listOf(
NavigationItem(
label = "Hottest",
route = Destinations.Hottest.getRoute(),
route = Destinations.Hottest.route,
icon = ClawIcons.Flame,
selectedIcon = ClawIcons.FlameFilled,
) {
@@ -96,7 +103,7 @@ fun LobstersApp(
},
NavigationItem(
label = "Newest",
route = Destinations.Newest.getRoute(),
route = Destinations.Newest.route,
icon = ClawIcons.New,
selectedIcon = ClawIcons.NewFilled,
) {
@@ -104,7 +111,7 @@ fun LobstersApp(
},
NavigationItem(
label = "Saved",
route = Destinations.Saved.getRoute(),
route = Destinations.Saved.route,
icon = ClawIcons.HeartBorder,
selectedIcon = ClawIcons.Heart,
) {
@@ -155,40 +162,39 @@ fun LobstersApp(
isVisible = navItems.any { it.route == currentDestination },
)
},
modifier = modifier.semantics { testTagsAsResourceId = true },
) { paddingValues ->
MaterialMotionNavHost(
NavHost(
navController = navController,
startDestination = Destinations.startDestination.getRoute(),
startDestination = Destinations.startDestination.route,
modifier = Modifier.padding(paddingValues),
) {
val uri = LobstersApi.BASE_URL
composable(
route = Destinations.Hottest.getRoute(),
route = Destinations.Hottest.route,
deepLinks =
listOf(navDeepLink { uriPattern = uri }, navDeepLink { uriPattern = "$uri/" }),
) {
setWebUri("https://lobste.rs/")
NetworkPosts(
items = hottestPosts,
lazyPagingItems = hottestPosts,
listState = hottestListState,
isPostSaved = viewModel::isPostSaved,
reloadPosts = viewModel::refreshHottestPosts,
postActions = postActions,
)
}
composable(
route = Destinations.Newest.getRoute(),
route = Destinations.Newest.route,
) {
setWebUri("https://lobste.rs/")
NetworkPosts(
items = newestPosts,
lazyPagingItems = newestPosts,
listState = newestListState,
isPostSaved = viewModel::isPostSaved,
reloadPosts = viewModel::refreshNewestPosts,
postActions = postActions,
)
}
composable(Destinations.Saved.getRoute()) {
composable(Destinations.Saved.route) {
setWebUri(null)
DatabasePosts(
items = savedPosts,
@@ -197,26 +203,29 @@ fun LobstersApp(
)
}
composable(
route = Destinations.Comments.getRoute("{postId}"),
route = Destinations.Comments.route,
arguments = listOf(navArgument("postId") { type = NavType.StringType }),
deepLinks =
listOf(
navDeepLink { uriPattern = "$uri/s/{postId}/.*" },
navDeepLink { uriPattern = "$uri/s/{postId}" },
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,
getDetails = viewModel::getPostComments,
postActions = postActions,
htmlConverter = htmlConverter,
getSeenComments = viewModel::getSeenComments,
markSeenComments = viewModel::markSeenComments,
)
}
composable(
route = Destinations.User.getRoute("{username}"),
route = Destinations.User.route,
arguments = listOf(navArgument("username") { type = NavType.StringType }),
deepLinks = listOf(navDeepLink { uriPattern = "$uri/u/{username}" }),
deepLinks =
listOf(navDeepLink { uriPattern = "$uri/u/${Destinations.User.placeholder}" }),
) { backStackEntry ->
val username = requireNotNull(backStackEntry.arguments?.getString("username"))
UserProfile(
@@ -1,3 +1,9 @@
/*
* 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.ui.decorations
import androidx.compose.animation.AnimatedVisibility
@@ -14,6 +20,7 @@ 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.platform.testTag
import androidx.navigation.NavController
import dev.msfjarvis.claw.android.ui.navigation.Destinations
@@ -61,11 +68,14 @@ fun ClawNavigationBar(
navItem.listStateResetCallback()
return@NavigationBarItem
}
navController.popBackStack(navController.graph.startDestinationRoute!!, false)
if (navItem.route != Destinations.startDestination.getRoute()) {
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()),
)
}
}
@@ -1,3 +1,9 @@
/*
* 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.ui
import android.content.Context
@@ -11,6 +17,8 @@ import dev.msfjarvis.claw.android.viewmodel.ClawViewModel
import dev.msfjarvis.claw.common.posts.PostActions
import dev.msfjarvis.claw.common.urllauncher.UrlLauncher
import dev.msfjarvis.claw.database.local.SavedPost
import dev.msfjarvis.claw.model.LinkMetadata
import dev.msfjarvis.claw.model.LobstersPostDetails
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
@@ -43,7 +51,9 @@ fun rememberPostActions(
}
override fun viewComments(postId: String) {
navController.navigate(Destinations.Comments.getRoute(postId))
navController.navigate(
Destinations.Comments.route.replace(Destinations.Comments.placeholder, postId)
)
}
override fun viewCommentsPage(commentsUrl: String) {
@@ -59,6 +69,14 @@ fun rememberPostActions(
override fun toggleSave(post: SavedPost) {
viewModel.toggleSave(post)
}
override suspend fun getComments(postId: String): LobstersPostDetails {
return viewModel.getPostComments(postId)
}
override suspend fun getLinkMetadata(url: String): LinkMetadata {
return viewModel.getLinkMetadata(url)
}
}
}
}
@@ -1,3 +1,9 @@
/*
* 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.android.ui.lists
import androidx.compose.foundation.ExperimentalFoundationApi
@@ -26,7 +32,7 @@ fun DatabasePosts(
) {
items.forEach { (month, posts) ->
stickyHeader { MonthHeader(month = month) }
items(posts) { item ->
items(posts, { it.shortId }) { item ->
ListItem(
item = item,
isSaved = { true },
@@ -1,3 +1,9 @@
/*
* 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.android.ui.lists
import androidx.compose.runtime.Composable
@@ -1,67 +1,63 @@
/*
* 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.android.ui.lists
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.Divider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.paging.LoadState
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.items
import com.google.accompanist.swiperefresh.SwipeRefresh
import com.google.accompanist.swiperefresh.SwipeRefreshIndicator
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
import dev.msfjarvis.claw.common.posts.PostActions
import dev.msfjarvis.claw.common.posts.toDbModel
import dev.msfjarvis.claw.common.ui.NetworkError
import dev.msfjarvis.claw.common.ui.ProgressBar
import dev.msfjarvis.claw.database.local.SavedPost
import dev.msfjarvis.claw.model.LobstersPost
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun NetworkPosts(
items: LazyPagingItems<LobstersPost>,
lazyPagingItems: LazyPagingItems<LobstersPost>,
listState: LazyListState,
isPostSaved: suspend (SavedPost) -> Boolean,
reloadPosts: () -> Unit,
postActions: PostActions,
modifier: Modifier = Modifier,
) {
val loadState = items.loadState.refresh
val isRefreshing = loadState == LoadState.Loading
SwipeRefresh(
state = rememberSwipeRefreshState(isRefreshing),
onRefresh = reloadPosts,
indicator = { state, trigger ->
val backgroundColor = MaterialTheme.colorScheme.surface
SwipeRefreshIndicator(
state = state,
refreshTriggerDistance = trigger,
backgroundColor = backgroundColor,
contentColor = contentColorFor(backgroundColor)
val refreshLoadState = lazyPagingItems.loadState.refresh
val isRefreshing = refreshLoadState == LoadState.Loading
val pullRefreshState = rememberPullRefreshState(isRefreshing, lazyPagingItems::refresh)
Box(modifier = modifier.fillMaxSize().pullRefresh(pullRefreshState)) {
if (lazyPagingItems.itemCount == 0 && refreshLoadState is LoadState.Error) {
NetworkError(
label = "Failed to load posts",
error = refreshLoadState.error,
modifier = Modifier.align(Alignment.Center),
)
}
) {
if (items.itemCount == 0) {
Box(modifier = Modifier.fillMaxSize()) {
if (loadState is LoadState.Error) {
NetworkError(
label = "Failed to load posts",
error = loadState.error,
modifier = Modifier.align(Alignment.Center),
)
}
}
} else {
LazyColumn(
state = listState,
modifier = modifier,
) {
items(items) { item ->
items(items = lazyPagingItems, key = { it.shortId }) { item ->
if (item != null) {
val dbModel = item.toDbModel()
ListItem(
@@ -73,7 +69,24 @@ fun NetworkPosts(
Divider()
}
}
if (lazyPagingItems.loadState.append == LoadState.Loading) {
item(key = "progressbar") {
ProgressBar(
modifier =
Modifier.fillMaxWidth()
.wrapContentWidth(Alignment.CenterHorizontally)
.padding(vertical = 16.dp),
)
}
}
}
}
PullRefreshIndicator(
refreshing = isRefreshing,
state = pullRefreshState,
modifier = Modifier.align(Alignment.TopCenter),
backgroundColor = MaterialTheme.colorScheme.surface,
contentColor = contentColorFor(MaterialTheme.colorScheme.surface),
)
}
}
@@ -1,24 +1,34 @@
/*
* 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.android.ui.navigation
sealed class Destinations {
abstract val route: String
object Hottest : Destinations() {
fun getRoute() = "hottest"
override val route = "hottest"
}
object Newest : Destinations() {
fun getRoute() = "newest"
override val route = "newest"
}
object Saved : Destinations() {
fun getRoute() = "saved"
override val route = "saved"
}
object Comments : Destinations() {
fun getRoute(postId: String) = "comments/$postId"
const val placeholder = "{postId}"
override val route = "comments/$placeholder"
}
object User : Destinations() {
fun getRoute(username: String) = "user/$username"
const val placeholder = "{username}"
override val route = "user/$placeholder"
}
companion object {
@@ -0,0 +1,21 @@
/*
* 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.ui.util
import androidx.compose.runtime.Stable
import dev.msfjarvis.claw.common.comments.HTMLConverter
import io.github.furstenheim.CopyDown
import javax.inject.Inject
@Stable
class HTMLConverterImpl @Inject constructor() : HTMLConverter {
private val copydown = CopyDown()
override fun convertHTMLToMarkdown(html: String): String {
return copydown.convert(html)
}
}
@@ -1,15 +1,28 @@
/*
* 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 androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import dagger.hilt.android.lifecycle.HiltViewModel
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.api.LobstersApi
import dev.msfjarvis.claw.database.local.SavedPost
import dev.msfjarvis.claw.model.Comment
import java.io.IOException
import java.net.HttpURLConnection
import java.time.Month
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -18,25 +31,23 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@HiltViewModel
@ContributesViewModel
class ClawViewModel
@Inject
constructor(
private val api: LobstersApi,
private val savedPostsRepository: SavedPostsRepository,
private val postDetailsRepository: PostDetailsRepository,
private val linkMetadataRepository: LinkMetadataRepository,
private val pagingSourceFactory: LobstersPagingSource.Factory,
@IODispatcher private val ioDispatcher: CoroutineDispatcher,
) : ViewModel() {
private var hottestPostsPagingSource: LobstersPagingSource? = null
private var newestPostsPagingSource: LobstersPagingSource? = null
private val hottestPostsPager =
Pager(PagingConfig(20)) {
pagingSourceFactory.create(api::getHottestPosts).also { hottestPostsPagingSource = it }
Pager(PagingConfig(pageSize = PAGE_SIZE), initialKey = STARTING_PAGE_INDEX) {
pagingSourceFactory.create(api::getHottestPosts)
}
private val newestPostsPager =
Pager(PagingConfig(20)) {
pagingSourceFactory.create(api::getHottestPosts).also { newestPostsPagingSource = it }
Pager(PagingConfig(pageSize = PAGE_SIZE), initialKey = STARTING_PAGE_INDEX) {
pagingSourceFactory.create(api::getNewestPosts)
}
val hottestPosts
@@ -73,18 +84,38 @@ constructor(
suspend fun getPostComments(postId: String) =
withContext(ioDispatcher) {
val details = api.getPostDetails(postId)
val extendedDetails = postDetailsRepository.getExtendedDetails(details)
extendedDetails
when (val result = api.getPostDetails(postId)) {
is Success -> result.value
is Failure.NetworkFailure -> throw result.error
is Failure.UnknownFailure -> throw result.error
is Failure.HttpFailure -> {
if (result.code == HttpURLConnection.HTTP_NOT_FOUND) {
throw IOException("Story was removed by moderator")
} else {
throw IOException("API returned an invalid response")
}
}
is Failure.ApiFailure -> throw IOException("API returned an invalid response")
}
}
suspend fun getUserProfile(username: String) = withContext(ioDispatcher) { api.getUser(username) }
suspend fun getSeenComments(postId: String) = savedPostsRepository.getSeenComments(postId)
fun refreshHottestPosts() {
hottestPostsPagingSource?.invalidate()
fun markSeenComments(postId: String, comments: List<Comment>) {
viewModelScope.launch { savedPostsRepository.markSeenComments(postId, comments) }
}
fun refreshNewestPosts() {
newestPostsPagingSource?.invalidate()
}
suspend fun getLinkMetadata(url: String) =
withContext(ioDispatcher) { linkMetadataRepository.getLinkMetadata(url) }
suspend fun getUserProfile(username: String) =
withContext(ioDispatcher) {
when (val result = api.getUser(username)) {
is Success -> result.value
is Failure.NetworkFailure -> throw result.error
is Failure.UnknownFailure -> throw result.error
is Failure.HttpFailure,
is Failure.ApiFailure -> throw IOException("API returned an invalid response")
}
}
}
@@ -0,0 +1,21 @@
/*
* 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.viewmodel
import dev.msfjarvis.claw.metadata.MetadataExtractor
import dev.msfjarvis.claw.model.LinkMetadata
import javax.inject.Inject
class LinkMetadataRepository
@Inject
constructor(
private val metadataExtractor: MetadataExtractor,
) {
suspend fun getLinkMetadata(url: String): LinkMetadata {
return metadataExtractor.getExtractedMetadata(url)
}
}
@@ -1,25 +0,0 @@
package dev.msfjarvis.claw.android.viewmodel
import dev.msfjarvis.claw.metadata.MetadataExtractor
import dev.msfjarvis.claw.model.ExtendedPostDetails
import dev.msfjarvis.claw.model.LobstersPostDetails
import javax.inject.Inject
class PostDetailsRepository
@Inject
constructor(
private val metadataExtractor: MetadataExtractor,
) {
suspend fun getExtendedDetails(details: LobstersPostDetails): ExtendedPostDetails {
val metadata = metadataExtractor.getExtractedMetadata(details.url)
return ExtendedPostDetails(
title = details.title,
linkMetadata = metadata,
description = details.description,
submitter = details.submitter,
tags = details.tags,
comments = details.comments,
commentsUrl = details.commentsUrl,
)
}
}
@@ -1,10 +1,18 @@
/*
* 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 app.cash.sqldelight.coroutines.asFlow
import app.cash.sqldelight.coroutines.mapToList
import dev.msfjarvis.claw.android.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
@@ -17,6 +25,7 @@ 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) {
@@ -24,11 +33,29 @@ constructor(
withContext(dbDispatcher) { savedPostQueries.insertOrReplacePost(post) }
}
suspend fun savePosts(posts: List<SavedPost>) {
Napier.d(tag = TAG) { "Saving posts: ${posts.joinToString(",") { it.shortId }}" }
withContext(dbDispatcher) {
savedPostQueries.transaction {
posts.forEach { post -> savedPostQueries.insertOrReplacePost(post) }
}
}
}
suspend fun removePost(post: SavedPost) {
Napier.d(tag = TAG) { "Removing post: ${post.shortId}" }
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,19 +1,23 @@
/*
* 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.work
import android.content.Context
import androidx.hilt.work.HiltWorker
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import com.deliveryhero.whetstone.ForScope
import com.deliveryhero.whetstone.worker.ContributesWorker
import com.deliveryhero.whetstone.worker.WorkerScope
import com.slack.eithernet.ApiResult.Success
import dev.msfjarvis.claw.android.viewmodel.SavedPostsRepository
import dev.msfjarvis.claw.api.LobstersApi
import dev.msfjarvis.claw.common.posts.toDbModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import dev.msfjarvis.claw.model.LobstersPostDetails
import javax.inject.Inject
import kotlinx.coroutines.flow.first
/**
@@ -22,29 +26,23 @@ import kotlinx.coroutines.flow.first
* saved posts that were saved before comment counts were added to be able to show a comment count
* and for new-enough posts that are still getting comments to have an accurate one.
*/
@Suppress("DEPRECATION") // We're being nasty
@OptIn(ExperimentalCoroutinesApi::class)
@HiltWorker
@ContributesWorker
class SavedPostUpdaterWorker
@AssistedInject
@Inject
constructor(
@ForScope(WorkerScope::class) appContext: Context,
workerParams: WorkerParameters,
private val savedPostsRepository: SavedPostsRepository,
private val lobstersApi: LobstersApi,
@Assisted appContext: Context,
@Assisted workerParams: WorkerParameters,
) : CoroutineWorker(appContext, workerParams) {
override suspend fun doWork(): Result {
val posts = savedPostsRepository.savedPosts.first()
posts
.map { post ->
CoroutineScope(coroutineContext + Job()).async {
val details = runCatching { lobstersApi.getPostDetails(post.shortId) }.getOrNull()
if (details != null) {
savedPostsRepository.savePost(details.toDbModel())
}
}
}
.awaitAll()
savedPostsRepository.savedPosts
.first()
.map { post -> lobstersApi.getPostDetails(post.shortId) }
.filterIsInstance<Success<LobstersPostDetails>>()
.map { result -> result.value.toDbModel() }
.toList()
.let { savedPostsRepository.savePosts(it) }
return Result.success()
}
}
+2 -2
View File
@@ -1,5 +1,5 @@
#
# This file was automatically generated by 'versioning-plugin'. DO NOT EDIT MANUALLY.
#
versioning-plugin.versionCode=10100
versioning-plugin.versionName=1.1.0
versioning-plugin.versionCode=12000
versioning-plugin.versionName=1.20.0
+21 -7
View File
@@ -1,17 +1,31 @@
/*
* 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.
*/
@file:Suppress("DSL_SCOPE_VIOLATION", "UnstableApiUsage")
plugins {
kotlin("jvm")
id("dev.msfjarvis.claw.kotlin-library")
id("dev.msfjarvis.claw.kotlin-android")
id("dev.msfjarvis.claw.android-library")
alias(libs.plugins.anvil)
alias(libs.plugins.whetstone)
}
android { namespace = "dev.msfjarvis.claw.api" }
anvil { generateDaggerFactories.set(true) }
dependencies {
api(projects.model)
api(libs.retrofit.lib)
implementation(libs.kotlinx.serialization.core)
testImplementation(libs.kotlinx.coroutines.core)
testImplementation(kotlin("test-junit"))
api(libs.retrofit)
api(libs.eithernet)
implementation(libs.dagger)
implementation(libs.javax.inject)
testImplementation(testFixtures(libs.eithernet))
testImplementation(libs.kotest.assertions.core)
testImplementation(libs.kotest.runner.junit5)
testImplementation(libs.kotlinx.serialization.json)
testImplementation(libs.retrofit.kotlinxSerializationConverter)
testImplementation(libs.retrofit.mock)
}
+4
View File
@@ -0,0 +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>
@@ -1,5 +1,12 @@
/*
* 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
import com.slack.eithernet.ApiResult
import dev.msfjarvis.claw.model.LobstersPost
import dev.msfjarvis.claw.model.LobstersPostDetails
import dev.msfjarvis.claw.model.User
@@ -10,14 +17,17 @@ import retrofit2.http.Query
/** Simple interface defining an API for lobste.rs */
interface LobstersApi {
@GET("hottest.json") suspend fun getHottestPosts(@Query("page") page: Int): List<LobstersPost>
@GET("hottest.json")
suspend fun getHottestPosts(@Query("page") page: Int): ApiResult<List<LobstersPost>, Unit>
@GET("newest.json") suspend fun getNewestPosts(@Query("page") page: Int): List<LobstersPost>
@GET("newest.json")
suspend fun getNewestPosts(@Query("page") page: Int): ApiResult<List<LobstersPost>, Unit>
@GET("s/{postId}.json")
suspend fun getPostDetails(@Path("postId") postId: String): LobstersPostDetails
suspend fun getPostDetails(@Path("postId") postId: String): ApiResult<LobstersPostDetails, Unit>
@GET("u/{username}.json") suspend fun getUser(@Path("username") username: String): User
@GET("u/{username}.json")
suspend fun getUser(@Path("username") username: String): ApiResult<User, Unit>
companion object {
const val BASE_URL = "https://lobste.rs"
@@ -0,0 +1,42 @@
/*
* 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()
}
}
@@ -1,52 +1,50 @@
/*
* 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.api
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlinx.coroutines.runBlocking
import retrofit2.Retrofit
import retrofit2.mock.MockRetrofit
import retrofit2.mock.NetworkBehavior
import retrofit2.mock.create
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.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
class ApiTest {
private val retrofit = Retrofit.Builder().baseUrl(LobstersApi.BASE_URL).build()
private val networkBehaviour = createNetworkBehaviour()
private val api =
MockRetrofit.Builder(retrofit)
.networkBehavior(networkBehaviour)
.build()
.create<LobstersApi>()
.let(::FakeApi)
class ApiTest : FunSpec() {
private val wrapper = ApiWrapper(newEitherNetController())
private val api
get() = wrapper.api
private fun createNetworkBehaviour(): NetworkBehavior {
val behaviour = NetworkBehavior.create()
behaviour.setFailurePercent(0)
behaviour.setErrorPercent(0)
return behaviour
}
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`() = runBlocking {
val posts = api.getHottestPosts(1)
assertEquals(25, posts.size)
}
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`() = runBlocking {
val posts = api.getHottestPosts(1)
val commentsOnlyPosts = posts.asSequence().filter { it.url.isEmpty() }.toSet()
assertEquals(2, commentsOnlyPosts.size)
}
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`() = runBlocking {
val postDetails = api.getPostDetails("tdfoqh")
assertEquals(7, postDetails.comments.size)
}
@Test
fun `get user details`() = runBlocking {
val user = api.getUser("msfjarvis")
assertEquals("msfjarvis", user.username)
test("get user details") {
val user = api.getUser("msfjarvis")
user.shouldBeTypeOf<Success<User>>()
user.value.username shouldBe "msfjarvis"
}
}
}
@@ -0,0 +1,40 @@
/*
* 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.api
import com.slack.eithernet.ApiResult.Companion.success
import com.slack.eithernet.test.EitherNetController
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 kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonNamingStrategy
@OptIn(ExperimentalSerializationApi::class)
class ApiWrapper(controller: EitherNetController<LobstersApi>) {
private val json = Json {
ignoreUnknownKeys = true
namingStrategy = JsonNamingStrategy.SnakeCase
}
private val hottest: List<LobstersPost> = json.decodeFromString(getJson("hottest.json"))
private val postDetails: LobstersPostDetails =
json.decodeFromString(getJson("post_details_tdfoqh.json"))
private val user: User = json.decodeFromString(getJson("msfjarvis.json"))
val api = controller.api
init {
controller.enqueue(LobstersApi::getHottestPosts) { success(hottest) }
controller.enqueue(LobstersApi::getHottestPosts) { success(hottest) }
controller.enqueue(LobstersApi::getPostDetails) { success(postDetails) }
controller.enqueue(LobstersApi::getUser) { success(user) }
}
}
@@ -1,33 +0,0 @@
package dev.msfjarvis.claw.api
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 kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import retrofit2.mock.BehaviorDelegate
class FakeApi(private val delegate: BehaviorDelegate<LobstersApi>) : LobstersApi {
private val json = Json { ignoreUnknownKeys = true }
private val hottest: List<LobstersPost> = json.decodeFromString(getJson("hottest.json"))
private val postDetails: LobstersPostDetails =
json.decodeFromString(getJson("post_details_tdfoqh.json"))
private val user: User = json.decodeFromString(getJson("msfjarvis.json"))
override suspend fun getHottestPosts(page: Int): List<LobstersPost> {
return delegate.returningResponse(hottest).getHottestPosts(page)
}
override suspend fun getNewestPosts(page: Int): List<LobstersPost> {
TODO("Not yet implemented")
}
override suspend fun getPostDetails(postId: String): LobstersPostDetails {
return delegate.returningResponse(postDetails).getPostDetails(postId)
}
override suspend fun getUser(username: String): User {
return delegate.returningResponse(user).getUser(username)
}
}
@@ -1,3 +1,9 @@
/*
* 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.util
import java.io.File
+8 -2
View File
@@ -1,3 +1,9 @@
/*
* 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.
*/
@file:Suppress("DSL_SCOPE_VIOLATION", "UnstableApiUsage")
plugins {
@@ -14,8 +20,8 @@ android {
buildTypes {
create("benchmark") {
isDebuggable = true
signingConfig = getByName("debug").signingConfig
matchingFallbacks += listOf("release")
signingConfig = signingConfigs["debug"]
matchingFallbacks += "release"
}
}
@@ -1,3 +1,9 @@
/*
* 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.benchmark
import androidx.benchmark.macro.BaselineProfileMode
@@ -5,14 +11,12 @@ import androidx.benchmark.macro.CompilationMode
import androidx.benchmark.macro.StartupMode
import androidx.benchmark.macro.StartupTimingMetric
import androidx.benchmark.macro.junit4.MacrobenchmarkRule
import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@LargeTest
class BaselineProfileBenchmark {
@get:Rule val benchmarkRule = MacrobenchmarkRule()
private lateinit var device: UiDevice
@@ -46,7 +50,7 @@ class BaselineProfileBenchmark {
startupMode = StartupMode.COLD,
compilationMode = compilationMode
) {
exploreUI(device)
startActivityAndWait()
}
}
}
@@ -1,16 +1,20 @@
/*
* 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.benchmark
import androidx.benchmark.macro.ExperimentalBaselineProfilesApi
import androidx.benchmark.macro.ExperimentalStableBaselineProfilesApi
import androidx.benchmark.macro.junit4.BaselineProfileRule
import androidx.test.filters.MediumTest
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@ExperimentalBaselineProfilesApi
@MediumTest
@OptIn(ExperimentalStableBaselineProfilesApi::class)
class BaselineProfileGenerator {
@get:Rule val baselineProfileRule = BaselineProfileRule()
private lateinit var device: UiDevice
@@ -23,5 +27,10 @@ class BaselineProfileGenerator {
@Test
fun startup() =
baselineProfileRule.collectBaselineProfile(packageName = PACKAGE_NAME) { exploreUI(device) }
baselineProfileRule.collectStableBaselineProfile(
packageName = PACKAGE_NAME,
maxIterations = 15,
) {
startActivityAndWait()
}
}
@@ -1,21 +1,60 @@
/*
* 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.benchmark
import androidx.benchmark.macro.MacrobenchmarkScope
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
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 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 {
listOf("HOTTEST", "NEWEST", "SAVED").forEach { desc ->
findObject(By.desc(desc)).click()
waitForIdle()
}
findObject(By.desc("HOTTEST")).click()
waitForIdle()
findObjects(By.desc("Open comments")).first().click()
waitForIdle()
savePosts()
exploreScreens()
returnToHottestScreen()
openCommentsScreen()
}
}
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() {
listOf(NAV_ID_HOTTEST, NAV_ID_NEWEST, NAV_ID_SAVED).forEach { tag ->
findObject(By.res(tag)).click()
waitForSubmitterName()
}
}
private fun UiDevice.returnToHottestScreen() {
findObject(By.res(NAV_ID_HOTTEST)).click()
waitForSubmitterName()
}
private fun UiDevice.openCommentsScreen() {
waitForSubmitterName()
findObjects(By.desc(COMMENT_BUTTON_DESC)).first().click()
}
-1
View File
@@ -1 +0,0 @@
/build
@@ -1,56 +0,0 @@
/*
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only
*/
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"
}
}
}
gradlePlugin {
plugins {
register("android-application") {
id = "dev.msfjarvis.claw.android-application"
implementationClass = "dev.msfjarvis.aps.gradle.ApplicationPlugin"
}
register("android-common") {
id = "dev.msfjarvis.claw.android-common"
implementationClass = "dev.msfjarvis.aps.gradle.AndroidCommonPlugin"
}
register("android-library") {
id = "dev.msfjarvis.claw.android-library"
implementationClass = "dev.msfjarvis.aps.gradle.LibraryPlugin"
}
register("rename-artifacts") {
id = "dev.msfjarvis.claw.rename-artifacts"
implementationClass = "dev.msfjarvis.aps.gradle.RenameArtifactsPlugin"
}
register("versioning") {
id = "dev.msfjarvis.claw.versioning-plugin"
implementationClass = "dev.msfjarvis.aps.gradle.versioning.VersioningPlugin"
}
}
}
dependencies {
implementation(libs.build.agp)
implementation(libs.build.cachefix)
implementation(libs.build.semver)
}
+86
View File
@@ -0,0 +1,86 @@
/*
* 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.
*/
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"
}
}
}
gradlePlugin {
plugins {
register("android-application") {
id = "dev.msfjarvis.claw.android-application"
implementationClass = "dev.msfjarvis.claw.gradle.ApplicationPlugin"
}
register("android-common") {
id = "dev.msfjarvis.claw.android-common"
implementationClass = "dev.msfjarvis.claw.gradle.AndroidCommonPlugin"
}
register("android-library") {
id = "dev.msfjarvis.claw.android-library"
implementationClass = "dev.msfjarvis.claw.gradle.LibraryPlugin"
}
register("kotlin-android") {
id = "dev.msfjarvis.claw.kotlin-android"
implementationClass = "dev.msfjarvis.claw.gradle.KotlinAndroidPlugin"
}
register("kotlin-common") {
id = "dev.msfjarvis.claw.kotlin-common"
implementationClass = "dev.msfjarvis.claw.gradle.KotlinCommonPlugin"
}
register("kotlin-jvm") {
id = "dev.msfjarvis.claw.kotlin-jvm"
implementationClass = "dev.msfjarvis.claw.gradle.KotlinJvmPlugin"
}
register("kotlin-kapt") {
id = "dev.msfjarvis.claw.kotlin-kapt"
implementationClass = "dev.msfjarvis.claw.gradle.KotlinKaptPlugin"
}
register("rename-artifacts") {
id = "dev.msfjarvis.claw.rename-artifacts"
implementationClass = "dev.msfjarvis.claw.gradle.RenameArtifactsPlugin"
}
register("spotless") {
id = "dev.msfjarvis.claw.spotless"
implementationClass = "dev.msfjarvis.claw.gradle.SpotlessPlugin"
}
register("versioning") {
id = "dev.msfjarvis.claw.versioning-plugin"
implementationClass = "dev.msfjarvis.claw.gradle.versioning.VersioningPlugin"
}
register("versions") {
id = "dev.msfjarvis.claw.versions"
implementationClass = "dev.msfjarvis.claw.gradle.DependencyUpdatesPlugin"
}
}
}
dependencies {
implementation(libs.build.agp)
implementation(libs.build.cachefix)
implementation(libs.build.detekt)
implementation(libs.build.kotlin.gradle)
implementation(libs.build.semver)
implementation(libs.build.spotless)
implementation(libs.build.vcu)
implementation(libs.build.versions)
}
@@ -1,59 +0,0 @@
/*
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only
*/
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"
}
}
}
gradlePlugin {
plugins {
register("kotlin-android") {
id = "dev.msfjarvis.claw.kotlin-android"
implementationClass = "dev.msfjarvis.aps.gradle.KotlinAndroidPlugin"
}
register("kotlin-common") {
id = "dev.msfjarvis.claw.kotlin-common"
implementationClass = "dev.msfjarvis.aps.gradle.KotlinCommonPlugin"
}
register("kotlin-library") {
id = "dev.msfjarvis.claw.kotlin-library"
implementationClass = "dev.msfjarvis.aps.gradle.KotlinLibraryPlugin"
}
register("spotless") {
id = "dev.msfjarvis.claw.spotless"
implementationClass = "dev.msfjarvis.aps.gradle.SpotlessPlugin"
}
register("versions") {
id = "dev.msfjarvis.claw.versions"
implementationClass = "dev.msfjarvis.aps.gradle.DependencyUpdatesPlugin"
}
}
}
dependencies {
implementation(libs.build.agp)
implementation(libs.build.kotlin.gradle)
implementation(libs.build.kotlin.serialization)
implementation(libs.build.spotless)
implementation(libs.build.vcu)
implementation(libs.build.versions)
}
@@ -1,18 +0,0 @@
/*
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only
*/
package dev.msfjarvis.aps.gradle
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.apply
@Suppress("Unused")
class KotlinLibraryPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.pluginManager.apply(KotlinCommonPlugin::class)
}
}
+4 -6
View File
@@ -1,6 +1,8 @@
/*
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only
* 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.
*/
@file:Suppress("UnstableApiUsage")
@@ -37,7 +39,3 @@ dependencyResolutionManagement {
}
versionCatalogs { create("libs") { from(files("../gradle/libs.versions.toml")) } }
}
include("android-plugins")
include("kotlin-plugins")
@@ -1,19 +1,29 @@
package dev.msfjarvis.aps.gradle
/*
* 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.
*/
@file:Suppress("UnstableApiUsage")
import com.android.build.api.dsl.TestExtension
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.LibraryAndroidComponentsExtension
import com.android.build.gradle.TestedExtension
import com.android.build.gradle.BaseExtension
import org.gradle.android.AndroidCacheFixPlugin
import org.gradle.api.JavaVersion
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.findByType
private const val SLIM_TESTS_PROPERTY = "slimTests"
@Suppress("UnstableApiUsage")
class AndroidCommonPlugin : Plugin<Project> {
private companion object {
@@ -21,22 +31,17 @@ class AndroidCommonPlugin : Plugin<Project> {
const val MIN_SDK = 26
const val TARGET_SDK = 33
}
override fun apply(project: Project) {
project.configureSlimTests()
project.pluginManager.apply(AndroidCacheFixPlugin::class)
project.extensions.findByType<TestedExtension>()?.run {
project.extensions.configure<BaseExtension> {
compileSdkVersion(COMPILE_SDK)
defaultConfig {
minSdk = MIN_SDK
targetSdk = TARGET_SDK
}
sourceSets {
named("main") { java.srcDirs("src/main/kotlin") }
named("test") { java.srcDirs("src/test/kotlin") }
named("androidTest") { java.srcDirs("src/androidTest/kotlin") }
}
packagingOptions {
resources.excludes.add("**/*.version")
resources.excludes.add("**/*.txt")
@@ -56,25 +61,19 @@ class AndroidCommonPlugin : Plugin<Project> {
unitTests.isReturnDefaultValues = true
}
}
project.extensions.findByType<TestExtension>()?.run {
compileSdk = COMPILE_SDK
defaultConfig {
minSdk = MIN_SDK
targetSdk = TARGET_SDK
}
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) }
}
}
private fun Lint.configureLint(project: Project) {
abortOnError = false
checkReleaseBuilds = false
warningsAsErrors = false
disable.add("DialogFragmentCallbacksDetector")
baseline = project.file("lint-baseline.xml")
}
/**
* When the "slimTests" project property is provided, disable the unit test tasks on `release` build
* type and `nonFree` product flavor to avoid running the same tests repeatedly in different build
@@ -83,7 +82,7 @@ class AndroidCommonPlugin : Plugin<Project> {
* Examples: `./gradlew test -PslimTests` will run unit tests for `nonFreeDebug` and `debug` build
* variants in Android App and Library projects, and all tests in JVM projects.
*/
internal fun Project.configureSlimTests() {
private fun Project.configureSlimTests() {
if (providers.gradleProperty(SLIM_TESTS_PROPERTY).isPresent) {
// Disable unit test tasks on the release build type for Android Library projects
extensions.findByType<LibraryAndroidComponentsExtension>()?.run {
@@ -1,14 +1,20 @@
/*
* 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("UnstableApiUsage")
package dev.msfjarvis.aps.gradle
package dev.msfjarvis.claw.gradle
import com.android.build.api.dsl.ApplicationExtension
import com.android.build.gradle.AppPlugin
import com.android.build.gradle.internal.dsl.BaseAppModuleExtension
import dev.msfjarvis.aps.gradle.signing.configureBuildSigning
import dev.msfjarvis.claw.gradle.signing.configureBuildSigning
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.getByType
import org.gradle.kotlin.dsl.configure
@Suppress("Unused")
class ApplicationPlugin : Plugin<Project> {
@@ -16,9 +22,7 @@ class ApplicationPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.pluginManager.apply(AppPlugin::class)
project.pluginManager.apply(AndroidCommonPlugin::class)
project.extensions.getByType<BaseAppModuleExtension>().run {
adbOptions.installOptions("--user 0")
project.extensions.configure<ApplicationExtension> {
dependenciesInfo {
includeInBundle = false
includeInApk = false
@@ -50,12 +54,4 @@ class ApplicationPlugin : Plugin<Project> {
project.configureBuildSigning()
}
}
private fun Project.isSnapshot(): Boolean {
with(project.providers) {
val workflow = environmentVariable("GITHUB_WORKFLOW")
val snapshot = environmentVariable("SNAPSHOT")
return workflow.isPresent || snapshot.isPresent
}
}
}
@@ -1,4 +1,10 @@
package dev.msfjarvis.aps.gradle
/*
* 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.gradle
import com.github.benmanes.gradle.versions.VersionsPlugin
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
@@ -7,7 +13,7 @@ import nl.littlerobots.vcu.plugin.VersionCatalogUpdatePlugin
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.getByType
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.withType
@Suppress("Unused")
@@ -20,13 +26,22 @@ class DependencyUpdatesPlugin : Plugin<Project> {
when (candidate.group) {
"com.squareup.okhttp3",
"org.jetbrains.kotlin" -> true
else -> false
else -> isNonStable(candidate.version) && !isNonStable(currentVersion)
}
}
checkConstraints = true
checkBuildEnvironmentConstraints = true
checkForGradleUpdate = true
}
project.extensions.getByType<VersionCatalogUpdateExtension>().run {
project.extensions.configure<VersionCatalogUpdateExtension> {
keep.keepUnusedLibraries.set(true)
}
}
private fun isNonStable(version: String): Boolean {
val stableKeyword = listOf("RELEASE", "FINAL", "GA").any { version.uppercase().contains(it) }
val regex = "^[0-9,.v-]+(-r)?$".toRegex()
val isStable = stableKeyword || regex.matches(version)
return isStable.not()
}
}
@@ -0,0 +1,35 @@
/*
* 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.gradle
import io.gitlab.arturbosch.detekt.DetektPlugin
import io.gitlab.arturbosch.detekt.extensions.DetektExtension
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
object Detekt {
private const val TWITTER_RULES_VERSION = "0.0.26"
fun apply(project: Project) {
project.pluginManager.apply(DetektPlugin::class.java)
project.extensions.configure<DetektExtension> {
debug = project.providers.gradleProperty("debugDetekt").isPresent
parallel = true
ignoredBuildTypes = listOf("benchmark", "release")
basePath = project.layout.projectDirectory.toString()
baseline =
project.rootProject.layout.projectDirectory
.dir("detekt-baselines")
.file("${project.name}.xml")
.asFile
}
project.dependencies.add(
"detektPlugins",
"com.twitter.compose.rules:detekt:$TWITTER_RULES_VERSION",
)
}
}
@@ -1,9 +1,10 @@
/*
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only
* 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.aps.gradle
package dev.msfjarvis.claw.gradle
import org.gradle.api.Plugin
import org.gradle.api.Project
@@ -1,9 +1,10 @@
/*
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only
* 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.aps.gradle
package dev.msfjarvis.claw.gradle
import org.gradle.api.JavaVersion
import org.gradle.api.Plugin
@@ -18,6 +19,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
class KotlinCommonPlugin : Plugin<Project> {
override fun apply(project: Project) {
Detekt.apply(project)
project.tasks.run {
withType<JavaCompile>().configureEach {
sourceCompatibility = JavaVersion.VERSION_11.toString()
@@ -25,16 +27,17 @@ class KotlinCommonPlugin : Plugin<Project> {
}
withType<KotlinCompile>().configureEach {
kotlinOptions {
allWarningsAsErrors = false
allWarningsAsErrors =
false // project.providers.environmentVariable("GITHUB_WORKFLOW").isPresent
jvmTarget = JavaVersion.VERSION_11.toString()
freeCompilerArgs = freeCompilerArgs + ADDITIONAL_COMPILER_ARGS
languageVersion = "1.5"
languageVersion = "1.7"
}
}
withType<Test>().configureEach {
maxParallelForks = Runtime.getRuntime().availableProcessors() * 2
testLogging { events(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED) }
doNotTrackState("We want tests to always run even if Gradle thinks otherwise")
useJUnitPlatform()
}
}
}
@@ -43,8 +46,6 @@ class KotlinCommonPlugin : Plugin<Project> {
private val ADDITIONAL_COMPILER_ARGS =
listOf(
"-opt-in=kotlin.RequiresOptIn",
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:suppressKotlinVersionCompatibilityCheck=1.7.20",
)
}
}
@@ -0,0 +1,20 @@
/*
* 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.gradle
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.apply
import org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper
@Suppress("Unused")
class KotlinJvmPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.pluginManager.apply(KotlinPluginWrapper::class)
project.pluginManager.apply(KotlinCommonPlugin::class)
}
}
@@ -0,0 +1,53 @@
/*
* 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.gradle
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.configure
import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin
import org.jetbrains.kotlin.gradle.plugin.KaptExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinAndroidPluginWrapper
@Suppress("Unused")
class KotlinKaptPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.pluginManager.run {
apply(KotlinAndroidPluginWrapper::class)
apply(Kapt3GradleSubplugin::class)
}
project.afterEvaluate {
project.extensions.configure<KaptExtension> {
javacOptions {
if (hasDaggerCompilerDependency()) {
// https://dagger.dev/dev-guide/compiler-options#fastinit-mode
option("-Adagger.fastInit=enabled")
// Enable the better, experimental error messages
// https://github.com/google/dagger/commit/0d2505a727b54f47b8677f42dd4fc5c1924e37f5
option("-Adagger.experimentalDaggerErrorMessages=enabled")
// KAPT nests errors causing real issues to be suppressed in CI logs
option("-Xmaxerrs", 500)
// Enables per-module validation for faster error detection
// https://github.com/google/dagger/commit/325b516ac6a53d3fc973d247b5231fafda9870a2
option("-Adagger.moduleBindingValidation=ERROR")
}
}
}
}
project.tasks
.matching { it.name.startsWith("kapt") && it.name.endsWith("UnitTestKotlin") }
.configureEach { enabled = false }
}
private fun Project.hasDaggerCompilerDependency(): Boolean {
return configurations.any {
it.dependencies.any { dependency -> dependency.name == "dagger-compiler" }
}
}
}
@@ -1,4 +1,10 @@
package dev.msfjarvis.aps.gradle
/*
* 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.gradle
import com.android.build.gradle.LibraryPlugin
import org.gradle.api.Plugin
@@ -1,13 +1,19 @@
package dev.msfjarvis.aps.gradle
/*
* 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.gradle
import com.android.build.api.artifact.SingleArtifact
import com.android.build.api.variant.ApplicationAndroidComponentsExtension
import com.android.build.api.variant.VariantOutputConfiguration
import dev.msfjarvis.aps.gradle.artifacts.CollectApksTask
import dev.msfjarvis.aps.gradle.artifacts.CollectBundleTask
import dev.msfjarvis.claw.gradle.artifacts.CollectApksTask
import dev.msfjarvis.claw.gradle.artifacts.CollectBundleTask
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.getByType
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.register
@Suppress("Unused")
@@ -15,23 +21,26 @@ class RenameArtifactsPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.pluginManager.withPlugin("com.android.application") {
project.extensions.getByType<ApplicationAndroidComponentsExtension>().run {
project.extensions.configure<ApplicationAndroidComponentsExtension> {
onVariants { variant ->
project.tasks.register<CollectApksTask>("collect${variant.name.capitalize()}Apks") {
val taskPrefix = "collect${variant.name.replaceFirstChar { it.uppercase() }}"
project.tasks.register<CollectApksTask>("${taskPrefix}Apks") {
variantName.set(variant.name)
apkFolder.set(variant.artifacts.get(SingleArtifact.APK))
mappingFile.set(variant.artifacts.get(SingleArtifact.OBFUSCATION_MAPPING_FILE))
builtArtifactsLoader.set(variant.artifacts.getBuiltArtifactsLoader())
outputDirectory.set(project.layout.projectDirectory.dir("outputs"))
outputDirectory.set(project.layout.projectDirectory.dir("apk"))
}
project.tasks.register<CollectBundleTask>("collect${variant.name.capitalize()}Bundle") {
project.tasks.register<CollectBundleTask>("${taskPrefix}Bundle") {
val mainOutput =
variant.outputs.single {
it.outputType == VariantOutputConfiguration.OutputType.SINGLE
}
variantName.set(variant.name)
versionName.set(mainOutput.versionName)
mappingFile.set(variant.artifacts.get(SingleArtifact.OBFUSCATION_MAPPING_FILE))
bundleFile.set(variant.artifacts.get(SingleArtifact.BUNDLE))
outputDirectory.set(project.layout.projectDirectory.dir("outputs"))
outputDirectory.set(project.layout.projectDirectory.dir("bundle"))
}
}
}
@@ -1,32 +1,39 @@
/*
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only
* 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.aps.gradle
package dev.msfjarvis.claw.gradle
import com.diffplug.gradle.spotless.SpotlessExtension
import com.diffplug.gradle.spotless.SpotlessPlugin
import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.getByType
import org.gradle.kotlin.dsl.configure
@Suppress("Unused")
class SpotlessPlugin : Plugin<Project> {
override fun apply(project: Project) {
if (project.rootProject != project) {
throw GradleException("Spotless plugin must only be applied to the root project.")
}
project.pluginManager.apply(SpotlessPlugin::class)
project.extensions.getByType<SpotlessExtension>().run {
project.extensions.configure<SpotlessExtension> {
kotlin {
ktfmt(KTFMT_VERSION).googleStyle()
target("**/*.kt")
targetExclude("**/build/", "**/gen/")
targetExclude("**/build/", "/spotless/")
licenseHeaderFile(project.file("spotless/license.kt"))
}
kotlinGradle {
ktfmt(KTFMT_VERSION).googleStyle()
target("**/*.kts")
targetExclude("**/build/")
licenseHeaderFile(project.file("spotless/license.kt"), "import|plugins|@file")
}
format("xml") {
target("**/*.xml")
@@ -39,6 +46,6 @@ class SpotlessPlugin : Plugin<Project> {
}
private companion object {
private const val KTFMT_VERSION = "0.41"
private const val KTFMT_VERSION = "0.43"
}
}
@@ -1,14 +1,24 @@
package dev.msfjarvis.aps.gradle.artifacts
/*
* 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.gradle.artifacts
import com.android.build.api.variant.BuiltArtifactsLoader
import java.io.File
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardCopyOption
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputDirectory
@@ -21,6 +31,10 @@ import org.gradle.api.tasks.TaskAction
abstract class CollectApksTask : DefaultTask() {
@get:InputFiles @get:PathSensitive(PathSensitivity.NONE) abstract val apkFolder: DirectoryProperty
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val mappingFile: RegularFileProperty
@get:Input abstract val variantName: Property<String>
@get:Internal abstract val builtArtifactsLoader: Property<BuiltArtifactsLoader>
@@ -30,9 +44,13 @@ abstract class CollectApksTask : DefaultTask() {
@TaskAction
fun run() {
val outputDir = outputDirectory.asFile.get()
val outputDirStream =
Files.walk(outputDir.toPath()).sorted(Comparator.reverseOrder()).map(Path::toFile)
outputDirStream.forEach(File::delete)
outputDirStream.close()
outputDir.mkdirs()
val builtArtifacts =
builtArtifactsLoader.get().load(apkFolder.get()) ?: throw RuntimeException("Cannot load APKs")
builtArtifactsLoader.get().load(apkFolder.get()) ?: error("Cannot load APKs")
builtArtifacts.elements.forEach { artifact ->
Files.copy(
Paths.get(artifact.outputFile),
@@ -40,5 +58,10 @@ abstract class CollectApksTask : DefaultTask() {
StandardCopyOption.REPLACE_EXISTING,
)
}
Files.copy(
mappingFile.get().asFile.toPath(),
outputDir.resolve("mapping.txt").toPath(),
StandardCopyOption.REPLACE_EXISTING,
)
}
}
@@ -1,6 +1,14 @@
package dev.msfjarvis.aps.gradle.artifacts
/*
* 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.gradle.artifacts
import java.io.File
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardCopyOption
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
@@ -14,6 +22,8 @@ import org.gradle.api.tasks.TaskAction
abstract class CollectBundleTask : DefaultTask() {
@get:InputFile abstract val bundleFile: RegularFileProperty
@get:InputFile abstract val mappingFile: RegularFileProperty
@get:Input abstract val variantName: Property<String>
@get:Input abstract val versionName: Property<String>
@@ -23,11 +33,20 @@ abstract class CollectBundleTask : DefaultTask() {
@TaskAction
fun taskAction() {
val outputDir = outputDirectory.asFile.get()
val outputDirStream =
Files.walk(outputDir.toPath()).sorted(Comparator.reverseOrder()).map(Path::toFile)
outputDirStream.forEach(File::delete)
outputDirStream.close()
outputDir.mkdirs()
Files.copy(
bundleFile.get().asFile.toPath(),
outputDir.resolve("Claw-${variantName.get()}-${versionName.get()}.aab").toPath(),
StandardCopyOption.REPLACE_EXISTING,
)
Files.copy(
mappingFile.get().asFile.toPath(),
outputDir.resolve("mapping.txt").toPath(),
StandardCopyOption.REPLACE_EXISTING,
)
}
}
@@ -1,9 +1,10 @@
/*
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only
* 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.aps.gradle.signing
package dev.msfjarvis.claw.gradle.signing
import com.android.build.gradle.internal.dsl.BaseAppModuleExtension
import java.util.Properties
@@ -1,4 +1,10 @@
package dev.msfjarvis.aps.gradle.versioning
/*
* 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.gradle.versioning
const val VERSIONING_PROP_FILE = "version.properties"
const val VERSIONING_PROP_VERSION_NAME = "versioning-plugin.versionName"
@@ -1,4 +1,10 @@
package dev.msfjarvis.aps.gradle.versioning
/*
* 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.gradle.versioning
import com.android.build.api.variant.ApplicationAndroidComponentsExtension
import com.android.build.api.variant.VariantOutputConfiguration
@@ -8,7 +14,7 @@ import java.util.Properties
import java.util.concurrent.atomic.AtomicBoolean
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.getByType
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.register
import org.gradle.kotlin.dsl.withType
@@ -40,11 +46,15 @@ class VersioningPlugin : Plugin<Project> {
}
project.plugins.withType<AppPlugin> {
androidAppPluginApplied.set(true)
extensions.getByType<ApplicationAndroidComponentsExtension>().onVariants { variant ->
val mainOutput =
variant.outputs.single { it.outputType == VariantOutputConfiguration.OutputType.SINGLE }
mainOutput.versionName.set(versionName)
mainOutput.versionCode.set(versionCode)
extensions.configure<ApplicationAndroidComponentsExtension> {
onVariants { variant ->
val mainOutput =
variant.outputs.single {
it.outputType == VariantOutputConfiguration.OutputType.SINGLE
}
mainOutput.versionName.set(versionName)
mainOutput.versionCode.set(versionCode)
}
}
}
val version = Semver(versionName)
@@ -1,4 +1,10 @@
package dev.msfjarvis.aps.gradle.versioning
/*
* 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.gradle.versioning
import com.vdurmont.semver4j.Semver
import org.gradle.api.DefaultTask
+6 -1
View File
@@ -1,9 +1,14 @@
/*
* 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.
*/
@file:Suppress("DSL_SCOPE_VIOLATION")
plugins {
id("dev.msfjarvis.claw.spotless")
id("dev.msfjarvis.claw.versions")
id("dev.msfjarvis.claw.kotlin-common")
alias(libs.plugins.hilt) apply false
alias(libs.plugins.android.test) apply false
}
+13 -3
View File
@@ -1,22 +1,32 @@
/*
* 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.
*/
@file:Suppress("DSL_SCOPE_VIOLATION", "UnstableApiUsage")
plugins {
kotlin("android")
id("dev.msfjarvis.claw.kotlin-common")
id("dev.msfjarvis.claw.kotlin-android")
id("dev.msfjarvis.claw.android-library")
alias(libs.plugins.anvil)
alias(libs.plugins.whetstone)
}
anvil { generateDaggerFactories.set(true) }
androidComponents { beforeVariants { it.enableUnitTest = false } }
dependencies {
implementation(platform(libs.androidx.compose.bom))
api(libs.napier)
implementation(projects.core)
implementation(projects.database)
implementation(projects.model)
implementation(libs.accompanist.flowlayout)
implementation(libs.androidx.browser)
implementation(libs.androidx.compose.animation)
implementation(libs.androidx.compose.foundation)
implementation(libs.androidx.compose.material)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.runtime)
implementation(libs.androidx.compose.ui.text)
+4
View File
@@ -0,0 +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>
@@ -1,6 +1,12 @@
/*
* 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.common
sealed class NetworkState {
internal sealed class NetworkState {
class Success<T>(val data: T) : NetworkState()
class Error(val error: Throwable, val description: String) : NetworkState()
object Loading : NetworkState()
@@ -1,3 +1,9 @@
/*
* Copyright © 2021-2023 Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.common.comments
import android.text.format.DateUtils
@@ -14,23 +20,22 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import dev.msfjarvis.claw.common.posts.PostActions
import dev.msfjarvis.claw.common.posts.PostTitle
@@ -39,21 +44,28 @@ import dev.msfjarvis.claw.common.posts.TagRow
import dev.msfjarvis.claw.common.res.ClawIcons
import dev.msfjarvis.claw.common.ui.NetworkImage
import dev.msfjarvis.claw.common.ui.ThemedRichText
import dev.msfjarvis.claw.model.Comment
import dev.msfjarvis.claw.model.ExtendedPostDetails
import dev.msfjarvis.claw.model.LinkMetadata
import dev.msfjarvis.claw.model.LobstersPostDetails
import java.time.Instant
import java.time.temporal.TemporalAccessor
@Composable
fun CommentsHeader(
postDetails: ExtendedPostDetails,
internal fun CommentsHeader(
postDetails: LobstersPostDetails,
postActions: PostActions,
htmlConverter: HTMLConverter,
modifier: Modifier = Modifier,
) {
val htmlConverter = LocalHTMLConverter.current
val uriHandler = LocalUriHandler.current
val linkMetadata by
produceState(
initialValue = LinkMetadata(postDetails.url, null, null),
) {
runCatching { postActions.getLinkMetadata(postDetails.url) }
.onSuccess { metadata -> value = metadata }
}
Surface(color = MaterialTheme.colorScheme.background) {
Surface(color = MaterialTheme.colorScheme.background, modifier = modifier) {
Column(
modifier = Modifier.padding(16.dp).fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp),
@@ -62,13 +74,11 @@ fun CommentsHeader(
TagRow(tags = postDetails.tags)
Spacer(Modifier.height(4.dp))
if (postDetails.linkMetadata.url.isNotBlank()) {
if (linkMetadata.url.isNotBlank()) {
PostLink(
linkMetadata = postDetails.linkMetadata,
linkMetadata = linkMetadata,
modifier =
Modifier.clickable {
postActions.viewPost(postDetails.linkMetadata.url, postDetails.commentsUrl)
},
Modifier.clickable { postActions.viewPost(linkMetadata.url, postDetails.commentsUrl) },
)
Spacer(Modifier.height(4.dp))
}
@@ -91,7 +101,7 @@ fun CommentsHeader(
}
@Composable
fun PostLink(
private fun PostLink(
linkMetadata: LinkMetadata,
modifier: Modifier = Modifier,
) {
@@ -101,22 +111,13 @@ fun PostLink(
shape = RoundedCornerShape(8.dp),
)
) {
Row(modifier = Modifier.padding(16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp)) {
if (linkMetadata.faviconUrl != null) {
NetworkImage(
url = linkMetadata.faviconUrl!!,
placeholder = ClawIcons.Web,
contentDescription = "",
modifier = Modifier.size(24.dp),
)
} else {
Icon(
painter = ClawIcons.Web,
contentDescription = null,
tint = MaterialTheme.colorScheme.onSecondary,
modifier = Modifier.clip(CircleShape),
)
}
Row(modifier = Modifier.padding(16.dp), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
NetworkImage(
url = linkMetadata.faviconUrl,
placeholder = ClawIcons.Web,
contentDescription = "",
modifier = Modifier.size(24.dp),
)
Text(
text = linkMetadata.url,
overflow = TextOverflow.Ellipsis,
@@ -128,20 +129,30 @@ fun PostLink(
}
}
private val CommentEntryPadding = 16f.dp
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun CommentEntry(
comment: Comment,
internal fun CommentEntry(
commentNode: CommentNode,
htmlConverter: HTMLConverter,
toggleExpanded: (CommentNode) -> Unit,
modifier: Modifier = Modifier,
) {
var expanded by remember(comment) { mutableStateOf(true) }
val htmlConverter = LocalHTMLConverter.current
val uriHandler = LocalUriHandler.current
val comment = commentNode.comment
Box(
modifier =
Modifier.fillMaxWidth()
.clickable { expanded = !expanded }
modifier
.fillMaxWidth()
.clickable { toggleExpanded(commentNode) }
.background(MaterialTheme.colorScheme.background)
.padding(start = (comment.indentLevel * 16).dp, end = 16.dp, top = 16.dp, bottom = 16.dp),
.padding(
start = CommentEntryPadding * comment.indentLevel,
end = CommentEntryPadding,
top = CommentEntryPadding,
bottom = CommentEntryPadding,
),
) {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
Submitter(
@@ -149,6 +160,7 @@ fun CommentEntry(
buildCommenterString(
commenterName = comment.user.username,
score = comment.score,
isUnread = commentNode.isUnread,
createdAt = comment.createdAt,
updatedAt = comment.updatedAt,
),
@@ -157,7 +169,7 @@ fun CommentEntry(
modifier =
Modifier.clickable { uriHandler.openUri("https://lobste.rs/u/${comment.user.username}") },
)
AnimatedContent(targetState = expanded) { expandedState ->
AnimatedContent(targetState = commentNode.isExpanded) { expandedState ->
if (expandedState) {
ThemedRichText(
text = htmlConverter.convertHTMLToMarkdown(comment.comment),
@@ -173,6 +185,7 @@ fun CommentEntry(
fun buildCommenterString(
commenterName: String,
score: Int,
isUnread: Boolean,
createdAt: TemporalAccessor,
updatedAt: TemporalAccessor,
): AnnotatedString {
@@ -211,5 +224,17 @@ fun buildCommenterString(
append(updatedRelative.toString())
append(')')
}
if (isUnread) {
append(' ')
withStyle(
style =
SpanStyle(
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.error,
)
) {
append("(unread)")
}
}
}
}
@@ -0,0 +1,107 @@
/*
* 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.common.comments
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.material3.Divider
import dev.msfjarvis.claw.database.local.PostComments
import dev.msfjarvis.claw.model.Comment
internal data class CommentNode(
val comment: Comment,
var parent: CommentNode? = null,
val children: MutableList<CommentNode> = mutableListOf(),
val isUnread: Boolean = false,
var isExpanded: Boolean = true,
) {
fun addChild(child: CommentNode) {
if (comment.indentLevel + 1 == child.comment.indentLevel) {
children.add(child)
child.parent = this
} else {
children.last().addChild(child)
}
}
}
internal fun createListNode(
comments: List<Comment>,
commentState: PostComments?
): MutableList<CommentNode> {
val commentNodes = mutableListOf<CommentNode>()
val isUnread = { id: String -> commentState?.commentIds?.contains(id) == false }
for (i in comments.indices) {
if (comments[i].indentLevel == 1) {
commentNodes.add(CommentNode(comment = comments[i], isUnread = isUnread(comments[i].shortId)))
} else {
commentNodes
.last()
.addChild(CommentNode(comment = comments[i], isUnread = isUnread(comments[i].shortId)))
}
}
return commentNodes
}
internal fun setExpanded(commentNode: CommentNode, expanded: Boolean): CommentNode {
commentNode.isExpanded = expanded
if (commentNode.children.isNotEmpty()) {
commentNode.children.forEach { setExpanded(it, expanded) }
}
return commentNode
}
internal fun findTopMostParent(node: CommentNode): CommentNode {
val parent = node.parent
return if (parent != null) {
findTopMostParent(parent)
} else {
node
}
}
internal fun LazyListScope.nodes(
nodes: List<CommentNode>,
htmlConverter: HTMLConverter,
toggleExpanded: (CommentNode) -> Unit,
) {
nodes.forEach { node ->
node(
node = node,
htmlConverter = htmlConverter,
toggleExpanded = toggleExpanded,
)
}
}
private fun LazyListScope.node(
node: CommentNode,
htmlConverter: HTMLConverter,
toggleExpanded: (CommentNode) -> Unit,
) {
// Skip the node if neither the node nor its parent is expanded
if (!node.isExpanded && node.parent?.isExpanded == false) {
return
}
item {
CommentEntry(
commentNode = node,
htmlConverter = htmlConverter,
toggleExpanded = toggleExpanded,
)
Divider()
}
if (node.children.isNotEmpty()) {
nodes(
node.children,
htmlConverter = htmlConverter,
toggleExpanded = toggleExpanded,
)
}
}
@@ -1,18 +1,26 @@
/*
* 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.common.comments
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material3.Divider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState
import androidx.compose.runtime.toMutableStateList
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
@@ -24,19 +32,34 @@ import dev.msfjarvis.claw.common.NetworkState.Success
import dev.msfjarvis.claw.common.posts.PostActions
import dev.msfjarvis.claw.common.ui.NetworkError
import dev.msfjarvis.claw.common.ui.ProgressBar
import dev.msfjarvis.claw.model.ExtendedPostDetails
import dev.msfjarvis.claw.database.local.PostComments
import dev.msfjarvis.claw.model.Comment
import dev.msfjarvis.claw.model.LobstersPostDetails
@Suppress("LongParameterList")
@Composable
private fun CommentsPageInternal(
details: ExtendedPostDetails,
details: LobstersPostDetails,
postActions: PostActions,
htmlConverter: HTMLConverter,
commentState: PostComments?,
markSeenComments: (String, List<Comment>) -> Unit,
modifier: Modifier = Modifier,
) {
val commentNodes = createListNode(details.comments, commentState).toMutableStateList()
LaunchedEffect(key1 = commentNodes) { markSeenComments(details.shortId, details.comments) }
Surface(color = MaterialTheme.colorScheme.surfaceVariant) {
LazyColumn(modifier = modifier, contentPadding = PaddingValues(bottom = 24.dp)) {
item { CommentsHeader(postDetails = details, postActions = postActions) }
item {
CommentsHeader(
postDetails = details,
postActions = postActions,
htmlConverter = htmlConverter,
)
}
if (details.comments.isNotEmpty()) {
if (commentNodes.isNotEmpty()) {
item {
Text(
text = "Comments",
@@ -45,12 +68,20 @@ private fun CommentsPageInternal(
)
}
itemsIndexed(details.comments) { index, item ->
if (index != 0) {
Divider()
}
CommentEntry(item)
}
nodes(
nodes = commentNodes,
htmlConverter = htmlConverter,
toggleExpanded = { node ->
val newNode = setExpanded(node, !node.isExpanded)
val parent = findTopMostParent(newNode)
val index =
commentNodes.indexOf(commentNodes.find { it.comment.url == parent.comment.url })
if (index != -1) {
commentNodes.removeAt(index)
commentNodes.add(index, parent)
}
},
)
} else {
item {
Text(
@@ -66,35 +97,52 @@ private fun CommentsPageInternal(
}
}
@Suppress("UNCHECKED_CAST")
@Suppress("UNCHECKED_CAST", "LongParameterList")
@Composable
fun CommentsPage(
postId: String,
getDetails: suspend (String) -> ExtendedPostDetails,
postActions: PostActions,
htmlConverter: HTMLConverter,
getSeenComments: suspend (String) -> PostComments?,
markSeenComments: (String, List<Comment>) -> Unit,
modifier: Modifier = Modifier,
) {
val postDetails by
produceState<NetworkState>(Loading) {
runCatching { getDetails(postId) }
runCatching { postActions.getComments(postId) }
.fold(
onSuccess = { details -> value = Success(details) },
onFailure = { value = Error(error = it, description = "Failed to load comments") }
)
}
val commentState by
produceState<PostComments?>(initialValue = null) { value = getSeenComments(postId) }
when (postDetails) {
is Success<*> -> {
CommentsPageInternal(
(postDetails as Success<ExtendedPostDetails>).data,
postActions,
modifier.fillMaxSize(),
details = (postDetails as Success<LobstersPostDetails>).data,
postActions = postActions,
htmlConverter = htmlConverter,
commentState = commentState,
markSeenComments = markSeenComments,
modifier = modifier.fillMaxSize(),
)
}
is Error -> {
val error = postDetails as Error
NetworkError(label = error.description, error = error.error)
Box(modifier = Modifier.fillMaxSize()) {
NetworkError(
label = error.description,
error = error.error,
modifier = Modifier.align(Alignment.Center),
)
}
}
Loading -> {
Box(modifier = Modifier.fillMaxSize()) {
ProgressBar(modifier = Modifier.align(Alignment.Center))
}
}
Loading -> ProgressBar()
}
}
@@ -1,10 +1,15 @@
/*
* 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.common.comments
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.runtime.Stable
/** Defines a contract to convert strings of HTML to Markdown. */
@Stable
fun interface HTMLConverter {
fun convertHTMLToMarkdown(html: String): String
}
val LocalHTMLConverter = staticCompositionLocalOf<HTMLConverter> { error("To be provided") }
@@ -0,0 +1,41 @@
/*
* 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.common.injection
import android.app.Application
import coil.Coil
import coil.ImageLoader
import coil.disk.DiskCache
import coil.memory.MemoryCache
import com.deliveryhero.whetstone.app.ApplicationScope
import com.squareup.anvil.annotations.ContributesMultibinding
import dev.msfjarvis.claw.core.injection.AppPlugin
import javax.inject.Inject
@ContributesMultibinding(ApplicationScope::class)
class CoilAppPlugin @Inject constructor() : AppPlugin {
override fun apply(application: Application) {
Coil.setImageLoader {
ImageLoader.Builder(application)
.memoryCache { MemoryCache.Builder(application).maxSizePercent(MEMORY_CACHE_RATIO).build() }
.diskCache {
DiskCache.Builder()
.directory(application.cacheDir.resolve("image_cache"))
.maxSizeBytes(DISK_CACHE_MAX_SIZE)
.build()
}
// Show a short crossfade when loading images asynchronously.
.crossfade(true)
.build()
}
}
private companion object {
private const val MEMORY_CACHE_RATIO = 0.25
private const val DISK_CACHE_MAX_SIZE = 25L * 1024 * 1024 // 25 MB
}
}
@@ -1,3 +1,9 @@
/*
* 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.common.posts
import androidx.compose.animation.Crossfade
@@ -5,7 +11,6 @@ import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -19,8 +24,6 @@ import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.Badge
import androidx.compose.material3.BadgedBox
import androidx.compose.material3.Divider
@@ -77,11 +80,7 @@ fun LobstersCard(
SaveButton(
isSaved = localSavedState,
modifier =
Modifier.clickable(
role = Role.Button,
indication = rememberRipple(bounded = false, radius = 24.dp),
interactionSource = remember { MutableInteractionSource() },
) {
Modifier.clickable(role = Role.Button) {
localSavedState = !localSavedState
postActions.toggleSave(post)
},
@@ -92,8 +91,6 @@ fun LobstersCard(
modifier =
Modifier.combinedClickable(
role = Role.Button,
indication = rememberRipple(bounded = false, radius = 24.dp),
interactionSource = remember { MutableInteractionSource() },
onClick = { postActions.viewComments(post.shortId) },
onLongClick = { postActions.viewCommentsPage(post.commentsUrl) },
),
@@ -118,7 +115,7 @@ fun PostDetails(post: SavedPost, modifier: Modifier = Modifier) {
}
@Composable
fun PostTitle(
internal fun PostTitle(
title: String,
modifier: Modifier = Modifier,
) {
@@ -131,7 +128,7 @@ fun PostTitle(
}
@Composable
fun Submitter(
internal fun Submitter(
text: AnnotatedString,
avatarUrl: String,
contentDescription: String,
@@ -146,15 +143,15 @@ fun Submitter(
url = avatarUrl,
placeholder = ClawIcons.Account,
contentDescription = contentDescription,
modifier = modifier.requiredSize(24.dp).clip(CircleShape),
modifier = Modifier.requiredSize(24.dp).clip(CircleShape),
)
Text(text = text, modifier = modifier, style = MaterialTheme.typography.bodyMedium)
Text(text = text, style = MaterialTheme.typography.bodyMedium)
}
}
@Composable
fun SaveButton(
private fun SaveButton(
isSaved: Boolean,
modifier: Modifier = Modifier,
) {
@@ -172,7 +169,7 @@ fun SaveButton(
@Composable
@OptIn(ExperimentalMaterial3Api::class)
fun CommentsButton(
private fun CommentsButton(
commentCount: Int?,
modifier: Modifier = Modifier,
) {
@@ -181,7 +178,7 @@ fun CommentsButton(
badge = {
if (commentCount != null) {
Badge(
modifier = Modifier.absoluteOffset(x = (-8).dp),
modifier = Modifier.absoluteOffset(x = -(8.dp)),
containerColor = MaterialTheme.colorScheme.tertiaryContainer,
) {
Text(
@@ -203,7 +200,7 @@ fun CommentsButton(
}
@Composable
fun TagRow(
internal fun TagRow(
tags: List<String>,
modifier: Modifier = Modifier,
) {
@@ -217,16 +214,16 @@ fun TagRow(
}
@Composable
fun TagText(
private fun TagText(
tag: String,
modifier: Modifier = Modifier,
) {
Text(
text = tag,
modifier =
Modifier.background(MaterialTheme.colorScheme.tertiaryContainer, RoundedCornerShape(50))
.padding(vertical = 4.dp, horizontal = 12.dp)
.then(modifier),
modifier
.background(MaterialTheme.colorScheme.tertiaryContainer, CircleShape)
.padding(vertical = 4.dp, horizontal = 12.dp),
color = MaterialTheme.colorScheme.onTertiaryContainer,
style = MaterialTheme.typography.labelLarge,
)
@@ -1,10 +1,22 @@
/*
* 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.common.posts
import androidx.compose.runtime.Stable
import dev.msfjarvis.claw.database.local.SavedPost
import dev.msfjarvis.claw.model.LinkMetadata
import dev.msfjarvis.claw.model.LobstersPostDetails
@Stable
interface PostActions {
fun viewPost(postUrl: String, commentsUrl: String)
fun viewComments(postId: String)
fun viewCommentsPage(commentsUrl: String)
fun toggleSave(post: SavedPost)
suspend fun getComments(postId: String): LobstersPostDetails
suspend fun getLinkMetadata(url: String): LinkMetadata
}
@@ -1,3 +1,9 @@
/*
* 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.common.posts
import dev.msfjarvis.claw.database.local.SavedPost
@@ -15,6 +21,7 @@ fun LobstersPost.toDbModel(): SavedPost {
submitterName = submitter.username,
submitterAvatarUrl = submitter.avatarUrl,
tags = tags,
description = description,
)
}
@@ -29,5 +36,6 @@ fun LobstersPostDetails.toDbModel(): SavedPost {
submitterName = submitter.username,
submitterAvatarUrl = submitter.avatarUrl,
tags = tags,
description = description,
)
}
@@ -1,3 +1,9 @@
/*
* 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.common.res
import dev.msfjarvis.claw.common.res.clawicons.account_circle_black_24dp
@@ -1,3 +1,18 @@
/*
* 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.
*/
@file:Suppress(
"ClassNaming",
"FunctionNaming",
"FunctionOnlyReturningConstant",
"LongMethod",
"MagicNumber",
"WildcardImport",
)
package dev.msfjarvis.claw.common.res.clawicons
import androidx.compose.ui.geometry.*
@@ -1,3 +1,18 @@
/*
* 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.
*/
@file:Suppress(
"ClassNaming",
"FunctionNaming",
"FunctionOnlyReturningConstant",
"LongMethod",
"MagicNumber",
"WildcardImport",
)
package dev.msfjarvis.claw.common.res.clawicons
import androidx.compose.ui.geometry.*

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