diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d271823555..a474bc2443 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -71,10 +71,10 @@ jobs: run: ./gradlew spotlessCheck --scan --full-stacktrace - name: Build (full) with Gradle - run: ./gradlew build -x :datacapture-kmp:build -x :sdc-kmp-demo:build --scan --full-stacktrace + run: ./gradlew build -Pcatalog.wasm.enabled=false -x :datacapture-kmp:build -x :sdc-kmp-demo:build --scan --full-stacktrace - name: Check with Gradle - run: ./gradlew check -x :datacapture-kmp:check -x :sdc-kmp-demo:check --scan --full-stacktrace + run: ./gradlew check -Pcatalog.wasm.enabled=false -x :datacapture-kmp:check -x :sdc-kmp-demo:check --scan --full-stacktrace - name: Run tests for datacapture Kotlin Multiplatform desktop environment with X Virtual Framebuffer run: | diff --git a/LICENSES-3RD-PARTY/LICENSE-JAI-IMAGEIO b/LICENSES-3RD-PARTY/LICENSE-JAI-IMAGEIO new file mode 100644 index 0000000000..589e191988 --- /dev/null +++ b/LICENSES-3RD-PARTY/LICENSE-JAI-IMAGEIO @@ -0,0 +1,39 @@ +Copyright (c) 2005 Sun Microsystems, Inc. +Copyright © 2010-2014 University of Manchester +Copyright © 2010-2015 Stian Soiland-Reyes +Copyright © 2015 Peter Hull +All Rights Reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +- Redistribution of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +- Redistribution in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + +Neither the name of Sun Microsystems, Inc. or the names of +contributors may be used to endorse or promote products derived +from this software without specific prior written permission. + +This software is provided "AS IS," without a warranty of any +kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND +WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY +EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL +NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF +USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS +DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR +ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, +CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND +REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR +INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + +You acknowledge that this software is not designed or intended for +use in the design, construction, operation or maintenance of any +nuclear facility. diff --git a/buildSrc/src/main/kotlin/LicenseeConfig.kt b/buildSrc/src/main/kotlin/LicenseeConfig.kt index 102eb51058..e94bb3ae75 100644 --- a/buildSrc/src/main/kotlin/LicenseeConfig.kt +++ b/buildSrc/src/main/kotlin/LicenseeConfig.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023-2025 Google LLC + * Copyright 2023-2026 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -224,6 +224,11 @@ private fun Project.configureLicensee() { // Logback allowDependency("ch.qos.logback", "logback-core", "1.4.14") { because("LGPL") } + + // jai-imageio-core in zxing + allowDependency("com.github.jai-imageio", "jai-imageio-core", "1.4.0") { + because("https://github.com/jai-imageio/jai-imageio-core/blob/master/LICENSE.txt") + } } } @@ -234,4 +239,9 @@ private val nonStandardLicenseUrls = "http://www.opensource.org/licenses/bsd-license.php", "https://asm.ow2.io/license.html", "https://developer.android.com/studio/terms.html", + "https://github.com/vinceglb/FileKit/blob/main/LICENSE", + "https://github.com/hypfvieh/dbus-java/blob/master/LICENSE", + "https://developers.google.com/ml-kit/terms", + "https://github.com/icerockdev/moko-permissions/blob/master/LICENSE.md", + "https://github.com/jordond/compass/blob/master/LICENSE", ) diff --git a/buildSrc/src/main/kotlin/Releases.kt b/buildSrc/src/main/kotlin/Releases.kt index cccd11fcf8..c7dfd1b397 100644 --- a/buildSrc/src/main/kotlin/Releases.kt +++ b/buildSrc/src/main/kotlin/Releases.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023-2025 Google LLC + * Copyright 2023-2026 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/catalog-iosApp/.gitignore b/catalog-iosApp/.gitignore new file mode 100644 index 0000000000..2c8444494f --- /dev/null +++ b/catalog-iosApp/.gitignore @@ -0,0 +1,4 @@ +## User settings +xcuserdata/ + +Configuration/LocalConfig.xcconfig diff --git a/catalog-iosApp/Configuration/Config.xcconfig b/catalog-iosApp/Configuration/Config.xcconfig new file mode 100644 index 0000000000..bb2e6e9418 --- /dev/null +++ b/catalog-iosApp/Configuration/Config.xcconfig @@ -0,0 +1,14 @@ +TEAM_ID= + +CURRENT_PROJECT_VERSION=1 +MARKETING_VERSION=1.0 + +// Optionally include `LocalConfig.xcconfig` +// that includes local overrides if it exists. +// `#include?` is supported as of Xcode 8 + +#include? "LocalConfig.xcconfig" + +PRODUCT_NAME=Catalog +DEVELOPMENT_TEAM=$(TEAM_ID) +PRODUCT_BUNDLE_IDENTIFIER=com.google.android.fhir.catalog$(TEAM_ID) diff --git a/catalog-iosApp/README.md b/catalog-iosApp/README.md new file mode 100644 index 0000000000..ed3d17d970 --- /dev/null +++ b/catalog-iosApp/README.md @@ -0,0 +1,64 @@ +#### Running on a real iOS device + +To run in a real iOS device, you'll need the following: + +* The `TEAM_ID` associated with your [Apple ID](https://support.apple.com/en-us/HT204316) +* The iOS device registered in Xcode + +> **Note** +> Before you continue, we suggest creating a simple "Hello, world!" project in Xcode to ensure you can successfully run +> apps on your device. +> You can follow the instructions below or watch +> this [Stanford CS193P lecture recording](https://youtu.be/bqu6BquVi2M?start=716&end=1399). + +
+How to create and run a simple project in Xcode + +1. On the Xcode welcome screen, select **Create a new project in Xcode**. +2. On the **iOS** tab, choose the **App** template. Click **Next**. +3. Specify the product name and keep other settings default. Click **Next**. +4. Select where to store the project on your computer and click **Create**. You'll see an app that displays "Hello, + world!" on the device screen. +5. At the top of your Xcode screen, click on the device name near the **Run** button. +6. Plug your device into the computer. You'll see this device in the list of run options. +7. Choose your device and click **Run**. + +
+ +##### Finding your Team ID + +Install KDoctor with [Homebrew](https://brew.sh/): + + ```text + brew install kdoctor + ``` + +Run `kdoctor --team-ids` to find your Team ID. +KDoctor will list all Team IDs currently configured on your system, for example: + +```text +3ABC246XYZ (Max Sample) +ZABCW6SXYZ (SampleTech Inc.) +``` + +
+Alternative way to find your Team ID + +If KDoctor doesn't work for you, try this alternative method: + +1. In Android Studio, run the `iosApp` configuration with the selected real device. The build should fail. +2. Go to Xcode and select **Open a project or file**. +3. Navigate to the `iosApp/iosApp.xcworkspace` file of your project. +4. In the left-hand menu, select `iosApp`. +5. Navigate to **Signing & Capabilities**. +6. In the **Team** list, select your team. + +If you haven't set up your team yet, use the **Add account** option and follow the steps. + +
+ +To run the application, set the `TEAM_ID`: + +1. Navigate to the `Configuration/LocalConfig.xcconfig` file. +2. Set your `TEAM_ID`. +3. Re-open the project in Android Studio. It should show the registered iOS device in the `iosApp` run configuration. \ No newline at end of file diff --git a/catalog-iosApp/iosApp.xcodeproj/project.pbxproj b/catalog-iosApp/iosApp.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..39467daf98 --- /dev/null +++ b/catalog-iosApp/iosApp.xcodeproj/project.pbxproj @@ -0,0 +1,390 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + BE6507D32F3CBEB300EE3190 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = BE6507D22F3CBEB300EE3190 /* README.md */; }; + BE6507D52F3CBECE00EE3190 /* .gitignore in Resources */ = {isa = PBXBuildFile; fileRef = BE6507D42F3CBECE00EE3190 /* .gitignore */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + B9DA97B12DC1472C00A4DA20 /* Catalog.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Catalog.app; sourceTree = BUILT_PRODUCTS_DIR; }; + BE6507D22F3CBEB300EE3190 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + BE6507D42F3CBECE00EE3190 /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + B99700CA2DC9B8D800C7335B /* Exceptions for "iosApp" folder in "iosApp" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = B9DA97B02DC1472C00A4DA20 /* iosApp */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + B9DA97B32DC1472C00A4DA20 /* iosApp */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + B99700CA2DC9B8D800C7335B /* Exceptions for "iosApp" folder in "iosApp" target */, + ); + path = iosApp; + sourceTree = ""; + }; + B9DA98002DC14AA900A4DA20 /* Configuration */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = Configuration; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + B9DA97AE2DC1472C00A4DA20 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + B9DA97A82DC1472C00A4DA20 = { + isa = PBXGroup; + children = ( + BE6507D42F3CBECE00EE3190 /* .gitignore */, + BE6507D22F3CBEB300EE3190 /* README.md */, + B9DA98002DC14AA900A4DA20 /* Configuration */, + B9DA97B32DC1472C00A4DA20 /* iosApp */, + B9DA97B22DC1472C00A4DA20 /* Products */, + ); + sourceTree = ""; + }; + B9DA97B22DC1472C00A4DA20 /* Products */ = { + isa = PBXGroup; + children = ( + B9DA97B12DC1472C00A4DA20 /* Catalog.app */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + B9DA97B02DC1472C00A4DA20 /* iosApp */ = { + isa = PBXNativeTarget; + buildConfigurationList = B9DA97BF2DC1472D00A4DA20 /* Build configuration list for PBXNativeTarget "iosApp" */; + buildPhases = ( + B9DA97F42DC1497100A4DA20 /* Compile Kotlin Framework */, + B9DA97AD2DC1472C00A4DA20 /* Sources */, + B9DA97AE2DC1472C00A4DA20 /* Frameworks */, + B9DA97AF2DC1472C00A4DA20 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + B9DA97B32DC1472C00A4DA20 /* iosApp */, + ); + name = iosApp; + packageProductDependencies = ( + ); + productName = iosApp; + productReference = B9DA97B12DC1472C00A4DA20 /* Catalog.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + B9DA97A92DC1472C00A4DA20 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1620; + LastUpgradeCheck = 1620; + TargetAttributes = { + B9DA97B02DC1472C00A4DA20 = { + CreatedOnToolsVersion = 16.2; + }; + }; + }; + buildConfigurationList = B9DA97AC2DC1472C00A4DA20 /* Build configuration list for PBXProject "iosApp" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = B9DA97A82DC1472C00A4DA20; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; + productRefGroup = B9DA97B22DC1472C00A4DA20 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + B9DA97B02DC1472C00A4DA20 /* iosApp */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + B9DA97AF2DC1472C00A4DA20 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + BE6507D52F3CBECE00EE3190 /* .gitignore in Resources */, + BE6507D32F3CBEB300EE3190 /* README.md in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + B9DA97F42DC1497100A4DA20 /* Compile Kotlin Framework */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Compile Kotlin Framework"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [ \"YES\" = \"$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED\" ]; then\n echo \"Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \\\"YES\\\"\"\n exit 0\nfi\ncd \"$SRCROOT/..\"\n./gradlew :catalog:embedAndSignAppleFrameworkForXcode\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + B9DA97AD2DC1472C00A4DA20 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + B9DA97BD2DC1472D00A4DA20 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + B9DA97BE2DC1472D00A4DA20 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.2; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + B9DA97C02DC1472D00A4DA20 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReferenceAnchor = B9DA98002DC14AA900A4DA20 /* Configuration */; + baseConfigurationReferenceRelativePath = Config.xcconfig; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; + ENABLE_PREVIEWS = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", + ); + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = iosApp/Info.plist; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + B9DA97C12DC1472D00A4DA20 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReferenceAnchor = B9DA98002DC14AA900A4DA20 /* Configuration */; + baseConfigurationReferenceRelativePath = Config.xcconfig; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; + ENABLE_PREVIEWS = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", + ); + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = iosApp/Info.plist; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + B9DA97AC2DC1472C00A4DA20 /* Build configuration list for PBXProject "iosApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B9DA97BD2DC1472D00A4DA20 /* Debug */, + B9DA97BE2DC1472D00A4DA20 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + B9DA97BF2DC1472D00A4DA20 /* Build configuration list for PBXNativeTarget "iosApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B9DA97C02DC1472D00A4DA20 /* Debug */, + B9DA97C12DC1472D00A4DA20 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = B9DA97A92DC1472C00A4DA20 /* Project object */; +} diff --git a/catalog-iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/catalog-iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..919434a625 --- /dev/null +++ b/catalog-iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/catalog-iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json b/catalog-iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000000..eb87897008 --- /dev/null +++ b/catalog-iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/catalog-iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/catalog-iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..4e8d485bf0 --- /dev/null +++ b/catalog-iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,36 @@ +{ + "images" : [ + { + "filename" : "app-icon-1024.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/catalog-iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png b/catalog-iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png new file mode 100644 index 0000000000..53fc536fb9 Binary files /dev/null and b/catalog-iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png differ diff --git a/catalog-iosApp/iosApp/Assets.xcassets/Contents.json b/catalog-iosApp/iosApp/Assets.xcassets/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/catalog-iosApp/iosApp/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/catalog-iosApp/iosApp/ContentView.swift b/catalog-iosApp/iosApp/ContentView.swift new file mode 100644 index 0000000000..bf3d1f709a --- /dev/null +++ b/catalog-iosApp/iosApp/ContentView.swift @@ -0,0 +1,21 @@ +import UIKit +import SwiftUI +import CatalogKit + +struct ComposeView: UIViewControllerRepresentable { + func makeUIViewController(context: Context) -> UIViewController { + MainViewControllerKt.MainViewController() + } + + func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} +} + +struct ContentView: View { + var body: some View { + ComposeView() + .ignoresSafeArea(.keyboard) // Compose has own keyboard handler + } +} + + + diff --git a/catalog-iosApp/iosApp/Info.plist b/catalog-iosApp/iosApp/Info.plist new file mode 100644 index 0000000000..3d898ba8db --- /dev/null +++ b/catalog-iosApp/iosApp/Info.plist @@ -0,0 +1,26 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + + NSCameraUsageDescription + Camera access needed to take photos + + NSPhotoLibraryUsageDescription + Photo library access needed to select images + + NSDocumentsFolderUsageDescription + Document access needed to select files for attachment + + NSDownloadsFolderUsageDescription + Download access needed to select files for attachment + + NSDesktopFolderUsageDescription + Desktop access needed to select files for attachment + + NSLocationWhenInUseUsageDescription + Location access needed for the location widget + + diff --git a/catalog-iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json b/catalog-iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/catalog-iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/catalog-iosApp/iosApp/iOSApp.swift b/catalog-iosApp/iosApp/iOSApp.swift new file mode 100644 index 0000000000..d83dca6113 --- /dev/null +++ b/catalog-iosApp/iosApp/iOSApp.swift @@ -0,0 +1,10 @@ +import SwiftUI + +@main +struct iOSApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} \ No newline at end of file diff --git a/catalog/build.gradle.kts b/catalog/build.gradle.kts index 0e6e5dab98..e5d8979948 100644 --- a/catalog/build.gradle.kts +++ b/catalog/build.gradle.kts @@ -1,14 +1,22 @@ +import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig + plugins { - alias(libs.plugins.android.application) - alias(libs.plugins.kotlin.android) + id("org.jetbrains.kotlin.multiplatform") + id("com.android.application") + id("org.jetbrains.kotlin.plugin.compose") + id("org.jetbrains.compose") + id("org.jetbrains.kotlin.plugin.serialization") alias(libs.plugins.androidx.navigation.safeargs) } -configureRuler() +// configureRuler() android { namespace = "com.google.android.fhir.catalog" compileSdk = Sdk.COMPILE_SDK + defaultConfig { applicationId = Releases.Catalog.applicationId minSdk = Sdk.MIN_SDK @@ -17,9 +25,6 @@ android { versionName = Releases.Catalog.versionName testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } - - buildFeatures { viewBinding = true } - buildTypes { release { isMinifyEnabled = true @@ -27,37 +32,80 @@ android { } } compileOptions { - // Flag to enable support for the new language APIs - // See https://developer.android.com/studio/write/java8-support - isCoreLibraryDesugaringEnabled = true + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } - packaging { resources.excludes.addAll( listOf("META-INF/ASL2.0", "META-INF/ASL-2.0.txt", "META-INF/LGPL-3.0.txt"), ) } - kotlin { jvmToolchain(11) } } -dependencies { - androidTestImplementation(libs.androidx.test.espresso.core) - androidTestImplementation(libs.androidx.test.ext.junit) - - coreLibraryDesugaring(libs.desugar.jdk.libs) - - implementation(libs.androidx.appcompat) - implementation(libs.androidx.constraintlayout) - implementation(libs.androidx.core) - implementation(libs.androidx.fragment) - implementation(libs.androidx.navigation.fragment) - implementation(libs.androidx.navigation.ui) - implementation(libs.kotlin.stdlib) - implementation(libs.material) - implementation(project(path = ":contrib:barcode")) - implementation(project(path = ":contrib:locationwidget")) - implementation(project(path = ":datacapture")) - implementation(project(path = ":engine")) - - testImplementation(libs.junit) +kotlin { + androidTarget { compilerOptions { jvmTarget.set(JvmTarget.JVM_11) } } + + jvm("desktop") + + val isWasmEnabled = project.findProperty("catalog.wasm.enabled") == "true" + if (isWasmEnabled) { + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + browser { + val rootProjectDir = rootProject.projectDir.path + commonWebpackConfig { + devServer = + (devServer ?: KotlinWebpackConfig.DevServer()).copy( + static = (devServer?.static ?: mutableListOf()).apply { add(rootProjectDir) }, + ) + } + } + binaries.executable() + } + } + + listOf( + iosX64(), + iosArm64(), + iosSimulatorArm64(), + ) + .forEach { iosTarget -> + iosTarget.binaries.framework { + baseName = "CatalogKit" + isStatic = true + } + } + + sourceSets { + androidMain.dependencies { + implementation(libs.androidx.appcompat) + implementation(libs.androidx.core) + implementation(libs.material) + // TODO restore after these libraries are migrated to Kotlin Multiplatform + // implementation(project(":engine")) + // implementation(project(":contrib:barcode")) + // implementation(project(":contrib:locationwidget")) + } + commonMain.dependencies { + implementation(compose.runtime) + implementation(compose.foundation) + implementation(compose.material3) + implementation(compose.ui) + implementation(compose.components.resources) + implementation(compose.components.uiToolingPreview) + implementation(libs.androidx.lifecycle.viewmodel.compose) + implementation(libs.androidx.lifecycle.runtime.compose) + implementation(libs.material.icons.extended) + implementation(libs.kermit) + implementation(libs.kotlin.fhir) + implementation(libs.kotlinx.serialization.json) + implementation(libs.kotlinx.coroutines.core) + implementation(libs.navigation.compose) + implementation(project(":contrib:barcode")) + implementation(project(":contrib:locationwidget")) + implementation(project(":datacapture-kmp")) + } + + val desktopMain by getting { dependencies { implementation(compose.desktop.currentOs) } } + } } diff --git a/catalog/src/main/AndroidManifest.xml b/catalog/src/androidMain/AndroidManifest.xml similarity index 93% rename from catalog/src/main/AndroidManifest.xml rename to catalog/src/androidMain/AndroidManifest.xml index 17469a9339..dc1f5303ed 100644 --- a/catalog/src/main/AndroidManifest.xml +++ b/catalog/src/androidMain/AndroidManifest.xml @@ -30,7 +30,7 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_card_file_box_round" android:supportsRtl="true" - android:theme="@style/Theme.Androidfhir" + android:theme="@style/Theme.AppCompat.DayNight.NoActionBar" > diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/DemoQuestionnaireViewModel.kt b/catalog/src/androidMain/kotlin/com/google/android/fhir/catalog/CatalogApplication.kt similarity index 53% rename from catalog/src/main/java/com/google/android/fhir/catalog/DemoQuestionnaireViewModel.kt rename to catalog/src/androidMain/kotlin/com/google/android/fhir/catalog/CatalogApplication.kt index 264225325f..bbdc4c83ec 100644 --- a/catalog/src/main/java/com/google/android/fhir/catalog/DemoQuestionnaireViewModel.kt +++ b/catalog/src/androidMain/kotlin/com/google/android/fhir/catalog/CatalogApplication.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 Google LLC + * Copyright 2022-2026 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,15 +17,17 @@ package com.google.android.fhir.catalog import android.app.Application -import androidx.lifecycle.AndroidViewModel -import androidx.lifecycle.SavedStateHandle -import ca.uhn.fhir.context.FhirContext -import ca.uhn.fhir.context.FhirVersionEnum -import org.hl7.fhir.r4.model.QuestionnaireResponse +import com.google.android.fhir.datacapture.DataCapture +import com.google.android.fhir.datacapture.DataCaptureConfig -class DemoQuestionnaireViewModel(application: Application, private val state: SavedStateHandle) : - AndroidViewModel(application) { +class CatalogApplication : Application(), DataCaptureConfig.Provider { - fun getQuestionnaireResponseJson(response: QuestionnaireResponse) = - FhirContext.forCached(FhirVersionEnum.R4).newJsonParser().encodeResourceToString(response) + override fun onCreate() { + super.onCreate() + DataCapture.initialize(this) + } + + override fun getDataCaptureConfig(): DataCaptureConfig { + return DataCaptureConfig(xFhirQueryResolver = { emptyList() }) + } } diff --git a/datacapture-kmp/src/androidHostTest/kotlin/com/google/android/fhir/datacapture/ExampleUnitTest.kt b/catalog/src/androidMain/kotlin/com/google/android/fhir/catalog/MainActivity.kt similarity index 60% rename from datacapture-kmp/src/androidHostTest/kotlin/com/google/android/fhir/datacapture/ExampleUnitTest.kt rename to catalog/src/androidMain/kotlin/com/google/android/fhir/catalog/MainActivity.kt index d1f961b2b5..8cf8cedbc1 100644 --- a/datacapture-kmp/src/androidHostTest/kotlin/com/google/android/fhir/datacapture/ExampleUnitTest.kt +++ b/catalog/src/androidMain/kotlin/com/google/android/fhir/catalog/MainActivity.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 Google LLC + * Copyright 2021-2026 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,19 +14,15 @@ * limitations under the License. */ -package com.google.android.fhir.datacapture +package com.google.android.fhir.catalog -import kotlin.test.Test -import kotlin.test.assertEquals +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { App() } } } diff --git a/catalog/src/main/res/mipmap-anydpi-v26/ic_card_file_box.xml b/catalog/src/androidMain/res/mipmap-anydpi-v26/ic_card_file_box.xml similarity index 100% rename from catalog/src/main/res/mipmap-anydpi-v26/ic_card_file_box.xml rename to catalog/src/androidMain/res/mipmap-anydpi-v26/ic_card_file_box.xml diff --git a/catalog/src/main/res/mipmap-anydpi-v26/ic_card_file_box_round.xml b/catalog/src/androidMain/res/mipmap-anydpi-v26/ic_card_file_box_round.xml similarity index 54% rename from catalog/src/main/res/mipmap-anydpi-v26/ic_card_file_box_round.xml rename to catalog/src/androidMain/res/mipmap-anydpi-v26/ic_card_file_box_round.xml index 78b23ed063..c242d1fe57 100644 --- a/catalog/src/main/res/mipmap-anydpi-v26/ic_card_file_box_round.xml +++ b/catalog/src/androidMain/res/mipmap-anydpi-v26/ic_card_file_box_round.xml @@ -1,5 +1,9 @@ - + diff --git a/catalog/src/main/res/mipmap-hdpi/ic_card_file_box.png b/catalog/src/androidMain/res/mipmap-hdpi/ic_card_file_box.png similarity index 100% rename from catalog/src/main/res/mipmap-hdpi/ic_card_file_box.png rename to catalog/src/androidMain/res/mipmap-hdpi/ic_card_file_box.png diff --git a/catalog/src/main/res/mipmap-hdpi/ic_card_file_box_foreground.png b/catalog/src/androidMain/res/mipmap-hdpi/ic_card_file_box_foreground.png similarity index 100% rename from catalog/src/main/res/mipmap-hdpi/ic_card_file_box_foreground.png rename to catalog/src/androidMain/res/mipmap-hdpi/ic_card_file_box_foreground.png diff --git a/catalog/src/main/res/mipmap-hdpi/ic_card_file_box_round.png b/catalog/src/androidMain/res/mipmap-hdpi/ic_card_file_box_round.png similarity index 100% rename from catalog/src/main/res/mipmap-hdpi/ic_card_file_box_round.png rename to catalog/src/androidMain/res/mipmap-hdpi/ic_card_file_box_round.png diff --git a/catalog/src/main/res/mipmap-mdpi/ic_card_file_box.png b/catalog/src/androidMain/res/mipmap-mdpi/ic_card_file_box.png similarity index 100% rename from catalog/src/main/res/mipmap-mdpi/ic_card_file_box.png rename to catalog/src/androidMain/res/mipmap-mdpi/ic_card_file_box.png diff --git a/catalog/src/main/res/mipmap-mdpi/ic_card_file_box_foreground.png b/catalog/src/androidMain/res/mipmap-mdpi/ic_card_file_box_foreground.png similarity index 100% rename from catalog/src/main/res/mipmap-mdpi/ic_card_file_box_foreground.png rename to catalog/src/androidMain/res/mipmap-mdpi/ic_card_file_box_foreground.png diff --git a/catalog/src/main/res/mipmap-mdpi/ic_card_file_box_round.png b/catalog/src/androidMain/res/mipmap-mdpi/ic_card_file_box_round.png similarity index 100% rename from catalog/src/main/res/mipmap-mdpi/ic_card_file_box_round.png rename to catalog/src/androidMain/res/mipmap-mdpi/ic_card_file_box_round.png diff --git a/catalog/src/main/res/mipmap-xhdpi/ic_card_file_box.png b/catalog/src/androidMain/res/mipmap-xhdpi/ic_card_file_box.png similarity index 100% rename from catalog/src/main/res/mipmap-xhdpi/ic_card_file_box.png rename to catalog/src/androidMain/res/mipmap-xhdpi/ic_card_file_box.png diff --git a/catalog/src/main/res/mipmap-xhdpi/ic_card_file_box_foreground.png b/catalog/src/androidMain/res/mipmap-xhdpi/ic_card_file_box_foreground.png similarity index 100% rename from catalog/src/main/res/mipmap-xhdpi/ic_card_file_box_foreground.png rename to catalog/src/androidMain/res/mipmap-xhdpi/ic_card_file_box_foreground.png diff --git a/catalog/src/main/res/mipmap-xhdpi/ic_card_file_box_round.png b/catalog/src/androidMain/res/mipmap-xhdpi/ic_card_file_box_round.png similarity index 100% rename from catalog/src/main/res/mipmap-xhdpi/ic_card_file_box_round.png rename to catalog/src/androidMain/res/mipmap-xhdpi/ic_card_file_box_round.png diff --git a/catalog/src/main/res/mipmap-xxhdpi/ic_card_file_box.png b/catalog/src/androidMain/res/mipmap-xxhdpi/ic_card_file_box.png similarity index 100% rename from catalog/src/main/res/mipmap-xxhdpi/ic_card_file_box.png rename to catalog/src/androidMain/res/mipmap-xxhdpi/ic_card_file_box.png diff --git a/catalog/src/main/res/mipmap-xxhdpi/ic_card_file_box_foreground.png b/catalog/src/androidMain/res/mipmap-xxhdpi/ic_card_file_box_foreground.png similarity index 100% rename from catalog/src/main/res/mipmap-xxhdpi/ic_card_file_box_foreground.png rename to catalog/src/androidMain/res/mipmap-xxhdpi/ic_card_file_box_foreground.png diff --git a/catalog/src/main/res/mipmap-xxhdpi/ic_card_file_box_round.png b/catalog/src/androidMain/res/mipmap-xxhdpi/ic_card_file_box_round.png similarity index 100% rename from catalog/src/main/res/mipmap-xxhdpi/ic_card_file_box_round.png rename to catalog/src/androidMain/res/mipmap-xxhdpi/ic_card_file_box_round.png diff --git a/catalog/src/main/res/mipmap-xxxhdpi/ic_card_file_box.png b/catalog/src/androidMain/res/mipmap-xxxhdpi/ic_card_file_box.png similarity index 100% rename from catalog/src/main/res/mipmap-xxxhdpi/ic_card_file_box.png rename to catalog/src/androidMain/res/mipmap-xxxhdpi/ic_card_file_box.png diff --git a/catalog/src/main/res/mipmap-xxxhdpi/ic_card_file_box_foreground.png b/catalog/src/androidMain/res/mipmap-xxxhdpi/ic_card_file_box_foreground.png similarity index 100% rename from catalog/src/main/res/mipmap-xxxhdpi/ic_card_file_box_foreground.png rename to catalog/src/androidMain/res/mipmap-xxxhdpi/ic_card_file_box_foreground.png diff --git a/catalog/src/main/res/mipmap-xxxhdpi/ic_card_file_box_round.png b/catalog/src/androidMain/res/mipmap-xxxhdpi/ic_card_file_box_round.png similarity index 100% rename from catalog/src/main/res/mipmap-xxxhdpi/ic_card_file_box_round.png rename to catalog/src/androidMain/res/mipmap-xxxhdpi/ic_card_file_box_round.png diff --git a/catalog/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/catalog/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from catalog/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to catalog/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/catalog/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/catalog/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png similarity index 100% rename from catalog/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png rename to catalog/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png diff --git a/catalog/src/main/res/values-night/colors.xml b/catalog/src/androidMain/res/values-night/colors.xml similarity index 55% rename from catalog/src/main/res/values-night/colors.xml rename to catalog/src/androidMain/res/values-night/colors.xml index d80470737a..f76e31bf10 100644 --- a/catalog/src/main/res/values-night/colors.xml +++ b/catalog/src/androidMain/res/values-night/colors.xml @@ -15,6 +15,38 @@ limitations under the License. --> + #A8C7FA + #3B5CFF + #062E6F + #0842A0 + #D3E3FD + + #7FCFFF + #003355 + #004A77 + #C2E7FF + + #91D5A3 + #0A3818 + #0F5223 + #C4EED0 + + #F2B8B5 + #601410 + #8C1D18 + #F9DEDC + + #1F1F1F + #E3E3E3 + + #1F1F1F + #E3E3E3 + + #444746 + #C4C7C5 + + #8E918F + #000000 #0C0A20 #201441 diff --git a/catalog/src/main/res/values/colors.xml b/catalog/src/androidMain/res/values/colors.xml similarity index 55% rename from catalog/src/main/res/values/colors.xml rename to catalog/src/androidMain/res/values/colors.xml index f9b63e732a..80b8681cfc 100644 --- a/catalog/src/main/res/values/colors.xml +++ b/catalog/src/androidMain/res/values/colors.xml @@ -1,127 +1,76 @@ - - #FFBB86FC - #FF6200EE - #FF3700B3 - #FF03DAC5 - #FF018786 - #FF000000 - #FFFFFFFF - #F8F9FA - #F1F3F4 - #E8EAED - #F1F3F4 - #3C4043 - #5F6368 - #202124 - #1A73E8 - #1967D2 - #1A73E8 - #8AB4F8 - #618AB4F8 - #D2E3FC - #E8F0FE - #D93025 - #F28B82 - #5F6368 - #DADCE0 - #0B57D0 + #A8C7FA + #FFFFFF + #062E6F #FFFFFF + #0842A0 #D3E3FD #041E49 + #D3E3FD #00639B + #7FCFFF + #003355 #FFFFFF + #004A77 #C2E7FF #001D35 + #C2E7FF #146C2E + #91D5A3 + #0A3818 #FFFFFF + #0F5223 #C4EED0 #072711 - - #B3261E - #FFFFFF - #F9DEDC - #410E0B - - #FFFFFF - #1F1F1F - - #FFFFFF - #1F1F1F - - #E1E3E1 - #444746 - - #747775 - - #A8C7FA - #062E6F - #0842A0 - #D3E3FD - - #7FCFFF - #003355 - #004A77 - #C2E7FF - - #146C2E - #0A3818 - #0F5223 #C4EED0 + #B3261E #F2B8B5 #601410 + #FFFFFF #8C1D18 + #F9DEDC + #410E0B #F9DEDC #1F1F1F + #FFFFFF + #1F1F1F #E3E3E3 #1F1F1F + #FFFFFF + #1F1F1F #E3E3E3 #444746 + #E1E3E1 + #444746 #C4C7C5 + #747775 #8E918F - - #7A9FFF - #668FFF - #5581FF - #476FFF - #3B5CFF - #3249FF - #2936FF - #2024FF - #1816FF - #FFFFFF - #FFFFFF - #FFFFFF - #FFFFFF - #FFFFFF - #FFFFFF - #FFFFFF - #FFFFFF - #FFFFFF + #000000 + #0C0A20 + #201441 + #341F63 + #482A85 + #5C35A6 + #7F5FBA + #A289CF + #C5B3E3 #FFFFFF - + #FFFFFF + #FFFFFF + #FFFFFF + #FFFFFF + #FFFFFF + #FFFFFF + #000000 + #000000 diff --git a/catalog/src/main/res/layout/fragment_questionnaire_container.xml b/catalog/src/androidMain/res/values/strings.xml similarity index 68% rename from catalog/src/main/res/layout/fragment_questionnaire_container.xml rename to catalog/src/androidMain/res/values/strings.xml index fb73b0b80c..ed024c1ead 100644 --- a/catalog/src/main/res/layout/fragment_questionnaire_container.xml +++ b/catalog/src/androidMain/res/values/strings.xml @@ -1,4 +1,3 @@ - - + + Structured Data Capture Catalog + diff --git a/catalog/src/main/res/xml/file_paths.xml b/catalog/src/androidMain/res/xml/file_paths.xml similarity index 100% rename from catalog/src/main/res/xml/file_paths.xml rename to catalog/src/androidMain/res/xml/file_paths.xml diff --git a/catalog/src/main/res/drawable/ic_answers_behavior.xml b/catalog/src/commonMain/composeResources/drawable/ic_answers_behavior.xml similarity index 100% rename from catalog/src/main/res/drawable/ic_answers_behavior.xml rename to catalog/src/commonMain/composeResources/drawable/ic_answers_behavior.xml diff --git a/catalog/src/main/res/drawable/ic_attachment.xml b/catalog/src/commonMain/composeResources/drawable/ic_attachment.xml similarity index 94% rename from catalog/src/main/res/drawable/ic_attachment.xml rename to catalog/src/commonMain/composeResources/drawable/ic_attachment.xml index b168e17c49..0c76cc76c1 100644 --- a/catalog/src/main/res/drawable/ic_attachment.xml +++ b/catalog/src/commonMain/composeResources/drawable/ic_attachment.xml @@ -7,7 +7,7 @@ android:tint="#1A73E8" > diff --git a/catalog/src/main/res/drawable/ic_autocomplete.xml b/catalog/src/commonMain/composeResources/drawable/ic_autocomplete.xml similarity index 100% rename from catalog/src/main/res/drawable/ic_autocomplete.xml rename to catalog/src/commonMain/composeResources/drawable/ic_autocomplete.xml diff --git a/contrib/barcode/src/main/res/drawable/ic_barcode.xml b/catalog/src/commonMain/composeResources/drawable/ic_barcode.xml similarity index 100% rename from contrib/barcode/src/main/res/drawable/ic_barcode.xml rename to catalog/src/commonMain/composeResources/drawable/ic_barcode.xml diff --git a/catalog/src/main/res/drawable/ic_behaviors.xml b/catalog/src/commonMain/composeResources/drawable/ic_behaviors.xml similarity index 54% rename from catalog/src/main/res/drawable/ic_behaviors.xml rename to catalog/src/commonMain/composeResources/drawable/ic_behaviors.xml index fa88a7f158..f15a83d1bb 100644 --- a/catalog/src/main/res/drawable/ic_behaviors.xml +++ b/catalog/src/commonMain/composeResources/drawable/ic_behaviors.xml @@ -4,30 +4,21 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" - android:tint="?attr/colorControlNormal" + android:tint="#FF000000" > + + + - - - diff --git a/catalog/src/main/res/drawable/ic_booleanchoice.xml b/catalog/src/commonMain/composeResources/drawable/ic_booleanchoice.xml similarity index 100% rename from catalog/src/main/res/drawable/ic_booleanchoice.xml rename to catalog/src/commonMain/composeResources/drawable/ic_booleanchoice.xml diff --git a/catalog/src/main/res/drawable/ic_calculations_behavior.xml b/catalog/src/commonMain/composeResources/drawable/ic_calculations_behavior.xml similarity index 100% rename from catalog/src/main/res/drawable/ic_calculations_behavior.xml rename to catalog/src/commonMain/composeResources/drawable/ic_calculations_behavior.xml diff --git a/catalog/src/main/res/drawable/ic_components.xml b/catalog/src/commonMain/composeResources/drawable/ic_components.xml similarity index 82% rename from catalog/src/main/res/drawable/ic_components.xml rename to catalog/src/commonMain/composeResources/drawable/ic_components.xml index 1426bb5310..b712ca1d0e 100644 --- a/catalog/src/main/res/drawable/ic_components.xml +++ b/catalog/src/commonMain/composeResources/drawable/ic_components.xml @@ -4,10 +4,10 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" - android:tint="?attr/colorControlNormal" + android:tint="#FF000000" > diff --git a/catalog/src/main/res/drawable/ic_context.xml b/catalog/src/commonMain/composeResources/drawable/ic_context.xml similarity index 100% rename from catalog/src/main/res/drawable/ic_context.xml rename to catalog/src/commonMain/composeResources/drawable/ic_context.xml diff --git a/catalog/src/main/res/drawable/ic_dateofbirth.xml b/catalog/src/commonMain/composeResources/drawable/ic_dateofbirth.xml similarity index 100% rename from catalog/src/main/res/drawable/ic_dateofbirth.xml rename to catalog/src/commonMain/composeResources/drawable/ic_dateofbirth.xml diff --git a/catalog/src/main/res/drawable/ic_datepicker.xml b/catalog/src/commonMain/composeResources/drawable/ic_datepicker.xml similarity index 100% rename from catalog/src/main/res/drawable/ic_datepicker.xml rename to catalog/src/commonMain/composeResources/drawable/ic_datepicker.xml diff --git a/catalog/src/main/res/drawable/ic_defaultlayout.xml b/catalog/src/commonMain/composeResources/drawable/ic_defaultlayout.xml similarity index 100% rename from catalog/src/main/res/drawable/ic_defaultlayout.xml rename to catalog/src/commonMain/composeResources/drawable/ic_defaultlayout.xml diff --git a/catalog/src/main/res/drawable/ic_dynamic_text_behavior.xml b/catalog/src/commonMain/composeResources/drawable/ic_dynamic_text_behavior.xml similarity index 100% rename from catalog/src/main/res/drawable/ic_dynamic_text_behavior.xml rename to catalog/src/commonMain/composeResources/drawable/ic_dynamic_text_behavior.xml diff --git a/catalog/src/main/res/drawable/ic_group_1278.xml b/catalog/src/commonMain/composeResources/drawable/ic_group_1278.xml similarity index 100% rename from catalog/src/main/res/drawable/ic_group_1278.xml rename to catalog/src/commonMain/composeResources/drawable/ic_group_1278.xml diff --git a/catalog/src/main/res/drawable/ic_help.xml b/catalog/src/commonMain/composeResources/drawable/ic_help.xml similarity index 100% rename from catalog/src/main/res/drawable/ic_help.xml rename to catalog/src/commonMain/composeResources/drawable/ic_help.xml diff --git a/catalog/src/main/res/drawable/ic_icon.xml b/catalog/src/commonMain/composeResources/drawable/ic_icon.xml similarity index 100% rename from catalog/src/main/res/drawable/ic_icon.xml rename to catalog/src/commonMain/composeResources/drawable/ic_icon.xml diff --git a/catalog/src/main/res/drawable/ic_info_24.xml b/catalog/src/commonMain/composeResources/drawable/ic_info_24.xml similarity index 78% rename from catalog/src/main/res/drawable/ic_info_24.xml rename to catalog/src/commonMain/composeResources/drawable/ic_info_24.xml index 27241420a4..31dc46860e 100644 --- a/catalog/src/main/res/drawable/ic_info_24.xml +++ b/catalog/src/commonMain/composeResources/drawable/ic_info_24.xml @@ -4,10 +4,10 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" - android:tint="?attr/colorControlNormal" + android:tint="#FF000000" > diff --git a/catalog/src/main/res/drawable/ic_initial_value_component.xml b/catalog/src/commonMain/composeResources/drawable/ic_initial_value_component.xml similarity index 100% rename from catalog/src/main/res/drawable/ic_initial_value_component.xml rename to catalog/src/commonMain/composeResources/drawable/ic_initial_value_component.xml diff --git a/catalog/src/main/res/drawable/ic_item_answer_media.xml b/catalog/src/commonMain/composeResources/drawable/ic_item_answer_media.xml similarity index 100% rename from catalog/src/main/res/drawable/ic_item_answer_media.xml rename to catalog/src/commonMain/composeResources/drawable/ic_item_answer_media.xml diff --git a/catalog/src/main/res/drawable/ic_item_media.xml b/catalog/src/commonMain/composeResources/drawable/ic_item_media.xml similarity index 100% rename from catalog/src/main/res/drawable/ic_item_media.xml rename to catalog/src/commonMain/composeResources/drawable/ic_item_media.xml diff --git a/catalog/src/main/res/drawable/ic_layouts.xml b/catalog/src/commonMain/composeResources/drawable/ic_layouts.xml similarity index 84% rename from catalog/src/main/res/drawable/ic_layouts.xml rename to catalog/src/commonMain/composeResources/drawable/ic_layouts.xml index 20aa044337..fe41081449 100644 --- a/catalog/src/main/res/drawable/ic_layouts.xml +++ b/catalog/src/commonMain/composeResources/drawable/ic_layouts.xml @@ -4,11 +4,11 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" - android:tint="?attr/colorControlNormal" + android:tint="#FF000000" android:autoMirrored="true" > diff --git a/catalog/src/main/res/drawable/ic_location_on.xml b/catalog/src/commonMain/composeResources/drawable/ic_location_on.xml similarity index 84% rename from catalog/src/main/res/drawable/ic_location_on.xml rename to catalog/src/commonMain/composeResources/drawable/ic_location_on.xml index 9821fffba8..c16679f482 100644 --- a/catalog/src/main/res/drawable/ic_location_on.xml +++ b/catalog/src/commonMain/composeResources/drawable/ic_location_on.xml @@ -2,12 +2,11 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:width="64dp" android:height="64dp" - android:tint="#1A73E8" android:viewportWidth="24" android:viewportHeight="24" > diff --git a/catalog/src/main/res/drawable/ic_modal.xml b/catalog/src/commonMain/composeResources/drawable/ic_modal.xml similarity index 100% rename from catalog/src/main/res/drawable/ic_modal.xml rename to catalog/src/commonMain/composeResources/drawable/ic_modal.xml diff --git a/catalog/src/main/res/drawable/ic_multiplechoice.xml b/catalog/src/commonMain/composeResources/drawable/ic_multiplechoice.xml similarity index 100% rename from catalog/src/main/res/drawable/ic_multiplechoice.xml rename to catalog/src/commonMain/composeResources/drawable/ic_multiplechoice.xml diff --git a/catalog/src/main/res/drawable/ic_openchoice.xml b/catalog/src/commonMain/composeResources/drawable/ic_openchoice.xml similarity index 100% rename from catalog/src/main/res/drawable/ic_openchoice.xml rename to catalog/src/commonMain/composeResources/drawable/ic_openchoice.xml diff --git a/catalog/src/main/res/drawable/ic_paginatedlayout.xml b/catalog/src/commonMain/composeResources/drawable/ic_paginatedlayout.xml similarity index 100% rename from catalog/src/main/res/drawable/ic_paginatedlayout.xml rename to catalog/src/commonMain/composeResources/drawable/ic_paginatedlayout.xml diff --git a/catalog/src/main/res/drawable/ic_rangepicker.xml b/catalog/src/commonMain/composeResources/drawable/ic_rangepicker.xml similarity index 100% rename from catalog/src/main/res/drawable/ic_rangepicker.xml rename to catalog/src/commonMain/composeResources/drawable/ic_rangepicker.xml diff --git a/catalog/src/main/res/drawable/ic_readonlylayout.xml b/catalog/src/commonMain/composeResources/drawable/ic_readonlylayout.xml similarity index 100% rename from catalog/src/main/res/drawable/ic_readonlylayout.xml rename to catalog/src/commonMain/composeResources/drawable/ic_readonlylayout.xml diff --git a/catalog/src/main/res/drawable/ic_repeatgroups.xml b/catalog/src/commonMain/composeResources/drawable/ic_repeatgroups.xml similarity index 100% rename from catalog/src/main/res/drawable/ic_repeatgroups.xml rename to catalog/src/commonMain/composeResources/drawable/ic_repeatgroups.xml diff --git a/catalog/src/main/res/drawable/ic_reviewlayout.xml b/catalog/src/commonMain/composeResources/drawable/ic_reviewlayout.xml similarity index 100% rename from catalog/src/main/res/drawable/ic_reviewlayout.xml rename to catalog/src/commonMain/composeResources/drawable/ic_reviewlayout.xml diff --git a/catalog/src/main/res/drawable/ic_rule.xml b/catalog/src/commonMain/composeResources/drawable/ic_rule.xml similarity index 100% rename from catalog/src/main/res/drawable/ic_rule.xml rename to catalog/src/commonMain/composeResources/drawable/ic_rule.xml diff --git a/catalog/src/main/res/drawable/ic_singlechoice.xml b/catalog/src/commonMain/composeResources/drawable/ic_singlechoice.xml similarity index 100% rename from catalog/src/main/res/drawable/ic_singlechoice.xml rename to catalog/src/commonMain/composeResources/drawable/ic_singlechoice.xml diff --git a/catalog/src/main/res/drawable/ic_skiplogic_behavior.xml b/catalog/src/commonMain/composeResources/drawable/ic_skiplogic_behavior.xml similarity index 100% rename from catalog/src/main/res/drawable/ic_skiplogic_behavior.xml rename to catalog/src/commonMain/composeResources/drawable/ic_skiplogic_behavior.xml diff --git a/catalog/src/main/res/drawable/ic_slider.xml b/catalog/src/commonMain/composeResources/drawable/ic_slider.xml similarity index 100% rename from catalog/src/main/res/drawable/ic_slider.xml rename to catalog/src/commonMain/composeResources/drawable/ic_slider.xml diff --git a/catalog/src/main/res/drawable/ic_textfield.xml b/catalog/src/commonMain/composeResources/drawable/ic_textfield.xml similarity index 100% rename from catalog/src/main/res/drawable/ic_textfield.xml rename to catalog/src/commonMain/composeResources/drawable/ic_textfield.xml diff --git a/catalog/src/main/res/drawable/ic_timepicker.xml b/catalog/src/commonMain/composeResources/drawable/ic_timepicker.xml similarity index 100% rename from catalog/src/main/res/drawable/ic_timepicker.xml rename to catalog/src/commonMain/composeResources/drawable/ic_timepicker.xml diff --git a/catalog/src/main/res/drawable/ic_unitoptions.xml b/catalog/src/commonMain/composeResources/drawable/ic_unitoptions.xml similarity index 100% rename from catalog/src/main/res/drawable/ic_unitoptions.xml rename to catalog/src/commonMain/composeResources/drawable/ic_unitoptions.xml diff --git a/catalog/src/main/res/drawable/text_format_48dp.xml b/catalog/src/commonMain/composeResources/drawable/text_format_48dp.xml similarity index 100% rename from catalog/src/main/res/drawable/text_format_48dp.xml rename to catalog/src/commonMain/composeResources/drawable/text_format_48dp.xml diff --git a/catalog/src/main/assets/behavior_answer_expression.json b/catalog/src/commonMain/composeResources/files/behavior_answer_expression.json similarity index 97% rename from catalog/src/main/assets/behavior_answer_expression.json rename to catalog/src/commonMain/composeResources/files/behavior_answer_expression.json index 5470ff179c..01b07921d6 100644 --- a/catalog/src/main/assets/behavior_answer_expression.json +++ b/catalog/src/commonMain/composeResources/files/behavior_answer_expression.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "extension": [ diff --git a/catalog/src/main/assets/behavior_calculated_expression.json b/catalog/src/commonMain/composeResources/files/behavior_calculated_expression.json similarity index 97% rename from catalog/src/main/assets/behavior_calculated_expression.json rename to catalog/src/commonMain/composeResources/files/behavior_calculated_expression.json index c41b6b0f69..2d1980f7f2 100644 --- a/catalog/src/main/assets/behavior_calculated_expression.json +++ b/catalog/src/commonMain/composeResources/files/behavior_calculated_expression.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "linkId": "birthdate", diff --git a/catalog/src/main/assets/behavior_context_variables.json b/catalog/src/commonMain/composeResources/files/behavior_context_variables.json similarity index 98% rename from catalog/src/main/assets/behavior_context_variables.json rename to catalog/src/commonMain/composeResources/files/behavior_context_variables.json index 9e175dc476..62d8372eeb 100644 --- a/catalog/src/main/assets/behavior_context_variables.json +++ b/catalog/src/commonMain/composeResources/files/behavior_context_variables.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "linkId": "1", diff --git a/catalog/src/main/assets/behavior_dynamic_question_text.json b/catalog/src/commonMain/composeResources/files/behavior_dynamic_question_text.json similarity index 98% rename from catalog/src/main/assets/behavior_dynamic_question_text.json rename to catalog/src/commonMain/composeResources/files/behavior_dynamic_question_text.json index 50af5eca49..7c9916167b 100644 --- a/catalog/src/main/assets/behavior_dynamic_question_text.json +++ b/catalog/src/commonMain/composeResources/files/behavior_dynamic_question_text.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "text": "Choose an option below", diff --git a/catalog/src/main/assets/behavior_questionnaire_constraint.json b/catalog/src/commonMain/composeResources/files/behavior_questionnaire_constraint.json similarity index 99% rename from catalog/src/main/assets/behavior_questionnaire_constraint.json rename to catalog/src/commonMain/composeResources/files/behavior_questionnaire_constraint.json index a592ccb466..3ba983fa7c 100644 --- a/catalog/src/main/assets/behavior_questionnaire_constraint.json +++ b/catalog/src/commonMain/composeResources/files/behavior_questionnaire_constraint.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "linkId": "1", diff --git a/catalog/src/main/assets/behavior_skip_logic.json b/catalog/src/commonMain/composeResources/files/behavior_skip_logic.json similarity index 99% rename from catalog/src/main/assets/behavior_skip_logic.json rename to catalog/src/commonMain/composeResources/files/behavior_skip_logic.json index d0081550ba..9a2fe4f0cc 100644 --- a/catalog/src/main/assets/behavior_skip_logic.json +++ b/catalog/src/commonMain/composeResources/files/behavior_skip_logic.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "extension": [ { "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemExtractionContext", diff --git a/catalog/src/main/assets/behavior_skip_logic_with_expression.json b/catalog/src/commonMain/composeResources/files/behavior_skip_logic_with_expression.json similarity index 85% rename from catalog/src/main/assets/behavior_skip_logic_with_expression.json rename to catalog/src/commonMain/composeResources/files/behavior_skip_logic_with_expression.json index 8151396ba9..ea455ac045 100644 --- a/catalog/src/main/assets/behavior_skip_logic_with_expression.json +++ b/catalog/src/commonMain/composeResources/files/behavior_skip_logic_with_expression.json @@ -1,10 +1,11 @@ { "resourceType": "Questionnaire", + "status": "active", "extension": [ { "url": "http://hl7.org/fhir/StructureDefinition/variable", "valueExpression": { - "name": "has-fever", + "name": "feverish", "language": "text/fhirpath", "expression": "%resource.descendants().where(linkId='1').answer.value" } @@ -31,7 +32,7 @@ } ], "linkId": "1.1", - "text": "Define the questionnaire variable 'has-fever' based on the answer to the question 'Does the patient have a fever?", + "text": "Define the questionnaire variable 'feverish' based on the answer to the question 'Does the patient have a fever?", "type": "display" } ] @@ -45,7 +46,7 @@ "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-enableWhenExpression", "valueExpression": { "language": "text/fhirpath", - "expression": "%has-fever" + "expression": "%feverish" } } ], @@ -65,7 +66,7 @@ } ], "linkId": "2.1", - "text": "Enabled if variable 'has-fever' evaluates to true", + "text": "Enabled if variable 'feverish' evaluates to true", "type": "display" } ] diff --git a/catalog/src/main/assets/component_attachment.json b/catalog/src/commonMain/composeResources/files/component_attachment.json similarity index 98% rename from catalog/src/main/assets/component_attachment.json rename to catalog/src/commonMain/composeResources/files/component_attachment.json index 4a4ce8bf93..eb61bc2430 100644 --- a/catalog/src/main/assets/component_attachment.json +++ b/catalog/src/commonMain/composeResources/files/component_attachment.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "extension": [ diff --git a/catalog/src/main/assets/component_attachment_with_validation.json b/catalog/src/commonMain/composeResources/files/component_attachment_with_validation.json similarity index 98% rename from catalog/src/main/assets/component_attachment_with_validation.json rename to catalog/src/commonMain/composeResources/files/component_attachment_with_validation.json index 6b3aee0303..1689de3a21 100644 --- a/catalog/src/main/assets/component_attachment_with_validation.json +++ b/catalog/src/commonMain/composeResources/files/component_attachment_with_validation.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "extension": [ diff --git a/catalog/src/main/assets/component_auto_complete.json b/catalog/src/commonMain/composeResources/files/component_auto_complete.json similarity index 99% rename from catalog/src/main/assets/component_auto_complete.json rename to catalog/src/commonMain/composeResources/files/component_auto_complete.json index e751258cd2..c9b111f8be 100644 --- a/catalog/src/main/assets/component_auto_complete.json +++ b/catalog/src/commonMain/composeResources/files/component_auto_complete.json @@ -6,6 +6,7 @@ }, "status": "draft", "resourceType": "Questionnaire", + "status": "active", "item": [ { "type": "choice", diff --git a/catalog/src/main/assets/component_auto_complete_with_validation.json b/catalog/src/commonMain/composeResources/files/component_auto_complete_with_validation.json similarity index 99% rename from catalog/src/main/assets/component_auto_complete_with_validation.json rename to catalog/src/commonMain/composeResources/files/component_auto_complete_with_validation.json index 519b94df4c..2fab92ef61 100644 --- a/catalog/src/main/assets/component_auto_complete_with_validation.json +++ b/catalog/src/commonMain/composeResources/files/component_auto_complete_with_validation.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "linkId": "1", diff --git a/catalog/src/commonMain/composeResources/files/component_barcode_widget.json b/catalog/src/commonMain/composeResources/files/component_barcode_widget.json new file mode 100644 index 0000000000..95f3873cd4 --- /dev/null +++ b/catalog/src/commonMain/composeResources/files/component_barcode_widget.json @@ -0,0 +1,26 @@ +{ + "resourceType": "Questionnaire", + "status": "active", + "language": "en", + "date": "2020-11-18T07:24:47.111Z", + "item": [ + { + "linkId": "barcode", + "type": "string", + "text": "Barcode Widget", + "extension": [ + { + "url": "https://github.com/google/android-fhir/StructureDefinition/questionnaire-itemControl", + "valueCodeableConcept": { + "coding": [ + { + "system": "https://github.com/google/android-fhir/questionnaire-item-control", + "code": "barcode" + } + ] + } + } + ] + } + ] +} diff --git a/catalog/src/main/assets/component_boolean_choice.json b/catalog/src/commonMain/composeResources/files/component_boolean_choice.json similarity index 97% rename from catalog/src/main/assets/component_boolean_choice.json rename to catalog/src/commonMain/composeResources/files/component_boolean_choice.json index 9e7c8d137d..6d69aba348 100644 --- a/catalog/src/main/assets/component_boolean_choice.json +++ b/catalog/src/commonMain/composeResources/files/component_boolean_choice.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "linkId": "1", diff --git a/catalog/src/main/assets/component_boolean_choice_with_validation.json b/catalog/src/commonMain/composeResources/files/component_boolean_choice_with_validation.json similarity index 97% rename from catalog/src/main/assets/component_boolean_choice_with_validation.json rename to catalog/src/commonMain/composeResources/files/component_boolean_choice_with_validation.json index b706ee1b2a..07abb60446 100644 --- a/catalog/src/main/assets/component_boolean_choice_with_validation.json +++ b/catalog/src/commonMain/composeResources/files/component_boolean_choice_with_validation.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "linkId": "1", diff --git a/catalog/src/main/assets/component_date_picker.json b/catalog/src/commonMain/composeResources/files/component_date_picker.json similarity index 97% rename from catalog/src/main/assets/component_date_picker.json rename to catalog/src/commonMain/composeResources/files/component_date_picker.json index 473bfc2fe3..c9998338c3 100644 --- a/catalog/src/main/assets/component_date_picker.json +++ b/catalog/src/commonMain/composeResources/files/component_date_picker.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "linkId": "1", diff --git a/catalog/src/main/assets/component_date_picker_with_validation.json b/catalog/src/commonMain/composeResources/files/component_date_picker_with_validation.json similarity index 97% rename from catalog/src/main/assets/component_date_picker_with_validation.json rename to catalog/src/commonMain/composeResources/files/component_date_picker_with_validation.json index 94a6a075a8..cbcd65f87a 100644 --- a/catalog/src/main/assets/component_date_picker_with_validation.json +++ b/catalog/src/commonMain/composeResources/files/component_date_picker_with_validation.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "linkId": "1", diff --git a/catalog/src/main/assets/component_date_time_picker.json b/catalog/src/commonMain/composeResources/files/component_date_time_picker.json similarity index 97% rename from catalog/src/main/assets/component_date_time_picker.json rename to catalog/src/commonMain/composeResources/files/component_date_time_picker.json index 1084fe3225..1ccc316350 100644 --- a/catalog/src/main/assets/component_date_time_picker.json +++ b/catalog/src/commonMain/composeResources/files/component_date_time_picker.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "linkId": "1", diff --git a/catalog/src/main/assets/component_date_time_picker_with_validation.json b/catalog/src/commonMain/composeResources/files/component_date_time_picker_with_validation.json similarity index 88% rename from catalog/src/main/assets/component_date_time_picker_with_validation.json rename to catalog/src/commonMain/composeResources/files/component_date_time_picker_with_validation.json index d82c6ef49f..9b47269d9e 100644 --- a/catalog/src/main/assets/component_date_time_picker_with_validation.json +++ b/catalog/src/commonMain/composeResources/files/component_date_time_picker_with_validation.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "linkId": "1", diff --git a/catalog/src/main/assets/component_dropdown.json b/catalog/src/commonMain/composeResources/files/component_dropdown.json similarity index 98% rename from catalog/src/main/assets/component_dropdown.json rename to catalog/src/commonMain/composeResources/files/component_dropdown.json index f5797d05dc..f7407a5626 100644 --- a/catalog/src/main/assets/component_dropdown.json +++ b/catalog/src/commonMain/composeResources/files/component_dropdown.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "linkId": "1", diff --git a/catalog/src/main/assets/component_dropdown_with_validation.json b/catalog/src/commonMain/composeResources/files/component_dropdown_with_validation.json similarity index 98% rename from catalog/src/main/assets/component_dropdown_with_validation.json rename to catalog/src/commonMain/composeResources/files/component_dropdown_with_validation.json index d20a24a75e..85d907879b 100644 --- a/catalog/src/main/assets/component_dropdown_with_validation.json +++ b/catalog/src/commonMain/composeResources/files/component_dropdown_with_validation.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "linkId": "1", diff --git a/catalog/src/main/assets/component_help.json b/catalog/src/commonMain/composeResources/files/component_help.json similarity index 98% rename from catalog/src/main/assets/component_help.json rename to catalog/src/commonMain/composeResources/files/component_help.json index 86f6eac6c7..eb4d935977 100644 --- a/catalog/src/main/assets/component_help.json +++ b/catalog/src/commonMain/composeResources/files/component_help.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "linkId": "1", diff --git a/catalog/src/main/assets/component_initial_value.json b/catalog/src/commonMain/composeResources/files/component_initial_value.json similarity index 99% rename from catalog/src/main/assets/component_initial_value.json rename to catalog/src/commonMain/composeResources/files/component_initial_value.json index 1a294ef2f5..50112ef173 100644 --- a/catalog/src/main/assets/component_initial_value.json +++ b/catalog/src/commonMain/composeResources/files/component_initial_value.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "linkId": "1.0", diff --git a/catalog/src/main/assets/component_item_media.json b/catalog/src/commonMain/composeResources/files/component_item_media.json similarity index 99% rename from catalog/src/main/assets/component_item_media.json rename to catalog/src/commonMain/composeResources/files/component_item_media.json index 902dfb72ca..723c56d07f 100644 --- a/catalog/src/main/assets/component_item_media.json +++ b/catalog/src/commonMain/composeResources/files/component_item_media.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "linkId": "1.1", diff --git a/catalog/src/main/assets/component_location_widget.json b/catalog/src/commonMain/composeResources/files/component_location_widget.json similarity index 100% rename from catalog/src/main/assets/component_location_widget.json rename to catalog/src/commonMain/composeResources/files/component_location_widget.json index 73c26da715..5ac3d947d1 100644 --- a/catalog/src/main/assets/component_location_widget.json +++ b/catalog/src/commonMain/composeResources/files/component_location_widget.json @@ -1,7 +1,7 @@ { "resourceType": "Questionnaire", - "language": "en", "status": "active", + "language": "en", "date": "2020-11-18T07:24:47.111Z", "item": [ { diff --git a/catalog/src/main/assets/component_modal.json b/catalog/src/commonMain/composeResources/files/component_modal.json similarity index 98% rename from catalog/src/main/assets/component_modal.json rename to catalog/src/commonMain/composeResources/files/component_modal.json index 54b8dc9222..a9a31f27c2 100644 --- a/catalog/src/main/assets/component_modal.json +++ b/catalog/src/commonMain/composeResources/files/component_modal.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "extension": [ diff --git a/catalog/src/main/assets/component_modal_with_validation.json b/catalog/src/commonMain/composeResources/files/component_modal_with_validation.json similarity index 98% rename from catalog/src/main/assets/component_modal_with_validation.json rename to catalog/src/commonMain/composeResources/files/component_modal_with_validation.json index 49a08f569c..735db07de8 100644 --- a/catalog/src/main/assets/component_modal_with_validation.json +++ b/catalog/src/commonMain/composeResources/files/component_modal_with_validation.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "linkId": "1.1", diff --git a/catalog/src/main/assets/component_multi_select_choice.json b/catalog/src/commonMain/composeResources/files/component_multi_select_choice.json similarity index 99% rename from catalog/src/main/assets/component_multi_select_choice.json rename to catalog/src/commonMain/composeResources/files/component_multi_select_choice.json index 8e5a72c3e2..c76871b24d 100644 --- a/catalog/src/main/assets/component_multi_select_choice.json +++ b/catalog/src/commonMain/composeResources/files/component_multi_select_choice.json @@ -3,6 +3,7 @@ "status": "active", "version": "0.0.1", "resourceType": "Questionnaire", + "status": "active", "item": [ { "linkId": "1.0.0", diff --git a/catalog/src/main/assets/component_multi_select_choice_with_validation.json b/catalog/src/commonMain/composeResources/files/component_multi_select_choice_with_validation.json similarity index 99% rename from catalog/src/main/assets/component_multi_select_choice_with_validation.json rename to catalog/src/commonMain/composeResources/files/component_multi_select_choice_with_validation.json index 415f17faa4..fdeff244fa 100644 --- a/catalog/src/main/assets/component_multi_select_choice_with_validation.json +++ b/catalog/src/commonMain/composeResources/files/component_multi_select_choice_with_validation.json @@ -3,6 +3,7 @@ "status": "active", "version": "0.0.1", "resourceType": "Questionnaire", + "status": "active", "item": [ { "linkId": "1.0.0", diff --git a/catalog/src/main/assets/component_open_choice.json b/catalog/src/commonMain/composeResources/files/component_open_choice.json similarity index 98% rename from catalog/src/main/assets/component_open_choice.json rename to catalog/src/commonMain/composeResources/files/component_open_choice.json index f9a8b36b72..7c848ed98e 100644 --- a/catalog/src/main/assets/component_open_choice.json +++ b/catalog/src/commonMain/composeResources/files/component_open_choice.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "linkId": "1", diff --git a/catalog/src/main/assets/component_open_choice_with_validation.json b/catalog/src/commonMain/composeResources/files/component_open_choice_with_validation.json similarity index 98% rename from catalog/src/main/assets/component_open_choice_with_validation.json rename to catalog/src/commonMain/composeResources/files/component_open_choice_with_validation.json index b12fc6f908..59cc0cdd5a 100644 --- a/catalog/src/main/assets/component_open_choice_with_validation.json +++ b/catalog/src/commonMain/composeResources/files/component_open_choice_with_validation.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "type": "choice", diff --git a/catalog/src/main/assets/component_per_question_custom_style.json b/catalog/src/commonMain/composeResources/files/component_per_question_custom_style.json similarity index 99% rename from catalog/src/main/assets/component_per_question_custom_style.json rename to catalog/src/commonMain/composeResources/files/component_per_question_custom_style.json index 2f2e8f3b86..37b926863c 100644 --- a/catalog/src/main/assets/component_per_question_custom_style.json +++ b/catalog/src/commonMain/composeResources/files/component_per_question_custom_style.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "linkId": "1", diff --git a/catalog/src/main/assets/component_quantity.json b/catalog/src/commonMain/composeResources/files/component_quantity.json similarity index 96% rename from catalog/src/main/assets/component_quantity.json rename to catalog/src/commonMain/composeResources/files/component_quantity.json index 93f5d1b682..54fdfa682b 100644 --- a/catalog/src/main/assets/component_quantity.json +++ b/catalog/src/commonMain/composeResources/files/component_quantity.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "linkId": "1", diff --git a/catalog/src/main/assets/component_quantity_with_validation.json b/catalog/src/commonMain/composeResources/files/component_quantity_with_validation.json similarity index 96% rename from catalog/src/main/assets/component_quantity_with_validation.json rename to catalog/src/commonMain/composeResources/files/component_quantity_with_validation.json index 39760c474c..27196f431f 100644 --- a/catalog/src/main/assets/component_quantity_with_validation.json +++ b/catalog/src/commonMain/composeResources/files/component_quantity_with_validation.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "linkId": "1", diff --git a/catalog/src/main/assets/component_repeated_group.json b/catalog/src/commonMain/composeResources/files/component_repeated_group.json similarity index 99% rename from catalog/src/main/assets/component_repeated_group.json rename to catalog/src/commonMain/composeResources/files/component_repeated_group.json index 683323e799..0a11783d1e 100644 --- a/catalog/src/main/assets/component_repeated_group.json +++ b/catalog/src/commonMain/composeResources/files/component_repeated_group.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "linkId": "1", diff --git a/catalog/src/main/assets/component_single_choice.json b/catalog/src/commonMain/composeResources/files/component_single_choice.json similarity index 98% rename from catalog/src/main/assets/component_single_choice.json rename to catalog/src/commonMain/composeResources/files/component_single_choice.json index 81b2000cf2..9ff1f8c7c7 100644 --- a/catalog/src/main/assets/component_single_choice.json +++ b/catalog/src/commonMain/composeResources/files/component_single_choice.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "extension": [ diff --git a/catalog/src/main/assets/component_single_choice_with_validation.json b/catalog/src/commonMain/composeResources/files/component_single_choice_with_validation.json similarity index 98% rename from catalog/src/main/assets/component_single_choice_with_validation.json rename to catalog/src/commonMain/composeResources/files/component_single_choice_with_validation.json index 25e4db11d1..63a83a9c40 100644 --- a/catalog/src/main/assets/component_single_choice_with_validation.json +++ b/catalog/src/commonMain/composeResources/files/component_single_choice_with_validation.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "extension": [ diff --git a/catalog/src/main/assets/component_slider.json b/catalog/src/commonMain/composeResources/files/component_slider.json similarity index 96% rename from catalog/src/main/assets/component_slider.json rename to catalog/src/commonMain/composeResources/files/component_slider.json index d2b58c5fd6..db2d401469 100644 --- a/catalog/src/main/assets/component_slider.json +++ b/catalog/src/commonMain/composeResources/files/component_slider.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "linkId": "1", diff --git a/catalog/src/main/assets/component_slider_with_validation.json b/catalog/src/commonMain/composeResources/files/component_slider_with_validation.json similarity index 98% rename from catalog/src/main/assets/component_slider_with_validation.json rename to catalog/src/commonMain/composeResources/files/component_slider_with_validation.json index 16abca1843..91b6e77c2e 100644 --- a/catalog/src/main/assets/component_slider_with_validation.json +++ b/catalog/src/commonMain/composeResources/files/component_slider_with_validation.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "extension": [ diff --git a/catalog/src/main/assets/component_text_fields.json b/catalog/src/commonMain/composeResources/files/component_text_fields.json similarity index 99% rename from catalog/src/main/assets/component_text_fields.json rename to catalog/src/commonMain/composeResources/files/component_text_fields.json index 51c35a7b05..0449126cc9 100644 --- a/catalog/src/main/assets/component_text_fields.json +++ b/catalog/src/commonMain/composeResources/files/component_text_fields.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "linkId": "1", diff --git a/catalog/src/main/assets/component_text_fields_with_validation.json b/catalog/src/commonMain/composeResources/files/component_text_fields_with_validation.json similarity index 99% rename from catalog/src/main/assets/component_text_fields_with_validation.json rename to catalog/src/commonMain/composeResources/files/component_text_fields_with_validation.json index 3bacc243b5..d0e3df8304 100644 --- a/catalog/src/main/assets/component_text_fields_with_validation.json +++ b/catalog/src/commonMain/composeResources/files/component_text_fields_with_validation.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "linkId": "1", diff --git a/catalog/src/main/assets/component_time_picker.json b/catalog/src/commonMain/composeResources/files/component_time_picker.json similarity index 97% rename from catalog/src/main/assets/component_time_picker.json rename to catalog/src/commonMain/composeResources/files/component_time_picker.json index ac904d99c8..5e9c64233a 100644 --- a/catalog/src/main/assets/component_time_picker.json +++ b/catalog/src/commonMain/composeResources/files/component_time_picker.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "linkId": "1", diff --git a/catalog/src/main/assets/component_time_picker_with_validation.json b/catalog/src/commonMain/composeResources/files/component_time_picker_with_validation.json similarity index 97% rename from catalog/src/main/assets/component_time_picker_with_validation.json rename to catalog/src/commonMain/composeResources/files/component_time_picker_with_validation.json index 198a8f5bea..041bbcaedf 100644 --- a/catalog/src/main/assets/component_time_picker_with_validation.json +++ b/catalog/src/commonMain/composeResources/files/component_time_picker_with_validation.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "linkId": "1", diff --git a/catalog/src/main/assets/layout_default.json b/catalog/src/commonMain/composeResources/files/layout_default.json similarity index 99% rename from catalog/src/main/assets/layout_default.json rename to catalog/src/commonMain/composeResources/files/layout_default.json index 29b841381e..54ec1a92b5 100644 --- a/catalog/src/main/assets/layout_default.json +++ b/catalog/src/commonMain/composeResources/files/layout_default.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "linkId": "1", diff --git a/catalog/src/main/assets/layout_paginated.json b/catalog/src/commonMain/composeResources/files/layout_paginated.json similarity index 99% rename from catalog/src/main/assets/layout_paginated.json rename to catalog/src/commonMain/composeResources/files/layout_paginated.json index bf2e339892..a7a3e12132 100644 --- a/catalog/src/main/assets/layout_paginated.json +++ b/catalog/src/commonMain/composeResources/files/layout_paginated.json @@ -1,5 +1,6 @@ { "resourceType": "Questionnaire", + "status": "active", "item": [ { "linkId": "1", diff --git a/catalog/src/main/assets/layout_review.json b/catalog/src/commonMain/composeResources/files/layout_review.json similarity index 99% rename from catalog/src/main/assets/layout_review.json rename to catalog/src/commonMain/composeResources/files/layout_review.json index 1e0a68975b..5427280660 100644 --- a/catalog/src/main/assets/layout_review.json +++ b/catalog/src/commonMain/composeResources/files/layout_review.json @@ -1,6 +1,7 @@ { "title": "COVID-19 Screening", "resourceType": "Questionnaire", + "status": "active", "meta": { "profile": [ "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire|2.7" diff --git a/catalog/src/main/assets/resource_data_bundle.json b/catalog/src/commonMain/composeResources/files/resource_data_bundle.json similarity index 100% rename from catalog/src/main/assets/resource_data_bundle.json rename to catalog/src/commonMain/composeResources/files/resource_data_bundle.json diff --git a/catalog/src/main/res/values/strings.xml b/catalog/src/commonMain/composeResources/values/strings.xml similarity index 90% rename from catalog/src/main/res/values/strings.xml rename to catalog/src/commonMain/composeResources/values/strings.xml index 69b5f26fa8..7874351f0e 100644 --- a/catalog/src/main/res/values/strings.xml +++ b/catalog/src/commonMain/composeResources/values/strings.xml @@ -38,6 +38,7 @@ Repeated Group Attachment Location Widget + Barcode Widget Per question custom style @@ -83,4 +84,12 @@ Widgets Miscellaneous components Open questionnaire + Errors found + Fix the following questions: + • %s + Fix questions + Submit anyway + Loading... diff --git a/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/App.kt b/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/App.kt new file mode 100644 index 0000000000..8516550a4d --- /dev/null +++ b/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/App.kt @@ -0,0 +1,227 @@ +/* + * Copyright 2025-2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.fhir.catalog + +import android_fhir.catalog.generated.resources.Res +import android_fhir.catalog.generated.resources.ic_behaviors +import android_fhir.catalog.generated.resources.ic_components +import android_fhir.catalog.generated.resources.ic_layouts +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Icon +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.Scaffold +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.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavDestination.Companion.hierarchy +import androidx.navigation.NavGraph.Companion.findStartDestination +import androidx.navigation.NavHostController +import androidx.navigation.NavType +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController +import androidx.navigation.navArgument +import androidx.savedstate.read +import com.google.android.fhir.catalog.ui.behaviors.BehaviorListScreen +import com.google.android.fhir.catalog.ui.behaviors.BehaviorListViewModel +import com.google.android.fhir.catalog.ui.components.ComponentListScreen +import com.google.android.fhir.catalog.ui.components.ComponentListViewModel +import com.google.android.fhir.catalog.ui.layouts.LayoutListScreen +import com.google.android.fhir.catalog.ui.layouts.LayoutListViewModel +import com.google.android.fhir.catalog.ui.questionnaire.QuestionnaireResponseScreen +import com.google.android.fhir.catalog.ui.questionnaire.QuestionnaireScreen +import com.google.android.fhir.catalog.ui.questionnaire.QuestionnaireViewModel +import com.google.android.fhir.catalog.ui.theme.AppTheme +import kotlinx.coroutines.CoroutineScope +import org.jetbrains.compose.resources.painterResource + +sealed class Screen(val route: String, val label: String, val icon: @Composable () -> Unit) { + object Components : + Screen( + "component_list", + "Components", + { Icon(painterResource(Res.drawable.ic_components), contentDescription = null) }, + ) + + object Layouts : + Screen( + "layout_list", + "Layouts", + { Icon(painterResource(Res.drawable.ic_layouts), contentDescription = null) }, + ) + + object Behaviors : + Screen( + "behavior_list", + "Behaviors", + { Icon(painterResource(Res.drawable.ic_behaviors), contentDescription = null) }, + ) +} + +@Composable +fun App() { + AppTheme { + Surface { + val navController: NavHostController = rememberNavController() + val scope: CoroutineScope = rememberCoroutineScope() + val componentViewModel: ComponentListViewModel = viewModel { ComponentListViewModel() } + val layoutViewModel: LayoutListViewModel = viewModel { LayoutListViewModel() } + val behaviorViewModel: BehaviorListViewModel = viewModel { BehaviorListViewModel() } + + var submittedResponseJson by remember { mutableStateOf(null) } + + val navBackStackEntry by navController.currentBackStackEntryAsState() + val currentDestination = navBackStackEntry?.destination + + val items = listOf(Screen.Components, Screen.Layouts, Screen.Behaviors) + + Scaffold( + bottomBar = { + if (items.any { it.route == currentDestination?.route }) { + NavigationBar { + items.forEach { screen -> + NavigationBarItem( + icon = screen.icon, + label = { Text(screen.label) }, + selected = + currentDestination?.hierarchy?.any { it.route == screen.route } == true, + onClick = { + navController.navigate(screen.route) { + popUpTo(navController.graph.findStartDestination().id) { saveState = true } + launchSingleTop = true + restoreState = true + } + }, + ) + } + } + } + }, + ) { innerPadding -> + NavHost( + navController = navController, + startDestination = Screen.Components.route, + modifier = Modifier.padding(innerPadding), + ) { + composable(Screen.Components.route) { + ComponentListScreen( + viewModel = componentViewModel, + onComponentClick = { component, title -> + val validationPart = + component.questionnaireFileWithValidation?.let { "?validationFile=$it" } ?: "" + navController.navigate( + "questionnaire/${component.questionnaireFile}/$title/false/false/false$validationPart", + ) + }, + ) + } + composable(Screen.Layouts.route) { + LayoutListScreen( + viewModel = layoutViewModel, + onLayoutClick = { layout, title -> + navController.navigate( + "questionnaire/${layout.questionnaireFileName}/$title/${layout.showReviewPage}/${layout.showReviewPageFirst}/${layout.isReadOnly}", + ) + }, + ) + } + composable(Screen.Behaviors.route) { + BehaviorListScreen( + viewModel = behaviorViewModel, + onBehaviorClick = { behavior, title -> + navController.navigate( + "questionnaire/${behavior.questionnaireFileName}/$title/false/false/false", + ) + }, + ) + } + composable( + route = + "questionnaire/{fileName}/{title}/{review}/{reviewFirst}/{readOnly}?validationFile={validationFile}", + arguments = + listOf( + navArgument("fileName") { type = NavType.StringType }, + navArgument("title") { type = NavType.StringType }, + navArgument("review") { type = NavType.BoolType }, + navArgument("reviewFirst") { type = NavType.BoolType }, + navArgument("readOnly") { type = NavType.BoolType }, + navArgument("validationFile") { + type = NavType.StringType + nullable = true + }, + ), + ) { backStackEntry -> + val arguments = backStackEntry.arguments + val fileName = + arguments?.read { if (contains("fileName")) getString("fileName") else null } ?: "" + val title = + arguments?.read { if (contains("title")) getString("title") else null } ?: "" + val review = + arguments?.read { if (contains("review")) getBoolean("review") else null } ?: false + val reviewFirst = + arguments?.read { if (contains("reviewFirst")) getBoolean("reviewFirst") else null } + ?: false + val readOnly = + arguments?.read { if (contains("readOnly")) getBoolean("readOnly") else null } + ?: false + val validationFile = + arguments?.read { + if (contains("validationFile")) getString("validationFile") else null + } + + QuestionnaireScreen( + viewModel = viewModel { QuestionnaireViewModel() }, + title = title, + fileName = fileName, + validationFileName = validationFile, + showReviewPage = review, + showReviewPageFirst = reviewFirst, + isReadOnly = readOnly, + coroutineScope = scope, + onBackClick = { navController.popBackStack() }, + navigateToResponse = { responseJson -> + submittedResponseJson = responseJson + navController.navigate("questionnaire_response/$title") + }, + ) + } + composable( + route = "questionnaire_response/{title}", + arguments = listOf(navArgument("title") { type = NavType.StringType }), + ) { backStackEntry -> + val arguments = backStackEntry.arguments + val title = + arguments?.read { if (contains("title")) getString("title") else null } ?: "" + QuestionnaireResponseScreen( + responseJson = submittedResponseJson ?: "", + onBackClick = { navController.popBackStack() }, + ) + } + } + } + } + } +} diff --git a/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/behaviors/BehaviorListScreen.kt b/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/behaviors/BehaviorListScreen.kt new file mode 100644 index 0000000000..a7dd057d09 --- /dev/null +++ b/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/behaviors/BehaviorListScreen.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2025-2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.fhir.catalog.ui.behaviors + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.google.android.fhir.catalog.ui.shared.CatalogItemCard +import com.google.android.fhir.catalog.ui.shared.CatalogTopAppBar +import org.jetbrains.compose.resources.stringResource + +@Composable +fun BehaviorListScreen( + viewModel: BehaviorListViewModel, + onBehaviorClick: (BehaviorListViewModel.Behavior, String) -> Unit, +) { + Scaffold( + topBar = { CatalogTopAppBar() }, + ) { padding -> + LazyVerticalGrid( + columns = GridCells.Fixed(2), + modifier = Modifier.fillMaxSize().padding(padding), + contentPadding = PaddingValues(8.dp), + ) { + items(viewModel.getBehaviorList()) { behavior -> + val title = stringResource(behavior.text) + CatalogItemCard( + icon = behavior.icon, + text = title, + onClick = { onBehaviorClick(behavior, title) }, + ) + } + } + } +} diff --git a/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/behaviors/BehaviorListViewModel.kt b/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/behaviors/BehaviorListViewModel.kt new file mode 100644 index 0000000000..6023bb3d5d --- /dev/null +++ b/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/behaviors/BehaviorListViewModel.kt @@ -0,0 +1,84 @@ +/* + * Copyright 2022-2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.fhir.catalog.ui.behaviors + +import android_fhir.catalog.generated.resources.Res +import android_fhir.catalog.generated.resources.behavior_name_answer_expression +import android_fhir.catalog.generated.resources.behavior_name_calculated_expression +import android_fhir.catalog.generated.resources.behavior_name_context_variables +import android_fhir.catalog.generated.resources.behavior_name_dynamic_question_text +import android_fhir.catalog.generated.resources.behavior_name_questionnaire_constraint +import android_fhir.catalog.generated.resources.behavior_name_skip_logic +import android_fhir.catalog.generated.resources.behavior_name_skip_logic_with_expression +import android_fhir.catalog.generated.resources.ic_answers_behavior +import android_fhir.catalog.generated.resources.ic_calculations_behavior +import android_fhir.catalog.generated.resources.ic_context +import android_fhir.catalog.generated.resources.ic_dynamic_text_behavior +import android_fhir.catalog.generated.resources.ic_rule +import android_fhir.catalog.generated.resources.ic_skiplogic_behavior +import androidx.lifecycle.ViewModel +import org.jetbrains.compose.resources.DrawableResource +import org.jetbrains.compose.resources.StringResource + +class BehaviorListViewModel : ViewModel() { + + fun getBehaviorList(): List { + return Behavior.entries + } + + enum class Behavior( + val questionnaireFileName: String, + val icon: DrawableResource, + val text: StringResource, + ) { + CALCULATED_EXPRESSION( + "behavior_calculated_expression.json", + Res.drawable.ic_calculations_behavior, + Res.string.behavior_name_calculated_expression, + ), + ANSWER_EXPRESSION( + "behavior_answer_expression.json", + Res.drawable.ic_answers_behavior, + Res.string.behavior_name_answer_expression, + ), + CONTEXT_VARIABLES( + "behavior_context_variables.json", + Res.drawable.ic_context, + Res.string.behavior_name_context_variables, + ), + SKIP_LOGIC( + "behavior_skip_logic.json", + Res.drawable.ic_skiplogic_behavior, + Res.string.behavior_name_skip_logic, + ), + SKIP_LOGIC_WITH_EXPRESSION( + "behavior_skip_logic_with_expression.json", + Res.drawable.ic_skiplogic_behavior, + Res.string.behavior_name_skip_logic_with_expression, + ), + DYNAMIC_QUESTION_TEXT( + "behavior_dynamic_question_text.json", + Res.drawable.ic_dynamic_text_behavior, + Res.string.behavior_name_dynamic_question_text, + ), + QUESTIONNAIRE_CONSTRAINT( + "behavior_questionnaire_constraint.json", + Res.drawable.ic_rule, + Res.string.behavior_name_questionnaire_constraint, + ), + } +} diff --git a/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/components/ComponentListScreen.kt b/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/components/ComponentListScreen.kt new file mode 100644 index 0000000000..1654ba94c9 --- /dev/null +++ b/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/components/ComponentListScreen.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2025-2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.fhir.catalog.ui.components + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.google.android.fhir.catalog.ui.shared.CatalogItemCard +import com.google.android.fhir.catalog.ui.shared.CatalogTopAppBar +import org.jetbrains.compose.resources.stringResource + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ComponentListScreen( + viewModel: ComponentListViewModel, + onComponentClick: (ComponentListViewModel.Component, String) -> Unit, +) { + Scaffold( + topBar = { CatalogTopAppBar() }, + ) { padding -> + LazyVerticalGrid( + columns = GridCells.Fixed(2), + modifier = Modifier.fillMaxSize().padding(padding), + contentPadding = PaddingValues(8.dp), + ) { + viewModel.viewItemList.forEach { item -> + when (item) { + is ComponentListViewModel.ViewItem.HeaderItem -> { + item(span = { GridItemSpan(2) }) { + Text( + text = stringResource(item.header.title), + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Normal, + modifier = Modifier.padding(16.dp).fillMaxWidth(), + textAlign = TextAlign.Center, + ) + } + } + is ComponentListViewModel.ViewItem.ComponentItem -> { + item { + val title = stringResource(item.component.text) + CatalogItemCard( + icon = item.component.icon, + text = title, + onClick = { onComponentClick(item.component, title) }, + ) + } + } + } + } + } + } +} diff --git a/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/components/ComponentListViewModel.kt b/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/components/ComponentListViewModel.kt new file mode 100644 index 0000000000..26249adf1d --- /dev/null +++ b/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/components/ComponentListViewModel.kt @@ -0,0 +1,241 @@ +/* + * Copyright 2023-2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.fhir.catalog.ui.components + +import android_fhir.catalog.generated.resources.Res +import android_fhir.catalog.generated.resources.component_name_attachment +import android_fhir.catalog.generated.resources.component_name_auto_complete +import android_fhir.catalog.generated.resources.component_name_barcode_widget +import android_fhir.catalog.generated.resources.component_name_boolean_choice +import android_fhir.catalog.generated.resources.component_name_date_picker +import android_fhir.catalog.generated.resources.component_name_date_time_picker +import android_fhir.catalog.generated.resources.component_name_dropdown +import android_fhir.catalog.generated.resources.component_name_help +import android_fhir.catalog.generated.resources.component_name_initial_value +import android_fhir.catalog.generated.resources.component_name_item_answer_media +import android_fhir.catalog.generated.resources.component_name_item_media +import android_fhir.catalog.generated.resources.component_name_location_widget +import android_fhir.catalog.generated.resources.component_name_modal +import android_fhir.catalog.generated.resources.component_name_multiple_choice +import android_fhir.catalog.generated.resources.component_name_open_choice +import android_fhir.catalog.generated.resources.component_name_per_question_custom_style +import android_fhir.catalog.generated.resources.component_name_quantity +import android_fhir.catalog.generated.resources.component_name_repeated_group +import android_fhir.catalog.generated.resources.component_name_single_choice +import android_fhir.catalog.generated.resources.component_name_slider +import android_fhir.catalog.generated.resources.component_name_text_field +import android_fhir.catalog.generated.resources.component_name_time_picker +import android_fhir.catalog.generated.resources.ic_attachment +import android_fhir.catalog.generated.resources.ic_autocomplete +import android_fhir.catalog.generated.resources.ic_barcode +import android_fhir.catalog.generated.resources.ic_booleanchoice +import android_fhir.catalog.generated.resources.ic_datepicker +import android_fhir.catalog.generated.resources.ic_group_1278 +import android_fhir.catalog.generated.resources.ic_help +import android_fhir.catalog.generated.resources.ic_initial_value_component +import android_fhir.catalog.generated.resources.ic_item_answer_media +import android_fhir.catalog.generated.resources.ic_item_media +import android_fhir.catalog.generated.resources.ic_location_on +import android_fhir.catalog.generated.resources.ic_modal +import android_fhir.catalog.generated.resources.ic_multiplechoice +import android_fhir.catalog.generated.resources.ic_openchoice +import android_fhir.catalog.generated.resources.ic_repeatgroups +import android_fhir.catalog.generated.resources.ic_singlechoice +import android_fhir.catalog.generated.resources.ic_slider +import android_fhir.catalog.generated.resources.ic_textfield +import android_fhir.catalog.generated.resources.ic_timepicker +import android_fhir.catalog.generated.resources.ic_unitoptions +import android_fhir.catalog.generated.resources.misc_components +import android_fhir.catalog.generated.resources.text_format_48dp +import android_fhir.catalog.generated.resources.widgets +import androidx.lifecycle.ViewModel +import org.jetbrains.compose.resources.DrawableResource +import org.jetbrains.compose.resources.StringResource + +class ComponentListViewModel : ViewModel() { + + sealed class ViewItem { + data class HeaderItem(val header: Header) : ViewItem() + + data class ComponentItem(val component: Component) : ViewItem() + } + + enum class Header(val title: StringResource) { + WIDGETS(Res.string.widgets), + MISC_COMPONENTS(Res.string.misc_components), + } + + enum class Component( + val questionnaireFile: String, + val icon: DrawableResource, + val text: StringResource, + val questionnaireFileWithValidation: String? = null, + ) { + BOOLEAN_CHOICE( + "component_boolean_choice.json", + Res.drawable.ic_booleanchoice, + Res.string.component_name_boolean_choice, + "component_boolean_choice_with_validation.json", + ), + SINGLE_CHOICE( + "component_single_choice.json", + Res.drawable.ic_singlechoice, + Res.string.component_name_single_choice, + "component_single_choice_with_validation.json", + ), + MULTIPLE_CHOICE( + "component_multi_select_choice.json", + Res.drawable.ic_multiplechoice, + Res.string.component_name_multiple_choice, + "component_multi_select_choice_with_validation.json", + ), + DROPDOWN( + "component_dropdown.json", + Res.drawable.ic_group_1278, + Res.string.component_name_dropdown, + "component_dropdown_with_validation.json", + ), + MODAL( + "component_modal.json", + Res.drawable.ic_modal, + Res.string.component_name_modal, + "component_modal_with_validation.json", + ), + OPEN_CHOICE( + "component_open_choice.json", + Res.drawable.ic_openchoice, + Res.string.component_name_open_choice, + "component_open_choice_with_validation.json", + ), + TEXT_FIELD( + "component_text_fields.json", + Res.drawable.ic_textfield, + Res.string.component_name_text_field, + "component_text_fields_with_validation.json", + ), + AUTO_COMPLETE( + "component_auto_complete.json", + Res.drawable.ic_autocomplete, + Res.string.component_name_auto_complete, + "component_auto_complete_with_validation.json", + ), + DATE_PICKER( + "component_date_picker.json", + Res.drawable.ic_datepicker, + Res.string.component_name_date_picker, + "component_date_picker_with_validation.json", + ), + TIME_PICKER( + "component_time_picker.json", + Res.drawable.ic_timepicker, + Res.string.component_name_time_picker, + "component_time_picker_with_validation.json", + ), + DATE_TIME_PICKER( + "component_date_time_picker.json", + Res.drawable.ic_timepicker, + Res.string.component_name_date_time_picker, + "component_date_time_picker_with_validation.json", + ), + SLIDER( + "component_slider.json", + Res.drawable.ic_slider, + Res.string.component_name_slider, + "component_slider_with_validation.json", + ), + QUANTITY( + "component_quantity.json", + Res.drawable.ic_unitoptions, + Res.string.component_name_quantity, + "component_quantity_with_validation.json", + ), + ATTACHMENT( + "component_attachment.json", + Res.drawable.ic_attachment, + Res.string.component_name_attachment, + "component_attachment_with_validation.json", + ), + REPEATED_GROUP( + "component_repeated_group.json", + Res.drawable.ic_repeatgroups, + Res.string.component_name_repeated_group, + ), + HELP( + "component_help.json", + Res.drawable.ic_help, + Res.string.component_name_help, + ), + ITEM_MEDIA( + "component_item_media.json", + Res.drawable.ic_item_media, + Res.string.component_name_item_media, + ), + ITEM_ANSWER_MEDIA( + "", + Res.drawable.ic_item_answer_media, + Res.string.component_name_item_answer_media, + ), + INITIAL_VALUE( + "component_initial_value.json", + Res.drawable.ic_initial_value_component, + Res.string.component_name_initial_value, + ), + LOCATION_WIDGET( + "component_location_widget.json", + Res.drawable.ic_location_on, + Res.string.component_name_location_widget, + ), + BARCODE_WIDGET( + "component_barcode_widget.json", + Res.drawable.ic_barcode, + Res.string.component_name_barcode_widget, + ), + QUESTION_ITEM_CUSTOM_STYLE( + "component_per_question_custom_style.json", + Res.drawable.text_format_48dp, + Res.string.component_name_per_question_custom_style, + ), + } + + val viewItemList = + listOf( + ViewItem.HeaderItem(Header.WIDGETS), + ViewItem.ComponentItem(Component.BOOLEAN_CHOICE), + ViewItem.ComponentItem(Component.SINGLE_CHOICE), + ViewItem.ComponentItem(Component.MULTIPLE_CHOICE), + ViewItem.ComponentItem(Component.DROPDOWN), + ViewItem.ComponentItem(Component.MODAL), + ViewItem.ComponentItem(Component.OPEN_CHOICE), + ViewItem.ComponentItem(Component.TEXT_FIELD), + ViewItem.ComponentItem(Component.AUTO_COMPLETE), + ViewItem.ComponentItem(Component.DATE_PICKER), + ViewItem.ComponentItem(Component.TIME_PICKER), + ViewItem.ComponentItem(Component.DATE_TIME_PICKER), + ViewItem.ComponentItem(Component.SLIDER), + ViewItem.ComponentItem(Component.QUANTITY), + ViewItem.ComponentItem(Component.ATTACHMENT), + ViewItem.ComponentItem(Component.REPEATED_GROUP), + ViewItem.HeaderItem(Header.MISC_COMPONENTS), + ViewItem.ComponentItem(Component.HELP), + ViewItem.ComponentItem(Component.ITEM_MEDIA), + ViewItem.ComponentItem(Component.ITEM_ANSWER_MEDIA), + ViewItem.ComponentItem(Component.INITIAL_VALUE), + ViewItem.ComponentItem(Component.LOCATION_WIDGET), + ViewItem.ComponentItem(Component.BARCODE_WIDGET), + ViewItem.ComponentItem(Component.QUESTION_ITEM_CUSTOM_STYLE), + ) +} diff --git a/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/layouts/LayoutListScreen.kt b/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/layouts/LayoutListScreen.kt new file mode 100644 index 0000000000..143eadee9f --- /dev/null +++ b/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/layouts/LayoutListScreen.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2025-2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.fhir.catalog.ui.layouts + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.google.android.fhir.catalog.ui.shared.CatalogItemCard +import com.google.android.fhir.catalog.ui.shared.CatalogTopAppBar +import org.jetbrains.compose.resources.stringResource + +@Composable +fun LayoutListScreen( + viewModel: LayoutListViewModel, + onLayoutClick: (LayoutListViewModel.Layout, String) -> Unit, +) { + Scaffold( + topBar = { CatalogTopAppBar() }, + ) { padding -> + LazyVerticalGrid( + columns = GridCells.Fixed(2), + modifier = Modifier.fillMaxSize().padding(padding), + contentPadding = PaddingValues(8.dp), + ) { + items(viewModel.getLayoutList()) { layout -> + val title = stringResource(layout.text) + CatalogItemCard( + icon = layout.icon, + text = title, + onClick = { onLayoutClick(layout, title) }, + ) + } + } + } +} diff --git a/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/layouts/LayoutListViewModel.kt b/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/layouts/LayoutListViewModel.kt new file mode 100644 index 0000000000..df04ff4c95 --- /dev/null +++ b/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/layouts/LayoutListViewModel.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2022-2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.fhir.catalog.ui.layouts + +import android_fhir.catalog.generated.resources.Res +import android_fhir.catalog.generated.resources.ic_defaultlayout +import android_fhir.catalog.generated.resources.ic_paginatedlayout +import android_fhir.catalog.generated.resources.ic_readonlylayout +import android_fhir.catalog.generated.resources.ic_reviewlayout +import android_fhir.catalog.generated.resources.layout_name_default_text +import android_fhir.catalog.generated.resources.layout_name_paginated +import android_fhir.catalog.generated.resources.layout_name_read_only +import android_fhir.catalog.generated.resources.layout_name_review +import androidx.lifecycle.ViewModel +import org.jetbrains.compose.resources.DrawableResource +import org.jetbrains.compose.resources.StringResource + +class LayoutListViewModel : ViewModel() { + + fun getLayoutList(): List { + return Layout.entries + } + + enum class Layout( + val questionnaireFileName: String, + val icon: DrawableResource, + val text: StringResource, + val showReviewPage: Boolean = false, + val showReviewPageFirst: Boolean = false, + val isReadOnly: Boolean = false, + ) { + DEFAULT( + "layout_default.json", + Res.drawable.ic_defaultlayout, + Res.string.layout_name_default_text, + ), + PAGINATED( + "layout_paginated.json", + Res.drawable.ic_paginatedlayout, + Res.string.layout_name_paginated, + ), + REVIEW( + "layout_review.json", + Res.drawable.ic_reviewlayout, + Res.string.layout_name_review, + showReviewPage = true, + showReviewPageFirst = true, + ), + READ_ONLY( + "layout_review.json", + Res.drawable.ic_readonlylayout, + Res.string.layout_name_read_only, + isReadOnly = true, + ), + } +} diff --git a/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/questionnaire/QuestionnaireResponseScreen.kt b/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/questionnaire/QuestionnaireResponseScreen.kt new file mode 100644 index 0000000000..47a9c844f7 --- /dev/null +++ b/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/questionnaire/QuestionnaireResponseScreen.kt @@ -0,0 +1,109 @@ +/* + * Copyright 2025-2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.fhir.catalog.ui.questionnaire + +import android_fhir.catalog.generated.resources.Res +import android_fhir.catalog.generated.resources.close +import android_fhir.catalog.generated.resources.questionnaire_response_subtitle +import android_fhir.catalog.generated.resources.questionnaire_response_title +import android_fhir.catalog.generated.resources.questionnaire_submitted +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import org.jetbrains.compose.resources.stringResource + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun QuestionnaireResponseScreen( + responseJson: String, + onBackClick: () -> Unit, +) { + Scaffold( + topBar = { + TopAppBar( + title = { + Text( + text = stringResource(Res.string.questionnaire_response_title), + modifier = Modifier.fillMaxWidth(), + ) + }, + navigationIcon = { + IconButton(onClick = onBackClick) { + Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") + } + }, + ) + }, + ) { padding -> + Column( + modifier = + Modifier.fillMaxSize() + .padding(padding) + .padding(horizontal = 24.dp) + .verticalScroll(rememberScrollState()), + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.Top, + ) { + Text( + text = stringResource(Res.string.questionnaire_submitted), + style = MaterialTheme.typography.headlineMedium, + modifier = Modifier.padding(bottom = 8.dp), + ) + Text( + text = stringResource(Res.string.questionnaire_response_subtitle), + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(bottom = 16.dp), + ) + HorizontalDivider(modifier = Modifier.padding(bottom = 24.dp)) + Text( + text = responseJson, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface, + ) + Spacer(modifier = Modifier.height(24.dp)) + Button( + onClick = onBackClick, + modifier = Modifier.width(132.dp).align(Alignment.CenterHorizontally), + ) { + Text(stringResource(Res.string.close)) + } + Spacer(modifier = Modifier.height(24.dp)) + } + } +} diff --git a/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/questionnaire/QuestionnaireScreen.kt b/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/questionnaire/QuestionnaireScreen.kt new file mode 100644 index 0000000000..d9eef2cca1 --- /dev/null +++ b/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/questionnaire/QuestionnaireScreen.kt @@ -0,0 +1,216 @@ +/* + * Copyright 2025-2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.fhir.catalog.ui.questionnaire + +import android_fhir.catalog.generated.resources.Res +import android_fhir.catalog.generated.resources.behavior_name_calculated_expression +import android_fhir.catalog.generated.resources.behavior_name_calculated_expression_info +import android_fhir.catalog.generated.resources.behavior_name_skip_logic +import android_fhir.catalog.generated.resources.behavior_name_skip_logic_info +import android_fhir.catalog.generated.resources.ic_info_24 +import android_fhir.catalog.generated.resources.loading +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.BiasAlignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import com.google.android.fhir.catalog.ui.questionnaire.components.ErrorStateToggleAction +import com.google.android.fhir.datacapture.Questionnaire +import com.google.android.fhir.datacapture.QuestionnaireItemViewFactoryMatcher +import com.google.android.fhir.datacapture.QuestionnaireItemViewFactoryMatchersProvider +import com.google.android.fhir.datacapture.contrib.views.barcode.BarcodeItemViewFactoryMatcher +import com.google.android.fhir.datacapture.contrib.views.locationwidget.LocationDataItemViewFactoryMatcher +import com.google.android.fhir.datacapture.contrib.views.locationwidget.LocationItemViewFactoryMatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.resources.stringResource + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun QuestionnaireScreen( + viewModel: QuestionnaireViewModel, + title: String, + fileName: String, + coroutineScope: CoroutineScope, + validationFileName: String? = null, + showReviewPage: Boolean = false, + showReviewPageFirst: Boolean = false, + isReadOnly: Boolean = false, + onBackClick: () -> Unit, + navigateToResponse: (String) -> Unit, +) { + val viewItemMatchersProvider = remember { + object : QuestionnaireItemViewFactoryMatchersProvider { + override fun get(): List { + return listOf( + BarcodeItemViewFactoryMatcher, + LocationItemViewFactoryMatcher, + LocationDataItemViewFactoryMatcher, + ) + } + } + } + + var isErrorState by remember { mutableStateOf(false) } + var questionnaireJson by remember { mutableStateOf(null) } + + val skipLogicTitle = stringResource(Res.string.behavior_name_skip_logic) + val calculatedExpressionTitle = stringResource(Res.string.behavior_name_calculated_expression) + + val launchContextMap = remember { + mapOf("patient" to "{\"resourceType\":\"Patient\",\"id\":\"P1\"}") + } + + LaunchedEffect(fileName, validationFileName, isErrorState) { + val fileToLoad = + if (isErrorState && validationFileName != null) validationFileName else fileName + if (fileToLoad.isNotEmpty()) { + questionnaireJson = viewModel.getQuestionnaire(fileToLoad) + } + } + + Scaffold( + topBar = { + Column { + TopAppBar( + title = { Text(title) }, + navigationIcon = { + IconButton(onClick = onBackClick) { + Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") + } + }, + actions = { + if (validationFileName != null) { + ErrorStateToggleAction( + isErrorState = isErrorState, + onToggle = { isErrorState = it }, + ) + } + }, + ) + } + }, + ) { paddingValues -> + Surface( + modifier = Modifier.fillMaxSize().padding(paddingValues), + color = Color(0xFFF5F5F5), + ) { + Box(modifier = Modifier.fillMaxSize()) { + Box(modifier = Modifier.fillMaxWidth()) { + questionnaireJson?.let { json -> + Questionnaire( + questionnaireJson = json, + questionnaireLaunchContextMap = launchContextMap, + showSubmitButton = true, + showReviewPage = showReviewPage, + showReviewPageFirst = showReviewPageFirst, + isReadOnly = isReadOnly, + showCancelButton = false, + onSubmit = { getResponse -> + coroutineScope.launch { + val response = getResponse() + val responseJson = viewModel.getQuestionnaireResponseJson(response) + navigateToResponse(responseJson) + } + }, + matchersProvider = viewItemMatchersProvider, + onCancel = {}, + ) + } + ?: run { Text(stringResource(Res.string.loading), modifier = Modifier.padding(16.dp)) } + } + + if (title == skipLogicTitle || title == calculatedExpressionTitle) { + InfoCard( + modifier = Modifier.fillMaxWidth().align(BiasAlignment(0f, 0.8f)).padding(16.dp), + title = title, + info = + if (title == skipLogicTitle) { + stringResource(Res.string.behavior_name_skip_logic_info) + } else { + stringResource(Res.string.behavior_name_calculated_expression_info) + }, + ) + } + } + } + } +} + +@Composable +fun InfoCard(title: String, info: String, modifier: Modifier = Modifier) { + Card( + modifier = modifier, + colors = + CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.secondaryContainer, + ), + ) { + Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + painter = painterResource(Res.drawable.ic_info_24), + contentDescription = null, + modifier = Modifier.size(20.dp), + tint = MaterialTheme.colorScheme.onSecondaryContainer, + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = title, + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSecondaryContainer, + ) + } + Text( + text = info, + style = MaterialTheme.typography.bodySmall, + modifier = Modifier.padding(top = 8.dp), + color = MaterialTheme.colorScheme.onSecondaryContainer, + ) + } + } +} diff --git a/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/questionnaire/QuestionnaireViewModel.kt b/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/questionnaire/QuestionnaireViewModel.kt new file mode 100644 index 0000000000..8399eb9d90 --- /dev/null +++ b/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/questionnaire/QuestionnaireViewModel.kt @@ -0,0 +1,91 @@ +/* + * Copyright 2022-2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.fhir.catalog.ui.questionnaire + +import android_fhir.catalog.generated.resources.Res +import androidx.lifecycle.ViewModel +import com.google.android.fhir.datacapture.validation.Invalid +import com.google.android.fhir.datacapture.validation.QuestionnaireResponseValidator +import com.google.fhir.model.r4.FhirR4Json +import com.google.fhir.model.r4.Questionnaire +import com.google.fhir.model.r4.QuestionnaireResponse + +class QuestionnaireViewModel : ViewModel() { + private val fhirJson = FhirR4Json() + + fun getQuestionnaireResponseJson(response: QuestionnaireResponse): String { + return fhirJson.encodeToString(response) + } + + suspend fun getQuestionnaire(fileName: String) = Res.readBytes("files/$fileName").decodeToString() + + /** Validates questionnaire response and returns list of invalid field names. */ + suspend fun validateAndGetErrors( + questionnaireJson: String, + response: QuestionnaireResponse, + launchContextMap: Map? = null, + ): List { + val questionnaire = fhirJson.decodeFromString(questionnaireJson) as Questionnaire + val parentMap = buildQuestionnaireItemParentMap(questionnaire) + val launchContextResources = launchContextMap?.mapValues { fhirJson.decodeFromString(it.value) } + + val validationResults = + QuestionnaireResponseValidator.validateQuestionnaireResponse( + questionnaire, + response, + parentMap, + launchContextResources, + ) + + return buildList { + validationResults.forEach { (linkId, results) -> + val hasError = results.any { it is Invalid } + if (hasError) { + // Find the questionnaire item with this linkId to get its text + findItemText(questionnaire.item, linkId)?.let { add(it) } + } + } + } + } + + private fun buildQuestionnaireItemParentMap( + questionnaire: Questionnaire, + ): Map { + val map = mutableMapOf() + fun traverse(item: Questionnaire.Item) { + for (child in item.item) { + map[child] = item + traverse(child) + } + } + for (item in questionnaire.item) { + traverse(item) + } + return map + } + + private fun findItemText(items: List, linkId: String): String? { + for (item in items) { + if (item.linkId.value == linkId) { + return item.text?.value + } + val nested = findItemText(item.item, linkId) + if (nested != null) return nested + } + return null + } +} diff --git a/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/questionnaire/components/ErrorStateComponents.kt b/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/questionnaire/components/ErrorStateComponents.kt new file mode 100644 index 0000000000..1352a8f805 --- /dev/null +++ b/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/questionnaire/components/ErrorStateComponents.kt @@ -0,0 +1,130 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.fhir.catalog.ui.questionnaire.components + +import android_fhir.catalog.generated.resources.Res +import android_fhir.catalog.generated.resources.options +import android_fhir.catalog.generated.resources.show_error_state +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Settings +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Checkbox +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import org.jetbrains.compose.resources.stringResource + +/** Action button that toggles error state via a bottom sheet. */ +@Composable +fun ErrorStateToggleAction( + isErrorState: Boolean, + onToggle: (Boolean) -> Unit, +) { + var showBottomSheet by remember { mutableStateOf(false) } + + IconButton(onClick = { showBottomSheet = true }) { + Icon(Icons.Outlined.Settings, contentDescription = "Options") + } + + if (showBottomSheet) { + ErrorStateBottomSheet( + isErrorState = isErrorState, + onDismiss = { showBottomSheet = false }, + onToggle = { onToggle(!isErrorState) }, + ) + } +} + +/** Bottom sheet for toggling error state display. */ +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun ErrorStateBottomSheet( + isErrorState: Boolean, + onDismiss: () -> Unit, + onToggle: () -> Unit, +) { + ModalBottomSheet(onDismissRequest = onDismiss) { + Column( + modifier = Modifier.fillMaxWidth().padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + text = stringResource(Res.string.options), + style = MaterialTheme.typography.headlineSmall, + modifier = Modifier.padding(bottom = 24.dp), + ) + Card( + modifier = + Modifier.fillMaxWidth() + .then( + if (!isErrorState) { + Modifier.border(1.dp, Color.LightGray, MaterialTheme.shapes.small) + } else { + Modifier + }, + ), + colors = + CardDefaults.cardColors( + containerColor = + if (isErrorState) { + MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.5f) + } else { + Color.Transparent + }, + ), + ) { + Row( + modifier = Modifier.fillMaxWidth().clickable(onClick = onToggle).padding(16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Checkbox( + checked = isErrorState, + onCheckedChange = { onToggle() }, + ) + Spacer(modifier = Modifier.width(16.dp)) + Text( + text = stringResource(Res.string.show_error_state), + style = MaterialTheme.typography.bodyLarge, + color = if (isErrorState) MaterialTheme.colorScheme.primary else Color.Unspecified, + ) + } + } + Spacer(modifier = Modifier.padding(bottom = 32.dp)) + } + } +} diff --git a/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/shared/CatalogItemCard.kt b/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/shared/CatalogItemCard.kt new file mode 100644 index 0000000000..6eca044f50 --- /dev/null +++ b/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/shared/CatalogItemCard.kt @@ -0,0 +1,78 @@ +/* + * Copyright 2025-2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.fhir.catalog.ui.shared + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import org.jetbrains.compose.resources.DrawableResource +import org.jetbrains.compose.resources.painterResource + +@Composable +fun CatalogItemCard( + icon: DrawableResource, + text: String, + onClick: () -> Unit, +) { + Surface( + onClick = onClick, + modifier = Modifier.padding(4.dp).fillMaxWidth().height(180.dp), + shape = RoundedCornerShape(8.dp), + tonalElevation = 1.dp, + shadowElevation = 2.dp, + ) { + Column { + Box( + modifier = Modifier.fillMaxWidth().height(128.dp).background(Color(0xFFF8F8F8)), + contentAlignment = Alignment.Center, + ) { + Image( + painter = painterResource(icon), + contentDescription = null, + contentScale = ContentScale.None, + ) + } + Box( + modifier = Modifier.fillMaxSize().padding(8.dp), + contentAlignment = Alignment.Center, + ) { + Text( + text = text, + style = MaterialTheme.typography.bodyMedium, + textAlign = TextAlign.Center, + maxLines = 2, + ) + } + } + } +} diff --git a/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/shared/CatalogTopAppBar.kt b/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/shared/CatalogTopAppBar.kt new file mode 100644 index 0000000000..f205dd3530 --- /dev/null +++ b/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/shared/CatalogTopAppBar.kt @@ -0,0 +1,87 @@ +/* + * Copyright 2025-2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.fhir.catalog.ui.shared + +import android_fhir.catalog.generated.resources.Res +import android_fhir.catalog.generated.resources.open_questionnaire +import android_fhir.catalog.generated.resources.toolbar_text +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.MoreVert +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import org.jetbrains.compose.resources.stringResource + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun CatalogTopAppBar( + onOpenQuestionnaireAction: () -> Unit = {}, +) { + var showMenu by remember { mutableStateOf(false) } + + CenterAlignedTopAppBar( + title = { + val text = stringResource(Res.string.toolbar_text) + val lines = text.split("\n") + Column(horizontalAlignment = Alignment.CenterHorizontally) { + if (lines.size > 1) { + Text( + text = lines.first().trim(), + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + Text( + text = lines.last().trim(), + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } else { + Text(text = text, style = MaterialTheme.typography.titleMedium) + } + } + }, + actions = { + Box { + IconButton(onClick = { showMenu = !showMenu }) { + Icon(Icons.Default.MoreVert, contentDescription = null) + } + DropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) { + DropdownMenuItem( + text = { Text(stringResource(Res.string.open_questionnaire)) }, + onClick = { + showMenu = false + onOpenQuestionnaireAction() + }, + ) + } + } + }, + ) +} diff --git a/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/theme/Color.kt b/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/theme/Color.kt new file mode 100644 index 0000000000..3f540759ab --- /dev/null +++ b/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/theme/Color.kt @@ -0,0 +1,83 @@ +/* + * Copyright 2025-2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.fhir.catalog.ui.theme + +import androidx.compose.ui.graphics.Color + +// Light Colors (40 Tone) +val PrimaryBlue40 = Color(0xFF0B57D0) +val OnPrimaryBlue100 = Color(0xFFFFFFFF) +val PrimaryContainerBlue90 = Color(0xFFD3E3FD) +val OnPrimaryContainerBlue10 = Color(0xFF041E49) + +val SecondaryBlue40 = Color(0xFF00639B) +val OnSecondaryBlue100 = Color(0xFFFFFFFF) +val SecondaryContainerBlue90 = Color(0xFFC2E7FF) +val OnSecondaryContainerBlue10 = Color(0xFF001D35) + +val TertiaryGreen40 = Color(0xFF146C2E) +val OnTertiaryGreen100 = Color(0xFFFFFFFF) +val TertiaryContainerGreen90 = Color(0xFFC4EED0) +val OnTertiaryContainerGreen10 = Color(0xFF072711) + +val ErrorRed40 = Color(0xFFB3261E) +val ErrorContainerRed100 = Color(0xFFFFFFFF) +val OnErrorRed90 = Color(0xFFF9DEDC) +val OnErrorContainerRed10 = Color(0xFF410E0B) + +val BackgroundNeutral100 = Color(0xFFFFFFFF) +val OnBackgroundNeutral10 = Color(0xFF1F1F1F) + +val SurfaceNeutral100 = Color(0xFFFFFFFF) +val OnSurfaceNeutral10 = Color(0xFF1F1F1F) + +val SurfaceVariantNeutralVariant90 = Color(0xFFE1E3E1) +val OnSurfaceVariantNeutralVariant30 = Color(0xFF444746) + +val OutlineNeutralVariant50 = Color(0xFF747775) + +// Dark Colors (80 Tone) +val PrimaryBlue80 = Color(0xFFA8C7FA) +val OnPrimaryBlue20 = Color(0xFF062E6F) +val PrimaryContainerBlue30 = Color(0xFF0842A0) +val OnPrimaryContainerBlue90 = Color(0xFFD3E3FD) + +val SecondaryBlue80 = Color(0xFF7FCFFF) +val OnSecondaryBlue20 = Color(0xFF003355) +val SecondaryContainerBlue30 = Color(0xFF004A77) +val OnSecondaryContainerBlue90 = Color(0xFFC2E7FF) + +val TertiaryGreen80 = Color(0xFF91D5A3) // Corrected tone +val OnTertiaryGreen20 = Color(0xFF0A3818) +val TertiaryContainerGreen30 = Color(0xFF0F5223) +val OnTertiaryContainerGreen90 = Color(0xFFC4EED0) + +val ErrorRed80 = Color(0xFFF2B8B5) +val ErrorContainerRed20 = Color(0xFF601410) +val OnErrorRed30 = Color(0xFF8C1D18) +val OnErrorContainerRed90 = Color(0xFFF9DEDC) + +val BackgroundNeutral10 = Color(0xFF1F1F1F) +val OnBackgroundNeutral90 = Color(0xFFE3E3E3) + +val SurfaceNeutral10 = Color(0xFF1F1F1F) +val OnSurfaceNeutral90 = Color(0xFFE3E3E3) + +val SurfaceVariantNeutralVariant30 = Color(0xFF444746) +val OnSurfaceVariantNeutralVariant80 = Color(0xFFC4C7C5) + +val OutlineNeutralVariant60 = Color(0xFF8E918F) diff --git a/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/theme/Theme.kt b/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/theme/Theme.kt new file mode 100644 index 0000000000..fda1eb8dd4 --- /dev/null +++ b/catalog/src/commonMain/kotlin/com/google/android/fhir/catalog/ui/theme/Theme.kt @@ -0,0 +1,90 @@ +/* + * Copyright 2025-2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.fhir.catalog.ui.theme + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import com.google.android.fhir.datacapture.theme.QuestionnaireTheme + +private val DarkColorScheme = + darkColorScheme( + primary = PrimaryBlue80, + onPrimary = OnPrimaryBlue20, + primaryContainer = PrimaryContainerBlue30, + onPrimaryContainer = OnPrimaryContainerBlue90, + secondary = SecondaryBlue80, + onSecondary = OnSecondaryBlue20, + secondaryContainer = SecondaryContainerBlue30, + onSecondaryContainer = OnSecondaryContainerBlue90, + tertiary = TertiaryGreen80, + onTertiary = OnTertiaryGreen20, + tertiaryContainer = TertiaryContainerGreen30, + onTertiaryContainer = OnTertiaryContainerGreen90, + error = ErrorRed80, + errorContainer = ErrorContainerRed20, + onError = OnErrorRed30, + onErrorContainer = OnErrorContainerRed90, + background = BackgroundNeutral10, + onBackground = OnBackgroundNeutral90, + surface = SurfaceNeutral10, + onSurface = OnSurfaceNeutral90, + surfaceVariant = SurfaceVariantNeutralVariant30, + onSurfaceVariant = OnSurfaceVariantNeutralVariant80, + outline = OutlineNeutralVariant60, + ) + +private val LightColorScheme = + lightColorScheme( + primary = PrimaryBlue40, + onPrimary = OnPrimaryBlue100, + primaryContainer = PrimaryContainerBlue90, + onPrimaryContainer = OnPrimaryContainerBlue10, + secondary = SecondaryBlue40, + onSecondary = OnSecondaryBlue100, + secondaryContainer = SecondaryContainerBlue90, + onSecondaryContainer = OnSecondaryContainerBlue10, + tertiary = TertiaryGreen40, + onTertiary = OnTertiaryGreen100, + tertiaryContainer = TertiaryContainerGreen90, + onTertiaryContainer = OnTertiaryContainerGreen10, + error = ErrorRed40, + errorContainer = ErrorContainerRed100, + onError = OnErrorRed90, + onErrorContainer = OnErrorContainerRed10, + background = BackgroundNeutral100, + onBackground = OnBackgroundNeutral10, + surface = SurfaceNeutral100, + onSurface = OnSurfaceNeutral10, + surfaceVariant = SurfaceVariantNeutralVariant90, + onSurfaceVariant = OnSurfaceVariantNeutralVariant30, + outline = OutlineNeutralVariant50, + ) + +@Composable +fun AppTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit, +) { + val colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme + + QuestionnaireTheme( + colorScheme = colorScheme, + content = content, + ) +} diff --git a/catalog/src/desktopMain/kotlin/com/google/android/fhir/catalog/Main.kt b/catalog/src/desktopMain/kotlin/com/google/android/fhir/catalog/Main.kt new file mode 100644 index 0000000000..ae2bee9bbe --- /dev/null +++ b/catalog/src/desktopMain/kotlin/com/google/android/fhir/catalog/Main.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2025-2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.fhir.catalog + +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.WindowPlacement +import androidx.compose.ui.window.WindowState +import androidx.compose.ui.window.application + +fun main() = application { + Window( + onCloseRequest = ::exitApplication, + title = "Structured data capture catalog", + state = WindowState(placement = WindowPlacement.Maximized), + ) { + App() + } +} diff --git a/contrib/barcode/src/main/java/com/google/android/fhir/datacapture/contrib/views/barcode/mlkit/md/camera/FrameMetadata.kt b/catalog/src/iosMain/kotlin/com/google/android/fhir/catalog/MainViewController.kt similarity index 71% rename from contrib/barcode/src/main/java/com/google/android/fhir/datacapture/contrib/views/barcode/mlkit/md/camera/FrameMetadata.kt rename to catalog/src/iosMain/kotlin/com/google/android/fhir/catalog/MainViewController.kt index ecfec801ae..5c7126f7a6 100644 --- a/contrib/barcode/src/main/java/com/google/android/fhir/datacapture/contrib/views/barcode/mlkit/md/camera/FrameMetadata.kt +++ b/catalog/src/iosMain/kotlin/com/google/android/fhir/catalog/MainViewController.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Google LLC + * Copyright 2025-2026 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,8 @@ * limitations under the License. */ -package com.google.android.fhir.datacapture.contrib.views.barcode.mlkit.md.camera +package com.google.android.fhir.catalog -/** Metadata info of a camera frame. */ -class FrameMetadata(val width: Int, val height: Int, val rotation: Int) +import androidx.compose.ui.window.ComposeUIViewController + +fun MainViewController() = ComposeUIViewController { App() } diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/BehaviorListFragment.kt b/catalog/src/main/java/com/google/android/fhir/catalog/BehaviorListFragment.kt deleted file mode 100644 index 4cf0cd5059..0000000000 --- a/catalog/src/main/java/com/google/android/fhir/catalog/BehaviorListFragment.kt +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2022-2024 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.fhir.catalog - -import android.os.Bundle -import android.view.Gravity -import android.view.View -import androidx.fragment.app.Fragment -import androidx.fragment.app.viewModels -import androidx.lifecycle.lifecycleScope -import androidx.navigation.fragment.findNavController -import androidx.recyclerview.widget.GridLayoutManager -import androidx.recyclerview.widget.RecyclerView -import kotlinx.coroutines.launch - -class BehaviorListFragment : Fragment(R.layout.behavior_list_fragment) { - private val viewModel: BehaviorListViewModel by viewModels() - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - setUpBehaviorsRecyclerView() - (activity as? MainActivity)?.showOpenQuestionnaireMenu(true) - } - - override fun onResume() { - super.onResume() - setUpActionBar() - (requireActivity() as MainActivity).showBottomNavigationView(View.VISIBLE) - } - - private fun setUpActionBar() { - (activity as MainActivity).setActionBar( - getString(R.string.toolbar_text), - Gravity.CENTER_HORIZONTAL, - ) - setHasOptionsMenu(true) - } - - private fun setUpBehaviorsRecyclerView() { - val behaviorRecyclerView = - requireView().findViewById(R.id.sdcBehaviorRecyclerView) - val adapter = - BehaviorsRecyclerViewAdapter(::onItemClick).apply { submitList(viewModel.getBehaviorList()) } - behaviorRecyclerView.adapter = adapter - behaviorRecyclerView.layoutManager = GridLayoutManager(context, 2) - } - - private fun onItemClick(behavior: BehaviorListViewModel.Behavior) { - if (behavior.questionnaireFileName.isEmpty()) { - return - } - launchQuestionnaireFragment(behavior) - } - - private fun launchQuestionnaireFragment(behavior: BehaviorListViewModel.Behavior) { - viewLifecycleOwner.lifecycleScope.launch { - findNavController() - .navigate( - MainNavGraphDirections.actionGlobalGalleryQuestionnaireFragment( - questionnaireTitleKey = context?.getString(behavior.textId) ?: "", - questionnaireJsonStringKey = - getQuestionnaireJsonStringFromAssets( - context = requireContext(), - backgroundContext = coroutineContext, - fileName = behavior.questionnaireFileName, - ), - ), - ) - } - } -} diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/BehaviorListViewModel.kt b/catalog/src/main/java/com/google/android/fhir/catalog/BehaviorListViewModel.kt deleted file mode 100644 index e5cbe1618d..0000000000 --- a/catalog/src/main/java/com/google/android/fhir/catalog/BehaviorListViewModel.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2022-2024 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.fhir.catalog - -import android.app.Application -import android.content.Context -import androidx.annotation.DrawableRes -import androidx.annotation.StringRes -import androidx.lifecycle.AndroidViewModel - -class BehaviorListViewModel(application: Application) : AndroidViewModel(application) { - - fun getBehaviorList(): List { - return Behavior.values().toList() - } - - enum class Behavior( - @DrawableRes val iconId: Int, - @StringRes val textId: Int, - val questionnaireFileName: String, - ) { - CALCULATED_EXPRESSION( - R.drawable.ic_calculations_behavior, - R.string.behavior_name_calculated_expression, - "behavior_calculated_expression.json", - ), - ANSWER_EXPRESSION( - R.drawable.ic_answers_behavior, - R.string.behavior_name_answer_expression, - "behavior_answer_expression.json", - ), - CONTEXT_VARIABLES( - R.drawable.ic_context, - R.string.behavior_name_context_variables, - "behavior_context_variables.json", - ), - SKIP_LOGIC( - R.drawable.ic_skiplogic_behavior, - R.string.behavior_name_skip_logic, - "behavior_skip_logic.json", - ), - SKIP_LOGIC_WITH_EXPRESSION( - R.drawable.ic_skiplogic_behavior, - R.string.behavior_name_skip_logic_with_expression, - "behavior_skip_logic_with_expression.json", - ), - DYNAMIC_QUESTION_TEXT( - R.drawable.ic_dynamic_text_behavior, - R.string.behavior_name_dynamic_question_text, - "behavior_dynamic_question_text.json", - ), - QUESTIONNAIRE_CONSTRAINT( - R.drawable.ic_rule, - R.string.behavior_name_questionnaire_constraint, - "behavior_questionnaire_constraint.json", - ), - } - - fun isBehavior(context: Context, title: String) = - getBehaviorList().map { context.getString(it.textId) }.contains(title) -} diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/BehaviorsRecyclerViewAdapter.kt b/catalog/src/main/java/com/google/android/fhir/catalog/BehaviorsRecyclerViewAdapter.kt deleted file mode 100644 index 82fe0578fa..0000000000 --- a/catalog/src/main/java/com/google/android/fhir/catalog/BehaviorsRecyclerViewAdapter.kt +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2022-2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.fhir.catalog - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter -import androidx.recyclerview.widget.RecyclerView -import com.google.android.fhir.catalog.databinding.LandingPageItemBinding - -class BehaviorsRecyclerViewAdapter( - private val onItemClick: (BehaviorListViewModel.Behavior) -> Unit, -) : ListAdapter(BehaviorDiffUtil()) { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BehaviorViewHolder { - return BehaviorViewHolder( - LandingPageItemBinding.inflate(LayoutInflater.from(parent.context), parent, false), - onItemClick, - ) - } - - override fun onBindViewHolder(holder: BehaviorViewHolder, position: Int) { - holder.bind(getItem(position)) - } -} - -class BehaviorViewHolder( - val binding: LandingPageItemBinding, - private val onItemClick: (BehaviorListViewModel.Behavior) -> Unit, -) : RecyclerView.ViewHolder(binding.root) { - fun bind(behavior: BehaviorListViewModel.Behavior) { - binding.componentLayoutIconImageview.setImageResource(behavior.iconId) - binding.componentLayoutTextView.text = - binding.componentLayoutTextView.context.getString(behavior.textId) - binding.root.setOnClickListener { onItemClick(behavior) } - } -} - -class BehaviorDiffUtil : DiffUtil.ItemCallback() { - override fun areItemsTheSame( - oldBehavior: BehaviorListViewModel.Behavior, - newBehavior: BehaviorListViewModel.Behavior, - ) = oldBehavior === newBehavior - - override fun areContentsTheSame( - oldBehavior: BehaviorListViewModel.Behavior, - newBehavior: BehaviorListViewModel.Behavior, - ) = oldBehavior == newBehavior -} diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/CatalogApplication.kt b/catalog/src/main/java/com/google/android/fhir/catalog/CatalogApplication.kt deleted file mode 100644 index 204651fd02..0000000000 --- a/catalog/src/main/java/com/google/android/fhir/catalog/CatalogApplication.kt +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2022-2024 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.fhir.catalog - -import android.app.Application -import ca.uhn.fhir.context.FhirContext -import com.google.android.fhir.FhirEngine -import com.google.android.fhir.FhirEngineConfiguration -import com.google.android.fhir.FhirEngineProvider -import com.google.android.fhir.datacapture.DataCaptureConfig -import com.google.android.fhir.search.search -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import org.hl7.fhir.r4.model.Bundle - -class CatalogApplication : Application(), DataCaptureConfig.Provider { - // Only initiate the FhirEngine when used for the first time, not when the app is created. - private val fhirEngine: FhirEngine by lazy { FhirEngineProvider.getInstance(this) } - - private lateinit var dataCaptureConfig: DataCaptureConfig - - override fun onCreate() { - super.onCreate() - - FhirEngineProvider.init(FhirEngineConfiguration()) - - dataCaptureConfig = - DataCaptureConfig( - xFhirQueryResolver = { fhirEngine.search(it).map { it.resource } }, - questionnaireItemViewHolderFactoryMatchersProviderFactory = - ContribQuestionnaireItemViewHolderFactoryMatchersProviderFactory, - ) - - CoroutineScope(Dispatchers.IO).launch { - assets - .open("resource_data_bundle.json") - .bufferedReader() - .use { bufferedReader -> bufferedReader.readText() } - .let { stringValue -> - FhirContext.forR4Cached().newJsonParser().parseResource(stringValue) as Bundle - } - .entry - .map { bundleEntryComponent -> bundleEntryComponent.resource } - .let { resources -> fhirEngine.create(*resources.toTypedArray()) } - } - } - - override fun getDataCaptureConfig(): DataCaptureConfig = dataCaptureConfig -} diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListFragment.kt b/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListFragment.kt deleted file mode 100644 index f5d9864396..0000000000 --- a/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListFragment.kt +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2022-2024 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.fhir.catalog - -import android.os.Bundle -import android.view.Gravity -import android.view.View -import androidx.appcompat.app.AppCompatActivity -import androidx.fragment.app.Fragment -import androidx.fragment.app.viewModels -import androidx.lifecycle.lifecycleScope -import androidx.navigation.fragment.findNavController -import androidx.recyclerview.widget.GridLayoutManager -import androidx.recyclerview.widget.RecyclerView -import kotlinx.coroutines.launch - -/** Fragment for the component list. */ -class ComponentListFragment : Fragment(R.layout.component_list_fragment) { - private val viewModel: ComponentListViewModel by viewModels() - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - setUpComponentsRecyclerView() - (activity as? MainActivity)?.showOpenQuestionnaireMenu(true) - } - - override fun onResume() { - super.onResume() - setUpActionBar() - (activity as MainActivity).showBottomNavigationView(View.VISIBLE) - } - - private fun setUpActionBar() { - (requireActivity() as AppCompatActivity).supportActionBar?.apply { - setDisplayHomeAsUpEnabled(false) - } - (activity as MainActivity).setActionBar( - getString(R.string.toolbar_text), - Gravity.CENTER_HORIZONTAL, - ) - setHasOptionsMenu(true) - } - - private fun setUpComponentsRecyclerView() { - val adapter = - ComponentsRecyclerViewAdapter(::onItemClick).apply { submitList(viewModel.viewItemList) } - with(requireView().findViewById(R.id.componentsRecyclerView)) { - this.adapter = adapter - layoutManager = - GridLayoutManager(requireContext(), ComponentsRecyclerViewAdapter.ViewType.HEADER.spanCount) - .apply { - spanSizeLookup = - object : GridLayoutManager.SpanSizeLookup() { - override fun getSpanSize(position: Int): Int { - return if ( - adapter.getItemViewType(position) == - ComponentsRecyclerViewAdapter.ViewType.HEADER.ordinal - ) { - ComponentsRecyclerViewAdapter.ViewType.HEADER.spanCount - } else { - ComponentsRecyclerViewAdapter.ViewType.ITEM.spanCount - } - } - } - } - } - } - - private fun onItemClick(component: ComponentListViewModel.Component) { - // TODO Remove check when all components questionnaire json are updated. - // https://github.com/google/android-fhir/issues/1076 - if (component.questionnaireFile.isEmpty()) { - return - } - launchQuestionnaireFragment(component) - } - - private fun launchQuestionnaireFragment(component: ComponentListViewModel.Component) { - viewLifecycleOwner.lifecycleScope.launch { - findNavController() - .navigate( - MainNavGraphDirections.actionGlobalGalleryQuestionnaireFragment( - questionnaireTitleKey = context?.getString(component.textId) ?: "", - questionnaireJsonStringKey = - getQuestionnaireJsonStringFromAssets( - context = requireContext(), - backgroundContext = coroutineContext, - fileName = component.questionnaireFile, - ), - questionnaireWithValidationJsonStringKey = - component.questionnaireFileWithValidation?.let { - getQuestionnaireJsonStringFromAssets( - context = requireContext(), - backgroundContext = coroutineContext, - fileName = it, - ) - }, - ), - ) - } - } -} diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListViewModel.kt b/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListViewModel.kt deleted file mode 100644 index 00f541e5c6..0000000000 --- a/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListViewModel.kt +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright 2023-2024 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.fhir.catalog - -import android.app.Application -import android.content.Context -import androidx.annotation.DrawableRes -import androidx.annotation.StringRes -import androidx.lifecycle.AndroidViewModel -import androidx.lifecycle.SavedStateHandle - -class ComponentListViewModel(application: Application, private val state: SavedStateHandle) : - AndroidViewModel(application) { - - sealed class ViewItem { - data class HeaderItem(val header: Header) : ViewItem() - - data class ComponentItem(val component: Component) : ViewItem() - } - - enum class Header(@StringRes val textId: Int) { - WIDGETS(R.string.widgets), - MISC_COMPONENTS(R.string.misc_components), - } - - enum class Component( - @DrawableRes val iconId: Int, - @StringRes val textId: Int, - /** Path to the questionnaire json file with no required fields. */ - val questionnaireFile: String, - /** - * Path to the questionnaire json file with some or all required fields. If the user doesn't - * answer the required questions, an error may be displayed on the particular question. - */ - val questionnaireFileWithValidation: String? = null, - ) { - BOOLEAN_CHOICE( - R.drawable.ic_booleanchoice, - R.string.component_name_boolean_choice, - "component_boolean_choice.json", - "component_boolean_choice_with_validation.json", - ), - SINGLE_CHOICE( - R.drawable.ic_singlechoice, - R.string.component_name_single_choice, - "component_single_choice.json", - "component_single_choice_with_validation.json", - ), - MULTIPLE_CHOICE( - R.drawable.ic_multiplechoice, - R.string.component_name_multiple_choice, - "component_multi_select_choice.json", - "component_multi_select_choice_with_validation.json", - ), - DROPDOWN( - R.drawable.ic_group_1278, - R.string.component_name_dropdown, - "component_dropdown.json", - "component_dropdown_with_validation.json", - ), - MODAL( - R.drawable.ic_modal, - R.string.component_name_modal, - "component_modal.json", - "component_modal_with_validation.json", - ), - OPEN_CHOICE( - R.drawable.ic_openchoice, - R.string.component_name_open_choice, - "component_open_choice.json", - "component_open_choice_with_validation.json", - ), - TEXT_FIELD( - R.drawable.ic_textfield, - R.string.component_name_text_field, - "component_text_fields.json", - "component_text_fields_with_validation.json", - ), - AUTO_COMPLETE( - R.drawable.ic_autocomplete, - R.string.component_name_auto_complete, - "component_auto_complete.json", - "component_auto_complete_with_validation.json", - ), - DATE_PICKER( - R.drawable.ic_datepicker, - R.string.component_name_date_picker, - "component_date_picker.json", - "component_date_picker_with_validation.json", - ), - TIME_PICKER( - R.drawable.ic_timepicker, - R.string.component_name_time_picker, - "component_time_picker.json", - "component_time_picker_with_validation.json", - ), - DATE_TIME_PICKER( - R.drawable.ic_timepicker, - R.string.component_name_date_time_picker, - "component_date_time_picker.json", - "component_date_time_picker_with_validation.json", - ), - SLIDER( - R.drawable.ic_slider, - R.string.component_name_slider, - "component_slider.json", - "component_slider_with_validation.json", - ), - QUANTITY( - R.drawable.ic_unitoptions, - R.string.component_name_quantity, - "component_quantity.json", - "component_quantity_with_validation.json", - ), - ATTACHMENT( - R.drawable.ic_attachment, - R.string.component_name_attachment, - "component_attachment.json", - "component_attachment_with_validation.json", - ), - REPEATED_GROUP( - R.drawable.ic_repeatgroups, - R.string.component_name_repeated_group, - "component_repeated_group.json", - ), - HELP(R.drawable.ic_help, R.string.component_name_help, "component_help.json"), - ITEM_MEDIA( - R.drawable.ic_item_media, - R.string.component_name_item_media, - "component_item_media.json", - ), - ITEM_ANSWER_MEDIA( - R.drawable.ic_item_answer_media, - R.string.component_name_item_answer_media, - "", - ), - INITIAL_VALUE( - R.drawable.ic_initial_value_component, - R.string.component_name_initial_value, - "component_initial_value.json", - ), - LOCATION_WIDGET( - R.drawable.ic_location_on, - R.string.component_name_location_widget, - "component_location_widget.json", - ), - QUESTION_ITEM_CUSTOM_STYLE( - R.drawable.text_format_48dp, - R.string.component_name_per_question_custom_style, - "component_per_question_custom_style.json", - ), - } - - val viewItemList = - listOf( - ViewItem.HeaderItem(Header.WIDGETS), - ViewItem.ComponentItem(Component.BOOLEAN_CHOICE), - ViewItem.ComponentItem(Component.SINGLE_CHOICE), - ViewItem.ComponentItem(Component.MULTIPLE_CHOICE), - ViewItem.ComponentItem(Component.DROPDOWN), - ViewItem.ComponentItem(Component.MODAL), - ViewItem.ComponentItem(Component.OPEN_CHOICE), - ViewItem.ComponentItem(Component.TEXT_FIELD), - ViewItem.ComponentItem(Component.AUTO_COMPLETE), - ViewItem.ComponentItem(Component.DATE_PICKER), - ViewItem.ComponentItem(Component.TIME_PICKER), - ViewItem.ComponentItem(Component.DATE_TIME_PICKER), - ViewItem.ComponentItem(Component.SLIDER), - ViewItem.ComponentItem(Component.QUANTITY), - ViewItem.ComponentItem(Component.ATTACHMENT), - ViewItem.ComponentItem(Component.REPEATED_GROUP), - ViewItem.HeaderItem(Header.MISC_COMPONENTS), - ViewItem.ComponentItem(Component.HELP), - ViewItem.ComponentItem(Component.ITEM_MEDIA), - ViewItem.ComponentItem(Component.ITEM_ANSWER_MEDIA), - ViewItem.ComponentItem(Component.INITIAL_VALUE), - ViewItem.ComponentItem(Component.LOCATION_WIDGET), - ViewItem.ComponentItem(Component.QUESTION_ITEM_CUSTOM_STYLE), - ) - - fun isComponent(context: Context, title: String) = - viewItemList - .filterIsInstance() - .map { context.getString(it.component.textId) } - .contains(title) -} diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/ComponentsRecyclerViewadapter.kt b/catalog/src/main/java/com/google/android/fhir/catalog/ComponentsRecyclerViewadapter.kt deleted file mode 100644 index cf0ae81809..0000000000 --- a/catalog/src/main/java/com/google/android/fhir/catalog/ComponentsRecyclerViewadapter.kt +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2022-2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.fhir.catalog - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter -import androidx.recyclerview.widget.RecyclerView -import com.google.android.fhir.catalog.databinding.ComponentHeaderLayoutBinding -import com.google.android.fhir.catalog.databinding.LandingPageItemBinding - -class ComponentsRecyclerViewAdapter( - private val onItemClick: (ComponentListViewModel.Component) -> Unit, -) : ListAdapter(ComponentDiffUtil()) { - - enum class ViewType(val spanCount: Int) { - HEADER(2), - ITEM(1), - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - return when (viewType) { - ViewType.HEADER.ordinal -> - ComponentHeaderViewHolder( - ComponentHeaderLayoutBinding.inflate(LayoutInflater.from(parent.context), parent, false), - ) - ViewType.ITEM.ordinal -> - ComponentListViewHolder( - LandingPageItemBinding.inflate(LayoutInflater.from(parent.context), parent, false), - onItemClick, - ) - else -> throw IllegalArgumentException("$viewType must be ViewType.") - } - } - - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - if (holder is ComponentViewHolder) { - holder.bind(getItem(position)) - } - } - - override fun getItemViewType(position: Int): Int { - return when (getItem(position)) { - is ComponentListViewModel.ViewItem.HeaderItem -> ViewType.HEADER.ordinal - is ComponentListViewModel.ViewItem.ComponentItem -> ViewType.ITEM.ordinal - } - } -} - -interface ComponentViewHolder { - fun bind(component: ComponentListViewModel.ViewItem) -} - -class ComponentListViewHolder( - private val binding: LandingPageItemBinding, - private val onItemClick: (ComponentListViewModel.Component) -> Unit, -) : RecyclerView.ViewHolder(binding.root), ComponentViewHolder { - override fun bind(component: ComponentListViewModel.ViewItem) { - val componentItem = component as ComponentListViewModel.ViewItem.ComponentItem - binding.componentLayoutIconImageview.setImageResource(componentItem.component.iconId) - binding.componentLayoutTextView.text = - binding.componentLayoutTextView.context.getString(componentItem.component.textId) - binding.root.setOnClickListener { onItemClick(component.component) } - } -} - -class ComponentHeaderViewHolder(private val binding: ComponentHeaderLayoutBinding) : - RecyclerView.ViewHolder(binding.root), ComponentViewHolder { - override fun bind(component: ComponentListViewModel.ViewItem) { - val headerItem = component as ComponentListViewModel.ViewItem.HeaderItem - binding.tvComponentHeader.text = - binding.tvComponentHeader.context.getString(headerItem.header.textId) - } -} - -class ComponentDiffUtil : DiffUtil.ItemCallback() { - override fun areItemsTheSame( - oldComponent: ComponentListViewModel.ViewItem, - newComponent: ComponentListViewModel.ViewItem, - ) = oldComponent === newComponent - - override fun areContentsTheSame( - oldComponent: ComponentListViewModel.ViewItem, - newComponent: ComponentListViewModel.ViewItem, - ) = oldComponent == newComponent -} diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/ContribQuestionnaireItemViewHolderFactoryMatchersProviderFactory.kt b/catalog/src/main/java/com/google/android/fhir/catalog/ContribQuestionnaireItemViewHolderFactoryMatchersProviderFactory.kt deleted file mode 100644 index 4541a27f50..0000000000 --- a/catalog/src/main/java/com/google/android/fhir/catalog/ContribQuestionnaireItemViewHolderFactoryMatchersProviderFactory.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2024 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.fhir.catalog - -import com.google.android.fhir.datacapture.QuestionnaireFragment -import com.google.android.fhir.datacapture.QuestionnaireItemViewHolderFactoryMatchersProviderFactory -import com.google.android.fhir.datacapture.contrib.views.locationwidget.LocationGpsCoordinateViewHolderFactory -import com.google.android.fhir.datacapture.contrib.views.locationwidget.LocationWidgetViewHolderFactory - -object ContribQuestionnaireItemViewHolderFactoryMatchersProviderFactory : - QuestionnaireItemViewHolderFactoryMatchersProviderFactory { - - const val LOCATION_WIDGET_PROVIDER = "location-widget-provider" - - override fun get( - provider: String, - ): QuestionnaireFragment.QuestionnaireItemViewHolderFactoryMatchersProvider = - when (provider) { - LOCATION_WIDGET_PROVIDER -> - object : QuestionnaireFragment.QuestionnaireItemViewHolderFactoryMatchersProvider() { - override fun get(): - List { - return listOf( - QuestionnaireFragment.QuestionnaireItemViewHolderFactoryMatcher( - factory = LocationGpsCoordinateViewHolderFactory, - matches = LocationGpsCoordinateViewHolderFactory::matcher, - ), - QuestionnaireFragment.QuestionnaireItemViewHolderFactoryMatcher( - factory = LocationWidgetViewHolderFactory, - matches = LocationWidgetViewHolderFactory::matcher, - ), - ) - } - } - else -> throw NotImplementedError() - } -} diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/DemoQuestionnaireFragment.kt b/catalog/src/main/java/com/google/android/fhir/catalog/DemoQuestionnaireFragment.kt deleted file mode 100644 index b9a869a6e0..0000000000 --- a/catalog/src/main/java/com/google/android/fhir/catalog/DemoQuestionnaireFragment.kt +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright 2023-2026 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.fhir.catalog - -import android.os.Bundle -import android.view.Gravity -import android.view.LayoutInflater -import android.view.Menu -import android.view.MenuInflater -import android.view.MenuItem -import android.view.View -import android.view.ViewGroup -import android.widget.TextView -import androidx.appcompat.app.AppCompatActivity -import androidx.fragment.app.Fragment -import androidx.fragment.app.commit -import androidx.fragment.app.setFragmentResultListener -import androidx.fragment.app.viewModels -import androidx.lifecycle.lifecycleScope -import androidx.navigation.fragment.NavHostFragment -import androidx.navigation.fragment.findNavController -import androidx.navigation.fragment.navArgs -import ca.uhn.fhir.context.FhirContext -import com.google.android.fhir.catalog.ModalBottomSheetFragment.Companion.BUNDLE_ERROR_KEY -import com.google.android.fhir.catalog.ModalBottomSheetFragment.Companion.REQUEST_ERROR_KEY -import com.google.android.fhir.datacapture.QuestionnaireFragment -import com.google.android.fhir.datacapture.QuestionnaireFragment.Companion.SUBMIT_REQUEST_KEY -import com.google.android.material.card.MaterialCardView -import kotlinx.coroutines.launch -import org.hl7.fhir.r4.model.Patient - -class DemoQuestionnaireFragment : Fragment() { - private val viewModel: DemoQuestionnaireViewModel by viewModels() - private val componentListViewModel: ComponentListViewModel by viewModels() - private val behaviorListViewModel: BehaviorListViewModel by viewModels() - private val layoutListViewModel: LayoutListViewModel by viewModels() - private val args: DemoQuestionnaireFragmentArgs by navArgs() - private var isErrorState = false - private lateinit var infoCard: MaterialCardView - private lateinit var infoCardHeader: TextView - private lateinit var infoCardText: TextView - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle?, - ): View { - requireContext().setTheme(getThemeId(args.questionnaireTitleKey)) - return inflater.inflate(R.layout.fragment_demo_questionnaire, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - infoCard = view.findViewById(R.id.infoCard) - when (args.questionnaireTitleKey) { - getString(R.string.behavior_name_skip_logic) -> { - infoCardHeader = view.findViewById(R.id.infoCardHeader) - infoCardHeader.text = args.questionnaireTitleKey - infoCardText = view.findViewById(R.id.infoCardText) - infoCardText.text = getString(R.string.behavior_name_skip_logic_info) - infoCard.visibility = View.VISIBLE - } - getString(R.string.behavior_name_calculated_expression) -> { - infoCardHeader = view.findViewById(R.id.infoCardHeader) - infoCardHeader.text = args.questionnaireTitleKey - infoCardText = view.findViewById(R.id.infoCardText) - infoCardText.text = getString(R.string.behavior_name_calculated_expression_info) - infoCard.visibility = View.VISIBLE - } - else -> infoCard.visibility = View.GONE - } - setFragmentResultListener(REQUEST_ERROR_KEY) { _, bundle -> - isErrorState = bundle.getBoolean(BUNDLE_ERROR_KEY) - replaceQuestionnaireFragmentWithQuestionnaireJson() - } - childFragmentManager.setFragmentResultListener(SUBMIT_REQUEST_KEY, viewLifecycleOwner) { _, _ -> - onSubmitQuestionnaireClick() - } - if (savedInstanceState == null) { - addQuestionnaireFragment() - } - (activity as? MainActivity)?.showOpenQuestionnaireMenu(false) - } - - override fun onResume() { - super.onResume() - (requireActivity() as MainActivity).showBottomNavigationView(View.GONE) - setUpActionBar() - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - return when (item.itemId) { - android.R.id.home -> { - NavHostFragment.findNavController(this).navigateUp() - true - } - R.id.error_menu -> { - launchModalBottomSheetFragment() - true - } - else -> super.onOptionsItemSelected(item) - } - } - - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - super.onCreateOptionsMenu(menu, inflater) - getMenu()?.let { inflater.inflate(it, menu) } - } - - private fun setUpActionBar() { - (requireActivity() as AppCompatActivity).supportActionBar?.apply { - setDisplayHomeAsUpEnabled(true) - } - (requireActivity() as MainActivity).setActionBar(args.questionnaireTitleKey, Gravity.TOP) - setHasOptionsMenu(true) - } - - private fun addQuestionnaireFragment() { - viewLifecycleOwner.lifecycleScope.launch { - if (childFragmentManager.findFragmentByTag(QUESTIONNAIRE_FRAGMENT_TAG) == null) { - childFragmentManager.commit { - setReorderingAllowed(true) - val questionnaireFragmentBuilder = - QuestionnaireFragment.builder().apply { - setCustomQuestionnaireItemViewHolderFactoryMatchersProvider( - ContribQuestionnaireItemViewHolderFactoryMatchersProviderFactory - .LOCATION_WIDGET_PROVIDER, - ) - setQuestionnaire(args.questionnaireJsonStringKey!!) - } - LayoutListViewModel.questionnaireLambdaMap[args.questionnaireLambdaKey ?: ""]!!.invoke( - questionnaireFragmentBuilder, - ) - add(R.id.container, questionnaireFragmentBuilder.build(), QUESTIONNAIRE_FRAGMENT_TAG) - } - } - } - } - - /** - * Replaces existing [QuestionnaireFragment] with questionnaire json as per [isErrorState] value. - * If isErrorState is true then existing fragment get replaced with questionnaire json which shows - * error. - */ - private fun replaceQuestionnaireFragmentWithQuestionnaireJson() { - // TODO: remove check once all files are added - if (args.questionnaireWithValidationJsonStringKey.isNullOrEmpty()) { - return - } - viewLifecycleOwner.lifecycleScope.launch { - val questionnaireJsonString = - if (isErrorState) { - args.questionnaireWithValidationJsonStringKey!! - } else { - args.questionnaireJsonStringKey!! - } - childFragmentManager.commit { - setReorderingAllowed(true) - replace( - R.id.container, - QuestionnaireFragment.builder() - .setQuestionnaire(questionnaireJsonString) - .setQuestionnaireLaunchContextMap( - FhirContext.forR4Cached() - .newJsonParser() - .encodeResourceToString(Patient().apply { id = "P1" }) - .let { mapOf("patient" to it) }, - ) - .setSubmitButtonText( - getString(com.google.android.fhir.datacapture.R.string.submit_questionnaire), - ) - .build(), - QUESTIONNAIRE_FRAGMENT_TAG, - ) - } - } - } - - private fun getThemeId(title: String) = - if ( - layoutListViewModel.isPaginatedLayout(requireContext(), title) || - componentListViewModel.isComponent(requireContext(), title) || - behaviorListViewModel.isBehavior(requireContext(), title) - ) { - R.style.Theme_Androidfhir_PaginatedLayout - } else { - R.style.Theme_Androidfhir_DefaultLayout - } - - private fun getMenu(): Int? = - if ( - componentListViewModel.isComponent( - requireContext(), - args.questionnaireTitleKey!!, - ) - ) { - R.menu.component_menu - } else { - null - } - - private fun onSubmitQuestionnaireClick() { - lifecycleScope.launch { - val questionnaireFragment = - childFragmentManager.findFragmentByTag(QUESTIONNAIRE_FRAGMENT_TAG) as QuestionnaireFragment - launchQuestionnaireResponseFragment( - viewModel.getQuestionnaireResponseJson(questionnaireFragment.getQuestionnaireResponse()), - ) - } - } - - private fun launchQuestionnaireResponseFragment(response: String) { - findNavController() - .navigate( - DemoQuestionnaireFragmentDirections - .actionGalleryQuestionnaireFragmentToQuestionnaireResponseFragment(response), - ) - } - - private fun launchModalBottomSheetFragment() { - findNavController() - .navigate( - DemoQuestionnaireFragmentDirections.actionGalleryQuestionnaireFragmentToModalBottomSheet( - isErrorState, - ), - ) - } - - companion object { - const val QUESTIONNAIRE_FRAGMENT_TAG = "questionnaire-fragment-tag" - } -} diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/LayoutListFragment.kt b/catalog/src/main/java/com/google/android/fhir/catalog/LayoutListFragment.kt deleted file mode 100644 index dd91fde727..0000000000 --- a/catalog/src/main/java/com/google/android/fhir/catalog/LayoutListFragment.kt +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2022-2025 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.fhir.catalog - -import android.os.Bundle -import android.view.Gravity -import android.view.View -import androidx.fragment.app.Fragment -import androidx.fragment.app.viewModels -import androidx.lifecycle.lifecycleScope -import androidx.navigation.fragment.findNavController -import androidx.recyclerview.widget.GridLayoutManager -import androidx.recyclerview.widget.RecyclerView -import kotlinx.coroutines.launch - -/** Fragment for the layout list. */ -class LayoutListFragment : Fragment(R.layout.layout_list_fragment) { - private val viewModel: LayoutListViewModel by viewModels() - - override fun onResume() { - super.onResume() - setUpActionBar() - (requireActivity() as MainActivity).showBottomNavigationView(View.VISIBLE) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - setUpLayoutsRecyclerView() - (activity as? MainActivity)?.showOpenQuestionnaireMenu(true) - } - - private fun setUpLayoutsRecyclerView() { - val adapter = - LayoutsRecyclerViewAdapter(::onItemClick).apply { submitList(viewModel.getLayoutList()) } - val recyclerView = requireView().findViewById(R.id.sdcLayoutsRecyclerView) - recyclerView.adapter = adapter - recyclerView.layoutManager = GridLayoutManager(context, 2) - } - - private fun setUpActionBar() { - (requireActivity() as MainActivity).setNavigationUp(false) - (activity as MainActivity).setActionBar( - getString(R.string.toolbar_text), - Gravity.CENTER_HORIZONTAL, - ) - setHasOptionsMenu(true) - } - - private fun onItemClick(layout: LayoutListViewModel.Layout) { - // TODO Remove check when all layout questionnaire json are updated. - // https://github.com/google/android-fhir/issues/1079 - if (layout.questionnaireFileName.isEmpty()) { - return - } - launchQuestionnaireFragment(layout) - } - - private fun launchQuestionnaireFragment(layout: LayoutListViewModel.Layout) { - viewLifecycleOwner.lifecycleScope.launch { - findNavController() - .navigate( - MainNavGraphDirections.actionGlobalGalleryQuestionnaireFragment( - questionnaireTitleKey = context?.getString(layout.textId) ?: "", - questionnaireJsonStringKey = - getQuestionnaireJsonStringFromAssets( - context = requireContext(), - backgroundContext = coroutineContext, - fileName = layout.questionnaireFileName, - ), - questionnaireLambdaKey = layout.questionnaireLambdaKey, - ), - ) - } - } -} diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/LayoutListViewModel.kt b/catalog/src/main/java/com/google/android/fhir/catalog/LayoutListViewModel.kt deleted file mode 100644 index 5a6356985a..0000000000 --- a/catalog/src/main/java/com/google/android/fhir/catalog/LayoutListViewModel.kt +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2022-2025 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.fhir.catalog - -import android.app.Application -import android.content.Context -import androidx.annotation.DrawableRes -import androidx.annotation.StringRes -import androidx.lifecycle.AndroidViewModel -import androidx.lifecycle.SavedStateHandle -import com.google.android.fhir.datacapture.QuestionnaireFragment - -class LayoutListViewModel(application: Application, private val state: SavedStateHandle) : - AndroidViewModel(application) { - - fun getLayoutList(): List { - return Layout.values().toList() - } - - enum class Layout( - @DrawableRes val iconId: Int, - @StringRes val textId: Int, - val questionnaireFileName: String, - val questionnaireLambdaKey: String, - ) { - DEFAULT( - R.drawable.ic_defaultlayout, - R.string.layout_name_default_text, - "layout_default.json", - "", - ), - PAGINATED( - R.drawable.ic_paginatedlayout, - R.string.layout_name_paginated, - "layout_paginated.json", - "", - ), - REVIEW( - R.drawable.ic_reviewlayout, - R.string.layout_name_review, - "layout_review.json", - "showreviewpagefirstandbeforesubmit", - ), - READ_ONLY(R.drawable.ic_readonlylayout, R.string.layout_name_read_only, "", ""), - } - - fun isDefaultLayout(context: Context, title: String) = - context.getString(Layout.DEFAULT.textId) == title - - fun isPaginatedLayout(context: Context, title: String) = - context.getString(Layout.PAGINATED.textId) == title - - companion object { - val questionnaireLambdaMap: Map Unit> = - mapOf( - "" to - { - showReviewPageFirst(false) - showReviewPageBeforeSubmit(false) - }, - "showreviewpagefirstandbeforesubmit" to - { - showReviewPageFirst(true) - showReviewPageBeforeSubmit(true) - }, - ) - } -} diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/LayoutsRecyclerViewAdapter.kt b/catalog/src/main/java/com/google/android/fhir/catalog/LayoutsRecyclerViewAdapter.kt deleted file mode 100644 index 88874c623d..0000000000 --- a/catalog/src/main/java/com/google/android/fhir/catalog/LayoutsRecyclerViewAdapter.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2021-2025 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.fhir.catalog - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter -import androidx.recyclerview.widget.RecyclerView -import com.google.android.fhir.catalog.databinding.LandingPageItemBinding - -class LayoutsRecyclerViewAdapter(private val onItemClick: (LayoutListViewModel.Layout) -> Unit) : - ListAdapter(LayoutDiffUtil()) { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LayoutViewHolder { - return LayoutViewHolder( - LandingPageItemBinding.inflate(LayoutInflater.from(parent.context), parent, false), - onItemClick, - ) - } - - override fun onBindViewHolder(holder: LayoutViewHolder, position: Int) { - holder.bind(getItem(position)) - } -} - -class LayoutViewHolder( - val binding: LandingPageItemBinding, - private val onItemClick: (LayoutListViewModel.Layout) -> Unit, -) : RecyclerView.ViewHolder(binding.root) { - fun bind(layout: LayoutListViewModel.Layout) { - binding.componentLayoutIconImageview.setImageResource(layout.iconId) - binding.componentLayoutTextView.text = - binding.componentLayoutTextView.context.getString(layout.textId) - binding.root.setOnClickListener { onItemClick(layout) } - } -} - -class LayoutDiffUtil : DiffUtil.ItemCallback() { - override fun areItemsTheSame( - oldLayout: LayoutListViewModel.Layout, - newLayout: LayoutListViewModel.Layout, - ) = oldLayout === newLayout - - override fun areContentsTheSame( - oldLayout: LayoutListViewModel.Layout, - newLayout: LayoutListViewModel.Layout, - ) = oldLayout == newLayout -} diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/MainActivity.kt b/catalog/src/main/java/com/google/android/fhir/catalog/MainActivity.kt deleted file mode 100644 index 09cc2f03eb..0000000000 --- a/catalog/src/main/java/com/google/android/fhir/catalog/MainActivity.kt +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2021-2024 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.fhir.catalog - -import android.net.Uri -import android.os.Bundle -import android.view.Menu -import android.view.MenuItem -import android.widget.TextView -import androidx.activity.result.contract.ActivityResultContracts -import androidx.appcompat.app.AppCompatActivity -import androidx.appcompat.widget.Toolbar -import androidx.lifecycle.lifecycleScope -import androidx.navigation.Navigation -import androidx.navigation.findNavController -import androidx.navigation.ui.NavigationUI -import com.google.android.material.bottomnavigation.BottomNavigationView -import kotlinx.coroutines.launch - -class MainActivity : AppCompatActivity(R.layout.activity_main) { - private var showOpenQuestionnaireMenu = true - val getContentLauncher = - registerForActivityResult(ActivityResultContracts.GetContent()) { - it?.let { launchQuestionnaireFragment(it) } - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setSupportActionBar(findViewById(R.id.toolbar)) - setUpBottomNavigationView() - } - - override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.open_questionnaire_menu, menu) - return true - } - - override fun onPrepareOptionsMenu(menu: Menu): Boolean { - menu.findItem(R.id.select_questionnaire_menu).isVisible = showOpenQuestionnaireMenu - return true - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - return when (item.itemId) { - R.id.select_questionnaire_menu -> { - getContentLauncher.launch("application/json") - true - } - else -> super.onOptionsItemSelected(item) - } - } - - fun showOpenQuestionnaireMenu(showMenu: Boolean) { - showOpenQuestionnaireMenu = showMenu - invalidateOptionsMenu() - } - - fun showBottomNavigationView(value: Int) { - findViewById(R.id.bottom_navigation_view).visibility = value - } - - fun setNavigationUp(value: Boolean) { - supportActionBar?.apply { setDisplayHomeAsUpEnabled(value) } - } - - fun setActionBar(title: String, gravity: Int) { - val toolbar = findViewById(R.id.toolbar) - setSupportActionBar(toolbar) - val titleTextView = toolbar.findViewById(R.id.toolbarTitle) - titleTextView.text = title - titleTextView.gravity = gravity - } - - private fun setUpBottomNavigationView() { - val navController = Navigation.findNavController(this, R.id.nav_host_fragment) - val bottomNavigationView = findViewById(R.id.bottom_navigation_view) - NavigationUI.setupWithNavController(bottomNavigationView, navController) - } - - private fun launchQuestionnaireFragment(uri: Uri) { - lifecycleScope.launch { - findNavController(R.id.nav_host_fragment) - .navigate( - MainNavGraphDirections.actionGlobalGalleryQuestionnaireFragment( - questionnaireTitleKey = "", - questionnaireJsonStringKey = - getQuestionnaireJsonStringFromFileUri( - context = applicationContext, - backgroundContext = coroutineContext, - uri = uri, - ), - ), - ) - } - } -} diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/ModalBottomSheetFragment.kt b/catalog/src/main/java/com/google/android/fhir/catalog/ModalBottomSheetFragment.kt deleted file mode 100644 index 5c2ca4d0fb..0000000000 --- a/catalog/src/main/java/com/google/android/fhir/catalog/ModalBottomSheetFragment.kt +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2021-2024 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.fhir.catalog - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.CheckBox -import androidx.core.os.bundleOf -import androidx.fragment.app.setFragmentResult -import androidx.navigation.fragment.navArgs -import com.google.android.material.bottomsheet.BottomSheetDialogFragment - -class ModalBottomSheetFragment : BottomSheetDialogFragment() { - private val args: ModalBottomSheetFragmentArgs by navArgs() - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle?, - ): View? { - return inflater.inflate(R.layout.fragment_modal_bottom_sheet, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val showHideErrorCheckBox = view.findViewById(R.id.errorToggleCheckBox) - showHideErrorCheckBox.isChecked = args.errorState - showHideErrorCheckBox.setOnCheckedChangeListener { _, isChecked -> - setFragmentResult( - REQUEST_ERROR_KEY, - bundleOf( - BUNDLE_ERROR_KEY to isChecked, - ), - ) - } - (activity as? MainActivity)?.showOpenQuestionnaireMenu(false) - } - - companion object { - const val REQUEST_ERROR_KEY = "errorRequestKey" - const val BUNDLE_ERROR_KEY = "errorBundleKey" - } -} diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/QuestionnaireFileOperationUtil.kt b/catalog/src/main/java/com/google/android/fhir/catalog/QuestionnaireFileOperationUtil.kt deleted file mode 100644 index 18dc4061c8..0000000000 --- a/catalog/src/main/java/com/google/android/fhir/catalog/QuestionnaireFileOperationUtil.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.fhir.catalog - -import android.content.Context -import android.net.Uri -import java.io.BufferedReader -import kotlin.coroutines.CoroutineContext -import kotlinx.coroutines.withContext - -suspend fun getQuestionnaireJsonStringFromAssets( - context: Context, - backgroundContext: CoroutineContext, - fileName: String, -): String? { - return withContext(backgroundContext) { - if (fileName.isNotEmpty()) { - context.assets.open(fileName).bufferedReader().use { it.readText() } - } else { - null - } - } -} - -suspend fun getQuestionnaireJsonStringFromFileUri( - context: Context, - backgroundContext: CoroutineContext, - uri: Uri, -): String { - return withContext(backgroundContext) { - val reader = BufferedReader(context.contentResolver.openInputStream(uri)?.reader()) - reader.use { reader -> reader.readText() } - } -} diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/QuestionnaireResponseDialogFragment.kt b/catalog/src/main/java/com/google/android/fhir/catalog/QuestionnaireResponseDialogFragment.kt deleted file mode 100644 index 3e4100f1b1..0000000000 --- a/catalog/src/main/java/com/google/android/fhir/catalog/QuestionnaireResponseDialogFragment.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2021-2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.fhir.catalog - -import android.app.AlertDialog -import android.app.Dialog -import android.os.Bundle -import androidx.fragment.app.DialogFragment -import com.google.android.fhir.catalog.databinding.QuestionnaireResponseDialogContentsBinding - -class QuestionnaireResponseDialogFragment() : DialogFragment() { - private var _binding: QuestionnaireResponseDialogContentsBinding? = null - private val binding - get() = _binding!! - - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val contents = requireArguments().getString(BUNDLE_KEY_CONTENTS) - return activity?.let { - _binding = QuestionnaireResponseDialogContentsBinding.inflate(layoutInflater) - binding.contents.text = contents - - AlertDialog.Builder(it).setView(binding.root).create() - } - ?: throw IllegalStateException("Activity cannot be null") - } - - companion object { - const val TAG = "questionnaire-response-dialog-fragment" - const val BUNDLE_KEY_CONTENTS = "contents" - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } -} diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/QuestionnaireResponseFragment.kt b/catalog/src/main/java/com/google/android/fhir/catalog/QuestionnaireResponseFragment.kt deleted file mode 100644 index a596dd8820..0000000000 --- a/catalog/src/main/java/com/google/android/fhir/catalog/QuestionnaireResponseFragment.kt +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2021-2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.fhir.catalog - -import android.os.Bundle -import android.view.Gravity -import android.view.LayoutInflater -import android.view.MenuItem -import android.view.View -import android.view.ViewGroup -import android.widget.Button -import android.widget.TextView -import androidx.appcompat.app.AppCompatActivity -import androidx.fragment.app.Fragment -import androidx.navigation.fragment.NavHostFragment -import androidx.navigation.fragment.navArgs -import org.json.JSONObject - -class QuestionnaireResponseFragment : Fragment() { - private val args: QuestionnaireResponseFragmentArgs by navArgs() - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle?, - ): View? { - return inflater.inflate(R.layout.fragment_questionnaire_response, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - setCloseOnClickListener() - view.findViewById(R.id.questionnaire_response_tv).text = - JSONObject(args.questionnaireResponse).toString(2) - (activity as? MainActivity)?.showOpenQuestionnaireMenu(false) - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - return when (item.itemId) { - android.R.id.home -> { - NavHostFragment.findNavController(this).navigateUp() - true - } - else -> super.onOptionsItemSelected(item) - } - } - - override fun onResume() { - super.onResume() - setUpActionBar() - } - - private fun setUpActionBar() { - (requireActivity() as AppCompatActivity).supportActionBar?.apply { - setDisplayHomeAsUpEnabled(true) - } - (requireActivity() as MainActivity).setActionBar( - getString(R.string.questionnaire_response_title), - Gravity.START, - ) - setHasOptionsMenu(true) - } - - private fun setCloseOnClickListener() { - view?.findViewById