1 /* <lambda>null2 * Copyright (C) 2024 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.wallpaper.picker.category.ui.view 18 19 import android.Manifest 20 import android.app.Activity 21 import android.content.ComponentName 22 import android.content.Intent 23 import android.content.pm.ResolveInfo 24 import android.net.Uri 25 import android.os.Bundle 26 import android.provider.Settings 27 import android.view.LayoutInflater 28 import android.view.View 29 import android.view.ViewGroup 30 import android.widget.TextView 31 import androidx.activity.result.ActivityResultLauncher 32 import androidx.activity.result.contract.ActivityResultContracts 33 import androidx.core.content.ContextCompat 34 import androidx.fragment.app.Fragment 35 import androidx.fragment.app.activityViewModels 36 import androidx.fragment.app.commit 37 import androidx.fragment.app.replace 38 import androidx.recyclerview.widget.RecyclerView 39 import com.android.wallpaper.R 40 import com.android.wallpaper.config.BaseFlags 41 import com.android.wallpaper.model.ImageWallpaperInfo 42 import com.android.wallpaper.module.MultiPanesChecker 43 import com.android.wallpaper.picker.AppbarFragment 44 import com.android.wallpaper.picker.MyPhotosStarter 45 import com.android.wallpaper.picker.WallpaperPickerDelegate.VIEW_ONLY_PREVIEW_WALLPAPER_REQUEST_CODE 46 import com.android.wallpaper.picker.category.ui.binder.BannerProvider 47 import com.android.wallpaper.picker.category.ui.binder.CategoriesBinder 48 import com.android.wallpaper.picker.category.ui.view.providers.IndividualPickerFactory 49 import com.android.wallpaper.picker.category.ui.viewmodel.CategoriesViewModel 50 import com.android.wallpaper.picker.common.preview.data.repository.PersistentWallpaperModelRepository 51 import com.android.wallpaper.picker.customization.ui.binder.ColorUpdateBinder 52 import com.android.wallpaper.picker.customization.ui.viewmodel.ColorUpdateViewModel 53 import com.android.wallpaper.picker.data.WallpaperModel 54 import com.android.wallpaper.picker.preview.ui.WallpaperPreviewActivity 55 import com.android.wallpaper.util.ActivityUtils 56 import com.android.wallpaper.util.SizeCalculator 57 import com.android.wallpaper.util.converter.WallpaperModelFactory 58 import com.google.android.material.appbar.AppBarLayout 59 import com.google.android.material.snackbar.Snackbar 60 import dagger.hilt.android.AndroidEntryPoint 61 import javax.inject.Inject 62 63 /** This fragment displays the user interface for the categories */ 64 @AndroidEntryPoint(AppbarFragment::class) 65 class CategoriesFragment : Hilt_CategoriesFragment() { 66 67 @Inject lateinit var individualPickerFactory: IndividualPickerFactory 68 @Inject lateinit var persistentWallpaperModelRepository: PersistentWallpaperModelRepository 69 @Inject lateinit var multiPanesChecker: MultiPanesChecker 70 @Inject lateinit var myPhotosStarterImpl: MyPhotosStarterImpl 71 @Inject lateinit var wallpaperModelFactory: WallpaperModelFactory 72 @Inject lateinit var colorUpdateViewModel: ColorUpdateViewModel 73 @Inject lateinit var bannerProvider: BannerProvider 74 private lateinit var photoPickerLauncher: ActivityResultLauncher<Intent> 75 76 // TODO: this may need to be scoped to fragment if the architecture changes 77 private val categoriesViewModel by activityViewModels<CategoriesViewModel>() 78 79 override fun onCreate(savedInstanceState: Bundle?) { 80 super.onCreate(savedInstanceState) 81 photoPickerLauncher = 82 registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> 83 if (result.resultCode != Activity.RESULT_OK) { 84 return@registerForActivityResult 85 } 86 87 val data: Intent? = result.data 88 val imageUri: Uri = data?.data ?: return@registerForActivityResult 89 val imageWallpaperInfo = ImageWallpaperInfo(imageUri) 90 val context = context ?: return@registerForActivityResult 91 val wallpaperModel = 92 wallpaperModelFactory.getWallpaperModel(context, imageWallpaperInfo) 93 startWallpaperPreviewActivity(wallpaperModel, false) 94 } 95 } 96 97 override fun onCreateView( 98 inflater: LayoutInflater, 99 container: ViewGroup?, 100 savedInstanceState: Bundle?, 101 ): View { 102 val view = 103 inflater.inflate(R.layout.categories_fragment, container, /* attachToRoot= */ false) 104 105 setUpToolbar(view) 106 setTitle(getText(R.string.wallpaper_title)) 107 108 val isNewPickerUi = BaseFlags.get().isNewPickerUi() 109 if (isNewPickerUi) { 110 ColorUpdateBinder.bind( 111 setColor = { _ -> 112 // There is no way to programmatically set app:liftOnScrollColor in 113 // AppBarLayout, therefore remove and re-add view to update colors based on new 114 // context 115 val contentParent = view.requireViewById<ViewGroup>(R.id.content_parent) 116 val appBarLayout = view.requireViewById<AppBarLayout>(R.id.app_bar) 117 contentParent.removeView(appBarLayout) 118 layoutInflater.inflate(R.layout.section_header_content, contentParent, true) 119 setUpToolbar(view) 120 setTitle(getText(R.string.wallpaper_title)) 121 contentParent.requestApplyInsets() 122 }, 123 color = colorUpdateViewModel.colorSurfaceContainer, 124 shouldAnimate = { false }, 125 lifecycleOwner = viewLifecycleOwner, 126 ) 127 } 128 129 CategoriesBinder.bind( 130 categoriesPage = view.requireViewById<RecyclerView>(R.id.content_parent), 131 viewModel = categoriesViewModel, 132 windowWidth = SizeCalculator.getActivityWindowWidthPx(this.activity), 133 colorUpdateViewModel = colorUpdateViewModel, 134 shouldAnimateColor = { false }, 135 bannerProvider = bannerProvider, 136 lifecycleOwner = viewLifecycleOwner, 137 ) { navigationEvent, callback -> 138 when (navigationEvent) { 139 is CategoriesViewModel.NavigationEvent.NavigateToWallpaperCollection -> { 140 switchFragment( 141 individualPickerFactory.getIndividualPickerInstance( 142 navigationEvent.categoryId, 143 navigationEvent.categoryType, 144 ) 145 ) 146 } 147 is CategoriesViewModel.NavigationEvent.NavigateToPhotosPicker -> { 148 if (BaseFlags.get().isPhotoPickerEnabled()) { 149 parentFragmentManager.commit { 150 replace<PhotoPickerFragment>(R.id.fragment_container) 151 addToBackStack(null) 152 } 153 } else { 154 // make call to permission handler to grab photos and pass callback 155 myPhotosStarterImpl.requestCustomPhotoPicker( 156 object : MyPhotosStarter.PermissionChangedListener { 157 override fun onPermissionsGranted() { 158 callback?.invoke() 159 } 160 161 override fun onPermissionsDenied(dontAskAgain: Boolean) { 162 if (dontAskAgain) { 163 showPermissionSnackbar() 164 } 165 } 166 }, 167 requireActivity(), 168 photoPickerLauncher, 169 ) 170 } 171 } 172 is CategoriesViewModel.NavigationEvent.NavigateToThirdParty -> { 173 startThirdPartyCategoryActivity( 174 requireActivity(), 175 SHOW_CATEGORY_REQUEST_CODE, 176 navigationEvent.resolveInfo, 177 ) 178 } 179 is CategoriesViewModel.NavigationEvent.NavigateToPreviewScreen -> { 180 startWallpaperPreviewActivity( 181 navigationEvent.wallpaperModel, 182 navigationEvent.categoryType == 183 CategoriesViewModel.CategoryType.CreativeCategories, 184 ) 185 } 186 } 187 } 188 return view 189 } 190 191 private fun startWallpaperPreviewActivity( 192 wallpaperModel: WallpaperModel, 193 isCreativeCategories: Boolean, 194 ) { 195 val appContext = requireContext() 196 val activity = requireActivity() 197 persistentWallpaperModelRepository.setWallpaperModel(wallpaperModel) 198 val isMultiPanel = multiPanesChecker.isMultiPanesEnabled(appContext) 199 val previewIntent = 200 WallpaperPreviewActivity.newIntent( 201 context = appContext, 202 isAssetIdPresent = true, 203 isViewAsHome = true, 204 isNewTask = isMultiPanel, 205 shouldCategoryRefresh = isCreativeCategories, 206 ) 207 ActivityUtils.startActivityForResultSafely( 208 activity, 209 previewIntent, 210 VIEW_ONLY_PREVIEW_WALLPAPER_REQUEST_CODE, // TODO: provide correct request code 211 ) 212 } 213 214 private fun showPermissionSnackbar() { 215 val snackbar = 216 Snackbar.make( 217 requireView(), 218 R.string.settings_snackbar_description, 219 Snackbar.LENGTH_LONG, 220 ) 221 val layout = snackbar.view as Snackbar.SnackbarLayout 222 val textView = 223 layout.findViewById<View>(com.google.android.material.R.id.snackbar_text) as TextView 224 layout.setBackgroundResource(R.drawable.snackbar_background) 225 226 textView.setTextColor(ContextCompat.getColor(requireContext(), R.color.system_on_primary)) 227 snackbar.setActionTextColor( 228 ContextCompat.getColor(requireContext(), R.color.system_surface_container) 229 ) 230 snackbar.setAction(requireContext().getString(R.string.settings_snackbar_enable)) { 231 startSettings(SETTINGS_APP_INFO_REQUEST_CODE) 232 } 233 snackbar.show() 234 } 235 236 private fun startSettings(resultCode: Int) { 237 val activity = activity ?: return 238 val appInfoIntent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) 239 val uri = Uri.fromParts("package", activity.packageName, /* fragment= */ null) 240 appInfoIntent.setData(uri) 241 startActivityForResult(appInfoIntent, resultCode) 242 } 243 244 private fun startThirdPartyCategoryActivity( 245 srcActivity: Activity, 246 requestCode: Int, 247 resolveInfo: ResolveInfo, 248 ) { 249 val itemComponentName = 250 ComponentName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name) 251 val launchIntent = Intent(Intent.ACTION_SET_WALLPAPER) 252 launchIntent.component = itemComponentName 253 ActivityUtils.startActivityForResultSafely(srcActivity, launchIntent, requestCode) 254 } 255 256 private fun switchFragment(fragment: Fragment) { 257 parentFragmentManager 258 .beginTransaction() 259 .replace(R.id.fragment_container, fragment) 260 .addToBackStack(null) 261 .commit() 262 parentFragmentManager.executePendingTransactions() 263 } 264 265 companion object { 266 const val SHOW_CATEGORY_REQUEST_CODE = 0 267 const val SETTINGS_APP_INFO_REQUEST_CODE = 1 268 const val READ_IMAGE_PERMISSION: String = Manifest.permission.READ_MEDIA_IMAGES 269 } 270 } 271