├── .gitignore
├── .gitlab-ci.yml
├── LICENSE
├── README.md
├── assets
├── gifs
│ └── survey-kit-demo.gif
├── top
│ ├── color-picker.png
│ ├── how_old_are_you_with_hint.png
│ ├── instruction_qbs.png
│ ├── known_allergies_with_2_selected.png
│ ├── single_choice_selection_1_selection.png
│ └── surveykit-logo.png
└── unused
│ ├── instruction_green.png
│ ├── instruction_orange.png
│ ├── instruction_start_cyan.png
│ ├── instruction_start_german_cyan.png
│ ├── integer_question_disabled.png
│ ├── multiple_choice_question_1_selected.png
│ ├── multiple_choice_question_multiple_selected.png
│ ├── multiple_choice_question_no_selected.png
│ ├── pick_a_value.png
│ ├── scale_question.png
│ ├── screenshot_main.png
│ ├── text_question_cyan_enabled.png
│ └── text_question_disabled_cyan.png
├── build.gradle.kts
├── buildSrc
├── build.gradle.kts
└── src
│ └── main
│ └── kotlin
│ ├── ApiKeys.kt
│ ├── Deps.kt
│ ├── Library.kt
│ └── Project.kt
├── example
├── build.gradle.kts
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── quickbirdstudios
│ │ └── example
│ │ ├── App.kt
│ │ ├── YandexAddressSuggestionProvider.kt
│ │ └── ui
│ │ └── main
│ │ └── MainActivity.kt
│ └── res
│ ├── drawable
│ ├── color1.xml
│ ├── color2.xml
│ ├── color3.xml
│ ├── color4.xml
│ ├── color5.xml
│ ├── color6.xml
│ ├── example2.png
│ └── example3.jpg
│ ├── layout
│ └── main_activity.xml
│ └── values
│ ├── colors.xml
│ ├── strings.xml
│ └── styles.xml
├── git-hook-install.gradle.kts
├── git-hooks
├── commit-msg
├── install.sh
└── pre-push
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── ktlint
├── settings.gradle
├── survey
├── build.gradle.kts
└── src
│ ├── main
│ ├── AndroidManifest.xml
│ ├── assets
│ │ └── fancy_checkmark.json
│ ├── java
│ │ ├── com.quickbirdstudios.surveykit
│ │ │ ├── AnswerFormat.kt
│ │ │ ├── FinishReason.kt
│ │ │ ├── Identifier.kt
│ │ │ ├── NavigationRule.kt
│ │ │ ├── SurveyTheme.kt
│ │ │ ├── Task.kt
│ │ │ ├── backend
│ │ │ │ ├── helpers
│ │ │ │ │ ├── extensions
│ │ │ │ │ │ ├── Context.kt
│ │ │ │ │ │ ├── Dimensions.kt
│ │ │ │ │ │ ├── Drawable.kt
│ │ │ │ │ │ ├── EditText+afterTextChanged.kt
│ │ │ │ │ │ └── View.kt
│ │ │ │ │ └── logTag.kt
│ │ │ │ ├── navigator
│ │ │ │ │ ├── NavigableOrderedTaskNavigator.kt
│ │ │ │ │ ├── OrderedTaskNavigator.kt
│ │ │ │ │ └── TaskNavigator.kt
│ │ │ │ ├── presenter
│ │ │ │ │ ├── NextAction.kt
│ │ │ │ │ ├── Presenter.kt
│ │ │ │ │ ├── PresenterImpl.kt
│ │ │ │ │ └── animations
│ │ │ │ │ │ └── ViewAnimator.kt
│ │ │ │ ├── result_gatherer
│ │ │ │ │ ├── ResultGatherer.kt
│ │ │ │ │ └── ResultGathererImpl.kt
│ │ │ │ └── views
│ │ │ │ │ ├── main_parts
│ │ │ │ │ ├── AbortDialogConfiguration.kt
│ │ │ │ │ ├── Content.kt
│ │ │ │ │ ├── DialogConfiguration.kt
│ │ │ │ │ ├── Dialogs.kt
│ │ │ │ │ ├── Footer.kt
│ │ │ │ │ ├── Header.kt
│ │ │ │ │ └── StyleablePart.kt
│ │ │ │ │ ├── question_parts
│ │ │ │ │ ├── DatePickerPart.kt
│ │ │ │ │ ├── DateTimePickerPart.kt
│ │ │ │ │ ├── InfoTextPart.kt
│ │ │ │ │ ├── IntegerTextFieldPart.kt
│ │ │ │ │ ├── LocationPickerPart.kt
│ │ │ │ │ ├── MultipleChoicePart.kt
│ │ │ │ │ ├── QuestionAnimation.kt
│ │ │ │ │ ├── ScalePart.kt
│ │ │ │ │ ├── SingleChoicePart.kt
│ │ │ │ │ ├── TextFieldPart.kt
│ │ │ │ │ ├── TimePickerPart.kt
│ │ │ │ │ ├── ValuePickerPart.kt
│ │ │ │ │ ├── helper
│ │ │ │ │ │ └── Checkable+createSelectableThemedBackground.kt
│ │ │ │ │ └── imageSelector
│ │ │ │ │ │ ├── ImageSelectorAdapter.kt
│ │ │ │ │ │ └── ImageSelectorPart.kt
│ │ │ │ │ ├── questions
│ │ │ │ │ ├── BooleanQuestionView.kt
│ │ │ │ │ ├── DatePickerQuestionView.kt
│ │ │ │ │ ├── DateTimePickerQuestionView.kt
│ │ │ │ │ ├── EmailQuestionView.kt
│ │ │ │ │ ├── FinishQuestionView.kt
│ │ │ │ │ ├── ImageSelectorQuestionView.kt
│ │ │ │ │ ├── IntegerQuestionView.kt
│ │ │ │ │ ├── IntroQuestionView.kt
│ │ │ │ │ ├── LocationPickerQuestionView.kt
│ │ │ │ │ ├── MultipleChoiceQuestionView.kt
│ │ │ │ │ ├── ScaleQuestionView.kt
│ │ │ │ │ ├── SingleChoiceQuestionView.kt
│ │ │ │ │ ├── TextQuestionView.kt
│ │ │ │ │ ├── TimePickerQuestionView.kt
│ │ │ │ │ └── ValuePickerQuestionView.kt
│ │ │ │ │ └── step
│ │ │ │ │ ├── Identifiable.kt
│ │ │ │ │ ├── QuestionView.kt
│ │ │ │ │ ├── Skipable.kt
│ │ │ │ │ ├── StepView.kt
│ │ │ │ │ └── ViewActions.kt
│ │ │ ├── result
│ │ │ │ ├── QuestionResult.kt
│ │ │ │ ├── Result.kt
│ │ │ │ ├── StepResult.kt
│ │ │ │ ├── TaskResult.kt
│ │ │ │ └── question_results
│ │ │ │ │ ├── BooleanQuestionResult.kt
│ │ │ │ │ ├── DateQuestionResult.kt
│ │ │ │ │ ├── DateTimeQuestionResult.kt
│ │ │ │ │ ├── EmailQuestionResult.kt
│ │ │ │ │ ├── FinishQuestionResult.kt
│ │ │ │ │ ├── ImageSelectorResult.kt
│ │ │ │ │ ├── IntegerQuestionResult.kt
│ │ │ │ │ ├── IntroQuestionResult.kt
│ │ │ │ │ ├── LocationQuestionResult.kt
│ │ │ │ │ ├── MultipleChoiceQuestionResult.kt
│ │ │ │ │ ├── ScaleQuestionResult.kt
│ │ │ │ │ ├── SingleChoiceQuestionResult.kt
│ │ │ │ │ ├── TextQuestionResult.kt
│ │ │ │ │ ├── TimeQuestionResult.kt
│ │ │ │ │ └── ValuePickerQuestionResult.kt
│ │ │ ├── steps
│ │ │ │ ├── CompletionStep.kt
│ │ │ │ ├── InstructionStep.kt
│ │ │ │ ├── QuestionStep.kt
│ │ │ │ └── Step.kt
│ │ │ └── survey
│ │ │ │ ├── Survey.kt
│ │ │ │ └── SurveyView.kt
│ │ └── com
│ │ │ └── quickbirdstudios
│ │ │ └── surveykit
│ │ │ ├── backend
│ │ │ └── address
│ │ │ │ ├── AddressSuggestionProvider.kt
│ │ │ │ └── GeocoderAddressSuggestionProvider.kt
│ │ │ └── extensions
│ │ │ └── ViewExtensions.kt
│ └── res
│ │ ├── anim
│ │ ├── enter_from_left.xml
│ │ ├── enter_from_right.xml
│ │ ├── exit_to_left.xml
│ │ ├── exit_to_right.xml
│ │ ├── slide_in_down.xml
│ │ ├── slide_in_up.xml
│ │ ├── slide_out_down.xml
│ │ └── slide_out_up.xml
│ │ ├── drawable
│ │ ├── check.xml
│ │ ├── header_background.xml
│ │ ├── ic_baseline_location_on.xml
│ │ ├── ic_baseline_search_24.xml
│ │ ├── input_border.xml
│ │ ├── input_border_bottom_border.xml
│ │ ├── input_border_bottom_border_pressed.xml
│ │ ├── input_border_pressed.xml
│ │ ├── left_arrow.xml
│ │ ├── main_button_background.xml
│ │ ├── skip_button_background.xml
│ │ └── tickmark.xml
│ │ ├── layout
│ │ ├── custom_step.xml
│ │ ├── layout_content.xml
│ │ ├── layout_footer.xml
│ │ ├── layout_header.xml
│ │ ├── layout_image_selector_image.xml
│ │ ├── layout_info_field.xml
│ │ ├── layout_question.xml
│ │ ├── lottie_checkmark_wrapper.xml
│ │ ├── main_activity.xml
│ │ ├── survey_activity_empty.xml
│ │ ├── test_activity.xml
│ │ └── view_question.xml
│ │ ├── raw
│ │ └── fancy_checkmark.json
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── ids.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── quickbirdstudios
│ └── test
│ ├── NavigableOrderedTaskNavigatorTest.kt
│ ├── OrderedTaskNavigatorTest.kt
│ ├── ResultGathererTest.kt
│ └── TaskNavigatorTest.kt
└── test
├── build.gradle.kts
└── src
├── androidTest
└── java
│ └── com
│ └── quickbirdstudios
│ └── test
│ ├── FullSurveyTest.kt
│ └── pages
│ ├── PageTest+testBooleanChoiceStep.kt
│ ├── PageTest+testCompletionStep.kt
│ ├── PageTest+testCustomStep.kt
│ ├── PageTest+testDatePickerStep.kt
│ ├── PageTest+testEmailStep.kt
│ ├── PageTest+testImageSelectorStep.kt
│ ├── PageTest+testIntroStep.kt
│ ├── PageTest+testLocationPickerStep.kt
│ ├── PageTest+testMultipleChoiceStep.kt
│ ├── PageTest+testNumberStep.kt
│ ├── PageTest+testScaleStep.kt
│ ├── PageTest+testSingleChoiceStep.kt
│ ├── PageTest+testTextStep.kt
│ ├── PageTest+testTimePickerStep.kt
│ ├── PageTest+testValuePickerStep.kt
│ └── PageTest.kt
└── main
├── AndroidManifest.xml
├── java
└── com
│ └── quickbirdstudios
│ └── test
│ ├── TestActivity.kt
│ ├── TestAddressProvider.kt
│ └── TestApp.kt
└── res
├── drawable
├── example2.png
└── example3.jpg
└── values
└── strings.xml
/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | variables:
2 | GIT_CLEAN_FLAGS: none
3 |
4 | stages:
5 | - test
6 | - deploy
7 |
8 | unitTests:
9 | stage: test
10 | tags:
11 | - android
12 | script:
13 | - ./gradlew clean
14 | - ./gradlew testDebugUnitTest
15 |
16 | appTests:
17 | tags:
18 | - android
19 | - emulator
20 | stage: test
21 | script:
22 | - ./gradlew clean
23 | - ./gradlew cDAT
24 |
25 | deployRelease:
26 | stage: deploy
27 | only:
28 | - tags
29 | script:
30 | - ./gradlew publish
31 |
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 QuickBird Studios
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/assets/gifs/survey-kit-demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/SurveyKit/83cd95cc79db67fb96e642e84b815a4c8b5fb5ab/assets/gifs/survey-kit-demo.gif
--------------------------------------------------------------------------------
/assets/top/color-picker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/SurveyKit/83cd95cc79db67fb96e642e84b815a4c8b5fb5ab/assets/top/color-picker.png
--------------------------------------------------------------------------------
/assets/top/how_old_are_you_with_hint.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/SurveyKit/83cd95cc79db67fb96e642e84b815a4c8b5fb5ab/assets/top/how_old_are_you_with_hint.png
--------------------------------------------------------------------------------
/assets/top/instruction_qbs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/SurveyKit/83cd95cc79db67fb96e642e84b815a4c8b5fb5ab/assets/top/instruction_qbs.png
--------------------------------------------------------------------------------
/assets/top/known_allergies_with_2_selected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/SurveyKit/83cd95cc79db67fb96e642e84b815a4c8b5fb5ab/assets/top/known_allergies_with_2_selected.png
--------------------------------------------------------------------------------
/assets/top/single_choice_selection_1_selection.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/SurveyKit/83cd95cc79db67fb96e642e84b815a4c8b5fb5ab/assets/top/single_choice_selection_1_selection.png
--------------------------------------------------------------------------------
/assets/top/surveykit-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/SurveyKit/83cd95cc79db67fb96e642e84b815a4c8b5fb5ab/assets/top/surveykit-logo.png
--------------------------------------------------------------------------------
/assets/unused/instruction_green.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/SurveyKit/83cd95cc79db67fb96e642e84b815a4c8b5fb5ab/assets/unused/instruction_green.png
--------------------------------------------------------------------------------
/assets/unused/instruction_orange.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/SurveyKit/83cd95cc79db67fb96e642e84b815a4c8b5fb5ab/assets/unused/instruction_orange.png
--------------------------------------------------------------------------------
/assets/unused/instruction_start_cyan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/SurveyKit/83cd95cc79db67fb96e642e84b815a4c8b5fb5ab/assets/unused/instruction_start_cyan.png
--------------------------------------------------------------------------------
/assets/unused/instruction_start_german_cyan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/SurveyKit/83cd95cc79db67fb96e642e84b815a4c8b5fb5ab/assets/unused/instruction_start_german_cyan.png
--------------------------------------------------------------------------------
/assets/unused/integer_question_disabled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/SurveyKit/83cd95cc79db67fb96e642e84b815a4c8b5fb5ab/assets/unused/integer_question_disabled.png
--------------------------------------------------------------------------------
/assets/unused/multiple_choice_question_1_selected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/SurveyKit/83cd95cc79db67fb96e642e84b815a4c8b5fb5ab/assets/unused/multiple_choice_question_1_selected.png
--------------------------------------------------------------------------------
/assets/unused/multiple_choice_question_multiple_selected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/SurveyKit/83cd95cc79db67fb96e642e84b815a4c8b5fb5ab/assets/unused/multiple_choice_question_multiple_selected.png
--------------------------------------------------------------------------------
/assets/unused/multiple_choice_question_no_selected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/SurveyKit/83cd95cc79db67fb96e642e84b815a4c8b5fb5ab/assets/unused/multiple_choice_question_no_selected.png
--------------------------------------------------------------------------------
/assets/unused/pick_a_value.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/SurveyKit/83cd95cc79db67fb96e642e84b815a4c8b5fb5ab/assets/unused/pick_a_value.png
--------------------------------------------------------------------------------
/assets/unused/scale_question.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/SurveyKit/83cd95cc79db67fb96e642e84b815a4c8b5fb5ab/assets/unused/scale_question.png
--------------------------------------------------------------------------------
/assets/unused/screenshot_main.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/SurveyKit/83cd95cc79db67fb96e642e84b815a4c8b5fb5ab/assets/unused/screenshot_main.png
--------------------------------------------------------------------------------
/assets/unused/text_question_cyan_enabled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/SurveyKit/83cd95cc79db67fb96e642e84b815a4c8b5fb5ab/assets/unused/text_question_cyan_enabled.png
--------------------------------------------------------------------------------
/assets/unused/text_question_disabled_cyan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/SurveyKit/83cd95cc79db67fb96e642e84b815a4c8b5fb5ab/assets/unused/text_question_disabled_cyan.png
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompile
2 |
3 | buildscript {
4 | repositories {
5 | mavenCentral()
6 | google()
7 | maven { url = uri("https://repository.quickbirdstudios.com/repository/public") }
8 | }
9 |
10 | dependencies {
11 | classpath(Deps.Plugins.kotlin)
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | mavenCentral()
18 | google()
19 | }
20 |
21 | tasks.withType(KotlinJvmCompile::class.java).all {
22 | kotlinOptions {
23 | jvmTarget = "1.8"
24 | freeCompilerArgs = listOf(
25 | "-Xuse-experimental=kotlin.Experimental",
26 | "-Xuse-experimental=kotlinx.coroutines.ObsoleteCoroutinesApi",
27 | "-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi",
28 | "-Xuse-experimental=kotlin.ExperimentalStdlibApi"
29 | )
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/buildSrc/build.gradle.kts:
--------------------------------------------------------------------------------
1 |
2 |
3 | plugins {
4 | `kotlin-dsl`
5 | }
6 |
7 | repositories {
8 | mavenCentral()
9 | google()
10 | }
11 |
12 | dependencies {
13 | implementation(gradleApi())
14 | implementation(localGroovy())
15 | implementation("com.android.tools.build:gradle:4.0.2")
16 | }
17 |
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/ApiKeys.kt:
--------------------------------------------------------------------------------
1 | package com.quickbirdstudios.surveykit
2 |
3 | import org.gradle.api.Project
4 | import java.io.IOException
5 | import java.util.*
6 |
7 | object ApiKeys {
8 |
9 | fun Project.yandexMapsKey(): String {
10 | return propertyOrEmpty("yandex_sdk_key")
11 | }
12 |
13 | fun Project.googleMapsKey(): String {
14 | return propertyOrEmpty("google_sdk_key")
15 | }
16 |
17 | private fun Project.propertyOrEmpty(name: String): String {
18 | return findPropertyCasted(name)
19 | .fallback { findInLocalPropertiesFile(name) }
20 | .fallback { findByProperties(name) } ?: ""
21 | }
22 |
23 | private fun Project.findPropertyCasted(name: String): String? = findProperty(name) as String?
24 |
25 | private fun Project.findInLocalPropertiesFile(name: String): String? {
26 | val properties = Properties()
27 | try {
28 | properties.load(project.rootProject.file("local.properties").inputStream())
29 | } catch (e: IOException) {
30 | println("Error trying to load local.properties file")
31 | }
32 | return properties[name] as? String?
33 | }
34 |
35 | private fun Project.findByProperties(name: String): String? {
36 | return properties[name] as String?
37 | }
38 |
39 | private fun String?.fallback(block: () -> String?): String? {
40 | return this ?: block()
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/Deps.kt:
--------------------------------------------------------------------------------
1 | object Versions {
2 |
3 | const val kotlin = "1.3.50"
4 | const val coroutines = "1.3.0"
5 | const val test = "1.1.1"
6 | const val jUnit = "5.5.2"
7 | const val jUnitPlatform = "1.5.2"
8 | const val lottie = "3.0.7"
9 | const val espresso = "3.1.0"
10 |
11 | object AndroidSupport {
12 | const val appCompat = "1.0.0"
13 | const val constraintLayout = "1.1.3"
14 | const val annotation = "1.0.0"
15 | const val recyclerView = "1.0.0"
16 | }
17 |
18 | object PlayServices {
19 | const val maps = "17.0.0"
20 | }
21 |
22 | object Google {
23 | const val material = "1.2.1"
24 | }
25 | }
26 |
27 | object Deps {
28 |
29 | object Plugins {
30 | const val kotlin = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}"
31 | }
32 |
33 | object Kotlin {
34 | const val stdlib = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${Versions.kotlin}"
35 | const val reflect = "org.jetbrains.kotlin:kotlin-reflect:${Versions.kotlin}"
36 | const val coroutines =
37 | "org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.coroutines}"
38 | const val androidCoroutines =
39 | "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.coroutines}"
40 | }
41 |
42 | object PlayServices {
43 | const val maps = "com.google.android.gms:play-services-maps:${Versions.PlayServices.maps}"
44 | }
45 |
46 | object Google {
47 | const val material = "com.google.android.material:material:${Versions.Google.material}"
48 | }
49 |
50 | object AndroidSupport {
51 | const val appCompat = "androidx.appcompat:appcompat:${Versions.AndroidSupport.appCompat}"
52 | const val constraint =
53 | "androidx.constraintlayout:constraintlayout:${Versions.AndroidSupport.constraintLayout}"
54 | const val recyclerView =
55 | "androidx.recyclerview:recyclerview:${Versions.AndroidSupport.recyclerView}"
56 | }
57 |
58 | object Test {
59 | const val jUnitJupiter = "org.junit.jupiter:junit-jupiter-engine:${Versions.jUnit}"
60 | const val jUnitPlatform =
61 | "org.junit.platform:junit-platform-runner:${Versions.jUnitPlatform}"
62 | const val core = "androidx.test:core:${Versions.test}"
63 | const val runner = "androidx.test:runner:${Versions.test}"
64 | const val rules = "androidx.test:rules:${Versions.test}"
65 | const val junitExt = "androidx.test.ext:junit:${Versions.test}"
66 | const val espresso = "androidx.test.espresso:espresso-core:${Versions.espresso}"
67 | const val espressoIntents = "androidx.test.espresso:espresso-intents:${Versions.espresso}"
68 | const val espressoContribs = "androidx.test.espresso:espresso-contrib:${Versions.espresso}"
69 | }
70 |
71 | const val lottie = "com.airbnb.android:lottie:${Versions.lottie}"
72 | }
73 |
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/Library.kt:
--------------------------------------------------------------------------------
1 | object Library {
2 | const val version = "2.0.0-alpha-4"
3 | }
4 |
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/Project.kt:
--------------------------------------------------------------------------------
1 | object Project {
2 | object Android {
3 | const val compileSdkVersion = 28
4 | const val targetSdkVersion = 28
5 | const val minSdkVersion = 21
6 | const val testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/example/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import com.quickbirdstudios.surveykit.ApiKeys.googleMapsKey
2 | import com.quickbirdstudios.surveykit.ApiKeys.yandexMapsKey
3 |
4 | plugins {
5 | id("com.android.application")
6 | kotlin("android")
7 | id("org.jetbrains.kotlin.android.extensions")
8 | }
9 |
10 | androidExtensions { isExperimental = true }
11 |
12 | android {
13 | compileSdkVersion(Project.Android.compileSdkVersion)
14 |
15 | defaultConfig {
16 | versionName = Library.version
17 | versionCode = 1
18 | minSdkVersion(Project.Android.minSdkVersion)
19 | targetSdkVersion(Project.Android.targetSdkVersion)
20 | testInstrumentationRunner = Project.Android.testInstrumentationRunner
21 | resValue("string", "google_api_key", googleMapsKey())
22 | resValue("string", "yandex_api_key", yandexMapsKey())
23 | }
24 | packagingOptions {
25 | exclude("META-INF/*kotlin*")
26 | }
27 | }
28 |
29 | dependencies {
30 | implementation(project(":survey"))
31 |
32 | /* Kotlin */
33 | implementation(Deps.Kotlin.stdlib)
34 | implementation(Deps.Kotlin.reflect)
35 | implementation(Deps.Kotlin.coroutines)
36 |
37 | /* Android Support */
38 | implementation(Deps.AndroidSupport.appCompat)
39 | implementation(Deps.AndroidSupport.constraint)
40 | }
41 |
--------------------------------------------------------------------------------
/example/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
23 |
24 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/example/src/main/java/com/quickbirdstudios/example/App.kt:
--------------------------------------------------------------------------------
1 | package com.quickbirdstudios.example
2 |
3 | import android.app.Application
4 |
5 | class App : Application()
6 |
--------------------------------------------------------------------------------
/example/src/main/res/drawable/color1.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/example/src/main/res/drawable/color2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/example/src/main/res/drawable/color3.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/example/src/main/res/drawable/color4.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/example/src/main/res/drawable/color5.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/example/src/main/res/drawable/color6.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/example/src/main/res/drawable/example2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/SurveyKit/83cd95cc79db67fb96e642e84b815a4c8b5fb5ab/example/src/main/res/drawable/example2.png
--------------------------------------------------------------------------------
/example/src/main/res/drawable/example3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/SurveyKit/83cd95cc79db67fb96e642e84b815a4c8b5fb5ab/example/src/main/res/drawable/example3.jpg
--------------------------------------------------------------------------------
/example/src/main/res/layout/main_activity.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/example/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #36BFCC
6 |
7 | #33C5D4
8 | #3CD0DF
9 | #33B8C5
10 |
11 | #48E924
12 | #40D31F
13 | #38B41C
14 |
15 | #E75B30
16 | #DF552B
17 | #AA4020
18 |
19 | #000000
20 |
21 |
--------------------------------------------------------------------------------
/example/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Welcome to the\nQuickBird Studios\nHealth Survey
6 | Get ready for a bunch of super random questions!
7 | Let\'s go!
8 |
9 | Tell us about you
10 | Tell us about yourself and why you want to improve your health.
11 |
12 | How old are you?
13 |
14 |
15 | Select your body type
16 |
17 | 1
18 | 5
19 |
20 | Known allergies
21 | Do you have any allergies that we should be aware of?
22 | Penicillin
23 | Latex
24 | Pet
25 | Pollen
26 |
27 | Done?
28 | We are done, do you mind to tell us more about yourself?
29 |
30 | Done!
31 | Thanks for taking the survey, we will contact you soon!
32 | Submit survey
33 |
34 | Survey-Kit Test App
35 | Please enter your age
36 | Pick a value
37 | Here you can pick any value you\'d like!
38 | BooleanAnswerFormat example title
39 | BooleanAnswerFormat example text
40 | Date Picker title
41 | Date Picker text
42 | Time Picker title
43 | Time Picker text
44 | Email question text
45 | Email question title
46 | Image choice
47 | Pick a color
48 | DateTime Question
49 | Choose a date and time
50 | Are you sure you want to quit the survey?
51 | SurveyKit
52 | Yes
53 | No
54 | Select your home address
55 | Tell us about yourself and your home address.
56 |
57 |
58 |
--------------------------------------------------------------------------------
/example/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
18 |
19 |
--------------------------------------------------------------------------------
/git-hook-install.gradle.kts:
--------------------------------------------------------------------------------
1 | import java.io.File
2 |
3 | /*
4 | ################################################################################################
5 | GLOBALS
6 | ################################################################################################
7 | */
8 | val hookStartIdentifier = "#QBS-HOOK-START"
9 | val hookEndIdentifier = "#QBS-HOOK-END"
10 | val existingGitHookFolder = File(".git${File.separator}hooks")
11 | val existingGitHooks by lazy {
12 | existingGitHookFolder
13 | .listFiles()
14 | ?.filterNot { it.isSample() }
15 | ?: throw IllegalStateException("Missing ${existingGitHookFolder.absolutePath}")
16 | }
17 | val projectSpecificHookFolder = File("git-hooks")
18 | val projectSpecificGitHooks = projectSpecificHookFolder.listFiles()
19 | /*
20 | ################################################################################################
21 | HELPER FUNCTIONS
22 | ################################################################################################
23 | */
24 | fun copyGitHook(folder: File, gitHook: File) {
25 | val target = File(folder, gitHook.name)
26 | val wrappedHook = gitHook.readText().wrapInHookIdentifiers()
27 | target.writeText(wrappedHook)
28 | target.setExecutable(true)
29 | }
30 |
31 | fun getExistingGitHook(fileName: String) =
32 | existingGitHooks.firstOrNull { file -> file.name == fileName }
33 |
34 | fun File.isSample() = name.endsWith(".sample")
35 | fun File.insertHookIfNeeded(text: String) {
36 | val fileContent = readText()
37 | /*
38 | hook is up to date
39 | */
40 | if (fileContent.contains(text)) return
41 | val hook = text.wrapInHookIdentifiers()
42 | /*
43 | hook already exists, update it
44 | */
45 | if (fileContent.containsQbHook()) {
46 | val startIndex = fileContent.indexOf(hookStartIdentifier)
47 | val endIndex = fileContent.indexOf(hookEndIdentifier) + hookEndIdentifier.length
48 | val updatedContent = fileContent.replaceRange(startIndex, endIndex, hook)
49 | this.writeText(updatedContent)
50 | } else {
51 | /*
52 | hook isn't installed, append to the current file
53 | */
54 | this.appendText("\n\n$hook")
55 | }
56 | }
57 |
58 | fun String.containsQbHook() = contains(hookStartIdentifier) && contains(hookEndIdentifier)
59 | fun String.wrapInHookIdentifiers() = "$hookStartIdentifier\n$this\n$hookEndIdentifier"
60 | /*
61 | ################################################################################################
62 | TASK
63 | ################################################################################################
64 | */
65 | task("installGitHooks") {
66 | try {
67 | projectSpecificGitHooks.forEach { gitHook ->
68 |
69 | val existingHook = getExistingGitHook(gitHook.name)
70 | if (existingHook != null) {
71 | existingHook.insertHookIfNeeded(gitHook.readText())
72 | } else {
73 | copyGitHook(existingGitHookFolder, gitHook)
74 | }
75 | }
76 | } catch (e: Throwable) {
77 | print("Something went wrong while installing the git hooks: $e.message")
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/git-hooks/commit-msg:
--------------------------------------------------------------------------------
1 | #QBS-HOOK-START
2 | #!/usr/bin/env bash
3 | # Automatically adds branch name every commit message.
4 | NAME=$(git branch | grep '*' | sed 's/* //')
5 | MESSAGE="$(cat "$1")"
6 | FIRST_MESSAGE_CHAR=${MESSAGE:0:1}
7 |
8 | if [[ ${MESSAGE} != *${NAME}* ]]
9 | then
10 | if [[ ${FIRST_MESSAGE_CHAR} == "-" ]]
11 | then
12 | echo "$NAME:\n $(cat "$1")" > "$1"
13 | else
14 | echo "$NAME: $(cat "$1")" > "$1"
15 | fi
16 | fi
17 | #QBS-HOOK-END
18 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536m
2 | kotlin.code.style=official
3 | org.gradle.parallel=true
4 | android.useAndroidX=true
5 | android.enableJetifier=true
6 | repositoryName=SurveyKit
7 | projectSummary=Create beautiful surveys on Android
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/SurveyKit/83cd95cc79db67fb96e642e84b815a4c8b5fb5ab/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Apr 01 15:38:24 CEST 2019
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/ktlint:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuickBirdEng/SurveyKit/83cd95cc79db67fb96e642e84b815a4c8b5fb5ab/ktlint
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | mavenCentral()
4 | google()
5 | maven { url 'https://repository.quickbirdstudios.com/repository/public' }
6 | }
7 |
8 | dependencies {
9 | classpath("com.quickbirdstudios:gradle-publishing:0.1.0")
10 | }
11 | }
12 |
13 | rootProject.name = 'SurveyKit'
14 |
15 | include ':survey'
16 | include ':test'
17 | include ':example'
18 |
--------------------------------------------------------------------------------
/survey/build.gradle.kts:
--------------------------------------------------------------------------------
1 | @file:Suppress("SuspiciousCollectionReassignment")
2 |
3 | plugins {
4 | id("com.android.library")
5 | kotlin("android")
6 | id("org.jetbrains.kotlin.android.extensions")
7 | id("gradle-publishing")
8 | }
9 |
10 | androidExtensions { isExperimental = true }
11 |
12 | android {
13 | compileSdkVersion(Project.Android.compileSdkVersion)
14 |
15 | defaultConfig {
16 | version = Library.version
17 | minSdkVersion(Project.Android.minSdkVersion)
18 | targetSdkVersion(Project.Android.targetSdkVersion)
19 | testInstrumentationRunner = Project.Android.testInstrumentationRunner
20 | }
21 |
22 | testOptions {
23 | animationsDisabled = true
24 | }
25 | }
26 |
27 | dependencies {
28 | implementation(Deps.Kotlin.stdlib)
29 | api(Deps.Kotlin.coroutines)
30 | implementation(Deps.Kotlin.androidCoroutines)
31 | implementation(Deps.AndroidSupport.appCompat)
32 | implementation(Deps.AndroidSupport.constraint)
33 | implementation(Deps.AndroidSupport.recyclerView)
34 | implementation(Deps.lottie)
35 | implementation(Deps.PlayServices.maps)
36 | implementation(Deps.Google.material)
37 |
38 | testImplementation(Deps.Test.jUnitJupiter)
39 | testImplementation(Deps.Test.jUnitPlatform)
40 | }
--------------------------------------------------------------------------------
/survey/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/survey/src/main/java/com.quickbirdstudios.surveykit/FinishReason.kt:
--------------------------------------------------------------------------------
1 | package com.quickbirdstudios.surveykit
2 |
3 | // TODO check if Saved can be removed
4 | enum class FinishReason {
5 | Saved, Discarded, Completed, Failed;
6 | }
7 |
--------------------------------------------------------------------------------
/survey/src/main/java/com.quickbirdstudios.surveykit/Identifier.kt:
--------------------------------------------------------------------------------
1 | package com.quickbirdstudios.surveykit
2 |
3 | import android.os.Parcelable
4 | import java.util.UUID
5 | import kotlinx.android.parcel.Parcelize
6 |
7 | @Parcelize
8 | open class Identifier(open val id: String) : Parcelable
9 |
10 | @Parcelize
11 | data class TaskIdentifier(override val id: String = UUID.randomUUID().toString()) :
12 | Identifier(id),
13 | Parcelable
14 |
15 | @Parcelize
16 | data class StepIdentifier(
17 | override val id: String = UUID.randomUUID().toString()
18 | ) : Identifier(id), Parcelable
19 |
--------------------------------------------------------------------------------
/survey/src/main/java/com.quickbirdstudios.surveykit/NavigationRule.kt:
--------------------------------------------------------------------------------
1 | package com.quickbirdstudios.surveykit
2 |
3 | sealed class NavigationRule {
4 | data class DirectStepNavigationRule(
5 | val destinationStepStepIdentifier: StepIdentifier
6 | ) : NavigationRule()
7 |
8 | data class ConditionalDirectionStepNavigationRule(
9 | val resultToStepIdentifierMapper: (String) -> StepIdentifier?
10 | ) : NavigationRule()
11 | }
12 |
--------------------------------------------------------------------------------
/survey/src/main/java/com.quickbirdstudios.surveykit/SurveyTheme.kt:
--------------------------------------------------------------------------------
1 | package com.quickbirdstudios.surveykit
2 |
3 | import androidx.annotation.ColorInt
4 | import com.quickbirdstudios.surveykit.backend.views.main_parts.AbortDialogConfiguration
5 |
6 | // Todo rename to configuration for next major version
7 | data class SurveyTheme(
8 | @ColorInt val themeColorDark: Int,
9 | @ColorInt val themeColor: Int,
10 | @ColorInt val textColor: Int,
11 | val abortDialogConfiguration: AbortDialogConfiguration? = null
12 | )
13 |
--------------------------------------------------------------------------------
/survey/src/main/java/com.quickbirdstudios.surveykit/Task.kt:
--------------------------------------------------------------------------------
1 | package com.quickbirdstudios.surveykit
2 |
3 | import com.quickbirdstudios.surveykit.steps.Step
4 |
5 | interface Task {
6 | val id: TaskIdentifier
7 | val steps: List
8 |
9 | operator fun get(id: StepIdentifier) = steps.find { it.id == id }
10 | }
11 |
12 | open class OrderedTask(
13 | override val steps: List,
14 | override val id: TaskIdentifier = TaskIdentifier()
15 | ) : Task
16 |
17 | class NavigableOrderedTask(
18 | override val steps: List,
19 | override val id: TaskIdentifier = TaskIdentifier()
20 | ) : Task {
21 |
22 | private val stepIdentifierToNavigationRuleMap: MutableMap =
23 | mutableMapOf()
24 |
25 | fun setNavigationRule(
26 | forTriggerStepStepIdentifier: StepIdentifier,
27 | navigationRule: NavigationRule
28 | ) {
29 | stepIdentifierToNavigationRuleMap[forTriggerStepStepIdentifier] = navigationRule
30 | }
31 |
32 | internal fun getRule(id: StepIdentifier): NavigationRule? =
33 | stepIdentifierToNavigationRuleMap[id]
34 | }
35 |
--------------------------------------------------------------------------------
/survey/src/main/java/com.quickbirdstudios.surveykit/backend/helpers/extensions/Context.kt:
--------------------------------------------------------------------------------
1 | package com.quickbirdstudios.surveykit.backend.helpers.extensions
2 |
3 | import android.Manifest
4 | import android.content.Context
5 | import android.content.pm.PackageManager
6 | import androidx.core.app.ActivityCompat
7 |
8 | fun Context.isLocationPermissionGranted(): Boolean {
9 | return (
10 | ActivityCompat.checkSelfPermission(
11 | this,
12 | Manifest.permission.ACCESS_FINE_LOCATION
13 | ) == PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
14 | this,
15 | Manifest.permission.ACCESS_COARSE_LOCATION
16 | ) == PackageManager.PERMISSION_GRANTED
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/survey/src/main/java/com.quickbirdstudios.surveykit/backend/helpers/extensions/Dimensions.kt:
--------------------------------------------------------------------------------
1 | package com.quickbirdstudios.surveykit.backend.helpers.extensions
2 |
3 | import android.content.Context
4 | import android.util.DisplayMetrics
5 |
6 | fun Context.dp(px: Number): Float {
7 | val resources = this.resources
8 | val metrics = resources.displayMetrics
9 | return (
10 | px.toFloat() /
11 | (metrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT.toFloat())
12 | )
13 | }
14 |
15 | fun Context.px(dp: Number): Float {
16 | val resources = this.resources
17 | val metrics = resources.displayMetrics
18 | return (
19 | dp.toFloat() *
20 | (metrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT.toFloat())
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/survey/src/main/java/com.quickbirdstudios.surveykit/backend/helpers/extensions/Drawable.kt:
--------------------------------------------------------------------------------
1 | package com.quickbirdstudios.surveykit.backend.helpers.extensions
2 |
3 | import android.graphics.drawable.Drawable
4 | import android.graphics.drawable.GradientDrawable
5 |
6 | fun GradientDrawable.colorStroke(width: Int, color: Int): Drawable {
7 | val drawable = this.mutate() as GradientDrawable
8 | drawable.setStroke(width, color)
9 | return drawable
10 | }
11 |
--------------------------------------------------------------------------------
/survey/src/main/java/com.quickbirdstudios.surveykit/backend/helpers/extensions/EditText+afterTextChanged.kt:
--------------------------------------------------------------------------------
1 | package com.quickbirdstudios.surveykit.backend.helpers.extensions
2 |
3 | import android.text.Editable
4 | import android.text.TextWatcher
5 | import android.widget.EditText
6 |
7 | fun EditText.afterTextChanged(listener: (String) -> Unit) {
8 | addTextChangedListener(object : TextWatcher {
9 | override fun afterTextChanged(s: Editable) = listener(s.toString())
10 | override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
11 | override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit
12 | })
13 | }
14 |
--------------------------------------------------------------------------------
/survey/src/main/java/com.quickbirdstudios.surveykit/backend/helpers/extensions/View.kt:
--------------------------------------------------------------------------------
1 | package com.quickbirdstudios.surveykit.backend.helpers.extensions
2 |
3 | import android.content.Context
4 | import android.view.View
5 | import android.view.inputmethod.InputMethodManager
6 | import android.widget.LinearLayout
7 |
8 | fun View.verticalMargin(margin: Int) {
9 | (this.layoutParams as LinearLayout.LayoutParams).setMargins(0, margin, 0, margin)
10 | }
11 |
12 | fun View.horizontalMargin(margin: Int) {
13 | (this.layoutParams as LinearLayout.LayoutParams).setMargins(margin, 0, margin, 0)
14 | }
15 |
16 | fun View.hideKeyboard() {
17 | val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
18 | imm.hideSoftInputFromWindow(windowToken, 0)
19 | }
20 |
21 |
--------------------------------------------------------------------------------
/survey/src/main/java/com.quickbirdstudios.surveykit/backend/helpers/logTag.kt:
--------------------------------------------------------------------------------
1 | package com.quickbirdstudios.surveykit.backend.helpers
2 |
3 | val Any.logTag: String get() = this::class.java.simpleName.orEmpty().take(23)
4 |
--------------------------------------------------------------------------------
/survey/src/main/java/com.quickbirdstudios.surveykit/backend/navigator/NavigableOrderedTaskNavigator.kt:
--------------------------------------------------------------------------------
1 | package com.quickbirdstudios.surveykit.backend.navigator
2 |
3 | import com.quickbirdstudios.surveykit.NavigableOrderedTask
4 | import com.quickbirdstudios.surveykit.NavigationRule
5 | import com.quickbirdstudios.surveykit.result.StepResult
6 | import com.quickbirdstudios.surveykit.steps.Step
7 | import java.util.Stack
8 |
9 | internal class NavigableOrderedTaskNavigator(
10 | override val task: NavigableOrderedTask
11 | ) : TaskNavigator {
12 |
13 | //region Members
14 |
15 | override var history: Stack = Stack()
16 |
17 | //endregion
18 |
19 | //region Public API
20 |
21 | override fun startStep(stepResult: StepResult?): Step? {
22 | val previousStep = peekHistory()
23 | return if (previousStep == null) {
24 | task.steps.firstOrNull()
25 | } else {
26 | nextStep(previousStep, stepResult)
27 | }
28 | }
29 |
30 | override fun finalStep(): Step? = task.steps.lastOrNull()
31 |
32 | override fun nextStep(step: Step, stepResult: StepResult?): Step? {
33 | step.record()
34 |
35 | return when (val rule = step.extractRule() ?: return step.nextInList()) {
36 | is NavigationRule.DirectStepNavigationRule -> rule.evaluateNextStep()
37 | is NavigationRule.ConditionalDirectionStepNavigationRule ->
38 | rule.evaluateNextStep(step, stepResult)
39 | }
40 | }
41 |
42 | override fun previousStep(step: Step): Step? {
43 | return if (history.empty()) null
44 | else history.pop()
45 | }
46 |
47 | //endregion
48 |
49 | //region Private Helper
50 |
51 | private fun Step.extractRule(): NavigationRule? = task.getRule(this.id)
52 |
53 | private fun NavigationRule.DirectStepNavigationRule.evaluateNextStep() =
54 | task[this.destinationStepStepIdentifier]
55 |
56 | private fun NavigationRule.ConditionalDirectionStepNavigationRule.evaluateNextStep(
57 | step: Step,
58 | stepResult: StepResult?
59 | ): Step? {
60 | stepResult ?: return step.nextInList()
61 | val firstResult = stepResult.results.firstOrNull() ?: return step.nextInList()
62 | val nextStepIdentifier = this.resultToStepIdentifierMapper(firstResult.stringIdentifier)
63 | ?: return step.nextInList()
64 |
65 | return task[nextStepIdentifier]
66 | }
67 |
68 | //endregion
69 | }
70 |
--------------------------------------------------------------------------------
/survey/src/main/java/com.quickbirdstudios.surveykit/backend/navigator/OrderedTaskNavigator.kt:
--------------------------------------------------------------------------------
1 | package com.quickbirdstudios.surveykit.backend.navigator
2 |
3 | import com.quickbirdstudios.surveykit.OrderedTask
4 | import com.quickbirdstudios.surveykit.result.StepResult
5 | import com.quickbirdstudios.surveykit.steps.Step
6 | import java.util.Stack
7 |
8 | internal class OrderedTaskNavigator(
9 | override val task: OrderedTask
10 | ) : TaskNavigator {
11 |
12 | //region Public API
13 |
14 | override var history: Stack = Stack()
15 |
16 | override fun startStep(stepResult: StepResult?): Step? {
17 | val previousStep = peekHistory()
18 | return if (previousStep == null) task.steps.firstOrNull()
19 | else previousStep.nextInList()
20 | }
21 |
22 | override fun finalStep(): Step? = task.steps.lastOrNull()
23 |
24 | override fun nextStep(step: Step, stepResult: StepResult?): Step? {
25 | step.record()
26 | return step.nextInList()
27 | }
28 |
29 | override fun previousStep(step: Step): Step? = step.previousInList()
30 |
31 | //endregion
32 |
33 | //region Private Helper
34 |
35 | private fun Step.previousInList(): Step? {
36 | val currentStepIndex = task.steps.indexOf(this)
37 | return if (currentStepIndex - 1 < 0) null
38 | else task.steps[currentStepIndex - 1]
39 | }
40 |
41 | //endregion
42 | }
43 |
--------------------------------------------------------------------------------
/survey/src/main/java/com.quickbirdstudios.surveykit/backend/navigator/TaskNavigator.kt:
--------------------------------------------------------------------------------
1 | package com.quickbirdstudios.surveykit.backend.navigator
2 |
3 | import com.quickbirdstudios.surveykit.NavigableOrderedTask
4 | import com.quickbirdstudios.surveykit.OrderedTask
5 | import com.quickbirdstudios.surveykit.Task
6 | import com.quickbirdstudios.surveykit.result.StepResult
7 | import com.quickbirdstudios.surveykit.steps.Step
8 | import java.util.Stack
9 |
10 | interface TaskNavigator {
11 |
12 | val task: Task
13 |
14 | var history: Stack
15 |
16 | fun lastStepInHistory(): Step? = peekHistory()
17 |
18 | fun startStep(stepResult: StepResult?): Step?
19 |
20 | fun finalStep(): Step?
21 |
22 | fun nextStep(step: Step, stepResult: StepResult? = null): Step?
23 |
24 | fun previousStep(step: Step): Step?
25 |
26 | fun Step.nextInList(): Step? {
27 | val currentStepIndex = task.steps.indexOf(this)
28 | return if (currentStepIndex + 1 > task.steps.size - 1) null
29 | else task.steps[currentStepIndex + 1]
30 | }
31 |
32 | fun peekHistory(): Step? {
33 | if (history.isEmpty()) return null
34 | return history.peek()
35 | }
36 |
37 | fun hasPreviousStep() : Boolean {
38 | val previousStep = peekHistory()
39 | return previousStep != null
40 | }
41 |
42 | fun Step?.record() {
43 | if (this != null) {
44 | history.push(this)
45 | }
46 | }
47 |
48 | companion object {
49 | operator fun invoke(task: Task): TaskNavigator =
50 | when (task) {
51 | is OrderedTask -> OrderedTaskNavigator(
52 | task
53 | )
54 | is NavigableOrderedTask -> NavigableOrderedTaskNavigator(
55 | task
56 | )
57 | else -> throw NotImplementedError()
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/survey/src/main/java/com.quickbirdstudios.surveykit/backend/presenter/NextAction.kt:
--------------------------------------------------------------------------------
1 | package com.quickbirdstudios.surveykit.backend.presenter
2 |
3 | import com.quickbirdstudios.surveykit.FinishReason
4 | import com.quickbirdstudios.surveykit.result.StepResult
5 |
6 | sealed class NextAction {
7 | data class Next(val result: StepResult) : NextAction()
8 | data class Back(val result: StepResult) : NextAction()
9 | object Skip : NextAction()
10 | data class Close(val result: StepResult, val finishReason: FinishReason) : NextAction()
11 | }
12 |
--------------------------------------------------------------------------------
/survey/src/main/java/com.quickbirdstudios.surveykit/backend/presenter/Presenter.kt:
--------------------------------------------------------------------------------
1 | package com.quickbirdstudios.surveykit.backend.presenter
2 |
3 | import android.content.Context
4 | import android.widget.FrameLayout
5 | import com.quickbirdstudios.surveykit.SurveyTheme
6 | import com.quickbirdstudios.surveykit.backend.navigator.TaskNavigator
7 | import com.quickbirdstudios.surveykit.result.StepResult
8 | import com.quickbirdstudios.surveykit.steps.Step
9 |
10 | interface Presenter {
11 | val context: Context
12 | val viewContainer: FrameLayout
13 | val surveyTheme: SurveyTheme
14 | val taskNavigator: TaskNavigator
15 |
16 | suspend operator fun invoke(
17 | transition: Transition,
18 | step: Step,
19 | stepResult: StepResult?
20 | ): NextAction
21 | fun triggerBackOnCurrentView()
22 |
23 | enum class Transition {
24 | None, SlideFromRight, SlideFromLeft;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/survey/src/main/java/com.quickbirdstudios.surveykit/backend/result_gatherer/ResultGatherer.kt:
--------------------------------------------------------------------------------
1 | package com.quickbirdstudios.surveykit.backend.result_gatherer
2 |
3 | import com.quickbirdstudios.surveykit.StepIdentifier
4 | import com.quickbirdstudios.surveykit.result.StepResult
5 | import com.quickbirdstudios.surveykit.result.TaskResult
6 |
7 | interface ResultGatherer {
8 | val taskResult: TaskResult
9 | var results: MutableList
10 |
11 | fun store(stepResult: StepResult)
12 | fun retrieve(identifier: StepIdentifier): StepResult?
13 | }
14 |
--------------------------------------------------------------------------------
/survey/src/main/java/com.quickbirdstudios.surveykit/backend/result_gatherer/ResultGathererImpl.kt:
--------------------------------------------------------------------------------
1 | package com.quickbirdstudios.surveykit.backend.result_gatherer
2 |
3 | import com.quickbirdstudios.surveykit.StepIdentifier
4 | import com.quickbirdstudios.surveykit.Task
5 | import com.quickbirdstudios.surveykit.result.StepResult
6 | import com.quickbirdstudios.surveykit.result.TaskResult
7 | import java.util.Date
8 |
9 | internal class ResultGathererImpl(private val task: Task) : ResultGatherer {
10 |
11 | private val startDate: Date = Date()
12 |
13 | override var results: MutableList = mutableListOf()
14 |
15 | override val taskResult: TaskResult
16 | get() = TaskResult(
17 | id = task.id,
18 | results = results,
19 | startDate = startDate
20 | ).apply { endDate = Date() }
21 |
22 | override fun store(stepResult: StepResult) {
23 | val previousResult = retrieve(stepResult.id)
24 | if (previousResult == null) results.add(stepResult)
25 | else {
26 | val previousResultIndex = results.indexOf(previousResult)
27 | results[previousResultIndex] = stepResult
28 | }
29 | }
30 |
31 | override fun retrieve(identifier: StepIdentifier) = results.find { it.id == identifier }
32 | }
33 |
--------------------------------------------------------------------------------
/survey/src/main/java/com.quickbirdstudios.surveykit/backend/views/main_parts/AbortDialogConfiguration.kt:
--------------------------------------------------------------------------------
1 | package com.quickbirdstudios.surveykit.backend.views.main_parts
2 |
3 | import androidx.annotation.StringRes
4 |
5 | data class AbortDialogConfiguration(
6 | @StringRes val title: Int,
7 | @StringRes val message: Int,
8 | @StringRes val neutralMessage: Int,
9 | @StringRes val negativeMessage: Int
10 | )
11 |
--------------------------------------------------------------------------------
/survey/src/main/java/com.quickbirdstudios.surveykit/backend/views/main_parts/Content.kt:
--------------------------------------------------------------------------------
1 | package com.quickbirdstudios.surveykit.backend.views.main_parts
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.LinearLayout
8 | import androidx.core.widget.NestedScrollView
9 | import com.quickbirdstudios.surveykit.R
10 | import com.quickbirdstudios.surveykit.SurveyTheme
11 |
12 | class Content @JvmOverloads constructor(
13 | context: Context,
14 | attrs: AttributeSet? = null,
15 | defStyleRes: Int = 0
16 | ) : NestedScrollView(context, attrs, defStyleRes), StyleablePart {
17 |
18 | //region Member
19 |
20 | private val root: View = View.inflate(context, R.layout.layout_content, this)
21 | private val container: ViewGroup = root.findViewById(R.id.content_container)
22 | private val footerContainer: ViewGroup = root.findViewById(R.id.footer_container)
23 | private val footer: Footer = Footer(context).apply { id = R.id.questionFooter }
24 |
25 | //endregion
26 |
27 | //region Public API
28 |
29 | fun add(view: T): T {
30 | container.addView(view)
31 | return view
32 | }
33 |
34 | fun clear() {
35 | container.removeAllViews()
36 | }
37 |
38 | //endregion
39 |
40 | //region Overrides
41 |
42 | override fun style(surveyTheme: SurveyTheme) {
43 | (0 until container.childCount).forEach {
44 | (container.getChildAt(it) as StyleablePart).style(surveyTheme)
45 | }
46 | footer.style(surveyTheme)
47 | }
48 |
49 | //endregion
50 |
51 | init {
52 | this.layoutParams = LinearLayout.LayoutParams(
53 | LinearLayout.LayoutParams.MATCH_PARENT,
54 | 0
55 | ).apply {
56 | this.weight = 1f
57 | }
58 | footerContainer.addView(footer)
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/survey/src/main/java/com.quickbirdstudios.surveykit/backend/views/main_parts/DialogConfiguration.kt:
--------------------------------------------------------------------------------
1 | package com.quickbirdstudios.surveykit.backend.views.main_parts
2 |
3 | data class DialogConfiguration(
4 | val title: String,
5 | val message: String,
6 | val neutralMessage: String,
7 | val negativeMessage: String
8 | )
9 |
--------------------------------------------------------------------------------
/survey/src/main/java/com.quickbirdstudios.surveykit/backend/views/main_parts/Dialogs.kt:
--------------------------------------------------------------------------------
1 | package com.quickbirdstudios.surveykit.backend.views.main_parts
2 |
3 | import android.app.AlertDialog
4 | import android.content.Context
5 | import android.content.DialogInterface
6 | import android.graphics.Color
7 |
8 | internal class Dialogs {
9 |
10 | companion object {
11 | fun cancel(
12 | context: Context,
13 | abortDialogConfiguration: AbortDialogConfiguration,
14 | cancelAction: () -> Unit
15 | ): AlertDialog? {
16 | val dialog = AlertDialog.Builder(context)
17 | .setMessage(abortDialogConfiguration.message)
18 | .setCancelable(true)
19 | .setNegativeButton(abortDialogConfiguration.negativeMessage) { _, which ->
20 | if (which == DialogInterface.BUTTON_NEGATIVE)
21 | cancelAction()
22 | }
23 | .setNeutralButton(abortDialogConfiguration.neutralMessage) { _, _ -> }
24 | .create()
25 |
26 | dialog.setTitle(abortDialogConfiguration.title)
27 | dialog.show()
28 | dialog.setButtonColors()
29 | return dialog
30 | }
31 | }
32 | }
33 |
34 | private fun AlertDialog.setButtonColors() {
35 | val negativeButton = this.getButton(DialogInterface.BUTTON_NEGATIVE) ?: return
36 | negativeButton.setTextColor(Color.RED)
37 | }
38 |
--------------------------------------------------------------------------------
/survey/src/main/java/com.quickbirdstudios.surveykit/backend/views/main_parts/Header.kt:
--------------------------------------------------------------------------------
1 | package com.quickbirdstudios.surveykit.backend.views.main_parts
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.graphics.Color
6 | import android.util.AttributeSet
7 | import android.view.View
8 | import android.view.inputmethod.InputMethodManager
9 | import android.widget.Button
10 | import android.widget.ImageView
11 | import android.widget.RelativeLayout
12 | import android.widget.TextView
13 | import androidx.appcompat.widget.Toolbar
14 | import com.quickbirdstudios.surveykit.R
15 | import com.quickbirdstudios.surveykit.SurveyTheme
16 |
17 | // TODO should take [Configuration] in constructor and remove public color setters and getters
18 | class Header @JvmOverloads constructor(
19 | context: Context,
20 | attrs: AttributeSet? = null,
21 | defStyleRes: Int = 0
22 | ) : Toolbar(context, attrs, defStyleRes),
23 | StyleablePart {
24 |
25 | //region Public API
26 |
27 | var themeColor = Color.RED
28 | set(value) {
29 | cancelButton.setTextColor(value)
30 | headerBackButtonImage.background.setTint(value)
31 | field = value
32 | }
33 |
34 | var canCancel: Boolean
35 | get() = cancelButton.visibility != View.VISIBLE
36 | set(value) {
37 | cancelButton.visibility = if (value) View.VISIBLE else View.GONE
38 | }
39 |
40 | var canBack: Boolean
41 | get() = buttonBack.visibility != View.VISIBLE
42 | set(value) {
43 | buttonBack.visibility = if (value) View.VISIBLE else View.GONE
44 | }
45 |
46 | var cancelButtonText: String
47 | get() = cancelButton.text.toString()
48 | set(value) {
49 | cancelButton.text = value
50 | }
51 |
52 | var label: String
53 | get() = headerLabel.text.toString()
54 | set(label) {
55 | headerLabel.text = label
56 | }
57 |
58 | var onBack: () -> Unit = {}
59 | var onCancel: () -> Unit = {}
60 |
61 | //endregion
62 |
63 | //region Members
64 |
65 | private val root: View = View.inflate(context, R.layout.layout_header, this)
66 | private val headerLabel: TextView = root.findViewById(R.id.headerLabel)
67 | private val buttonBack = root.findViewById(R.id.headerBackButton).apply {
68 | setOnClickListener {
69 | hideKeyboard()
70 | onBack()
71 | }
72 | }
73 | private val headerBackButtonImage =
74 | buttonBack.findViewById(R.id.headerBackButtonImage).apply {
75 | background.setTint(themeColor)
76 | }
77 | private val cancelButton = root.findViewById