protected ArrayList<LedgerAccount> doInBackground(CommitAccountsTaskParams... params) {
Data.backgroundTaskStarted();
ArrayList<LedgerAccount> newList = new ArrayList<>();
- String profile = Data.profile.get().getUuid();
+ String profile = Data.profile.getValue().getUuid();
try {
SQLiteDatabase db = MLDB.getDatabase();
private Pattern reAccountName = Pattern.compile("/register\\?q=inacct%3A([a-zA-Z0-9%]+)\"");
private Pattern reAccountValue = Pattern.compile(
"<span class=\"[^\"]*\\bamount\\b[^\"]*\">\\s*([-+]?[\\d.,]+)(?:\\s+(\\S+))?</span>");
- public RetrieveTransactionsTask(WeakReference<MainActivity> contextRef) {
+ private MobileLedgerProfile profile;
+ public RetrieveTransactionsTask(WeakReference<MainActivity> contextRef,
+ MobileLedgerProfile profile) {
this.contextRef = contextRef;
+ this.profile = profile;
}
private static void L(String msg) {
//debug("transaction-parser", msg);
if (context == null) return;
context.onRetrieveDone(null);
}
- private String retrieveTransactionListLegacy(MobileLedgerProfile profile)
+ private String retrieveTransactionListLegacy()
throws IOException, ParseException, HTTPException {
Progress progress = new Progress();
int maxTransactionId = Progress.INDETERMINATE;
new String[]{profile.getUuid()});
db.execSQL("update accounts set keep=0 where profile=?;", new String[]{profile.getUuid()});
}
- private boolean retrieveAccountList(MobileLedgerProfile profile)
+ private boolean retrieveAccountList()
throws IOException, HTTPException {
Progress progress = new Progress();
return true;
}
- private boolean retrieveTransactionList(MobileLedgerProfile profile)
+ private boolean retrieveTransactionList()
throws IOException, ParseException, HTTPException {
Progress progress = new Progress();
int maxTransactionId = Progress.INDETERMINATE;
@SuppressLint("DefaultLocale")
@Override
protected String doInBackground(Void... params) {
- MobileLedgerProfile profile = Data.profile.get();
Data.backgroundTaskStarted();
try {
- if (!retrieveAccountList(profile) || !retrieveTransactionList(profile))
- return retrieveTransactionListLegacy(profile);
+ if (!retrieveAccountList() || !retrieveTransactionList())
+ return retrieveTransactionListLegacy();
return null;
}
catch (MalformedURLException e) {
protected ArrayList<LedgerAccount> doInBackground(Void... params) {
Data.backgroundTaskStarted();
try {
- MobileLedgerProfile profile = Data.profile.get();
+ MobileLedgerProfile profile = Data.profile.getValue();
+ assert profile != null;
String profileUUID = profile.getUuid();
boolean onlyStarred = Data.optShowOnlyStarred.get();
ArrayList<LedgerAccount> newList = new ArrayList<>();
public class UpdateTransactionsTask extends AsyncTask<String, Void, String> {
protected String doInBackground(String[] filterAccName) {
- final MobileLedgerProfile profile = Data.profile.get();
+ final MobileLedgerProfile profile = Data.profile.getValue();
if (profile == null) return "Profile not configured";
String profile_uuid = profile.getUuid();
import android.database.sqlite.SQLiteDatabase;
import net.ktnx.mobileledger.utils.LockHolder;
+import net.ktnx.mobileledger.utils.Locker;
+import net.ktnx.mobileledger.utils.Logger;
import net.ktnx.mobileledger.utils.MLDB;
import net.ktnx.mobileledger.utils.ObservableList;
import net.ktnx.mobileledger.utils.ObservableValue;
public static ObservableList<TransactionListItem> transactions =
new ObservableList<>(new ArrayList<>());
public static ObservableList<LedgerAccount> accounts = new ObservableList<>(new ArrayList<>());
- private static AtomicInteger backgroundTaskCount = new AtomicInteger(0);
public static MutableLiveData<Boolean> backgroundTasksRunning = new MutableLiveData<>(false);
public static MutableLiveData<Date> lastUpdateDate = new MutableLiveData<>();
- public static ObservableValue<MobileLedgerProfile> profile = new ObservableValue<>();
- public static ObservableList<MobileLedgerProfile> profiles =
- new ObservableList<>(new ArrayList<>());
+ public static MutableLiveData<MobileLedgerProfile> profile = new MutableLiveData<>();
+ public static MutableLiveData<ArrayList<MobileLedgerProfile>> profiles =
+ new MutableLiveData<>(new ArrayList<>());
public static ObservableValue<Boolean> optShowOnlyStarred = new ObservableValue<>();
public static MutableLiveData<String> accountFilter = new MutableLiveData<>();
+ private static AtomicInteger backgroundTaskCount = new AtomicInteger(0);
+ private static Locker profilesLocker = new Locker();
public static void backgroundTaskStarted() {
int cnt = backgroundTaskCount.incrementAndGet();
debug("data",
}
public static void setCurrentProfile(MobileLedgerProfile newProfile) {
MLDB.setOption(MLDB.OPT_PROFILE_UUID, newProfile.getUuid());
- profile.set(newProfile);
+ profile.postValue(newProfile);
}
public static int getProfileIndex(MobileLedgerProfile profile) {
- try (LockHolder ignored = profiles.lockForReading()) {
- for (int i = 0; i < profiles.size(); i++) {
- MobileLedgerProfile p = profiles.get(i);
+ try (LockHolder ignored = profilesLocker.lockForReading()) {
+ List<MobileLedgerProfile> prList = profiles.getValue();
+ assert prList != null;
+ for (int i = 0; i < prList.size(); i++) {
+ MobileLedgerProfile p = prList.get(i);
if (p.equals(profile)) return i;
}
}
@SuppressWarnings("WeakerAccess")
public static int getProfileIndex(String profileUUID) {
- try (LockHolder ignored = profiles.lockForReading()) {
- for (int i = 0; i < profiles.size(); i++) {
- MobileLedgerProfile p = profiles.get(i);
+ try (LockHolder ignored = profilesLocker.lockForReading()) {
+ List<MobileLedgerProfile> prList = profiles.getValue();
+ assert prList != null;
+ for (int i = 0; i < prList.size(); i++) {
+ MobileLedgerProfile p = prList.get(i);
if (p.getUuid().equals(profileUUID)) return i;
}
}
public static MobileLedgerProfile getProfile(String profileUUID) {
MobileLedgerProfile profile;
- if (profiles.isEmpty()) {
- profile = MobileLedgerProfile.loadAllFromDB(profileUUID);
- }
- else {
- try (LockHolder ignored = profiles.lockForReading()) {
+ try (LockHolder readLock = profilesLocker.lockForReading()) {
+ List<MobileLedgerProfile> prList = profiles.getValue();
+ assert prList != null;
+ if (prList.isEmpty()) {
+ readLock.close();
+ try (LockHolder ignored = profilesLocker.lockForWriting()) {
+ profile = MobileLedgerProfile.loadAllFromDB(profileUUID);
+ }
+ }
+ else {
int i = getProfileIndex(profileUUID);
if (i == -1) i = 0;
- profile = profiles.get(i);
+ profile = prList.get(i);
}
}
return profile;
import java.util.Comparator;
import java.util.Date;
-import static net.ktnx.mobileledger.utils.Logger.debug;
-
public class LedgerTransaction {
private static final String DIGEST_TYPE = "SHA-256";
public final Comparator<LedgerTransactionAccount> comparator =
dataLoaded = false;
}
public LedgerTransaction(Integer id, Date date, String description) {
- this(id, date, description, Data.profile.get());
+ this(id, date, description, Data.profile.getValue());
}
public LedgerTransaction(Date date, String description) {
this(null, date, description);
import net.ktnx.mobileledger.async.DbOpQueue;
import net.ktnx.mobileledger.utils.Globals;
-import net.ktnx.mobileledger.utils.LockHolder;
import net.ktnx.mobileledger.utils.MLDB;
import java.util.ArrayList;
// returns the profile with the given UUID
public static MobileLedgerProfile loadAllFromDB(String currentProfileUUID) {
MobileLedgerProfile result = null;
- List<MobileLedgerProfile> list = new ArrayList<>();
+ ArrayList<MobileLedgerProfile> list = new ArrayList<>();
SQLiteDatabase db = MLDB.getDatabase();
try (Cursor cursor = db.rawQuery("SELECT uuid, name, url, use_authentication, auth_user, " +
"auth_password, permit_posting, theme, order_no, " +
if (item.getUuid().equals(currentProfileUUID)) result = item;
}
}
- Data.profiles.setList(list);
+ Data.profiles.setValue(list);
return result;
}
public static void storeProfilesOrder() {
db.beginTransaction();
try {
int orderNo = 0;
- try (LockHolder lh = Data.profiles.lockForReading()) {
- for (int i = 0; i < Data.profiles.size(); i++) {
- MobileLedgerProfile p = Data.profiles.get(i);
- db.execSQL("update profiles set order_no=? where uuid=?",
- new Object[]{orderNo, p.getUuid()});
- p.orderNo = orderNo;
- orderNo++;
- }
+ for (MobileLedgerProfile p : Data.profiles.getValue()) {
+ db.execSQL("update profiles set order_no=? where uuid=?",
+ new Object[]{orderNo, p.getUuid()});
+ p.orderNo = orderNo;
+ orderNo++;
}
db.setTransactionSuccessful();
}
Colors.themeWatch.observe(this, this::themeChanged);
swiper.setOnRefreshListener(() -> {
debug("ui", "refreshing accounts via swipe");
- mActivity.scheduleTransactionListRetrieval();
+ Data.scheduleTransactionListRetrieval(mActivity);
});
Data.accounts.addObserver(
new CommitAccountsTaskParams(Data.accounts, Data.optShowOnlyStarred.get()));
}
static public void scheduleAccountListReload() {
- if (Data.profile.get() == null) return;
+ if (Data.profile.getValue() == null) return;
UAT task = new UAT();
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
private int mCurrentPage;
private String mAccountFilter;
private boolean mBackMeansToAccountList = false;
- private Observer profileObserver;
- private Observer profilesObserver;
private Toolbar mToolbar;
private DrawerLayout.SimpleDrawerListener drawerListener;
private ActionBarDrawerToggle barDrawerToggle;
private ViewPager.SimpleOnPageChangeListener pageChangeListener;
private Observer editingProfilesObserver;
+ private MobileLedgerProfile profile;
@Override
protected void onStart() {
super.onStart();
@Override
protected void onDestroy() {
mSectionsPagerAdapter = null;
- Data.profile.deleteObserver(profileObserver);
- profileObserver = null;
- Data.profiles.deleteObserver(profilesObserver);
- profilesObserver = null;
RecyclerView root = findViewById(R.id.nav_profile_list);
if (root != null) root.setAdapter(null);
if (drawer != null) drawer.removeDrawerListener(drawerListener);
mToolbar = findViewById(R.id.toolbar);
setSupportActionBar(mToolbar);
- if (profileObserver == null) {
- profileObserver = (o, arg) -> onProfileChanged(arg);
- Data.profile.addObserver(profileObserver);
- }
+ Data.profile.observe(this, this::onProfileChanged);
- if (profilesObserver == null) {
- profilesObserver = (o, arg) -> onProfileListChanged(arg);
- Data.profiles.addObserver(profilesObserver);
- }
+ Data.profiles.observe(this, this::onProfileListChanged);
if (barDrawerToggle == null) {
barDrawerToggle = new ActionBarDrawerToggle(this, drawer, mToolbar,
findViewById(R.id.nav_profile_list_head_layout)
.setOnClickListener(this::navProfilesHeadClicked);
findViewById(R.id.nav_profiles_label).setOnClickListener(this::navProfilesHeadClicked);
- boolean initialStart = Data.profile.get() == null;
setupProfile();
- if (!initialStart) onProfileChanged(null);
-
- updateLastUpdateTextFromDB();
}
private void scheduleDataRetrievalIfStale(Date lastUpdate) {
long now = new Date().getTime();
scheduleTransactionListRetrieval();
}
}
- private void createShortcuts() {
+ private void createShortcuts(List<MobileLedgerProfile> list) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
List<ShortcutInfo> shortcuts = new ArrayList<>();
- try (LockHolder ignored = Data.profiles.lockForReading()) {
- for (int i = 0; i < Data.profiles.size(); i++) {
- MobileLedgerProfile p = Data.profiles.get(i);
- if (!p.isPostingPermitted()) continue;
-
- ShortcutInfo si = new ShortcutInfo.Builder(this, "new_transaction_" + p.getUuid())
- .setShortLabel(p.getName())
- .setIcon(Icon.createWithResource(this, R.drawable.svg_thick_plus_white))
- .setIntent(new Intent(Intent.ACTION_VIEW, null, this,
- NewTransactionActivity.class).putExtra("profile_uuid", p.getUuid()))
- .setRank(i).build();
- shortcuts.add(si);
- }
+ int i = 0;
+ for (MobileLedgerProfile p : list) {
+ if (!p.isPostingPermitted()) continue;
+
+ ShortcutInfo si = new ShortcutInfo.Builder(this, "new_transaction_" + p.getUuid())
+ .setShortLabel(p.getName())
+ .setIcon(Icon.createWithResource(this, R.drawable.svg_thick_plus_white))
+ .setIntent(
+ new Intent(Intent.ACTION_VIEW, null, this, NewTransactionActivity.class)
+ .putExtra("profile_uuid", p.getUuid())).setRank(i).build();
+ shortcuts.add(si);
+ i++;
}
ShortcutManager sm = getSystemService(ShortcutManager.class);
sm.setDynamicShortcuts(shortcuts);
}
- private void onProfileListChanged(Object arg) {
+ private void onProfileListChanged(List<MobileLedgerProfile> newList) {
+ if (newList.isEmpty()) {
+ findViewById(R.id.no_profiles_layout).setVisibility(View.VISIBLE);
+ findViewById(R.id.pager_layout).setVisibility(View.GONE);
+ findViewById(R.id.loading_layout).setVisibility(View.GONE);
+ return;
+ }
+
+ findViewById(R.id.pager_layout).setVisibility(View.VISIBLE);
+ findViewById(R.id.no_profiles_layout).setVisibility(View.GONE);
+ findViewById(R.id.loading_layout).setVisibility(View.GONE);
+
findViewById(R.id.nav_profile_list).setMinimumHeight(
- (int) (getResources().getDimension(R.dimen.thumb_row_height) *
- Data.profiles.size()));
+ (int) (getResources().getDimension(R.dimen.thumb_row_height) * newList.size()));
debug("profiles", "profile list changed");
- if (arg == null) mProfileListAdapter.notifyDataSetChanged();
- else mProfileListAdapter.notifyItemChanged((int) arg);
-
- createShortcuts();
- }
- private void onProfileChanged(Object arg) {
- MobileLedgerProfile profile = Data.profile.get();
- MainActivity.this.runOnUiThread(() -> {
-
- boolean haveProfile = profile != null;
- findViewById(R.id.no_profiles_layout)
- .setVisibility(haveProfile ? View.GONE : View.VISIBLE);
- findViewById(R.id.pager_layout)
- .setVisibility(haveProfile ? View.VISIBLE : View.VISIBLE);
-
- if (profile == null) MainActivity.this.setTitle(R.string.app_name);
- else MainActivity.this.setTitle(profile.getName());
- MainActivity.this.updateLastUpdateTextFromDB();
- int old_index = -1;
- int new_index = -1;
- if (arg != null) {
- MobileLedgerProfile old = (MobileLedgerProfile) arg;
- old_index = Data.getProfileIndex(old);
- new_index = Data.getProfileIndex(profile);
- }
+ mProfileListAdapter.notifyDataSetChanged();
- if ((old_index != -1) && (new_index != -1)) {
- mProfileListAdapter.notifyItemChanged(old_index);
- mProfileListAdapter.notifyItemChanged(new_index);
- }
- else mProfileListAdapter.notifyDataSetChanged();
-
- MainActivity.this.collapseProfileList();
-
- int newProfileTheme = (profile == null) ? -1 : profile.getThemeId();
- if (newProfileTheme != Colors.profileThemeId) {
- debug("profiles", String.format("profile theme %d → %d", Colors.profileThemeId,
- newProfileTheme));
- MainActivity.this.profileThemeChanged();
- Colors.profileThemeId = newProfileTheme;
- // profileThemeChanged would restart the activity, so no need to reload the
- // data sets below
- return;
- }
- drawer.closeDrawers();
+ createShortcuts(newList);
+ }
+ /** called when the current profile has changed */
+ private void onProfileChanged(MobileLedgerProfile profile) {
+ boolean haveProfile = profile != null;
+ findViewById(R.id.no_profiles_layout).setVisibility(haveProfile ? View.GONE : View.VISIBLE);
+ findViewById(R.id.pager_layout).setVisibility(haveProfile ? View.VISIBLE : View.VISIBLE);
- Data.transactions.clear();
- debug("transactions", "requesting list reload");
- TransactionListViewModel.scheduleTransactionListReload();
+ if (haveProfile) setTitle(profile.getName());
+ else setTitle(R.string.app_name);
+
+ this.profile = profile;
+
+ mProfileListAdapter.notifyDataSetChanged();
- Data.accounts.clear();
- AccountSummaryViewModel.scheduleAccountListReload();
+ int newProfileTheme = haveProfile ? profile.getThemeId() : -1;
+ if (newProfileTheme != Colors.profileThemeId) {
+ debug("profiles",
+ String.format(Locale.ENGLISH, "profile theme %d → %d", Colors.profileThemeId,
+ newProfileTheme));
+ MainActivity.this.profileThemeChanged();
+ Colors.profileThemeId = newProfileTheme;
+ // profileThemeChanged would restart the activity, so no need to reload the
+ // data sets below
+ return;
+ }
+ collapseProfileList();
+
+ drawer.closeDrawers();
- if (profile == null) {
+ Data.transactions.clear();
+ debug("transactions", "requesting list reload");
+ TransactionListViewModel.scheduleTransactionListReload();
+
+ Data.accounts.clear();
+ AccountSummaryViewModel.scheduleAccountListReload();
+
+ if (haveProfile) {
+ if (profile.isPostingPermitted()) {
mToolbar.setSubtitle(null);
- fab.hide();
+ fab.show();
}
else {
- if (profile.isPostingPermitted()) {
- mToolbar.setSubtitle(null);
- fab.show();
- }
- else {
- mToolbar.setSubtitle(R.string.profile_subitlte_read_only);
- fab.hide();
- }
+ mToolbar.setSubtitle(R.string.profile_subitlte_read_only);
+ fab.hide();
}
+ }
+ else {
+ mToolbar.setSubtitle(null);
+ fab.hide();
+ }
- updateLastUpdateTextFromDB();
- });
+ updateLastUpdateTextFromDB();
}
private void updateLastUpdateDisplay(Date newValue) {
LinearLayout l = findViewById(R.id.transactions_last_update_layout);
scheduleDataRetrievalIfStale(newValue);
}
- @Override
- public void finish() {
- if (profilesObserver != null) {
- Data.profiles.deleteObserver(profilesObserver);
- profilesObserver = null;
- }
-
- if (profileObserver != null) {
- Data.profile.deleteObserver(profileObserver);
- profileObserver = null;
- }
-
- super.finish();
- }
private void profileThemeChanged() {
setupProfileColors();
onSaveInstanceState(bundle);
// restart activity to reflect theme change
finish();
+
+ // un-hook all observed LiveData
+ Data.profile.removeObservers(this);
+ Data.profiles.removeObservers(this);
+ Data.lastUpdateDate.removeObservers(this);
Intent intent = new Intent(this, this.getClass());
intent.putExtra(BUNDLE_SAVED_STATE, bundle);
startActivity(intent);
}
private void setupProfile() {
String profileUUID = MLDB.getOption(MLDB.OPT_PROFILE_UUID, null);
- MobileLedgerProfile profile;
-
- profile = Data.getProfile(profileUUID);
-
- if (Data.profiles.isEmpty()) {
- findViewById(R.id.no_profiles_layout).setVisibility(View.VISIBLE);
- findViewById(R.id.pager_layout).setVisibility(View.GONE);
- return;
- }
-
- findViewById(R.id.pager_layout).setVisibility(View.VISIBLE);
- findViewById(R.id.no_profiles_layout).setVisibility(View.GONE);
-
- if (profile == null) profile = Data.profiles.get(0);
-
- if (profile == null) throw new AssertionError("profile must have a value");
+ MobileLedgerProfile startupProfile;
- Data.setCurrentProfile(profile);
+ startupProfile = Data.getProfile(profileUUID);
+ Data.setCurrentProfile(startupProfile);
}
public void fabNewTransactionClicked(View view) {
Intent intent = new Intent(this, NewTransactionActivity.class);
}
}
public void updateLastUpdateTextFromDB() {
- final MobileLedgerProfile profile = Data.profile.get();
long last_update = (profile != null) ? profile.getLongOption(MLDB.OPT_LAST_SCRAPE, 0L) : 0;
debug("transactions", String.format(Locale.ENGLISH, "Last update = %d", last_update));
}
}
public void fabShouldShow() {
- MobileLedgerProfile profile = Data.profile.get();
if ((profile != null) && profile.isPostingPermitted()) fab.show();
}
public void fabHide() {
profileListHeadArrow.startAnimation(AnimationUtils.loadAnimation(this, R.anim.rotate_180));
profileListHeadMore.setVisibility(View.VISIBLE);
profileListHeadMore.startAnimation(AnimationUtils.loadAnimation(this, R.anim.fade_in));
+ final ArrayList<MobileLedgerProfile> profiles = Data.profiles.getValue();
findViewById(R.id.nav_profile_list).setMinimumHeight(
(int) (getResources().getDimension(R.dimen.thumb_row_height) *
- Data.profiles.size()));
+ (profiles != null ? profiles.size() : 0)));
}
private void collapseProfileList() {
boolean wasExpanded = profileListExpanded;
acc.toggleExpanded();
DbOpQueue.add("update accounts set expanded=? where name=? and profile=?",
- new Object[]{acc.isExpanded(), acc.getName(), Data.profile.get().getUuid()
+ new Object[]{acc.isExpanded(), acc.getName(), profile.getUuid()
});
if (wasExpanded) {
debug("accounts", String.format("Expanding account '%s'", acc.getName()));
arrow.setRotation(180);
animator.rotationBy(-180);
- List<LedgerAccount> children =
- Data.profile.get().loadVisibleChildAccountsOf(acc);
+ List<LedgerAccount> children = profile.loadVisibleChildAccountsOf(acc);
try (LockHolder ignored = Data.accounts.lockForWriting()) {
int parentPos = Data.accounts.indexOf(acc);
if (parentPos != -1) {
DbOpQueue
.add("update accounts set amounts_expanded=? where name=? and profile=?",
new Object[]{acc.amountsExpanded(), acc.getName(),
- Data.profile.get().getUuid()
+ profile.getUuid()
});
Data.accounts.triggerItemChangedNotification(acc);
}
import net.ktnx.mobileledger.ui.profiles.ProfileDetailFragment;
import net.ktnx.mobileledger.utils.Colors;
+import java.util.ArrayList;
import java.util.Locale;
import androidx.appcompat.app.ActionBar;
final int index = getIntent().getIntExtra(ProfileDetailFragment.ARG_ITEM_ID, -1);
if (index != -1) {
- profile = Data.profiles.get(index);
- if (profile == null) throw new AssertionError(
- String.format("Can't get profile " + "(index:%d) from the global list", index));
+ ArrayList<MobileLedgerProfile> profiles = Data.profiles.getValue();
+ if (profiles != null) {
+ profile = profiles.get(index);
+ if (profile == null) throw new AssertionError(
+ String.format("Can't get profile " + "(index:%d) from the global list",
+ index));
debug("profiles", String.format(Locale.ENGLISH, "Editing profile %s (%s); hue=%d",
profile.getName(), profile.getUuid(), profile.getThemeId()));
+ }
}
super.onCreate(savedInstanceState);
setupProfileColors();
}
protected void initProfile() {
- mProfile = Data.profile.get();
+ mProfile = Data.profile.getValue();
}
}
import net.ktnx.mobileledger.ui.activity.ProfileDetailActivity;
import net.ktnx.mobileledger.utils.Colors;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
import java.util.Objects;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
import static net.ktnx.mobileledger.utils.Logger.debug;
public ProfileDetailFragment() {
}
@Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ public void onCreateOptionsMenu(@NotNull Menu menu, @NotNull MenuInflater inflater) {
debug("profiles", "[fragment] Creating profile details options menu");
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.profile_details, menu);
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setTitle(mProfile.getName());
builder.setMessage(R.string.remove_profile_dialog_message);
- builder.setPositiveButton(R.string.Remove, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- debug("profiles",
- String.format("[fragment] removing profile %s", mProfile.getUuid()));
- mProfile.removeFromDB();
- Data.profiles.remove(mProfile);
- if (Data.profile.get().equals(mProfile)) {
- debug("profiles", "[fragment] setting current profile to 0");
- Data.setCurrentProfile(Data.profiles.get(0));
- }
- getActivity().finish();
+ builder.setPositiveButton(R.string.Remove, (dialog, which) -> {
+ debug("profiles",
+ String.format("[fragment] removing profile %s", mProfile.getUuid()));
+ mProfile.removeFromDB();
+ ArrayList<MobileLedgerProfile> oldList = Data.profiles.getValue();
+ assert oldList != null;
+ ArrayList<MobileLedgerProfile> newList =
+ (ArrayList<MobileLedgerProfile>) oldList.clone();
+ newList.remove(mProfile);
+ Data.profiles.setValue(newList);
+ if (mProfile.equals(Data.profile.getValue())) {
+ debug("profiles", "[fragment] setting current profile to 0");
+ Data.setCurrentProfile(newList.get(0));
+ final FragmentActivity activity = getActivity();
+ if (activity != null) activity.finish();
}
});
builder.show();
return false;
});
- menuDeleteProfile.setVisible((mProfile != null) && (Data.profiles.size() > 1));
+ final ArrayList<MobileLedgerProfile> profiles = Data.profiles.getValue();
+ menuDeleteProfile
+ .setVisible((mProfile != null) && (profiles != null) && (profiles.size() > 1));
if (BuildConfig.DEBUG) {
final MenuItem menuWipeProfileData = menu.findItem(R.id.menuWipeData);
- menuWipeProfileData.setOnMenuItemClickListener(this::onWipeDataMenuClicked);
+ menuWipeProfileData.setOnMenuItemClickListener(ignored -> onWipeDataMenuClicked());
menuWipeProfileData.setVisible(mProfile != null);
}
}
- private boolean onWipeDataMenuClicked(MenuItem item) {
+ private boolean onWipeDataMenuClicked() {
// this is a development option, so no confirmation
mProfile.wipeAllData();
- Data.profile.forceNotifyObservers();
+ if (mProfile.equals(Data.profile.getValue())) triggerProfileChange();
return true;
}
+ private void triggerProfileChange() {
+ int index = Data.getProfileIndex(mProfile);
+ MobileLedgerProfile newProfile = new MobileLedgerProfile(mProfile);
+ final ArrayList<MobileLedgerProfile> profiles = Data.profiles.getValue();
+ assert profiles != null;
+ profiles.set(index, newProfile);
+ if (mProfile.equals(Data.profile.getValue())) Data.profile.setValue(newProfile);
+ }
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if ((getArguments() != null) && getArguments().containsKey(ARG_ITEM_ID)) {
int index = getArguments().getInt(ARG_ITEM_ID, -1);
- if (index != -1) mProfile = Data.profiles.get(index);
+ ArrayList<MobileLedgerProfile> profiles = Data.profiles.getValue();
+ if ((profiles != null) && (index != -1) && (index < profiles.size()))
+ mProfile = profiles.get(index);
Activity activity = this.getActivity();
if (activity == null) throw new AssertionError();
if (context == null) return;
FloatingActionButton fab = context.findViewById(R.id.fab);
- fab.setOnClickListener(v -> {
- if (!checkValidity()) return;
-
- if (mProfile != null) {
- updateProfileFromUI();
-// debug("profiles", String.format("Selected item is %d", mProfile.getThemeId()));
- mProfile.storeInDB();
- debug("profiles", "profile stored in DB");
- Data.profiles.triggerItemChangedNotification(mProfile);
-
+ fab.setOnClickListener(v -> onSaveFabClicked());
- if (mProfile.getUuid().equals(Data.profile.get().getUuid())) {
- // dummy update to notify the observers of the possibly new name/URL
- Data.profile.forceNotifyObservers();
- }
- }
- else {
- mProfile = new MobileLedgerProfile();
- updateProfileFromUI();
- mProfile.storeInDB();
- Data.profiles.add(mProfile);
- MobileLedgerProfile.storeProfilesOrder();
+ profileName.requestFocus();
+ }
+ private void onSaveFabClicked() {
+ if (!checkValidity()) return;
- // first profile ever?
- if (Data.profiles.size() == 1) Data.profile.set(mProfile);
- }
+ if (mProfile != null) {
+ updateProfileFromUI();
+// debug("profiles", String.format("Selected item is %d", mProfile.getThemeId()));
+ mProfile.storeInDB();
+ debug("profiles", "profile stored in DB");
+ triggerProfileChange();
+ }
+ else {
+ mProfile = new MobileLedgerProfile();
+ updateProfileFromUI();
+ mProfile.storeInDB();
+ final ArrayList<MobileLedgerProfile> profiles = Data.profiles.getValue();
+ assert profiles != null;
+ ArrayList<MobileLedgerProfile> newList =
+ (ArrayList<MobileLedgerProfile>) profiles.clone();
+ newList.add(mProfile);
+ Data.profiles.setValue(newList);
+ MobileLedgerProfile.storeProfilesOrder();
- Activity activity = getActivity();
- if (activity != null) activity.finish();
- });
+ // first profile ever?
+ if (newList.size() == 1) Data.profile.setValue(mProfile);
+ }
- profileName.requestFocus();
+ Activity activity = getActivity();
+ if (activity != null) activity.finish();
}
private void updateProfileFromUI() {
mProfile.setName(profileName.getText());
import net.ktnx.mobileledger.utils.Colors;
import net.ktnx.mobileledger.utils.ObservableValue;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.Locale;
import java.util.Observer;
public boolean onMove(@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder,
@NonNull RecyclerView.ViewHolder target) {
- Data.profiles.blockNotifications();
- try {
- Collections.swap(Data.profiles, viewHolder.getAdapterPosition(),
- target.getAdapterPosition());
- MobileLedgerProfile.storeProfilesOrder();
- notifyItemMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition());
- }
- finally {
- Data.profiles.unblockNotifications();
- }
+ final ArrayList<MobileLedgerProfile> profiles = Data.profiles.getValue();
+ assert profiles != null;
+ Collections.swap(profiles, viewHolder.getAdapterPosition(),
+ target.getAdapterPosition());
+ MobileLedgerProfile.storeProfilesOrder();
+ notifyItemMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition());
return true;
}
@Override
else startEditingProfiles();
}
private void editProfile(View view, MobileLedgerProfile profile) {
- int index = Data.profiles.indexOf(profile);
+ int index = Data.getProfileIndex(profile);
Context context = view.getContext();
Intent intent = new Intent(context, ProfileDetailActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION);
}
@Override
public void onBindViewHolder(@NonNull final ProfileListViewHolder holder, int position) {
- final MobileLedgerProfile profile = Data.profiles.get(position);
- final MobileLedgerProfile currentProfile = Data.profile.get();
+ final ArrayList<MobileLedgerProfile> profiles = Data.profiles.getValue();
+ assert profiles != null;
+ final MobileLedgerProfile profile = profiles.get(position);
+ final MobileLedgerProfile currentProfile = Data.profile.getValue();
debug("profiles", String.format(Locale.ENGLISH,"pos %d: %s, current: %s", position, profile.getUuid(),
(currentProfile == null) ? "<NULL>" : currentProfile.getUuid()));
holder.itemView.setTag(profile);
}
@Override
public int getItemCount() {
- return Data.profiles.size();
+ final ArrayList<MobileLedgerProfile> profiles = Data.profiles.getValue();
+ return profiles != null ? profiles.size() : 0;
}
public boolean isEditingProfiles() {
return editingProfiles.get();
public static ObservableValue<String> updateError = new ObservableValue<>();
public static void scheduleTransactionListReload() {
- if (Data.profile.get() == null) return;
+ if (Data.profile.getValue() == null) return;
String filter = Data.accountFilter.getValue();
AsyncTask<String, Void, String> task = new UTT();
return result;
}
public static void setupTheme(Activity activity) {
- MobileLedgerProfile profile = Data.profile.get();
+ MobileLedgerProfile profile = Data.profile.getValue();
setupTheme(activity, profile);
}
public static void setupTheme(Activity activity, MobileLedgerProfile profile) {
--- /dev/null
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.utils;
+
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+public class Locker {
+ private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
+ public LockHolder lockForWriting() {
+ ReentrantReadWriteLock.WriteLock wLock = lock.writeLock();
+ wLock.lock();
+
+ ReentrantReadWriteLock.ReadLock rLock = lock.readLock();
+ rLock.lock();
+
+ return new LockHolder(rLock, wLock);
+ }
+ public LockHolder lockForReading() {
+ ReentrantReadWriteLock.ReadLock rLock = lock.readLock();
+ rLock.lock();
+ return new LockHolder(rLock);
+ }
+}
final AutoCompleteTextView view,
final String table, final String field,
final boolean profileSpecific) {
- hookAutocompletionAdapter(context, view, table, field, profileSpecific, null, null, Data.profile.get());
+ hookAutocompletionAdapter(context, view, table, field, profileSpecific, null, null,
+ Data.profile.getValue());
}
@TargetApi(Build.VERSION_CODES.N)
public static void hookAutocompletionAdapter(final Context context,