]> git.ktnx.net Git - mobile-ledger.git/blob - app/src/main/java/net/ktnx/mobileledger/ui/activity/MainActivity.java
rework main activity fab show/hide to slide up/down
[mobile-ledger.git] / app / src / main / java / net / ktnx / mobileledger / ui / activity / MainActivity.java
1 /*
2  * Copyright © 2021 Damyan Ivanov.
3  * This file is part of MoLe.
4  * MoLe is free software: you can distribute it and/or modify it
5  * under the term of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your opinion), any later version.
8  *
9  * MoLe is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License terms for details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
16  */
17
18 package net.ktnx.mobileledger.ui.activity;
19
20 import android.animation.Animator;
21 import android.animation.AnimatorListenerAdapter;
22 import android.animation.TimeInterpolator;
23 import android.content.Intent;
24 import android.content.pm.PackageInfo;
25 import android.content.pm.ShortcutInfo;
26 import android.content.pm.ShortcutManager;
27 import android.content.res.ColorStateList;
28 import android.graphics.Color;
29 import android.graphics.drawable.Icon;
30 import android.os.Build;
31 import android.os.Bundle;
32 import android.text.format.DateUtils;
33 import android.util.Log;
34 import android.view.View;
35 import android.view.ViewPropertyAnimator;
36 import android.view.animation.AnimationUtils;
37 import android.widget.TextView;
38
39 import androidx.annotation.NonNull;
40 import androidx.appcompat.app.ActionBarDrawerToggle;
41 import androidx.appcompat.app.AlertDialog;
42 import androidx.core.view.GravityCompat;
43 import androidx.drawerlayout.widget.DrawerLayout;
44 import androidx.fragment.app.Fragment;
45 import androidx.fragment.app.FragmentActivity;
46 import androidx.lifecycle.ViewModelProvider;
47 import androidx.recyclerview.widget.LinearLayoutManager;
48 import androidx.recyclerview.widget.RecyclerView;
49 import androidx.viewpager2.adapter.FragmentStateAdapter;
50 import androidx.viewpager2.widget.ViewPager2;
51
52 import com.google.android.material.snackbar.Snackbar;
53
54 import net.ktnx.mobileledger.R;
55 import net.ktnx.mobileledger.async.RetrieveTransactionsTask;
56 import net.ktnx.mobileledger.databinding.ActivityMainBinding;
57 import net.ktnx.mobileledger.model.Data;
58 import net.ktnx.mobileledger.model.MobileLedgerProfile;
59 import net.ktnx.mobileledger.ui.MainModel;
60 import net.ktnx.mobileledger.ui.account_summary.AccountSummaryFragment;
61 import net.ktnx.mobileledger.ui.new_transaction.NewTransactionActivity;
62 import net.ktnx.mobileledger.ui.profiles.ProfilesRecyclerViewAdapter;
63 import net.ktnx.mobileledger.ui.templates.TemplatesActivity;
64 import net.ktnx.mobileledger.ui.transaction_list.TransactionListFragment;
65 import net.ktnx.mobileledger.utils.Colors;
66 import net.ktnx.mobileledger.utils.Logger;
67 import net.ktnx.mobileledger.utils.MLDB;
68
69 import org.jetbrains.annotations.NotNull;
70
71 import java.util.ArrayList;
72 import java.util.Date;
73 import java.util.List;
74 import java.util.Locale;
75 import java.util.Objects;
76
77 /*
78  * TODO: reports
79  *  */
80
81 public class MainActivity extends ProfileThemedActivity {
82     public static final String STATE_CURRENT_PAGE = "current_page";
83     public static final String BUNDLE_SAVED_STATE = "bundle_savedState";
84     public static final String STATE_ACC_FILTER = "account_filter";
85     private static final boolean FAB_HIDDEN = false;
86     private static final boolean FAB_SHOWN = true;
87     private SectionsPagerAdapter mSectionsPagerAdapter;
88     private ProfilesRecyclerViewAdapter mProfileListAdapter;
89     private int mCurrentPage;
90     private boolean mBackMeansToAccountList = false;
91     private DrawerLayout.SimpleDrawerListener drawerListener;
92     private ActionBarDrawerToggle barDrawerToggle;
93     private ViewPager2.OnPageChangeCallback pageChangeCallback;
94     private MobileLedgerProfile profile;
95     private MainModel mainModel;
96     private ActivityMainBinding b;
97     private int fabVerticalOffset;
98     private ViewPropertyAnimator fabSlideAnimator;
99     private boolean wantedFabState = FAB_SHOWN;
100     @Override
101     protected void onStart() {
102         super.onStart();
103
104         Logger.debug("MainActivity", "onStart()");
105
106         b.mainPager.setCurrentItem(mCurrentPage, false);
107     }
108     @Override
109     protected void onSaveInstanceState(@NotNull Bundle outState) {
110         super.onSaveInstanceState(outState);
111         outState.putInt(STATE_CURRENT_PAGE, b.mainPager.getCurrentItem());
112         if (mainModel.getAccountFilter()
113                      .getValue() != null)
114             outState.putString(STATE_ACC_FILTER, mainModel.getAccountFilter()
115                                                           .getValue());
116     }
117     @Override
118     protected void onDestroy() {
119         mSectionsPagerAdapter = null;
120         b.navProfileList.setAdapter(null);
121         b.drawerLayout.removeDrawerListener(drawerListener);
122         drawerListener = null;
123         b.drawerLayout.removeDrawerListener(barDrawerToggle);
124         barDrawerToggle = null;
125         b.mainPager.unregisterOnPageChangeCallback(pageChangeCallback);
126         pageChangeCallback = null;
127         super.onDestroy();
128     }
129     @Override
130     protected void onResume() {
131         super.onResume();
132         fabShouldShow();
133     }
134     @Override
135     protected void onCreate(Bundle savedInstanceState) {
136         Logger.debug("MainActivity", "onCreate()/entry");
137         super.onCreate(savedInstanceState);
138         Logger.debug("MainActivity", "onCreate()/after super");
139         b = ActivityMainBinding.inflate(getLayoutInflater());
140         setContentView(b.getRoot());
141
142         mainModel = new ViewModelProvider(this).get(MainModel.class);
143
144         mSectionsPagerAdapter = new SectionsPagerAdapter(this);
145
146         Bundle extra = getIntent().getBundleExtra(BUNDLE_SAVED_STATE);
147         if (extra != null && savedInstanceState == null)
148             savedInstanceState = extra;
149
150
151         setSupportActionBar(b.toolbar);
152
153         Data.observeProfile(this, this::onProfileChanged);
154
155         Data.profiles.observe(this, this::onProfileListChanged);
156         Data.backgroundTaskProgress.observe(this, this::onRetrieveProgress);
157         Data.backgroundTasksRunning.observe(this, this::onRetrieveRunningChanged);
158
159         if (barDrawerToggle == null) {
160             barDrawerToggle = new ActionBarDrawerToggle(this, b.drawerLayout, b.toolbar,
161                     R.string.navigation_drawer_open, R.string.navigation_drawer_close);
162             b.drawerLayout.addDrawerListener(barDrawerToggle);
163         }
164         barDrawerToggle.syncState();
165
166         try {
167             PackageInfo pi = getApplicationContext().getPackageManager()
168                                                     .getPackageInfo(getPackageName(), 0);
169             ((TextView) b.navUpper.findViewById(R.id.drawer_version_text)).setText(pi.versionName);
170             ((TextView) b.noProfilesLayout.findViewById(R.id.drawer_version_text)).setText(
171                     pi.versionName);
172         }
173         catch (Exception e) {
174             e.printStackTrace();
175         }
176
177         markDrawerItemCurrent(R.id.nav_account_summary);
178
179         b.mainPager.setAdapter(mSectionsPagerAdapter);
180
181         if (pageChangeCallback == null) {
182             pageChangeCallback = new ViewPager2.OnPageChangeCallback() {
183                 @Override
184                 public void onPageSelected(int position) {
185                     mCurrentPage = position;
186                     switch (position) {
187                         case 0:
188                             markDrawerItemCurrent(R.id.nav_account_summary);
189                             break;
190                         case 1:
191                             markDrawerItemCurrent(R.id.nav_latest_transactions);
192                             break;
193                         default:
194                             Log.e("MainActivity",
195                                     String.format("Unexpected page index %d", position));
196                     }
197
198                     super.onPageSelected(position);
199                 }
200             };
201             b.mainPager.registerOnPageChangeCallback(pageChangeCallback);
202         }
203
204         mCurrentPage = 0;
205         if (savedInstanceState != null) {
206             int currentPage = savedInstanceState.getInt(STATE_CURRENT_PAGE, -1);
207             if (currentPage != -1) {
208                 mCurrentPage = currentPage;
209             }
210             mainModel.getAccountFilter()
211                      .setValue(savedInstanceState.getString(STATE_ACC_FILTER, null));
212         }
213
214         b.btnNoProfilesAdd.setOnClickListener(
215                 v -> MobileLedgerProfile.startEditProfileActivity(this, null));
216
217         b.btnAddTransaction.setOnClickListener(this::fabNewTransactionClicked);
218
219         b.navNewProfileButton.setOnClickListener(
220                 v -> MobileLedgerProfile.startEditProfileActivity(this, null));
221
222         b.transactionListCancelDownload.setOnClickListener(this::onStopTransactionRefreshClick);
223
224         if (mProfileListAdapter == null)
225             mProfileListAdapter = new ProfilesRecyclerViewAdapter();
226         b.navProfileList.setAdapter(mProfileListAdapter);
227
228         mProfileListAdapter.editingProfiles.observe(this, newValue -> {
229             if (newValue) {
230                 b.navProfilesStartEdit.setVisibility(View.GONE);
231                 b.navProfilesCancelEdit.setVisibility(View.VISIBLE);
232                 b.navNewProfileButton.setVisibility(View.VISIBLE);
233                 if (b.drawerLayout.isDrawerOpen(GravityCompat.START)) {
234                     b.navProfilesStartEdit.startAnimation(
235                             AnimationUtils.loadAnimation(MainActivity.this, R.anim.fade_out));
236                     b.navProfilesCancelEdit.startAnimation(
237                             AnimationUtils.loadAnimation(MainActivity.this, R.anim.fade_in));
238                     b.navNewProfileButton.startAnimation(
239                             AnimationUtils.loadAnimation(MainActivity.this, R.anim.fade_in));
240                 }
241             }
242             else {
243                 b.navProfilesCancelEdit.setVisibility(View.GONE);
244                 b.navProfilesStartEdit.setVisibility(View.VISIBLE);
245                 b.navNewProfileButton.setVisibility(View.GONE);
246                 if (b.drawerLayout.isDrawerOpen(GravityCompat.START)) {
247                     b.navProfilesCancelEdit.startAnimation(
248                             AnimationUtils.loadAnimation(MainActivity.this, R.anim.fade_out));
249                     b.navProfilesStartEdit.startAnimation(
250                             AnimationUtils.loadAnimation(MainActivity.this, R.anim.fade_in));
251                     b.navNewProfileButton.startAnimation(
252                             AnimationUtils.loadAnimation(MainActivity.this, R.anim.fade_out));
253                 }
254             }
255
256             mProfileListAdapter.notifyDataSetChanged();
257         });
258
259         LinearLayoutManager llm = new LinearLayoutManager(this);
260
261         llm.setOrientation(RecyclerView.VERTICAL);
262         b.navProfileList.setLayoutManager(llm);
263
264         b.navProfilesStartEdit.setOnClickListener((v) -> mProfileListAdapter.flipEditingProfiles());
265         b.navProfilesCancelEdit.setOnClickListener(
266                 (v) -> mProfileListAdapter.flipEditingProfiles());
267         b.navProfileListHeadButtons.setOnClickListener(
268                 (v) -> mProfileListAdapter.flipEditingProfiles());
269         if (drawerListener == null) {
270             drawerListener = new DrawerLayout.SimpleDrawerListener() {
271                 @Override
272                 public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
273                     if (slideOffset > 0.2)
274                         fabHide();
275                 }
276                 @Override
277                 public void onDrawerClosed(View drawerView) {
278                     super.onDrawerClosed(drawerView);
279                     mProfileListAdapter.setAnimationsEnabled(false);
280                     mProfileListAdapter.editingProfiles.setValue(false);
281                     Data.drawerOpen.setValue(false);
282                     fabShouldShow();
283                 }
284                 @Override
285                 public void onDrawerOpened(View drawerView) {
286                     super.onDrawerOpened(drawerView);
287                     mProfileListAdapter.setAnimationsEnabled(true);
288                     Data.drawerOpen.setValue(true);
289                     fabHide();
290                 }
291             };
292             b.drawerLayout.addDrawerListener(drawerListener);
293         }
294
295         Data.drawerOpen.observe(this, open -> {
296             if (open)
297                 b.drawerLayout.open();
298             else
299                 b.drawerLayout.close();
300         });
301
302         mainModel.getUpdateError()
303                  .observe(this, (error) -> {
304                      if (error == null)
305                          return;
306
307                      Snackbar.make(b.mainPager, error, Snackbar.LENGTH_INDEFINITE)
308                              .show();
309                      mainModel.clearUpdateError();
310                  });
311         Data.locale.observe(this, l -> refreshLastUpdateInfo());
312         Data.lastUpdateDate.observe(this, date -> refreshLastUpdateInfo());
313         Data.lastUpdateTransactionCount.observe(this, date -> refreshLastUpdateInfo());
314         Data.lastUpdateAccountCount.observe(this, date -> refreshLastUpdateInfo());
315         b.navAccountSummary.setOnClickListener(this::onAccountSummaryClicked);
316         b.navLatestTransactions.setOnClickListener(this::onLatestTransactionsClicked);
317         b.navPatterns.setOnClickListener(this::onPatternsClick);
318     }
319     private void onPatternsClick(View view) {
320         Intent intent = new Intent(this, TemplatesActivity.class);
321         startActivity(intent);
322     }
323     private void scheduleDataRetrievalIfStale(long lastUpdate) {
324         long now = new Date().getTime();
325         if ((lastUpdate == 0) || (now > (lastUpdate + (24 * 3600 * 1000)))) {
326             if (lastUpdate == 0)
327                 Logger.debug("db::", "WEB data never fetched. scheduling a fetch");
328             else
329                 Logger.debug("db", String.format(Locale.ENGLISH,
330                         "WEB data last fetched at %1.3f and now is %1.3f. re-fetching",
331                         lastUpdate / 1000f, now / 1000f));
332
333             mainModel.scheduleTransactionListRetrieval();
334         }
335     }
336     private void createShortcuts(List<MobileLedgerProfile> list) {
337         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1)
338             return;
339
340         ShortcutManager sm = getSystemService(ShortcutManager.class);
341         List<ShortcutInfo> shortcuts = new ArrayList<>();
342         int i = 0;
343         for (MobileLedgerProfile p : list) {
344             if (shortcuts.size() >= sm.getMaxShortcutCountPerActivity())
345                 break;
346
347             if (!p.isPostingPermitted())
348                 continue;
349
350             final ShortcutInfo.Builder builder =
351                     new ShortcutInfo.Builder(this, "new_transaction_" + p.getUuid());
352             ShortcutInfo si = builder.setShortLabel(p.getName())
353                                      .setIcon(Icon.createWithResource(this,
354                                              R.drawable.thick_plus_icon))
355                                      .setIntent(new Intent(Intent.ACTION_VIEW, null, this,
356                                              NewTransactionActivity.class).putExtra("profile_uuid",
357                                              p.getUuid()))
358                                      .setRank(i)
359                                      .build();
360             shortcuts.add(si);
361             i++;
362         }
363         sm.setDynamicShortcuts(shortcuts);
364     }
365     private void onProfileListChanged(List<MobileLedgerProfile> newList) {
366         if ((newList == null) || newList.isEmpty()) {
367             b.noProfilesLayout.setVisibility(View.VISIBLE);
368             b.mainAppLayout.setVisibility(View.GONE);
369             return;
370         }
371
372         b.mainAppLayout.setVisibility(View.VISIBLE);
373         b.noProfilesLayout.setVisibility(View.GONE);
374
375         b.navProfileList.setMinimumHeight(
376                 (int) (getResources().getDimension(R.dimen.thumb_row_height) * newList.size()));
377
378         Logger.debug("profiles", "profile list changed");
379         mProfileListAdapter.notifyDataSetChanged();
380
381         createShortcuts(newList);
382     }
383     /**
384      * called when the current profile has changed
385      */
386     private void onProfileChanged(MobileLedgerProfile profile) {
387         if (this.profile == null) {
388             if (profile == null)
389                 return;
390         }
391         else {
392             if (this.profile.equals(profile))
393                 return;
394         }
395
396         boolean haveProfile = profile != null;
397
398         if (haveProfile)
399             setTitle(profile.getName());
400         else
401             setTitle(R.string.app_name);
402
403         mainModel.setProfile(profile);
404
405         this.profile = profile;
406
407         int newProfileTheme = haveProfile ? profile.getThemeHue() : -1;
408         if (newProfileTheme != Colors.profileThemeId) {
409             Logger.debug("profiles",
410                     String.format(Locale.ENGLISH, "profile theme %d → %d", Colors.profileThemeId,
411                             newProfileTheme));
412             Colors.profileThemeId = newProfileTheme;
413             profileThemeChanged();
414             // profileThemeChanged would restart the activity, so no need to reload the
415             // data sets below
416             return;
417         }
418
419         b.noProfilesLayout.setVisibility(haveProfile ? View.GONE : View.VISIBLE);
420         b.pagerLayout.setVisibility(haveProfile ? View.VISIBLE : View.VISIBLE);
421
422         mProfileListAdapter.notifyDataSetChanged();
423
424         mainModel.clearAccounts();
425         mainModel.clearTransactions();
426
427         if (haveProfile) {
428             mainModel.scheduleAccountListReload();
429             Logger.debug("transactions", "requesting list reload");
430             mainModel.scheduleTransactionListReload();
431
432             if (profile.isPostingPermitted()) {
433                 b.toolbar.setSubtitle(null);
434                 b.btnAddTransaction.show();
435             }
436             else {
437                 b.toolbar.setSubtitle(R.string.profile_subtitle_read_only);
438                 b.btnAddTransaction.hide();
439             }
440         }
441         else {
442             b.toolbar.setSubtitle(null);
443             b.btnAddTransaction.hide();
444         }
445
446         updateLastUpdateTextFromDB();
447     }
448     private void profileThemeChanged() {
449         // un-hook all observed LiveData
450         Data.removeProfileObservers(this);
451         Data.profiles.removeObservers(this);
452         Data.lastUpdateTransactionCount.removeObservers(this);
453         Data.lastUpdateAccountCount.removeObservers(this);
454         Data.lastUpdateDate.removeObservers(this);
455
456         recreate();
457     }
458     public void fabNewTransactionClicked(View view) {
459         Intent intent = new Intent(this, NewTransactionActivity.class);
460         startActivity(intent);
461         overridePendingTransition(R.anim.slide_in_up, R.anim.dummy);
462     }
463     public void markDrawerItemCurrent(int id) {
464         TextView item = b.drawerLayout.findViewById(id);
465         item.setBackgroundColor(Colors.tableRowDarkBG);
466
467         for (int i = 0; i < b.navActions.getChildCount(); i++) {
468             View view = b.navActions.getChildAt(i);
469             if (view.getId() != id) {
470                 view.setBackgroundColor(Color.TRANSPARENT);
471             }
472         }
473     }
474     public void onAccountSummaryClicked(View view) {
475         b.drawerLayout.closeDrawers();
476
477         showAccountSummaryFragment();
478     }
479     private void showAccountSummaryFragment() {
480         b.mainPager.setCurrentItem(0, true);
481         mainModel.getAccountFilter()
482                  .setValue(null);
483     }
484     public void onLatestTransactionsClicked(View view) {
485         b.drawerLayout.closeDrawers();
486
487         showTransactionsFragment(null);
488     }
489     public void showTransactionsFragment(String accName) {
490         mainModel.getAccountFilter()
491                  .setValue(accName);
492         b.mainPager.setCurrentItem(1, true);
493     }
494     public void showAccountTransactions(String accountName) {
495         mBackMeansToAccountList = true;
496         showTransactionsFragment(accountName);
497     }
498     @Override
499     public void onBackPressed() {
500         if (b.drawerLayout.isDrawerOpen(GravityCompat.START)) {
501             b.drawerLayout.closeDrawer(GravityCompat.START);
502         }
503         else {
504             if (mBackMeansToAccountList && (b.mainPager.getCurrentItem() == 1)) {
505                 mainModel.getAccountFilter()
506                          .setValue(null);
507                 showAccountSummaryFragment();
508                 mBackMeansToAccountList = false;
509             }
510             else {
511                 Logger.debug("fragments", String.format(Locale.ENGLISH, "manager stack: %d",
512                         getSupportFragmentManager().getBackStackEntryCount()));
513
514                 super.onBackPressed();
515             }
516         }
517     }
518     public void updateLastUpdateTextFromDB() {
519         if (profile == null)
520             return;
521
522         long lastUpdate = profile.getLongOption(MLDB.OPT_LAST_SCRAPE, 0L);
523
524         Logger.debug("transactions", String.format(Locale.ENGLISH, "Last update = %d", lastUpdate));
525         if (lastUpdate == 0) {
526             Data.lastUpdateDate.postValue(null);
527         }
528         else {
529             Data.lastUpdateDate.postValue(new Date(lastUpdate));
530         }
531
532         scheduleDataRetrievalIfStale(lastUpdate);
533
534     }
535     private void refreshLastUpdateInfo() {
536         final int formatFlags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR |
537                                 DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_NUMERIC_DATE;
538         String templateForTransactions =
539                 getResources().getString(R.string.transaction_count_summary);
540         String templateForAccounts = getResources().getString(R.string.account_count_summary);
541         Integer accountCount = Data.lastUpdateAccountCount.getValue();
542         Integer transactionCount = Data.lastUpdateTransactionCount.getValue();
543         Date lastUpdate = Data.lastUpdateDate.getValue();
544         if (lastUpdate == null) {
545             Data.lastTransactionsUpdateText.set("----");
546             Data.lastAccountsUpdateText.set("----");
547         }
548         else {
549             Data.lastTransactionsUpdateText.set(
550                     String.format(Objects.requireNonNull(Data.locale.getValue()),
551                             templateForTransactions,
552                             transactionCount == null ? 0 : transactionCount,
553                             DateUtils.formatDateTime(this, lastUpdate.getTime(), formatFlags)));
554             Data.lastAccountsUpdateText.set(
555                     String.format(Objects.requireNonNull(Data.locale.getValue()),
556                             templateForAccounts, accountCount == null ? 0 : accountCount,
557                             DateUtils.formatDateTime(this, lastUpdate.getTime(), formatFlags)));
558         }
559     }
560     public void onStopTransactionRefreshClick(View view) {
561         Logger.debug("interactive", "Cancelling transactions refresh");
562         mainModel.stopTransactionsRetrieval();
563         b.transactionListCancelDownload.setEnabled(false);
564     }
565     public void onRetrieveRunningChanged(Boolean running) {
566         if (running) {
567             b.transactionListCancelDownload.setEnabled(true);
568             ColorStateList csl = Colors.getColorStateList();
569             b.transactionListProgressBar.setIndeterminateTintList(csl);
570             b.transactionListProgressBar.setProgressTintList(csl);
571             b.transactionListProgressBar.setIndeterminate(true);
572             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
573                 b.transactionListProgressBar.setProgress(0, false);
574             }
575             else {
576                 b.transactionListProgressBar.setProgress(0);
577             }
578             b.transactionProgressLayout.setVisibility(View.VISIBLE);
579         }
580         else {
581             b.transactionProgressLayout.setVisibility(View.GONE);
582         }
583     }
584     public void onRetrieveProgress(RetrieveTransactionsTask.Progress progress) {
585         if (progress.getState() == RetrieveTransactionsTask.ProgressState.FINISHED) {
586             Logger.debug("progress", "Done");
587             b.transactionProgressLayout.setVisibility(View.GONE);
588
589             mainModel.transactionRetrievalDone();
590
591             String error = progress.getError();
592             if (error != null) {
593                 if (error.equals(RetrieveTransactionsTask.Result.ERR_JSON_PARSER_ERROR))
594                     error = getResources().getString(R.string.err_json_parser_error);
595
596                 AlertDialog.Builder builder = new AlertDialog.Builder(this);
597                 builder.setMessage(error);
598                 builder.setPositiveButton(R.string.btn_profile_options, (dialog, which) -> {
599                     Logger.debug("error", "will start profile editor");
600                     MobileLedgerProfile.startEditProfileActivity(this, profile);
601                 });
602                 builder.create()
603                        .show();
604                 return;
605             }
606
607             return;
608         }
609
610
611         b.transactionListCancelDownload.setEnabled(true);
612 //        ColorStateList csl = Colors.getColorStateList();
613 //        progressBar.setIndeterminateTintList(csl);
614 //        progressBar.setProgressTintList(csl);
615 //        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
616 //            progressBar.setProgress(0, false);
617 //        else
618 //            progressBar.setProgress(0);
619         b.transactionProgressLayout.setVisibility(View.VISIBLE);
620
621         if (progress.isIndeterminate() || (progress.getTotal() <= 0)) {
622             b.transactionListProgressBar.setIndeterminate(true);
623             Logger.debug("progress", "indeterminate");
624         }
625         else {
626             if (b.transactionListProgressBar.isIndeterminate()) {
627                 b.transactionListProgressBar.setIndeterminate(false);
628             }
629 //            Logger.debug("progress",
630 //                    String.format(Locale.US, "%d/%d", progress.getProgress(), progress.getTotal
631 //                    ()));
632             b.transactionListProgressBar.setMax(progress.getTotal());
633             // for some reason animation doesn't work - no progress is shown (stick at 0)
634             // on lineageOS 14.1 (Nougat, 7.1.2)
635             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
636                 b.transactionListProgressBar.setProgress(progress.getProgress(), false);
637             else
638                 b.transactionListProgressBar.setProgress(progress.getProgress());
639         }
640     }
641     public void fabShouldShow() {
642         if (fabVerticalOffset <= 0) {
643             int top = b.btnAddTransaction.getTop();
644             int parentHeight = b.mainAppLayout.getHeight();
645             fabVerticalOffset = parentHeight - top;
646         }
647         if ((profile != null) && profile.isPostingPermitted() && !b.drawerLayout.isOpen()) {
648             fabShow();
649         }
650         else
651             fabHide();
652     }
653     private void fabShow() {
654         if (wantedFabState == FAB_SHOWN)
655             return;
656
657 //        b.btnAddTransaction.show();
658         if (this.fabSlideAnimator != null) {
659             this.fabSlideAnimator.cancel();
660             b.btnAddTransaction.clearAnimation();
661         }
662
663         wantedFabState = FAB_SHOWN;
664         slideFabTo(b.btnAddTransaction, 0, 200L,
665                 com.google.android.material.animation.AnimationUtils.LINEAR_OUT_SLOW_IN_INTERPOLATOR);
666     }
667     public void fabHide() {
668         if (wantedFabState == FAB_HIDDEN)
669             return;
670
671         if (fabVerticalOffset <= 0)
672             return;
673
674 //        b.btnAddTransaction.hide();
675         if (this.fabSlideAnimator != null) {
676             this.fabSlideAnimator.cancel();
677             b.btnAddTransaction.clearAnimation();
678         }
679
680         wantedFabState = FAB_HIDDEN;
681         slideFabTo(b.btnAddTransaction, fabVerticalOffset, 150L,
682                 com.google.android.material.animation.AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR);
683     }
684     private void slideFabTo(View view, int target, long duration, TimeInterpolator interpolator) {
685         fabSlideAnimator = view.animate()
686                                .translationY((float) target)
687                                .setInterpolator(interpolator)
688                                .setDuration(duration)
689                                .setListener(new AnimatorListenerAdapter() {
690                                    public void onAnimationEnd(Animator animation) {
691                                        fabSlideAnimator = null;
692                                    }
693                                });
694     }
695
696     public static class SectionsPagerAdapter extends FragmentStateAdapter {
697
698         public SectionsPagerAdapter(@NonNull FragmentActivity fragmentActivity) {
699             super(fragmentActivity);
700         }
701         @NotNull
702         @Override
703         public Fragment createFragment(int position) {
704             Logger.debug("main",
705                     String.format(Locale.ENGLISH, "Switching to fragment %d", position));
706             switch (position) {
707                 case 0:
708 //                    debug("flow", "Creating account summary fragment");
709                     return new AccountSummaryFragment();
710                 case 1:
711                     return new TransactionListFragment();
712                 default:
713                     throw new IllegalStateException(
714                             String.format("Unexpected fragment index: " + "%d", position));
715             }
716         }
717
718         @Override
719         public int getItemCount() {
720             return 2;
721         }
722     }
723 }