Java tutorial
/* * Mr.Mantou - On the importance of taste * Copyright (C) 2015-2016 XiNGRZ <xxx@oxo.ooo> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package ooo.oxo.mr; import android.Manifest; import android.annotation.TargetApi; import android.app.WallpaperManager; import android.content.Intent; import android.databinding.DataBindingUtil; import android.databinding.ObservableArrayList; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.media.MediaScannerConnection; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.support.annotation.IdRes; import android.support.annotation.NonNull; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentStatePagerAdapter; import android.support.v4.app.SharedElementCallback; import android.support.v4.content.FileProvider; import android.support.v4.view.ViewPager; import android.transition.Transition; import android.util.Log; import android.view.View; import com.bumptech.glide.Glide; import com.jakewharton.rxbinding.view.RxMenuItem; import com.tbruyelle.rxpermissions.RxPermissions; import com.trello.rxlifecycle.components.support.RxAppCompatActivity; import com.umeng.analytics.MobclickAgent; import java.io.File; import java.io.IOException; import java.util.List; import java.util.Locale; import java.util.Map; import ooo.oxo.library.widget.PullBackLayout; import ooo.oxo.mr.databinding.ViewerActivityBinding; import ooo.oxo.mr.model.Image; import ooo.oxo.mr.rx.RxFiles; import ooo.oxo.mr.rx.RxGlide; import ooo.oxo.mr.util.ImmersiveUtil; import ooo.oxo.mr.util.InOutAnimationUtils; import ooo.oxo.mr.util.ObservableListPagerAdapterCallback; import ooo.oxo.mr.util.SimpleTransitionListener; import ooo.oxo.mr.util.ToastUtil; import rx.Observable; import rx.android.schedulers.AndroidSchedulers; import rx.schedulers.Schedulers; public class ViewerActivity extends RxAppCompatActivity implements PullBackLayout.Callback { private static final String TAG = "ViewerActivity"; private static final String AUTHORITY_IMAGES = BuildConfig.APPLICATION_ID + ".images"; private final ObservableArrayList<Image> images = MrSharedState.getInstance().getImages(); private ViewerActivityBinding binding; private Adapter adapter; private ObservableListPagerAdapterCallback listener; private ColorDrawable background; private static String makeFileName(Image image) { return String.format(Locale.US, "%d.%s", image.getCreatedAt().getTime(), image.getType()); } private Observable<Void> menuItemClicks(@IdRes int id) { return RxMenuItem.clicks(binding.toolbar.getMenu().findItem(id)); } private Observable<File> ensureExternalDirectory(String name) { return RxFiles.mkdirsIfNotExists(new File(Environment.getExternalStorageDirectory(), name)); } private Observable<File> download(Image image) { return RxGlide.download(Glide.with(this), image.getUrl()); } private Observable<File> save(Image image, File destination) { return download(image).flatMap(tmp -> RxFiles.copy(tmp, destination)); } private Observable<File> saveIfNeeded(Image image) { return ensureExternalDirectory("Mr.Mantou").map(directory -> new File(directory, makeFileName(image))) .flatMap(file -> file.exists() ? Observable.just(file) : save(image, file)); } private Observable.Transformer<Void, Void> ensurePermissions(@NonNull String... permissions) { return source -> source.compose(RxPermissions.getInstance(this).ensure(permissions)).filter(granted -> { if (granted) { return true; } else { ToastUtil.shorts(this, R.string.permission_required); return false; } }).map(granted -> null); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = DataBindingUtil.setContentView(this, R.layout.viewer_activity); setTitle(null); binding.toolbar.setNavigationOnClickListener(v -> supportFinishAfterTransition()); binding.toolbar.inflateMenu(R.menu.viewer); binding.puller.setCallback(this); supportPostponeEnterTransition(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { getWindow().getEnterTransition().addListener(new SimpleTransitionListener() { @Override @TargetApi(Build.VERSION_CODES.LOLLIPOP) public void onTransitionEnd(Transition transition) { getWindow().getEnterTransition().removeListener(this); fadeIn(); } }); } else { fadeIn(); } background = new ColorDrawable(Color.BLACK); binding.getRoot().setBackground(background); adapter = new Adapter(); binding.pager.setAdapter(adapter); binding.pager.setCurrentItem(getIntent().getIntExtra("index", 0)); binding.pager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { @Override public void onPageScrollStateChanged(int state) { if (state == ViewPager.SCROLL_STATE_DRAGGING) { fadeOut(); } } }); listener = new ObservableListPagerAdapterCallback(adapter); images.addOnListChangedCallback(listener); setEnterSharedElementCallback(new SharedElementCallback() { @Override public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) { Image image = images.get(binding.pager.getCurrentItem()); sharedElements.clear(); sharedElements.put(String.format("%s.image", image.getObjectId()), getCurrent().getSharedElement()); } }); menuItemClicks(R.id.share).compose(bindToLifecycle()) .compose(ensurePermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)) .map(avoid -> getCurrentImage()) .doOnNext(image -> MobclickAgent.onEvent(this, "share", image.getObjectId())) .observeOn(Schedulers.io()).flatMap(this::saveIfNeeded).observeOn(AndroidSchedulers.mainThread()) .doOnNext(this::notifyMediaScanning).map(Uri::fromFile).retry().subscribe(uri -> { final Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("image/jpeg"); intent.putExtra(Intent.EXTRA_STREAM, uri); startActivity(Intent.createChooser(intent, getString(R.string.share_title))); }); menuItemClicks(R.id.save).compose(bindToLifecycle()) .compose(ensurePermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)) .map(avoid -> getCurrentImage()) .doOnNext(image -> MobclickAgent.onEvent(this, "save", image.getObjectId())) .observeOn(Schedulers.io()).flatMap(this::saveIfNeeded).observeOn(AndroidSchedulers.mainThread()) .doOnNext(this::notifyMediaScanning).retry().subscribe(file -> { ToastUtil.shorts(this, R.string.save_success, file.getPath()); }); final WallpaperManager wm = WallpaperManager.getInstance(this); menuItemClicks(R.id.set_wallpaper).compose(bindToLifecycle()).map(avoid -> getCurrentImage()) .doOnNext(image -> MobclickAgent.onEvent(this, "set_wallpaper", image.getObjectId())) .observeOn(Schedulers.io()).flatMap(this::download).observeOn(AndroidSchedulers.mainThread()) .map(file -> FileProvider.getUriForFile(this, AUTHORITY_IMAGES, file)).retry().subscribe(uri -> { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { startActivity(wm.getCropAndSetWallpaperIntent(uri)); } else { try { wm.setStream(getContentResolver().openInputStream(uri)); ToastUtil.shorts(this, R.string.set_wallpaper_success); } catch (IOException e) { Log.e(TAG, "Failed to set wallpaper", e); ToastUtil.shorts(this, e.getMessage(), e); } } }); } @Override protected void onDestroy() { super.onDestroy(); images.removeOnListChangedCallback(listener); } @Override protected void onResume() { super.onResume(); MobclickAgent.onResume(this); } @Override protected void onPause() { super.onPause(); MobclickAgent.onPause(this); } private Image getCurrentImage() { return images.get(binding.pager.getCurrentItem()); } void fadeIn() { InOutAnimationUtils.animateIn(binding.toolbar, R.anim.viewer_toolbar_fade_in); ImmersiveUtil.exit(this); } void fadeOut() { InOutAnimationUtils.animateOut(binding.toolbar, R.anim.viewer_toolbar_fade_out); ImmersiveUtil.enter(this); } void toggleFade() { if (binding.toolbar.getVisibility() == View.VISIBLE) { fadeOut(); } else { fadeIn(); } } @Override public void onPullStart() { InOutAnimationUtils.animateOut(binding.toolbar, R.anim.viewer_toolbar_fade_out); ImmersiveUtil.exit(this); } @Override public void onPull(float progress) { progress = Math.min(1f, progress * 3f); background.setAlpha((int) (0xff * (1f - progress))); } @Override public void onPullCancel() { fadeIn(); } @Override public void onPullComplete() { supportFinishAfterTransition(); } public ViewerFragment getCurrent() { return (ViewerFragment) adapter.instantiateItem(binding.pager, binding.pager.getCurrentItem()); } @Override public void supportFinishAfterTransition() { Intent data = new Intent(); data.putExtra("index", binding.pager.getCurrentItem()); setResult(RESULT_OK, data); ImmersiveUtil.exit(this); super.supportFinishAfterTransition(); } private void notifyMediaScanning(File file) { MediaScannerConnection.scanFile(getApplicationContext(), new String[] { file.getPath() }, null, null); } private class Adapter extends FragmentStatePagerAdapter { public Adapter() { super(getSupportFragmentManager()); } @Override public Fragment getItem(int position) { Image image = images.get(position); Bundle arguments = new Bundle(); arguments.putParcelable("image", image); if (position == getIntent().getIntExtra("index", 0)) { arguments.putString("thumbnail", getIntent().getStringExtra("thumbnail")); } Fragment fragment = new ViewerFragment(); fragment.setArguments(arguments); return fragment; } @Override public int getCount() { return images.size(); } } }