/*
* This source is part of the
* _____ ___ ____
* __ / / _ \/ _ | / __/___ _______ _
* / // / , _/ __ |/ _/_/ _ \/ __/ _ `/
* \___/_/|_/_/ |_/_/ (_)___/_/ \_, /
* /___/
* repository.
*
* Copyright (C) 2013 Benoit 'BoD' Lubek (BoD@JRAF.org)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.jraf.android.bikey.app.display;
import android.animation.Animator;
import android.animation.AnimatorInflater;
import android.annotation.TargetApi;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.PointF;
import android.graphics.drawable.AnimationDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.WindowManager;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ImageView;
import android.widget.TextView;
import org.jraf.android.bikey.R;
import org.jraf.android.bikey.app.collect.LogCollectorService;
import org.jraf.android.bikey.app.display.fragment.compass.CompassDisplayFragment;
import org.jraf.android.bikey.app.display.fragment.currenttime.CurrentTimeDisplayFragment;
import org.jraf.android.bikey.app.display.fragment.elapsedtime.ElapsedTimeDisplayFragment;
import org.jraf.android.bikey.app.display.fragment.heartrate.HeartRateDisplayFragment;
import org.jraf.android.bikey.app.display.fragment.speed.SpeedDisplayFragment;
import org.jraf.android.bikey.app.display.fragment.totaldistance.TotalDistanceDisplayFragment;
import org.jraf.android.bikey.backend.heartrate.HeartRateManager;
import org.jraf.android.bikey.backend.location.LocationManager;
import org.jraf.android.bikey.backend.location.LocationManager.StatusListener;
import org.jraf.android.bikey.backend.ride.RideListener;
import org.jraf.android.bikey.backend.ride.RideManager;
import org.jraf.android.bikey.common.widget.fragmentcycler.FragmentCycler;
import org.jraf.android.util.app.base.BaseFragmentActivity;
import org.jraf.android.util.log.Log;
import org.jraf.android.util.ui.checkable.CheckableRelativeLayout;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
public class DisplayActivity extends BaseFragmentActivity {
private static final long DELAY_HIDE_CONTROLS = 4500;
private Handler mHandler = new Handler();
private ImageView mImgGpsStatus;
private CheckableRelativeLayout mChkRecord;
private TextView mChkRecordText;
private Animator mChkRecordTextAnimator;
private View mConTabsA;
private View mConTabsB;
private View mConFragments;
private TextView mTxtTitle;
private boolean mNavigationBarHiding = false;
private Uri mRideUri;
private FragmentCycler mFragmentCycler;
private boolean mControlsVisible = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);// TODO only if setting says so
mRideUri = getIntent().getData();
setContentView(R.layout.display);
mChkRecord = (CheckableRelativeLayout) findViewById(R.id.chkRecord);
mChkRecord.setEnabled(false);
mChkRecordText = (TextView) findViewById(R.id.chkRecord_text);
mChkRecordTextAnimator = AnimatorInflater.loadAnimator(this, R.animator.blink);
mChkRecordTextAnimator.setTarget(mChkRecordText);
mImgGpsStatus = (ImageView) findViewById(R.id.imgGpsStatus);
((AnimationDrawable) mImgGpsStatus.getDrawable()).start();
findViewById(R.id.vieFragmentCycle).setOnTouchListener(mFragmentCycleOnTouchListener);
mConTabsA = findViewById(R.id.conTabsA);
mConTabsB = findViewById(R.id.conTabsB);
mConFragments = findViewById(R.id.conFragments);
mTxtTitle = (TextView) findViewById(R.id.txtTitle);
setupFragments(savedInstanceState == null ? 0 : savedInstanceState.getInt("mFragmentCycler.currentVisibleIndex"));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
setupNavigationBarHiding();
}
scheduleHideControls();
setupFragmentContainer();
}
private void setupFragmentContainer() {
mConFragments.post(() -> {
PointF shrinkPercents = getShrinkPercents();
mConFragments.setScaleX(shrinkPercents.x);
mConFragments.setScaleY(shrinkPercents.y);
mTxtTitle.setAlpha(0);
});
}
private void toggleRecordingIfActive() {
Single.fromCallable(() -> RideManager.get().getState(mRideUri))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(rideState -> {
switch (rideState) {
case CREATED:
mChkRecord.setChecked(false, false);
mChkRecordText.setText(R.string.display_chkRecord_created);
break;
case ACTIVE:
mChkRecord.setChecked(true, false);
mChkRecordText.setText(R.string.display_chkRecord_active);
if (!mChkRecordTextAnimator.isStarted()) mChkRecordTextAnimator.start();
break;
case PAUSED:
mChkRecord.setChecked(false, false);
mChkRecordText.setText(R.string.display_chkRecord_paused);
if (mChkRecordTextAnimator.isStarted()) mChkRecordTextAnimator.cancel();
mChkRecordText.setAlpha(1f);
break;
}
mChkRecord.setEnabled(true);
mChkRecord.setOnCheckedChangeListener(mRecordingOnCheckedChangeListener);
});
}
@Override
protected void onStart() {
super.onStart();
toggleRecordingIfActive();
// GPS status
LocationManager.get().addStatusListener(mGpsStatusListener);
// Ride
RideManager.get().addListener(mRideListener);
}
@Override
protected void onStop() {
// GPS status
LocationManager.get().removeStatusListener(mGpsStatusListener);
// Ride
RideManager.get().removeListener(mRideListener);
super.onStop();
}
private void setupFragments(int currentVisibleIndex) {
long updateTitleDelay = getResources().getInteger(R.integer.animation_controls_showHide);
int tabColorEnabled = getResources().getColor(R.color.bright_foreground_dark);
int tabColorDisabled = getResources().getColor(R.color.bright_foreground_disabled_dark);
mFragmentCycler = new FragmentCycler(R.id.conFragments, mTxtTitle, updateTitleDelay, tabColorEnabled, tabColorDisabled);
mFragmentCycler.setCurrentVisibleIndex(currentVisibleIndex);
mFragmentCycler.add(this, SpeedDisplayFragment.newInstance(), R.id.chkTabSpeed, R.string.display_title_speed);
mFragmentCycler.add(this, ElapsedTimeDisplayFragment.newInstance(), R.id.chkTabDuration, R.string.display_title_duration);
mFragmentCycler.add(this, TotalDistanceDisplayFragment.newInstance(), R.id.chkTabDistance, R.string.display_title_distance);
mFragmentCycler.add(this, HeartRateDisplayFragment.newInstance(), R.id.chkHeartRate, R.string.display_title_heartRate);
mFragmentCycler.add(this, CompassDisplayFragment.newInstance(), R.id.chkTabCompass, R.string.display_title_compass);
mFragmentCycler.add(this, CurrentTimeDisplayFragment.newInstance(), R.id.chkTabCurrentTime, R.string.display_title_currentTime);
HeartRateManager heartRateManager = HeartRateManager.get();
mFragmentCycler.setEnabled(this, HeartRateDisplayFragment.class, heartRateManager.isConnected() || heartRateManager.isConnecting());
mFragmentCycler.show(this);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
outState.putInt("mFragmentCycler.currentVisibleIndex", mFragmentCycler.getCurrentVisibleIndex());
super.onSaveInstanceState(outState);
}
private OnTouchListener mFragmentCycleOnTouchListener = new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
mFragmentCycler.cycle(thiz);
}
return true;
}
};
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
private void setupNavigationBarHiding() {
findViewById(android.R.id.content).setOnSystemUiVisibilityChangeListener(visibility -> {
Log.d("visibility=" + visibility);
if ((visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) {
Log.d("Navigation bar showing");
if (!isPaused()) mFragmentCycler.cycle(thiz);
scheduleHideNavigationBar();
showControls();
scheduleHideControls();
}
});
scheduleHideNavigationBar();
mNavigationBarHiding = true;
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mNavigationBarHiding) {
switch (ev.getAction()) {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
Log.d("up");
scheduleHideNavigationBar();
scheduleHideControls();
break;
default:
mHandler.removeCallbacks(mHideNavigationBarRunnable);
mHandler.removeCallbacks(mHideControlsRunnable);
showControls();
break;
}
}
return super.dispatchTouchEvent(ev);
}
private Runnable mHideNavigationBarRunnable = this::hideNavigationBar;
private void scheduleHideNavigationBar() {
mHandler.removeCallbacks(mHideNavigationBarRunnable);
mHandler.postDelayed(mHideNavigationBarRunnable, DELAY_HIDE_CONTROLS + 1000);
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
private void hideNavigationBar() {
Log.d();
findViewById(android.R.id.content).setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
}
private OnCheckedChangeListener mRecordingOnCheckedChangeListener = new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Log.d("isChecked=" + isChecked);
if (isChecked) {
startService(new Intent(LogCollectorService.ACTION_START_COLLECTING, mRideUri, thiz, LogCollectorService.class));
mChkRecordText.setText(R.string.display_chkRecord_active);
// mChkRecordTextAnimator.start();
} else {
startService(new Intent(LogCollectorService.ACTION_STOP_COLLECTING, mRideUri, thiz, LogCollectorService.class));
mChkRecordText.setText(R.string.display_chkRecord_paused);
// mChkRecordTextAnimator.cancel();
// mChkRecordText.setAlpha(1f);
}
}
};
private StatusListener mGpsStatusListener = new StatusListener() {
@Override
public void onStatusChanged(boolean active) {
if (active) {
mImgGpsStatus.setVisibility(View.GONE);
} else {
mImgGpsStatus.setVisibility(View.VISIBLE);
}
}
};
public Uri getRideUri() {
return mRideUri;
}
/*
* Ride listener.
*/
private RideListener mRideListener = new RideListener() {
@Override
public void onActivated(Uri rideUri) {
if (!rideUri.equals(mRideUri)) return;
mChkRecord.setChecked(true, false);
mChkRecordText.setText(R.string.display_chkRecord_active);
if (!mChkRecordTextAnimator.isStarted()) mChkRecordTextAnimator.start();
}
@Override
public void onPaused(Uri rideUri) {
if (!rideUri.equals(mRideUri)) return;
mChkRecord.setChecked(false, false);
mChkRecordText.setText(R.string.display_chkRecord_paused);
if (mChkRecordTextAnimator.isStarted()) mChkRecordTextAnimator.cancel();
mChkRecordText.setAlpha(1f);
}
};
/*
* Controls show / hide.
*/
private void hideControls() {
mControlsVisible = false;
int duration = getResources().getInteger(R.integer.animation_controls_showHide);
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
// Landscape: tab containers slide to left / right
mConTabsA.animate().alpha(0).translationX(-mConTabsA.getWidth()).setInterpolator(new AccelerateInterpolator()).setDuration(duration);
mConTabsB.animate().alpha(0).translationX(mConTabsB.getWidth()).setInterpolator(new AccelerateInterpolator()).setDuration(duration);
} else {
// Portrait: tab containers slide to top / bottom
mConTabsA.animate().alpha(0).translationY(-mConTabsA.getHeight()).setInterpolator(new AccelerateInterpolator()).setDuration(duration);
mConTabsB.animate().alpha(0).translationY(mConTabsB.getHeight()).setInterpolator(new AccelerateInterpolator()).setDuration(duration);
}
// 'Uncompress' the main fragment
mConFragments.animate().scaleX(1f).scaleY(1f).setInterpolator(new AccelerateInterpolator()).setDuration(duration);
// Record button
mChkRecord.animate().alpha(0).translationY(-mChkRecord.getHeight()).setInterpolator(new AccelerateInterpolator()).setDuration(duration);
// Show the title
mTxtTitle.animate().alpha(1f).setDuration(duration).setStartDelay(duration);
}
private PointF getShrinkPercents() {
float percentX;
float percentY;
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
int fragmentWidth = mConFragments.getWidth();
percentX = (fragmentWidth - 2 * getResources().getDimension(R.dimen.display_tabs_width)) / fragmentWidth;
// Remove 5% because it looks better
percentX -= .05f;
percentY = percentX + (1f - percentX) / 2f;
} else {
int fragmentHeight = mConFragments.getHeight();
percentY = (fragmentHeight - 2 * getResources().getDimension(R.dimen.display_tabs_width)) / fragmentHeight;
// Remove 5% because it looks better
percentY -= .05f;
percentX = percentY + (1f - percentY) / 2f;
}
return new PointF(percentX, percentY);
}
private void showControls() {
if (mControlsVisible) return;
mControlsVisible = true;
int duration = getResources().getInteger(R.integer.animation_controls_showHide);
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
// Landscape: tab containers slide from left / right
mConTabsA.animate().alpha(1).translationX(0).setInterpolator(new DecelerateInterpolator()).setDuration(duration);
mConTabsB.animate().alpha(1).translationX(0).setInterpolator(new DecelerateInterpolator()).setDuration(duration);
} else {
// Portrait: tab containers slide from top / bottom
mConTabsA.animate().alpha(1).translationY(0).setInterpolator(new DecelerateInterpolator()).setDuration(duration);
mConTabsB.animate().alpha(1).translationY(0).setInterpolator(new DecelerateInterpolator()).setDuration(duration);
}
// 'Compress' the main fragment, to make space for the tab containers
PointF shrinkPercents = getShrinkPercents();
mConFragments.animate().scaleX(shrinkPercents.x).scaleY(shrinkPercents.y).setInterpolator(new DecelerateInterpolator()).setDuration(duration);
// Record button
mChkRecord.animate().alpha(1).translationY(0).setInterpolator(new DecelerateInterpolator()).setDuration(duration);
mTxtTitle.animate().alpha(0).setDuration(duration).setStartDelay(0);
}
private Runnable mHideControlsRunnable = this::hideControls;
private void scheduleHideControls() {
mHandler.removeCallbacks(mHideControlsRunnable);
mHandler.postDelayed(mHideControlsRunnable, DELAY_HIDE_CONTROLS);
}
}