1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settingslib.spa.framework.common 18 19 import android.os.Bundle 20 import androidx.compose.runtime.Composable 21 import androidx.compose.runtime.CompositionLocalProvider 22 import androidx.compose.runtime.ProvidedValue 23 import androidx.compose.runtime.compositionLocalOf 24 import androidx.compose.runtime.remember 25 import com.android.settingslib.spa.framework.compose.LocalNavController 26 27 interface EntryData { 28 val pageId: String? 29 get() = null 30 val entryId: String? 31 get() = null 32 val isHighlighted: Boolean 33 get() = false 34 val arguments: Bundle? 35 get() = null 36 } 37 38 val LocalEntryDataProvider = <lambda>null39 compositionLocalOf<EntryData> { object : EntryData {} } 40 41 typealias UiLayerRenderer = @Composable (arguments: Bundle?) -> Unit 42 typealias StatusDataGetter = (arguments: Bundle?) -> EntryStatusData? 43 typealias SearchDataGetter = (arguments: Bundle?) -> EntrySearchData? 44 45 /** 46 * Defines data of a Settings entry. 47 */ 48 data class SettingsEntry( 49 // The unique id of this entry, which is computed by name + owner + fromPage + toPage. 50 val id: String, 51 52 // The name of the entry, which is used to compute the unique id, and need to be stable. 53 private val name: String, 54 55 // The label of the entry, for better readability. 56 // For migration mapping, this should match the android:key field in the old architecture 57 // if applicable. 58 val label: String, 59 60 // The owner page of this entry. 61 val owner: SettingsPage, 62 63 // Defines linking of Settings entries 64 val fromPage: SettingsPage? = null, 65 val toPage: SettingsPage? = null, 66 67 /** 68 * ======================================== 69 * Defines entry attributes here. 70 * ======================================== 71 */ 72 val isAllowSearch: Boolean = false, 73 74 // Indicate whether the search indexing data of entry is dynamic. 75 val isSearchDataDynamic: Boolean = false, 76 77 // Indicate whether the status of entry is mutable. 78 // If so, for instance, we'll reindex its status for search. 79 val hasMutableStatus: Boolean = false, 80 81 /** 82 * ======================================== 83 * Defines entry APIs to get data here. 84 * ======================================== 85 */ 86 87 /** 88 * API to get the status data of the entry, such as isDisabled / isSwitchOff. 89 * Returns null if this entry do NOT have any status. 90 */ <lambda>null91 private val statusDataImpl: StatusDataGetter = { null }, 92 93 /** 94 * API to get Search indexing data for this entry, such as title / keyword. 95 * Returns null if this entry do NOT support search. 96 */ <lambda>null97 private val searchDataImpl: SearchDataGetter = { null }, 98 99 /** 100 * API to Render UI of this entry directly. For now, we use it in the internal injection, to 101 * support the case that the injection page owner wants to maintain both data and UI of the 102 * injected entry. In the long term, we may deprecate the @Composable Page() API in SPP, and 103 * use each entries' UI rendering function in the page instead. 104 */ <lambda>null105 private val uiLayoutImpl: UiLayerRenderer = {}, 106 ) { containerPagenull107 fun containerPage(): SettingsPage { 108 // The Container page of the entry, which is the from-page or 109 // the owner-page if from-page is unset. 110 return fromPage ?: owner 111 } 112 fullArgumentnull113 private fun fullArgument(runtimeArguments: Bundle? = null): Bundle { 114 return Bundle().apply { 115 if (owner.arguments != null) putAll(owner.arguments) 116 // Put runtime args later, which can override page args. 117 if (runtimeArguments != null) putAll(runtimeArguments) 118 } 119 } 120 getStatusDatanull121 fun getStatusData(runtimeArguments: Bundle? = null): EntryStatusData? { 122 return statusDataImpl(fullArgument(runtimeArguments)) 123 } 124 getSearchDatanull125 fun getSearchData(runtimeArguments: Bundle? = null): EntrySearchData? { 126 return searchDataImpl(fullArgument(runtimeArguments)) 127 } 128 129 @Composable UiLayoutnull130 fun UiLayout(runtimeArguments: Bundle? = null) { 131 val arguments = remember { fullArgument(runtimeArguments) } 132 CompositionLocalProvider(provideLocalEntryData(arguments)) { 133 uiLayoutImpl(arguments) 134 } 135 } 136 137 @Composable provideLocalEntryDatanull138 private fun provideLocalEntryData(arguments: Bundle): ProvidedValue<EntryData> { 139 val controller = LocalNavController.current 140 return LocalEntryDataProvider provides remember { 141 object : EntryData { 142 override val pageId = containerPage().id 143 override val entryId = id 144 override val isHighlighted = controller.highlightEntryId == id 145 override val arguments = arguments 146 } 147 } 148 } 149 } 150