diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 916ebb792..8e11bb23e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -38,7 +38,7 @@ jobs:
run: ./gradlew app:lintOrangeDebug
- name: Test
- run: ./gradlew app:testOrangeDebugUnitTest
+ run: ./gradlew app:testOrangeDebugUnitTest checks:test
- name: Build
run: ./gradlew app:buildOrangeDebug
diff --git a/app/build.gradle b/app/build.gradle
index a4842298a..3e7c1d90a 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -217,4 +217,6 @@ dependencies {
androidTestImplementation libs.androidx.test.junit
androidTestImplementation libs.hilt.android.testing
androidTestImplementation libs.androidx.test.core.ktx
+
+ lintChecks project(":checks")
}
diff --git a/app/src/main/res/layout/activity_account.xml b/app/src/main/res/layout/activity_account.xml
index 4f7964a9a..2229ffc78 100644
--- a/app/src/main/res/layout/activity_account.xml
+++ b/app/src/main/res/layout/activity_account.xml
@@ -427,7 +427,7 @@
-
-
-
+
-
-
@@ -49,4 +49,4 @@
-
\ No newline at end of file
+
diff --git a/app/src/main/res/layout/activity_search.xml b/app/src/main/res/layout/activity_search.xml
index 2607d0802..679fb7880 100644
--- a/app/src/main/res/layout/activity_search.xml
+++ b/app/src/main/res/layout/activity_search.xml
@@ -13,7 +13,7 @@
app:liftOnScrollTargetViewId="@id/pages"
app:layout_collapseMode="pin">
-
-
-
\ No newline at end of file
+
diff --git a/checks/.gitignore b/checks/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/checks/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/checks/build.gradle b/checks/build.gradle
new file mode 100644
index 000000000..b8648c92b
--- /dev/null
+++ b/checks/build.gradle
@@ -0,0 +1,36 @@
+plugins {
+ id "java-library"
+ id "kotlin"
+ id "com.android.lint"
+}
+
+lintOptions {
+ htmlReport(true)
+ htmlOutput(file("lint-report.html"))
+ textReport(true)
+ absolutePaths(false)
+ ignoreTestSources(true)
+}
+
+jar {
+ manifest {
+ attributes("Lint-Registry-v2": "app.pachli.lint.checks.LintRegistry")
+ }
+}
+
+dependencies {
+ compileOnly("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.61")
+
+ // Derived from the AGP version. If the AGP version is X.Y.Z the version
+ // here must by X+23.Y.Z.
+ // See https://github.com/googlesamples/android-custom-lint-rules#lint-version
+ def lintVersion = "31.1.2" // = AGP 8.1.2
+
+ // For a description of the below dependencies, see the main project README
+ compileOnly("com.android.tools.lint:lint-api:$lintVersion")
+ compileOnly("com.android.tools.lint:lint-checks:$lintVersion")
+ testImplementation("com.android.tools.lint:lint:$lintVersion")
+ testImplementation("com.android.tools.lint:lint-tests:$lintVersion")
+
+ testImplementation("junit:junit:4.13.2")
+}
diff --git a/checks/src/main/java/app/pachli/lint/checks/AndroidxToolbarDetector.kt b/checks/src/main/java/app/pachli/lint/checks/AndroidxToolbarDetector.kt
new file mode 100644
index 000000000..d5cbce272
--- /dev/null
+++ b/checks/src/main/java/app/pachli/lint/checks/AndroidxToolbarDetector.kt
@@ -0,0 +1,58 @@
+package app.pachli.lint.checks
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.LintFix
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.XmlContext
+import com.android.tools.lint.detector.api.XmlScanner
+import org.w3c.dom.Element
+
+class AndroidxToolbarDetector : Detector(), XmlScanner {
+ override fun getApplicableElements(): Collection {
+ return listOf(ANDROIDX_TOOLBAR)
+ }
+
+ override fun visitElement(context: XmlContext, element: Element) {
+ val quickFixData = LintFix.create()
+ .name("Replace with `com.google.android.material.appbar.MaterialToolbar`")
+ .replace()
+ .text(element.localName)
+ .with("com.google.android.material.appbar.MaterialToolbar")
+ .independent(true)
+ .build()
+
+ context.report(
+ issue = ISSUE,
+ scope = element,
+ location = context.getElementLocation(element),
+ message = "Use `com.google.android.material.appbar.MaterialToolbar` instead of `androidx.appcompat.widget.Toolbar`",
+ quickfixData = quickFixData,
+ )
+ }
+
+ companion object {
+ private const val ANDROIDX_TOOLBAR = "androidx.appcompat.widget.Toolbar"
+
+ @JvmField
+ val ISSUE: Issue = Issue.create(
+ id = "AndroidxToolbarDetector",
+ briefDescription = "Don't use `androidx.appcompat.widget.Toolbar` in this project",
+ explanation = """
+ Use `com.google.android.material.appbar.MaterialToolbar` instead of
+ `androidx.appcompat.widget.Toolbar` to ensure it works as expected with
+ other Material components. See https://developer.android.com/reference/com/google/android/material/appbar/MaterialToolbar
+ """,
+ category = Category.CORRECTNESS,
+ priority = 6,
+ severity = Severity.WARNING,
+ implementation = Implementation(
+ AndroidxToolbarDetector::class.java,
+ Scope.RESOURCE_FILE_SCOPE,
+ ),
+ )
+ }
+}
diff --git a/checks/src/main/java/app/pachli/lint/checks/LintRegistry.kt b/checks/src/main/java/app/pachli/lint/checks/LintRegistry.kt
new file mode 100644
index 000000000..c0cfde5fc
--- /dev/null
+++ b/checks/src/main/java/app/pachli/lint/checks/LintRegistry.kt
@@ -0,0 +1,14 @@
+package app.pachli.lint.checks
+
+import com.android.tools.lint.client.api.IssueRegistry
+import com.android.tools.lint.detector.api.CURRENT_API
+import com.android.tools.lint.detector.api.Issue
+
+@Suppress("UnstableApiUsage")
+class LintRegistry : IssueRegistry() {
+ override val issues: List
+ get() = listOf(AndroidxToolbarDetector.ISSUE)
+
+ override val api: Int
+ get() = CURRENT_API
+}
diff --git a/checks/src/test/java/app/pachli/lint/checks/AndroidxToolbarDetectorTest.kt b/checks/src/test/java/app/pachli/lint/checks/AndroidxToolbarDetectorTest.kt
new file mode 100644
index 000000000..ee540aba0
--- /dev/null
+++ b/checks/src/test/java/app/pachli/lint/checks/AndroidxToolbarDetectorTest.kt
@@ -0,0 +1,89 @@
+package app.pachli.lint.checks
+
+import com.android.tools.lint.checks.infrastructure.TestFiles.xml
+import com.android.tools.lint.checks.infrastructure.TestLintTask.lint
+import org.junit.Test
+
+class AndroidxToolbarDetectorTest {
+ @Test
+ fun testError() {
+ lint().files(
+ xml(
+ "res/layout/test.xml",
+ """
+
+
+
+
+
+
+
+
+
+ """,
+ ).indented(),
+ ).issues(AndroidxToolbarDetector.ISSUE).allowMissingSdk().run().expectWarningCount(1)
+ .expect(
+ """res/layout/test.xml:16: Warning: Use com.google.android.material.appbar.MaterialToolbar instead of androidx.appcompat.widget.Toolbar [AndroidxToolbarDetector]
+
+
+
+
+
+
+
+
+
+
+ """,
+ ).indented(),
+ ).issues(AndroidxToolbarDetector.ISSUE)
+ .allowMissingSdk()
+ .run()
+ .expectWarningCount(0)
+ }
+}
diff --git a/settings.gradle b/settings.gradle
index fe112d554..4dd3ebaac 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -20,3 +20,4 @@ enableFeaturePreview("STABLE_CONFIGURATION_CACHE")
include ':app'
include ':tools:mklanguages'
+include ':checks'