Skip to content

Commit

Permalink
Merge pull request #970 from psykzz/drag-favs
Browse files Browse the repository at this point in the history
Support drag and drop moving of favourites
Neamar authored May 6, 2018

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
2 parents faace6a + bea096d commit be1fe18
Showing 4 changed files with 231 additions and 49 deletions.
47 changes: 47 additions & 0 deletions app/src/main/java/fr/neamar/kiss/DataHandler.java
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@
import android.graphics.Bitmap.CompressFormat;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;

@@ -431,6 +432,52 @@ public ArrayList<Pojo> getFavorites(int limit) {
return favorites;
}

/**
* This method is used to set the specific position of an app in the fav array.
*
* @param context The mainActivity context
* @param id the app you want to set the position of
* @param position the new position of the fav
*/
public void setFavoritePosition(MainActivity context, String id, int position) {
String favApps = PreferenceManager.getDefaultSharedPreferences(this.context).
getString("favorite-apps-list", "");
List<String> favAppsList = new ArrayList<>(Arrays.asList(favApps.split(";")));

int currentPos = favAppsList.indexOf(id);
if(currentPos == -1) {
Log.e(TAG, "Couldn't find id in favAppsList");
return;
}
// Clamp the position so we dont just extend past the end of the array.
position = Math.min(position, favAppsList.size() - 1);

favAppsList.remove(currentPos);
// Because we're removing ourselves from the array, positions may change, we should take that into account
favAppsList.add(currentPos > position ? position + 1 : position, id);
String newFavList = TextUtils.join(";", favAppsList);

PreferenceManager.getDefaultSharedPreferences(context).edit()
.putString("favorite-apps-list", newFavList + ";").apply();

context.onFavoriteChange();
}

/**
* Helper function to get the position of a favorite. Used mainly by the drag and drop system to know where to place the dropped app.
*
* @param context mainActivity context
* @param id the app you want to get the position of.
* @return
*/
public int getFavoritePosition(MainActivity context, String id) {
String favApps = PreferenceManager.getDefaultSharedPreferences(this.context).
getString("favorite-apps-list", "");
List<String> favAppsList = new ArrayList<>(Arrays.asList(favApps.split(";")));

return favAppsList.indexOf(id);
}

public void addToFavorites(MainActivity context, String id) {

String favApps = PreferenceManager.getDefaultSharedPreferences(context).
53 changes: 25 additions & 28 deletions app/src/main/java/fr/neamar/kiss/MainActivity.java
Original file line number Diff line number Diff line change
@@ -18,6 +18,7 @@
import android.text.TextWatcher;
import android.util.Log;
import android.view.ContextMenu;
import android.view.DragEvent;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
@@ -276,6 +277,18 @@ public void onTextChanged(CharSequence s, int start, int before, int count) {
}
});


// Fixes bug when dropping onto a textEdit widget which can cause a NPE
// This fix should be on ALL TextEdit Widgets !!!
// See : https://stackoverflow.com/a/23483957
searchEditText.setOnDragListener( new View.OnDragListener() {
@Override
public boolean onDrag( View v, DragEvent event) {
return true;
}
});


// On validate, launch first record
searchEditText.setOnEditorActionListener(new OnEditorActionListener() {
@Override
@@ -360,9 +373,7 @@ protected void onResume() {
return;
}

if (mPopup != null) {
mPopup.dismiss();
}
dismissPopup();

if (KissApplication.getApplication(this).getDataHandler().allProvidersHaveLoaded) {
displayLoader(false);
@@ -531,24 +542,9 @@ public void onLauncherButtonClicked(View launcherButton) {

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mPopup != null) {
View popupContentView = mPopup.getContentView();
int[] popupPos = {0, 0};
popupContentView.getLocationOnScreen(popupPos);
final float offsetX = -popupPos[0];
final float offsetY = -popupPos[1];
ev.offsetLocation(offsetX, offsetY);
try {
boolean handled = popupContentView.dispatchTouchEvent(ev);
ev.offsetLocation(-offsetX, -offsetY);
if (!handled)
handled = super.dispatchTouchEvent(ev);
return handled;
}
catch(IllegalArgumentException e) {
// Quick temporary fix for #925
return false;
}
if (mPopup != null && ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
dismissPopup();
return true;
}
return super.dispatchTouchEvent(ev);
}
@@ -675,9 +671,7 @@ public void updateSearchRecords() {
*/
private void updateSearchRecords(String query) {
resetTask();

if (mPopup != null)
mPopup.dismiss();
dismissPopup();

forwarderManager.updateSearchRecords(query);

@@ -722,8 +716,7 @@ public void launchOccurred() {
public void registerPopup(ListPopup popup) {
if (mPopup == popup)
return;
if (mPopup != null)
mPopup.dismiss();
dismissPopup();
mPopup = popup;
popup.setVisibilityHelper(systemUiVisibilityHelper);
popup.setOnDismissListener(new PopupWindow.OnDismissListener() {
@@ -764,8 +757,7 @@ public void hideKeyboard() {
}

systemUiVisibilityHelper.onKeyboardVisibilityChanged(false);
if (mPopup != null)
mPopup.dismiss();
dismissPopup();
}

@Override
@@ -795,4 +787,9 @@ public void beforeListChange() {
public void afterListChange() {
list.animateChange();
}

public void dismissPopup() {
if (mPopup != null)
mPopup.dismiss();
}
}
179 changes: 158 additions & 21 deletions app/src/main/java/fr/neamar/kiss/forwarder/Favorites.java
Original file line number Diff line number Diff line change
@@ -10,6 +10,9 @@
import android.net.Uri;
import android.provider.ContactsContract;
import android.util.Log;
import android.view.DragEvent;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;

@@ -25,7 +28,7 @@
import fr.neamar.kiss.ui.ListPopup;
import fr.neamar.kiss.ui.RoundedQuickContactBadge;

public class Favorites extends Forwarder implements View.OnClickListener, View.OnLongClickListener {
public class Favorites extends Forwarder implements View.OnClickListener, View.OnLongClickListener, View.OnTouchListener, View.OnDragListener {
private static final String TAG = "FavoriteForwarder";

// Package used by Android when an Intent can be matched with more than one app
@@ -53,6 +56,23 @@ public class Favorites extends Forwarder implements View.OnClickListener, View.O
*/
private ArrayList<Pojo> favoritesPojo = new ArrayList<>();

/**
* Globals for drag and drop support
*/
private static long startTime = 0; // Start of the drag and drop, used for long press menu
private float currentX = 0.0f; // Current X position of the drag op, this is 0 on DRAG END so we keep a copy here
private Pojo overApp; // the view for the DRAG_END event is typically wrong, so we store a reference of the last dragged over app.

/**
* Configuration for drag and drop
*/
private final int MOVE_SENSITIVITY = 5; // How much you need to move your finger to be considered "moving"
private final int LONG_PRESS_DELAY = 250; // How long to hold your finger inplace to trigger the app menu.

// Use so we dont over process on the drag events.
private boolean isDragging = false;
private boolean contextMenuShown = false;

Favorites(MainActivity mainActivity) {
super(mainActivity);
}
@@ -73,8 +93,8 @@ void onCreate() {
favoritesViews[i] = mainActivity.favoritesBar.findViewById(FAV_IDS[i]);
}

registerClickOnFavorites();
registerLongClickOnFavorites();
registerTouchOnFavorites();
registerDragOnFavorites();

if (prefs.getBoolean("firstRun", true)) {
// It is the first run. Make sure this is not an update by checking if history is empty
@@ -224,52 +244,169 @@ private void addDefaultAppsToFavs() {
}
}

private void registerClickOnFavorites() {
private void registerTouchOnFavorites() {
for (View v : favoritesViews) {
v.setOnClickListener(this);
v.setOnTouchListener(this);
}
}

private void registerLongClickOnFavorites() {
private void registerDragOnFavorites() {
for (View v : favoritesViews) {
v.setOnLongClickListener(this);
v.setOnDragListener(this);
}
}

@Override
public void onClick(View v) {
int favNumber = Integer.parseInt((String) v.getTag());
private Result getFavResult(int favNumber) {
if (favNumber >= favoritesPojo.size()) {
// Clicking on a favorite before everything is loaded.
Log.i(TAG, "Clicking on an unitialized favorite.");
return;
return null;
}

// Favorites handling
Pojo pojo = favoritesPojo.get(favNumber);
final Result result = Result.fromPojo(mainActivity, pojo);
return Result.fromPojo(mainActivity, pojo);
}

@Override
public void onClick(View v) {
int favNumber = Integer.parseInt((String) v.getTag());
final Result result = getFavResult(favNumber);
result.fastLaunch(mainActivity, v);
v.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);

}

@Override
public boolean onLongClick(View v) {
int favNumber = Integer.parseInt((String) v.getTag());
if (favNumber >= favoritesPojo.size()) {
// Clicking on a favorite before everything is loaded.
Log.i(TAG, "Long clicking on an unitialized favorite.");
return false;
}
// Favorites handling
Pojo pojo = favoritesPojo.get(favNumber);
final Result result = Result.fromPojo(mainActivity, pojo);
final Result result = getFavResult(favNumber);
ListPopup popup = result.getPopupMenu(mainActivity, mainActivity.adapter, v);
mainActivity.registerPopup(popup);
popup.show(v);
v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
return true;
}

private boolean isExternalFavoriteBarEnabled() {
return prefs.getBoolean("enable-favorites-bar", true);
}

@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
startTime = motionEvent.getEventTime();
contextMenuShown = false;
return true;
}
// No need to do the extra work
if(isDragging) {
return true;
}

// Click handlers first
long holdTime = motionEvent.getEventTime() - startTime;
if (holdTime < LONG_PRESS_DELAY && motionEvent.getAction() == MotionEvent.ACTION_UP) {
this.onClick(view);
return true;
}
if(!contextMenuShown && holdTime > LONG_PRESS_DELAY) {
contextMenuShown = true;
this.onLongClick(view);
return true;
}

// Drag handlers
int intCurrentY = Math.round(motionEvent.getY());
int intCurrentX = Math.round(motionEvent.getX());
int intStartY = motionEvent.getHistorySize() > 0 ? Math.round(motionEvent.getHistoricalY(0)) : intCurrentY;
int intStartX = motionEvent.getHistorySize() > 0 ? Math.round(motionEvent.getHistoricalX(0)) : intCurrentX;
boolean hasMoved = (Math.abs(intCurrentX - intStartX) > MOVE_SENSITIVITY) || (Math.abs(intCurrentY - intStartY) > MOVE_SENSITIVITY);

if (hasMoved) {
mainActivity.dismissPopup();
mainActivity.closeContextMenu();
View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view);
view.startDrag(null, shadowBuilder, view, 0);
view.setVisibility(View.INVISIBLE);
return true;
}

return false;
}

@Override
public boolean onDrag(View v, final DragEvent event) {
int overFavIndex;

switch (event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
isDragging = true;
break;

case DragEvent.ACTION_DRAG_ENTERED:
case DragEvent.ACTION_DRAG_EXITED:
case DragEvent.ACTION_DROP:
if (!isDragging) {
return true;
}

overFavIndex = Integer.parseInt((String) v.getTag());
overApp = favoritesPojo.get(overFavIndex);

currentX = (event.getX() != 0.0f) ? event.getX() : currentX;
break;

case DragEvent.ACTION_DRAG_ENDED:
// Only need to handle this action once.
if(!isDragging) {
return true;
}
isDragging = false;

final View draggedView = (View) event.getLocalState();

// Sometimes we dont trigger onDrag over another app, in which case just drop.
if (overApp == null) {
Log.w(TAG, "Wasn't dragged over an app, returning app to starting position");
draggedView.post(new Runnable() {
@Override
public void run() {
draggedView.setVisibility(View.VISIBLE);
}
});
break;
}

int draggedFavIndex = Integer.parseInt((String) draggedView.getTag());
final Pojo draggedApp = favoritesPojo.get(draggedFavIndex);

int left = v.getLeft();
int right = v.getRight();
int width = right - left;

// currentX is relative to the view not the screen, so add the current X of the view.
final boolean leftSide = (left + currentX < left + (width / 2));

final int pos = KissApplication.getApplication(mainActivity).getDataHandler().getFavoritePosition(mainActivity, overApp.id);
draggedView.post(new Runnable() {
@Override
public void run() {
// Signals to a View that the drag and drop operation has concluded.
// If event result is set, this means the dragged view was dropped in target
if (event.getResult()) {
KissApplication.getApplication(mainActivity).getDataHandler().setFavoritePosition(mainActivity, draggedApp.id, leftSide ? pos - 1 : pos);
mainActivity.onFavoriteChange();
} else {
draggedView.setVisibility(View.VISIBLE);
}
}
});

break;
default:
break;
}
return true;
}
}

1 change: 1 addition & 0 deletions app/src/main/res/layout/main.xml
Original file line number Diff line number Diff line change
@@ -58,6 +58,7 @@

<LinearLayout
android:id="@+id/externalFavoriteBar"
android:animateLayoutChanges="true"
android:layout_width="match_parent"
android:layout_height="58dp"
android:layout_above="@+id/searchEditLayout"

0 comments on commit be1fe18

Please sign in to comment.