|
| 1 | +package com.example.nav3recipes.deeplink.basic |
| 2 | + |
| 3 | +import android.os.Bundle |
| 4 | +import androidx.activity.ComponentActivity |
| 5 | +import androidx.activity.compose.setContent |
| 6 | +import androidx.compose.runtime.LaunchedEffect |
| 7 | +import androidx.compose.runtime.getValue |
| 8 | +import androidx.compose.runtime.mutableStateMapOf |
| 9 | +import androidx.compose.runtime.mutableStateOf |
| 10 | +import androidx.compose.runtime.remember |
| 11 | +import androidx.compose.runtime.setValue |
| 12 | +import com.example.nav3recipes.deeplink.basic.ui.DeepLinkButton |
| 13 | +import com.example.nav3recipes.deeplink.basic.ui.EMPTY |
| 14 | +import com.example.nav3recipes.deeplink.basic.ui.EntryScreen |
| 15 | +import com.example.nav3recipes.deeplink.basic.ui.FIRST_NAME_JOHN |
| 16 | +import com.example.nav3recipes.deeplink.basic.ui.FIRST_NAME_JULIE |
| 17 | +import com.example.nav3recipes.deeplink.basic.ui.FIRST_NAME_MARY |
| 18 | +import com.example.nav3recipes.deeplink.basic.ui.FIRST_NAME_TOM |
| 19 | +import com.example.nav3recipes.deeplink.basic.ui.LOCATION_BC |
| 20 | +import com.example.nav3recipes.deeplink.basic.ui.LOCATION_BR |
| 21 | +import com.example.nav3recipes.deeplink.basic.ui.LOCATION_CA |
| 22 | +import com.example.nav3recipes.deeplink.basic.ui.LOCATION_US |
| 23 | +import com.example.nav3recipes.deeplink.basic.ui.MenuDropDown |
| 24 | +import com.example.nav3recipes.deeplink.basic.ui.MenuTextInput |
| 25 | +import com.example.nav3recipes.deeplink.basic.ui.PATH_BASE |
| 26 | +import com.example.nav3recipes.deeplink.basic.ui.PATH_INCLUDE |
| 27 | +import com.example.nav3recipes.deeplink.basic.ui.PATH_SEARCH |
| 28 | +import com.example.nav3recipes.deeplink.basic.ui.STRING_LITERAL_HOME |
| 29 | +import com.example.nav3recipes.deeplink.basic.ui.SearchKey |
| 30 | +import com.example.nav3recipes.deeplink.basic.ui.TextContent |
| 31 | +import com.example.nav3recipes.deeplink.basic.ui.HomeKey |
| 32 | +import com.example.nav3recipes.deeplink.basic.ui.UsersKey |
| 33 | + |
| 34 | +/** |
| 35 | + * This activity allows the user to create a deep link and make a request with it. |
| 36 | + * |
| 37 | + * **HOW THIS RECIPE WORKS** it consists of two activities - [CreateDeepLinkActivity] to construct |
| 38 | + * and trigger the deeplink request, and the [MainActivity] to show how an app can handle |
| 39 | + * that request. |
| 40 | + * |
| 41 | + * **DEMONSTRATED FORMS OF DEEPLINK** The [MainActivity] has a several backStack keys to |
| 42 | + * demonstrate different types of supported deeplinks: |
| 43 | + * 1. [HomeKey] - deeplink with an exact url (no deeplink arguments) |
| 44 | + * 2. [UsersKey] - deeplink with path arguments |
| 45 | + * 3. [SearchKey] - deeplink with query arguments |
| 46 | + * See [MainActivity.deepLinkPatterns] for the actual url pattern of each. |
| 47 | + * |
| 48 | + * **RECIPE STRUCTURE** This recipe consists of three main packages: |
| 49 | + * 1. basic.deeplink - Contains the two activities |
| 50 | + * 2. basic.deeplink.ui - Contains the activity UI code, i.e. Screens, global string variables etc |
| 51 | + * 3. basic.deeplink.deeplinkutil - Contains the classes and helper methods to parse and match |
| 52 | + * the deeplinks |
| 53 | + * |
| 54 | + * See [MainActivity] for how the requested deeplink is handled. |
| 55 | + */ |
| 56 | +class CreateDeepLinkActivity : ComponentActivity() { |
| 57 | + override fun onCreate(savedInstanceState: Bundle?) { |
| 58 | + super.onCreate(savedInstanceState) |
| 59 | + |
| 60 | + setContent { |
| 61 | + /** |
| 62 | + * UI for deeplink sandbox |
| 63 | + */ |
| 64 | + EntryScreen("Sandbox - Build Your Deeplink") { |
| 65 | + TextContent("Base url:\n${PATH_BASE}/") |
| 66 | + var showFilterOptions by remember { mutableStateOf(false) } |
| 67 | + val selectedPath = remember { mutableStateOf(MENU_OPTIONS_PATH[KEY_PATH]?.first()) } |
| 68 | + |
| 69 | + var showQueryOptions by remember { mutableStateOf(false) } |
| 70 | + var selectedFilter by remember { mutableStateOf("") } |
| 71 | + val selectedSearchQuery = remember { mutableStateMapOf<String, String>() } |
| 72 | + |
| 73 | + // manage path options |
| 74 | + MenuDropDown( |
| 75 | + menuOptions = MENU_OPTIONS_PATH, |
| 76 | + ) { _, selection -> |
| 77 | + selectedPath.value = selection |
| 78 | + when (selection) { |
| 79 | + PATH_SEARCH -> { |
| 80 | + showQueryOptions = true |
| 81 | + showFilterOptions = false |
| 82 | + } |
| 83 | + |
| 84 | + PATH_INCLUDE -> { |
| 85 | + showQueryOptions = false |
| 86 | + showFilterOptions = true |
| 87 | + } |
| 88 | + |
| 89 | + else -> { |
| 90 | + showQueryOptions = false |
| 91 | + showFilterOptions = false |
| 92 | + } |
| 93 | + } |
| 94 | + } |
| 95 | + |
| 96 | + // manage path filter options, reset state if menu is closed |
| 97 | + LaunchedEffect(showFilterOptions) { |
| 98 | + selectedFilter = if (showFilterOptions) { |
| 99 | + MENU_OPTIONS_FILTER.values.first().first() |
| 100 | + } else { |
| 101 | + "" |
| 102 | + } |
| 103 | + } |
| 104 | + if (showFilterOptions) { |
| 105 | + MenuDropDown( |
| 106 | + menuOptions = MENU_OPTIONS_FILTER, |
| 107 | + ) { _, selected -> |
| 108 | + selectedFilter = selected |
| 109 | + } |
| 110 | + } |
| 111 | + |
| 112 | + // manage query options, reset state if menu is closed |
| 113 | + LaunchedEffect(showQueryOptions) { |
| 114 | + if (showQueryOptions) { |
| 115 | + val initEntry = MENU_OPTIONS_SEARCH.entries.first() |
| 116 | + selectedSearchQuery[initEntry.key] = initEntry.value.first() |
| 117 | + } else { |
| 118 | + selectedSearchQuery.clear() |
| 119 | + } |
| 120 | + } |
| 121 | + if (showQueryOptions) { |
| 122 | + MenuTextInput( |
| 123 | + menuLabels = MENU_LABELS_SEARCH, |
| 124 | + ) { label, selected -> |
| 125 | + selectedSearchQuery[label] = selected |
| 126 | + } |
| 127 | + MenuDropDown( |
| 128 | + menuOptions = MENU_OPTIONS_SEARCH, |
| 129 | + ) { label, selected -> |
| 130 | + selectedSearchQuery[label] = selected |
| 131 | + } |
| 132 | + } |
| 133 | + |
| 134 | + // form final deeplink url |
| 135 | + val arguments = when (selectedPath.value) { |
| 136 | + PATH_INCLUDE -> "/${selectedFilter}" |
| 137 | + PATH_SEARCH -> { |
| 138 | + buildString { |
| 139 | + selectedSearchQuery.forEach { entry -> |
| 140 | + if (entry.value.isNotEmpty()) { |
| 141 | + val prefix = if (isEmpty()) "?" else "&" |
| 142 | + append("$prefix${entry.key}=${entry.value}") |
| 143 | + } |
| 144 | + } |
| 145 | + } |
| 146 | + } |
| 147 | + |
| 148 | + else -> "" |
| 149 | + } |
| 150 | + val finalUrl = "${PATH_BASE}/${selectedPath.value}$arguments" |
| 151 | + TextContent("Final url:\n$finalUrl") |
| 152 | + // deeplink to target |
| 153 | + DeepLinkButton( |
| 154 | + context = this@CreateDeepLinkActivity, |
| 155 | + targetActivity = MainActivity::class.java, |
| 156 | + deepLinkUrl = finalUrl |
| 157 | + ) |
| 158 | + } |
| 159 | + } |
| 160 | + } |
| 161 | +} |
| 162 | + |
| 163 | +private const val KEY_PATH = "path" |
| 164 | +private val MENU_OPTIONS_PATH = mapOf( |
| 165 | + KEY_PATH to listOf( |
| 166 | + STRING_LITERAL_HOME, |
| 167 | + PATH_INCLUDE, |
| 168 | + PATH_SEARCH, |
| 169 | + ), |
| 170 | +) |
| 171 | + |
| 172 | +private val MENU_OPTIONS_FILTER = mapOf( |
| 173 | + UsersKey.FILTER_KEY to listOf(UsersKey.FILTER_OPTION_RECENTLY_ADDED, UsersKey.FILTER_OPTION_ALL), |
| 174 | +) |
| 175 | + |
| 176 | +private val MENU_OPTIONS_SEARCH = mapOf( |
| 177 | + SearchKey::firstName.name to listOf( |
| 178 | + EMPTY, |
| 179 | + FIRST_NAME_JOHN, |
| 180 | + FIRST_NAME_TOM, |
| 181 | + FIRST_NAME_MARY, |
| 182 | + FIRST_NAME_JULIE |
| 183 | + ), |
| 184 | + SearchKey::location.name to listOf(EMPTY, LOCATION_CA, LOCATION_BC, LOCATION_BR, LOCATION_US) |
| 185 | +) |
| 186 | + |
| 187 | +private val MENU_LABELS_SEARCH = listOf(SearchKey::ageMin.name, SearchKey::ageMax.name) |
| 188 | + |
| 189 | + |
0 commit comments