• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2023 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 package com.android.permissioncontroller.wear.permission.components
17 
18 import android.app.Activity
19 import android.content.Context
20 import android.content.ContextWrapper
21 import androidx.compose.foundation.layout.BoxScope
22 import androidx.compose.runtime.Composable
23 import androidx.compose.runtime.LaunchedEffect
24 import androidx.compose.runtime.getValue
25 import androidx.compose.runtime.mutableStateOf
26 import androidx.compose.runtime.remember
27 import androidx.compose.runtime.setValue
28 import androidx.compose.ui.platform.LocalContext
29 import androidx.fragment.app.FragmentActivity
30 import androidx.wear.compose.foundation.ExpandableState
31 import androidx.wear.compose.foundation.SwipeToDismissValue
32 import androidx.wear.compose.foundation.rememberSwipeToDismissBoxState
33 import androidx.wear.compose.material.SwipeToDismissBox
34 import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionScaffold
35 import com.android.permissioncontroller.wear.permission.components.theme.ResourceHelper
36 import com.android.permissioncontroller.wear.permission.components.theme.WearPermissionMaterialUIVersion
37 
38 /**
39  * Screen that contains a list of items defined using the [content] parameter, adds the time text
40  * (if [showTimeText] is true), the tile (if [title] is not null), the vignette and the position
41  * indicator. It also manages the scaling animation and allows the user to scroll the content using
42  * the crown.
43  */
44 @Composable
45 fun ScrollableScreen(
46     materialUIVersion: WearPermissionMaterialUIVersion = ResourceHelper.materialUIVersionInSettings,
47     asScalingList: Boolean = false,
48     showTimeText: Boolean = true,
49     title: String? = null,
50     subtitle: CharSequence? = null,
51     image: Any? = null,
52     isLoading: Boolean = false,
53     titleTestTag: String? = null,
54     subtitleTestTag: String? = null,
55     content: ListScopeWrapper.() -> Unit,
56 ) {
57     var dismissed by remember { mutableStateOf(false) }
58     val activity = LocalContext.current.findActivity()
59     val state = rememberSwipeToDismissBoxState()
60 
61     LaunchedEffect(state.currentValue) {
62         // If the swipe is complete
63         if (state.currentValue == SwipeToDismissValue.Dismissed) {
64             // pop the top fragment immediately or dismiss activity.
65             dismiss(activity)
66             // Set  dismissed state as true
67             dismissed = true
68             // Set swipe box back to starting position(that is cancelled swipe effect) to
69             // show loading indicator while fragment dismisses.
70             // For some reason fragment `popBackImmediate` takes few secs at times.
71             state.snapTo(SwipeToDismissValue.Default)
72         }
73     }
74 
75     if (getBackStackEntryCount(activity) > 0) {
76         SwipeToDismissBox(state = state) { isBackground ->
77             WearPermissionScaffold(
78                 materialUIVersion,
79                 asScalingList,
80                 showTimeText,
81                 title,
82                 subtitle,
83                 image,
84                 isLoading = isLoading || isBackground || dismissed,
85                 content,
86                 titleTestTag,
87                 subtitleTestTag,
88             )
89         }
90     } else {
91         WearPermissionScaffold(
92             materialUIVersion,
93             asScalingList,
94             showTimeText,
95             title,
96             subtitle,
97             image,
98             isLoading,
99             content,
100             titleTestTag,
101             subtitleTestTag,
102         )
103     }
104 }
105 
dismissnull106 fun dismiss(activity: Activity) {
107     if (activity is FragmentActivity) {
108         if (!activity.supportFragmentManager.popBackStackImmediate()) {
109             activity.finish()
110         }
111     } else {
112         activity.finish()
113     }
114 }
115 
getBackStackEntryCountnull116 internal fun getBackStackEntryCount(activity: Activity): Int {
117     return if (activity is FragmentActivity) {
118         activity.supportFragmentManager.primaryNavigationFragment
119             ?.childFragmentManager
120             ?.backStackEntryCount ?: 0
121     } else {
122         0
123     }
124 }
125 
Contextnull126 fun Context.findActivity(): Activity {
127     var context = this
128     while (context is ContextWrapper) {
129         if (context is Activity) return context
130         context = context.baseContext
131     }
132     throw IllegalStateException("The screen should be called in the context of an Activity")
133 }
134 
135 interface ListScopeWrapper {
itemnull136     fun item(key: Any? = null, contentType: Any? = null, content: @Composable () -> Unit)
137 
138     fun items(
139         count: Int,
140         key: ((index: Int) -> Any)? = null,
141         contentType: (index: Int) -> Any? = { null },
142         content: @Composable (index: Int) -> Unit,
143     )
144 
expandableItemsnull145     fun expandableItems(
146         state: ExpandableState,
147         count: Int,
148         key: ((index: Int) -> Any)? = null,
149         itemContent: @Composable BoxScope.(index: Int) -> Unit,
150     )
151 
152     fun expandableButton(state: ExpandableState, key: Any? = null, content: @Composable () -> Unit)
153 }
154