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