@ThreadSafe
class Example_11_ViewModel_Address implements IViewModel<Example_11_Model> {

  public final BehaviorSubject<Boolean> vm2v_edit = BehaviorSubject.create();

  public final BehaviorSubject<String> v2vm_address = BehaviorSubject.create();
  public final BehaviorSubject<String> vm2v_address = BehaviorSubject.create();

  public final BehaviorSubject<Boolean> vm2v_dirty =
      BehaviorSubject.create(false /* initial state */);

  public Example_11_ViewModel_Address() {
    wireInternally();
  }

  private void wireInternally() {
    v2vm_address
        .map(anschriftGemässView -> !anschriftGemässView.equals(vm2v_address.getValue()))
        .subscribe(vm2v_dirty);

    // Simulating some data
    vm2v_address.onNext("Koch Peti\nLadengasse 42\n4242 Vordemberg\nSchweiz");
  }

  @Override
  public void connectTo(final Example_11_Model model) {
    // NO-OP
  }
}
  public DownloadManager(
      Context context, SourceManager sourceManager, PreferencesHelper preferences) {
    this.context = context;
    this.sourceManager = sourceManager;
    this.preferences = preferences;

    gson = new Gson();
    queue = new DownloadQueue();

    downloadsQueueSubject = PublishSubject.create();
    threadsNumber = BehaviorSubject.create();
    runningSubject = BehaviorSubject.create();
  }
Beispiel #3
0
  public Subscription subscribeData(Observer<List<GankBeauty>> observer) {
    if (mCache == null) {
      mCache = BehaviorSubject.create();
      Observable.create(
              new Observable.OnSubscribe<List<GankBeauty>>() {

                @Override
                public void call(Subscriber<? super List<GankBeauty>> subscriber) {
                  List<GankBeauty> itemList = getDataFromDisk(DATA_FILE_NAME);
                  if (ListUtils.isEmpty(itemList)) {
                    Logger.d("Load data from network");
                    setDataSource(DATA_SOURCE_NETWORK);
                    loadFromNetwork();
                  } else {
                    Logger.d("Load data from disk");
                    setDataSource(DATA_SOURCE_DISK);
                    subscriber.onNext(itemList);
                  }
                }
              })
          .subscribeOn(Schedulers.io())
          .subscribe(mCache);
    } else {
      Logger.d("Load data from memory.");
      setDataSource(DATA_SOURCE_MEMORY);
    }
    return mCache.observeOn(AndroidSchedulers.mainThread()).subscribe(observer);
  }
Beispiel #4
0
public class RepositoryViewModel extends AbstractViewModel {
  private static final String TAG = RepositoryViewModel.class.getSimpleName();

  private final DataFunctions.GetUserSettings getUserSettings;
  private final DataFunctions.FetchAndGetGitHubRepository fetchAndGetGitHubRepository;

  private final BehaviorSubject<GitHubRepository> repository = BehaviorSubject.create();

  public RepositoryViewModel(
      @NonNull DataFunctions.GetUserSettings getUserSettings,
      @NonNull DataFunctions.FetchAndGetGitHubRepository fetchAndGetGitHubRepository) {
    Preconditions.checkNotNull(getUserSettings, "Gey User Settings cannot be null.");
    Preconditions.checkNotNull(
        fetchAndGetGitHubRepository, "Fetch And Get GitHub Repository cannot be null.");

    this.getUserSettings = getUserSettings;
    this.fetchAndGetGitHubRepository = fetchAndGetGitHubRepository;
    Log.v(TAG, "RepositoryViewModel");
  }

  @Override
  public void subscribeToDataStoreInternal(@NonNull CompositeSubscription compositeSubscription) {
    compositeSubscription.add(
        getUserSettings
            .call()
            .map(UserSettings::getSelectedRepositoryId)
            .switchMap(fetchAndGetGitHubRepository::call)
            .subscribe(repository));
  }

  @NonNull
  public Observable<GitHubRepository> getRepository() {
    return repository.asObservable();
  }
}
Beispiel #5
0
/** Created by chanx on 16/5/20. */
public class DataStore {
  private static DataStore INSTANCE;

  // actual data store could be files, databases, or ContentProvider etc
  private final BehaviorSubject<List<GithubUser>> mUserListDataStoreStream =
      BehaviorSubject.create();

  private DataStore() {}

  public static DataStore getInstance() {
    if (INSTANCE == null) {
      synchronized (DataStore.class) {
        if (INSTANCE == null) {
          INSTANCE = new DataStore();
        }
      }
    }
    return INSTANCE;
  }

  public Observable<List<GithubUser>> getGithubUserListDataStore() {
    return mUserListDataStoreStream.asObservable();
  }

  public void fetchRandomGithubUsersToDataStore() {
    Intent intent = new Intent(GithubNetworkService.NETWORK_REQUEST_RANDOM_GITHUB_USERS);
    intent.setClass(DemoApplication.getContext(), GithubNetworkService.class);
    DemoApplication.getContext().startService(intent);
    Logger.d("send intent to NetworkService:" + intent);
  }

  public void setUserList(List<GithubUser> list) {
    mUserListDataStoreStream.onNext(list);
  }
}
@ThreadSafe
class Example_10_ViewModel_Address implements IViewModel<Example_10_Model> {

  public final BehaviorSubject<Boolean> vm2v_edit = BehaviorSubject.create();

  public final BehaviorSubject<String> v2vm_address = BehaviorSubject.create();
  public final BehaviorSubject<String> vm2v_address = BehaviorSubject.create();

  public Example_10_ViewModel_Address() {
    wireInternally();
  }

  private void wireInternally() {
    // Simulating some data
    vm2v_address.onNext("Koch Peti\nLadengasse 42\n4242 Vordemberg\nSchweiz");
  }

  @Override
  public void connectTo(final Example_10_Model model) {
    // NO-OP
  }
}
Beispiel #7
0
public class AppRouter extends Router {

  private final BehaviorSubject<RouteChange> screenChangeSubject = BehaviorSubject.create();

  public AppRouter(ScreenScooper screenScooper) {
    super(screenScooper);
  }

  public Observable<RouteChange> observeScreenChange() {
    return screenChangeSubject.asObservable();
  }

  @Override
  protected void onScoopChanged(RouteChange change) {
    screenChangeSubject.onNext(change);
  }
}
public class BaseActivity extends FragmentActivity {
  @Nonnull
  private final BehaviorSubject<LifecycleEvent> lifecycleSubject = BehaviorSubject.create();

  protected final LifecycleMainObservable lifecycleMainObservable =
      new LifecycleMainObservable(
          new LifecycleMainObservable.LifecycleProviderActivity(lifecycleSubject, this));

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    lifecycleSubject.onNext(LifecycleEvent.CREATE);
  }

  @Override
  protected void onStart() {
    super.onStart();

    lifecycleSubject.onNext(LifecycleEvent.START);
  }

  @Override
  protected void onResume() {
    super.onResume();
    lifecycleSubject.onNext(LifecycleEvent.RESUME);
  }

  @Override
  protected void onPause() {
    lifecycleSubject.onNext(LifecycleEvent.PAUSE);
    super.onPause();
  }

  @Override
  protected void onStop() {
    lifecycleSubject.onNext(LifecycleEvent.STOP);
    super.onStop();
  }

  @Override
  protected void onDestroy() {
    lifecycleSubject.onNext(LifecycleEvent.DESTROY);
    super.onDestroy();
  }
}
Beispiel #9
0
public abstract class BaseActivity extends RoboActionBarActivity {

  private final BehaviorSubject<LifecycleEvent> lifecycleSubject = BehaviorSubject.create();

  public Observable<LifecycleEvent> lifecycle() {
    return lifecycleSubject.asObservable();
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    lifecycleSubject.onNext(LifecycleEvent.CREATE);
  }

  @Override
  protected void onStart() {
    super.onStart();
    lifecycleSubject.onNext(LifecycleEvent.START);
  }

  @Override
  protected void onResume() {
    super.onResume();
    lifecycleSubject.onNext(LifecycleEvent.RESUME);
  }

  @Override
  protected void onPause() {
    lifecycleSubject.onNext(LifecycleEvent.PAUSE);
    super.onPause();
  }

  @Override
  protected void onStop() {
    lifecycleSubject.onNext(LifecycleEvent.STOP);
    super.onStop();
  }

  @Override
  protected void onDestroy() {
    lifecycleSubject.onNext(LifecycleEvent.DESTROY);
    super.onDestroy();
  }
}
Beispiel #10
0
/** Created by Administrator on 2016/3/18. */
public class RxBus {

  // PublishSubject仅会向Observer释放在订阅之后Observable释放的数据。
  private final Subject<Object, Object> publishBus =
      new SerializedSubject<>(PublishSubject.create());

  // AsyncSubject仅释放Observable释放的最后一个数据,并且仅在Observable完成之后。然而如果当Observable因为异常而终止,AsyncSubject将不会释放任何数据,但是会向Observer传递一个异常通知。
  private final Subject<Object, Object> asyncBus = new SerializedSubject<>(AsyncSubject.create());

  // 当Observer订阅了一个BehaviorSubject,它一开始就会释放Observable最近释放的一个数据对象,当还没有任何数据释放时,它则是一个默认值。接下来就会释放Observable释放的所有数据。
  // 如果Observable因异常终止,BehaviorSubject将不会向后续的Observer释放数据,但是会向Observer传递一个异常通知。
  private final Subject<Object, Object> behaviorBus =
      new SerializedSubject<>(BehaviorSubject.create());

  // 不管Observer何时订阅ReplaySubject,ReplaySubject会向所有Observer释放Observable释放过的数据。
  // 有不同类型的ReplaySubject,它们是用来限定Replay的范围,例如设定Buffer的具体大小,或者设定具体的时间范围。
  // 如果使用ReplaySubject作为Observer,注意不要在多个线程中调用onNext、onComplete和onError方法,因为这会导致顺序错乱,这个是违反了Observer规则的。
  private final Subject<Object, Object> replayBus = new SerializedSubject<>(ReplaySubject.create());
}
/** Created by Christoph on 05.10.2015. */
public class RacletteRxFragment<V extends ViewModel, B extends ViewDataBinding>
    extends RacletteFragment<V, B> implements FragmentLifecycleProvider {

  protected final BehaviorSubject<FragmentEvent> lifecycleSubject = BehaviorSubject.create();

  @Override
  @NonNull
  @CheckResult
  public final Observable<FragmentEvent> lifecycle() {
    return lifecycleSubject.asObservable();
  }

  @Override
  @NonNull
  @CheckResult
  public final <T> Observable.Transformer<T, T> bindUntilEvent(@NonNull FragmentEvent event) {
    return RxLifecycle.bindUntilEvent(lifecycleSubject, event);
  }

  @Override
  @NonNull
  @CheckResult
  public final <T> Observable.Transformer<T, T> bindToLifecycle() {
    return RxLifecycle.bindFragment(lifecycleSubject);
  }

  @Override
  @CallSuper
  public void onAttach(Context context) {
    super.onAttach(context);
    lifecycleSubject.onNext(FragmentEvent.ATTACH);
  }

  @Override
  @CallSuper
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    lifecycleSubject.onNext(FragmentEvent.CREATE);
  }

  @Override
  @CallSuper
  public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    lifecycleSubject.onNext(FragmentEvent.CREATE_VIEW);
  }

  @Override
  @CallSuper
  public void onStart() {
    super.onStart();
    lifecycleSubject.onNext(FragmentEvent.START);
  }

  @Override
  @CallSuper
  public void onResume() {
    super.onResume();
    lifecycleSubject.onNext(FragmentEvent.RESUME);
  }

  @Override
  @CallSuper
  public void onPause() {
    lifecycleSubject.onNext(FragmentEvent.PAUSE);
    super.onPause();
  }

  @Override
  @CallSuper
  public void onStop() {
    lifecycleSubject.onNext(FragmentEvent.STOP);
    super.onStop();
  }

  @Override
  @CallSuper
  public void onDestroyView() {
    lifecycleSubject.onNext(FragmentEvent.DESTROY_VIEW);
    super.onDestroyView();
  }

  @Override
  @CallSuper
  public void onDestroy() {
    lifecycleSubject.onNext(FragmentEvent.DESTROY);
    super.onDestroy();
  }

  @Override
  @CallSuper
  public void onDetach() {
    lifecycleSubject.onNext(FragmentEvent.DETACH);
    super.onDetach();
  }
}
Beispiel #12
0
public class RxFragment extends Fragment implements FragmentLifecycleProvider {

  private final BehaviorSubject<FragmentEvent> lifecycleSubject = BehaviorSubject.create();

  @Override
  public final Observable<FragmentEvent> lifecycle() {
    return lifecycleSubject.asObservable();
  }

  @Override
  public final <T> Observable.Transformer<T, T> bindUntilEvent(FragmentEvent event) {
    return RxLifecycle.bindUntilFragmentEvent(lifecycleSubject, event);
  }

  @Override
  public final <T> Observable.Transformer<T, T> bindToLifecycle() {
    return RxLifecycle.bindFragment(lifecycleSubject);
  }

  @Override
  @CallSuper
  public void onAttach(android.app.Activity activity) {
    super.onAttach(activity);
    lifecycleSubject.onNext(FragmentEvent.ATTACH);
  }

  @Override
  @CallSuper
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    lifecycleSubject.onNext(FragmentEvent.CREATE);
  }

  @Override
  @CallSuper
  public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    lifecycleSubject.onNext(FragmentEvent.CREATE_VIEW);
  }

  @Override
  @CallSuper
  public void onStart() {
    super.onStart();
    lifecycleSubject.onNext(FragmentEvent.START);
  }

  @Override
  @CallSuper
  public void onResume() {
    super.onResume();
    lifecycleSubject.onNext(FragmentEvent.RESUME);
  }

  @Override
  @CallSuper
  public void onPause() {
    lifecycleSubject.onNext(FragmentEvent.PAUSE);
    super.onPause();
  }

  @Override
  @CallSuper
  public void onStop() {
    lifecycleSubject.onNext(FragmentEvent.STOP);
    super.onStop();
  }

  @Override
  @CallSuper
  public void onDestroyView() {
    lifecycleSubject.onNext(FragmentEvent.DESTROY_VIEW);
    super.onDestroyView();
  }

  @Override
  @CallSuper
  public void onDestroy() {
    lifecycleSubject.onNext(FragmentEvent.DESTROY);
    super.onDestroy();
  }

  @Override
  @CallSuper
  public void onDetach() {
    lifecycleSubject.onNext(FragmentEvent.DETACH);
    super.onDetach();
  }
}
/**
 * Maintains a stream of distributions for a given Command. There is a rolling window abstraction on
 * this stream. The latency distribution object is calculated over a window of t1 milliseconds. This
 * window has b buckets. Therefore, a new set of counters is produced every t2 (=t1/b) milliseconds
 * t1 = metricsRollingPercentileWindowInMilliseconds() b = metricsRollingPercentileBucketSize()
 *
 * <p>These values are stable - there's no peeking into a bucket until it is emitted
 *
 * <p>These values get produced and cached in this class.
 */
public class RollingDistributionStream<Event extends HystrixEvent> {
  private AtomicReference<Subscription> rollingDistributionSubscription =
      new AtomicReference<Subscription>(null);
  private final BehaviorSubject<CachedValuesHistogram> rollingDistribution =
      BehaviorSubject.create(
          CachedValuesHistogram.backedBy(CachedValuesHistogram.getNewHistogram()));
  private final Observable<CachedValuesHistogram> rollingDistributionStream;

  private static final Func2<Histogram, Histogram, Histogram> distributionAggregator =
      new Func2<Histogram, Histogram, Histogram>() {
        @Override
        public Histogram call(Histogram initialDistribution, Histogram distributionToAdd) {
          initialDistribution.add(distributionToAdd);
          return initialDistribution;
        }
      };

  private static final Func1<Observable<Histogram>, Observable<Histogram>>
      reduceWindowToSingleDistribution =
          new Func1<Observable<Histogram>, Observable<Histogram>>() {
            @Override
            public Observable<Histogram> call(Observable<Histogram> window) {
              return window.reduce(distributionAggregator);
            }
          };

  private static final Func1<Histogram, CachedValuesHistogram> cacheHistogramValues =
      new Func1<Histogram, CachedValuesHistogram>() {
        @Override
        public CachedValuesHistogram call(Histogram histogram) {
          return CachedValuesHistogram.backedBy(histogram);
        }
      };

  private static final Func1<
          Observable<CachedValuesHistogram>, Observable<List<CachedValuesHistogram>>>
      convertToList =
          new Func1<Observable<CachedValuesHistogram>, Observable<List<CachedValuesHistogram>>>() {
            @Override
            public Observable<List<CachedValuesHistogram>> call(
                Observable<CachedValuesHistogram> windowOf2) {
              return windowOf2.toList();
            }
          };

  protected RollingDistributionStream(
      final HystrixEventStream<Event> stream,
      final int numBuckets,
      final int bucketSizeInMs,
      final Func2<Histogram, Event, Histogram> addValuesToBucket) {
    final List<Histogram> emptyDistributionsToStart = new ArrayList<Histogram>();
    for (int i = 0; i < numBuckets; i++) {
      emptyDistributionsToStart.add(CachedValuesHistogram.getNewHistogram());
    }

    final Func1<Observable<Event>, Observable<Histogram>> reduceBucketToSingleDistribution =
        new Func1<Observable<Event>, Observable<Histogram>>() {
          @Override
          public Observable<Histogram> call(Observable<Event> bucket) {
            return bucket.reduce(CachedValuesHistogram.getNewHistogram(), addValuesToBucket);
          }
        };

    rollingDistributionStream =
        Observable.defer(
                new Func0<Observable<CachedValuesHistogram>>() {
                  @Override
                  public Observable<CachedValuesHistogram> call() {
                    return stream
                        .observe()
                        .window(
                            bucketSizeInMs, TimeUnit.MILLISECONDS) // stream of unaggregated buckets
                        .flatMap(
                            reduceBucketToSingleDistribution) // stream of aggregated Histograms
                        .startWith(
                            emptyDistributionsToStart) // stream of aggregated Histograms that
                        // starts with n empty
                        .window(
                            numBuckets,
                            1) // windowed stream: each OnNext is a stream of n Histograms
                        .flatMap(
                            reduceWindowToSingleDistribution) // reduced stream: each OnNext is a
                        // single Histogram
                        .map(cacheHistogramValues); // convert to CachedValueHistogram
                    // (commonly-accessed values are cached)
                  }
                })
            .share(); // multicast
  }

  public Observable<CachedValuesHistogram> observe() {
    return rollingDistributionStream;
  }

  public int getLatestMean() {
    CachedValuesHistogram latest = getLatest();
    if (latest != null) {
      return latest.getMean();
    } else {
      return 0;
    }
  }

  public int getLatestPercentile(double percentile) {
    CachedValuesHistogram latest = getLatest();
    if (latest != null) {
      return latest.getValueAtPercentile(percentile);
    } else {
      return 0;
    }
  }

  public void startCachingStreamValuesIfUnstarted() {
    if (rollingDistributionSubscription.get() == null) {
      // the stream is not yet started
      Subscription candidateSubscription = observe().subscribe(rollingDistribution);
      if (rollingDistributionSubscription.compareAndSet(null, candidateSubscription)) {
        // won the race to set the subscription
      } else {
        // lost the race to set the subscription, so we need to cancel this one
        candidateSubscription.unsubscribe();
      }
    }
  }

  CachedValuesHistogram getLatest() {
    startCachingStreamValuesIfUnstarted();
    if (rollingDistribution.hasValue()) {
      return rollingDistribution.getValue();
    } else {
      return null;
    }
  }

  public void unsubscribe() {
    Subscription s = rollingDistributionSubscription.get();
    if (s != null) {
      s.unsubscribe();
      rollingDistributionSubscription.compareAndSet(s, null);
    }
  }
}
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    final MainPresenter presenter = getRetentionFragment(savedInstanceState).presenter();

    setContentView(R.layout.main_activity);
    final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.main_activity_recycler_view);
    final LinearLayoutManager layout =
        new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
    recyclerView.setLayoutManager(layout);
    final MainAdapter adapter = new MainAdapter();
    recyclerView.setAdapter(adapter);
    recyclerView.setItemAnimator(new DefaultItemAnimator());

    final BehaviorSubject<Boolean> isLastSubject = BehaviorSubject.create(true);
    recyclerView.setOnScrollListener(
        new RecyclerView.OnScrollListener() {
          boolean manualScrolling = false;

          @Override
          public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
              manualScrolling = true;
            }
            if (manualScrolling && newState == RecyclerView.SCROLL_STATE_IDLE) {
              manualScrolling = false;

              final int lastVisibleItemPosition = layout.findLastVisibleItemPosition();
              final int previousItemsCount = adapter.getItemCount();
              final boolean isLast = previousItemsCount - 1 == lastVisibleItemPosition;
              isLastSubject.onNext(isLast);
            }
          }
        });

    subs =
        new CompositeSubscription(
            isLastSubject.subscribe(presenter.lastItemInViewObserver()),
            presenter
                .itemsWithScrollObservable()
                .subscribe(
                    new Action1<MainPresenter.ItemsWithScroll>() {
                      @Override
                      public void call(final MainPresenter.ItemsWithScroll itemsWithScroll) {
                        adapter.call(itemsWithScroll.items());
                        if (itemsWithScroll.shouldScroll()) {
                          recyclerView.post(
                              new Runnable() {
                                @Override
                                public void run() {
                                  recyclerView.smoothScrollToPosition(
                                      itemsWithScroll.scrollToPosition());
                                }
                              });
                        }
                      }
                    }),
            presenter
                .connectButtonEnabledObservable()
                .subscribe(ViewActions.setEnabled(findViewById(R.id.main_activity_connect_button))),
            presenter
                .disconnectButtonEnabledObservable()
                .subscribe(
                    ViewActions.setEnabled(findViewById(R.id.macin_activity_disconnect_button))),
            presenter
                .sendButtonEnabledObservable()
                .subscribe(ViewActions.setEnabled(findViewById(R.id.main_activity_send_button))),
            ViewObservable.clicks(findViewById(R.id.main_activity_connect_button))
                .subscribe(presenter.connectClickObserver()),
            ViewObservable.clicks(findViewById(R.id.macin_activity_disconnect_button))
                .subscribe(presenter.disconnectClickObserver()),
            ViewObservable.clicks(findViewById(R.id.main_activity_send_button))
                .subscribe(presenter.sendClickObserver()));
  }
Beispiel #15
0
/** Created by Mr.Jude on 2015/6/12. 关于账户的信息。主要用于账户类型:商家/用户,和权限管理。 会发送AccountInfo事件 */
public class AccountModel extends AbsModel {
  private static final String ACCOUNTFILE = "account";

  public static AccountModel getInstance() {
    return getInstance(AccountModel.class);
  }

  public boolean isUser = true;
  public UserAccountData userAccountData;
  public BehaviorSubject<UserAccountData> userAccountDataBehaviorSubject = BehaviorSubject.create();

  @Override
  protected void onAppCreate(Context ctx) {
    super.onAppCreate(ctx);
    Activity a = new Activity();
    Window.Callback callback = a;
    View.OnCreateContextMenuListener menuListener = a;
    ComponentCallbacks2 componentCallbacks = a;

    userAccountData =
        (UserAccountData)
            Utils.readObjectFromFile(
                FileManager.getInstance().getChild(FileManager.Dir.Object, ACCOUNTFILE));
    if (userAccountData != null) {
      applyToken(userAccountData.getTokenApp());
    } else {
      applyToken("");
    }
    updateAccountData();
  }

  public boolean isUser() {
    return isUser;
  }

  /** @return return null if not login */
  public AccountData getAccount() {
    if (isUser) return userAccountData;
    else return null;
  }

  public UserAccountData getUserAccount() {
    return userAccountData;
  }

  public void updateAccountData() {
    RequestManager.getInstance()
        .post(
            API.URL.GetUserData,
            null,
            new DataCallback<UserAccountData>() {
              @Override
              public void success(String info, UserAccountData data) {
                setUserAccountData(data);
              }
            });
  }

  public Subscription registerUserAccountUpdate(Action1<UserAccountData> user) {
    return userAccountDataBehaviorSubject.subscribe(user);
  }

  public void setUserAccountData(UserAccountData userAccountData) {
    isUser = true;
    this.userAccountData = userAccountData;
    saveAccount();
    applyToken(userAccountData.getTokenApp());
    userAccountDataBehaviorSubject.onNext(userAccountData);
    Utils.Log("f**k");
  }

  public void UserLoginOut() {
    this.userAccountData = null;
    FileManager.getInstance().getChild(FileManager.Dir.Object, ACCOUNTFILE).delete();
    applyToken("");
    RongYunModel.getInstance().loginOut();
    userAccountDataBehaviorSubject.onNext(null);
  }

  public void LoginOut() {
    if (isUser()) UserLoginOut();
  }

  public void saveAccount() {
    if (isUser) {
      Utils.writeObjectToFile(
          userAccountData, FileManager.getInstance().getChild(FileManager.Dir.Object, ACCOUNTFILE));
    } else {

    }
  }

  private void applyToken(String token) {
    HashMap<String, String> map = new HashMap();
    JSONObject json = new JSONObject();
    try {
      json.put("token", token);
      json.put("type", "android");
      json.put("version", Utils.getAppVersionCode() + "");
    } catch (JSONException e) {
      e.printStackTrace();
    }
    map.put("token", json.toString());
    RequestManager.getInstance().setHeader(map);
    Utils.Log("setToken:" + json.toString());
  }

  public void userRegister(
      String name, String tel, String password, String verify, StatusCallback callback) {
    RequestMap params = new RequestMap();
    params.put("name", name);
    params.put("tel", tel);
    params.put("pass", Utils.MD5(password.getBytes()));
    params.put("code", verify);
    RequestManager.getInstance().post(API.URL.Register, params, callback);
  }

  public void isRegistered(String tel, StatusCallback callback) {
    RequestMap params = new RequestMap();
    params.put("tel", tel);
    RequestManager.getInstance().post(API.URL.IsRegistered, params, callback);
  }

  public void userLogin(String tel, String password, StatusCallback callback) {
    RequestMap params = new RequestMap();
    params.put("tel", tel);
    params.put("pass", Utils.MD5(password.getBytes()));
    RequestManager.getInstance()
        .post(
            API.URL.Login,
            params,
            callback.add(
                new DataCallback<UserAccountData>() {
                  @Override
                  public void success(String info, UserAccountData data) {
                    setUserAccountData(data);
                  }

                  @Override
                  public void error(String errorInfo) {}
                }));
  }

  public void userLoginThroughQQ(String openId, String face, String name, StatusCallback callback) {
    RequestMap params = new RequestMap();
    params.put("type", "0");
    params.put("openId", openId);
    params.put("face", face);
    params.put("name", name);
    RequestManager.getInstance()
        .post(
            API.URL.ThirdLogin,
            params,
            callback.add(
                new DataCallback<UserAccountData>() {
                  @Override
                  public void success(String info, UserAccountData data) {
                    setUserAccountData(data);
                  }

                  @Override
                  public void error(String errorInfo) {}
                }));
  }

  public void userLoginThroughSina(
      String openId, String face, String name, StatusCallback callback) {
    RequestMap params = new RequestMap();
    params.put("type", "1");
    params.put("openId", openId);
    params.put("face", face);
    params.put("name", name);
    RequestManager.getInstance()
        .post(
            API.URL.ThirdLogin,
            params,
            callback.add(
                new DataCallback<UserAccountData>() {
                  @Override
                  public void success(String info, UserAccountData data) {
                    setUserAccountData(data);
                  }

                  @Override
                  public void error(String errorInfo) {}
                }));
  }

  public void userLoginThroughWeChat(
      String openId, String face, String name, StatusCallback callback) {
    RequestMap params = new RequestMap();
    params.put("type", "2");
    params.put("openId", openId);
    params.put("face", face);
    params.put("name", name);
    RequestManager.getInstance()
        .post(
            API.URL.ThirdLogin,
            params,
            callback.add(
                new DataCallback<UserAccountData>() {
                  @Override
                  public void success(String info, UserAccountData data) {
                    setUserAccountData(data);
                  }

                  @Override
                  public void error(String errorInfo) {}
                }));
  }
  /**
   * 修改密码
   *
   * @param tel
   * @param password
   * @param verify
   * @param callback
   */
  public void modifyPassword(String tel, String password, String verify, StatusCallback callback) {
    RequestMap params = new RequestMap();
    params.put("tel", tel);
    params.put("newP", Utils.MD5(password.getBytes()));
    params.put("code", verify);
    RequestManager.getInstance().post(API.URL.ModifyPassword, params, callback);
  }

  /**
   * 绑定手机
   *
   * @param
   * @param code
   */
  public void boundTel(
      String oldTel,
      String newTel,
      String oldPassword,
      String newPassword,
      String code,
      StatusCallback callback) {
    RequestMap params = new RequestMap();
    params.put("oldTel", oldTel);
    params.put("newTel", newTel);
    params.put("oldPassword", Utils.MD5(oldPassword.getBytes()));
    params.put("newPassword", Utils.MD5(newPassword.getBytes()));
    params.put("code", code);
    RequestManager.getInstance().post(API.URL.BindTel, params, callback);
  }

  /**
   * 商家登录
   *
   * @param email
   * @param password
   * @param callback
   */
  public void bizLogin(String email, String password, DataCallback callback) {
    RequestMap param = new RequestMap();
    param.put("email", email);
    param.put("pass", password);
    RequestManager.getInstance().post(API.URL.BizLogin, param, callback);
  }
}
 @Inject
 public AuthDataRepository(AuthApi authApi, AccessTokenMapper accessTokenMapper) {
   this.authApi = authApi;
   this.accessTokenMapper = accessTokenMapper;
   this.accessTokenSubject = BehaviorSubject.create();
 }
/**
 * This is an extension of {@link Presenter} which provides RxJava functionality.
 *
 * @param <V> a type of view.
 */
public class RxPresenter<V> extends Presenter<V> {

  private static final String REQUESTED_KEY = RxPresenter.class.getName() + "#requested";

  private final BehaviorSubject<V> views = BehaviorSubject.create();
  private final SubscriptionList subscriptions = new SubscriptionList();

  private final HashMap<Integer, Func0<Subscription>> restartables = new HashMap<>();
  private final HashMap<Integer, Subscription> restartableSubscriptions = new HashMap<>();
  private final ArrayList<Integer> requested = new ArrayList<>();

  /** {@inheritDoc} */
  @CallSuper
  @Override
  protected void onCreate(Bundle savedState) {
    if (savedState != null) requested.addAll(savedState.getIntegerArrayList(REQUESTED_KEY));
  }

  /** {@inheritDoc} */
  @CallSuper
  @Override
  protected void onTakeView(V view) {
    views.onNext(view);
  }

  /** {@inheritDoc} */
  @CallSuper
  @Override
  protected void onSave(Bundle state) {
    for (int i = requested.size() - 1; i >= 0; i--) {
      int restartableId = requested.get(i);
      Subscription subscription = restartableSubscriptions.get(restartableId);
      if (subscription != null && subscription.isUnsubscribed()) requested.remove(i);
    }
    state.putIntegerArrayList(REQUESTED_KEY, requested);
  }

  /** {@inheritDoc} */
  @CallSuper
  @Override
  protected void onDropView() {
    views.onNext(null);
  }

  /** {@inheritDoc} */
  @CallSuper
  @Override
  protected void onDestroy() {
    views.onCompleted();
    subscriptions.unsubscribe();
    for (Map.Entry<Integer, Subscription> entry : restartableSubscriptions.entrySet())
      entry.getValue().unsubscribe();
  }

  /**
   * Returns an {@link Observable} that emits the current attached view or null. See {@link
   * BehaviorSubject} for more information.
   *
   * @return an observable that emits the current attached view or null.
   */
  public Observable<V> view() {
    return views;
  }

  /**
   * Registers a subscription to automatically unsubscribe it during onDestroy. See {@link
   * SubscriptionList#add(Subscription) for details.}
   *
   * @param subscription a subscription to add.
   */
  public void add(Subscription subscription) {
    subscriptions.add(subscription);
  }

  /**
   * Removes and unsubscribes a subscription that has been registered with {@link #add} previously.
   * See {@link SubscriptionList#remove(Subscription)} for details.
   *
   * @param subscription a subscription to remove.
   */
  public void remove(Subscription subscription) {
    subscriptions.remove(subscription);
  }

  /**
   * A restartable is any RxJava observable that can be started (subscribed) and should be
   * automatically restarted (re-subscribed) after a process restart if it was still subscribed at
   * the moment of saving presenter's state.
   *
   * <p>Registers a factory. Re-subscribes the restartable after the process restart.
   *
   * @param restartableId id of the restartable
   * @param factory factory of the restartable
   */
  public void restartable(int restartableId, Func0<Subscription> factory) {
    restartables.put(restartableId, factory);
    if (requested.contains(restartableId)) start(restartableId);
  }

  /**
   * Starts the given restartable.
   *
   * @param restartableId id of the restartable
   */
  public void start(int restartableId) {
    stop(restartableId);
    requested.add(restartableId);
    restartableSubscriptions.put(restartableId, restartables.get(restartableId).call());
  }

  /**
   * Unsubscribes a restartable
   *
   * @param restartableId id of a restartable.
   */
  public void stop(int restartableId) {
    requested.remove((Integer) restartableId);
    Subscription subscription = restartableSubscriptions.get(restartableId);
    if (subscription != null) subscription.unsubscribe();
  }

  /**
   * Checks if a restartable is unsubscribed.
   *
   * @param restartableId id of the restartable.
   * @return true if the subscription is null or unsubscribed, false otherwise.
   */
  public boolean isUnsubscribed(int restartableId) {
    Subscription subscription = restartableSubscriptions.get(restartableId);
    return subscription == null || subscription.isUnsubscribed();
  }

  /**
   * This is a shortcut that can be used instead of combining together {@link #restartable(int,
   * Func0)}, {@link #deliverFirst()}, {@link #split(Action2, Action2)}.
   *
   * @param restartableId an id of the restartable.
   * @param observableFactory a factory that should return an Observable when the restartable should
   *     run.
   * @param onNext a callback that will be called when received data should be delivered to view.
   * @param onError a callback that will be called if the source observable emits onError.
   * @param <T> the type of the observable.
   */
  public <T> void restartableFirst(
      int restartableId,
      final Func0<Observable<T>> observableFactory,
      final Action2<V, T> onNext,
      @Nullable final Action2<V, Throwable> onError) {

    restartable(
        restartableId,
        new Func0<Subscription>() {
          @Override
          public Subscription call() {
            return observableFactory
                .call()
                .compose(RxPresenter.this.<T>deliverFirst())
                .subscribe(split(onNext, onError));
          }
        });
  }

  /**
   * This is a shortcut for calling {@link #restartableFirst(int, Func0, Action2, Action2)} with the
   * last parameter = null.
   */
  public <T> void restartableFirst(
      int restartableId, final Func0<Observable<T>> observableFactory, final Action2<V, T> onNext) {
    restartableFirst(restartableId, observableFactory, onNext, null);
  }

  /**
   * This is a shortcut that can be used instead of combining together {@link #restartable(int,
   * Func0)}, {@link #deliverLatestCache()}, {@link #split(Action2, Action2)}.
   *
   * @param restartableId an id of the restartable.
   * @param observableFactory a factory that should return an Observable when the restartable should
   *     run.
   * @param onNext a callback that will be called when received data should be delivered to view.
   * @param onError a callback that will be called if the source observable emits onError.
   * @param <T> the type of the observable.
   */
  public <T> void restartableLatestCache(
      int restartableId,
      final Func0<Observable<T>> observableFactory,
      final Action2<V, T> onNext,
      @Nullable final Action2<V, Throwable> onError) {

    restartable(
        restartableId,
        new Func0<Subscription>() {
          @Override
          public Subscription call() {
            return observableFactory
                .call()
                .compose(RxPresenter.this.<T>deliverLatestCache())
                .subscribe(split(onNext, onError));
          }
        });
  }

  /**
   * This is a shortcut for calling {@link #restartableLatestCache(int, Func0, Action2, Action2)}
   * with the last parameter = null.
   */
  public <T> void restartableLatestCache(
      int restartableId, final Func0<Observable<T>> observableFactory, final Action2<V, T> onNext) {
    restartableLatestCache(restartableId, observableFactory, onNext, null);
  }

  /**
   * This is a shortcut that can be used instead of combining together {@link #restartable(int,
   * Func0)}, {@link #deliverReplay()}, {@link #split(Action2, Action2)}.
   *
   * @param restartableId an id of the restartable.
   * @param observableFactory a factory that should return an Observable when the restartable should
   *     run.
   * @param onNext a callback that will be called when received data should be delivered to view.
   * @param onError a callback that will be called if the source observable emits onError.
   * @param <T> the type of the observable.
   */
  public <T> void restartableReplay(
      int restartableId,
      final Func0<Observable<T>> observableFactory,
      final Action2<V, T> onNext,
      @Nullable final Action2<V, Throwable> onError) {

    restartable(
        restartableId,
        new Func0<Subscription>() {
          @Override
          public Subscription call() {
            return observableFactory
                .call()
                .compose(RxPresenter.this.<T>deliverReplay())
                .subscribe(split(onNext, onError));
          }
        });
  }

  /**
   * This is a shortcut for calling {@link #restartableReplay(int, Func0, Action2, Action2)} with
   * the last parameter = null.
   */
  public <T> void restartableReplay(
      int restartableId, final Func0<Observable<T>> observableFactory, final Action2<V, T> onNext) {
    restartableReplay(restartableId, observableFactory, onNext, null);
  }

  /**
   * Returns an {@link Observable.Transformer} that couples views with data that has been emitted by
   * the source {@link Observable}.
   *
   * <p>{@link #deliverLatestCache} keeps the latest onNext value and emits it each time a new view
   * gets attached. If a new onNext value appears while a view is attached, it will be delivered
   * immediately.
   *
   * @param <T> the type of source observable emissions
   */
  public <T> DeliverLatestCache<V, T> deliverLatestCache() {
    return new DeliverLatestCache<>(views);
  }

  /**
   * Returns an {@link Observable.Transformer} that couples views with data that has been emitted by
   * the source {@link Observable}.
   *
   * <p>{@link #deliverFirst} delivers only the first onNext value that has been emitted by the
   * source observable.
   *
   * @param <T> the type of source observable emissions
   */
  public <T> DeliverFirst<V, T> deliverFirst() {
    return new DeliverFirst<>(views);
  }

  /**
   * Returns an {@link Observable.Transformer} that couples views with data that has been emitted by
   * the source {@link Observable}.
   *
   * <p>{@link #deliverReplay} keeps all onNext values and emits them each time a new view gets
   * attached. If a new onNext value appears while a view is attached, it will be delivered
   * immediately.
   *
   * @param <T> the type of source observable emissions
   */
  public <T> DeliverReplay<V, T> deliverReplay() {
    return new DeliverReplay<>(views);
  }

  /**
   * Returns a method that can be used for manual restartable chain build. It returns an Action1
   * that splits a received {@link Delivery} into two {@link Action2} onNext and onError calls.
   *
   * @param onNext a method that will be called if the delivery contains an emitted onNext value.
   * @param onError a method that will be called if the delivery contains an onError throwable.
   * @param <T> a type on onNext value.
   * @return an Action1 that splits a received {@link Delivery} into two {@link Action2} onNext and
   *     onError calls.
   */
  public <T> Action1<Delivery<V, T>> split(
      final Action2<V, T> onNext, @Nullable final Action2<V, Throwable> onError) {
    return new Action1<Delivery<V, T>>() {
      @Override
      public void call(Delivery<V, T> delivery) {
        delivery.split(onNext, onError);
      }
    };
  }

  /**
   * This is a shortcut for calling {@link #split(Action2, Action2)} when the second parameter is
   * null.
   */
  public <T> Action1<Delivery<V, T>> split(Action2<V, T> onNext) {
    return split(onNext, null);
  }
}
/** Fragment which exposes useful lifecycle callbacks as an {@link rx.Observable} */
abstract class RxFragment extends Fragment {

  private final BehaviorSubject<FragmentEvent> lifecycleSubject = BehaviorSubject.create();

  public Observable<FragmentEvent> lifecycle() {
    return lifecycleSubject.asObservable();
  }

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    lifecycleSubject.onNext(FragmentEvent.CREATE);
  }

  @SuppressWarnings("deprecation")
  @Override
  public void onAttach(android.app.Activity activity) {
    super.onAttach(activity);
    lifecycleSubject.onNext(FragmentEvent.ATTACH);
  }

  @Override
  public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    lifecycleSubject.onNext(FragmentEvent.CREATE_VIEW);
  }

  @Override
  public void onStart() {
    super.onStart();
    lifecycleSubject.onNext(FragmentEvent.START);
  }

  @Override
  public void onResume() {
    super.onResume();
    lifecycleSubject.onNext(FragmentEvent.RESUME);
  }

  @Override
  public void onPause() {
    lifecycleSubject.onNext(FragmentEvent.PAUSE);
    super.onPause();
  }

  @Override
  public void onStop() {
    lifecycleSubject.onNext(FragmentEvent.STOP);
    super.onStop();
  }

  @Override
  public void onDestroyView() {
    lifecycleSubject.onNext(FragmentEvent.DESTROY_VIEW);
    super.onDestroyView();
  }

  @Override
  public void onDestroy() {
    lifecycleSubject.onNext(FragmentEvent.DESTROY);
    super.onDestroy();
  }

  @Override
  public void onDetach() {
    lifecycleSubject.onNext(FragmentEvent.DETACH);
    super.onDetach();
  }
}
/**
 * A list fragment representing a list of Candidates. This fragment also supports tablet devices by
 * allowing list items to be given an 'activated' state upon selection. This helps indicate which
 * item is currently being viewed in a {@link CandidateDetailFragment}.
 *
 * <p>Activities containing this fragment MUST implement the {@link Callbacks} interface.
 */
public class CandidateListFragment extends ListFragment {

  /**
   * The serialization (saved instance state) Bundle key representing the activated item position.
   * Only used on tablets.
   */
  private static final String STATE_ACTIVATED_POSITION = "activated_position";

  /** The fragment's current callback object, which is notified of list item clicks. */
  private Callbacks mCallbacks = sDummyCallbacks;

  /** The current activated item position. Only used on tablets. */
  private int mActivatedPosition = ListView.INVALID_POSITION;

  private BehaviorSubject<FragmentEvent> lifecycle$ = BehaviorSubject.create();
  private BehaviorSubject<Integer> selectedItemPos$ = BehaviorSubject.create();

  /**
   * A callback interface that all activities containing this fragment must implement. This
   * mechanism allows activities to be notified of item selections.
   */
  public interface Callbacks {
    /** Callback for when an item has been selected. */
    public void onItemSelected(String pos);
  }

  /**
   * A dummy implementation of the {@link Callbacks} interface that does nothing. Used only when
   * this fragment is not attached to an activity.
   */
  private static Callbacks sDummyCallbacks =
      new Callbacks() {
        @Override
        public void onItemSelected(String id) {}
      };

  /**
   * Mandatory empty constructor for the fragment manager to instantiate the fragment (e.g. upon
   * screen orientation changes).
   */
  public CandidateListFragment() {}

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    Observable<List<CandidateBuzz>> candidateBuzzes$ = App.i().streams.candidateBuzzes$;
    CandidateBuzzAdapter adapter =
        new CandidateBuzzAdapter(getActivity(), R.layout.candidate_list_item);
    setListAdapter(adapter);
    syncListAdapter(candidateBuzzes$, adapter);

    lifecycle$.onNext(FragmentEvent.CREATE);
  }

  private void syncListAdapter(
      Observable<List<CandidateBuzz>> candidateBuzzes$, final CandidateBuzzAdapter adapter) {
    Observable<List<CandidateBuzz>> composed =
        candidateBuzzes$
            .observeOn(AndroidSchedulers.mainThread())
            .compose(RxLifecycle.bindFragment(lifecycle$));
    composed.subscribe(
        candidateBuzzes -> {
          adapter.clear();
          adapter.addAll(candidateBuzzes);
        });
  }

  @Override
  public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    // Restore the previously serialized activated item position.
    if (savedInstanceState != null && savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) {
      setActivatedPosition(savedInstanceState.getInt(STATE_ACTIVATED_POSITION));
    }

    lifecycle$.onNext(FragmentEvent.CREATE_VIEW);
  }

  @Override
  public void onAttach(Activity activity) {
    super.onAttach(activity);

    // Activities containing this fragment must implement its callbacks.
    if (!(activity instanceof Callbacks)) {
      throw new IllegalStateException("Activity must implement fragment's callbacks.");
    }

    mCallbacks = (Callbacks) activity;

    lifecycle$.onNext(FragmentEvent.ATTACH);
  }

  @Override
  public void onResume() {
    super.onResume();
    getListView().setDivider(null);
    App.i().loadCandidateBuzzes();

    lifecycle$.onNext(FragmentEvent.RESUME);
  }

  @Override
  public void onDetach() {
    super.onDetach();

    // Reset the active callbacks interface to the dummy implementation.
    mCallbacks = sDummyCallbacks;

    lifecycle$.onNext(FragmentEvent.DETACH);
  }

  @Override
  public void onDestroyView() {
    super.onDestroyView();
    lifecycle$.onNext(FragmentEvent.DESTROY_VIEW);
  }

  @Override
  public void onDestroy() {
    super.onDestroy();
    lifecycle$.onNext(FragmentEvent.DESTROY);
  }

  @Override
  public void onListItemClick(ListView listView, View view, int position, long id) {
    super.onListItemClick(listView, view, position, id);
    mCallbacks.onItemSelected(((CandidateBuzz) getListAdapter().getItem(position)).candidate.id);
  }

  @Override
  public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    if (mActivatedPosition != ListView.INVALID_POSITION) {
      // Serialize and persist the activated item position.
      outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
    }
  }

  /**
   * Turns on activate-on-click mode. When this mode is on, list items will be given the 'activated'
   * state when touched.
   */
  public void setActivateOnItemClick(boolean activateOnItemClick) {
    // When setting CHOICE_MODE_SINGLE, ListView will automatically
    // give items the 'activated' state when touched.
    getListView()
        .setChoiceMode(
            activateOnItemClick ? ListView.CHOICE_MODE_SINGLE : ListView.CHOICE_MODE_NONE);
  }

  private void setActivatedPosition(int position) {
    if (position == ListView.INVALID_POSITION) {
      getListView().setItemChecked(mActivatedPosition, false);
    } else {
      getListView().setItemChecked(position, true);
    }

    mActivatedPosition = position;
  }

  private static class CandidateBuzzAdapter extends ArrayAdapter<CandidateBuzz> {
    private final int layoutId;

    public CandidateBuzzAdapter(Context ctx, int layoutId) {
      super(ctx, layoutId);
      this.layoutId = layoutId;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
      if (convertView == null)
        convertView = LayoutInflater.from(getContext()).inflate(this.layoutId, parent, false);

      CandidateBuzz item = getItem(position);
      ((TextView) convertView.findViewById(R.id.candidate_name)).setText(item.candidate.name);
      ((TextView) convertView.findViewById(R.id.candidate_location))
          .setText(item.candidate.province);
      ((TextView) convertView.findViewById(R.id.candidate_buzz)).setText(item.buzz + " buzz");
      Picasso.with(getContext())
          .load(item.candidate.profpic)
          .fit()
          .centerCrop()
          .into((ImageView) convertView.findViewById(R.id.candidate_img));

      return convertView;
    }
  }
}
public class RxPresenter<View> extends Presenter<View> {
  private static final String REQUESTED_KEY = RxPresenter.class.getName() + "#requested";
  private final ArrayList<Integer> requested = new ArrayList();
  private final HashMap<Integer, Subscription> restartableSubscriptions = new HashMap();
  private final HashMap<Integer, Func0<Subscription>> restartables = new HashMap();
  private final SubscriptionList subscriptions = new SubscriptionList();
  private final BehaviorSubject<View> views = BehaviorSubject.create();

  public void add(Subscription paramSubscription) {
    this.subscriptions.add(paramSubscription);
  }

  public <T> DeliverFirst<View, T> deliverFirst() {
    return new DeliverFirst(this.views);
  }

  public <T> DeliverLatestCache<View, T> deliverLatestCache() {
    return new DeliverLatestCache(this.views);
  }

  public <T> DeliverReplay<View, T> deliverReplay() {
    return new DeliverReplay(this.views);
  }

  @CallSuper
  protected void onCreate(Bundle paramBundle) {
    if (paramBundle != null) {
      this.requested.addAll(paramBundle.getIntegerArrayList(REQUESTED_KEY));
    }
  }

  @CallSuper
  protected void onDestroy() {
    super.onDestroy();
    this.views.onCompleted();
    this.subscriptions.unsubscribe();
    Iterator localIterator = this.restartableSubscriptions.entrySet().iterator();
    while (localIterator.hasNext()) {
      ((Subscription) ((Map.Entry) localIterator.next()).getValue()).unsubscribe();
    }
  }

  @CallSuper
  protected void onDropView() {
    super.onDropView();
    this.views.onNext(null);
  }

  @CallSuper
  protected void onSave(Bundle paramBundle) {
    super.onSave(paramBundle);
    int i = this.requested.size() - 1;
    while (i >= 0) {
      int j = ((Integer) this.requested.get(i)).intValue();
      Subscription localSubscription =
          (Subscription) this.restartableSubscriptions.get(Integer.valueOf(j));
      if ((localSubscription != null) && (localSubscription.isUnsubscribed())) {
        this.requested.remove(i);
      }
      i -= 1;
    }
    paramBundle.putIntegerArrayList(REQUESTED_KEY, this.requested);
  }

  @CallSuper
  protected void onTakeView(View paramView) {
    super.onTakeView(paramView);
    this.views.onNext(paramView);
  }

  public void remove(Subscription paramSubscription) {
    this.subscriptions.remove(paramSubscription);
  }

  public void restartable(int paramInt, Func0<Subscription> paramFunc0) {
    this.restartables.put(Integer.valueOf(paramInt), paramFunc0);
    if (this.requested.contains(Integer.valueOf(paramInt))) {
      start(paramInt);
    }
  }

  public <T> void restartableFirst(
      int paramInt, Func0<Observable<T>> paramFunc0, Action2<View, T> paramAction2) {
    restartableFirst(paramInt, paramFunc0, paramAction2, null);
  }

  public <T> void restartableFirst(
      int paramInt,
      final Func0<Observable<T>> paramFunc0,
      final Action2<View, T> paramAction2,
      @Nullable final Action2<View, Throwable> paramAction21) {
    restartable(
        paramInt,
        new Func0() {
          public Subscription call() {
            return ((Observable) paramFunc0.call())
                .compose(RxPresenter.this.deliverFirst())
                .subscribe(RxPresenter.this.split(paramAction2, paramAction21));
          }
        });
  }

  public <T> void restartableLatestCache(
      int paramInt, Func0<Observable<T>> paramFunc0, Action2<View, T> paramAction2) {
    restartableLatestCache(paramInt, paramFunc0, paramAction2, null);
  }

  public <T> void restartableLatestCache(
      int paramInt,
      final Func0<Observable<T>> paramFunc0,
      final Action2<View, T> paramAction2,
      @Nullable final Action2<View, Throwable> paramAction21) {
    restartable(
        paramInt,
        new Func0() {
          public Subscription call() {
            return ((Observable) paramFunc0.call())
                .compose(RxPresenter.this.deliverLatestCache())
                .subscribe(RxPresenter.this.split(paramAction2, paramAction21));
          }
        });
  }

  public <T> void restartableReplay(
      int paramInt, Func0<Observable<T>> paramFunc0, Action2<View, T> paramAction2) {
    restartableReplay(paramInt, paramFunc0, paramAction2, null);
  }

  public <T> void restartableReplay(
      int paramInt,
      final Func0<Observable<T>> paramFunc0,
      final Action2<View, T> paramAction2,
      @Nullable final Action2<View, Throwable> paramAction21) {
    restartable(
        paramInt,
        new Func0() {
          public Subscription call() {
            return ((Observable) paramFunc0.call())
                .compose(RxPresenter.this.deliverReplay())
                .subscribe(RxPresenter.this.split(paramAction2, paramAction21));
          }
        });
  }

  public <T> Action1<Delivery<View, T>> split(Action2<View, T> paramAction2) {
    return split(paramAction2, null);
  }

  public <T> Action1<Delivery<View, T>> split(
      final Action2<View, T> paramAction2, @Nullable final Action2<View, Throwable> paramAction21) {
    new Action1() {
      public void call(Delivery<View, T> paramAnonymousDelivery) {
        paramAnonymousDelivery.split(paramAction2, paramAction21);
      }
    };
  }

  public void start(int paramInt) {
    stop(paramInt);
    this.requested.add(Integer.valueOf(paramInt));
    this.restartableSubscriptions.put(
        Integer.valueOf(paramInt),
        ((Func0) this.restartables.get(Integer.valueOf(paramInt))).call());
  }

  public void stop(int paramInt) {
    this.requested.remove(Integer.valueOf(paramInt));
    Subscription localSubscription =
        (Subscription) this.restartableSubscriptions.get(Integer.valueOf(paramInt));
    if (localSubscription != null) {
      localSubscription.unsubscribe();
    }
  }

  public Observable<View> view() {
    return this.views;
  }
}
public class MainActivity extends RxAppCompatActivity
    implements SwipeRefreshLayout.OnRefreshListener {

  private static final String REDDIT_BASE_URL = "http://www.reddit.com";

  @InjectView(R.id.toolbar)
  Toolbar mToolbar;

  @InjectView(R.id.swipe)
  SwipeRefreshLayout mSwipeRefreshLayout;

  @InjectView(R.id.list)
  RecyclerView mRecyclerView;

  private final BehaviorSubject<String> mFetchAfterSubject = BehaviorSubject.create();

  private RecyclerView.OnScrollListener mOnScrollListener =
      new RecyclerView.OnScrollListener() {

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
          super.onScrolled(recyclerView, dx, dy);
          int itemCount = mLayoutManager.getItemCount();
          int lastVisibleItemPosition = mLayoutManager.findLastVisibleItemPosition();
          if (lastVisibleItemPosition == itemCount - 1 && PhotosValidator.hasAfter(mPhotos)) {
            mFetchAfterSubject.onNext(mPhotos.getAfter());
          }
        }
      };

  RedditPhotoService mPhotoService;
  Photos mPhotos;
  PhotosAdapter mPhotosAdapter;
  LinearLayoutManager mLayoutManager;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // Init UI components
    ButterKnife.inject(this);
    setSupportActionBar(mToolbar);
    mLayoutManager = new LinearLayoutManager(this);
    mRecyclerView.setLayoutManager(mLayoutManager);
    mRecyclerView.addOnScrollListener(mOnScrollListener);
    mSwipeRefreshLayout.setOnRefreshListener(this);
    mSwipeRefreshLayout.setColorSchemeColors(
        getResources().getColor(R.color.accent), getResources().getColor(R.color.flipagram_red));

    initServices();
    initData();
  }

  @Override
  protected void onDestroy() {
    super.onDestroy();
    mRecyclerView.removeOnScrollListener(mOnScrollListener);
  }

  @Override
  public void onRefresh() {
    initData();
  }

  private void initData() {
    // Initial load
    mPhotoService
        .getHotPhotos(null)
        .doOnNext(__ -> runOnUiThread(() -> mSwipeRefreshLayout.setRefreshing(true)))
        .compose(bindToLifecycle())
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(this::bind, this::onErrorDownloading);

    // Observable for loading more pages
    mFetchAfterSubject
        .filter(after -> !StringUtils.isNullOrEmpty(after))
        .distinctUntilChanged()
        .switchMap(mPhotoService::getHotPhotos)
        .doOnNext(__ -> runOnUiThread(() -> mSwipeRefreshLayout.setRefreshing(true)))
        .compose(bindUntilEvent(ActivityEvent.DESTROY))
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(this::merge, this::onErrorDownloading);
  }

  private void merge(Photos photos) {
    mSwipeRefreshLayout.setRefreshing(false);
    mPhotos.mergeWith(photos);
    mPhotosAdapter.notifyDataSetChanged();
  }

  private void onErrorDownloading(Throwable throwable) {
    mSwipeRefreshLayout.setRefreshing(false);
    Toast.makeText(this, throwable.getMessage(), Toast.LENGTH_SHORT).show();
  }

  private void initServices() {
    GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.registerTypeAdapter(Photos.class, new PhotosDeserializer());

    RestAdapter restAdapter =
        new RestAdapter.Builder()
            .setEndpoint(REDDIT_BASE_URL)
            .setConverter(new GsonConverter(gsonBuilder.create()))
            .build();

    mPhotoService = restAdapter.create(RedditPhotoService.class);
  }

  private void bind(Photos photos) {
    mSwipeRefreshLayout.setRefreshing(false);
    mPhotos = photos;
    mPhotosAdapter = new PhotosAdapter(mPhotos.getPhotos());
    mRecyclerView.setAdapter(mPhotosAdapter);

    Observable.merge(
            mPhotosAdapter
                .getThumbnailClickedSubject()
                .filter(PhotosValidator::isSourceValid)
                .map(Photo::getSourceUrl),
            mPhotosAdapter
                .getItemClickedSubject()
                .filter(photo -> StringUtils.isValidUrl(photo.getPermalink()))
                .map(Photo::getPermalink))
        .compose(bindUntilEvent(ActivityEvent.DESTROY))
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(OnlyNextObserver.forAction(this::view));
  }

  private void view(String url) {
    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.setData(Uri.parse(url));
    startActivity(intent);
  }
}
Beispiel #22
0
/** This fragment shows the content of one post. */
public class PostFragment extends BaseFragment
    implements NewTagDialogFragment.OnAddNewTagsListener,
        CommentsAdapter.CommentActionListener,
        InfoLineView.OnDetailClickedListener {

  private static final String ARG_FEED_ITEM = "PostFragment.post";
  private static final String ARG_COMMENT_DRAFT = "PostFragment.comment-draft";

  private FeedItem feedItem;
  private MediaView viewer;

  @Inject FeedService feedService;

  @Inject VoteService voteService;

  @Inject Settings settings;

  @Inject SeenService seenService;

  @Inject DownloadManager downloadManager;

  @Inject SingleShotService singleShotService;

  @Inject InMemoryCacheService inMemoryCacheService;

  @Inject UserService userService;

  @Inject DownloadService downloadService;

  @Inject FavedCommentService favedCommentService;

  @Bind(R.id.refresh)
  SwipeRefreshLayout swipeRefreshLayout;

  @Bind(R.id.player_container)
  ViewGroup playerContainer;

  @Bind(R.id.post_content)
  RecyclerView content;

  @Bind(R.id.vote_indicator)
  TextView voteAnimationIndicator;

  @Bind(R.id.repost_hint)
  View repostHint;

  private InfoLineView infoLineView;

  // start with an empty adapter here
  private MergeRecyclerAdapter adapter;
  private CommentsAdapter commentsAdapter;

  private Optional<Long> autoScrollTo;
  private RecyclerView.OnScrollListener scrollHandler;

  private final LoginActivity.DoIfAuthorizedHelper doIfAuthorizedHelper =
      LoginActivity.helper(this);
  private PreviewInfo previewInfo;

  private List<Tag> tags;
  private List<Comment> comments;
  private boolean rewindOnLoad;
  private boolean tabletLayout;

  private final BehaviorSubject<Boolean> activeStateSubject = BehaviorSubject.create(false);

  @Override
  public void onCreate(Bundle savedState) {
    super.onCreate(savedState);
    setHasOptionsMenu(true);

    // get the item that is to be displayed.
    feedItem = getArguments().getParcelable(ARG_FEED_ITEM);

    if (savedState != null) {
      tags = Parceler.get(TagListParceler.class, savedState, "PostFragment.tags");
      comments = Parceler.get(CommentListParceler.class, savedState, "PostFragment.comments");
    }

    autoScrollTo = Optional.absent();

    activeState()
        .compose(bindToLifecycleForeground())
        .subscribe(
            active -> {
              if (viewer != null) {
                if (active) {
                  viewer.playMedia();
                } else {
                  viewer.stopMedia();
                }
              }

              if (!active) {
                exitFullscreenAnimated(false);
              }
            });
  }

  private Observable<Boolean> activeState() {
    Observable<FragmentEvent> startStopLifecycle =
        lifecycle().filter(ev -> ev == FragmentEvent.START || ev == FragmentEvent.STOP);

    // now combine with the activeStateSubject and return a new observable with
    // the "active state".
    Observable<Boolean> combined =
        combineLatest(
            startStopLifecycle,
            activeStateSubject,
            (ev, active) -> active && ev == FragmentEvent.START);

    return combined.distinctUntilChanged();
  }

  @Override
  protected void injectComponent(ActivityComponent activityComponent) {
    activityComponent.inject(this);
  }

  @Override
  public View onCreateView(
      LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

    ViewGroup view = (ViewGroup) inflater.inflate(R.layout.fragment_post, container, false);
    addWarnOverlayIfNecessary(inflater, view);
    return view;
  }

  @Override
  public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    this.tabletLayout = view.findViewById(R.id.infoview) != null;

    if (!(getActivity() instanceof ToolbarActivity)) {
      throw new IllegalStateException("Fragment must be child of a ToolbarActivity.");
    }

    ToolbarActivity activity = (ToolbarActivity) getActivity();
    activity.getScrollHideToolbarListener().reset();

    int abHeight = AndroidUtility.getActionBarContentOffset(getActivity());
    if (tabletLayout) {
      View tabletLayout = ButterKnife.findById(view, R.id.tabletlayout);
      tabletLayout.setPadding(0, abHeight, 0, 0);

      ((FrameLayout.LayoutParams) voteAnimationIndicator.getLayoutParams()).gravity =
          Gravity.CENTER;

      scrollHandler = new NoopOnScrollListener();
    } else {
      // use height of the toolbar to configure swipe refresh layout.
      swipeRefreshLayout.setProgressViewOffset(false, 0, (int) (1.5 * abHeight));

      scrollHandler = new ScrollHandler(activity);
    }

    content.addOnScrollListener(scrollHandler);

    swipeRefreshLayout.setColorSchemeResources(R.color.primary);
    swipeRefreshLayout.setOnRefreshListener(
        () -> {
          if (!isVideoFullScreen()) {
            rewindOnLoad = true;
            loadPostDetails();
          }
        });

    swipeRefreshLayout.setKeepScreenOn(settings.keepScreenOn());

    adapter = new MergeRecyclerAdapter();
    content.setItemAnimator(null);
    content.setLayoutManager(new LinearLayoutManager(getActivity()));
    content.setAdapter(adapter);

    initializeMediaView();
    initializeInfoLine();
    initializeCommentPostLine();

    commentsAdapter = new CommentsAdapter(userService.getName().or(""));
    commentsAdapter.setCommentActionListener(this);
    commentsAdapter.setPrioritizeOpComments(settings.prioritizeOpComments());
    commentsAdapter.setShowFavCommentButton(userService.isAuthorized());
    adapter.addAdapter(commentsAdapter);

    // restore the postInfo, if possible.
    if (tags != null && comments != null) {
      displayTags(tags);
      displayComments(comments);
    }

    loadPostDetails();

    // show the repost badge if this is a repost
    repostHint.setVisibility(isRepost() ? View.VISIBLE : View.GONE);
  }

  private boolean isRepost() {
    return inMemoryCacheService.isRepost(feedItem);
  }

  @Override
  public void onDestroyView() {
    content.removeOnScrollListener(scrollHandler);

    // restore orientation if the user closes this view
    Screen.unlockOrientation(getActivity());

    adapter = null;

    super.onDestroyView();
  }

  @Override
  public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putParcelable("PostFragment.tags", new TagListParceler(tags));
    outState.putParcelable("PostFragment.comments", new CommentListParceler(comments));
  }

  private void addWarnOverlayIfNecessary(LayoutInflater inflater, ViewGroup view) {
    // add a view over the main view, if the post is not visible now
    if (!settings.getContentType().contains(feedItem.getContentType())) {
      View overlay = inflater.inflate(R.layout.warn_post_can_not_be_viewed, view, false);
      view.addView(overlay);

      // link the hide button
      View button = overlay.findViewById(R.id.hide_warning_button);
      button.setOnClickListener(v -> AndroidUtility.removeView(overlay));
    }
  }

  @Override
  public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    doIfAuthorizedHelper.onActivityResult(requestCode, resultCode);

    if (requestCode == RequestCodes.WRITE_COMMENT && resultCode == Activity.RESULT_OK) {
      onNewComments(WriteMessageActivity.getNewComment(data));
    }
  }

  public void setPreviewInfo(PreviewInfo previewInfo) {
    this.previewInfo = previewInfo;
  }

  private void initializeCommentPostLine() {
    ViewGroup.MarginLayoutParams layoutParams =
        new ViewGroup.MarginLayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);

    CommentPostLine line = new CommentPostLine(getActivity());
    line.setLayoutParams(layoutParams);
    adapter.addAdapter(SingleViewAdapter.ofView(line));

    line.setCommentDraft(getArguments().getString(ARG_COMMENT_DRAFT, ""));
    line.textChanges().subscribe(text -> getArguments().putString(ARG_COMMENT_DRAFT, text));

    line.comments()
        .subscribe(
            text -> {
              Runnable action =
                  () -> {
                    line.clear();
                    writeComment(text);
                  };

              doIfAuthorizedHelper.run(action, action);
            });
  }

  private void writeComment(String text) {
    voteService
        .postComment(feedItem, 0, text)
        .compose(bindToLifecycle())
        .lift(busyDialog(this))
        .subscribe(this::onNewComments, defaultOnError());
  }

  /**
   * Scroll the th given comment
   *
   * @param commentId The comment id to scroll to
   */
  private void scrollToComment(long commentId) {
    Optional<Integer> offset = adapter.getOffset(commentsAdapter);
    if (!offset.isPresent()) return;

    for (int idx = 0; idx < commentsAdapter.getItemCount(); idx++) {
      if (commentsAdapter.getItemId(idx) == commentId) {
        content.scrollToPosition(offset.get() + idx);
        break;
      }
    }

    commentsAdapter.setSelectedCommentId(commentId);
  }

  public void autoScrollToComment(long commentId) {
    if (commentId > 0) {
      autoScrollTo = Optional.of(commentId);
    } else {
      autoScrollTo = Optional.absent();
    }
  }

  @Override
  public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    super.onCreateOptionsMenu(menu, inflater);
    inflater.inflate(R.menu.menu_post, menu);
  }

  @Override
  public void onPrepareOptionsMenu(Menu menu) {
    boolean isImage = isStaticImage(feedItem);
    boolean isRotated =
        getActivity().getWindowManager().getDefaultDisplay().getRotation() != Surface.ROTATION_0;

    MenuItem item;
    if ((item = menu.findItem(R.id.action_refresh)) != null)
      item.setVisible(settings.showRefreshButton() && !isVideoFullScreen());

    if ((item = menu.findItem(R.id.action_zoom)) != null)
      item.setVisible(!isVideoFullScreen() && (isImage || !isRotated));

    if ((item = menu.findItem(R.id.action_share_image)) != null)
      item.setVisible(ShareProvider.canShare(getActivity(), feedItem));

    if ((item = menu.findItem(R.id.action_search_image)) != null)
      item.setVisible(isImage && settings.showGoogleImageButton());
  }

  @OnOptionsItemSelected(R.id.action_zoom)
  public void enterFullscreen() {
    FragmentActivity activity = getActivity();
    if (activity == null) return;

    if (isStaticImage(feedItem)) {
      boolean hq = settings.loadHqInZoomView();
      Intent intent = ZoomViewActivity.newIntent(activity, feedItem, hq);
      startActivity(intent);

    } else {
      FullscreenParams params = new FullscreenParams();

      ObjectAnimator.ofPropertyValuesHolder(
              viewer,
              ofFloat(View.ROTATION, params.rotation),
              ofFloat(View.TRANSLATION_Y, params.trY),
              ofFloat(View.SCALE_X, params.scale),
              ofFloat(View.SCALE_Y, params.scale))
          .setDuration(500)
          .start();

      repostHint.setVisibility(View.GONE);

      // hide content below
      swipeRefreshLayout.setVisibility(View.GONE);

      if (activity instanceof ToolbarActivity) {
        // hide the toolbar if required necessary
        ((ToolbarActivity) activity).getScrollHideToolbarListener().hide();
      }

      viewer.setClipBoundsCompat(null);

      activity.supportInvalidateOptionsMenu();
      registerExitFullscreenListener();

      // forbid orientation changes while in fullscreen
      Screen.lockOrientation(activity);
    }
  }

  private void realignFullScreen() {
    FullscreenParams params = new FullscreenParams();

    viewer.setTranslationY(params.trY);
    viewer.setScaleX(params.scale);
    viewer.setScaleY(params.scale);
  }

  private void registerExitFullscreenListener() {
    // add a listener to show/hide the fullscreen.
    View view = getView();
    if (view != null) {
      // get the focus for the back button
      view.setFocusableInTouchMode(true);
      view.requestFocus();

      view.setOnKeyListener(
          (v, keyCode, event) -> {
            if (isVideoFullScreen()) {
              if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
                // remove listener
                view.setOnKeyListener(null);
                view.setOnFocusChangeListener(null);

                // and move back to normal state
                exitFullscreen();
                return true;
              }
            }

            return false;
          });

      view.setOnFocusChangeListener(
          (v, hasFocus) -> {
            if (!hasFocus && isVideoFullScreen()) {
              view.requestFocus();
            }
          });
    }
  }

  public void exitFullscreen() {
    exitFullscreenAnimated(true);
  }

  private void exitFullscreenAnimated(boolean animated) {
    if (!isVideoFullScreen()) return;

    swipeRefreshLayout.setVisibility(View.VISIBLE);

    // reset the values correctly
    viewer.setRotation(0.f);
    viewer.setScaleX(1.f);
    viewer.setScaleY(1.f);
    viewer.setTranslationX(0.f);

    // simulate scrolling to fix the clipping and translationY
    simulateScroll();

    // go back to normal!
    FragmentActivity activity = getActivity();
    activity.supportInvalidateOptionsMenu();

    if (activity instanceof ToolbarActivity) {
      // show the toolbar again
      ((ToolbarActivity) activity).getScrollHideToolbarListener().reset();
    }

    Screen.unlockOrientation(activity);
  }

  private boolean isVideoFullScreen() {
    return swipeRefreshLayout != null && swipeRefreshLayout.getVisibility() != View.VISIBLE;
  }

  @OnOptionsItemSelected(MainActivity.ID_FAKE_HOME)
  public boolean onHomePressed() {
    if (isVideoFullScreen()) {
      exitFullscreen();
      return true;
    }

    return false;
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
      case R.id.action_search_image:
        ShareHelper.searchImage(getActivity(), feedItem);
        return true;

      case R.id.action_share_post:
        ShareHelper.sharePost(getActivity(), feedItem);
        return true;

      case R.id.action_share_direct_link:
        ShareHelper.shareDirectLink(getActivity(), feedItem);
        return true;

      case R.id.action_share_image:
        ShareHelper.shareImage(getActivity(), feedItem);
        return true;

      default:
        return OptionMenuHelper.dispatch(this, item) || super.onOptionsItemSelected(item);
    }
  }

  @OnOptionsItemSelected(R.id.action_refresh)
  public void refreshWithIndicator() {
    if (swipeRefreshLayout.isRefreshing()) return;

    rewindOnLoad = true;
    swipeRefreshLayout.setRefreshing(true);
    swipeRefreshLayout.postDelayed(this::loadPostDetails, 500);
  }

  @OnOptionsItemSelected(R.id.action_download)
  public void downloadPostMedia() {
    ((PermissionHelperActivity) getActivity())
        .requirePermission(WRITE_EXTERNAL_STORAGE)
        .compose(bindUntilEvent(FragmentEvent.DESTROY))
        .subscribe(ignored -> downloadPostWithPermissionGranted(), defaultOnError());
  }

  private void downloadPostWithPermissionGranted() {
    Optional<String> error = downloadService.download(feedItem);
    if (error.isPresent()) showErrorString(getFragmentManager(), error.get());
  }

  @Override
  public void onStart() {
    super.onStart();

    favedCommentService
        .favedCommentIds()
        .compose(bindToLifecycle())
        .subscribe(commentsAdapter::setFavedComments);
  }

  @Override
  public void onResume() {
    super.onResume();

    // set ordering
    commentsAdapter.setPrioritizeOpComments(settings.prioritizeOpComments());
  }

  /** Loads the information about the post. This includes the tags and the comments. */
  private void loadPostDetails() {
    int delay = Sdk.isAtLeastKitKat() ? 500 : 100;

    feedService
        .loadPostDetails(feedItem.getId())
        .delay(delay, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
        .compose(bindUntilEvent(FragmentEvent.DESTROY_VIEW))
        .subscribe(this::onPostReceived, defaultOnError());
  }

  @SuppressWarnings("CodeBlock2Expr")
  private void initializeInfoLine() {
    // get the vote from the service
    Observable<Vote> cachedVote = voteService.getVote(feedItem).compose(bindToLifecycle());

    //noinspection ConstantConditions
    infoLineView = ButterKnife.findById(getView(), R.id.infoview);
    if (infoLineView == null) {
      infoLineView = new InfoLineView(getActivity());
      adapter.addAdapter(SingleViewAdapter.ofView(infoLineView));
    }

    boolean isSelfPost =
        userService
            .getName()
            .transform(name -> name.equalsIgnoreCase(feedItem.getUser()))
            .or(false);

    // display the feed item in the view
    infoLineView.setFeedItem(feedItem, isSelfPost, cachedVote);

    infoLineView.setOnDetailClickedListener(this);

    // register the vote listener
    infoLineView.setOnVoteListener(
        vote -> {
          Runnable action =
              () -> {
                showPostVoteAnimation(vote);

                voteService
                    .vote(feedItem, vote)
                    .compose(bindToLifecycle())
                    .subscribe(Actions.empty(), defaultOnError());
              };

          Runnable retry = () -> infoLineView.getVoteView().setVote(vote);
          return doIfAuthorizedHelper.run(action, retry);
        });

    // and a vote listener vor voting tags.
    infoLineView.setTagVoteListener(
        (tag, vote) -> {
          Runnable action =
              () -> {
                voteService
                    .vote(tag, vote)
                    .compose(bindToLifecycle())
                    .subscribe(Actions.empty(), defaultOnError());
              };

          return doIfAuthorizedHelper.run(action, action);
        });

    infoLineView.setOnAddTagClickedListener(
        () -> {
          NewTagDialogFragment dialog = new NewTagDialogFragment();
          dialog.show(getChildFragmentManager(), null);
        });
  }

  private void showPostVoteAnimation(Vote vote) {
    if (vote == null || vote == Vote.NEUTRAL) return;

    // quickly center the vote button
    simulateScroll();

    String text = vote == Vote.UP ? "+" : (vote == Vote.DOWN ? "-" : "*");
    voteAnimationIndicator.setText(text);

    voteAnimationIndicator.setVisibility(View.VISIBLE);
    voteAnimationIndicator.setAlpha(0);
    voteAnimationIndicator.setScaleX(0.7f);
    voteAnimationIndicator.setScaleY(0.7f);

    ObjectAnimator animator =
        ObjectAnimator.ofPropertyValuesHolder(
            voteAnimationIndicator,
            ofFloat(View.ALPHA, 0, 0.6f, 0.7f, 0.6f, 0),
            ofFloat(View.SCALE_X, 0.7f, 1.3f),
            ofFloat(View.SCALE_Y, 0.7f, 1.3f));

    animator.start();
    animator.addListener(
        new AnimatorListenerAdapter() {
          @Override
          public void onAnimationEnd(Animator animation) {
            View view = PostFragment.this.voteAnimationIndicator;
            if (view != null) {
              view.setVisibility(View.GONE);
            }
          }
        });
  }

  private void initializeMediaView() {
    int padding = AndroidUtility.getActionBarContentOffset(getActivity());

    // initialize a new viewer fragment
    MediaUri uri = MediaUri.of(feedItem.getId(), UriHelper.of(getContext()).media(feedItem));
    if (!uri.isLocal() && AndroidUtility.isOnMobile(getActivity())) {
      Settings.ConfirmOnMobile confirmOnMobile = settings.confirmPlayOnMobile(getContext());
      if (confirmOnMobile == Settings.ConfirmOnMobile.ALL) {
        uri = uri.withDelay(true);

      } else if (confirmOnMobile == Settings.ConfirmOnMobile.VIDEO
          && uri.getMediaType() != MediaUri.MediaType.IMAGE) {

        uri = uri.withDelay(true);
      }
    }

    viewer =
        MediaViews.newInstance(
            getActivity(),
            uri,
            () -> {
              //  mark this item seen. We do that in a background thread
              seenService.markAsSeen(feedItem);
            });

    // inform viewer over fragment lifecycle events!
    MediaViews.adaptFragmentLifecycle(lifecycle(), viewer);

    registerTapListener(viewer);

    PreviewInfo previewInfo =
        this.previewInfo != null ? this.previewInfo : getPreviewInfoFromCache();

    if (previewInfo != null) {
      viewer.setPreviewImage(previewInfo, "TransitionTarget-" + feedItem.getId());
      if (Sdk.isAtLeastLollipop()) {
        viewer.postDelayed(this::onTransitionEnds, 350);
      } else {
        viewer.post(this::onTransitionEnds);
      }
    } else {
      viewer.post(this::onTransitionEnds);
    }

    // add views in the correct order
    int idx = playerContainer.indexOfChild(voteAnimationIndicator);
    playerContainer.addView(viewer, idx);

    if (tabletLayout) {
      viewer.setLayoutParams(
          new FrameLayout.LayoutParams(
              ViewGroup.LayoutParams.MATCH_PARENT,
              ViewGroup.LayoutParams.MATCH_PARENT,
              Gravity.CENTER));
    } else {
      viewer.setPadding(0, padding, 0, 0);

      // we add a placeholder to the first element of the recycler view.
      // this placeholder will mirror the size of the viewer.
      PlaceholderView placeholder = new PlaceholderView();
      adapter.addAdapter(SingleViewAdapter.ofView(placeholder));

      RxView.layoutChanges(viewer)
          .subscribe(
              event -> {
                int newHeight = viewer.getMeasuredHeight();
                if (newHeight != placeholder.fixedHeight) {
                  placeholder.fixedHeight = newHeight;

                  if (Sdk.isAtLeastKitKat()) {
                    placeholder.requestLayout();
                  } else {
                    // it looks like a requestLayout is not honored on pre kitkat devices
                    // if already in a layout pass.
                    placeholder.post(placeholder::requestLayout);
                  }

                  if (isVideoFullScreen()) {
                    realignFullScreen();
                  }
                }
              });

      RxView.layoutChanges(placeholder)
          .subscribe(
              event -> {
                // simulate scroll after layouting the placeholder to
                // reflect changes to the viewers clipping.
                simulateScroll();
              });
    }
  }

  private void onTransitionEnds() {
    if (viewer != null && content != null) {
      viewer.onTransitionEnds();
    }
  }

  private void simulateScroll() {
    if (scrollHandler instanceof ScrollHandler) {
      scrollHandler.onScrolled(content, 0, 0);
    } else {
      // simulate a scroll to "null"
      offsetMediaView(true, 0.0f);
    }
  }

  /**
   * Registers a tap listener on the given viewer instance. The listener is used to handle
   * double-tap-to-vote events from the view.
   *
   * @param viewer The viewer to register the tap listener to.
   */
  private void registerTapListener(MediaView viewer) {
    viewer.setTapListener(
        new MediaView.TapListener() {
          final boolean isImage = isStaticImage(feedItem);

          @Override
          public boolean onSingleTap() {
            if (isImage && settings.singleTapForFullscreen()) {
              enterFullscreen();
            }

            return true;
          }

          @Override
          public boolean onDoubleTap() {
            if (settings.doubleTapToUpvote()) {
              infoLineView.getVoteView().triggerUpVoteClicked();
            }

            return true;
          }
        });
  }

  /**
   * Called with the downloaded post information.
   *
   * @param post The post information that was downloaded.
   */
  private void onPostReceived(Post post) {
    swipeRefreshLayout.setRefreshing(false);

    // update from post
    displayTags(post.getTags());
    displayComments(post.getComments());

    if (rewindOnLoad) {
      rewindOnLoad = false;
      viewer.rewind();
    }
  }

  private void displayTags(List<Tag> tags_) {
    List<Tag> tags = inMemoryCacheService.enhanceTags(feedItem.getId(), tags_);
    this.tags = ImmutableList.copyOf(tags);

    // show tags now
    infoLineView.setTags(toMap(tags, tag -> Vote.NEUTRAL));

    // and update tags with votes later.
    voteService
        .getTagVotes(tags)
        .filter(votes -> !votes.isEmpty())
        .onErrorResumeNext(Observable.<Map<Long, Vote>>empty())
        .compose(bindToLifecycle())
        .subscribe(
            votes ->
                infoLineView.setTags(
                    toMap(tags, tag -> firstNonNull(votes.get(tag.getId()), Vote.NEUTRAL))));

    hideProgressIfLoop(tags);
  }

  /**
   * If the current post is a loop, we'll check if it is a loop. If it is, we will hide the little
   * video progress bar.
   */
  private void hideProgressIfLoop(List<Tag> tags) {
    MediaView actualView = viewer != null ? viewer.getActualMediaView() : null;

    if (actualView instanceof AbstractProgressMediaView) {
      if (Iterables.any(tags, tag -> isLoopTag(tag.getTag()))) {
        ((AbstractProgressMediaView) actualView).setProgressEnabled(false);
      }
    }
  }

  /**
   * Displays the given list of comments combined with the voting for those comments.
   *
   * @param comments The list of comments to display.
   */
  private void displayComments(List<Comment> comments) {
    this.comments = ImmutableList.copyOf(comments);

    // show now
    commentsAdapter.set(comments, emptyMap(), feedItem.getUser());

    if (autoScrollTo.isPresent()) {
      scrollToComment(autoScrollTo.get());
      autoScrollTo = Optional.absent();
    }

    // load the votes for the comments and update, when we found any
    voteService
        .getCommentVotes(comments)
        .filter(votes -> !votes.isEmpty())
        .onErrorResumeNext(empty())
        .compose(bindToLifecycle())
        .subscribe(votes -> commentsAdapter.set(comments, votes, feedItem.getUser()));
  }

  /** Returns the feed item that is displayed in this {@link PostFragment}. */
  public FeedItem getFeedItem() {
    return feedItem;
  }

  /**
   * Called from the {@link PostPagerFragment} if this fragment is currently the active/selected
   * fragment - or if it is not the active fragment anymore.
   *
   * @param active The new active status.
   */
  public void setActive(boolean active) {
    activeStateSubject.onNext(active);
  }

  @Override
  public void onAddNewTags(List<String> tags) {
    voteService
        .tag(feedItem, tags)
        .compose(bindToLifecycle())
        .lift(busyDialog(this))
        .subscribe(this::displayTags, defaultOnError());
  }

  /** Creates a new instance of a {@link PostFragment} displaying the given {@link FeedItem}. */
  public static PostFragment newInstance(FeedItem item) {
    checkNotNull(item, "Item must not be null");

    Bundle arguments = new Bundle();
    arguments.putParcelable(ARG_FEED_ITEM, item);

    PostFragment fragment = new PostFragment();
    fragment.setArguments(arguments);
    return fragment;
  }

  @SuppressWarnings("CodeBlock2Expr")
  @Override
  public boolean onCommentVoteClicked(Comment comment, Vote vote) {
    return doIfAuthorizedHelper.run(
        () -> {
          voteService
              .vote(comment, vote)
              .compose(bindToLifecycle())
              .subscribe(Actions.empty(), defaultOnError());
        });
  }

  @SuppressWarnings("CodeBlock2Expr")
  @Override
  public void onAnswerClicked(Comment comment) {
    Runnable retry = () -> onAnswerClicked(comment);

    doIfAuthorizedHelper.run(
        () -> {
          startActivityForResult(
              WriteMessageActivity.answerToComment(getActivity(), feedItem, comment),
              RequestCodes.WRITE_COMMENT);
        },
        retry);
  }

  @Override
  public void onCommentAuthorClicked(Comment comment) {
    onUserClicked(comment.getName());
  }

  @Override
  public void onCommentMarkAsFavoriteClicked(Comment comment, boolean markAsFavorite) {
    Observable<Void> result;
    if (markAsFavorite) {
      result =
          favedCommentService.save(
              ImmutableFavedComment.builder()
                  .id(comment.getId())
                  .name(comment.getName())
                  .content(comment.getContent())
                  .created(comment.getCreated())
                  .up(comment.getUp())
                  .down(comment.getDown())
                  .mark(comment.getMark())
                  .thumb(feedItem.getThumb())
                  .itemId(feedItem.getId())
                  .flags(feedItem.getFlags())
                  .build());
    } else {
      result = favedCommentService.delete(comment.getId());
    }

    result
        .compose(bindUntilEvent(FragmentEvent.DESTROY_VIEW))
        .subscribe(Actions.empty(), defaultOnError());

    if (singleShotService.isFirstTime("kfav-userscript-hint")) {
      DialogBuilder.start(getContext())
          .content(R.string.hint_kfav_userscript)
          .positive(
              R.string.open_website,
              di -> {
                Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://goo.gl/py7xNW"));
                getContext().startActivity(intent);
              })
          .negative(R.string.ignore)
          .show();
    }
  }

  @Override
  public void onTagClicked(Tag tag) {
    if (getParentFragment() instanceof PostPagerFragment)
      ((PostPagerFragment) getParentFragment()).onTagClicked(tag);
  }

  @Override
  public void onUserClicked(String username) {
    if (getParentFragment() instanceof PostPagerFragment)
      ((PostPagerFragment) getParentFragment()).onUsernameClicked(username);
  }

  private void onNewComments(NewComment response) {
    autoScrollToComment(response.getCommentId());
    displayComments(response.getComments());

    Snackbar.make(content, R.string.comment_written_successful, Snackbar.LENGTH_LONG).show();
    hideSoftKeyboard();
  }

  @SuppressWarnings("ConstantConditions")
  private void hideSoftKeyboard() {
    try {
      InputMethodManager imm =
          (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);

      imm.hideSoftInputFromWindow(getView().getWindowToken(), 0);
    } catch (Exception ignored) {
    }
  }

  @Nullable
  private PreviewInfo getPreviewInfoFromCache() {
    Uri previewUri = UriHelper.of(getActivity()).thumbnail(feedItem);
    return inMemoryCacheService
        .getSizeInfo(feedItem.getId())
        .transform(
            info -> new PreviewInfo(info.getId(), previewUri, info.getWidth(), info.getHeight()))
        .orNull();
  }

  public void mediaHorizontalOffset(int offset) {
    viewer.setTranslationX(offset);
  }

  private class PlaceholderView extends View {
    int fixedHeight = AndroidUtility.dp(getActivity(), 150);

    public PlaceholderView() {
      super(PostFragment.this.getContext());
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
      int width = MeasureSpec.getSize(widthMeasureSpec);
      setMeasuredDimension(width, fixedHeight);
    }

    @Override
    public boolean onTouchEvent(@NonNull MotionEvent event) {
      return viewer.onTouchEvent(event);
    }
  }

  private class ScrollHandler extends RecyclerView.OnScrollListener {
    private final ToolbarActivity activity;

    public ScrollHandler(ToolbarActivity activity) {
      this.activity = activity;
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
      if (isVideoFullScreen()) return;

      // get our facts straight
      int recyclerHeight = recyclerView.getHeight();
      Optional<Integer> scrollEstimate = estimateRecyclerViewScrollY(recyclerView);
      boolean viewerVisible = scrollEstimate.isPresent();

      int scrollY = scrollEstimate.or(viewer.getHeight());
      int viewerHeight = viewer.getHeight();
      boolean doFancyScroll = viewerHeight < recyclerHeight;

      ScrollHideToolbarListener toolbar = activity.getScrollHideToolbarListener();
      if (!doFancyScroll || dy < 0 || scrollY > toolbar.getToolbarHeight()) {
        toolbar.onScrolled(dy);
      }

      float scroll = doFancyScroll ? 0.7f * scrollY : scrollY;

      if (doFancyScroll) {
        int clipTop = (int) (scroll + 0.5f);
        int clipBottom = viewer.getHeight() - (int) (scrollY - scroll + 0.5f);

        if (clipTop < clipBottom) {
          viewer.setClipBoundsCompat(new Rect(0, clipTop, viewer.getRight(), clipBottom));
        } else {
          viewerVisible = false;
        }
      } else {
        // reset bounds. we might have set some previously and want
        // to clear those bounds now.
        viewer.setClipBoundsCompat(null);
      }

      offsetMediaView(viewerVisible, scroll);

      // position the vote indicator
      float remaining = viewerHeight - scrollY;
      int tbVisibleHeight = toolbar.getVisibleHeight();
      float voteIndicatorY =
          Math.min((remaining - tbVisibleHeight) / 2, (recyclerHeight - tbVisibleHeight) / 2)
              + tbVisibleHeight;

      voteAnimationIndicator.setTranslationY(voteIndicatorY);
    }

    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
      if (!isVideoFullScreen() && newState == RecyclerView.SCROLL_STATE_IDLE) {
        int y = estimateRecyclerViewScrollY(recyclerView).or(Integer.MAX_VALUE);
        activity.getScrollHideToolbarListener().onScrollFinished(y);
      }
    }
  }

  /** Positions the media view using the given offset (on the y axis) */
  private void offsetMediaView(boolean viewerVisible, float offset) {
    if (viewerVisible) {
      // finally position the viewer
      viewer.setTranslationY(-offset);
      viewer.setVisibility(View.VISIBLE);

      // position the repost badge, if it is visible
      if (repostHint.getVisibility() == View.VISIBLE) {
        repostHint.setTranslationY(viewer.getPaddingTop() - repostHint.getPivotY() - offset);
      }
    } else {
      viewer.setVisibility(View.INVISIBLE);
    }
  }

  private class FullscreenParams {
    private final float scale;
    private final float trY;
    private final float rotation;

    FullscreenParams() {
      int windowWidth = swipeRefreshLayout.getWidth();
      float windowHeight = swipeRefreshLayout.getHeight();

      //noinspection UnnecessaryLocalVariable
      int viewerWidth = windowWidth;
      int viewerHeight = viewer.getHeight() - viewer.getPaddingTop();

      viewer.setPivotY(viewer.getHeight() - 0.5f * viewerHeight);
      viewer.setPivotX(viewerWidth / 2.f);
      trY = (windowHeight / 2.f - viewer.getPivotY());

      float scaleRot =
          Math.min(windowHeight / (float) viewerWidth, windowWidth / (float) viewerHeight);

      float scaleNoRot =
          Math.min(windowHeight / (float) viewerHeight, windowWidth / (float) viewerWidth);

      // check if rotation is necessary
      if (scaleRot > scaleNoRot) {
        rotation = 90.f;
        scale = scaleRot;
      } else {
        rotation = 0.f;
        scale = scaleNoRot;
      }
    }
  }

  /** Returns true, if the given tag looks like some "loop" tag. */
  private static boolean isLoopTag(String tag) {
    tag = tag.toLowerCase();
    return tag.contains("loop") && !(tag.contains("verschenkt") || tag.contains("verkackt"));
  }

  /**
   * Returns true, if the given url links to a static image. This does only a check on the filename
   * and not on the data.
   *
   * @param image The url of the image to check
   */
  private static boolean isStaticImage(FeedItem image) {
    return image.getImage().toLowerCase().matches(".*\\.(jpg|jpeg|png)");
  }
}
public class UpdateProcess {
  private static final Logger log = LoggerFactory.getLogger(UpdateProcess.class);

  private static final List<ECPoint> UPDATE_SIGNING_KEYS =
      Crypto.decode("029EF2D0D33A2546CB15FB10D969B7D65CAFB811CB3AC902E8D9A46BE847B1DA21");
  private static final String UPDATES_BASE_URL = "https://bitsquare.io/updateFX/v03";
  private static final int UPDATE_SIGNING_THRESHOLD = 1;
  private static final Path ROOT_CLASS_PATH = UpdateFX.findCodePath(BitsquareAppMain.class);

  private final BitsquareEnvironment environment;

  public enum State {
    CHECK_FOR_UPDATES,
    UPDATE_AVAILABLE,
    UP_TO_DATE,
    NEW_RELEASE, // if a new minor release is out we inform the user to download the new binary
    FAILURE
  }

  public final ObjectProperty<State> state = new SimpleObjectProperty<>(State.CHECK_FOR_UPDATES);

  private String releaseUrl;
  private final Subject<State, State> process = BehaviorSubject.create();
  private Timer timeoutTimer;

  @Inject
  public UpdateProcess(BitsquareEnvironment environment) {
    this.environment = environment;
  }

  public void restart() {
    UpdateFX.restartApp();
  }

  public Observable<State> getProcess() {
    return process.asObservable();
  }

  public void init() {
    log.info("UpdateFX current version " + Version.PATCH_VERSION);

    // process.timeout() will cause an error state back but we don't want to break startup in case
    // of an timeout
    timeoutTimer =
        Utilities.setTimeout(
            10000,
            () -> {
              log.error("Timeout reached for UpdateFX");
              process.onCompleted();
            });
    String userAgent = environment.getProperty(BitsquareEnvironment.APP_NAME_KEY) + Version.VERSION;

    // Check if there is a new minor version release out. The release_url should be empty if no
    // release is available, otherwise the download url.
    try {
      releaseUrl = Utilities.readTextFileFromServer(UPDATES_BASE_URL + "/release_url", userAgent);
      if (releaseUrl != null && releaseUrl.length() > 0) {
        log.info("New release available at: " + releaseUrl);
        state.set(State.NEW_RELEASE);
        timeoutTimer.cancel();
        return;
      } else {
        // All ok. Empty file if we have no new release.
      }
    } catch (IOException e) {
      // ignore. File might be missing
    }

    Updater updater =
        new Updater(
            UPDATES_BASE_URL,
            userAgent,
            Version.PATCH_VERSION,
            Paths.get(environment.getProperty(BitsquareEnvironment.APP_DATA_DIR_KEY)),
            ROOT_CLASS_PATH,
            UPDATE_SIGNING_KEYS,
            UPDATE_SIGNING_THRESHOLD) {
          @Override
          protected void updateProgress(long workDone, long max) {
            // log.trace("updateProgress " + workDone + "/" + max);
            super.updateProgress(workDone, max);
          }
        };

    /* updater.progressProperty().addListener((observableValue, oldValue, newValue) -> {
        log.trace("progressProperty newValue = " + newValue);
    });*/

    log.info("Checking for updates!");
    updater.setOnSucceeded(
        event -> {
          try {
            UpdateSummary summary = updater.get();
            log.info("summary " + summary.toString());
            if (summary.descriptions != null && summary.descriptions.size() > 0) {
              log.info("One liner: {}", summary.descriptions.get(0).getOneLiner());
              log.info("{}", summary.descriptions.get(0).getDescription());
            }
            if (summary.highestVersion > Version.PATCH_VERSION) {
              log.info("UPDATE_AVAILABLE");
              state.set(State.UPDATE_AVAILABLE);
              // We stop the timeout and treat it not completed.
              // The user should click the restart button manually if there are updates available.
              timeoutTimer.cancel();
            } else if (summary.highestVersion == Version.PATCH_VERSION) {
              log.info("UP_TO_DATE");
              state.set(State.UP_TO_DATE);
              timeoutTimer.cancel();
              process.onCompleted();
            }
          } catch (Throwable e) {
            log.error("Exception at processing UpdateSummary: " + e.getMessage());

            // we treat errors as update not as critical errors to prevent startup,
            // so we use state.onCompleted() instead of state.onError()
            state.set(State.FAILURE);
            timeoutTimer.cancel();
            process.onCompleted();
          }
        });
    updater.setOnFailed(
        event -> {
          log.error("Update failed: " + updater.getException());
          updater.getException().printStackTrace();

          // we treat errors as update not as critical errors to prevent startup,
          // so we use state.onCompleted() instead of state.onError()
          state.set(State.FAILURE);
          timeoutTimer.cancel();
          process.onCompleted();
        });

    Thread thread = new Thread(updater, "Online update check");
    thread.setDaemon(true);
    thread.start();
  }

  public String getReleaseUrl() {
    return releaseUrl;
  }
}
 public Observable<String> getObservable() {
   if (mSubject == null) {
     mSubject = BehaviorSubject.create(get());
   }
   return mSubject.asObservable();
 }