diff --git a/app/src/main/java/io/pslab/models/PSLabSensor.java b/app/src/main/java/io/pslab/models/PSLabSensor.java new file mode 100644 index 000000000..ba0b5e766 --- /dev/null +++ b/app/src/main/java/io/pslab/models/PSLabSensor.java @@ -0,0 +1,334 @@ +package io.pslab.models; + +import android.Manifest; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.Handler; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.BottomSheetBehavior; +import android.support.design.widget.CoordinatorLayout; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.view.GestureDetector; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import butterknife.BindView; +import butterknife.ButterKnife; +import io.pslab.R; +import io.pslab.activity.DataLoggerActivity; +import io.pslab.activity.MapsActivity; +import io.pslab.activity.SettingsActivity; +import io.pslab.others.MathUtils; +import io.pslab.others.SwipeGestureDetector; + +/** + * Created by Padmal on 10/20/18. + */ + +public abstract class PSLabSensor extends AppCompatActivity { + + public boolean recordData = false; + + public final int MY_PERMISSIONS_REQUEST_STORAGE_FOR_MAPS = 102; + + public Toolbar sensorToolBar; + + public BottomSheetBehavior bottomSheetBehavior; + public GestureDetector gestureDetector; + + @BindView(R.id.cl) + CoordinatorLayout coordinatorLayout; + @BindView(R.id.bottom_sheet) + LinearLayout bottomSheet; + @BindView(R.id.shadow) + View tvShadow; + @BindView(R.id.img_arrow) + ImageView arrowUpDown; + @BindView(R.id.sheet_slide_text) + TextView bottomSheetSlideText; + @BindView(R.id.guide_title) + TextView bottomSheetGuideTitle; + @BindView(R.id.custom_dialog_text) + TextView bottomSheetText; + @BindView(R.id.custom_dialog_schematic) + ImageView bottomSheetSchematic; + @BindView(R.id.custom_dialog_desc) + TextView bottomSheetDesc; + @BindView(R.id.custom_dialog_additional_content) + LinearLayout bottomSheetAdditionalContent; + + /** + * Getting layout file distinct to each sensor + * + * @return Layout resource file in 'R.layout.id' format + */ + public abstract int getLayout(); + + /** + * Getting menu layout distinct to each sensor + * + * @return Menu resource file in 'R.menu.id' format + */ + public abstract int getMenu(); + + /** + * Getting toolbar layout distinct to each sensor + * + * @return Toolbar resource in 'R.id.id' format + */ + public abstract int getSensorToolBar(); + + /** + * Getting saved setting configurations for dialogs + * + * @return SharedPreferences in Private mode + */ + public abstract SharedPreferences getStateSettings(); + + /** + * Getting ID to fetch first time usage of each sensor + * + * @return String ID of the first time usage ID of sensor + */ + public abstract String getFirstTimeSettingID(); + + /** + * Sensor ID + * + * @return String ID of the sensor + */ + public abstract String getSensorName(); + + /** + * Title of the sensor guide + * + * @return Sensor name as a String resource + */ + public abstract int getGuideTitle(); + + /** + * Abstract of the sensor guide + * + * @return Sensor abstract as a String resource + */ + public abstract int getGuideAbstract(); + + /** + * Circuit diagrams and pin settings for the sensor + * + * @return Schematics as a drawable resource + */ + public abstract int getGuideSchematics(); + + /** + * Description of the sensor guide + * + * @return Sensor guide description as a String resource + */ + public abstract int getGuideDescription(); + + /** + * Extra content for a specific sensor if it is not a generic one + * + * @return Layout id of the content file + */ + public abstract int getGuideExtraContent(); + + /** + * This method will be called upon when menu button for recording data has been clicked + */ + public abstract void startRecordSensorData(); + + /** + * This method will be called upon when menu button for stop recording data has been clicked + */ + public abstract void stopRecordSensorData(); + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(getLayout()); + ButterKnife.bind(this); + sensorToolBar = findViewById(getSensorToolBar()); + setSupportActionBar(sensorToolBar); + setUpBottomSheet(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(getMenu(), menu); + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + MenuItem item = menu.findItem(R.id.record_data); + item.setIcon(recordData ? R.drawable.ic_record_stop_white : R.drawable.ic_record_white); + return super.onPrepareOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.record_data: + if (recordData) { + startRecordSensorData(); + } else { + stopRecordSensorData(); + } + recordData = !recordData; + invalidateOptionsMenu(); + break; + case R.id.show_map: + if (ContextCompat.checkSelfPermission(this, + Manifest.permission.WRITE_EXTERNAL_STORAGE) + != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(this, + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, + MY_PERMISSIONS_REQUEST_STORAGE_FOR_MAPS); + return true; + } + Intent MAP = new Intent(getApplicationContext(), MapsActivity.class); + startActivity(MAP); + break; + case R.id.settings: + Intent settingIntent = new Intent(this, SettingsActivity.class); + settingIntent.putExtra("title", getSensorName()); + startActivity(settingIntent); + break; + case R.id.show_logged_data: + Intent intent = new Intent(this, DataLoggerActivity.class); + intent.putExtra(DataLoggerActivity.CALLER_ACTIVITY, getSensorName()); + startActivity(intent); + break; + case R.id.show_guide: + bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED); + break; + default: + break; + } + return true; + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, + @NonNull int[] grantResults) { + if (requestCode == MY_PERMISSIONS_REQUEST_STORAGE_FOR_MAPS + && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + Intent MAP = new Intent(getApplicationContext(), MapsActivity.class); + startActivity(MAP); + } + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + gestureDetector.onTouchEvent(event); + return super.onTouchEvent(event); + } + + /** + * Configure the sensor guide with content and settings + */ + private void setUpBottomSheet() { + setupGuideLayout(); + handleFirstTimeUsage(); + handleBottomSheetBehavior(); + gestureDetector = new GestureDetector(this, + new SwipeGestureDetector(bottomSheetBehavior)); + } + + /** + * Handle sliding up and down behaviors and proper handling in closure. + */ + private void handleBottomSheetBehavior() { + bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() { + private Handler handler = new Handler(); + private Runnable runnable = new Runnable() { + @Override + public void run() { + bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); + } + }; + + @Override + public void onStateChanged(@NonNull final View bottomSheet, int newState) { + switch (newState) { + case BottomSheetBehavior.STATE_EXPANDED: + handler.removeCallbacks(runnable); + bottomSheetSlideText.setText(R.string.hide_guide_text); + break; + + case BottomSheetBehavior.STATE_COLLAPSED: + handler.postDelayed(runnable, 2000); + break; + + default: + handler.removeCallbacks(runnable); + bottomSheetSlideText.setText(R.string.show_guide_text); + break; + } + } + + @Override + public void onSlide(@NonNull View bottomSheet, float slideOffset) { + Float value = (float) MathUtils.map((double) slideOffset, 0.0, 1.0, 0.0, 0.8); + tvShadow.setVisibility(View.VISIBLE); + tvShadow.setAlpha(value); + arrowUpDown.setRotation(slideOffset * 180); + } + }); + } + + /** + * Inflate each individual view with content to fill up the sensor guide + */ + private void setupGuideLayout() { + bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet); + + bottomSheetGuideTitle.setText(getGuideTitle()); + bottomSheetText.setText(getGuideAbstract()); + bottomSheetSchematic.setImageResource(getGuideSchematics()); + bottomSheetDesc.setText(getGuideDescription()); + // If a sensor has extra content than provided in the standard layout, create a new layout + // and attach the layout id with getGuideExtraContent() + if (getGuideExtraContent() != 0) { + LayoutInflater I = (LayoutInflater) this.getSystemService(LAYOUT_INFLATER_SERVICE); + assert I != null; + View childLayout = I.inflate(getGuideExtraContent(), null); + bottomSheetAdditionalContent.addView(childLayout); + } + } + + /** + * Handle first time usage of sensor to show the guide at startup + */ + private void handleFirstTimeUsage() { + if (getStateSettings().getBoolean(getFirstTimeSettingID(), true)) { + bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED); + tvShadow.setVisibility(View.VISIBLE); + tvShadow.setAlpha(0.8f); + arrowUpDown.setRotation(180); + bottomSheetSlideText.setText(R.string.hide_guide_text); + SharedPreferences.Editor editor = getStateSettings().edit(); + editor.putBoolean(getFirstTimeSettingID(), false); + editor.apply(); + } else { + bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); + } + } + +} diff --git a/app/src/main/res/layout/bottom_sheet_custom.xml b/app/src/main/res/layout/bottom_sheet_custom.xml index 24a115f88..c8671f84c 100644 --- a/app/src/main/res/layout/bottom_sheet_custom.xml +++ b/app/src/main/res/layout/bottom_sheet_custom.xml @@ -59,10 +59,6 @@ android:orientation="vertical" android:padding="@dimen/bottom_sheet_padding"> - - - + android:layout_height="wrap_content" + android:orientation="horizontal" />