1 /* 2 * Copyright (C) 2019 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.customization.picker.clock; 17 18 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; 19 20 import android.content.Context; 21 import android.content.res.Resources; 22 import android.os.Bundle; 23 import android.util.Log; 24 import android.view.LayoutInflater; 25 import android.view.View; 26 import android.view.ViewGroup; 27 import android.widget.ImageView; 28 import android.widget.Toast; 29 30 import androidx.annotation.NonNull; 31 import androidx.annotation.Nullable; 32 import androidx.core.widget.ContentLoadingProgressBar; 33 import androidx.recyclerview.widget.RecyclerView; 34 35 import com.android.customization.model.CustomizationManager.Callback; 36 import com.android.customization.model.CustomizationManager.OptionsFetchedListener; 37 import com.android.customization.model.clock.BaseClockManager; 38 import com.android.customization.model.clock.Clockface; 39 import com.android.customization.module.ThemesUserEventLogger; 40 import com.android.customization.picker.BasePreviewAdapter; 41 import com.android.customization.picker.BasePreviewAdapter.PreviewPage; 42 import com.android.customization.widget.OptionSelectorController; 43 import com.android.wallpaper.R; 44 import com.android.wallpaper.asset.Asset; 45 import com.android.wallpaper.module.InjectorProvider; 46 import com.android.wallpaper.picker.ToolbarFragment; 47 import com.android.wallpaper.widget.PreviewPager; 48 49 import java.util.List; 50 51 /** 52 * Fragment that contains the main UI for selecting and applying a Clockface. 53 */ 54 public class ClockFragment extends ToolbarFragment { 55 56 private static final String TAG = "ClockFragment"; 57 58 /** 59 * Interface to be implemented by an Activity hosting a {@link ClockFragment} 60 */ 61 public interface ClockFragmentHost { getClockManager()62 BaseClockManager getClockManager(); 63 } 64 newInstance(CharSequence title)65 public static ClockFragment newInstance(CharSequence title) { 66 ClockFragment fragment = new ClockFragment(); 67 fragment.setArguments(ToolbarFragment.createArguments(title)); 68 return fragment; 69 } 70 71 private RecyclerView mOptionsContainer; 72 private OptionSelectorController<Clockface> mOptionsController; 73 private Clockface mSelectedOption; 74 private BaseClockManager mClockManager; 75 private PreviewPager mPreviewPager; 76 private ContentLoadingProgressBar mLoading; 77 private View mContent; 78 private View mError; 79 private ThemesUserEventLogger mEventLogger; 80 81 @Override onAttach(Context context)82 public void onAttach(Context context) { 83 super.onAttach(context); 84 mClockManager = ((ClockFragmentHost) context).getClockManager(); 85 mEventLogger = (ThemesUserEventLogger) 86 InjectorProvider.getInjector().getUserEventLogger(context); 87 } 88 89 @Nullable 90 @Override onCreateView(@onNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)91 public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, 92 @Nullable Bundle savedInstanceState) { 93 View view; 94 if (ADD_SCALABLE_HEADER) { 95 // TODO(b/147780560): Once the temporary flag (ADD_SCALABLE_HEADER) is removed, 96 // we should have a layout with the same name for portrait and landscape. 97 int orientation = getResources().getConfiguration().orientation; 98 view = inflater.inflate( 99 orientation == ORIENTATION_LANDSCAPE 100 ? R.layout.fragment_clock_picker 101 : R.layout.fragment_clock_scalable_picker, 102 container, /* attachToRoot */ false); 103 } else { 104 view = inflater.inflate( 105 R.layout.fragment_clock_picker, container, /* attachToRoot */ false); 106 } 107 setUpToolbar(view); 108 mContent = view.findViewById(R.id.content_section); 109 mPreviewPager = view.findViewById(R.id.clock_preview_pager); 110 mOptionsContainer = view.findViewById(R.id.options_container); 111 mLoading = view.findViewById(R.id.loading_indicator); 112 mError = view.findViewById(R.id.error_section); 113 setUpOptions(); 114 view.findViewById(R.id.apply_button).setOnClickListener(v -> { 115 mClockManager.apply(mSelectedOption, new Callback() { 116 @Override 117 public void onSuccess() { 118 mOptionsController.setAppliedOption(mSelectedOption); 119 Toast.makeText(getContext(), R.string.applied_clock_msg, 120 Toast.LENGTH_SHORT).show(); 121 } 122 123 @Override 124 public void onError(@Nullable Throwable throwable) { 125 if (throwable != null) { 126 Log.e(TAG, "Error loading clockfaces", throwable); 127 } 128 //TODO(santie): handle 129 } 130 }); 131 132 }); 133 return view; 134 } 135 createAdapter()136 private void createAdapter() { 137 mPreviewPager.setAdapter(new ClockPreviewAdapter(getContext(), mSelectedOption)); 138 } 139 setUpOptions()140 private void setUpOptions() { 141 hideError(); 142 mLoading.show(); 143 mClockManager.fetchOptions(new OptionsFetchedListener<Clockface>() { 144 @Override 145 public void onOptionsLoaded(List<Clockface> options) { 146 mLoading.hide(); 147 mOptionsController = new OptionSelectorController<>(mOptionsContainer, options); 148 149 mOptionsController.addListener(selected -> { 150 mSelectedOption = (Clockface) selected; 151 mEventLogger.logClockSelected(mSelectedOption); 152 createAdapter(); 153 }); 154 mOptionsController.initOptions(mClockManager); 155 for (Clockface option : options) { 156 if (option.isActive(mClockManager)) { 157 mSelectedOption = option; 158 } 159 } 160 // For development only, as there should always be a grid set. 161 if (mSelectedOption == null) { 162 mSelectedOption = options.get(0); 163 } 164 createAdapter(); 165 } 166 @Override 167 public void onError(@Nullable Throwable throwable) { 168 if (throwable != null) { 169 Log.e(TAG, "Error loading clockfaces", throwable); 170 } 171 showError(); 172 } 173 }, false); 174 } 175 hideError()176 private void hideError() { 177 mContent.setVisibility(View.VISIBLE); 178 mError.setVisibility(View.GONE); 179 } 180 showError()181 private void showError() { 182 mLoading.hide(); 183 mContent.setVisibility(View.GONE); 184 mError.setVisibility(View.VISIBLE); 185 } 186 187 private static class ClockfacePreviewPage extends PreviewPage { 188 189 private final Asset mPreviewAsset; 190 ClockfacePreviewPage(String title, Asset previewAsset)191 public ClockfacePreviewPage(String title, Asset previewAsset) { 192 super(title); 193 mPreviewAsset = previewAsset; 194 } 195 196 @Override bindPreviewContent()197 public void bindPreviewContent() { 198 ImageView previewImage = card.findViewById(R.id.clock_preview_image); 199 Context context = previewImage.getContext(); 200 Resources res = previewImage.getResources(); 201 mPreviewAsset.loadDrawableWithTransition(context, previewImage, 202 100 /* transitionDurationMillis */, 203 null /* drawableLoadedListener */, 204 res.getColor(android.R.color.transparent, null) /* placeholderColor */); 205 card.setContentDescription(card.getResources().getString( 206 R.string.clock_preview_content_description, title)); 207 } 208 } 209 210 /** 211 * Adapter class for mPreviewPager. 212 * This is a ViewPager as it allows for a nice pagination effect (ie, pages snap on swipe, 213 * we don't want to just scroll) 214 */ 215 private static class ClockPreviewAdapter extends BasePreviewAdapter<ClockfacePreviewPage> { ClockPreviewAdapter(Context context, Clockface clockface)216 ClockPreviewAdapter(Context context, Clockface clockface) { 217 super(context, R.layout.clock_preview_card); 218 addPage(new ClockfacePreviewPage(clockface.getTitle(), clockface.getPreviewAsset())); 219 } 220 } 221 } 222