package io.nextop.demo.flip; import android.os.Bundle; import android.view.*; import android.widget.*; import io.nextop.Id; import io.nextop.Nextop; import io.nextop.NextopAndroid; import io.nextop.v15.fragment.DebugFragment; import io.nextop.rx.RxActivity; import io.nextop.rx.RxViewGroup; import io.nextop.view.ImageView; import io.nextop.vm.ImageViewModel; import rx.Observable; import rx.Observer; import rx.Subscription; import rx.android.schedulers.AndroidSchedulers; import rx.functions.Action0; import rx.functions.Action1; import rx.functions.Func1; import rx.subjects.PublishSubject; import rx.subscriptions.CompositeSubscription; import javax.annotation.Nullable; import java.util.concurrent.TimeUnit; public class MainActivity extends RxActivity { Flip flip; FeedAdapter feedAdapter; int animateIntervalMs = 500; @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.debug: // toggle the debug fragment View debugContainer = findViewById(R.id.debug_container); if (View.VISIBLE != debugContainer.getVisibility()) { if (null == getFragmentManager().findFragmentById(R.id.debug_container)) { getFragmentManager().beginTransaction() .add(R.id.debug_container, DebugFragment.newInstance()) .commit(); } debugContainer.setVisibility(View.VISIBLE); } else { debugContainer.setVisibility(View.GONE); } return true; case R.id.about: // FIXME return true; default: return super.onOptionsItemSelected(item); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); flip = (Flip) getApplication(); feedAdapter = new FeedAdapter(); setContentView(R.layout.activity_main); GridView gridView = (GridView) findViewById(R.id.grid); gridView.setAdapter(feedAdapter); ImageButton createButton = (ImageButton) findViewById(R.id.create_button); bind(flip.getFeedVmm().get(flip.getFeedId())).subscribe(feedAdapter); gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Id flipId = feedAdapter.getItem(position); startActivity(FlipActivity.viewIntent(MainActivity.this, flipId)); } }); createButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Id flipId = Id.create(); // pop up a dialog asking for title // FIXME speech recognize the title; don't prompt for it // FlipViewModel flipVm = new FlipViewModel(flipId); // flipVm.title = ""; flip.getFeedVmm().addFlip(flip.getFeedId(), flipId); startActivity(FlipActivity.recordIntent(MainActivity.this, flipId)); } }); } // // @Override // protected void onResume() { // super.onResume(); // // // FIXME this should happen via bind(...) subscribes when bind works! // feedAdapter.notifyDataSetChanged(); // } private class FeedAdapter extends BaseAdapter implements Observer<FeedViewModel> { @Nullable FeedViewModel feedVm = null; @Override public void onNext(FeedViewModel feedVm) { this.feedVm = feedVm; notifyDataSetChanged(); } @Override public void onCompleted() { feedVm = null; notifyDataSetChanged(); } @Override public void onError(Throwable e) { feedVm = null; notifyDataSetChanged(); } @Override public int getCount() { return null != feedVm ? feedVm.size() : 0; } @Override public Id getItem(int position) { return feedVm.getFlipId(position); } @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_feed_tile, parent, false); @Nullable RxViewGroup rxVg = (RxViewGroup) convertView.findViewWithTag(RxViewGroup.TAG); if (null != rxVg) { rxVg.cascadeDispose(MainActivity.this); } } Id flipId = getItem(position); RxViewGroup rxVg = (RxViewGroup) convertView.findViewWithTag(RxViewGroup.TAG); if (null == rxVg) { throw new IllegalStateException(); } // if (null != rxVg) { if (rxVg.reset(flipId)) { updateIndefinitely(rxVg, convertView, rxVg.bind(flip.getFlipInfoVmm().get(flipId)), rxVg.bind(flip.getFlipVmm().get(flipId))); } // } else { // // update immediate snapshot // updateIndefinitely(null, convertView, flip.getFlipInfoVmm().peek(flipId), flip.getFlipVmm().peek(flipId)); // } 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(RxViewGroup rxVg, View view, Observable<FlipInfoViewModel> flipInfoVmSource, Observable<FlipViewModel> flipVmSource) { ImageView imageView = (ImageView) view.findViewById(R.id.image); final TextView shortIntroView = (TextView) view.findViewById(R.id.short_intro); imageView.reset(); Observable<ImageViewModel> imageVmSource = new LoadingImageVmSource(NextopAndroid.getActive(view), animateIntervalMs, flipVmSource) .out; // .distinctUntilChanged(); rxVg.bind(imageVmSource).subscribe(new ImageView.Updater(imageView, null, ImageView.Transition.instantHold())); Observable<String> shortIntroSource = flipInfoVmSource.map(new Func1<FlipInfoViewModel, String>() { @Override public String call(FlipInfoViewModel flipInfoVm) { return flipInfoVm.intro; } }); shortIntroSource.subscribe(new Action1<String>() { @Override public void call(String intro) { shortIntroView.setText(intro); } }); } @Override public int getItemViewType(int position) { return 0; } @Override public int getViewTypeCount() { return 1; } } private static final class LoadingImageVmSource { final Nextop nextop; final int loopIntervalMs; final PublishSubject<ImageViewModel> subject; final Observable<ImageViewModel> out; Subscription flipVmSubscription = null; Subscription emitSubscription; LoadingImageVmSource(Nextop nextop, int loopIntervalMs, final Observable<FlipViewModel> flipVmSource) { this.nextop = nextop; this.loopIntervalMs = loopIntervalMs; subject = PublishSubject.create(); out = subject.doOnSubscribe(new Action0() { @Override public void call() { flipVmSubscription = flipVmSource.doOnNext(new Action1<FlipViewModel>() { @Override public void call(FlipViewModel flipVm) { if (null != emitSubscription) { emitSubscription.unsubscribe(); emitSubscription = null; } int n = flipVm.size(); FrameViewModel[] frameVms = new FrameViewModel[n]; for (int i = 0; i < n; ++i) { frameVms[i] = flipVm.getFrameVm(i); } if (0 < frameVms.length) { subject.onNext(frameVms[0].imageVm); CompositeSubscription s = new CompositeSubscription(); emitSubscription = s; emit(frameVms, 0, s); } } }).doOnCompleted(new Action0() { @Override public void call() { if (null != emitSubscription) { emitSubscription.unsubscribe(); emitSubscription = null; } } }).subscribe(); } }).doOnUnsubscribe(new Action0() { @Override public void call() { if (null != emitSubscription) { emitSubscription.unsubscribe(); emitSubscription = null; } flipVmSubscription.unsubscribe(); } }).share(); } void emit(final FrameViewModel[] frameVms, int i, final CompositeSubscription s) { if (s.isUnsubscribed()) { return; } // find the next frame vm with a local source while (i < frameVms.length && null == frameVms[i].imageVm.localId) { ++i; } if (i < frameVms.length) { final int locali = i; s.clear(); s.add(nextop.transferStatus(frameVms[locali].imageVm.localId).subscribe(new Observer<Nextop.TransferStatus>() { int count = 0; @Override public void onNext(Nextop.TransferStatus transferStatus) { if (1 == ++count && transferStatus.send.asFloat() < 1.f) { subject.onNext(frameVms[locali].imageVm); } } @Override public void onCompleted() { emit(frameVms, locali + 1, s); } @Override public void onError(Throwable e) { emit(frameVms, locali + 1, s); } })); } else { // now all the frames are loaded s.clear(); if (loopIntervalMs <= 0) { subject.onNext(frameVms[0].imageVm); } else { // loop through all the frames s.add(AndroidSchedulers.mainThread().createWorker().schedulePeriodically(new Action0() { int frameCount = 0; @Override public void call() { int index = frameCount++; subject.onNext(frameVms[index % frameVms.length].imageVm); } }, /* have frames hit near each other */ loopIntervalMs - (System.currentTimeMillis() % loopIntervalMs), loopIntervalMs, TimeUnit.MILLISECONDS)); } } } } }