--- /dev/null
+/*
+ * Copyright © 2021 Damyan Ivanov.
+ * This file is part of MoLe.
+ * MoLe is free software: you can distribute it and/or modify it
+ * under the term of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your opinion), any later version.
+ *
+ * MoLe 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 terms for details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.ui;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.TimeInterpolator;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewParent;
+import android.view.ViewPropertyAnimator;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+
+import net.ktnx.mobileledger.utils.DimensionUtils;
+import net.ktnx.mobileledger.utils.Logger;
+
+public class FabManager {
+ private static final boolean FAB_SHOWN = true;
+ private static final boolean FAB_HIDDEN = false;
+ private final FloatingActionButton fab;
+ private boolean wantedFabState = FAB_SHOWN;
+ private ViewPropertyAnimator fabSlideAnimator;
+ private int fabVerticalOffset;
+ public FabManager(FloatingActionButton fab) {
+ this.fab = fab;
+ }
+ @SuppressLint("ClickableViewAccessibility")
+ public static void handle(FabHandler activity, RecyclerView recyclerView) {
+ final float triggerAbsolutePixels = DimensionUtils.dp2px(activity.getContext(), 20f);
+ final float triggerRelativePixels = triggerAbsolutePixels / 4f;
+ recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
+ Logger.debug("touch", "Scrolled " + dy);
+ if (dy <= 0)
+ activity.showManagedFab();
+ else
+ activity.hideManagedFab();
+
+ super.onScrolled(recyclerView, dx, dy);
+ }
+ });
+ recyclerView.addOnItemTouchListener(new RecyclerView.SimpleOnItemTouchListener() {
+ private float absoluteAnchor = -1;
+ @Override
+ public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
+ switch (e.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ absoluteAnchor = e.getRawY();
+// Logger.debug("touch",
+// String.format(Locale.US, "Touch down at %4.2f", absoluteAnchor));
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (absoluteAnchor < 0)
+ break;
+
+ final float absoluteY = e.getRawY();
+// Logger.debug("touch", String.format(Locale.US, "Move to %4.2f", absoluteY));
+
+ if (absoluteY > absoluteAnchor + triggerAbsolutePixels) {
+ // swipe down
+// Logger.debug("touch", "SHOW");
+ activity.showManagedFab();
+ absoluteAnchor = absoluteY;
+ }
+ else if (absoluteY < absoluteAnchor - triggerAbsolutePixels) {
+ // swipe up
+// Logger.debug("touch", "HIDE");
+ activity.hideManagedFab();
+ absoluteAnchor = absoluteY;
+ }
+
+ break;
+ }
+ return false;
+ }
+ });
+ }
+ private void slideFabTo(int target, long duration, TimeInterpolator interpolator) {
+ fabSlideAnimator = fab.animate()
+ .translationY((float) target)
+ .setInterpolator(interpolator)
+ .setDuration(duration)
+ .setListener(new AnimatorListenerAdapter() {
+ public void onAnimationEnd(Animator animation) {
+ fabSlideAnimator = null;
+ }
+ });
+ }
+ public void showFab() {
+ if (wantedFabState == FAB_SHOWN)
+ return;
+
+// b.btnAddTransaction.show();
+ if (this.fabSlideAnimator != null) {
+ this.fabSlideAnimator.cancel();
+ fab.clearAnimation();
+ }
+
+ wantedFabState = FAB_SHOWN;
+ slideFabTo(0, 200L,
+ com.google.android.material.animation.AnimationUtils.LINEAR_OUT_SLOW_IN_INTERPOLATOR);
+ }
+ public void hideFab() {
+ if (wantedFabState == FAB_HIDDEN)
+ return;
+
+ calcVerticalFabOffset();
+
+// b.btnAddTransaction.hide();
+ if (this.fabSlideAnimator != null) {
+ this.fabSlideAnimator.cancel();
+ fab.clearAnimation();
+ }
+
+ wantedFabState = FAB_HIDDEN;
+ slideFabTo(fabVerticalOffset, 150L,
+ com.google.android.material.animation.AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR);
+ }
+ private void calcVerticalFabOffset() {
+ if (fabVerticalOffset > 0)
+ return;// already calculated
+ int top = fab.getTop();
+ ViewParent parent = fab.getParent();
+ while (parent != null && !(parent instanceof View))
+ parent = parent.getParent();
+
+ if (parent instanceof View) {
+ View parentView = (View) parent;
+ int parentHeight = parentView.getHeight();
+ fabVerticalOffset = parentHeight - top;
+ }
+ }
+ public interface FabHandler {
+ Context getContext();
+
+ void showManagedFab();
+
+ void hideManagedFab();
+ }
+}
package net.ktnx.mobileledger.ui;
-import android.view.MotionEvent;
-
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
import net.ktnx.mobileledger.ui.activity.MainActivity;
import net.ktnx.mobileledger.ui.transaction_list.TransactionListAdapter;
import net.ktnx.mobileledger.utils.Colors;
-import net.ktnx.mobileledger.utils.DimensionUtils;
public class MobileLedgerListFragment extends Fragment {
public SwipeRefreshLayout refreshLayout;
return;
refreshLayout.setRefreshing(isRunning);
}
- protected void manageFabOnScroll() {
- final MainActivity mainActivity = getMainActivity();
- int triggerPixels = DimensionUtils.dp2px(mainActivity, 10f);
- root.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
- private float upAnchor = -1;
- private float lastY;
- @Override
- public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
- switch (e.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- lastY = upAnchor = e.getAxisValue(MotionEvent.AXIS_Y);
- break;
- case MotionEvent.ACTION_MOVE:
- final float currentY = e.getAxisValue(MotionEvent.AXIS_Y);
- if (currentY > lastY) {
- // swipe down
- upAnchor = lastY;
-
- mainActivity.fabShouldShow();
- }
- else {
- // swipe up
- if (currentY < upAnchor - triggerPixels)
- mainActivity.fabHide();
- }
-
- lastY = currentY;
-
- break;
- }
- return false;
- }
- @Override
- public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
- }
- @Override
- public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
- }
- });
- }
}
import net.ktnx.mobileledger.R;
import net.ktnx.mobileledger.model.AccountListItem;
import net.ktnx.mobileledger.model.Data;
+import net.ktnx.mobileledger.ui.FabManager;
import net.ktnx.mobileledger.ui.MainModel;
import net.ktnx.mobileledger.ui.MobileLedgerListFragment;
import net.ktnx.mobileledger.ui.activity.MainActivity;
mainActivity.fabShouldShow();
- manageFabOnScroll();
+ if (mainActivity instanceof FabManager.FabHandler)
+ FabManager.handle((FabManager.FabHandler) mainActivity, root);
refreshLayout = view.findViewById(R.id.account_swipe_refresh_layout);
Colors.themeWatch.observe(getViewLifecycleOwner(), this::themeChanged);
package net.ktnx.mobileledger.ui.activity;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.TimeInterpolator;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.ShortcutInfo;
import android.text.format.DateUtils;
import android.util.Log;
import android.view.View;
-import android.view.ViewPropertyAnimator;
import android.view.animation.AnimationUtils;
import android.widget.TextView;
import net.ktnx.mobileledger.databinding.ActivityMainBinding;
import net.ktnx.mobileledger.model.Data;
import net.ktnx.mobileledger.model.MobileLedgerProfile;
+import net.ktnx.mobileledger.ui.FabManager;
import net.ktnx.mobileledger.ui.MainModel;
import net.ktnx.mobileledger.ui.account_summary.AccountSummaryFragment;
import net.ktnx.mobileledger.ui.new_transaction.NewTransactionActivity;
* TODO: reports
* */
-public class MainActivity extends ProfileThemedActivity {
+public class MainActivity extends ProfileThemedActivity implements FabManager.FabHandler {
public static final String STATE_CURRENT_PAGE = "current_page";
public static final String BUNDLE_SAVED_STATE = "bundle_savedState";
public static final String STATE_ACC_FILTER = "account_filter";
private MainModel mainModel;
private ActivityMainBinding b;
private int fabVerticalOffset;
- private ViewPropertyAnimator fabSlideAnimator;
- private boolean wantedFabState = FAB_SHOWN;
+ private FabManager fabManager;
@Override
protected void onStart() {
super.onStart();
mProfileListAdapter.notifyDataSetChanged();
});
+ fabManager = new FabManager(b.btnAddTransaction);
+
LinearLayoutManager llm = new LinearLayoutManager(this);
llm.setOrientation(RecyclerView.VERTICAL);
@Override
public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
if (slideOffset > 0.2)
- fabHide();
+ fabManager.hideFab();
}
@Override
public void onDrawerClosed(View drawerView) {
super.onDrawerOpened(drawerView);
mProfileListAdapter.setAnimationsEnabled(true);
Data.drawerOpen.setValue(true);
- fabHide();
+ fabManager.hideFab();
}
};
b.drawerLayout.addDrawerListener(drawerListener);
}
}
public void fabShouldShow() {
- if (fabVerticalOffset <= 0) {
- int top = b.btnAddTransaction.getTop();
- int parentHeight = b.mainAppLayout.getHeight();
- fabVerticalOffset = parentHeight - top;
- }
- if ((profile != null) && profile.isPostingPermitted() && !b.drawerLayout.isOpen()) {
- fabShow();
- }
- else
- fabHide();
+ if ((profile != null) && profile.isPostingPermitted() && !b.drawerLayout.isOpen())
+ fabManager.showFab();
}
- private void fabShow() {
- if (wantedFabState == FAB_SHOWN)
- return;
-
-// b.btnAddTransaction.show();
- if (this.fabSlideAnimator != null) {
- this.fabSlideAnimator.cancel();
- b.btnAddTransaction.clearAnimation();
- }
-
- wantedFabState = FAB_SHOWN;
- slideFabTo(b.btnAddTransaction, 0, 200L,
- com.google.android.material.animation.AnimationUtils.LINEAR_OUT_SLOW_IN_INTERPOLATOR);
+ @Override
+ public Context getContext() {
+ return this;
}
- public void fabHide() {
- if (wantedFabState == FAB_HIDDEN)
- return;
-
- if (fabVerticalOffset <= 0)
- return;
-
-// b.btnAddTransaction.hide();
- if (this.fabSlideAnimator != null) {
- this.fabSlideAnimator.cancel();
- b.btnAddTransaction.clearAnimation();
- }
-
- wantedFabState = FAB_HIDDEN;
- slideFabTo(b.btnAddTransaction, fabVerticalOffset, 150L,
- com.google.android.material.animation.AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR);
+ @Override
+ public void showManagedFab() {
+ fabShouldShow();
}
- private void slideFabTo(View view, int target, long duration, TimeInterpolator interpolator) {
- fabSlideAnimator = view.animate()
- .translationY((float) target)
- .setInterpolator(interpolator)
- .setDuration(duration)
- .setListener(new AnimatorListenerAdapter() {
- public void onAnimationEnd(Animator animation) {
- fabSlideAnimator = null;
- }
- });
+ @Override
+ public void hideManagedFab() {
+ fabManager.hideFab();
}
-
public static class SectionsPagerAdapter extends FragmentStateAdapter {
public SectionsPagerAdapter(@NonNull FragmentActivity fragmentActivity) {
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelStoreOwner;
import androidx.navigation.NavController;
import net.ktnx.mobileledger.R;
import net.ktnx.mobileledger.databinding.TemplateDetailsFragmentBinding;
+import net.ktnx.mobileledger.ui.FabManager;
import net.ktnx.mobileledger.utils.Logger;
public class TemplateDetailsFragment extends Fragment {
mViewModel.getItems(mPatternId)
.observe(getViewLifecycleOwner(), adapter::setItems);
+ FragmentActivity activity = requireActivity();
+ if (activity instanceof FabManager.FabHandler)
+ FabManager.handle((FabManager.FabHandler) activity, b.patternDetailsRecyclerView);
+
return b.getRoot();
}
interface InteractionListener {
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleEventObserver;
import androidx.lifecycle.LifecycleOwner;
import net.ktnx.mobileledger.databinding.FragmentTemplateListBinding;
import net.ktnx.mobileledger.db.DB;
import net.ktnx.mobileledger.db.TemplateHeader;
+import net.ktnx.mobileledger.ui.FabManager;
import net.ktnx.mobileledger.utils.Logger;
import org.jetbrains.annotations.NotNull;
LinearLayoutManager llm = new LinearLayoutManager(getContext());
llm.setOrientation(RecyclerView.VERTICAL);
b.templateList.setLayoutManager(llm);
+
+ FragmentActivity activity = requireActivity();
+ if (activity instanceof FabManager.FabHandler)
+ FabManager.handle((FabManager.FabHandler) activity, b.templateList);
+
return b.getRoot();
}
@Override
package net.ktnx.mobileledger.ui.templates;
+import android.content.Context;
import android.os.Bundle;
import android.view.MenuItem;
import net.ktnx.mobileledger.databinding.ActivityTemplatesBinding;
import net.ktnx.mobileledger.db.DB;
import net.ktnx.mobileledger.db.TemplateWithAccounts;
+import net.ktnx.mobileledger.ui.FabManager;
import net.ktnx.mobileledger.ui.QR;
import net.ktnx.mobileledger.ui.activity.CrashReportingActivity;
import net.ktnx.mobileledger.utils.Logger;
public class TemplatesActivity extends CrashReportingActivity
implements TemplateListFragment.OnTemplateListFragmentInteractionListener,
- TemplateDetailsFragment.InteractionListener, QR.QRScanResultReceiver, QR.QRScanTrigger {
+ TemplateDetailsFragment.InteractionListener, QR.QRScanResultReceiver, QR.QRScanTrigger,
+ FabManager.FabHandler {
public static final String ARG_ADD_TEMPLATE = "add-template";
private ActivityTemplatesBinding b;
private NavController navController;
private ActivityResultLauncher<Void> qrScanLauncher;
+ private FabManager fabManager;
// @Override
// public boolean onCreateOptionsMenu(Menu menu) {
// super.onCreateOptionsMenu(menu);
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
if (destination.getId() == R.id.templateListFragment) {
- b.fabAdd.show();
- b.fabSave.hide();
b.toolbarLayout.setTitle(getString(R.string.title_activity_templates));
+ b.fab.setImageResource(R.drawable.ic_add_white_24dp);
}
- if (destination.getId() == R.id.templateDetailsFragment) {
- b.fabAdd.hide();
- b.fabSave.show();
+ else {
+ b.fab.setImageResource(R.drawable.ic_save_white_24dp);
}
});
b.toolbarLayout.setTitle(getString(R.string.title_activity_templates));
- b.fabAdd.setOnClickListener(v -> onEditTemplate(null));
- b.fabSave.setOnClickListener(v -> onSaveTemplate());
+ b.fab.setOnClickListener(v -> {
+ if (navController.getCurrentDestination()
+ .getId() == R.id.templateListFragment)
+ onEditTemplate(null);
+ else
+ onSaveTemplate();
+ });
qrScanLauncher = QR.registerLauncher(this, this);
+
+ fabManager = new FabManager(b.fab);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
public void triggerQRScan() {
qrScanLauncher.launch(null);
}
+ @Override
+ public Context getContext() {
+ return this;
+ }
+ @Override
+ public void showManagedFab() {
+ fabManager.showFab();
+ }
+ @Override
+ public void hideManagedFab() {
+ fabManager.hideFab();
+ }
}
\ No newline at end of file
import net.ktnx.mobileledger.model.Data;
import net.ktnx.mobileledger.model.MobileLedgerProfile;
import net.ktnx.mobileledger.ui.DatePickerFragment;
+import net.ktnx.mobileledger.ui.FabManager;
import net.ktnx.mobileledger.ui.MainModel;
import net.ktnx.mobileledger.ui.MobileLedgerListFragment;
import net.ktnx.mobileledger.ui.activity.MainActivity;
mainActivity.fabShouldShow();
- manageFabOnScroll();
+ if (mainActivity instanceof FabManager.FabHandler)
+ FabManager.handle((FabManager.FabHandler) mainActivity, root);
LinearLayoutManager llm = new LinearLayoutManager(mainActivity);
</androidx.core.widget.NestedScrollView>
<com.google.android.material.floatingactionbutton.FloatingActionButton
- android:id="@+id/fabAdd"
+ android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
- app:srcCompat="@drawable/ic_add_white_24dp"
android:contentDescription="@string/add_button_description"
- app:layout_behavior="@string/hide_bottom_view_on_scroll_behavior"
- />
-
- <com.google.android.material.floatingactionbutton.FloatingActionButton
- android:id="@+id/fabSave"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom|end"
- android:layout_margin="@dimen/fab_margin"
- app:srcCompat="@drawable/ic_save_white_24dp"
- android:contentDescription="@string/save_button_description"
- app:layout_behavior="@string/hide_bottom_view_on_scroll_behavior"
+ app:srcCompat="@drawable/ic_add_white_24dp"
/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>