diff --git a/shaky-sample/src/main/java/com/linkedin/android/shaky/app/ShakyApplication.java b/shaky-sample/src/main/java/com/linkedin/android/shaky/app/ShakyApplication.java
index 350b282..6ef34f7 100644
--- a/shaky-sample/src/main/java/com/linkedin/android/shaky/app/ShakyApplication.java
+++ b/shaky-sample/src/main/java/com/linkedin/android/shaky/app/ShakyApplication.java
@@ -52,6 +52,12 @@ public Integer getPopupTheme() {
return popupTheme;
}
+ @Nullable
+ @Override
+ public String getSelectScreenSubtitle() {
+ return "Did you encounter a problem? You can turn on \"Shake to send feedback\" " +
+ "below and shake your device where the problem was to take a screenshot";
+ }
}, new ShakyFlowCallback() {
@Override
public void onShakyStarted(@ShakyFlowCallback.ShakyStartedReason int reason) {
diff --git a/shaky/src/main/java/com/linkedin/android/shaky/FeedbackActivity.java b/shaky/src/main/java/com/linkedin/android/shaky/FeedbackActivity.java
index d877244..df37550 100644
--- a/shaky/src/main/java/com/linkedin/android/shaky/FeedbackActivity.java
+++ b/shaky/src/main/java/com/linkedin/android/shaky/FeedbackActivity.java
@@ -46,6 +46,10 @@ public class FeedbackActivity extends AppCompatActivity {
static final String RES_MENU = "resMenu";
static final String SUBCATEGORY = "subcategory";
static final String THEME = "theme";
+ static final String SELECT_SCREEN_TITLE = "selectScreenTitle";
+ static final String SELECT_SCREEN_SUBTITLE = "selectScreenSubtitle";
+ static final String SHAKE_TURNED_ON = "shakeTurnedOn";
+ static final String SHAKE_ENABLED = "shakeEnabled";
static final int MISSING_RESOURCE = 0;
private Uri imageUri;
@@ -59,12 +63,20 @@ public static Intent newIntent(@NonNull Context context,
@Nullable Uri screenshotUri,
@Nullable Bundle userData,
@MenuRes int resMenu,
- @StyleRes int theme) {
+ @StyleRes int theme,
+ @Nullable String selectScreenTitle,
+ @Nullable String selectScreenSubtitle,
+ boolean shakeTurnedOn,
+ boolean shakeEnabled) {
Intent intent = new Intent(context, FeedbackActivity.class);
intent.putExtra(SCREENSHOT_URI, screenshotUri);
intent.putExtra(USER_DATA, userData);
intent.putExtra(RES_MENU, resMenu);
intent.putExtra(THEME, theme);
+ intent.putExtra(SELECT_SCREEN_TITLE, selectScreenTitle);
+ intent.putExtra(SELECT_SCREEN_SUBTITLE, selectScreenSubtitle);
+ intent.putExtra(SHAKE_TURNED_ON, shakeTurnedOn);
+ intent.putExtra(SHAKE_ENABLED, shakeEnabled);
return intent;
}
@@ -84,7 +96,13 @@ public void onCreate(Bundle savedInstanceState) {
if (savedInstanceState == null) {
getSupportFragmentManager()
.beginTransaction()
- .add(R.id.shaky_fragment_container, SelectFragment.newInstance(customTheme))
+ .add(R.id.shaky_fragment_container,
+ SelectFragment.newInstance(
+ customTheme,
+ getIntent().getStringExtra(SELECT_SCREEN_TITLE),
+ getIntent().getStringExtra(SELECT_SCREEN_SUBTITLE),
+ getIntent().getBooleanExtra(SHAKE_TURNED_ON, false),
+ getIntent().getBooleanExtra(SHAKE_ENABLED, false)))
.commit();
}
}
diff --git a/shaky/src/main/java/com/linkedin/android/shaky/SelectFragment.java b/shaky/src/main/java/com/linkedin/android/shaky/SelectFragment.java
index 8c25585..a79a5e9 100644
--- a/shaky/src/main/java/com/linkedin/android/shaky/SelectFragment.java
+++ b/shaky/src/main/java/com/linkedin/android/shaky/SelectFragment.java
@@ -15,11 +15,14 @@
*/
package com.linkedin.android.shaky;
+import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StyleRes;
import androidx.fragment.app.Fragment;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.appcompat.widget.Toolbar;
@@ -27,23 +30,41 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.CompoundButton;
+import android.widget.Switch;
+import android.widget.TextView;
/**
* Kicks off the feedback flow, pretty selector to choose the type of feedback.
*/
public class SelectFragment extends Fragment {
+ public static final String UPDATE_SHAKE_DETECTION_STATUS = "UpdateShakeDetectionStatus";
+ public static final String SHAKE_TURNED_ON = "ShakeTurnedOn";
+
private static final String KEY_THEME = "theme";
+ private static final String KEY_TITLE = "title";
+ private static final String KEY_SUBTITLE = "subtitle";
+ private static final String KEY_SHAKE_TURNED_ON = "shakeTurnedOn";
+ private static final String KEY_SHAKE_ENABLED = "shakeEnabled";
@Nullable private LayoutInflater inflater;
@NonNull
- static SelectFragment newInstance(@Nullable @StyleRes Integer theme) {
+ static SelectFragment newInstance(@Nullable @StyleRes Integer theme,
+ @Nullable String title,
+ @Nullable String subtitle,
+ boolean shakeTurnedOn,
+ boolean shakeEnabled) {
SelectFragment fragment = new SelectFragment();
Bundle args = new Bundle();
if (theme != null) {
args.putInt(KEY_THEME, theme);
}
+ args.putString(KEY_TITLE, title);
+ args.putString(KEY_SUBTITLE, subtitle);
+ args.putBoolean(KEY_SHAKE_TURNED_ON, shakeTurnedOn);
+ args.putBoolean(KEY_SHAKE_ENABLED, shakeEnabled);
fragment.setArguments(args);
return fragment;
}
@@ -65,6 +86,8 @@ public void onViewCreated(View view, Bundle savedInstanceState) {
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.shaky_recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
recyclerView.setAdapter(adapter);
+ recyclerView.addItemDecoration(new DividerItemDecoration(recyclerView.getContext(),
+ DividerItemDecoration.VERTICAL));
Toolbar toolbar = (Toolbar) view.findViewById(R.id.shaky_toolbar);
toolbar.setTitle(R.string.shaky_feedback_title);
@@ -75,6 +98,34 @@ public void onClick(View v) {
getActivity().onBackPressed();
}
});
+
+ TextView title = view.findViewById(R.id.shaky_select_title);
+ String customTitle = getArguments().getString(KEY_TITLE);
+ if (customTitle != null) {
+ title.setText(customTitle);
+ }
+
+ TextView subtitle = view.findViewById(R.id.shaky_select_subtitle);
+ String customSubtitle = getArguments().getString(KEY_SUBTITLE);
+ if (customSubtitle != null) {
+ subtitle.setText(customSubtitle);
+ subtitle.setVisibility(View.VISIBLE);
+ }
+
+ Switch shakeToggle = view.findViewById(R.id.shaky_select_shake_switch);
+ shakeToggle.setChecked(getArguments().getBoolean(KEY_SHAKE_TURNED_ON, false));
+ if (getArguments().getBoolean(KEY_SHAKE_ENABLED)) {
+ shakeToggle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ Intent intent = new Intent(UPDATE_SHAKE_DETECTION_STATUS);
+ intent.putExtra(SHAKE_TURNED_ON, isChecked);
+ LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(intent);
+ }
+ });
+ } else {
+ shakeToggle.setVisibility(View.GONE);
+ }
}
@NonNull
diff --git a/shaky/src/main/java/com/linkedin/android/shaky/ShakeDelegate.java b/shaky/src/main/java/com/linkedin/android/shaky/ShakeDelegate.java
index 35d17f8..433c07c 100644
--- a/shaky/src/main/java/com/linkedin/android/shaky/ShakeDelegate.java
+++ b/shaky/src/main/java/com/linkedin/android/shaky/ShakeDelegate.java
@@ -53,7 +53,8 @@ public abstract class ShakeDelegate {
@MenuRes protected int resMenu = FormFragment.DEFAULT_MENU;
/**
- * @return true if shake detection should be enabled, false otherwise
+ * @return true if shake detection should be enabled, false otherwise. If enabled, the user can
+ * still turn off shaking through the UI. If disabled, UI to toggle shaking won't be shown.
*/
public boolean isEnabled() {
return true;
@@ -122,6 +123,22 @@ public Integer getPopupTheme() {
return null;
}
+ /**
+ * @return the title of the category select screen, or null if the default title should be shown
+ */
+ @Nullable
+ public String getSelectScreenTitle() {
+ return null;
+ }
+
+ /**
+ * @return the subtitle of the category select screen, or null if no subtitle should be shown
+ */
+ @Nullable
+ public String getSelectScreenSubtitle() {
+ return null;
+ }
+
/**
* Called from the background thread during the feedback collection flow. This method
* can be used to collect extra debug information to include in the feedback
diff --git a/shaky/src/main/java/com/linkedin/android/shaky/Shaky.java b/shaky/src/main/java/com/linkedin/android/shaky/Shaky.java
index d2d9440..ce8157e 100644
--- a/shaky/src/main/java/com/linkedin/android/shaky/Shaky.java
+++ b/shaky/src/main/java/com/linkedin/android/shaky/Shaky.java
@@ -27,6 +27,7 @@
import android.os.Bundle;
import android.view.View;
+import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
@@ -36,6 +37,8 @@
import com.jraska.falcon.Falcon;
import com.squareup.seismic.ShakeDetector;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
@@ -48,6 +51,16 @@
*/
public class Shaky implements ShakeDetector.Listener {
+ public static final int SHAKE_ON = 25;
+ public static final int SHAKE_OFF = 26;
+
+ @IntDef({
+ SHAKE_ON,
+ SHAKE_OFF
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ShakeDetectionStatus {}
+
private static final String SEND_FEEDBACK_TAG = "SendFeedback";
private static final String COLLECT_DATA_TAG = "CollectFeedbackData";
@@ -62,6 +75,7 @@ public class Shaky implements ShakeDetector.Listener {
private Activity activity;
private Context appContext;
private long lastShakeTime;
+ private boolean shakeTurnedOn = true;
private CollectDataTask collectDataTask;
Shaky(@NonNull Context context, @NonNull ShakeDelegate delegate, @Nullable ShakyFlowCallback callback) {
@@ -78,6 +92,7 @@ public class Shaky implements ShakeDetector.Listener {
filter.addAction(FeedbackActivity.ACTION_END_FEEDBACK_FLOW);
filter.addAction(FeedbackActivity.ACTION_ACTIVITY_CLOSED_BY_USER);
filter.addAction(ShakySettingDialog.UPDATE_SHAKY_SENSITIVITY);
+ filter.addAction(SelectFragment.UPDATE_SHAKE_DETECTION_STATUS);
LocalBroadcastManager.getInstance(appContext).registerReceiver(createReceiver(), filter);
}
@@ -122,6 +137,10 @@ public void setSensitivity(@ShakeDelegate.SensitivityLevel int sensitivityLevel)
shakeDetector.setSensitivity(getDetectorSensitivityLevel());
}
+ public void setShakeStatus(@ShakeDetectionStatus int status) {
+ shakeTurnedOn = status == SHAKE_ON;
+ }
+
void setActivity(@Nullable Activity activity) {
this.activity = activity;
if (activity != null) {
@@ -193,10 +212,14 @@ public void hearShake() {
}
/**
- * @return true if a shake happened in the last {@link #SHAKE_COOLDOWN_MS}, false otherwise.
+ * @return true if a shake happened in the last {@link #SHAKE_COOLDOWN_MS} or shake detection
+ * is disabled, or false otherwise
*/
@VisibleForTesting
boolean shouldIgnoreShake() {
+ if (!shakeTurnedOn) {
+ return true;
+ }
long now = System.currentTimeMillis();
if (now < lastShakeTime + SHAKE_COOLDOWN_MS) {
if (shakyFlowCallback != null) {
@@ -285,6 +308,8 @@ public void onReceive(Context context, Intent intent) {
if (shakyFlowCallback != null) {
shakyFlowCallback.onShakyFinished(ShakyFlowCallback.SHAKY_FINISHED_SENSITIVITY_UPDATED);
}
+ } else if (SelectFragment.UPDATE_SHAKE_DETECTION_STATUS.equals(intent.getAction())) {
+ shakeTurnedOn = intent.getBooleanExtra(SelectFragment.SHAKE_TURNED_ON, false);
}
}
};
@@ -322,7 +347,9 @@ private void startFeedbackActivity(@NonNull Result result) {
result.getScreenshotUri(),
result.getData(),
delegate.resMenu,
- delegate.getTheme() != null ? delegate.getTheme() : FeedbackActivity.MISSING_RESOURCE);
+ delegate.getTheme() != null ? delegate.getTheme() : FeedbackActivity.MISSING_RESOURCE,
+ delegate.getSelectScreenTitle(),
+ delegate.getSelectScreenSubtitle(), true, true);
activity.startActivity(intent);
if (shakyFlowCallback != null) {
diff --git a/shaky/src/main/res/layout/shaky_select.xml b/shaky/src/main/res/layout/shaky_select.xml
index 91d23a9..0232862 100644
--- a/shaky/src/main/res/layout/shaky_select.xml
+++ b/shaky/src/main/res/layout/shaky_select.xml
@@ -27,6 +27,51 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/shaky/src/main/res/layout/shaky_single_row.xml b/shaky/src/main/res/layout/shaky_single_row.xml
index 8dda99a..526a0b9 100644
--- a/shaky/src/main/res/layout/shaky_single_row.xml
+++ b/shaky/src/main/res/layout/shaky_single_row.xml
@@ -22,14 +22,14 @@
android:focusable="true"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeight"
- android:orientation="horizontal"
- android:padding="@dimen/shaky_button_padding">
+ android:orientation="horizontal">
+
+
+
diff --git a/shaky/src/main/res/values/shaky_dimens.xml b/shaky/src/main/res/values/shaky_dimens.xml
index e1f36dd..e99acf9 100644
--- a/shaky/src/main/res/values/shaky_dimens.xml
+++ b/shaky/src/main/res/values/shaky_dimens.xml
@@ -21,13 +21,14 @@
8dp
8dp
16dp
- 8dp
+ 12dp
+ 16dp
20sp
16sp
18sp
- 2sp
+ 1dp
200dp
diff --git a/shaky/src/main/res/values/shaky_styles.xml b/shaky/src/main/res/values/shaky_styles.xml
index 4ec9669..c287c6d 100644
--- a/shaky/src/main/res/values/shaky_styles.xml
+++ b/shaky/src/main/res/values/shaky_styles.xml
@@ -51,8 +51,11 @@
- ?attr/shakyBackgroundColor
+ - ?attr/shakyTitleColor
+ - ?attr/shakyContentColor
- ?attr/shakyTitleColor
- ?attr/shakyContentColor
+ - ?attr/shakyCategoryListItemSubtitleColor
- @color/shaky_gray