From: Damyan Ivanov Date: Sun, 24 Nov 2019 20:43:43 +0000 (+0200) Subject: move new transaction UI into a fragment, have a clean saving progress X-Git-Tag: v0.11.0~38 X-Git-Url: https://git.ktnx.net/?a=commitdiff_plain;h=30d7068474c9a3c8b1d0763f23950fe75ac9ea03;p=mobile-ledger.git move new transaction UI into a fragment, have a clean saving progress --- diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/NewTransactionSavingFragment.java b/app/src/main/java/net/ktnx/mobileledger/ui/NewTransactionSavingFragment.java new file mode 100644 index 00000000..022885f3 --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/ui/NewTransactionSavingFragment.java @@ -0,0 +1,49 @@ +/* + * Copyright © 2019 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 . + */ + +package net.ktnx.mobileledger.ui; + + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.fragment.app.Fragment; + +import net.ktnx.mobileledger.R; + + +/** + * A simple {@link Fragment} subclass. + */ +public class NewTransactionSavingFragment extends Fragment { + + + public NewTransactionSavingFragment() { + // Required empty public constructor + } + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_new_transaction_saving, container, false); + } + +} diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionActivity.java b/app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionActivity.java index 90ce60b1..6287ac8e 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionActivity.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionActivity.java @@ -22,19 +22,10 @@ import android.os.Bundle; import android.util.TypedValue; import android.view.Menu; import android.view.MenuItem; -import android.view.View; -import android.widget.ProgressBar; -import androidx.annotation.NonNull; import androidx.appcompat.widget.Toolbar; -import androidx.lifecycle.ViewModelProviders; -import androidx.recyclerview.widget.ItemTouchHelper; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import com.google.android.material.floatingactionbutton.FloatingActionButton; -import com.google.android.material.snackbar.BaseTransientBottomBar; -import com.google.android.material.snackbar.Snackbar; +import androidx.navigation.NavController; +import androidx.navigation.Navigation; import net.ktnx.mobileledger.BuildConfig; import net.ktnx.mobileledger.R; @@ -42,9 +33,7 @@ import net.ktnx.mobileledger.async.SendTransactionTask; import net.ktnx.mobileledger.async.TaskCallback; import net.ktnx.mobileledger.model.Data; import net.ktnx.mobileledger.model.LedgerTransaction; -import net.ktnx.mobileledger.model.LedgerTransactionAccount; -import java.util.Date; import java.util.Objects; import static net.ktnx.mobileledger.utils.Logger.debug; @@ -56,13 +45,9 @@ import static net.ktnx.mobileledger.utils.Logger.debug; * (the last problem with the POST was the missing content-length header) * */ -public class NewTransactionActivity extends ProfileThemedActivity implements TaskCallback { - private static SendTransactionTask saver; - private ProgressBar progress; - private FloatingActionButton fab; - private NewTransactionItemsAdapter listAdapter; - private NewTransactionModel viewModel; - private RecyclerView list; +public class NewTransactionActivity extends ProfileThemedActivity implements TaskCallback, + NewTransactionFragment.OnNewTransactionFragmentInteractionListener { + private NavController navController; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -73,73 +58,10 @@ public class NewTransactionActivity extends ProfileThemedActivity implements Tas Data.profile.observe(this, mobileLedgerProfile -> toolbar.setSubtitle(mobileLedgerProfile.getName())); - progress = findViewById(R.id.save_transaction_progress); - fab = findViewById(R.id.fab); - fab.setOnClickListener(v -> saveTransaction()); + navController = Navigation.findNavController(this, R.id.new_transaction_nav); Objects.requireNonNull(getSupportActionBar()) .setDisplayHomeAsUpEnabled(true); - list = findViewById(R.id.new_transaction_accounts); - viewModel = ViewModelProviders.of(this) - .get(NewTransactionModel.class); - listAdapter = new NewTransactionItemsAdapter(viewModel, mProfile); - list.setAdapter(listAdapter); - list.setLayoutManager(new LinearLayoutManager(this)); - Data.profile.observe(this, profile -> listAdapter.setProfile(profile)); - listAdapter.notifyDataSetChanged(); - new ItemTouchHelper(new ItemTouchHelper.Callback() { - @Override - public int getMovementFlags(@NonNull RecyclerView recyclerView, - @NonNull RecyclerView.ViewHolder viewHolder) { - int flags = makeFlag(ItemTouchHelper.ACTION_STATE_IDLE, ItemTouchHelper.END); - // the top item is always there (date and description) - if (viewHolder.getAdapterPosition() > 0) { - if (viewModel.getAccountCount() > 2) { - flags |= makeFlag(ItemTouchHelper.ACTION_STATE_SWIPE, - ItemTouchHelper.START | ItemTouchHelper.END); - } - } - - return flags; - } - @Override - public boolean onMove(@NonNull RecyclerView recyclerView, - @NonNull RecyclerView.ViewHolder viewHolder, - @NonNull RecyclerView.ViewHolder target) { - return false; - } - @Override - public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { - if (viewModel.getAccountCount() == 2) - Snackbar.make(list, R.string.msg_at_least_two_accounts_are_required, - Snackbar.LENGTH_LONG) - .setAction("Action", null) - .show(); - else { - int pos = viewHolder.getAdapterPosition(); - viewModel.removeItem(pos - 1); - listAdapter.notifyItemRemoved(pos); - viewModel.sendCountNotifications(); // needed after items re-arrangement - viewModel.checkTransactionSubmittable(listAdapter); - } - } - }).attachToRecyclerView(list); - - viewModel.isSubmittable() - .observe(this, isSubmittable -> { - if (isSubmittable) { - if (fab != null) { - fab.show(); - fab.setEnabled(true); - } - } - else { - if (fab != null) { - fab.hide(); - } - } - }); - viewModel.checkTransactionSubmittable(listAdapter); } @Override protected void initProfile() { @@ -157,7 +79,7 @@ public class NewTransactionActivity extends ProfileThemedActivity implements Tas @Override public void finish() { super.finish(); - overridePendingTransition(R.anim.dummy, R.anim.slide_out_right); + overridePendingTransition(R.anim.dummy, R.anim.slide_out_down); } @Override public boolean onOptionsItemSelected(MenuItem item) { @@ -173,49 +95,19 @@ public class NewTransactionActivity extends ProfileThemedActivity implements Tas super.onStart(); // FIXME if (tvDescription.getText().toString().isEmpty()) tvDescription.requestFocus(); } - public void saveTransaction() { - if (fab != null) - fab.setEnabled(false); - listAdapter.toggleAllEditing(false); - progress.setVisibility(View.VISIBLE); + public void onTransactionSave(LedgerTransaction tr) { + navController.navigate(R.id.action_newTransactionFragment_to_newTransactionSavingFragment); try { - saver = new SendTransactionTask(this, mProfile); - - Date date = viewModel.getDate(); - LedgerTransaction tr = - new LedgerTransaction(null, date, viewModel.getDescription(), mProfile); - - LedgerTransactionAccount emptyAmountAccount = null; - float emptyAmountAccountBalance = 0; - for (int i = 0; i < viewModel.getAccountCount(); i++) { - LedgerTransactionAccount acc = viewModel.getAccount(i); - if (acc.getAccountName() - .trim() - .isEmpty()) - continue; - - if (acc.isAmountSet()) { - emptyAmountAccountBalance += acc.getAmount(); - } - else { - emptyAmountAccount = acc; - } - - tr.addAccount(acc); - } - - if (emptyAmountAccount != null) - emptyAmountAccount.setAmount(-emptyAmountAccountBalance); + SendTransactionTask saver = new SendTransactionTask(this, mProfile); saver.execute(tr); } catch (Exception e) { debug("new-transaction", "Unknown error", e); - progress.setVisibility(View.GONE); - listAdapter.toggleAllEditing(true); - if (fab != null) - fab.setEnabled(true); + Bundle b = new Bundle(); + b.putString("error", "unknown error"); + navController.navigate(R.id.newTransactionFragment, b); } } public void simulateCrash(MenuItem item) { @@ -239,23 +131,15 @@ public class NewTransactionActivity extends ProfileThemedActivity implements Tas return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics())); } - public void resetTransactionFromMenu(MenuItem item) { - listAdapter.reset(); - } @Override public void done(String error) { - progress.setVisibility(View.INVISIBLE); - debug("visuals", "hiding progress"); - - if (error == null) - listAdapter.reset(); + Bundle b = new Bundle(); + if (error != null) { + b.putString("error", error); + navController.navigate(R.id.action_newTransactionSavingFragment_Failure); + } else - Snackbar.make(list, error, BaseTransientBottomBar.LENGTH_LONG) - .show(); - - listAdapter.toggleAllEditing(true); - - viewModel.checkTransactionSubmittable(listAdapter); + navController.navigate(R.id.action_newTransactionSavingFragment_Success, b); } private class AsyncCrasher extends AsyncTask { diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionFragment.java b/app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionFragment.java new file mode 100644 index 00000000..2775716b --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionFragment.java @@ -0,0 +1,237 @@ +/* + * Copyright © 2019 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 . + */ + +package net.ktnx.mobileledger.ui.activity; + +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; +import android.renderscript.RSInvalidStateException; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProviders; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.google.android.material.snackbar.Snackbar; + +import net.ktnx.mobileledger.R; +import net.ktnx.mobileledger.model.Data; +import net.ktnx.mobileledger.model.LedgerTransaction; +import net.ktnx.mobileledger.model.LedgerTransactionAccount; +import net.ktnx.mobileledger.model.MobileLedgerProfile; + +import org.jetbrains.annotations.NotNull; + +import java.util.Date; + +/** + * A simple {@link Fragment} subclass. + * Activities that contain this fragment must implement the + * {@link OnNewTransactionFragmentInteractionListener} interface + * to handle interaction events. + */ +public class NewTransactionFragment extends Fragment { + private NewTransactionItemsAdapter listAdapter; + private NewTransactionModel viewModel; + private RecyclerView list; + private FloatingActionButton fab; + private OnNewTransactionFragmentInteractionListener mListener; + private MobileLedgerProfile mProfile; + public NewTransactionFragment() { + // Required empty public constructor + setHasOptionsMenu(true); + } + @Override + public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + inflater.inflate(R.menu.new_transaction_fragment, menu); + menu.findItem(R.id.action_reset_new_transaction_activity) + .setOnMenuItemClickListener(item -> { + listAdapter.reset(); + return true; + }); + } + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_new_transaction, container, false); + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + Activity activity = getActivity(); + if (activity == null) + throw new RSInvalidStateException( + "getActivity() returned null within onActivityCreated()"); + + list = activity.findViewById(R.id.new_transaction_accounts); + viewModel = ViewModelProviders.of(this) + .get(NewTransactionModel.class); + mProfile = Data.profile.getValue(); + listAdapter = new NewTransactionItemsAdapter(viewModel, mProfile); + list.setAdapter(listAdapter); + list.setLayoutManager(new LinearLayoutManager(activity)); + Data.profile.observe(this, profile -> { + mProfile = profile; + listAdapter.setProfile(profile); + }); + listAdapter.notifyDataSetChanged(); + new ItemTouchHelper(new ItemTouchHelper.Callback() { + @Override + public int getMovementFlags(@NonNull RecyclerView recyclerView, + @NonNull RecyclerView.ViewHolder viewHolder) { + int flags = makeFlag(ItemTouchHelper.ACTION_STATE_IDLE, ItemTouchHelper.END); + // the top item is always there (date and description) + if (viewHolder.getAdapterPosition() > 0) { + if (viewModel.getAccountCount() > 2) { + flags |= makeFlag(ItemTouchHelper.ACTION_STATE_SWIPE, + ItemTouchHelper.START | ItemTouchHelper.END); + } + } + + return flags; + } + @Override + public boolean onMove(@NonNull RecyclerView recyclerView, + @NonNull RecyclerView.ViewHolder viewHolder, + @NonNull RecyclerView.ViewHolder target) { + return false; + } + @Override + public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { + if (viewModel.getAccountCount() == 2) + Snackbar.make(list, R.string.msg_at_least_two_accounts_are_required, + Snackbar.LENGTH_LONG) + .setAction("Action", null) + .show(); + else { + int pos = viewHolder.getAdapterPosition(); + viewModel.removeItem(pos - 1); + listAdapter.notifyItemRemoved(pos); + viewModel.sendCountNotifications(); // needed after items re-arrangement + viewModel.checkTransactionSubmittable(listAdapter); + } + } + }).attachToRecyclerView(list); + + viewModel.isSubmittable() + .observe(this, isSubmittable -> { + if (isSubmittable) { + if (fab != null) { + fab.show(); + fab.setEnabled(true); + } + } + else { + if (fab != null) { + fab.hide(); + } + } + }); + viewModel.checkTransactionSubmittable(listAdapter); + + fab = activity.findViewById(R.id.fab); + fab.setOnClickListener(v -> onFabPressed()); + + Bundle args = getArguments(); + if (args != null) { + String error = args.getString("error"); + if (error != null) { + // TODO display error + } + else { + viewModel.reset(); + } + } + } + private void onFabPressed() { + fab.setEnabled(false); + if (mListener != null) { + Date date = viewModel.getDate(); + LedgerTransaction tr = + new LedgerTransaction(null, date, viewModel.getDescription(), mProfile); + + LedgerTransactionAccount emptyAmountAccount = null; + float emptyAmountAccountBalance = 0; + for (int i = 0; i < viewModel.getAccountCount(); i++) { + LedgerTransactionAccount acc = viewModel.getAccount(i); + if (acc.getAccountName() + .trim() + .isEmpty()) + continue; + + if (acc.isAmountSet()) { + emptyAmountAccountBalance += acc.getAmount(); + } + else { + emptyAmountAccount = acc; + } + + tr.addAccount(acc); + } + + if (emptyAmountAccount != null) + emptyAmountAccount.setAmount(-emptyAmountAccountBalance); + + mListener.onTransactionSave(tr); + } + } + + @Override + public void onAttach(@NotNull Context context) { + super.onAttach(context); + if (context instanceof OnNewTransactionFragmentInteractionListener) { + mListener = (OnNewTransactionFragmentInteractionListener) context; + } + else { + throw new RuntimeException( + context.toString() + " must implement OnFragmentInteractionListener"); + } + } + + @Override + public void onDetach() { + super.onDetach(); + mListener = null; + } + + /** + * This interface must be implemented by activities that contain this + * fragment to allow an interaction in this fragment to be communicated + * to the activity and potentially other fragments contained in that + * activity. + *

+ * See the Android Training lesson Communicating with Other Fragments for more information. + */ + public interface OnNewTransactionFragmentInteractionListener { + void onTransactionSave(LedgerTransaction tr); + } +} diff --git a/app/src/main/res/anim/slide_in_up.xml b/app/src/main/res/anim/slide_in_up.xml index 36c411a2..2513facc 100644 --- a/app/src/main/res/anim/slide_in_up.xml +++ b/app/src/main/res/anim/slide_in_up.xml @@ -16,7 +16,7 @@ --> + android:duration="@android:integer/config_mediumAnimTime"> diff --git a/app/src/main/res/anim/slide_out_down.xml b/app/src/main/res/anim/slide_out_down.xml new file mode 100644 index 00000000..963c4d98 --- /dev/null +++ b/app/src/main/res/anim/slide_out_down.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_out_up.xml b/app/src/main/res/anim/slide_out_up.xml index 0437ef5d..69784395 100644 --- a/app/src/main/res/anim/slide_out_up.xml +++ b/app/src/main/res/anim/slide_out_up.xml @@ -16,7 +16,7 @@ --> + android:duration="@android:integer/config_longAnimTime"> diff --git a/app/src/main/res/layout/activity_new_transaction.xml b/app/src/main/res/layout/activity_new_transaction.xml index c865fdcd..6913a770 100644 --- a/app/src/main/res/layout/activity_new_transaction.xml +++ b/app/src/main/res/layout/activity_new_transaction.xml @@ -27,14 +27,13 @@ android:layout_height="match_parent"> + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> - - - - - + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/toolbar_layout" + app:navGraph="@navigation/new_transaction_navigation" /> diff --git a/app/src/main/res/layout/fragment_new_transaction.xml b/app/src/main/res/layout/fragment_new_transaction.xml new file mode 100644 index 00000000..8a35cf68 --- /dev/null +++ b/app/src/main/res/layout/fragment_new_transaction.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_new_transaction_saving.xml b/app/src/main/res/layout/fragment_new_transaction_saving.xml new file mode 100644 index 00000000..eab55f0e --- /dev/null +++ b/app/src/main/res/layout/fragment_new_transaction_saving.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/new_transaction_accounts.xml b/app/src/main/res/layout/new_transaction_accounts.xml deleted file mode 100644 index 0a40f785..00000000 --- a/app/src/main/res/layout/new_transaction_accounts.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/menu/new_transaction.xml b/app/src/main/res/menu/new_transaction.xml index 51fd8853..45f11d73 100644 --- a/app/src/main/res/menu/new_transaction.xml +++ b/app/src/main/res/menu/new_transaction.xml @@ -17,12 +17,6 @@

- + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/new_transaction_navigation.xml b/app/src/main/res/navigation/new_transaction_navigation.xml new file mode 100644 index 00000000..b68760c2 --- /dev/null +++ b/app/src/main/res/navigation/new_transaction_navigation.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + \ No newline at end of file