package io.nextop.demo.flip; import android.content.Context; import android.graphics.Canvas; import android.os.Bundle; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.*; import io.nextop.Id; import io.nextop.v15.rx.RxFragment; import io.nextop.rx.RxViewGroup; import io.nextop.view.ImageView; import io.nextop.vm.ImageViewModel; import rx.Observable; import rx.Observer; import rx.functions.Action1; import rx.functions.Func1; import rx.subjects.PublishSubject; import javax.annotation.Nullable; public class FlipFragment extends RxFragment { public static FlipFragment newInstance(Id flipId) { Bundle args = new Bundle(); args.putString("flipId", flipId.toString()); FlipFragment f = new FlipFragment(); f.setArguments(args); return f; } Id flipId; Flip flip; FlipAdapter flipAdapter; ListView listView; boolean scrollToEnd = false; int targetHeight; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); flipId = Id.valueOf(getArguments().getString("flipId")); flip = (Flip) getActivity().getApplication(); flipAdapter = new FlipAdapter(); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_flip, container, false); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); View view = getView(); listView = (ListView) view.findViewById(R.id.list); LayoutInflater inflater = LayoutInflater.from(view.getContext()); final View header = inflater.inflate(R.layout.view_flip_header, listView, false); final View footer = inflater.inflate(R.layout.view_flip_footer, listView, false); final ImageView largeView = (ImageView) view.findViewById(R.id.large); ImageButton createButton = (ImageButton) view.findViewById(R.id.create_button); listView.addHeaderView(header); listView.addFooterView(footer); listView.setAdapter(flipAdapter); bind(flip.getFlipVmm().get(flipId)).subscribe(flipAdapter); final PublishSubject<ImageViewModel> largeSource = PublishSubject.create(); bind(largeSource).distinctUntilChanged().subscribe(new ImageView.Updater(largeView, ImageView.Transition.instantHold())); createButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ((FlipActivity) getActivity()).startRecording(); } }); // on layout change, set header and foot height to match list height listView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { targetHeight = bottom - top; float headerf = 0.25f; float footerf = 0.15f; if (targetHeight != header.getLayoutParams().height) { header.getLayoutParams().height = Math.round(headerf * targetHeight); header.invalidate(); } if (targetHeight != footer.getLayoutParams().height) { footer.getLayoutParams().height = Math.round(footerf * targetHeight); footer.invalidate(); } } }); // FIXME animations here listView.setOnScrollListener(new AbsListView.OnScrollListener() { int scrollState = SCROLL_STATE_IDLE; boolean visible = false; int publishIndex = -1; void show() { // if (!visible) { // visible = true; // // if (View.VISIBLE != largeView.getVisibility()) { //// largeView.setAlpha(0.f); // largeView.setVisibility(View.VISIBLE); // } // // float a = largeView.getAlpha(); // // ObjectAnimator animator = ObjectAnimator.ofFloat(largeView, "alpha", a, 1.f); // animator.setAutoCancel(true); // animator.setDuration(Math.round((1.f - a) * 200)); // animator.setInterpolator(new DecelerateInterpolator()); // animator.start(); // // } if (!visible) { visible = true; largeView.setVisibility(View.VISIBLE); } } void hide() { // if (visible) { // visible = false; // // if (View.VISIBLE == largeView.getVisibility()) { // // float a = largeView.getAlpha(); // // ObjectAnimator animator = ObjectAnimator.ofFloat(largeView, "alpha", a, 0.f); // animator.setAutoCancel(true); // animator.setDuration(Math.round(a * 200)); // animator.setInterpolator(new AccelerateInterpolator()); // // animator.addListener(new Animator.AnimatorListener() { // @Override // public void onAnimationEnd(Animator animation) { // largeView.setVisibility(View.INVISIBLE); // } // // @Override // public void onAnimationStart(Animator animation) { // // } // // @Override // public void onAnimationCancel(Animator animation) { // // } // // @Override // public void onAnimationRepeat(Animator animation) { // // } // }); // animator.start(); // // } // // } if (visible) { visible = false; largeView.setVisibility(View.INVISIBLE); } } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { this.scrollState = scrollState; // switch (scrollState) { // case SCROLL_STATE_IDLE: // case SCROLL_STATE_TOUCH_SCROLL: // hide(); // break; // default: // // ignore // break; // } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { // FIXME bind // FIXME fade bg of last element to black on scroll down if (publishLarge(firstVisibleItem, visibleItemCount)) { show(); } else { hide(); } // switch (scrollState) { // case SCROLL_STATE_FLING: // if (publishLarge(firstVisibleItem, visibleItemCount)) { // show(); // } else { // hide(); // } // break; // default: // // ignore // break; // } } boolean publishLarge(int firstVisibleItem, int visibleItemCount) { // publish large the frame closest to the center // int centerPosition = firstVisibleItem + visibleItemCount / 2; // if (publishLarge(centerPosition)) { // return true; // } // // for (int i = 1; i <= visibleItemCount / 2; ++i) { // if (publishLarge(centerPosition + i) || publishLarge(centerPosition - i)) { // return true; // } // } // // return false; int index = indexOf(firstVisibleItem); if (0 <= index && 0 <= indexOf(firstVisibleItem + visibleItemCount - 1)) { publishLarge(index); return true; } else { return false; } } int indexOf(int position) { int index = position - listView.getHeaderViewsCount(); if (index < 0 || flipAdapter.getCount() <= index) { return -1; } return index; } void publishLarge(int index) { if (publishIndex != index) { publishIndex = index; Id frameId = flipAdapter.getItem(index); largeSource.onNext(flipAdapter.flipVm.getFrameVm(frameId).imageVm); } } }); updateHeaderIndefinitely(header, bind(flip.getFlipInfoVmm().get(flipId))); // set up list adapter // set up scroll listener // set up logic to show fixed frame when scrolling fast scrollToEnd(); } private void updateHeaderIndefinitely(View view, Observable<FlipInfoViewModel> flipInfoVmSource) { final TextView intro = (TextView) view.findViewById(R.id.intro); Observable<String> introSource = flipInfoVmSource.map(new Func1<FlipInfoViewModel, String>() { @Override public String call(FlipInfoViewModel flipInfoVm) { return flipInfoVm.intro; } }); introSource.subscribe(new Action1<String>() { @Override public void call(String s) { intro.setText(s); } }); } void onStartRecording() { // Do nothing } void onStopRecording() { // Do nothing scrollToEnd = true; scrollToEnd(); } void scrollToEnd() { if (scrollToEnd && null != listView) { scrollToEnd = false; int n = flipAdapter.getCount(); int index = n - 1; for (; 0 <= index; --index) { @Nullable FrameViewModel frameVm = flipAdapter.flipVm.getFrameVm(flipAdapter.getItem(index)); if (null != frameVm && null != frameVm.imageVm.uri) { break; } } if (index + 1 < n) { listView.setSelection(listView.getHeaderViewsCount() + index + 1); } } } private class FlipAdapter extends BaseAdapter implements Observer<FlipViewModel> { @Nullable FlipViewModel flipVm = null; @Override public void onNext(FlipViewModel flipVm) { this.flipVm = flipVm; notifyDataSetChanged(); } @Override public void onCompleted() { flipVm = null; notifyDataSetChanged(); } @Override public void onError(Throwable e) { flipVm = null; notifyDataSetChanged(); } @Override public int getCount() { return null != flipVm ? flipVm.size() : 0; } @Override public Id getItem(int position) { return flipVm.getFrameVm(position).id; } @Override public long getItemId(int position) { return getItem(position).longHashCode(); } @Override public View getView(int position, View convertView, ViewGroup parent) { if (null == convertView) { convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_flip_frame, parent, false); @Nullable RxViewGroup rxVg = (RxViewGroup) convertView.findViewWithTag(RxViewGroup.TAG); if (null != rxVg) { rxVg.cascadeDispose(FlipFragment.this); } } convertView.getLayoutParams().height = targetHeight; final Id frameId = getItem(position); @Nullable RxViewGroup rxVg = (RxViewGroup) convertView.findViewWithTag(RxViewGroup.TAG); if (null == rxVg) { throw new IllegalStateException(); } // if (null != rxVg) { if (rxVg.reset(frameId)) { updateIndefinitely(convertView, rxVg.bind(flip.getFlipVmm().get(flipId).map(new Func1<FlipViewModel, FrameViewModel>() { @Override public FrameViewModel call(FlipViewModel flipVm) { return flipVm.getFrameVm(frameId); } }))); } // } else { // // update immediate snapshot // updateIndefinitely(convertView, flip.getFlipVmm().peek(flipId).map(new Func1<FlipViewModel, FrameViewModel>() { // @Override // public FrameViewModel call(FlipViewModel flipVm) { // return flipVm.getFrameVm(frameId); // } // })); // } return convertView; } // assume the subscription will be managed via onComplete from a higher level // this code just assumes it runs until done private void updateIndefinitely(View view, Observable<FrameViewModel> frameVmSource) { Observable<ImageViewModel> imageVmSource = frameVmSource.map(new Func1<FrameViewModel, ImageViewModel>() { @Override public ImageViewModel call(FrameViewModel frameVm) { return frameVm.imageVm; } }).distinctUntilChanged(); ImageView imageView = (ImageView) view.findViewById(R.id.image); imageView.reset(); imageVmSource.subscribe(new ImageView.Updater(imageView, ImageView.Transition.instant())); } @Override public int getItemViewType(int position) { return 0; } @Override public int getViewTypeCount() { return 1; } } public static final class ProgressHero extends View { public ProgressHero(Context context) { super(context); } public ProgressHero(Context context, AttributeSet attrs) { super(context, attrs); } public ProgressHero(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public ProgressHero(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } // FIXME accept frameIndex, progressf [0, 1) for frame index // FIXME draw a hero marker for the point @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // FIXME animation sequence // Paint paint = new Paint(); // paint.setColor(Color.argb(92, 0, 0, 0)); // canvas.drawRect(100, 100, 200, 200, paint); } } }