1 /*
<lambda>null2  * Copyright 2021 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 androidx.compose.ui.tooling
18 
19 import android.content.pm.ApplicationInfo
20 import android.os.Bundle
21 import android.util.Log
22 import androidx.activity.ComponentActivity
23 import androidx.activity.compose.setContent
24 import androidx.compose.foundation.layout.Box
25 import androidx.compose.foundation.layout.padding
26 import androidx.compose.material.ExtendedFloatingActionButton
27 import androidx.compose.material.Scaffold
28 import androidx.compose.material.Text
29 import androidx.compose.runtime.currentComposer
30 import androidx.compose.runtime.mutableIntStateOf
31 import androidx.compose.runtime.remember
32 import androidx.compose.ui.ExperimentalComposeUiApi
33 import androidx.compose.ui.Modifier
34 
35 /**
36  * Activity used to run `@Composable` previews from Android Studio.
37  *
38  * The supported `@Composable` functions either have no parameters, or have only parameters with
39  * default values and/or *one* parameter annotated with `@PreviewParameter`.
40  *
41  * The `@Composable` fully qualified name must be passed to this Activity through intent parameters,
42  * using `composable` as the key. When deploying Compose Previews with `@PreviewParameter` annotated
43  * parameters, the provider should be specified as an intent parameter as well, using the key
44  * `parameterProviderClassName`. Optionally, `parameterProviderIndex` can also be set to display a
45  * specific provider value instead of all of them.
46  */
47 @Suppress("ForbiddenSuperClass")
48 class PreviewActivity : ComponentActivity() {
49 
50     private val TAG = "PreviewActivity"
51 
52     override fun onCreate(savedInstanceState: Bundle?) {
53         super.onCreate(savedInstanceState)
54         if (applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE == 0) {
55             Log.d(TAG, "Application is not debuggable. Compose Preview not allowed.")
56             finish()
57             return
58         }
59 
60         intent?.getStringExtra("composable")?.let { setComposableContent(it) }
61     }
62 
63     @Suppress("DEPRECATION")
64     @OptIn(ExperimentalComposeUiApi::class)
65     private fun setComposableContent(composableFqn: String) {
66         Log.d(TAG, "PreviewActivity has composable $composableFqn")
67         val className = composableFqn.substringBeforeLast('.')
68         val methodName = composableFqn.substringAfterLast('.')
69 
70         intent.getStringExtra("parameterProviderClassName")?.let { parameterProvider ->
71             setParameterizedContent(className, methodName, parameterProvider)
72             return@setComposableContent
73         }
74         Log.d(TAG, "Previewing '$methodName' without a parameter provider.")
75         setContent { ComposableInvoker.invokeComposable(className, methodName, currentComposer) }
76     }
77 
78     /**
79      * Sets the activity content according to a given `@PreviewParameter` provider. If
80      * `parameterProviderIndex` is also set, the content will be a single `@Composable` that uses
81      * the `parameterProviderIndex`-th value in the provider's sequence as the argument value.
82      * Otherwise, the content will display a FAB that changes the argument value on click, cycling
83      * through all the values in the provider's sequence.
84      */
85     @Suppress("DEPRECATION")
86     @OptIn(ExperimentalComposeUiApi::class)
87     private fun setParameterizedContent(
88         className: String,
89         methodName: String,
90         parameterProvider: String
91     ) {
92         Log.d(TAG, "Previewing '$methodName' with parameter provider: '$parameterProvider'")
93         val previewParameters =
94             getPreviewProviderParameters(
95                 parameterProvider.asPreviewProviderClass(),
96                 intent.getIntExtra("parameterProviderIndex", -1)
97             )
98 
99         // Handle the case where parameterProviderIndex is not provided. In this case, instead of
100         // showing an arbitrary value (e.g. the first one), we display a FAB that can be used to
101         // cycle through all the values.
102         if (previewParameters.size > 1) {
103             setContent {
104                 val index = remember { mutableIntStateOf(0) }
105 
106                 Scaffold(
107                     content = { padding ->
108                         Box(Modifier.padding(padding)) {
109                             ComposableInvoker.invokeComposable(
110                                 className,
111                                 methodName,
112                                 currentComposer,
113                                 previewParameters[index.intValue]
114                             )
115                         }
116                     },
117                     floatingActionButton = {
118                         ExtendedFloatingActionButton(
119                             text = { Text("Next") },
120                             onClick = {
121                                 index.intValue = (index.intValue + 1) % previewParameters.size
122                             }
123                         )
124                     }
125                 )
126             }
127         } else {
128             setContent {
129                 ComposableInvoker.invokeComposable(
130                     className,
131                     methodName,
132                     currentComposer,
133                     *previewParameters
134                 )
135             }
136         }
137     }
138 }
139