├── .gitignore
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── gallery
│ │ └── templates
│ │ └── contentful
│ │ ├── App.java
│ │ ├── activities
│ │ ├── AboutActivity.java
│ │ ├── GalleryActivity.java
│ │ └── MainActivity.java
│ │ ├── fragments
│ │ ├── GalleryInfoFragment.java
│ │ └── SlideFragment.java
│ │ ├── gallery
│ │ ├── GalleryAdapter.java
│ │ ├── SlideFragmentAdapter.java
│ │ └── SlideImageAdapter.java
│ │ ├── lib
│ │ ├── ClientProvider.java
│ │ ├── Const.java
│ │ ├── Holder.java
│ │ ├── Intents.java
│ │ ├── LoaderId.java
│ │ ├── Preferences.java
│ │ ├── TargetAdapter.java
│ │ ├── TransitionListenerAdapter.java
│ │ └── Utils.java
│ │ ├── loaders
│ │ ├── AbsLoader.java
│ │ └── GalleryListLoader.java
│ │ ├── ui
│ │ ├── AnimativeToolBar.java
│ │ ├── FloatingActionButton.java
│ │ ├── GalleryBottomView.java
│ │ ├── GridImageView.java
│ │ ├── LockableViewPager.java
│ │ └── ViewUtils.java
│ │ └── vault
│ │ ├── Author.java
│ │ ├── Gallery.java
│ │ ├── GallerySpace.java
│ │ └── Image.java
│ └── res
│ ├── anim
│ └── button_raise.xml
│ ├── drawable-hdpi
│ ├── box.png
│ ├── ic_fab_star.png
│ └── logo.png
│ ├── drawable-xhdpi
│ ├── box.png
│ ├── ic_fab_star.png
│ └── logo.png
│ ├── drawable-xxhdpi
│ ├── box.png
│ ├── ic_fab_star.png
│ └── logo.png
│ ├── drawable
│ ├── about_buttons_divider.xml
│ ├── grid_placeholder.xml
│ └── info_background.xml
│ ├── layout
│ ├── activity_about.xml
│ ├── activity_gallery.xml
│ ├── activity_main.xml
│ ├── fragment_gallery_info.xml
│ ├── fragment_slide.xml
│ ├── grid_item.xml
│ └── grid_section.xml
│ ├── menu
│ └── menu_main.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxxhdpi
│ └── ic_launcher.png
│ ├── transition
│ └── change_image_transform.xml
│ ├── values-v21
│ ├── integers.xml
│ └── styles.xml
│ ├── values-w820dp
│ └── dimens.xml
│ └── values
│ ├── colors.xml
│ ├── config.xml
│ ├── dimens.xml
│ ├── integers.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── screenshots
├── sc1.png
└── sc2.png
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | /local.properties
3 | /.idea/workspace.xml
4 | /.idea/libraries
5 | .DS_Store
6 | /build
7 | .idea/
8 | *.iml
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 Contentful GmbH - https://www.contentful.com
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Gallery Showcase
2 |
3 | This is an Android application example for the [Contentful][1] gallery space template.
4 |
5 | ## Intro:
6 |
7 | > [Contentful][1] is a content management platform for web applications, mobile apps and connected devices. It allows you to create, edit & manage content in the cloud and publish it anywhere via powerful API. Contentful offers tools for managing editorial teams and enabling cooperation between organizations.
8 |
9 | ## Screenshots
10 |
11 |  
12 |
13 | ## Usage
14 |
15 | - Create a space with the "Gallery" space template on [Contentful][1]
16 | - Clone this repo
17 | - Enter your Delivery API credentials in the [Const.java][const] file
18 | - \o/
19 |
20 | ## License
21 |
22 | Copyright (c) 2015 Contentful GmbH. See [LICENSE][2] for further details.
23 |
24 |
25 | [1]: https://www.contentful.com
26 | [2]: LICENSE
27 | [const]: app/src/main/java/gallery/templates/contentful/lib/Const.java
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 28
5 | defaultConfig {
6 | applicationId "gallery.templates.contentful"
7 | minSdkVersion 24
8 | targetSdkVersion 28
9 | versionCode 1
10 | versionName "1.0"
11 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | }
20 |
21 | dependencies {
22 | implementation fileTree(dir: 'libs', include: ['*.jar'])
23 | implementation 'androidx.appcompat:appcompat:1.0.0-rc02'
24 | implementation 'androidx.cardview:cardview:1.0.0-rc02'
25 | implementation 'androidx.recyclerview:recyclerview:1.0.0-rc02'
26 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
27 | implementation 'androidx.preference:preference:1.0.0-rc02'
28 | implementation 'androidx.appcompat:appcompat:1.0.0-rc02'
29 | implementation 'androidx.palette:palette:1.0.0-rc02'
30 | implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0-rc02'
31 |
32 | implementation 'com.squareup.okhttp:okhttp:2.7.5'
33 | implementation 'com.squareup.okhttp:okhttp-urlconnection:2.7.5'
34 |
35 | implementation 'com.jakewharton:butterknife:9.0.0-SNAPSHOT'
36 | annotationProcessor 'com.jakewharton:butterknife-compiler:9.0.0-SNAPSHOT'
37 |
38 | implementation 'com.squareup.picasso:picasso:2.71828'
39 |
40 | annotationProcessor 'com.contentful.vault:compiler:3.2.1'
41 | annotationProcessor 'com.contentful.vault:core:3.2.1'
42 |
43 | implementation 'com.contentful.vault:core:3.2.1'
44 |
45 | annotationProcessor 'org.parceler:parceler:1.1.11'
46 | implementation 'org.parceler:parceler-api:1.1.11'
47 | }
48 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
17 |
18 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
38 |
39 |
43 |
44 |
45 |
51 |
52 |
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/app/src/main/java/gallery/templates/contentful/App.java:
--------------------------------------------------------------------------------
1 | package gallery.templates.contentful;
2 |
3 | import android.app.Application;
4 |
5 | import com.contentful.vault.SyncConfig;
6 | import com.contentful.vault.Vault;
7 |
8 | import gallery.templates.contentful.lib.ClientProvider;
9 | import gallery.templates.contentful.vault.GallerySpace;
10 |
11 | public class App extends Application {
12 | private static App instance;
13 |
14 | @Override public void onCreate() {
15 | super.onCreate();
16 | instance = this;
17 | requestSync();
18 | }
19 |
20 | public static App get() {
21 | return instance;
22 | }
23 |
24 | public static void requestSync() {
25 | requestSync(false);
26 | }
27 |
28 | public static void requestSync(boolean invalidate) {
29 | Vault.with(get(), GallerySpace.class).requestSync(
30 | SyncConfig.builder()
31 | .setClient(ClientProvider.get())
32 | .setInvalidate(invalidate)
33 | .build());
34 | }
35 | }
--------------------------------------------------------------------------------
/app/src/main/java/gallery/templates/contentful/activities/AboutActivity.java:
--------------------------------------------------------------------------------
1 | package gallery.templates.contentful.activities;
2 |
3 | import android.content.Intent;
4 | import android.net.Uri;
5 | import android.os.Bundle;
6 | import android.view.View;
7 |
8 | import androidx.appcompat.app.AppCompatActivity;
9 | import butterknife.ButterKnife;
10 | import butterknife.OnClick;
11 | import gallery.templates.contentful.R;
12 |
13 | public class AboutActivity extends AppCompatActivity {
14 | @Override protected void onCreate(Bundle savedInstanceState) {
15 | super.onCreate(savedInstanceState);
16 | setContentView(R.layout.activity_about);
17 | ButterKnife.bind(this);
18 |
19 | getSupportActionBar().setDisplayHomeAsUpEnabled(true);
20 | }
21 |
22 | @SuppressWarnings("unused")
23 | @OnClick({R.id.btn_faq, R.id.btn_feedback, R.id.btn_contact, R.id.btn_license})
24 | void onClickButton(View view) {
25 | int urlResId;
26 |
27 | switch (view.getId()) {
28 | case R.id.btn_faq:
29 | urlResId = R.string.url_faq;
30 | break;
31 |
32 | case R.id.btn_feedback:
33 | urlResId = R.string.url_feedback;
34 | break;
35 |
36 | case R.id.btn_contact:
37 | urlResId = R.string.url_contact;
38 | break;
39 |
40 | case R.id.btn_license:
41 | urlResId = R.string.url_licensing;
42 | break;
43 |
44 | default:
45 | return;
46 | }
47 |
48 | startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(getString(urlResId))));
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/src/main/java/gallery/templates/contentful/activities/GalleryActivity.java:
--------------------------------------------------------------------------------
1 | package gallery.templates.contentful.activities;
2 |
3 | import android.animation.Animator;
4 | import android.animation.ArgbEvaluator;
5 | import android.content.BroadcastReceiver;
6 | import android.content.Context;
7 | import android.content.Intent;
8 | import android.content.IntentFilter;
9 | import android.content.res.ColorStateList;
10 | import android.content.res.Configuration;
11 | import android.graphics.PorterDuff;
12 | import android.graphics.drawable.Drawable;
13 | import android.graphics.drawable.RippleDrawable;
14 | import android.os.Build;
15 | import android.os.Bundle;
16 | import android.transition.Transition;
17 | import android.view.View;
18 | import android.view.ViewGroup;
19 | import android.view.ViewPropertyAnimator;
20 | import android.widget.ImageView;
21 |
22 | import org.parceler.Parcels;
23 |
24 | import androidx.appcompat.app.AppCompatActivity;
25 | import androidx.core.content.ContextCompat;
26 | import butterknife.BindView;
27 | import butterknife.ButterKnife;
28 | import butterknife.OnClick;
29 | import butterknife.OnPageChange;
30 | import gallery.templates.contentful.R;
31 | import gallery.templates.contentful.fragments.GalleryInfoFragment;
32 | import gallery.templates.contentful.fragments.SlideFragment;
33 | import gallery.templates.contentful.gallery.SlideFragmentAdapter;
34 | import gallery.templates.contentful.gallery.SlideImageAdapter;
35 | import gallery.templates.contentful.lib.Const;
36 | import gallery.templates.contentful.lib.Holder;
37 | import gallery.templates.contentful.lib.Intents;
38 | import gallery.templates.contentful.lib.TransitionListenerAdapter;
39 | import gallery.templates.contentful.lib.Utils;
40 | import gallery.templates.contentful.ui.FloatingActionButton;
41 | import gallery.templates.contentful.ui.LockableViewPager;
42 | import gallery.templates.contentful.ui.ViewUtils;
43 | import gallery.templates.contentful.vault.Gallery;
44 | import gallery.templates.contentful.vault.Image;
45 |
46 | public class GalleryActivity extends AppCompatActivity {
47 | private static final ArgbEvaluator EVALUATOR = new ArgbEvaluator();
48 |
49 | private SlideImageAdapter imageAdapter;
50 |
51 | private SlideFragmentAdapter fragmentAdapter;
52 |
53 | private GalleryInfoFragment galleryInfoFragment;
54 |
55 | private BroadcastReceiver colorizeReceiver;
56 |
57 | private Gallery gallery;
58 |
59 | private Image image;
60 |
61 | @BindView(R.id.photo) ImageView photo;
62 |
63 | @BindView(R.id.star) FloatingActionButton star;
64 |
65 | @BindView(R.id.pager) LockableViewPager viewPager;
66 |
67 | @BindView(R.id.info_container) ViewGroup infoContainer;
68 |
69 | @Override protected void onCreate(Bundle savedInstanceState) {
70 | super.onCreate(savedInstanceState);
71 | setContentView(R.layout.activity_gallery);
72 | ButterKnife.bind(this);
73 |
74 | extractIntentArguments();
75 | attachGalleryInfoFragment();
76 | createAdapterInstances();
77 | setViewPagerAdapter();
78 | initializeViews();
79 | setupColorizeReceiver(true);
80 | }
81 |
82 | @Override protected void onDestroy() {
83 | setupColorizeReceiver(false);
84 | super.onDestroy();
85 | }
86 |
87 | @Override public void onConfigurationChanged(Configuration newConfig) {
88 | super.onConfigurationChanged(newConfig);
89 | int index = viewPager.getCurrentItem();
90 | setViewPagerAdapter();
91 | viewPager.setCurrentItem(index);
92 | }
93 |
94 | @Override public void onBackPressed() {
95 | if (Const.HAS_L) {
96 | if (isShowingInitialImage()) {
97 | createAlphaAnimator(star, false).withEndAction(new Runnable() {
98 | @Override public void run() {
99 | GalleryActivity.super.onBackPressed();
100 | }
101 | }).start();
102 | } else {
103 | getWindow().setSharedElementReturnTransition(null);
104 | finish();
105 | }
106 |
107 | return;
108 | }
109 |
110 | super.onBackPressed();
111 | }
112 |
113 | @OnPageChange(value = R.id.pager, callback = OnPageChange.Callback.PAGE_SCROLLED)
114 | void onPageSelected(int position, float positionOffset, int positionOffsetPixels) {
115 | if (positionOffsetPixels == 0) {
116 | return;
117 | }
118 |
119 | SlideFragment leftFragment = fragmentAdapter.fragmentForPosition(position);
120 | SlideFragment rightFragment = fragmentAdapter.fragmentForPosition(position + 1);
121 |
122 | if (leftFragment != null && rightFragment != null) {
123 | int leftVibrant = leftFragment.getColorVibrant();
124 | int rightVibrant = rightFragment.getColorVibrant();
125 |
126 | int leftDarkMuted = leftFragment.getColorDarkMuted();
127 | int rightDarkMuted = rightFragment.getColorDarkMuted();
128 |
129 | int vibrant = (Integer) EVALUATOR.evaluate(
130 | positionOffset, leftVibrant, rightVibrant);
131 |
132 | int darkMuted = (Integer) EVALUATOR.evaluate(
133 | positionOffset, leftDarkMuted, rightDarkMuted);
134 |
135 | colorizeButton(vibrant, darkMuted);
136 | }
137 | }
138 |
139 | @OnClick(R.id.star) void onClickStar(View v) {
140 | toggleInfoContainer(v);
141 | }
142 |
143 | private void initializeViews() {
144 | viewPager.setCurrentItem(gallery.images().indexOf(image));
145 | viewPager.setOffscreenPageLimit(getResources().getInteger(
146 | R.integer.gallery_pager_offscreen_limit));
147 |
148 | repositionStar();
149 | ViewUtils.setViewHeight(infoContainer, Const.IMAGE_HEIGHT, true);
150 |
151 | if (Const.HAS_L && Utils.isPortrait(this)) {
152 | ViewUtils.setViewHeight(photo, Const.IMAGE_HEIGHT, false);
153 | photo.setImageDrawable(Holder.get());
154 |
155 | getWindow().getSharedElementEnterTransition().addListener(new TransitionListenerAdapter() {
156 | @Override public void onTransitionEnd(Transition transition) {
157 | getWindow().getSharedElementEnterTransition().removeListener(this);
158 | photo.setVisibility(View.GONE);
159 | }
160 | });
161 | }
162 | }
163 |
164 | private void attachGalleryInfoFragment() {
165 | galleryInfoFragment = (GalleryInfoFragment) GalleryInfoFragment
166 | .instantiate(this, GalleryInfoFragment.class.getName(), getIntent().getExtras());
167 |
168 | getSupportFragmentManager().beginTransaction()
169 | .add(R.id.info_container, galleryInfoFragment)
170 | .commit();
171 | }
172 |
173 | private void extractIntentArguments() {
174 | Intent intent = getIntent();
175 | gallery = Parcels.unwrap(intent.getParcelableExtra(Intents.EXTRA_GALLERY));
176 | image = Parcels.unwrap(intent.getParcelableExtra(Intents.EXTRA_IMAGE));
177 | }
178 |
179 | private void createAdapterInstances() {
180 | fragmentAdapter = new SlideFragmentAdapter(this, getSupportFragmentManager(), gallery);
181 | imageAdapter = new SlideImageAdapter(gallery.images());
182 | }
183 |
184 | private void setViewPagerAdapter() {
185 | if (Utils.isPortrait(this)) {
186 | viewPager.setAdapter(fragmentAdapter);
187 | } else {
188 | viewPager.setAdapter(imageAdapter);
189 | }
190 | }
191 |
192 | private void toggleInfoContainer(final View view) {
193 | final boolean show = ViewUtils.isGone(infoContainer);
194 |
195 | if (show) {
196 | SlideFragment slideFragment = fragmentAdapter.fragmentForPosition(viewPager.getCurrentItem());
197 |
198 | galleryInfoFragment.colorize(slideFragment.getColorDarkMuted(),
199 | slideFragment.getColorVibrant(), slideFragment.getColorLightMuted());
200 | }
201 |
202 | Animator animator;
203 | Runnable startRunnable = new Runnable() {
204 | @Override public void run() {
205 | star.setEnabled(false);
206 | viewPager.setLocked(show);
207 | }
208 | };
209 |
210 | Runnable endRunnable = new Runnable() {
211 | @Override public void run() {
212 | star.setEnabled(true);
213 | viewPager.setLocked(show);
214 | }
215 | };
216 |
217 | int duration = getResources().getInteger(R.integer.toggle_animation_duration);
218 | if (Const.HAS_L) {
219 | float radius = Math.max(photo.getWidth(), photo.getHeight()) * 2.0f;
220 | animator = ViewUtils.toggleViewCircular(view, infoContainer, show, radius, duration,
221 | startRunnable, endRunnable);
222 | } else {
223 | animator = ViewUtils.toggleViewFade(infoContainer, show, duration,
224 | startRunnable, endRunnable);
225 | }
226 |
227 | animator.start();
228 | }
229 |
230 | private void repositionStar() {
231 | int fabSize = getResources().getDimensionPixelSize(R.dimen.fab_size);
232 | star.setTranslationY(Const.IMAGE_HEIGHT - (fabSize / 2.0f));
233 | }
234 |
235 | private boolean isShowingInitialImage() {
236 | int originalIndex = gallery.images().indexOf(image);
237 | return originalIndex == viewPager.getCurrentItem();
238 | }
239 |
240 | private void colorizeButton(int colorNormal, int colorPressed) {
241 | Drawable drawable = ContextCompat.getDrawable(this, R.drawable.info_background);
242 | drawable.setColorFilter(colorNormal, PorterDuff.Mode.MULTIPLY);
243 |
244 | if (Const.HAS_L) {
245 | ColorStateList stateList = new ColorStateList(
246 | new int[][] {
247 | new int[] { }
248 | },
249 | new int[] {
250 | colorPressed
251 | });
252 |
253 | RippleDrawable rippleDrawable = new RippleDrawable(stateList, drawable, null);
254 | ViewUtils.setBackground(star, rippleDrawable);
255 | } else {
256 | ViewUtils.setBackground(star, drawable);
257 | }
258 | }
259 |
260 | private void setupColorizeReceiver(boolean register) {
261 | if (register) {
262 | if (colorizeReceiver == null) {
263 | colorizeReceiver = new BroadcastReceiver() {
264 | @Override public void onReceive(Context context, Intent intent) {
265 | Image image = Parcels.unwrap(intent.getParcelableExtra(Intents.EXTRA_IMAGE));
266 | int vibrant = intent.getIntExtra(Intents.EXTRA_CLR_VIBRANT, 0);
267 | int darkMuted = intent.getIntExtra(Intents.EXTRA_CLR_DARK_MUTED, 0);
268 | colorize(image, vibrant, darkMuted);
269 | }
270 | };
271 | }
272 |
273 | registerReceiver(colorizeReceiver, new IntentFilter(Intents.ACTION_COLORIZE));
274 | } else {
275 | unregisterReceiver(colorizeReceiver);
276 | }
277 | }
278 |
279 | private void colorize(Image image, int colorVibrant, int colorDarkMuted) {
280 | if (image.equals(fragmentAdapter.getImage(viewPager.getCurrentItem()))) {
281 | colorizeButton(colorVibrant, colorDarkMuted);
282 |
283 | if (star.getAlpha() == 0) {
284 | long startDelay = getResources().getInteger(R.integer.toggle_animation_duration);
285 | createAlphaAnimator(star, true).setStartDelay(startDelay).start();
286 | }
287 | }
288 | }
289 |
290 | private ViewPropertyAnimator createAlphaAnimator(View v, boolean show) {
291 | int duration = getResources().getInteger(R.integer.gallery_alpha_duration);
292 | float alpha = show ? 1.0f : 0.0f;
293 | return v.animate().alpha(alpha).setDuration(duration);
294 | }
295 | }
296 |
--------------------------------------------------------------------------------
/app/src/main/java/gallery/templates/contentful/activities/MainActivity.java:
--------------------------------------------------------------------------------
1 | package gallery.templates.contentful.activities;
2 |
3 | import android.app.ActivityOptions;
4 | import android.content.BroadcastReceiver;
5 | import android.content.Context;
6 | import android.content.Intent;
7 | import android.content.IntentFilter;
8 | import android.graphics.Point;
9 | import android.graphics.drawable.Drawable;
10 | import android.os.Bundle;
11 | import android.view.Display;
12 | import android.view.Menu;
13 | import android.view.MenuItem;
14 | import android.view.View;
15 | import android.view.WindowManager;
16 |
17 | import com.contentful.vault.Vault;
18 |
19 | import org.parceler.Parcels;
20 |
21 | import java.util.List;
22 |
23 | import androidx.appcompat.app.AppCompatActivity;
24 | import androidx.core.app.ActivityCompat;
25 | import androidx.loader.app.LoaderManager;
26 | import androidx.loader.content.Loader;
27 | import androidx.recyclerview.widget.GridLayoutManager;
28 | import androidx.recyclerview.widget.RecyclerView;
29 | import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
30 | import butterknife.BindView;
31 | import butterknife.ButterKnife;
32 | import gallery.templates.contentful.App;
33 | import gallery.templates.contentful.R;
34 | import gallery.templates.contentful.gallery.GalleryAdapter;
35 | import gallery.templates.contentful.lib.Const;
36 | import gallery.templates.contentful.lib.Holder;
37 | import gallery.templates.contentful.lib.Intents;
38 | import gallery.templates.contentful.lib.LoaderId;
39 | import gallery.templates.contentful.lib.Utils;
40 | import gallery.templates.contentful.loaders.GalleryListLoader;
41 | import gallery.templates.contentful.ui.AnimativeToolBar;
42 | import gallery.templates.contentful.vault.Gallery;
43 | import gallery.templates.contentful.vault.Image;
44 |
45 | import static gallery.templates.contentful.gallery.GalleryAdapter.ItemViewHolder;
46 |
47 | public class MainActivity extends AppCompatActivity
48 | implements LoaderManager.LoaderCallbacks> {
49 |
50 | private static final int GRID_SPAN_COUNT = App.get().getResources().getInteger(R.integer.gallery_grid_span_count);
51 |
52 | private static final int LOADER_ID = LoaderId.forClass(MainActivity.class);
53 |
54 | private GalleryAdapter adapter;
55 |
56 | private GridLayoutManager layoutManager;
57 |
58 | private BroadcastReceiver reloadReceiver;
59 |
60 | @BindView(R.id.toolbar) AnimativeToolBar toolbar;
61 |
62 | @BindView(R.id.swipe_refresh) SwipeRefreshLayout swipeRefreshLayout;
63 |
64 | @BindView(R.id.recycler) RecyclerView recyclerView;
65 |
66 | @Override protected void onCreate(Bundle savedInstanceState) {
67 | super.onCreate(savedInstanceState);
68 | setContentView(R.layout.activity_main);
69 | ButterKnife.bind(this);
70 |
71 | setSupportActionBar(toolbar);
72 | calculateImageHeight();
73 | createAdapter();
74 | initSwipeRefresh();
75 | initRecycler();
76 | setupReloadReceiver(true);
77 | getSupportLoaderManager().initLoader(LOADER_ID, null, this);
78 | }
79 |
80 | @Override protected void onResume() {
81 | super.onResume();
82 | Holder.set(null);
83 | }
84 |
85 | @Override protected void onPause() {
86 | super.onPause();
87 | }
88 |
89 | @Override public void onDestroy() {
90 | setupReloadReceiver(false);
91 | super.onDestroy();
92 | }
93 |
94 | @Override public boolean onCreateOptionsMenu(Menu menu) {
95 | getMenuInflater().inflate(R.menu.menu_main, menu);
96 | return true;
97 | }
98 |
99 | @Override public boolean onOptionsItemSelected(MenuItem item) {
100 | int id = item.getItemId();
101 |
102 | if (id == R.id.action_about) {
103 | startActivity(new Intent(this, AboutActivity.class));
104 | return true;
105 | }
106 |
107 | return super.onOptionsItemSelected(item);
108 | }
109 |
110 | @Override public Loader> onCreateLoader(int id, Bundle args) {
111 | return new GalleryListLoader(getApplicationContext());
112 | }
113 |
114 | @Override public void onLoadFinished(Loader> loader, final List data) {
115 | if (swipeRefreshLayout.isRefreshing()) {
116 | swipeRefreshLayout.setRefreshing(false);
117 | }
118 |
119 | if (data != null) {
120 | adapter.setData(data);
121 | adapter.notifyDataSetChanged();
122 | }
123 | }
124 |
125 | @Override public void onLoaderReset(Loader> loader) {
126 |
127 | }
128 |
129 | private void initSwipeRefresh() {
130 | swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
131 | @Override public void onRefresh() {
132 | App.requestSync();
133 | }
134 | });
135 | }
136 |
137 | private void setupReloadReceiver(boolean register) {
138 | if (register) {
139 | if (reloadReceiver == null) {
140 | reloadReceiver = new BroadcastReceiver() {
141 | @Override public void onReceive(Context context, Intent intent) {
142 | getSupportLoaderManager().restartLoader(LOADER_ID, null, MainActivity.this);
143 | }
144 | };
145 | }
146 |
147 | registerReceiver(reloadReceiver, new IntentFilter(Vault.ACTION_SYNC_COMPLETE));
148 | } else {
149 | unregisterReceiver(reloadReceiver);
150 | }
151 | }
152 |
153 | private void initRecycler() {
154 | createLayoutManager();
155 |
156 | recyclerView.setHasFixedSize(true);
157 | recyclerView.setLayoutManager(layoutManager);
158 | recyclerView.setAdapter(adapter);
159 | recyclerView.addOnScrollListener(createRecyclerScrollListener());
160 | }
161 |
162 | private void createLayoutManager() {
163 | layoutManager = new GridLayoutManager(this, GRID_SPAN_COUNT);
164 |
165 | layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
166 | @Override public int getSpanSize(int position) {
167 | return adapter.isSection(position) ? GRID_SPAN_COUNT : 1;
168 | }
169 | });
170 | }
171 |
172 | private void createAdapter() {
173 | int imageSize = Utils.getDisplayDimensions(this).x / GRID_SPAN_COUNT;
174 | adapter = new GalleryAdapter(createAdapterClickListener(), imageSize);
175 | }
176 |
177 | private void onClickImage(ItemViewHolder holder, Gallery gallery, Image image) {
178 | Intent intent = new Intent(this, GalleryActivity.class)
179 | .putExtra(Intents.EXTRA_GALLERY, Parcels.wrap(gallery))
180 | .putExtra(Intents.EXTRA_IMAGE, Parcels.wrap(image));
181 |
182 | Bundle options = null;
183 | if (Const.HAS_L) {
184 | Drawable drawable = holder.photo.getDrawable();
185 | if (drawable != null) {
186 | Holder.set(drawable);
187 |
188 | options = ActivityOptions.makeSceneTransitionAnimation(this, holder.photo,
189 | getString(R.string.gallery_photo_hero)).toBundle();
190 | }
191 | }
192 |
193 | ActivityCompat.startActivity(this, intent, options);
194 | }
195 |
196 | private void calculateImageHeight() {
197 | if (Const.IMAGE_WIDTH == null || Const.IMAGE_HEIGHT == null) {
198 | WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
199 | Display defaultDisplay = windowManager.getDefaultDisplay();
200 | Point p = new Point();
201 | defaultDisplay.getSize(p);
202 | Const.IMAGE_WIDTH = p.x;
203 | Const.IMAGE_HEIGHT = (int) (p.y * 2 / 3.0f);
204 | }
205 | }
206 |
207 | private View.OnClickListener createAdapterClickListener() {
208 | return new View.OnClickListener() {
209 | @Override public void onClick(View v) {
210 | int position = recyclerView.getChildAdapterPosition(v);
211 | int viewType = adapter.getItemViewType(position);
212 | RecyclerView.ViewHolder viewHolder = recyclerView.getChildViewHolder(v);
213 | Object item = adapter.getItem(position);
214 |
215 | if (GalleryAdapter.VIEW_TYPE_ITEM == viewType) {
216 | onClickImage((ItemViewHolder) viewHolder, adapter.sectionFor(position), (Image) item);
217 | }
218 | }
219 | };
220 | }
221 |
222 | private RecyclerView.OnScrollListener createRecyclerScrollListener() {
223 | return new RecyclerView.OnScrollListener() {
224 | @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
225 | }
226 |
227 | @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
228 | if (RecyclerView.SCROLL_STATE_DRAGGING != recyclerView.getScrollState()) {
229 | if (dy > 0 && toolbar.visible()) {
230 | toolbar.hide();
231 | } else if (dy < 0 && !toolbar.visible()) {
232 | toolbar.show();
233 | }
234 | }
235 | }
236 | };
237 | }
238 | }
239 |
--------------------------------------------------------------------------------
/app/src/main/java/gallery/templates/contentful/fragments/GalleryInfoFragment.java:
--------------------------------------------------------------------------------
1 | package gallery.templates.contentful.fragments;
2 |
3 | import android.graphics.Color;
4 | import android.os.Bundle;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.TextView;
9 |
10 | import org.parceler.Parcels;
11 |
12 | import androidx.annotation.Nullable;
13 | import androidx.fragment.app.Fragment;
14 | import butterknife.BindView;
15 | import butterknife.ButterKnife;
16 | import gallery.templates.contentful.R;
17 | import gallery.templates.contentful.lib.Intents;
18 | import gallery.templates.contentful.vault.Gallery;
19 |
20 | public class GalleryInfoFragment extends Fragment {
21 | private Gallery gallery;
22 |
23 | @BindView(R.id.root) ViewGroup root;
24 |
25 | @BindView(R.id.title) TextView title;
26 |
27 | @BindView(R.id.description) TextView description;
28 |
29 | @Override public void onCreate(Bundle savedInstanceState) {
30 | super.onCreate(savedInstanceState);
31 | extractIntentArguments();
32 | }
33 |
34 | @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
35 | @Nullable Bundle savedInstanceState) {
36 | super.onCreateView(inflater, container, savedInstanceState);
37 | return inflater.inflate(R.layout.fragment_gallery_info, container, false);
38 | }
39 |
40 | @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
41 | super.onViewCreated(view, savedInstanceState);
42 | ButterKnife.bind(this, view);
43 | initializeViews();
44 | }
45 |
46 | private void extractIntentArguments() {
47 | gallery = Parcels.unwrap(getArguments().getParcelable(Intents.EXTRA_GALLERY));
48 | }
49 |
50 | private void initializeViews() {
51 | title.setText(gallery.title());
52 | description.setText(gallery.description());
53 | }
54 |
55 | public void colorize(int colorDarkMuted, int colorVibrant, int colorLightMuted) {
56 | int red = Color.red(colorDarkMuted);
57 | int green = Color.green(colorDarkMuted);
58 | int blue = Color.blue(colorDarkMuted);
59 |
60 | root.setBackgroundColor(Color.argb(
61 | getResources().getInteger(R.integer.gallery_info_bg_alpha),
62 | red, green, blue));
63 |
64 | title.setTextColor(colorVibrant);
65 | description.setTextColor(colorLightMuted);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/src/main/java/gallery/templates/contentful/fragments/SlideFragment.java:
--------------------------------------------------------------------------------
1 | package gallery.templates.contentful.fragments;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.graphics.Bitmap;
6 | import android.os.AsyncTask;
7 | import android.os.Bundle;
8 | import android.view.LayoutInflater;
9 | import android.view.View;
10 | import android.view.ViewGroup;
11 | import android.widget.ImageView;
12 | import android.widget.TextView;
13 |
14 | import com.squareup.picasso.Picasso;
15 | import com.squareup.picasso.Target;
16 |
17 | import org.parceler.Parcels;
18 |
19 | import androidx.annotation.Nullable;
20 | import androidx.fragment.app.Fragment;
21 | import androidx.palette.graphics.Palette;
22 | import butterknife.ButterKnife;
23 | import butterknife.BindView;
24 | import gallery.templates.contentful.R;
25 | import gallery.templates.contentful.lib.Const;
26 | import gallery.templates.contentful.lib.Intents;
27 | import gallery.templates.contentful.lib.TargetAdapter;
28 | import gallery.templates.contentful.lib.Utils;
29 | import gallery.templates.contentful.ui.ViewUtils;
30 | import gallery.templates.contentful.vault.Image;
31 |
32 | public class SlideFragment extends Fragment implements Palette.PaletteAsyncListener {
33 | private Image image;
34 |
35 | private AsyncTask paletteTask;
36 |
37 | private Target target;
38 |
39 | private Bitmap bitmap;
40 |
41 | private boolean hasPalette;
42 |
43 | private int colorLightMuted;
44 |
45 | private int colorDarkMuted;
46 |
47 | private int colorVibrant;
48 |
49 | @BindView(R.id.photo) ImageView photo;
50 |
51 | @BindView(R.id.bottom) ViewGroup bottomContainer;
52 |
53 | @BindView(R.id.title) TextView title;
54 |
55 | @BindView(R.id.caption) TextView caption;
56 |
57 | public static SlideFragment newSlide(Context context, Image image) {
58 | Bundle b = new Bundle();
59 | b.putParcelable(Intents.EXTRA_IMAGE, Parcels.wrap(image));
60 | return (SlideFragment) SlideFragment.instantiate(context, SlideFragment.class.getName(), b);
61 | }
62 |
63 | @Override public void onCreate(Bundle savedInstanceState) {
64 | super.onCreate(savedInstanceState);
65 | extractIntentArguments();
66 | displayPhoto();
67 | }
68 |
69 | @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
70 | @Nullable Bundle savedInstanceState) {
71 | super.onCreateView(inflater, container, savedInstanceState);
72 | return inflater.inflate(R.layout.fragment_slide, container, false);
73 | }
74 |
75 | @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
76 | super.onViewCreated(view, savedInstanceState);
77 | ButterKnife.bind(this, view);
78 | ViewUtils.setViewHeight(photo, Const.IMAGE_HEIGHT, true);
79 | title.setText(image.title());
80 | caption.setText(image.caption());
81 | applyColor();
82 | applyImage();
83 | }
84 |
85 | @Override public void onDestroy() {
86 | cancelPaletteTask();
87 | if (target != null) {
88 | Picasso.get().cancelRequest(target);
89 | target = null;
90 | }
91 |
92 | bitmap = null;
93 | super.onDestroy();
94 | }
95 |
96 | @Override public void onGenerated(Palette palette) {
97 | hasPalette = true;
98 | target = null;
99 | int black = getResources().getColor(android.R.color.black);
100 | int white = getResources().getColor(android.R.color.white);
101 | colorDarkMuted = palette.getDarkMutedColor(black);
102 | colorVibrant = palette.getVibrantColor(white);
103 | colorLightMuted = palette.getLightMutedColor(white);
104 | applyColor();
105 | sendPalette();
106 | }
107 |
108 | private void extractIntentArguments() {
109 | Bundle b = getArguments();
110 | image = Parcels.unwrap(b.getParcelable(Intents.EXTRA_IMAGE));
111 | }
112 |
113 | private void cancelPaletteTask() {
114 | if (paletteTask != null) {
115 | paletteTask.cancel(true);
116 | paletteTask = null;
117 | }
118 | }
119 |
120 | private void sendPalette() {
121 | getActivity().sendBroadcast(attachColors(
122 | new Intent(Intents.ACTION_COLORIZE)
123 | .putExtra(Intents.EXTRA_IMAGE, Parcels.wrap(image))));
124 | }
125 |
126 | private Intent attachColors(Intent intent) {
127 | intent.putExtra(Intents.EXTRA_CLR_LIGHT_MUTED, colorLightMuted);
128 | intent.putExtra(Intents.EXTRA_CLR_DARK_MUTED, colorDarkMuted);
129 | intent.putExtra(Intents.EXTRA_CLR_VIBRANT, colorVibrant);
130 | return intent;
131 | }
132 |
133 | private void applyImage() {
134 | if (bitmap != null && photo != null) {
135 | photo.setImageBitmap(bitmap);
136 | }
137 | }
138 |
139 | private void applyColor() {
140 | if (hasPalette && bottomContainer != null) {
141 | bottomContainer.setBackgroundColor(colorDarkMuted);
142 | title.setTextColor(colorVibrant);
143 | caption.setTextColor(colorLightMuted);
144 | bottomContainer.animate().alpha(1.0f).setDuration(200).start();
145 | sendPalette();
146 | }
147 | }
148 |
149 | private void displayPhoto() {
150 | Picasso.get().load(Utils.imageUrl(image.photo().url()))
151 | .resize(Const.IMAGE_WIDTH, Const.IMAGE_HEIGHT)
152 | .centerCrop()
153 | .into(target = new TargetAdapter() {
154 | @Override public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
155 | SlideFragment.this.bitmap = bitmap;
156 | applyImage();
157 | paletteTask = new Palette.Builder(bitmap)
158 | .maximumColorCount(32)
159 | .generate(SlideFragment.this);
160 | }
161 | });
162 | }
163 |
164 | public int getColorVibrant() {
165 | return colorVibrant;
166 | }
167 |
168 | public int getColorDarkMuted() {
169 | return colorDarkMuted;
170 | }
171 |
172 | public int getColorLightMuted() {
173 | return colorLightMuted;
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/app/src/main/java/gallery/templates/contentful/gallery/GalleryAdapter.java:
--------------------------------------------------------------------------------
1 | package gallery.templates.contentful.gallery;
2 |
3 | import android.view.LayoutInflater;
4 | import android.view.View;
5 | import android.view.ViewGroup;
6 | import android.widget.ImageView;
7 | import android.widget.TextView;
8 |
9 | import com.squareup.picasso.Picasso;
10 |
11 | import java.util.ArrayList;
12 | import java.util.HashMap;
13 | import java.util.List;
14 | import java.util.Map;
15 |
16 | import androidx.recyclerview.widget.RecyclerView;
17 | import butterknife.BindView;
18 | import butterknife.ButterKnife;
19 | import gallery.templates.contentful.R;
20 | import gallery.templates.contentful.lib.Utils;
21 | import gallery.templates.contentful.vault.Gallery;
22 | import gallery.templates.contentful.vault.Image;
23 |
24 | public class GalleryAdapter extends RecyclerView.Adapter {
25 | public static final int VIEW_TYPE_SECTION = R.layout.grid_section;
26 |
27 | public static final int VIEW_TYPE_ITEM = R.layout.grid_item;
28 |
29 | private final Map sections;
30 |
31 | private final ArrayList