From: Damyan Ivanov Date: Sun, 24 Feb 2019 21:48:29 +0000 (+0200) Subject: major rework of the profile management X-Git-Tag: v0.7~57 X-Git-Url: https://git.ktnx.net/?p=mobile-ledger.git;a=commitdiff_plain;h=59e1c221c18f8dfaf6394cdf0a29e4ba2319151d major rework of the profile management moved entirely to the navigation bar --- diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index eec9d79b..3407017d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -62,7 +62,7 @@ . + */ + +package net.ktnx.mobileledger; + +import androidx.lifecycle.ViewModel; + +public class ProfileListViewModel extends ViewModel { + // TODO: Implement the ViewModel +} diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/activity/MainActivity.java b/app/src/main/java/net/ktnx/mobileledger/ui/activity/MainActivity.java index 15e4bef5..958c0e91 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/activity/MainActivity.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/activity/MainActivity.java @@ -23,22 +23,18 @@ import android.content.res.ColorStateList; import android.graphics.Color; import android.os.Build; import android.os.Bundle; -import com.google.android.material.floatingactionbutton.FloatingActionButton; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentPagerAdapter; -import androidx.core.view.GravityCompat; -import androidx.viewpager.widget.ViewPager; -import androidx.drawerlayout.widget.DrawerLayout; -import androidx.appcompat.app.ActionBarDrawerToggle; -import androidx.appcompat.widget.Toolbar; import android.util.Log; import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; +import com.google.android.material.floatingactionbutton.FloatingActionButton; + import net.ktnx.mobileledger.R; import net.ktnx.mobileledger.async.RefreshDescriptionsTask; import net.ktnx.mobileledger.async.RetrieveTransactionsTask; @@ -46,6 +42,8 @@ import net.ktnx.mobileledger.model.Data; import net.ktnx.mobileledger.model.LedgerAccount; import net.ktnx.mobileledger.model.MobileLedgerProfile; import net.ktnx.mobileledger.ui.account_summary.AccountSummaryFragment; +import net.ktnx.mobileledger.ui.profiles.ProfileDetailFragment; +import net.ktnx.mobileledger.ui.profiles.ProfilesRecyclerViewAdapter; import net.ktnx.mobileledger.ui.transaction_list.TransactionListFragment; import net.ktnx.mobileledger.utils.Colors; import net.ktnx.mobileledger.utils.MLDB; @@ -54,8 +52,23 @@ import java.lang.ref.WeakReference; import java.text.DateFormat; import java.util.Date; +import androidx.appcompat.app.ActionBarDrawerToggle; +import androidx.appcompat.widget.Toolbar; +import androidx.core.view.GravityCompat; +import androidx.drawerlayout.widget.DrawerLayout; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentPagerAdapter; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.viewpager.widget.ViewPager; + public class MainActivity extends CrashReportingActivity { + private static final String STATE_CURRENT_PAGE = "current_page"; + private static final String BUNDLE_SAVED_STATE = "bundle_savedState"; DrawerLayout drawer; + private LinearLayout profileListContainer; + private View profileListHeadArrow; private FragmentManager fragmentManager; private TextView tvLastUpdate; private RetrieveTransactionsTask retrieveTransactionsTask; @@ -65,6 +78,9 @@ public class MainActivity extends CrashReportingActivity { private SectionsPagerAdapter mSectionsPagerAdapter; private ViewPager mViewPager; private FloatingActionButton fab; + private boolean profileModificationEnabled = false; + private boolean profileListExpanded = false; + private ProfilesRecyclerViewAdapter mProfileListAdapter; @Override protected void onStart() { @@ -85,13 +101,34 @@ public class MainActivity extends CrashReportingActivity { } } @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt(STATE_CURRENT_PAGE, mViewPager.getCurrentItem()); + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); + + fab = findViewById(R.id.btn_add_transaction); + profileListContainer = findViewById(R.id.nav_profile_list_container); + profileListHeadArrow = findViewById(R.id.nav_profiles_arrow); + drawer = findViewById(R.id.drawer_layout); + tvLastUpdate = findViewById(R.id.transactions_last_update); + bTransactionListCancelDownload = findViewById(R.id.transaction_list_cancel_download); + progressBar = findViewById(R.id.transaction_list_progress_bar); + progressLayout = findViewById(R.id.transaction_progress_layout); + fragmentManager = getSupportFragmentManager(); + mSectionsPagerAdapter = new SectionsPagerAdapter(fragmentManager); + mViewPager = findViewById(R.id.root_frame); + + Bundle extra = getIntent().getBundleExtra(BUNDLE_SAVED_STATE); + if (extra != null && savedInstanceState == null) savedInstanceState = extra; + + Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); - fab = findViewById(R.id.btn_add_transaction); Data.profile.addObserver((o, arg) -> { MobileLedgerProfile profile = Data.profile.get(); @@ -118,7 +155,6 @@ public class MainActivity extends CrashReportingActivity { }); }); - drawer = findViewById(R.id.drawer_layout); ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); @@ -136,22 +172,13 @@ public class MainActivity extends CrashReportingActivity { e.printStackTrace(); } - tvLastUpdate = findViewById(R.id.transactions_last_update); - - bTransactionListCancelDownload = findViewById(R.id.transaction_list_cancel_download); - progressBar = findViewById(R.id.transaction_list_progress_bar); if (progressBar == null) throw new RuntimeException("Can't get hold on the transaction value progress bar"); - progressLayout = findViewById(R.id.transaction_progress_layout); if (progressLayout == null) throw new RuntimeException( "Can't get hold on the transaction value progress bar layout"); - fragmentManager = getSupportFragmentManager(); - mSectionsPagerAdapter = new SectionsPagerAdapter(fragmentManager); - markDrawerItemCurrent(R.id.nav_account_summary); - mViewPager = findViewById(R.id.root_frame); mViewPager.setAdapter(mSectionsPagerAdapter); mViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { @Override @@ -171,6 +198,13 @@ public class MainActivity extends CrashReportingActivity { } }); + if (savedInstanceState != null) { + int currentPage = savedInstanceState.getInt(STATE_CURRENT_PAGE, -1); + if (currentPage != -1) { + mViewPager.setCurrentItem(currentPage, false); + } + } + Data.lastUpdateDate.addObserver((o, arg) -> { Log.d("main", "lastUpdateDate changed"); runOnUiThread(() -> { @@ -186,16 +220,35 @@ public class MainActivity extends CrashReportingActivity { }); }); - findViewById(R.id.btn_no_profiles_add).setOnClickListener(v -> startAddProfileActivity()); + findViewById(R.id.btn_no_profiles_add) + .setOnClickListener(v -> startEditProfileActivity(null)); findViewById(R.id.btn_add_transaction).setOnClickListener(this::fabNewTransactionClicked); + + findViewById(R.id.nav_new_profile_button) + .setOnClickListener(v -> startEditProfileActivity(null)); + + RecyclerView root = findViewById(R.id.nav_profile_list); + if (root == null) + throw new RuntimeException("Can't get hold on the transaction value view"); + + mProfileListAdapter = new ProfilesRecyclerViewAdapter(); + root.setAdapter(mProfileListAdapter); + + LinearLayoutManager llm = new LinearLayoutManager(this); + + llm.setOrientation(RecyclerView.VERTICAL); + root.setLayoutManager(llm); } private void profileThemeChanged() { setupProfileColors(); + Bundle bundle = new Bundle(); + onSaveInstanceState(bundle); // restart activity to reflect theme change finish(); Intent intent = new Intent(this, this.getClass()); + intent.putExtra(BUNDLE_SAVED_STATE, bundle); startActivity(intent); } @Override @@ -203,11 +256,13 @@ public class MainActivity extends CrashReportingActivity { super.onResume(); setupProfile(); } - private void startAddProfileActivity() { - Intent intent = new Intent(this, ProfileListActivity.class); + public void startEditProfileActivity(MobileLedgerProfile profile) { + Intent intent = new Intent(this, ProfileDetailActivity.class); Bundle args = new Bundle(); - args.putInt(ProfileListActivity.ARG_ACTION, ProfileListActivity.ACTION_EDIT_PROFILE); - args.putInt(ProfileListActivity.ARG_PROFILE_INDEX, ProfileListActivity.PROFILE_INDEX_NONE); + if (profile != null) { + int index = Data.getProfileIndex(profile); + if (index != -1) intent.putExtra(ProfileDetailFragment.ARG_ITEM_ID, index); + } intent.putExtras(args); startActivity(intent, args); } @@ -377,15 +432,75 @@ public class MainActivity extends CrashReportingActivity { progressBar.setIndeterminate(false); } } - public void navProfilesClicked(View view) { - drawer.closeDrawers(); - Intent intent = new Intent(this, ProfileListActivity.class); - startActivity(intent); - } public void fabShouldShow() { MobileLedgerProfile profile = Data.profile.get(); if ((profile != null) && profile.isPostingPermitted()) fab.show(); } + public void navProfilesHeadClicked(View view) { + if (profileListExpanded) { + collapseProfileList(); + } + else { + expandProfileList(); + } + } + private void expandProfileList() { + profileListExpanded = true; + + + profileListContainer.setVisibility(View.VISIBLE); + profileListContainer.startAnimation(AnimationUtils.loadAnimation(this, R.anim.slide_down)); + profileListHeadArrow.startAnimation(AnimationUtils.loadAnimation(this, R.anim.rotate_180)); + } + private void collapseProfileList() { + profileListExpanded = false; + + final Animation animation = AnimationUtils.loadAnimation(this, R.anim.slide_up); + animation.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + + } + @Override + public void onAnimationEnd(Animation animation) { + profileListContainer.setVisibility(View.GONE); + } + @Override + public void onAnimationRepeat(Animation animation) { + + } + }); + profileListContainer.startAnimation(animation); + profileListHeadArrow + .startAnimation(AnimationUtils.loadAnimation(this, R.anim.rotate_180_back)); + + mProfileListAdapter.stopEditingProfiles(); + } + public void onProfileRowClicked(View v) { + Data.setCurrentProfile((MobileLedgerProfile) v.getTag()); + } + public void enableProfileModifications() { + profileModificationEnabled = true; + ViewGroup profileList = findViewById(R.id.nav_profile_list); + for (int i = 0; i < profileList.getChildCount(); i++) { + View aRow = profileList.getChildAt(i); + aRow.findViewById(R.id.profile_list_edit_button).setVisibility(View.VISIBLE); + aRow.findViewById(R.id.profile_list_rearrange_handle).setVisibility(View.VISIBLE); + } + // FIXME enable rearranging + + } + public void disableProfileModifications() { + profileModificationEnabled = false; + ViewGroup profileList = findViewById(R.id.nav_profile_list); + for (int i = 0; i < profileList.getChildCount(); i++) { + View aRow = profileList.getChildAt(i); + aRow.findViewById(R.id.profile_list_edit_button).setVisibility(View.GONE); + aRow.findViewById(R.id.profile_list_rearrange_handle).setVisibility(View.GONE); + } + // FIXME disable rearranging + + } public class SectionsPagerAdapter extends FragmentPagerAdapter { @@ -412,4 +527,5 @@ public class MainActivity extends CrashReportingActivity { return 2; } } + } diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/activity/ProfileDetailActivity.java b/app/src/main/java/net/ktnx/mobileledger/ui/activity/ProfileDetailActivity.java index fa55b878..ee4707ca 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/activity/ProfileDetailActivity.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/activity/ProfileDetailActivity.java @@ -17,22 +17,23 @@ package net.ktnx.mobileledger.ui.activity; -import android.content.Intent; import android.os.Bundle; -import com.google.android.material.floatingactionbutton.FloatingActionButton; -import com.google.android.material.snackbar.Snackbar; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.widget.Toolbar; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; +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.MobileLedgerProfile; import net.ktnx.mobileledger.ui.profiles.ProfileDetailFragment; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.widget.Toolbar; + /** * An activity representing a single Profile detail screen. This * activity is only used on narrow width devices. On tablet-size devices, @@ -115,19 +116,4 @@ public class ProfileDetailActivity extends CrashReportingActivity { return true; } - @Override - public boolean onOptionsItemSelected(MenuItem item) { - int id = item.getItemId(); - if (id == android.R.id.home) { - // This ID represents the Home or Up button. In the case of this - // activity, the Up button is shown. For - // more details, see the Navigation pattern on Android Design: - // - // http://developer.android.com/design/patterns/navigation.html#up-vs-back - // - navigateUpTo(new Intent(this, ProfileListActivity.class)); - return true; - } - return super.onOptionsItemSelected(item); - } } diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/activity/ProfileListActivity.java b/app/src/main/java/net/ktnx/mobileledger/ui/activity/ProfileListActivity.java deleted file mode 100644 index 2fde3640..00000000 --- a/app/src/main/java/net/ktnx/mobileledger/ui/activity/ProfileListActivity.java +++ /dev/null @@ -1,264 +0,0 @@ -/* - * 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.content.Context; -import android.content.Intent; -import android.graphics.Color; -import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.appcompat.app.ActionBar; -import androidx.recyclerview.widget.DividerItemDecoration; -import androidx.recyclerview.widget.RecyclerView; -import androidx.appcompat.widget.Toolbar; -import androidx.recyclerview.widget.ItemTouchHelper; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.View; -import android.view.ViewGroup; -import android.widget.RadioButton; -import android.widget.TextView; - -import net.ktnx.mobileledger.R; -import net.ktnx.mobileledger.model.Data; -import net.ktnx.mobileledger.model.MobileLedgerProfile; -import net.ktnx.mobileledger.ui.profiles.ProfileDetailFragment; -import net.ktnx.mobileledger.utils.Colors; - -import java.util.Collections; - -/** - * An activity representing a list of Profiles. This activity - * has different presentations for handset and tablet-size devices. On - * handsets, the activity presents a list of items, which when touched, - * lead to a {@link ProfileDetailActivity} representing - * item details. On tablets, the activity presents the list of items and - * item details side-by-side using two vertical panes. - */ -public class ProfileListActivity extends CrashReportingActivity { - - public static final String ARG_ACTION = "action"; - public static final String ARG_PROFILE_INDEX = "profile_index"; - public static final int PROFILE_INDEX_NONE = -1; - public static final int ACTION_EDIT_PROFILE = 1; - public static final int ACTION_INVALID = -1; - /** - * Whether or not the activity is in two-pane mode, i.e. running on a tablet - * device. - */ - private boolean mTwoPane; - private RecyclerView recyclerView; - - @Override - public boolean onSupportNavigateUp() { - onBackPressed(); - return super.onSupportNavigateUp(); - } - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_profile_list); - - Toolbar toolbar = findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - toolbar.setTitle(getTitle()); - final ActionBar supportActionBar = getSupportActionBar(); - if (supportActionBar != null) { - supportActionBar.setDisplayHomeAsUpEnabled(true); - supportActionBar.setDisplayShowHomeEnabled(true); - } - - recyclerView = findViewById(R.id.profile_list); - if (recyclerView == null) throw new AssertionError(); - setupRecyclerView(recyclerView); - - if (findViewById(R.id.profile_detail_container) != null) { - // The detail container view will be present only in the - // large-screen layouts (res/values-w900dp). - // If this view is present, then the - // activity should be in two-pane mode. - mTwoPane = true; - } - - int action = getIntent().getIntExtra(ARG_ACTION, ACTION_INVALID); - if (action == ACTION_EDIT_PROFILE) { - Log.d("profiles", "got edit profile action"); - int index = getIntent().getIntExtra(ARG_PROFILE_INDEX, PROFILE_INDEX_NONE); - - MobileLedgerProfile profile = (index >= 0) ? Data.profiles.get(index) : null; - ProfilesRecyclerViewAdapter adapter = - (ProfilesRecyclerViewAdapter) recyclerView.getAdapter(); - if (adapter != null) { - adapter.editProfile(recyclerView, profile); - - // if invoked from the initial screen, get out so that when the new profile - // activity finishes the user i navigated to the main activity - if ((profile == null) && Data.profiles.getList().isEmpty()) finish(); - } - } - } - void launchNewProfileActivity() { - ProfilesRecyclerViewAdapter adapter = - (ProfilesRecyclerViewAdapter) recyclerView.getAdapter(); - if (adapter != null) adapter.editProfile(recyclerView, null); - } - private void setupRecyclerView(@NonNull RecyclerView recyclerView) { - final ProfilesRecyclerViewAdapter adapter = new ProfilesRecyclerViewAdapter(this, mTwoPane); - recyclerView.setAdapter(adapter); - ItemTouchHelper.Callback cb = new ItemTouchHelper.Callback() { - @Override - public int getMovementFlags(@NonNull RecyclerView recyclerView, - @NonNull RecyclerView.ViewHolder viewHolder) { - return makeMovementFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0); - } - @Override - public boolean onMove(@NonNull RecyclerView recyclerView, - @NonNull RecyclerView.ViewHolder viewHolder, - @NonNull RecyclerView.ViewHolder target) { - Collections.swap(Data.profiles.getList(), viewHolder.getAdapterPosition(), - target.getAdapterPosition()); - MobileLedgerProfile.storeProfilesOrder(); - adapter.notifyItemMoved(viewHolder.getAdapterPosition(), - target.getAdapterPosition()); - return true; - } - @Override - public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) { - - } - }; - new ItemTouchHelper(cb).attachToRecyclerView(recyclerView); - recyclerView.addItemDecoration(new DividerItemDecoration(recyclerView.getContext(), - DividerItemDecoration.VERTICAL)); - } - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.profile_list, menu); - menu.findItem(R.id.menu_add_profile).setOnMenuItemClickListener(item -> { - launchNewProfileActivity(); - return true; - }); - return super.onCreateOptionsMenu(menu); - } - public static class ProfilesRecyclerViewAdapter - extends RecyclerView.Adapter { - - private final ProfileListActivity mParentActivity; - private final boolean mTwoPane; - private final View.OnClickListener mOnClickListener = view -> { - MobileLedgerProfile profile = (MobileLedgerProfile) ((View) view.getParent()).getTag(); - editProfile(view, profile); - }; - ProfilesRecyclerViewAdapter(ProfileListActivity parent, boolean twoPane) { - mParentActivity = parent; - mTwoPane = twoPane; - Data.profiles.addObserver((o, arg) -> { - Log.d("profiles", "profile list changed"); - if (arg == null) notifyDataSetChanged(); - else notifyItemChanged((int) arg); - }); - } - private void editProfile(View view, MobileLedgerProfile profile) { - int index = Data.profiles.indexOf(profile); - if (mTwoPane) { - Bundle arguments = new Bundle(); - arguments.putInt(ProfileDetailFragment.ARG_ITEM_ID, index); - ProfileDetailFragment fragment = new ProfileDetailFragment(); - fragment.setArguments(arguments); - mParentActivity.getSupportFragmentManager().beginTransaction() - .replace(R.id.profile_detail_container, fragment).commit(); - } - else { - Context context = view.getContext(); - Intent intent = new Intent(context, ProfileDetailActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION); - if (index != -1) intent.putExtra(ProfileDetailFragment.ARG_ITEM_ID, index); - - context.startActivity(intent); - } - } - @NonNull - @Override - public ProfileListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.profile_list_content, parent, false); - ProfileListViewHolder holder = new ProfileListViewHolder(view); - - holder.mRadioView.setOnCheckedChangeListener((buttonView, isChecked) -> { - if (!isChecked) return; - Log.d("profiles", - String.format("Item %d got checked", holder.getAdapterPosition())); - MobileLedgerProfile profile = (MobileLedgerProfile) holder.itemView.getTag(); - if (profile != null) { - Log.d("profiles", - String.format("Setting current profile to %s", profile.getUuid())); - Data.setCurrentProfile(profile); - } - }); - View.OnClickListener profileSelector = v -> holder.mRadioView.setChecked(true); - holder.mTitle.setOnClickListener(profileSelector); - holder.mSubTitle.setOnClickListener(profileSelector); - Data.profile.addObserver((o, arg) -> { - MobileLedgerProfile myProfile = (MobileLedgerProfile) holder.itemView.getTag(); - final MobileLedgerProfile currentProfile = Data.profile.get(); - final boolean sameProfile = currentProfile.equals(myProfile); - if (holder.mRadioView.isChecked() != sameProfile) { - holder.mRadioView.setChecked(sameProfile); - } - }); - return holder; - } - @Override - public void onBindViewHolder(@NonNull final ProfileListViewHolder holder, int position) { - final MobileLedgerProfile profile = Data.profiles.get(position); - final MobileLedgerProfile currentProfile = Data.profile.get(); - Log.d("profiles", String.format("pos %d: %s, current: %s", position, profile.getUuid(), - (currentProfile == null) ? "" : currentProfile.getUuid())); - holder.itemView.setTag(profile); - - int hue = profile.getThemeId(); - if (hue == -1) holder.mColorTag.setBackgroundColor(Color.TRANSPARENT); - else holder.mColorTag.setBackgroundColor(Colors.getPrimaryColorForHue(hue)); - - holder.mTitle.setText(profile.getName()); - holder.mSubTitle.setText(profile.getUrl()); - holder.mRadioView.setChecked(profile.equals(currentProfile)); - - holder.mEditButton.setOnClickListener(mOnClickListener); - } - @Override - public int getItemCount() { - return Data.profiles.size(); - } - class ProfileListViewHolder extends RecyclerView.ViewHolder { - final RadioButton mRadioView; - final TextView mEditButton; - final TextView mTitle, mSubTitle, mColorTag; - - ProfileListViewHolder(View view) { - super(view); - mRadioView = view.findViewById(R.id.profile_list_radio); - mEditButton = view.findViewById(R.id.profile_list_edit_button); - mTitle = view.findViewById(R.id.title); - mSubTitle = view.findViewById(R.id.subtitle); - mColorTag = view.findViewById(R.id.colorTag); - } - } - } -} diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfileDetailFragment.java b/app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfileDetailFragment.java index 6d26e4ba..0f307e0a 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfileDetailFragment.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfileDetailFragment.java @@ -21,13 +21,6 @@ import android.app.Activity; import android.content.Context; import android.os.Build; import android.os.Bundle; -import androidx.annotation.ColorInt; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import com.google.android.material.appbar.CollapsingToolbarLayout; -import com.google.android.material.floatingactionbutton.FloatingActionButton; -import com.google.android.material.textfield.TextInputLayout; -import androidx.fragment.app.Fragment; import android.text.Editable; import android.text.TextWatcher; import android.util.Log; @@ -44,21 +37,28 @@ import android.widget.Spinner; import android.widget.Switch; import android.widget.TextView; +import com.google.android.material.appbar.CollapsingToolbarLayout; +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.google.android.material.textfield.TextInputLayout; + import net.ktnx.mobileledger.R; import net.ktnx.mobileledger.model.Data; import net.ktnx.mobileledger.model.MobileLedgerProfile; import net.ktnx.mobileledger.ui.activity.ProfileDetailActivity; -import net.ktnx.mobileledger.ui.activity.ProfileListActivity; import net.ktnx.mobileledger.utils.Colors; import org.jetbrains.annotations.NotNull; import java.util.List; +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + /** * A fragment representing a single Profile detail screen. - * This fragment is either contained in a {@link ProfileListActivity} - * in two-pane mode (on tablets) or a {@link ProfileDetailActivity} + * a {@link ProfileDetailActivity} * on handsets. */ public class ProfileDetailFragment extends Fragment { diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfilesRecyclerViewAdapter.java b/app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfilesRecyclerViewAdapter.java new file mode 100644 index 00000000..54386d92 --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfilesRecyclerViewAdapter.java @@ -0,0 +1,195 @@ +/* + * 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 Mobile-Ledger. If not, see . + */ + +package net.ktnx.mobileledger.ui.profiles; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import net.ktnx.mobileledger.R; +import net.ktnx.mobileledger.model.Data; +import net.ktnx.mobileledger.model.MobileLedgerProfile; +import net.ktnx.mobileledger.ui.activity.ProfileDetailActivity; +import net.ktnx.mobileledger.utils.Colors; + +import java.util.Collections; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.RecyclerView; + +public class ProfilesRecyclerViewAdapter + extends RecyclerView.Adapter { + private final View.OnClickListener mOnClickListener = view -> { + MobileLedgerProfile profile = (MobileLedgerProfile) ((View) view.getParent()).getTag(); + editProfile(view, profile); + }; + private boolean editingProfiles = false; + private RecyclerView recyclerView; + private ItemTouchHelper rearrangeHelper; + public ProfilesRecyclerViewAdapter() { + Data.profiles.addObserver((o, arg) -> { + Log.d("profiles", "profile list changed"); + if (arg == null) notifyDataSetChanged(); + else notifyItemChanged((int) arg); + }); + + ItemTouchHelper.Callback cb = new ItemTouchHelper.Callback() { + @Override + public int getMovementFlags(@NonNull RecyclerView recyclerView, + @NonNull RecyclerView.ViewHolder viewHolder) { + return makeMovementFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0); + } + @Override + public boolean onMove(@NonNull RecyclerView recyclerView, + @NonNull RecyclerView.ViewHolder viewHolder, + @NonNull RecyclerView.ViewHolder target) { + Collections.swap(Data.profiles.getList(), viewHolder.getAdapterPosition(), + target.getAdapterPosition()); + MobileLedgerProfile.storeProfilesOrder(); + notifyItemMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition()); + return true; + } + @Override + public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) { + } + }; + rearrangeHelper = new ItemTouchHelper(cb); + } + @Override + public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) { + rearrangeHelper.attachToRecyclerView(null); + super.onDetachedFromRecyclerView(recyclerView); + this.recyclerView = null; + } + @Override + public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) { + super.onAttachedToRecyclerView(recyclerView); + this.recyclerView = recyclerView; + rearrangeHelper.attachToRecyclerView(recyclerView); + } + public boolean editingProfiles() { + return this.editingProfiles; + } + public void startEditingProfiles() { + if (editingProfiles) return; + this.editingProfiles = true; + notifyDataSetChanged(); + rearrangeHelper.attachToRecyclerView(recyclerView); + } + public void stopEditingProfiles() { + if (!editingProfiles) return; + this.editingProfiles = false; + notifyDataSetChanged(); + rearrangeHelper.attachToRecyclerView(null); + } + public void flipEditingProfiles() { + if (editingProfiles) stopEditingProfiles(); + else startEditingProfiles(); + } + private void editProfile(View view, MobileLedgerProfile profile) { + int index = Data.profiles.indexOf(profile); + Context context = view.getContext(); + Intent intent = new Intent(context, ProfileDetailActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION); + if (index != -1) intent.putExtra(ProfileDetailFragment.ARG_ITEM_ID, index); + + context.startActivity(intent); + } + @NonNull + @Override + public ProfileListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.profile_list_content, parent, false); + ProfileListViewHolder holder = new ProfileListViewHolder(view); + + holder.mTitle.setOnClickListener(v -> { + View row = (View) v.getParent(); + MobileLedgerProfile profile = (MobileLedgerProfile) row.getTag(); + if (profile == null) + throw new IllegalStateException("Profile row without associated profile"); + Log.d("profiles", "Setting profile to " + profile.getName()); + Data.setCurrentProfile(profile); + }); + holder.mTitle.setOnLongClickListener(v -> { + flipEditingProfiles(); + return true; + }); + Data.profile.addObserver((o, arg) -> { + MobileLedgerProfile myProfile = (MobileLedgerProfile) holder.itemView.getTag(); + final MobileLedgerProfile currentProfile = Data.profile.get(); + final boolean sameProfile = currentProfile.equals(myProfile); + view.setAlpha(sameProfile ? 1 : 0.5f); + }); + return holder; + } + @Override + public void onBindViewHolder(@NonNull final ProfileListViewHolder holder, int position) { + final MobileLedgerProfile profile = Data.profiles.get(position); + final MobileLedgerProfile currentProfile = Data.profile.get(); + Log.d("profiles", String.format("pos %d: %s, current: %s", position, profile.getUuid(), + (currentProfile == null) ? "" : currentProfile.getUuid())); + holder.itemView.setTag(profile); + + int hue = profile.getThemeId(); + if (hue == -1) holder.mColorTag.setBackgroundColor(Color.TRANSPARENT); + else holder.mColorTag.setBackgroundColor(Colors.getPrimaryColorForHue(hue)); + + holder.mTitle.setText(profile.getName()); +// holder.mSubTitle.setText(profile.getUrl()); + + holder.mEditButton.setOnClickListener(mOnClickListener); + + final boolean sameProfile = currentProfile.equals(profile); + holder.itemView.setAlpha(sameProfile ? 1 : 0.5f); + holder.itemView + .setBackground(sameProfile ? new ColorDrawable(Colors.tableRowLightBG) : null); + if (editingProfiles) { + holder.mRearrangeHandle.setVisibility(View.VISIBLE); + holder.mEditButton.setVisibility(View.VISIBLE); + } + else { + holder.mRearrangeHandle.setVisibility(View.GONE); + holder.mEditButton.setVisibility(View.GONE); + } + } + @Override + public int getItemCount() { + return Data.profiles.size(); + } + class ProfileListViewHolder extends RecyclerView.ViewHolder { + final TextView mEditButton; + final TextView mTitle, mColorTag; + final ImageView mRearrangeHandle; + + ProfileListViewHolder(View view) { + super(view); + mEditButton = view.findViewById(R.id.profile_list_edit_button); + mTitle = view.findViewById(R.id.title); + mColorTag = view.findViewById(R.id.colorTag); + mRearrangeHandle = view.findViewById(R.id.profile_list_rearrange_handle); + } + } +} diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/profiles/dummy/DummyContent.java b/app/src/main/java/net/ktnx/mobileledger/ui/profiles/dummy/DummyContent.java new file mode 100644 index 00000000..4100c511 --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/ui/profiles/dummy/DummyContent.java @@ -0,0 +1,89 @@ +/* + * 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 Mobile-Ledger. If not, see . + */ + +package net.ktnx.mobileledger.ui.profiles.dummy; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Helper class for providing sample content for user interfaces created by + * Android template wizards. + *

+ * TODO: Replace all uses of this class before publishing your app. + */ +public class DummyContent { + + /** + * An array of sample (dummy) items. + */ + public static final List ITEMS = new ArrayList(); + + /** + * A map of sample (dummy) items, by ID. + */ + public static final Map ITEM_MAP = new HashMap(); + + private static final int COUNT = 25; + + static { + // Add some sample items. + for (int i = 1; i <= COUNT; i++) { + addItem(createDummyItem(i)); + } + } + + private static void addItem(DummyItem item) { + ITEMS.add(item); + ITEM_MAP.put(item.id, item); + } + + private static DummyItem createDummyItem(int position) { + return new DummyItem(String.valueOf(position), "Item " + position, makeDetails(position)); + } + + private static String makeDetails(int position) { + StringBuilder builder = new StringBuilder(); + builder.append("Details about Item: ").append(position); + for (int i = 0; i < position; i++) { + builder.append("\nMore details information here."); + } + return builder.toString(); + } + + /** + * A dummy item representing a piece of content. + */ + public static class DummyItem { + public final String id; + public final String content; + public final String details; + + public DummyItem(String id, String content, String details) { + this.id = id; + this.content = content; + this.details = details; + } + + @Override + public String toString() { + return content; + } + } +} diff --git a/app/src/main/res/anim/layout_slide_down.xml b/app/src/main/res/anim/layout_slide_down.xml new file mode 100644 index 00000000..6efb0da9 --- /dev/null +++ b/app/src/main/res/anim/layout_slide_down.xml @@ -0,0 +1,20 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/anim/rotate_180.xml b/app/src/main/res/anim/rotate_180.xml new file mode 100644 index 00000000..f04f8ed2 --- /dev/null +++ b/app/src/main/res/anim/rotate_180.xml @@ -0,0 +1,26 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/rotate_180_back.xml b/app/src/main/res/anim/rotate_180_back.xml new file mode 100644 index 00000000..2bba9784 --- /dev/null +++ b/app/src/main/res/anim/rotate_180_back.xml @@ -0,0 +1,27 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_down.xml b/app/src/main/res/anim/slide_down.xml new file mode 100644 index 00000000..a435507c --- /dev/null +++ b/app/src/main/res/anim/slide_down.xml @@ -0,0 +1,29 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_in_down.xml b/app/src/main/res/anim/slide_in_down.xml new file mode 100644 index 00000000..620bb57d --- /dev/null +++ b/app/src/main/res/anim/slide_in_down.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_up.xml b/app/src/main/res/anim/slide_up.xml new file mode 100644 index 00000000..a02516ad --- /dev/null +++ b/app/src/main/res/anim/slide_up.xml @@ -0,0 +1,29 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-anydpi-v21/ic_expand_more_black_24dp.xml b/app/src/main/res/drawable-anydpi-v21/ic_expand_more_black_24dp.xml new file mode 100644 index 00000000..809847ad --- /dev/null +++ b/app/src/main/res/drawable-anydpi-v21/ic_expand_more_black_24dp.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/app/src/main/res/drawable-anydpi-v21/ic_keyboard_arrow_down_black_24dp.xml b/app/src/main/res/drawable-anydpi-v21/ic_keyboard_arrow_down_black_24dp.xml new file mode 100644 index 00000000..7f0caed0 --- /dev/null +++ b/app/src/main/res/drawable-anydpi-v21/ic_keyboard_arrow_down_black_24dp.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/app/src/main/res/drawable-anydpi-v21/ic_unfold_more_black_24dp.xml b/app/src/main/res/drawable-anydpi-v21/ic_unfold_more_black_24dp.xml new file mode 100644 index 00000000..2d601d98 --- /dev/null +++ b/app/src/main/res/drawable-anydpi-v21/ic_unfold_more_black_24dp.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index bd9f2f84..e7cb8dba 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -178,140 +178,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/app/src/main/res/layout/main_navigation.xml b/app/src/main/res/layout/main_navigation.xml new file mode 100644 index 00000000..4f4f5c50 --- /dev/null +++ b/app/src/main/res/layout/main_navigation.xml @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/nav_profile_list_head.xml b/app/src/main/res/layout/nav_profile_list_head.xml new file mode 100644 index 00000000..540ded50 --- /dev/null +++ b/app/src/main/res/layout/nav_profile_list_head.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/nav_profile_list_new_profile_row.xml b/app/src/main/res/layout/nav_profile_list_new_profile_row.xml new file mode 100644 index 00000000..d93743a4 --- /dev/null +++ b/app/src/main/res/layout/nav_profile_list_new_profile_row.xml @@ -0,0 +1,37 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/profile_list_content.xml b/app/src/main/res/layout/profile_list_content.xml index c1b1087e..0f23c761 100644 --- a/app/src/main/res/layout/profile_list_content.xml +++ b/app/src/main/res/layout/profile_list_content.xml @@ -20,15 +20,20 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/profile_list_item" + android:alpha="0.50" + android:animateLayoutChanges="true" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="@dimen/thumb_row_height" android:layout_gravity="center_horizontal" android:foregroundGravity="center_vertical"> + app:layout_constraintTop_toTopOf="parent" + android:visibility="invisible"/> - + app:layout_constraintStart_toEndOf="@id/colorTag" + app:layout_constraintEnd_toStartOf="@id/title"/> - + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-h360dp/styles.xml b/app/src/main/res/values-h360dp/styles.xml index aa98a23c..c7315af1 100644 --- a/app/src/main/res/values-h360dp/styles.xml +++ b/app/src/main/res/values-h360dp/styles.xml @@ -17,14 +17,15 @@ - \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index cdc6d5a0..21eb5e36 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -267,9 +267,9 @@ + 40dp