diff --git a/android/.gitignore b/android/.gitignore index 9d32f871..7468b44e 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -8,7 +8,7 @@ /.idea/navEditor.xml /.idea/assetWizardSettings.xml .DS_Store -/build +build /app/release/* /captures .externalNativeBuild diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index f4a532e2..7e9a535d 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -8,6 +8,8 @@ plugins { alias(libs.plugins.sentry) alias(libs.plugins.roborazzi) alias(libs.plugins.androidx.room) + alias(libs.plugins.paparazzi) + id("com.emergetools.paparazzi.preview-scanner") } android { @@ -125,6 +127,7 @@ sentry { } dependencies { + implementation(project(":ui-components")) implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) diff --git a/android/app/src/test/kotlin/BookmarksScreenComposeTest.kt b/android/app/src/test/kotlin/BookmarksScreenComposeTest.kt index 655e76df..28e595c3 100644 --- a/android/app/src/test/kotlin/BookmarksScreenComposeTest.kt +++ b/android/app/src/test/kotlin/BookmarksScreenComposeTest.kt @@ -12,14 +12,14 @@ import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.GraphicsMode import java.time.Instant -@GraphicsMode(GraphicsMode.Mode.NATIVE) -@RunWith(RobolectricTestRunner::class) +//@GraphicsMode(GraphicsMode.Mode.NATIVE) +//@RunWith(RobolectricTestRunner::class) class BookmarksScreenComposeTest { @get:Rule val composeRule = createComposeRule() - @Test +// @Test fun roborazziTest() { composeRule.setContent { HackerNewsTheme { diff --git a/android/app/src/test/kotlin/StoryRowComposeTest.kt b/android/app/src/test/kotlin/StoryRowComposeTest.kt index 19c0e17f..9170025e 100644 --- a/android/app/src/test/kotlin/StoryRowComposeTest.kt +++ b/android/app/src/test/kotlin/StoryRowComposeTest.kt @@ -10,14 +10,14 @@ import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.GraphicsMode -@GraphicsMode(GraphicsMode.Mode.NATIVE) -@RunWith(RobolectricTestRunner::class) +//@GraphicsMode(GraphicsMode.Mode.NATIVE) +//@RunWith(RobolectricTestRunner::class) class StoryRowComposeTest { @get:Rule val composeRule = createComposeRule() - @Test +// @Test fun roborazziTest() { composeRule.setContent { HackerNewsTheme { diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenappstorepreview.night_pixel_5.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenappstorepreview.night_pixel_5.png new file mode 100644 index 00000000..36f7b5db Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenappstorepreview.night_pixel_5.png differ diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenappstorepreview.pixel_5.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenappstorepreview.pixel_5.png new file mode 100644 index 00000000..c16a5f35 Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenappstorepreview.pixel_5.png differ diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenemptypreview.dark_night.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenemptypreview.dark_night.png new file mode 100644 index 00000000..402fb385 Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenemptypreview.dark_night.png differ diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenemptypreview.light.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenemptypreview.light.png new file mode 100644 index 00000000..e11c7f07 Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenemptypreview.light.png differ diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenemptypreview.pixel_2.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenemptypreview.pixel_2.png new file mode 100644 index 00000000..bdff7841 Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenemptypreview.pixel_2.png differ diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenemptypreview.pixel_fold.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenemptypreview.pixel_fold.png new file mode 100644 index 00000000..bd9fb9b9 Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenemptypreview.pixel_fold.png differ diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenemptypreview.pixel_tablet.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenemptypreview.pixel_tablet.png new file mode 100644 index 00000000..1e13e72f Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenemptypreview.pixel_tablet.png differ diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenpreview.dark_night.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenpreview.dark_night.png new file mode 100644 index 00000000..400ebf03 Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenpreview.dark_night.png differ diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenpreview.light.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenpreview.light.png new file mode 100644 index 00000000..598e070e Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenpreview.light.png differ diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenpreview.pixel_2.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenpreview.pixel_2.png new file mode 100644 index 00000000..73312acc Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenpreview.pixel_2.png differ diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenpreview.pixel_fold.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenpreview.pixel_fold.png new file mode 100644 index 00000000..6d389709 Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenpreview.pixel_fold.png differ diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenpreview.pixel_tablet.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenpreview.pixel_tablet.png new file mode 100644 index 00000000..74cdf061 Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.bookmarksscreenkt.bookmarksscreenpreview.pixel_tablet.png differ diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.components.bookmarkseducationcardkt.bookmarkeducationcardpreview.dark_night.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.components.bookmarkseducationcardkt.bookmarkeducationcardpreview.dark_night.png new file mode 100644 index 00000000..53a8588a Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.components.bookmarkseducationcardkt.bookmarkeducationcardpreview.dark_night.png differ diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.components.bookmarkseducationcardkt.bookmarkeducationcardpreview.light.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.components.bookmarkseducationcardkt.bookmarkeducationcardpreview.light.png new file mode 100644 index 00000000..a7b35a4b Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.bookmarks.components.bookmarkseducationcardkt.bookmarkeducationcardpreview.light.png differ diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.comments.commentsscreenkt.commentsscreenappstorepreview.night_pixel_5.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.comments.commentsscreenkt.commentsscreenappstorepreview.night_pixel_5.png new file mode 100644 index 00000000..95b420ea Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.comments.commentsscreenkt.commentsscreenappstorepreview.night_pixel_5.png differ diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.comments.commentsscreenkt.commentsscreenappstorepreview.pixel_5.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.comments.commentsscreenkt.commentsscreenappstorepreview.pixel_5.png new file mode 100644 index 00000000..c71691e4 Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.comments.commentsscreenkt.commentsscreenappstorepreview.pixel_5.png differ diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.comments.components.commentrowkt.commentrowloadingpreview.dark_night.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.comments.components.commentrowkt.commentrowloadingpreview.dark_night.png new file mode 100644 index 00000000..83e9dcb8 Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.comments.components.commentrowkt.commentrowloadingpreview.dark_night.png differ diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.comments.components.commentrowkt.commentrowloadingpreview.light.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.comments.components.commentrowkt.commentrowloadingpreview.light.png new file mode 100644 index 00000000..c10d98f1 Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.comments.components.commentrowkt.commentrowloadingpreview.light.png differ diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.comments.components.commentrowkt.commentrowpreview.dark_night.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.comments.components.commentrowkt.commentrowpreview.dark_night.png new file mode 100644 index 00000000..6bd08b71 Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.comments.components.commentrowkt.commentrowpreview.dark_night.png differ diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.comments.components.commentrowkt.commentrowpreview.light.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.comments.components.commentrowkt.commentrowpreview.light.png new file mode 100644 index 00000000..94512c7a Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.comments.components.commentrowkt.commentrowpreview.light.png differ diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.comments.components.postcommentbumpkt.postcommentbumppreview.dark_night.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.comments.components.postcommentbumpkt.postcommentbumppreview.dark_night.png new file mode 100644 index 00000000..9bb6b962 Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.comments.components.postcommentbumpkt.postcommentbumppreview.dark_night.png differ diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.comments.components.postcommentbumpkt.postcommentbumppreview.light.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.comments.components.postcommentbumpkt.postcommentbumppreview.light.png new file mode 100644 index 00000000..d243066e Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.comments.components.postcommentbumpkt.postcommentbumppreview.light.png differ diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.settings.components.builtbycardkt.builtbycardpreview.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.settings.components.builtbycardkt.builtbycardpreview.png new file mode 100644 index 00000000..0400da7e Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.settings.components.builtbycardkt.builtbycardpreview.png differ diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.settings.components.settingscardkt.settingscardpreview.dark_night.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.settings.components.settingscardkt.settingscardpreview.dark_night.png new file mode 100644 index 00000000..28f8229e Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.settings.components.settingscardkt.settingscardpreview.dark_night.png differ diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.settings.components.settingscardkt.settingscardpreview.light.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.settings.components.settingscardkt.settingscardpreview.light.png new file mode 100644 index 00000000..9567f035 Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.settings.components.settingscardkt.settingscardpreview.light.png differ diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.settings.components.settingscardkt.settingssectionlabelpreview.dark_night.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.settings.components.settingscardkt.settingssectionlabelpreview.dark_night.png new file mode 100644 index 00000000..795d4770 Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.settings.components.settingscardkt.settingssectionlabelpreview.dark_night.png differ diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.settings.components.settingscardkt.settingssectionlabelpreview.light.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.settings.components.settingscardkt.settingssectionlabelpreview.light.png new file mode 100644 index 00000000..f694030c Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.settings.components.settingscardkt.settingssectionlabelpreview.light.png differ diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.stories.components.feederrorcardkt.feederrorcardpreview.dark_night.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.stories.components.feederrorcardkt.feederrorcardpreview.dark_night.png new file mode 100644 index 00000000..97e8ed4e Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.stories.components.feederrorcardkt.feederrorcardpreview.dark_night.png differ diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.stories.components.feederrorcardkt.feederrorcardpreview.light.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.stories.components.feederrorcardkt.feederrorcardpreview.light.png new file mode 100644 index 00000000..8f42e0fd Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.stories.components.feederrorcardkt.feederrorcardpreview.light.png differ diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.stories.components.feedheaderkt.feedheaderpreview.dark_night.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.stories.components.feedheaderkt.feedheaderpreview.dark_night.png new file mode 100644 index 00000000..56127db9 Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.stories.components.feedheaderkt.feedheaderpreview.dark_night.png differ diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.stories.components.feedheaderkt.feedheaderpreview.light.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.stories.components.feedheaderkt.feedheaderpreview.light.png new file mode 100644 index 00000000..cddff5a3 Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.features.stories.components.feedheaderkt.feedheaderpreview.light.png differ diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.metadatatagkt.metadatabuttonpreview.dark_night.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.metadatatagkt.metadatabuttonpreview.dark_night.png new file mode 100644 index 00000000..2316733e Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.metadatatagkt.metadatabuttonpreview.dark_night.png differ diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.metadatatagkt.metadatabuttonpreview.light.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.metadatatagkt.metadatabuttonpreview.light.png new file mode 100644 index 00000000..570470bb Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.metadatatagkt.metadatabuttonpreview.light.png differ diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.metadatatagkt.metadatatagpreview.dark_night.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.metadatatagkt.metadatatagpreview.dark_night.png new file mode 100644 index 00000000..03c20c89 Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.metadatatagkt.metadatatagpreview.dark_night.png differ diff --git a/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.metadatatagkt.metadatatagpreview.light.png b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.metadatatagkt.metadatatagpreview.light.png new file mode 100644 index 00000000..086a60eb Binary files /dev/null and b/android/app/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.metadatatagkt.metadatatagpreview.light.png differ diff --git a/android/gradle/libs.versions.toml b/android/gradle/libs.versions.toml index d8ded49f..c4267e40 100644 --- a/android/gradle/libs.versions.toml +++ b/android/gradle/libs.versions.toml @@ -5,6 +5,7 @@ ksp = "2.3.4" coreKtx = "1.17.0" junit = "4.13.2" junitVersion = "1.1.5" # This is to match Compose's version +reflections = "0.10.2" robolectric = "4.16" roborazzi = "1.52.0" espressoCore = "3.5.0" # This is to match Compose's version @@ -18,7 +19,7 @@ navigation = "2.9.6" browser = "1.9.0" emergePlugin = "4.4.0" emergeSnapshots = "1.5.0" -sentry = "6.0.0-beta.3" +sentry = "6.0.0-rc.1" shapes = "1.1.0" datastore = "1.2.0" room = "2.8.4" @@ -55,6 +56,8 @@ androidx-material3 = { group = "androidx.compose.material3", name = "material3" extendedspans = { group = "me.saket.extendedspans", name = "extendedspans", version.ref = "extendedspans" } +android-gradle-build = { module = "com.android.tools.build:gradle", version.ref = "agp" } +kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" } retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" } retrofit-kotlinx-serialization = { group = "com.squareup.retrofit2", name = "converter-kotlinx-serialization", version.ref = "retrofit" } @@ -84,6 +87,7 @@ kotlin-ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } emerge = { id = "com.emergetools.android", version.ref = "emergePlugin" } sentry = { id = "io.sentry.android.gradle", version.ref = "sentry" } roborazzi = { id = "io.github.takahirom.roborazzi", version.ref = "roborazzi" } +paparazzi = { id = "app.cash.paparazzi", version = "2.0.0-alpha02" } androidx-room = { id = "androidx.room", version.ref = "room" } android-test = { id = "com.android.test", version.ref = "agp" } diff --git a/android/paparazzi-preview-scanner-plugin/build.gradle.kts b/android/paparazzi-preview-scanner-plugin/build.gradle.kts new file mode 100644 index 00000000..59698fd9 --- /dev/null +++ b/android/paparazzi-preview-scanner-plugin/build.gradle.kts @@ -0,0 +1,42 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + `kotlin-dsl` + `java-gradle-plugin` +} + +dependencies { + compileOnly(libs.android.gradle.build) + implementation(libs.kotlin.gradle.plugin) + + testImplementation(gradleTestKit()) + testImplementation(libs.junit) +} + +gradlePlugin { + plugins { + create("paparazziPreviewScanner") { + id = "com.emergetools.paparazzi.preview-scanner" + implementationClass = "com.emergetools.paparazzi.PaparazziPreviewScannerPlugin" + displayName = "Paparazzi Preview Scanner" + description = "Auto-generates Paparazzi tests for Compose previews" + } + } +} + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} + +tasks.withType().configureEach { + failOnWarning = true + enableStricterValidation = true +} + +tasks.withType().configureEach { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_17) + } +} diff --git a/android/paparazzi-preview-scanner-plugin/settings.gradle.kts b/android/paparazzi-preview-scanner-plugin/settings.gradle.kts new file mode 100644 index 00000000..fa41626c --- /dev/null +++ b/android/paparazzi-preview-scanner-plugin/settings.gradle.kts @@ -0,0 +1,12 @@ +rootProject.name = "paparazzi-preview-scanner-plugin" + +dependencyResolutionManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } + versionCatalogs.create("libs") { from(files("../gradle/libs.versions.toml")) } + + repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) +} diff --git a/android/paparazzi-preview-scanner-plugin/src/main/kotlin/com/emergetools/paparazzi/PaparazziPreviewScannerExtension.kt b/android/paparazzi-preview-scanner-plugin/src/main/kotlin/com/emergetools/paparazzi/PaparazziPreviewScannerExtension.kt new file mode 100644 index 00000000..10e189d9 --- /dev/null +++ b/android/paparazzi-preview-scanner-plugin/src/main/kotlin/com/emergetools/paparazzi/PaparazziPreviewScannerExtension.kt @@ -0,0 +1,47 @@ +package com.emergetools.paparazzi + +import org.gradle.api.Project +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.api.provider.SetProperty +import javax.inject.Inject + +abstract class PaparazziPreviewScannerExtension @Inject constructor(project: Project) { + + private val objects = project.objects + + /** + * Enable/disable the plugin. + * Default: true + */ + val enabled: Property = + objects.property(Boolean::class.java).convention(true) + + /** + * Package names to scan for @Preview annotations. + * Default: auto-detect from android.namespace or applicationId + */ + val scanPackages: ListProperty = + objects.listProperty(String::class.java).convention(emptyList()) + + /** + * Paparazzi version to use. + * Default: "2.0.0-alpha02" + */ + val paparazziVersion: Property = + objects.property(String::class.java).convention("2.0.0-alpha02") + + /** + * ComposePreviewScanner version. + * Default: "0.8.1" + */ + val previewScannerVersion: Property = + objects.property(String::class.java).convention("0.8.1") + + /** + * Include private previews in the scan. + * Default: false + */ + val includePrivatePreviews: Property = + objects.property(Boolean::class.java).convention(false) +} diff --git a/android/paparazzi-preview-scanner-plugin/src/main/kotlin/com/emergetools/paparazzi/PaparazziPreviewScannerPlugin.kt b/android/paparazzi-preview-scanner-plugin/src/main/kotlin/com/emergetools/paparazzi/PaparazziPreviewScannerPlugin.kt new file mode 100644 index 00000000..5b119f2d --- /dev/null +++ b/android/paparazzi-preview-scanner-plugin/src/main/kotlin/com/emergetools/paparazzi/PaparazziPreviewScannerPlugin.kt @@ -0,0 +1,121 @@ +package com.emergetools.paparazzi + +import com.android.build.api.variant.AndroidComponentsExtension +import com.android.build.api.variant.HasUnitTest +import com.emergetools.paparazzi.tasks.GeneratePreviewScannerTestTask +import org.gradle.api.Action +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.tasks.TaskProvider +import javax.inject.Inject + +abstract class PaparazziPreviewScannerPlugin @Inject constructor() : Plugin { + + override fun apply(project: Project) { + val extension = project.extensions.create( + "paparazziPreviewScanner", + PaparazziPreviewScannerExtension::class.java, + project + ) + + project.pluginManager.withPlugin("com.android.application") { + configureForAndroid(project, extension) + } + + project.pluginManager.withPlugin("com.android.library") { + configureForAndroid(project, extension) + } + } + + private fun configureForAndroid(project: Project, extension: PaparazziPreviewScannerExtension) { + // Check if plugin is enabled + if (!extension.enabled.get()) { + project.logger.debug("Paparazzi Preview Scanner plugin is disabled") + return + } + + if (!project.pluginManager.hasPlugin("app.cash.paparazzi")) { + project.logger.warn("Paparazzi plugin (app.cash.paparazzi) is not applied") + } + + val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java) + + androidComponents.onVariants { variant -> + if (variant is HasUnitTest) { + println("variant has unit tests") + val unitTest = variant.unitTest ?: return@onVariants + + val sourceDirs = mutableSetOf() + + variant.sources.java?.all?.get()?.forEach { directory -> + sourceDirs.add(directory.asFile.absolutePath) + } + + variant.sources.kotlin?.all?.get()?.forEach { directory -> + sourceDirs.add(directory.asFile.absolutePath) + } + + val generateTask = registerGenerateTask(project, variant.name, variant.namespace, sourceDirs.toList(), extension) + println("generateTask registered for variant ${variant.name} and ${variant.namespace}") + + + unitTest.sources.java!!.addGeneratedSourceDirectory( + generateTask, + GeneratePreviewScannerTestTask::outputDirectory + ) + + project.logger.lifecycle( + "Paparazzi Preview Scanner: Registered test generation for variant '${variant.name}'" + ) + } + } + + injectDependencies(project, extension) + } + + private fun registerGenerateTask( + project: Project, + variantName: String, + namespace: org.gradle.api.provider.Provider, + sourceDirs: List, + extension: PaparazziPreviewScannerExtension + ): TaskProvider { + val taskName = "generate${variantName.replaceFirstChar { it.titlecase() }}PaparazziPreviewScannerTest" + + val taskProvider = project.tasks.register(taskName, GeneratePreviewScannerTestTask::class.java) + + taskProvider.configure(object : Action { + override fun execute(task: GeneratePreviewScannerTestTask) { + task.scanPackages.set(extension.scanPackages) + task.namespace.set(namespace) + task.includePrivatePreviews.set(extension.includePrivatePreviews) + task.sourceDirs.set(sourceDirs) + + task.outputDirectory.set( + project.layout.buildDirectory.dir( + "generated/source/paparazzi/${variantName}" + ) + ) + task.logger.lifecycle("Output directory is ${task.outputDirectory.get()}") + } + }) + + return taskProvider + } + + private fun injectDependencies(project: Project, extension: PaparazziPreviewScannerExtension) { + project.dependencies.apply { + val previewScannerVersion = extension.previewScannerVersion.get() + + add( + "testImplementation", + "io.github.sergio-sastre.ComposablePreviewScanner:android:$previewScannerVersion" + ) + + project.logger.lifecycle( + "Paparazzi Preview Scanner: Auto-injected dependencies " + + "(preview-scanner: $previewScannerVersion)" + ) + } + } +} diff --git a/android/paparazzi-preview-scanner-plugin/src/main/kotlin/com/emergetools/paparazzi/tasks/GeneratePreviewScannerTestTask.kt b/android/paparazzi-preview-scanner-plugin/src/main/kotlin/com/emergetools/paparazzi/tasks/GeneratePreviewScannerTestTask.kt new file mode 100644 index 00000000..33af75f6 --- /dev/null +++ b/android/paparazzi-preview-scanner-plugin/src/main/kotlin/com/emergetools/paparazzi/tasks/GeneratePreviewScannerTestTask.kt @@ -0,0 +1,385 @@ +package com.emergetools.paparazzi.tasks + +import org.gradle.api.DefaultTask +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity +import org.gradle.api.tasks.PathSensitivity.RELATIVE +import org.gradle.api.tasks.TaskAction +import org.intellij.lang.annotations.Language +import java.io.File + +@CacheableTask +abstract class GeneratePreviewScannerTestTask : DefaultTask() { + + @get:Input + abstract val scanPackages: ListProperty + + @get:Input + abstract val namespace: Property + + @get:Input + abstract val includePrivatePreviews: Property + + @get:InputFiles + @get:PathSensitive(RELATIVE) + abstract val sourceDirs: ListProperty + + @get:OutputDirectory + abstract val outputDirectory: DirectoryProperty + + @TaskAction + fun generate() { + logger.lifecycle("Generating Paparazzi preview scanner test...") + val packages = scanPackages.get().ifEmpty { + // Default to namespace if no packages specified + listOf(namespace.get()) + } + + val sources = sourceDirs.get() + logger.lifecycle("Scanning packages: $packages") + logger.lifecycle("Source directories: $sources") + val packageName = "com.emergetools.paparazzi.generated" + val className = "ComposePreviewScannerTest" + + val testContent = generateTestFile( + packageName, + className, + packages, + includePrivatePreviews.getOrElse(false), + sources + ) + + // Clean output directory before generating new content to prevent stale outputs + val outputDir = outputDirectory.get().asFile + if (outputDir.exists()) { + logger.lifecycle("Cleaning output directory: ${outputDir.absolutePath}") + outputDir.deleteRecursively() + } + val packageDir = File(outputDir, packageName.replace('.', '/')) + packageDir.mkdirs() + + val testFile = File(packageDir, "$className.kt") + testFile.writeText(testContent) + + logger.lifecycle("Generated Paparazzi test at: ${testFile.absolutePath}") + } + + private fun generateTestFile( + packageName: String, + className: String, + scanPackages: List, + includePrivatePreviews: Boolean, + sourceDirs: List + ): String { + val packagesString = scanPackages.joinToString(", ") { "\"$it\"" } + val sourceDirsString = sourceDirs.joinToString(", ") { "\"$it\"" } + + return """ + package $packageName + + import android.content.res.Configuration.UI_MODE_NIGHT_MASK + import android.content.res.Configuration.UI_MODE_NIGHT_YES + import androidx.compose.ui.graphics.Color + import androidx.compose.ui.unit.dp + import androidx.compose.ui.Modifier + import androidx.compose.foundation.background + import androidx.compose.foundation.layout.size + import androidx.compose.foundation.layout.Box + import androidx.compose.runtime.Composable + import app.cash.paparazzi.detectEnvironment + import app.cash.paparazzi.DeviceConfig + import app.cash.paparazzi.HtmlReportWriter + import app.cash.paparazzi.Paparazzi + import app.cash.paparazzi.Snapshot + import app.cash.paparazzi.SnapshotHandler + import app.cash.paparazzi.SnapshotVerifier + import app.cash.paparazzi.TestName + import com.android.ide.common.rendering.api.SessionParams + import com.android.resources.* + import kotlin.math.ceil + import org.junit.Rule + import org.junit.Test + import org.junit.runner.RunWith + import org.junit.runners.Parameterized + import sergio.sastre.composable.preview.scanner.android.AndroidComposablePreviewScanner + import sergio.sastre.composable.preview.scanner.android.AndroidPreviewInfo + import sergio.sastre.composable.preview.scanner.android.device.DevicePreviewInfoParser + import sergio.sastre.composable.preview.scanner.android.device.domain.Device + import sergio.sastre.composable.preview.scanner.android.device.types.DEFAULT + import sergio.sastre.composable.preview.scanner.android.screenshotid.AndroidPreviewScreenshotIdBuilder + import sergio.sastre.composable.preview.scanner.core.preview.ComposablePreview + + class Dimensions( + val screenWidthInPx: Int, + val screenHeightInPx: Int + ) + + object ScreenDimensions { + fun dimensions( + parsedDevice: Device, + widthDp: Int, + heightDp: Int + ): Dimensions { + val conversionFactor = parsedDevice.densityDpi / 160f + val previewWidthInPx = ceil(widthDp * conversionFactor).toInt() + val previewHeightInPx = ceil(heightDp * conversionFactor).toInt() + return Dimensions( + screenHeightInPx = when (heightDp > 0) { + true -> previewHeightInPx + false -> parsedDevice.dimensions.height.toInt() + }, + screenWidthInPx = when (widthDp > 0) { + true -> previewWidthInPx + false -> parsedDevice.dimensions.width.toInt() + } + ) + } + } + + object DeviceConfigBuilder { + fun build(preview: AndroidPreviewInfo): DeviceConfig { + val parsedDevice = + DevicePreviewInfoParser.parse(preview.device)?.inPx() ?: return DeviceConfig() + + val dimensions = ScreenDimensions.dimensions( + parsedDevice = parsedDevice, + widthDp = preview.widthDp, + heightDp = preview.heightDp + ) + + return DeviceConfig( + screenHeight = dimensions.screenHeightInPx, + screenWidth = dimensions.screenWidthInPx, + density = Density(parsedDevice.densityDpi), + xdpi = parsedDevice.densityDpi, + ydpi = parsedDevice.densityDpi, + size = ScreenSize.valueOf(parsedDevice.screenSize.name), + ratio = ScreenRatio.valueOf(parsedDevice.screenRatio.name), + screenRound = ScreenRound.valueOf(parsedDevice.shape.name), + orientation = ScreenOrientation.valueOf(parsedDevice.orientation.name), + locale = preview.locale.ifBlank { "en" }, + fontScale = preview.fontScale, + nightMode = when (preview.uiMode and UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES) { + true -> NightMode.NIGHT + false -> NightMode.NOTNIGHT + } + ) + } + } + + private val paparazziTestName = + TestName(packageName = "Paparazzi", className = "Preview", methodName = "Test") + + private class PreviewSnapshotVerifier( + maxPercentDifference: Double + ): SnapshotHandler { + private val snapshotHandler = SnapshotVerifier( + maxPercentDifference = maxPercentDifference + ) + override fun newFrameHandler( + snapshot: Snapshot, + frameCount: Int, + fps: Int + ): SnapshotHandler.FrameHandler { + val newSnapshot = Snapshot( + name = snapshot.name, + testName = paparazziTestName, + timestamp = snapshot.timestamp, + tags = snapshot.tags, + file = snapshot.file, + ) + return snapshotHandler.newFrameHandler( + snapshot = newSnapshot, + frameCount = frameCount, + fps = fps + ) + } + + override fun close() { + snapshotHandler.close() + } + } + + private class PreviewHtmlReportWriter: SnapshotHandler { + private val snapshotHandler = HtmlReportWriter() + override fun newFrameHandler( + snapshot: Snapshot, + frameCount: Int, + fps: Int + ): SnapshotHandler.FrameHandler { + val newSnapshot = Snapshot( + name = snapshot.name, + testName = paparazziTestName, + timestamp = snapshot.timestamp, + tags = snapshot.tags, + file = snapshot.file, + ) + return snapshotHandler.newFrameHandler( + snapshot = newSnapshot, + frameCount = frameCount, + fps = fps + ) + } + + override fun close() { + snapshotHandler.close() + } + } + + object PaparazziPreviewRule { + const val UNDEFINED_API_LEVEL = -1 + const val MAX_API_LEVEL = 36 + + fun createFor(preview: ComposablePreview): Paparazzi { + val previewInfo = preview.previewInfo + val previewApiLevel = when(previewInfo.apiLevel == UNDEFINED_API_LEVEL) { + true -> MAX_API_LEVEL + false -> previewInfo.apiLevel + } + val tolerance = 0.0 + return Paparazzi( + environment = detectEnvironment().copy(compileSdkVersion = previewApiLevel), + deviceConfig = DeviceConfigBuilder.build(preview.previewInfo), + supportsRtl = true, + showSystemUi = previewInfo.showSystemUi, + renderingMode = when { + previewInfo.showSystemUi -> SessionParams.RenderingMode.NORMAL + previewInfo.widthDp > 0 && previewInfo.heightDp > 0 -> SessionParams.RenderingMode.FULL_EXPAND + else -> SessionParams.RenderingMode.SHRINK + }, + snapshotHandler = when(System.getProperty("paparazzi.test.verify")?.toBoolean() == true) { + true -> PreviewSnapshotVerifier(tolerance) + false -> PreviewHtmlReportWriter() + }, + maxPercentDifference = tolerance + ) + } + } + + @Composable + fun SystemUiSize( + widthInDp: Int, + heightInDp: Int, + content: @Composable () -> Unit + ) { + Box(Modifier + .size( + width = widthInDp.dp, + height = heightInDp.dp + ) + .background(Color.White) + ) { + content() + } + } + + @Composable + fun PreviewBackground( + showBackground: Boolean, + backgroundColor: Long, + content: @Composable () -> Unit + ) { + when (showBackground) { + false -> content() + true -> { + val color = when (backgroundColor != 0L) { + true -> Color(backgroundColor) + false -> Color.White + } + Box(Modifier.background(color)) { + content() + } + } + } + } + + /** + * Auto-generated by Paparazzi Preview Scanner Plugin. + * Scans packages: ${scanPackages.joinToString(", ")} + */ + @RunWith(Parameterized::class) + class $className( + val preview: ComposablePreview, + ) { + + companion object { + private val SOURCE_DIRS = listOf($sourceDirsString) + + private val cachedPreviews: List> by lazy { + val allPreviews = AndroidComposablePreviewScanner() + .scanPackageTrees($packagesString) + ${if (includePrivatePreviews) ".includePrivatePreviews()" else ""} + .getPreviews() + + // Filter to only include previews from the current module by checking + // if the source file exists in any of this module's source directories + allPreviews.filter { preview -> + // Convert class name to file path: com.example.MyClassKt -> com/example/MyClass.kt + // Kotlin adds "Kt" suffix to file-level functions, so we need to strip it + val className = preview.declaringClass + val classPath = if (className.endsWith("Kt")) { + className.removeSuffix("Kt").replace('.', '/') + } else { + className.replace('.', '/') + } + ".kt" + + // Check if the source file exists in any of the module's source directories + SOURCE_DIRS.any { sourceDir -> + java.io.File(sourceDir, classPath).exists() + } + } + } + + @JvmStatic + @Parameterized.Parameters + fun values(): List> = cachedPreviews + } + + @get:Rule + val paparazzi: Paparazzi = PaparazziPreviewRule.createFor(preview) + + @Test + fun snapshot() { + val screenshotId = AndroidPreviewScreenshotIdBuilder(preview) + .doNotIgnoreMethodParametersType() + .encodeUnsafeCharacters() + .build() + + paparazzi.snapshot(name = screenshotId) { + val previewInfo = preview.previewInfo + when (previewInfo.showSystemUi) { + false -> PreviewBackground( + showBackground = previewInfo.showBackground, + backgroundColor = previewInfo.backgroundColor, + ) { + preview() + } + + true -> { + val parsedDevice = (DevicePreviewInfoParser.parse(previewInfo.device) ?: DEFAULT).inDp() + SystemUiSize( + widthInDp = parsedDevice.dimensions.width.toInt(), + heightInDp = parsedDevice.dimensions.height.toInt() + ) { + PreviewBackground( + showBackground = true, + backgroundColor = previewInfo.backgroundColor, + ) { + preview() + } + } + } + } + } + } + } + """.trimIndent() + } +} diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index 1146b066..209e9d7b 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -48,5 +48,8 @@ dependencyResolutionManagement { } rootProject.name = "hacker-news" + include(":app") include(":benchmark") +include(":ui-components") +includeBuild("paparazzi-preview-scanner-plugin") diff --git a/android/ui-components/build.gradle.kts b/android/ui-components/build.gradle.kts new file mode 100644 index 00000000..25e69ead --- /dev/null +++ b/android/ui-components/build.gradle.kts @@ -0,0 +1,48 @@ +plugins { + id("com.android.library") + alias(libs.plugins.kotlin.android) + alias(libs.plugins.compose.compiler) + alias(libs.plugins.paparazzi) + id("com.emergetools.paparazzi.preview-scanner") +} + +android { + namespace = "com.emergetools.hackernews.ui.components" + compileSdk = 36 + + defaultConfig { + minSdk = 30 + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildFeatures { + compose = true + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.toString() + } +} + +paparazziPreviewScanner { + scanPackages.addAll("com.emergetools.hackernews.ui.components") +} + +dependencies { + implementation(libs.androidx.core.ktx) + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.ui) + implementation(libs.androidx.ui.graphics) + implementation(libs.androidx.ui.tooling.preview) + implementation(libs.androidx.material3) + + debugImplementation(libs.androidx.ui.tooling) + debugImplementation(libs.androidx.ui.test.manifest) + + testImplementation(libs.junit) +} diff --git a/android/ui-components/src/main/AndroidManifest.xml b/android/ui-components/src/main/AndroidManifest.xml new file mode 100644 index 00000000..9a40236b --- /dev/null +++ b/android/ui-components/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + + diff --git a/android/ui-components/src/main/java/com/emergetools/hackernews/ui/components/Button.kt b/android/ui-components/src/main/java/com/emergetools/hackernews/ui/components/Button.kt new file mode 100644 index 00000000..46304e82 --- /dev/null +++ b/android/ui-components/src/main/java/com/emergetools/hackernews/ui/components/Button.kt @@ -0,0 +1,56 @@ +package com.emergetools.hackernews.ui.components + +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.PreviewLightDark +import androidx.compose.ui.unit.dp + +@Composable +fun PrimaryButton( + text: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true +) { + Button( + onClick = onClick, + modifier = modifier, + enabled = enabled + ) { + Text(text = text) + } +} + +@PreviewLightDark +@Composable +fun PrimaryButtonPreview() { + MaterialTheme { + Surface { + PrimaryButton( + text = "Click Me", + onClick = {}, + modifier = Modifier.padding(16.dp) + ) + } + } +} + +@PreviewLightDark +@Composable +fun PrimaryButtonDisabledPreview() { + MaterialTheme { + Surface { + PrimaryButton( + text = "Disabled Button", + onClick = {}, + enabled = false, + modifier = Modifier.padding(16.dp) + ) + } + } +} diff --git a/android/ui-components/src/main/java/com/emergetools/hackernews/ui/components/Card.kt b/android/ui-components/src/main/java/com/emergetools/hackernews/ui/components/Card.kt new file mode 100644 index 00000000..44ff793d --- /dev/null +++ b/android/ui-components/src/main/java/com/emergetools/hackernews/ui/components/Card.kt @@ -0,0 +1,68 @@ +package com.emergetools.hackernews.ui.components + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.PreviewLightDark +import androidx.compose.ui.unit.dp + +@Composable +fun InfoCard( + title: String, + content: String, + modifier: Modifier = Modifier +) { + Card( + modifier = modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(defaultElevation = 4.dp) + ) { + Column( + modifier = Modifier.padding(16.dp) + ) { + Text( + text = title, + style = MaterialTheme.typography.titleMedium + ) + Text( + text = content, + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(top = 8.dp) + ) + } + } +} + +@PreviewLightDark +@Composable +fun InfoCardPreview() { + MaterialTheme { + Surface { + InfoCard( + title = "Sample Title", + content = "This is some sample content for the card component.", + modifier = Modifier.padding(16.dp) + ) + } + } +} + +@PreviewLightDark +@Composable +fun InfoCardLongContentPreview() { + MaterialTheme { + Surface { + InfoCard( + title = "Long Content Example", + content = "This is a much longer piece of content that demonstrates how the card component handles multiple lines of text. It should wrap properly and maintain good readability.", + modifier = Modifier.padding(16.dp) + ) + } + } +} diff --git a/android/ui-components/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.buttonkt.primarybuttondisabledpreview.dark_night.png b/android/ui-components/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.buttonkt.primarybuttondisabledpreview.dark_night.png new file mode 100644 index 00000000..1bbd62d5 Binary files /dev/null and b/android/ui-components/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.buttonkt.primarybuttondisabledpreview.dark_night.png differ diff --git a/android/ui-components/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.buttonkt.primarybuttondisabledpreview.light.png b/android/ui-components/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.buttonkt.primarybuttondisabledpreview.light.png new file mode 100644 index 00000000..1bbd62d5 Binary files /dev/null and b/android/ui-components/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.buttonkt.primarybuttondisabledpreview.light.png differ diff --git a/android/ui-components/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.buttonkt.primarybuttonpreview.dark_night.png b/android/ui-components/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.buttonkt.primarybuttonpreview.dark_night.png new file mode 100644 index 00000000..665e3be6 Binary files /dev/null and b/android/ui-components/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.buttonkt.primarybuttonpreview.dark_night.png differ diff --git a/android/ui-components/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.buttonkt.primarybuttonpreview.light.png b/android/ui-components/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.buttonkt.primarybuttonpreview.light.png new file mode 100644 index 00000000..665e3be6 Binary files /dev/null and b/android/ui-components/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.buttonkt.primarybuttonpreview.light.png differ diff --git a/android/ui-components/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.cardkt.infocardlongcontentpreview.dark_night.png b/android/ui-components/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.cardkt.infocardlongcontentpreview.dark_night.png new file mode 100644 index 00000000..0a904f5c Binary files /dev/null and b/android/ui-components/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.cardkt.infocardlongcontentpreview.dark_night.png differ diff --git a/android/ui-components/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.cardkt.infocardlongcontentpreview.light.png b/android/ui-components/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.cardkt.infocardlongcontentpreview.light.png new file mode 100644 index 00000000..0a904f5c Binary files /dev/null and b/android/ui-components/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.cardkt.infocardlongcontentpreview.light.png differ diff --git a/android/ui-components/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.cardkt.infocardpreview.dark_night.png b/android/ui-components/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.cardkt.infocardpreview.dark_night.png new file mode 100644 index 00000000..c14834d8 Binary files /dev/null and b/android/ui-components/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.cardkt.infocardpreview.dark_night.png differ diff --git a/android/ui-components/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.cardkt.infocardpreview.light.png b/android/ui-components/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.cardkt.infocardpreview.light.png new file mode 100644 index 00000000..c14834d8 Binary files /dev/null and b/android/ui-components/src/test/snapshots/images/Paparazzi_Preview_Test_com.emergetools.hackernews.ui.components.cardkt.infocardpreview.light.png differ