Compare commits

...
64 Commits
Author SHA1 Message Date
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
70 changed files with 12928 additions and 11770 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
+10
View File
@@ -73,6 +73,16 @@
],
"datasourceTemplate": "maven",
"depNameTemplate": "androidx.compose.compiler:compiler"
},
{
"fileMatch": [
"qodana.yaml"
],
"matchStrings": [
"jetbrains/qodana-jvm-community:(?<currentValue>.*)"
],
"datasourceTemplate": "docker",
"depNameTemplate": "jetbrains/qodana-jvm-community",
}
]
}
+92
View File
@@ -0,0 +1,92 @@
name: Baseline profile generation
on:
# every day at 00:43
schedule:
- cron: '43 0 * * *'
workflow_dispatch:
jobs:
baseline-profile:
runs-on: macos-latest
timeout-minutes: 45
env:
TERM: dumb
steps:
- name: Checkout repository
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3.1.0
with:
token: ${{ secrets.POST_RELEASE_GH_TOKEN }}
fetch-depth: 0
- name: Set up JDK
uses: actions/setup-java@c3ac5dd0ed8db40fedb61c32fbe677e6b355e94c # v3.8.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
- 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 and 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: 33
target: google_apis
arch: x86_64
profile: Galaxy Nexus
script: |
# Run our benchmark, enabling only tests using BaselineProfile
./gradlew connectedBenchmarkAndroidTest -Pandroid.testInstrumentationRunnerArguments.size=large
# 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 app/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 app/src/main/baseline-prof.txt) != '' ]]; then
git add app/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@83fd05a356d7e2593de66fc9913b3002723633cb # v3.1.1
with:
name: Baseline profile output
path: benchmark/build/outputs/baseline-prof
- name: Clean secrets
if: always()
run: scripts/signing-cleanup.sh
+5 -3
View File
@@ -21,7 +21,7 @@ jobs:
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3.1.0
- name: Set up JDK
uses: actions/setup-java@de1bb2b0c5634f0fc4438d7aa9944e68f9bf86cc # tag=v3.6.0
uses: actions/setup-java@c3ac5dd0ed8db40fedb61c32fbe677e6b355e94c # v3.8.0
with:
distribution: temurin
java-version: 18
@@ -56,13 +56,15 @@ jobs:
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3.1.0
- name: Set up JDK
uses: actions/setup-java@de1bb2b0c5634f0fc4438d7aa9944e68f9bf86cc # tag=v3.6.0
uses: actions/setup-java@c3ac5dd0ed8db40fedb61c32fbe677e6b355e94c # v3.8.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 }}
@@ -0,0 +1,30 @@
name: "CodeQL"
on:
push:
branches:
- main
schedule:
- cron: "20 3 * * 0"
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3
- name: Initialize CodeQL
uses: github/codeql-action/init@a669cc5936cc5e1b6a362ec1ff9e410dc570d190 # v2
with:
languages: java
- name: Autobuild
uses: github/codeql-action/autobuild@a669cc5936cc5e1b6a362ec1ff9e410dc570d190 # v2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@a669cc5936cc5e1b6a362ec1ff9e410dc570d190 # v2
with:
category: "/language:java"
@@ -0,0 +1,19 @@
name: Qodana
on:
workflow_dispatch:
pull_request:
push:
branches:
- main
jobs:
qodana:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3
with:
fetch-depth: 0
- name: 'Qodana Scan'
uses: JetBrains/qodana-action@3fdeefa830a7634a5c04f1cd70fcfb69956137a2 # v2022.3.0
env:
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
+6 -3
View File
@@ -13,7 +13,7 @@ jobs:
fetch-depth: 0
- name: Set up JDK
uses: actions/setup-java@de1bb2b0c5634f0fc4438d7aa9944e68f9bf86cc # tag=v3.6.0
uses: actions/setup-java@c3ac5dd0ed8db40fedb61c32fbe677e6b355e94c # v3.8.0
with:
distribution: temurin
java-version: 18
@@ -31,7 +31,9 @@ jobs:
cache-read-only: true
- 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 }}
@@ -45,7 +47,8 @@ jobs:
- name: Update changelog
uses: thomaseizinger/keep-a-changelog-new-release@5bc232893483441c5d6cd6c9dcb1e48abf9a2bae # 1.3.0
with:
tag: ${{ env.VERSION }}
tag: v${{ env.VERSION }}
version: ${{ env.VERSION }}
- name: Commit changes
shell: bash
+40 -3
View File
@@ -7,6 +7,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [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
@@ -88,11 +117,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Initial Play Store release
[Unreleased]: https://github.com/msfjarvis/compose-lobsters/compare/1.10.0...HEAD
[Unreleased]: https://github.com/msfjarvis/compose-lobsters/compare/v1.14.0...HEAD
[1.10.0]: https://github.com/msfjarvis/compose-lobsters/compare/1.9.0...1.10.0
[1.14.0]: https://github.com/msfjarvis/compose-lobsters/compare/v1.13.0...v1.14.0
[1.9.0]: https://github.com/msfjarvis/compose-lobsters/compare/1.8.0...1.9.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
+19
View File
@@ -6,6 +6,9 @@
*/
@file:Suppress("DSL_SCOPE_VIOLATION", "UnstableApiUsage")
import org.gradle.kotlin.dsl.withType
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("dev.msfjarvis.claw.android-application")
id("dev.msfjarvis.claw.rename-artifacts")
@@ -23,6 +26,22 @@ whetstone {
}
}
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions {
freeCompilerArgs +=
listOf(
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +
rootProject.buildDir.absolutePath +
"/compose_metrics/",
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" +
rootProject.buildDir.absolutePath +
"/compose_metrics/",
)
}
}
android {
namespace = "dev.msfjarvis.claw.android"
defaultConfig { applicationId = "dev.msfjarvis.claw.android" }
+13
View File
@@ -149,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>
+12168 -11504
View File
File diff suppressed because it is too large Load Diff
@@ -7,10 +7,17 @@
package dev.msfjarvis.claw.android
import android.app.Application
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
@ContributesAppInjector(generateAppComponent = true)
@@ -25,5 +32,26 @@ class ClawApplication : Application(), ApplicationComponentOwner {
Whetstone.inject(this)
super.onCreate()
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,
)
}
private companion object {
private const val POST_REFRESH_PERIOD = 24L // 24 hours
}
}
@@ -12,18 +12,11 @@ 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 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
@ContributesActivityInjector
@@ -44,23 +37,6 @@ class MainActivity : ComponentActivity() {
setWebUri = { url -> webUri = url },
)
}
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@MainActivity)
.enqueueUniquePeriodicWork(
"updateSavedPosts",
ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE,
postUpdateWorkRequest,
)
}
override fun onProvideAssistContent(outContent: AssistContent?) {
@@ -73,8 +49,4 @@ class MainActivity : ComponentActivity() {
}
}
}
private companion object {
private const val POST_REFRESH_PERIOD = 24L // 24 hours
}
}
@@ -8,22 +8,13 @@ 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 dev.msfjarvis.claw.android.ui.util.HTMLConverterImpl
import dev.msfjarvis.claw.common.comments.HTMLConverter
import io.github.furstenheim.CopyDown
@Module
@ContributesTo(ApplicationScope::class)
object HTMLConverterModule {
@Provides
fun provideHTMLConverter() =
object : HTMLConverter {
private val copydown = CopyDown()
override fun convertHTMLToMarkdown(html: String): String {
return copydown.convert(html)
}
}
interface HTMLConverterModule {
@Binds fun HTMLConverterImpl.bind(): HTMLConverter
}
@@ -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,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)
}
}
@@ -30,6 +30,15 @@ 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) }
@@ -12,15 +12,13 @@ import androidx.work.WorkerParameters
import com.deliveryhero.whetstone.ForScope
import com.deliveryhero.whetstone.worker.ContributesWorker
import com.deliveryhero.whetstone.worker.WorkerScope
import com.slack.eithernet.ApiResult
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 dev.msfjarvis.claw.model.LobstersPostDetails
import javax.inject.Inject
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.supervisorScope
/**
* WorkManager-backed [CoroutineWorker] that gets all the posts from [SavedPostsRepository], fetches
@@ -38,19 +36,13 @@ constructor(
private val lobstersApi: LobstersApi,
) : CoroutineWorker(appContext, workerParams) {
override suspend fun doWork(): Result {
val posts = savedPostsRepository.savedPosts.first()
supervisorScope {
posts
.map { post ->
async {
val details = lobstersApi.getPostDetails(post.shortId)
if (details is ApiResult.Success) {
savedPostsRepository.savePost(details.value.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=11000
versioning-plugin.versionName=1.10.0
versioning-plugin.versionCode=11400
versioning-plugin.versionName=1.14.0
@@ -15,14 +15,10 @@ import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@LargeTest
@Ignore(
"These tests fail saying that ProfileInstaller 1.3.0-alpha02 should be used which we already are"
)
class BaselineProfileBenchmark {
@get:Rule val benchmarkRule = MacrobenchmarkRule()
private lateinit var device: UiDevice
@@ -6,6 +6,7 @@
*/
package dev.msfjarvis.claw.benchmark
import androidx.benchmark.macro.ExperimentalStableBaselineProfilesApi
import androidx.benchmark.macro.junit4.BaselineProfileRule
import androidx.test.filters.MediumTest
import androidx.test.platform.app.InstrumentationRegistry
@@ -14,6 +15,7 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
@ExperimentalStableBaselineProfilesApi
@MediumTest
class BaselineProfileGenerator {
@get:Rule val baselineProfileRule = BaselineProfileRule()
@@ -27,5 +29,10 @@ class BaselineProfileGenerator {
@Test
fun startup() =
baselineProfileRule.collectBaselineProfile(packageName = PACKAGE_NAME) { exploreUI(device) }
baselineProfileRule.collectStableBaselineProfile(
packageName = PACKAGE_NAME,
maxIterations = 5,
) {
exploreUI(device)
}
}
@@ -54,6 +54,6 @@ private fun UiDevice.returnToHottestScreen() {
}
private fun UiDevice.openCommentsScreen() {
findObjects(By.desc(COMMENT_BUTTON_DESC)).first().click()
waitForSubmitterName()
findObjects(By.desc(COMMENT_BUTTON_DESC)).first().click()
}
-1
View File
@@ -1 +0,0 @@
/build
@@ -1,57 +0,0 @@
/*
* Copyright © 2022 Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
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)
}
@@ -27,38 +27,60 @@ afterEvaluate {
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.aps.gradle.KotlinAndroidPlugin"
implementationClass = "dev.msfjarvis.claw.gradle.KotlinAndroidPlugin"
}
register("kotlin-common") {
id = "dev.msfjarvis.claw.kotlin-common"
implementationClass = "dev.msfjarvis.aps.gradle.KotlinCommonPlugin"
implementationClass = "dev.msfjarvis.claw.gradle.KotlinCommonPlugin"
}
register("kotlin-kapt") {
id = "dev.msfjarvis.claw.kotlin-kapt"
implementationClass = "dev.msfjarvis.aps.gradle.KotlinKaptPlugin"
implementationClass = "dev.msfjarvis.claw.gradle.KotlinKaptPlugin"
}
register("kotlin-library") {
id = "dev.msfjarvis.claw.kotlin-library"
implementationClass = "dev.msfjarvis.aps.gradle.KotlinLibraryPlugin"
implementationClass = "dev.msfjarvis.claw.gradle.KotlinLibraryPlugin"
}
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.aps.gradle.SpotlessPlugin"
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.aps.gradle.DependencyUpdatesPlugin"
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.kotlin.serialization)
implementation(libs.build.semver)
implementation(libs.build.spotless)
implementation(libs.build.vcu)
implementation(libs.build.versions)
-4
View File
@@ -39,7 +39,3 @@ dependencyResolutionManagement {
}
versionCatalogs { create("libs") { from(files("../gradle/libs.versions.toml")) } }
}
include("android-plugins")
include("kotlin-plugins")
@@ -6,19 +6,20 @@
*/
@file:Suppress("UnstableApiUsage")
package dev.msfjarvis.aps.gradle
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.BaseExtension
import com.android.build.gradle.internal.dsl.BaseAppModuleExtension
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"
@@ -34,7 +35,7 @@ class AndroidCommonPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.configureSlimTests()
project.pluginManager.apply(AndroidCacheFixPlugin::class)
project.extensions.findByType<BaseExtension>()?.run {
project.extensions.configure<BaseExtension> {
compileSdkVersion(COMPILE_SDK)
defaultConfig {
minSdk = MIN_SDK
@@ -60,7 +61,7 @@ class AndroidCommonPlugin : Plugin<Project> {
unitTests.isReturnDefaultValues = true
}
}
project.extensions.findByType<BaseAppModuleExtension>()?.run { lint.configureLint(project) }
project.extensions.findByType<ApplicationExtension>()?.run { lint.configureLint(project) }
project.extensions.findByType<LibraryExtension>()?.run { lint.configureLint(project) }
}
}
@@ -6,15 +6,15 @@
*/
@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> {
@@ -22,8 +22,8 @@ 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> {
installation.installOptions("--user 0")
dependenciesInfo {
includeInBundle = false
@@ -4,7 +4,7 @@
* 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.github.benmanes.gradle.versions.VersionsPlugin
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
@@ -13,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")
@@ -31,7 +31,7 @@ class DependencyUpdatesPlugin : Plugin<Project> {
}
checkForGradleUpdate = true
}
project.extensions.getByType<VersionCatalogUpdateExtension>().run {
project.extensions.configure<VersionCatalogUpdateExtension> {
keep.keepUnusedLibraries.set(true)
}
}
@@ -4,7 +4,7 @@
* 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 io.gitlab.arturbosch.detekt.DetektPlugin
import io.gitlab.arturbosch.detekt.extensions.DetektExtension
@@ -4,7 +4,7 @@
* 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
@@ -4,7 +4,7 @@
* 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
@@ -4,12 +4,12 @@
* 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
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.getByType
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
@@ -23,7 +23,7 @@ class KotlinKaptPlugin : Plugin<Project> {
apply(Kapt3GradleSubplugin::class)
}
project.afterEvaluate {
project.extensions.getByType<KaptExtension>().run {
project.extensions.configure<KaptExtension> {
javacOptions {
if (hasDaggerCompilerDependency()) {
// https://dagger.dev/dev-guide/compiler-options#fastinit-mode
@@ -4,7 +4,7 @@
* 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
@@ -4,7 +4,7 @@
* 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.android.build.gradle.LibraryPlugin
import org.gradle.api.Plugin
@@ -4,17 +4,17 @@
* 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.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 java.util.Locale
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")
@@ -22,7 +22,7 @@ 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 ->
val taskPrefix = "collect${variant.name.capitalize(Locale.ROOT)}"
project.tasks.register<CollectApksTask>("${taskPrefix}Apks") {
@@ -4,7 +4,7 @@
* 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
@@ -12,7 +12,7 @@ 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> {
@@ -22,7 +22,7 @@ class SpotlessPlugin : Plugin<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")
@@ -4,7 +4,7 @@
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.aps.gradle.artifacts
package dev.msfjarvis.claw.gradle.artifacts
import com.android.build.api.variant.BuiltArtifactsLoader
import java.io.File
@@ -4,7 +4,7 @@
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.aps.gradle.artifacts
package dev.msfjarvis.claw.gradle.artifacts
import java.io.File
import java.nio.file.Files
@@ -4,7 +4,7 @@
* 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
@@ -4,7 +4,7 @@
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.aps.gradle.versioning
package dev.msfjarvis.claw.gradle.versioning
const val VERSIONING_PROP_FILE = "version.properties"
const val VERSIONING_PROP_VERSION_NAME = "versioning-plugin.versionName"
@@ -4,7 +4,7 @@
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.aps.gradle.versioning
package dev.msfjarvis.claw.gradle.versioning
import com.android.build.api.variant.ApplicationAndroidComponentsExtension
import com.android.build.api.variant.VariantOutputConfiguration
@@ -14,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
@@ -46,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)
@@ -4,7 +4,7 @@
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.aps.gradle.versioning
package dev.msfjarvis.claw.gradle.versioning
import com.vdurmont.semver4j.Semver
import org.gradle.api.DefaultTask
+20 -2
View File
@@ -6,9 +6,11 @@
*/
@file:Suppress("DSL_SCOPE_VIOLATION", "UnstableApiUsage")
import org.gradle.kotlin.dsl.withType
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
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)
@@ -18,6 +20,22 @@ anvil { generateDaggerFactories.set(true) }
androidComponents { beforeVariants { it.enableUnitTest = false } }
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions {
freeCompilerArgs +=
listOf(
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +
rootProject.buildDir.absolutePath +
"/compose_metrics/",
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" +
rootProject.buildDir.absolutePath +
"/compose_metrics/",
)
}
}
dependencies {
implementation(platform(libs.androidx.compose.bom))
api(libs.napier)
@@ -6,7 +6,7 @@
*/
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()
@@ -20,20 +20,15 @@ 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.buildAnnotatedString
@@ -46,14 +41,13 @@ 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.LinkMetadata
import dev.msfjarvis.claw.model.LobstersPostDetails
import java.time.Instant
import java.time.temporal.TemporalAccessor
@Composable
fun CommentsHeader(
internal fun CommentsHeader(
postDetails: LobstersPostDetails,
postActions: PostActions,
htmlConverter: HTMLConverter,
@@ -104,7 +98,7 @@ fun CommentsHeader(
}
@Composable
fun PostLink(
private fun PostLink(
linkMetadata: LinkMetadata,
modifier: Modifier = Modifier,
) {
@@ -114,22 +108,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,
@@ -145,18 +130,19 @@ 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 uriHandler = LocalUriHandler.current
val comment = commentNode.comment
Box(
modifier =
modifier
.fillMaxWidth()
.clickable { expanded = !expanded }
.clickable { toggleExpanded(commentNode) }
.background(MaterialTheme.colorScheme.background)
.padding(
start = CommentEntryPadding * comment.indentLevel,
@@ -179,7 +165,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),
@@ -0,0 +1,99 @@
/*
* 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.comments
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.material3.Divider
import dev.msfjarvis.claw.model.Comment
internal data class CommentNode(
val comment: Comment,
var parent: CommentNode? = null,
val children: MutableList<CommentNode> = mutableListOf(),
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>): MutableList<CommentNode> {
val commentNodes = mutableListOf<CommentNode>()
for (i in comments.indices) {
if (comments[i].indentLevel == 1) {
commentNodes.add(CommentNode(comment = comments[i]))
} else {
commentNodes.last().addChild(CommentNode(comment = comments[i]))
}
}
return commentNodes
}
internal fun toggleAllExpanded(commentNode: CommentNode): CommentNode {
commentNode.isExpanded = !commentNode.isExpanded
if (commentNode.children.isNotEmpty()) {
commentNode.children.forEach { toggleAllExpanded(it) }
}
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,
)
}
}
@@ -11,14 +11,13 @@ 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.getValue
import androidx.compose.runtime.produceState
import androidx.compose.runtime.toMutableStateList
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
@@ -39,6 +38,8 @@ private fun CommentsPageInternal(
htmlConverter: HTMLConverter,
modifier: Modifier = Modifier,
) {
val commentNodes = createListNode(details.comments).toMutableStateList()
Surface(color = MaterialTheme.colorScheme.surfaceVariant) {
LazyColumn(modifier = modifier, contentPadding = PaddingValues(bottom = 24.dp)) {
item {
@@ -49,7 +50,7 @@ private fun CommentsPageInternal(
)
}
if (details.comments.isNotEmpty()) {
if (commentNodes.isNotEmpty()) {
item {
Text(
text = "Comments",
@@ -58,12 +59,20 @@ private fun CommentsPageInternal(
)
}
itemsIndexed(details.comments) { index, item ->
if (index != 0) {
Divider()
}
CommentEntry(comment = item, htmlConverter = htmlConverter)
}
nodes(
nodes = commentNodes,
htmlConverter = htmlConverter,
toggleExpanded = { node ->
val newNode = toggleAllExpanded(node)
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(
@@ -6,7 +6,10 @@
*/
package dev.msfjarvis.claw.common.comments
import androidx.compose.runtime.Stable
/** Defines a contract to convert strings of HTML to Markdown. */
@Stable
fun interface HTMLConverter {
fun convertHTMLToMarkdown(html: String): String
}
@@ -115,7 +115,7 @@ fun PostDetails(post: SavedPost, modifier: Modifier = Modifier) {
}
@Composable
fun PostTitle(
internal fun PostTitle(
title: String,
modifier: Modifier = Modifier,
) {
@@ -128,7 +128,7 @@ fun PostTitle(
}
@Composable
fun Submitter(
internal fun Submitter(
text: AnnotatedString,
avatarUrl: String,
contentDescription: String,
@@ -151,7 +151,7 @@ fun Submitter(
}
@Composable
fun SaveButton(
private fun SaveButton(
isSaved: Boolean,
modifier: Modifier = Modifier,
) {
@@ -169,7 +169,7 @@ fun SaveButton(
@Composable
@OptIn(ExperimentalMaterial3Api::class)
fun CommentsButton(
private fun CommentsButton(
commentCount: Int?,
modifier: Modifier = Modifier,
) {
@@ -200,7 +200,7 @@ fun CommentsButton(
}
@Composable
fun TagRow(
internal fun TagRow(
tags: List<String>,
modifier: Modifier = Modifier,
) {
@@ -214,7 +214,7 @@ fun TagRow(
}
@Composable
fun TagText(
private fun TagText(
tag: String,
modifier: Modifier = Modifier,
) {
@@ -6,10 +6,12 @@
*/
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)
@@ -21,6 +21,7 @@ fun LobstersPost.toDbModel(): SavedPost {
submitterName = submitter.username,
submitterAvatarUrl = submitter.avatarUrl,
tags = tags,
description = description,
)
}
@@ -35,5 +36,6 @@ fun LobstersPostDetails.toDbModel(): SavedPost {
submitterName = submitter.username,
submitterAvatarUrl = submitter.avatarUrl,
tags = tags,
description = description,
)
}
@@ -19,7 +19,7 @@ import androidx.compose.runtime.ProvidedValue
import androidx.compose.ui.platform.LocalContext
import com.halilibo.richtext.ui.material3.SetupMaterial3RichText
internal val LightThemeColors =
private val LightThemeColors =
lightColorScheme(
primary = md_theme_light_primary,
onPrimary = md_theme_light_onPrimary,
@@ -48,7 +48,7 @@ internal val LightThemeColors =
inverseSurface = md_theme_light_inverseSurface,
)
internal val DarkThemeColors =
private val DarkThemeColors =
darkColorScheme(
primary = md_theme_dark_primary,
onPrimary = md_theme_dark_onPrimary,
@@ -23,7 +23,7 @@ import dev.msfjarvis.claw.common.theme.LobstersTheme
import dev.msfjarvis.claw.common.ui.preview.ThemePreviews
@Composable
fun ThemedRichText(
internal fun ThemedRichText(
text: String,
modifier: Modifier = Modifier,
) {
@@ -50,7 +50,7 @@ fun ThemedRichText(
@ThemePreviews
@Composable
fun ThemedRichTextPreview() {
internal fun ThemedRichTextPreview() {
val text =
"""
### Heading
@@ -72,6 +72,6 @@ fun NetworkError(
@ThemePreviews
@Composable
fun NetworkErrorPreview() {
internal fun NetworkErrorPreview() {
LobstersTheme { NetworkError(label = "Failed to load posts", error = Throwable("Preview")) }
}
@@ -12,8 +12,8 @@ import androidx.compose.ui.graphics.painter.Painter
import coil.compose.AsyncImage
@Composable
fun NetworkImage(
url: String,
internal fun NetworkImage(
url: String?,
placeholder: Painter,
contentDescription: String,
modifier: Modifier = Modifier,
@@ -11,9 +11,11 @@ import android.content.Context
import android.net.Uri
import android.widget.Toast
import androidx.browser.customtabs.CustomTabsIntent
import androidx.compose.runtime.Stable
import androidx.compose.ui.platform.UriHandler
import io.github.aakira.napier.Napier
@Stable
class UrlLauncher(private val context: Context) : UriHandler {
override fun openUri(uri: String) {
val customTabsIntent =
@@ -90,9 +90,11 @@ private fun UserProfileInternal(
ThemedRichText(
text = user.about,
)
ThemedRichText(
text = "Invited by [${user.invitedBy}](https://lobste.rs/u/${user.invitedBy})",
)
user.invitedBy?.let { invitedBy ->
ThemedRichText(
text = "Invited by [${invitedBy}](https://lobste.rs/u/${user.invitedBy})",
)
}
}
}
}
@@ -0,0 +1,2 @@
ALTER TABLE SavedPost
ADD COLUMN description TEXT NOT NULL DEFAULT "";
@@ -11,7 +11,8 @@ CREATE TABLE IF NOT EXISTS SavedPost(
commentsUrl TEXT NOT NULL,
submitterName TEXT NOT NULL,
submitterAvatarUrl TEXT NOT NULL,
tags TEXT AS List<String> NOT NULL
tags TEXT AS List<String> NOT NULL,
description TEXT NOT NULL DEFAULT ""
);
insertOrReplacePost:
@@ -131,6 +131,7 @@ class SqlDelightQueriesTest : FunSpec() {
submitterName = "test_user_$i",
submitterAvatarUrl = "test_avatar_url",
tags = listOf(),
description = "",
)
posts.add(post)
+5 -5
View File
@@ -13,20 +13,20 @@ kotlin = "1.7.21"
# @pin Needs to be aligned with Retrofit
okhttp = "3.14.9"
retrofit = "2.9.0"
richtext = "0.15.0"
richtext = "0.16.0"
serialization = "1.4.1"
sqldelight = "2.0.0-alpha04"
whetstone = "0.5.0-SNAPSHOT"
workmanager = "2.8.0-beta02"
workmanager = "2.8.0-rc01"
[libraries]
accompanist-flowlayout = { module = "com.google.accompanist:accompanist-flowlayout", version.ref = "accompanist" }
accompanist-sysuicontroller = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanist" }
androidx-activity-compose = "androidx.activity:activity-compose:1.7.0-alpha02"
androidx-benchmark-macro-junit4 = "androidx.benchmark:benchmark-macro-junit4:1.2.0-alpha07"
androidx-browser = "androidx.browser:browser:1.5.0-alpha01"
androidx-benchmark-macro-junit4 = "androidx.benchmark:benchmark-macro-junit4:1.2.0-alpha08"
androidx-browser = "androidx.browser:browser:1.5.0-alpha02"
androidx-compose-animation = { module = "androidx.compose.animation:animation" }
androidx-compose-bom = "androidx.compose:compose-bom:2022.11.00"
androidx-compose-bom = "androidx.compose:compose-bom:2022.12.00"
androidx-compose-foundation = { module = "androidx.compose.foundation:foundation" }
androidx-compose-material = { module = "androidx.compose.material:material" }
androidx-compose-material3 = { module = "androidx.compose.material3:material3" }
@@ -15,7 +15,7 @@ import kotlinx.serialization.Serializable
class User(
val username: String,
val about: String,
@SerialName("invited_by_user") val invitedBy: String,
@SerialName("invited_by_user") val invitedBy: String?,
@SerialName("avatar_url") val avatarUrl: String,
@SerialName("created_at") val createdAt: String,
)
+4
View File
@@ -0,0 +1,4 @@
version: "1.0"
linter: jetbrains/qodana-jvm-community:2022.3
exclude:
- name: KotlinAnnotator
+31
View File
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
set -euxo pipefail
TEMP_DIR="$(mktemp -d)"
BIN_DIR=""
AGE_VERSION="v1.0.0"
AGE_FILE=""
case "$(uname)" in
Linux)
AGE_FILE="age-${AGE_VERSION}-linux-amd64.tar.gz"
BIN_DIR="${HOME}/.local/bin"
;;
Darwin)
AGE_FILE="age-${AGE_VERSION}-darwin-amd64.tar.gz"
BIN_DIR="${HOME}/bin"
;;
*) echo "Unsupported system: $(uname)"; exit 1 ;;
esac
pushd "${TEMP_DIR}"
curl -L --silent --show-error --retry 3 --fail -o age.tar.gz "https://github.com/FiloSottile/age/releases/download/v1.0.0/${AGE_FILE:?}"
tar xvf age.tar.gz
rm age/LICENSE
mkdir -p "${BIN_DIR}"
mv -v age/age "${BIN_DIR}" && chmod +x "${BIN_DIR}/age"
mv -v age/age-keygen "${BIN_DIR}" && chmod +x "${BIN_DIR}/age-keygen"
popd
+17 -11
View File
@@ -2,16 +2,22 @@
set -euo pipefail
ENCRYPT_KEY="${1:-}"
ENCRYPT_KEY="${1}"
TEMP_KEY="$(mktemp)"
declare -A SECRETS
SECRETS[secrets/keystore.cipher]=keystore.jks
SECRETS[secrets/props.cipher]=keystore.properties
trap "rm -rf ${TEMP_KEY} 2>/dev/null" INT TERM EXIT
if [[ -n "$ENCRYPT_KEY" ]]; then
for src in "${!SECRETS[@]}"; do
openssl enc -aes-256-cbc -md sha256 -pbkdf2 -d -in "${src}" -out "${SECRETS[${src}]}" -k "${ENCRYPT_KEY}"
done
else
echo "Usage: ./signing-setup.sh <encryption key>"
fi
echo "${ENCRYPT_KEY:?}" > "${TEMP_KEY}"
function decrypt() {
if ! command -v age 1>/dev/null; then
echo "age not installed"
exit 1
fi
SRC="${1}"
DST="${2}"
age --decrypt -i "${TEMP_KEY}" -o "${DST:?}" "${SRC:?}"
}
decrypt secrets/keystore.cipher keystore.jks
decrypt secrets/props.cipher keystore.properties
Binary file not shown.
Binary file not shown.
-7
View File
@@ -125,13 +125,6 @@ dependencyResolutionManagement {
includeGroup("org.jetbrains.skiko")
}
}
exclusiveContent {
forRepository { gradlePluginPortal() }
filter {
includeModule("org.gradle.android.cache-fix", "org.gradle.android.cache-fix.gradle.plugin")
includeModule("gradle.plugin.org.gradle.android", "android-cache-fix-gradle-plugin")
}
}
exclusiveContent {
forRepository { maven("https://oss.sonatype.org/content/repositories/snapshots/") }
filter { includeGroup("dev.msfjarvis.whetstone") }