# Changes
-## [0.21.2] = 2022-04-04
+## [0.21.7] = 2024-03-19
+
+* FIXES:
+ + allow user certificates in network security config
+* OTHERS:
+ + bump gradle version
+
+## [0.21.6] - 2023-06-20
+
+* FIXES
+ + fix sending transations to hledger-web 1.23+
+
+## [0.21.5] - 2022-09-03
+
+* FIXES
+ + fix cloud backup
+
+## [0.21.4] - 2022-06-18
+
+* FIXES
+ + fix compatibility wuth hledger-web 1.23+ when submitting new transactions. Thanks to Faye Duxovni for the patch!
+ + fix a crash when deleting templates
+ + fix a rare crash when submitting transactions with multiple accounts with no amounts with zero remaining balance
+
+## [0.21.3] - 2022-04-06
+
+* FIXES
+ + sync gradle version requirements
+* OTHERS
+ + bump version of several dependent libraries
+ + bump SDK version to 31
+ + adjust deprecated constructor usage
+
+## [0.21.2] - 2022-04-04
* FIXES
+ fix crash when auto-balancing multi currency transaction
--- /dev/null
+* Easy way to add tag:value pairs to transactions and transaction accounts
+
+* Filter by tag:value pairs
+
+* Refresh button in account/transaction list
+
+* Top button in account/transaction list
/*
- * Copyright © 2022 Damyan Ivanov.
+ * Copyright © 2023 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
minSdkVersion 22
targetSdkVersion 31
vectorDrawables.useSupportLibrary true
- versionCode 51
- versionName '0.21.2'
+ versionCode 56
+ versionName '0.21.7'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
}
buildFeatures.viewBinding = true
buildToolsVersion '30.0.3'
+ namespace 'net.ktnx.mobileledger'
}
dependencies {
<?xml version="1.0" encoding="utf-8"?><!--
- ~ Copyright © 2022 Damyan Ivanov.
+ ~ Copyright © 2024 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
~ along with MoLe. If not, see <https://www.gnu.org/licenses/>.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- package="net.ktnx.mobileledger">
+ xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
android:name=".App"
android:allowBackup="true"
android:appCategory="productivity"
- android:fullBackupContent="@xml/backup_descriptor"
android:icon="@drawable/app_icon"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@drawable/app_icon_round"
android:supportsRtl="true"
- android:backupAgent="net.ktnx.mobileledger.backup.MobileLedgerBackupAgent"
+ android:backupAgent=".backup.MobileLedgerBackupAgent"
tools:ignore="GoogleAppIndexingWarning">
<activity
android:name=".BackupsActivity"
/*
- * Copyright © 2021 Damyan Ivanov.
+ * Copyright © 2023 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
package net.ktnx.mobileledger.async;
+import static net.ktnx.mobileledger.utils.Logger.debug;
+
import android.util.Log;
import net.ktnx.mobileledger.db.Profile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import static net.ktnx.mobileledger.utils.Logger.debug;
-
/* TODO: get rid of the custom session/cookie and auth code?
* (the last problem with the POST was the missing content-length header)
* This will resolve itself when hledger-web 1.14+ is released with Debian/stable,
case v1_14:
case v1_15:
case v1_19_1:
+ case v1_23:
sendOK(profileApiVersion);
break;
default:
error = e.getMessage();
}
- Misc.onMainThread(()->{
- taskCallback.onTransactionSaveDone(error, transaction);
- });
+ Misc.onMainThread(() -> taskCallback.onTransactionSaveDone(error, transaction));
}
private void legacySendOkWithRetry() throws IOException {
int tried = 0;
/*
- * Copyright © 2021 Damyan Ivanov.
+ * Copyright © 2022 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
package net.ktnx.mobileledger.backup;
-import android.app.backup.BackupAgentHelper;
+import android.app.backup.BackupAgent;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
import android.os.ParcelFileDescriptor;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
-class MobileLedgerBackupAgent extends BackupAgentHelper {
+public class MobileLedgerBackupAgent extends BackupAgent {
private static final int READ_BUF_LEN = 10;
public static String SETTINGS_KEY = "settings";
@Override
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState) throws IOException {
- super.onBackup(oldState, data, newState);
+ Logger.debug("backup", "onBackup()");
backupSettings(data);
newState.close();
}
import android.util.JsonReader;
import android.util.JsonToken;
+import net.ktnx.mobileledger.App;
import net.ktnx.mobileledger.backup.ConfigIO.Keys;
import net.ktnx.mobileledger.dao.CurrencyDAO;
import net.ktnx.mobileledger.dao.ProfileDAO;
import net.ktnx.mobileledger.db.TemplateAccount;
import net.ktnx.mobileledger.db.TemplateHeader;
import net.ktnx.mobileledger.db.TemplateWithAccounts;
+import net.ktnx.mobileledger.model.Data;
+import net.ktnx.mobileledger.utils.Logger;
import java.io.BufferedReader;
import java.io.IOException;
restoreCommodities();
restoreProfiles();
restoreTemplates();
+ restoreCurrentProfile();
}
private void restoreTemplates() {
if (templates == null)
dao.insert(c);
}
}
+ private void restoreCurrentProfile() {
+ if (currentProfile == null) {
+ Logger.debug("backup", "Not restoring current profile (not present in backup)");
+ return;
+ }
+
+ ProfileDAO dao = DB.get()
+ .getProfileDAO();
+
+ Profile p = dao.getByUuidSync(currentProfile);
+
+ if (p != null) {
+ Logger.debug("backup", "Restoring current profile "+p.getName());
+ Data.postCurrentProfile(p);
+ App.storeStartupProfileAndTheme(p.getId(), p.getTheme());
+ }
+ else {
+ Logger.debug("backup", "Not restoring profile "+currentProfile+": not found in DB");
+ }
+ }
}
/*
- * Copyright © 2021 Damyan Ivanov.
+ * Copyright © 2024 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
@Query("DELETE FROM accounts")
public abstract void deleteAllSync();
- @Query("SELECT * FROM accounts WHERE profile_id=:profileId ORDER BY name")
- public abstract LiveData<List<Account>> getAll(long profileId);
+ @Query("SELECT * FROM accounts WHERE profile_id=:profileId AND IIF(:includeZeroBalances=1, 1," +
+ " (EXISTS(SELECT 1 FROM account_values av WHERE av.account_id=accounts.id AND av.value" +
+ " <> 0) OR EXISTS(SELECT 1 FROM accounts a WHERE a.parent_name = accounts.name))) " +
+ "ORDER BY name")
+ public abstract LiveData<List<Account>> getAll(long profileId, boolean includeZeroBalances);
@Transaction
- @Query("SELECT * FROM accounts WHERE profile_id = :profileId ORDER BY name")
- public abstract LiveData<List<AccountWithAmounts>> getAllWithAmounts(long profileId);
+ @Query("SELECT * FROM accounts WHERE profile_id = :profileId AND IIF(:includeZeroBalances=1, " +
+ "1, (EXISTS(SELECT 1 FROM account_values av WHERE av.account_id=accounts.id AND av" +
+ ".value <> 0) OR EXISTS(SELECT 1 FROM accounts a WHERE a.parent_name = accounts.name))" +
+ ") ORDER BY name")
+ public abstract LiveData<List<AccountWithAmounts>> getAllWithAmounts(long profileId,
+ boolean includeZeroBalances);
@Query("SELECT * FROM accounts WHERE id=:id")
public abstract Account getByIdSync(long id);
private List<List<String>> ttags = new ArrayList<>();
private List<ParsedSourcePos> tsourcepos = new ArrayList<>();
public ParsedLedgerTransaction() {
+ ParsedSourcePos startPos = new ParsedSourcePos();
+ ParsedSourcePos endPos = new ParsedSourcePos();
+ endPos.setSourceLine(2);
+
+ tsourcepos.add(startPos);
+ tsourcepos.add(endPos);
}
public static ParsedLedgerTransaction fromLedgerTransaction(LedgerTransaction tr) {
ParsedLedgerTransaction result = new ParsedLedgerTransaction();
ParsedStyle style = new ParsedStyle();
style.setAscommodityside(getCommoditySide());
style.setAscommodityspaced(getCommoditySpaced());
- style.setAsprecision(new ParsedPrecision(2));
+ style.setAsprecision(2);
style.setAsdecimalpoint('.');
amt.setAstyle(style);
if (acc.getCurrency() != null)
+++ /dev/null
-/*
- * Copyright © 2020 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.json.v1_23;
-
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-
-@JsonIgnoreProperties(ignoreUnknown = true)
-class ParsedPrecision {
- private int contents;
- private String tag;
- ParsedPrecision() {
- tag = "NaturalPrecision";
- }
- ParsedPrecision(int contents) {
- this.contents = contents;
- tag = "Precision";
- }
- public int getContents() {
- return contents;
- }
- public void setContents(int contents) {
- this.contents = contents;
- }
- public String getTag() {
- return tag;
- }
- public void setTag(String tag) {
- this.tag = tag;
- }
-}
package net.ktnx.mobileledger.json.v1_23;
class ParsedSourcePos {
- private String sourceName;
- private int sourceLine;
- private int sourceColumn;
+ private String sourceName = "";
+ private int sourceLine = 1;
+ private int sourceColumn = 1;
public ParsedSourcePos() {
}
public String getSourceName() {
@JsonIgnoreProperties(ignoreUnknown = true)
public class ParsedStyle extends net.ktnx.mobileledger.json.ParsedStyle {
- private ParsedPrecision asprecision;
+ private int asprecision;
public ParsedStyle() {
}
- public ParsedPrecision getAsprecision() {
+ public int getAsprecision() {
return asprecision;
}
- public void setAsprecision(ParsedPrecision asprecision) {
+ public void setAsprecision(int asprecision) {
this.asprecision = asprecision;
}
}
/*
- * Copyright © 2021 Damyan Ivanov.
+ * Copyright © 2024 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
else
throw new RuntimeException("Unsupported sub-class " + this);
}
- @NotNull
- public LedgerAccount getAccount() {
- if (this instanceof Account)
- return ((Account) this).account;
-
- throw new IllegalStateException(String.format("Item type is not Account, but %s", this));
+ public boolean isAccount() {
+ return this instanceof Account;
+ }
+ public Account toAccount() {
+ assert isAccount();
+ return ((Account) this);
+ }
+ public boolean isHeader() {
+ return this instanceof Header;
+ }
+ public Header toHeader() {
+ assert isHeader();
+ return ((Header) this);
}
public enum Type {ACCOUNT, HEADER}
((Account) other).account.getAmountsString()
.equals(account.getAmountsString());
}
+ @NotNull
+ public LedgerAccount getAccount() {
+ return account;
+ }
+ public boolean allAmountsAreZero() {
+ return account.allAmountsAreZero();
+ }
}
public static class Header extends AccountListItem {
/*
- * Copyright © 2021 Damyan Ivanov.
+ * Copyright © 2024 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
if (amounts != null)
amounts.clear();
}
- public boolean amountsExpanded() { return amountsExpanded; }
- public void setAmountsExpanded(boolean flag) { amountsExpanded = flag; }
- public void toggleAmountsExpanded() { amountsExpanded = !amountsExpanded; }
+ public boolean amountsExpanded() {return amountsExpanded;}
+ public void setAmountsExpanded(boolean flag) {amountsExpanded = flag;}
+ public void toggleAmountsExpanded() {amountsExpanded = !amountsExpanded;}
public void propagateAmountsTo(LedgerAccount acc) {
for (LedgerAmount a : amounts)
a.propagateToAccount(acc);
}
+ public boolean allAmountsAreZero() {
+ for (LedgerAmount a : amounts) {
+ if (a.getAmount() != 0)
+ return false;
+ }
+
+ return true;
+ }
public List<LedgerAmount> getAmounts() {
return amounts;
}
/*
- * Copyright © 2021 Damyan Ivanov.
+ * Copyright © 2024 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
public class MainModel extends ViewModel {
public final MutableLiveData<Integer> foundTransactionItemIndex = new MutableLiveData<>(null);
private final MutableLiveData<Boolean> updatingFlag = new MutableLiveData<>(false);
+ private final MutableLiveData<Boolean> showZeroBalanceAccounts = new MutableLiveData<>(true);
private final MutableLiveData<String> accountFilter = new MutableLiveData<>(null);
private final MutableLiveData<List<TransactionListItem>> displayedTransactions =
new MutableLiveData<>(new ArrayList<>());
private SimpleDate firstTransactionDate;
private SimpleDate lastTransactionDate;
transient private RetrieveTransactionsTask retrieveTransactionsTask;
- transient private Thread displayedAccountsUpdater;
private TransactionsDisplayedFilter displayedTransactionsUpdater;
public LiveData<Boolean> getUpdatingFlag() {
return updatingFlag;
public void setFirstTransactionDate(SimpleDate earliestDate) {
this.firstTransactionDate = earliestDate;
}
+ public MutableLiveData<Boolean> getShowZeroBalanceAccounts() {return showZeroBalanceAccounts;}
public MutableLiveData<String> getAccountFilter() {
return accountFilter;
}
return;
}
Profile profile = Data.getProfile();
+ assert profile != null;
retrieveTransactionsTask = new RetrieveTransactionsTask(profile);
Logger.debug("db", "Created a background transaction retrieval task");
/*
- * Copyright © 2021 Damyan Ivanov.
+ * Copyright © 2024 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
package net.ktnx.mobileledger.ui.account_summary;
+import static net.ktnx.mobileledger.utils.Logger.debug;
+
import android.content.res.Resources;
import android.view.LayoutInflater;
import android.view.View;
import java.util.List;
import java.util.Locale;
-import static net.ktnx.mobileledger.utils.Logger.debug;
-
public class AccountSummaryAdapter extends RecyclerView.Adapter<AccountSummaryAdapter.RowHolder> {
public static final int AMOUNT_LIMIT = 3;
private static final int ITEM_TYPE_HEADER = 1;
@NonNull AccountListItem newItem) {
Change changes = new Change();
- final LedgerAccount oldAcc = oldItem.getAccount();
- final LedgerAccount newAcc = newItem.getAccount();
+ final LedgerAccount oldAcc = oldItem.toAccount()
+ .getAccount();
+ final LedgerAccount newAcc = newItem.toAccount()
+ .getAccount();
if (!Misc.equalStrings(oldAcc.getName(), newAcc.getName()))
changes.add(Change.NAME);
if (oldType == AccountListItem.Type.HEADER)
return true;
- return oldItem.getAccount()
- .getId() == newItem.getAccount()
+ return oldItem.toAccount()
+ .getAccount()
+ .getId() == newItem.toAccount()
+ .getAccount()
.getId();
}
@Override
return 0;
return listDiffer.getCurrentList()
.get(position)
+ .toAccount()
.getAccount()
.getId();
}
private LedgerAccount getAccount() {
return listDiffer.getCurrentList()
.get(getBindingAdapterPosition())
+ .toAccount()
.getAccount();
}
private void toggleAmountsExpanded() {
}
@Override
public void bind(AccountListItem item, @Nullable List<Object> payloads) {
- LedgerAccount acc = item.getAccount();
+ LedgerAccount acc = item.toAccount()
+ .getAccount();
Change changes = new Change();
if (payloads != null) {
/*
- * Copyright © 2021 Damyan Ivanov.
+ * Copyright © 2024 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
package net.ktnx.mobileledger.ui.account_summary;
+import static net.ktnx.mobileledger.utils.Logger.debug;
+
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
+import net.ktnx.mobileledger.R;
import net.ktnx.mobileledger.async.GeneralBackgroundTasks;
import net.ktnx.mobileledger.databinding.AccountSummaryFragmentBinding;
import net.ktnx.mobileledger.db.AccountWithAmounts;
import java.util.HashMap;
import java.util.List;
-import static net.ktnx.mobileledger.utils.Logger.debug;
-
public class AccountSummaryFragment extends MobileLedgerListFragment {
public AccountSummaryAdapter modelAdapter;
private AccountSummaryFragmentBinding b;
+ private MenuItem menuShowZeroBalances;
+ private MainModel model;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
debug("flow", "AccountSummaryFragment.onActivityCreated()");
super.onViewCreated(view, savedInstanceState);
- MainModel model = new ViewModelProvider(requireActivity()).get(MainModel.class);
+ model = new ViewModelProvider(requireActivity()).get(MainModel.class);
Data.backgroundTasksRunning.observe(this.getViewLifecycleOwner(),
this::onBackgroundTaskRunningChanged);
model.scheduleTransactionListRetrieval();
});
- Data.observeProfile(this, this::onProfileChanged);
+ Data.observeProfile(this, profile -> onProfileChanged(profile, Boolean.TRUE.equals(
+ model.getShowZeroBalanceAccounts()
+ .getValue())));
+ }
+ @Override
+ public void onCreateOptionsMenu(@NotNull Menu menu, @NotNull MenuInflater inflater) {
+ inflater.inflate(R.menu.account_list, menu);
+
+ menuShowZeroBalances = menu.findItem(R.id.menu_account_list_show_zero_balances);
+ if ((menuShowZeroBalances == null))
+ throw new AssertionError();
+
+ menuShowZeroBalances.setOnMenuItemClickListener(menuItem -> {
+ model.getShowZeroBalanceAccounts()
+ .setValue(Boolean.FALSE.equals(model.getShowZeroBalanceAccounts()
+ .getValue()));
+ return true;
+ });
+
+ model.getShowZeroBalanceAccounts()
+ .observe(this, v -> {
+ menuShowZeroBalances.setChecked(v);
+ onProfileChanged(Data.getProfile(), v);
+ });
+
+ super.onCreateOptionsMenu(menu, inflater);
}
- private void onProfileChanged(Profile profile) {
+ private void onProfileChanged(Profile profile, boolean showZeroBalanceAccounts) {
if (profile == null)
return;
DB.get()
.getAccountDAO()
- .getAllWithAmounts(profile.getId())
+ .getAllWithAmounts(profile.getId(), showZeroBalanceAccounts)
.observe(getViewLifecycleOwner(), list -> GeneralBackgroundTasks.run(() -> {
List<AccountListItem> adapterList = new ArrayList<>();
adapterList.add(new AccountListItem.Header(Data.lastAccountsUpdateText));
adapterList.add(new AccountListItem.Account(account));
accMap.put(dbAcc.account.getName(), account);
}
+
+ if (!showZeroBalanceAccounts) {
+ removeZeroAccounts(adapterList);
+ }
modelAdapter.setAccounts(adapterList);
Data.lastUpdateAccountCount.postValue(adapterList.size() - 1);
}));
}
+ private void removeZeroAccounts(List<AccountListItem> list) {
+ boolean removed = true;
+
+ while (removed) {
+ AccountListItem last = null;
+ removed = false;
+ List<AccountListItem> newList = new ArrayList<>();
+
+ for (AccountListItem item : list) {
+ if (last == null) {
+ last = item;
+ continue;
+ }
+
+ if (!last.isAccount() || !last.toAccount()
+ .allAmountsAreZero() || last.toAccount()
+ .getAccount()
+ .isParentOf(
+ item.toAccount()
+ .getAccount()))
+ {
+ newList.add(last);
+ }
+ else {
+ removed = true;
+ }
+
+ last = item;
+ }
+
+ if (last != null) {
+ if (!last.isAccount() || !last.toAccount()
+ .allAmountsAreZero())
+ {
+ newList.add(last);
+ }
+ else {
+ removed = true;
+ }
+ }
+
+ list.clear();
+ list.addAll(newList);
+ }
+ }
}
if (emptyAmountAccounts.size() > 0) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
emptyAmountAccounts.forEach((currency, accounts) -> {
- if (accounts.size() != 1)
+ final Float balance = emptyAmountAccountBalance.get(currency);
+
+ if (balance != null && !Misc.isZero(balance) && accounts.size() != 1) {
throw new RuntimeException(String.format(Locale.US,
- "Should not happen: approved transaction has %d accounts for " +
- "currency %s", accounts.size(), currency));
- accounts.get(0)
- .setAmount(-Objects.requireNonNull(
- emptyAmountAccountBalance.get(currency)));
+ "Should not happen: approved transaction has %d accounts " +
+ "without amounts for currency '%s'", accounts.size(), currency));
+ }
+ accounts.forEach(acc -> acc.setAmount(balance == null ? 0 : -balance));
});
}
else {
for (String currency : emptyAmountAccounts.keySet()) {
List<LedgerTransactionAccount> accounts =
Objects.requireNonNull(emptyAmountAccounts.get(currency));
-
- if (accounts.size() != 1)
+ final Float balance = emptyAmountAccountBalance.get(currency);
+ if (balance != null && !Misc.isZero(balance) && accounts.size() != 1)
throw new RuntimeException(String.format(Locale.US,
"Should not happen: approved transaction has %d accounts for " +
"currency %s", accounts.size(), currency));
- accounts.get(0)
- .setAmount(-Objects.requireNonNull(
- emptyAmountAccountBalance.get(currency)));
+ for (LedgerTransactionAccount acc : accounts) {
+ acc.setAmount(balance == null ? 0 : -balance);
+ }
}
}
}
/*
- * Copyright © 2021 Damyan Ivanov.
+ * Copyright © 2022 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
* along with MoLe. If not, see <https://www.gnu.org/licenses/>.
*/
-//
-// Substantial portions taken from DividerItemDecoration subject to the following license terms:
-//
-// Copyright 2018 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
package net.ktnx.mobileledger.ui.templates;
import android.content.Context;
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final int childAdapterPosition = parent.getChildAdapterPosition(child);
- if (adapter.getItemViewType(childAdapterPosition) ==
+ if (childAdapterPosition == RecyclerView.NO_POSITION ||
+ adapter.getItemViewType(childAdapterPosition) ==
TemplatesRecyclerViewAdapter.ITEM_TYPE_DIVIDER ||
childAdapterPosition + 1 < itemCount &&
adapter.getItemViewType(childAdapterPosition + 1) ==
}
private void drawHorizontal(Canvas canvas, RecyclerView parent) {
+ final RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
+ if (layoutManager == null)
+ return;
+
canvas.save();
final int top;
final int bottom;
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final int childAdapterPosition = parent.getChildAdapterPosition(child);
- if (adapter.getItemViewType(childAdapterPosition) ==
+ if (childAdapterPosition == RecyclerView.NO_POSITION ||
+ adapter.getItemViewType(childAdapterPosition) ==
TemplatesRecyclerViewAdapter.ITEM_TYPE_DIVIDER ||
childAdapterPosition + 1 < itemCount &&
adapter.getItemViewType(childAdapterPosition + 1) ==
TemplatesRecyclerViewAdapter.ITEM_TYPE_DIVIDER)
+ {
continue;
- parent.getLayoutManager()
- .getDecoratedBoundsWithMargins(child, mBounds);
+ }
+ layoutManager.getDecoratedBoundsWithMargins(child, mBounds);
final int right = mBounds.right + Math.round(child.getTranslationX());
final int left = right - divider.getIntrinsicWidth();
divider.setBounds(left, top, right, bottom);
(TemplatesRecyclerViewAdapter) Objects.requireNonNull(parent.getAdapter());
final int itemCount = adapter.getItemCount();
- if (adapter.getItemViewType(childAdapterPosition) ==
+ if (childAdapterPosition == RecyclerView.NO_POSITION ||
+ adapter.getItemViewType(childAdapterPosition) ==
TemplatesRecyclerViewAdapter.ITEM_TYPE_DIVIDER ||
childAdapterPosition + 1 < itemCount &&
adapter.getItemViewType(childAdapterPosition + 1) ==
/*
- * Copyright © 2021 Damyan Ivanov.
+ * Copyright © 2024 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
import android.view.View;
+import androidx.constraintlayout.widget.ConstraintLayout;
+
import net.ktnx.mobileledger.App;
import net.ktnx.mobileledger.databinding.TransactionDelimiterBinding;
import net.ktnx.mobileledger.model.TransactionListItem;
+import net.ktnx.mobileledger.utils.DimensionUtils;
import net.ktnx.mobileledger.utils.Globals;
import net.ktnx.mobileledger.utils.SimpleDate;
b.transactionDelimiterMonth.setText(
Globals.monthNames[cal.get(GregorianCalendar.MONTH)]);
b.transactionDelimiterMonth.setVisibility(View.VISIBLE);
- // holder.vDelimiterLine.setBackgroundResource(R.drawable
- // .dashed_border_8dp);
b.transactionDelimiterThick.setVisibility(View.VISIBLE);
+ ConstraintLayout.LayoutParams lp =
+ (ConstraintLayout.LayoutParams) b.transactionDelimiterThick.getLayoutParams();
+ lp.height = DimensionUtils.dp2px(b.getRoot()
+ .getContext(), 4);
+ b.transactionDelimiterThick.setLayoutParams(lp);
}
else {
b.transactionDelimiterMonth.setVisibility(View.GONE);
- // holder.vDelimiterLine.setBackgroundResource(R.drawable
- // .dashed_border_1dp);
- b.transactionDelimiterThick.setVisibility(View.GONE);
+ ConstraintLayout.LayoutParams lp =
+ (ConstraintLayout.LayoutParams) b.transactionDelimiterThick.getLayoutParams();
+ lp.height = DimensionUtils.dp2px(b.getRoot()
+ .getContext(), 1.3f);
+ b.transactionDelimiterThick.setLayoutParams(lp);
+ b.transactionDelimiterThick.setVisibility(View.VISIBLE);
}
}
/*
- * Copyright © 2021 Damyan Ivanov.
+ * Copyright © 2024 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
package net.ktnx.mobileledger.utils;
+import static net.ktnx.mobileledger.utils.Logger.debug;
+
import android.app.Activity;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import java.util.Locale;
import java.util.Objects;
-import static net.ktnx.mobileledger.utils.Logger.debug;
-
public class Colors {
public static final int DEFAULT_HUE_DEG = 261;
public static final MutableLiveData<Integer> themeWatch = new MutableLiveData<>(0);
TypedValue tv = new TypedValue();
theme.resolveAttribute(R.attr.table_row_dark_bg, tv, true);
tableRowDarkBG = tv.data;
- theme.resolveAttribute(R.attr.colorPrimary, tv, true);
+ theme.resolveAttribute(androidx.appcompat.R.attr.colorPrimary, tv, true);
primary = tv.data;
if (themePrimaryColor.size() == 0) {
Resources.Theme tmpTheme = theme.getResources()
.newTheme();
tmpTheme.applyStyle(themeId, true);
- tmpTheme.resolveAttribute(R.attr.colorPrimary, tv, false);
+ tmpTheme.resolveAttribute(androidx.appcompat.R.attr.colorPrimary, tv, false);
themePrimaryColor.put(themeId, tv.data);
}
}
huesSB.append(", ");
huesSB.append(h);
}
- debug("profiles", String.format("used hues: %s", huesSB.toString()));
+ debug("profiles", String.format("used hues: %s", huesSB));
}
hues.add(hues.get(0));
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright © 2024 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/>.
+ -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ >
+
+ <item
+ android:id="@+id/menu_account_list_show_zero_balances"
+ android:checkable="true"
+ android:enabled="true"
+ android:menuCategory="container"
+ android:title="@string/accounts_menu_show_zero"
+ android:titleCondensed="@string/accounts_menu_show_zero_condensed"
+ android:visible="true"
+ app:showAsAction="withText"
+ />
+</menu>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright © 2021 Damyan Ivanov.
+ ~ Copyright © 2024 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
<string name="no_profile_restore_hint">… а може и да възстановите настройките от резервно копие</string>
<string name="profile_not_available">Недостъпен профил</string>
<string name="api_1_23">Версия 1.23</string>
+ <string name="accounts_menu_show_zero">Сметки с нулев баланс</string>
+ <string name="accounts_menu_show_zero_condensed">Нулеви сметки</string>
</resources>
<!--
- ~ Copyright © 2021 Damyan Ivanov.
+ ~ Copyright © 2024 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
<string name="no_profile_restore_hint">… or, you may restore from backup</string>
<string name="profile_not_available">Profile not available</string>
<string name="api_1_23">Version 1.23</string>
+ <string name="accounts_menu_show_zero">Show zero balances</string>
+ <string name="accounts_menu_show_zero_condensed">Zero balances</string>
</resources>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ 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/>.
- -->
-
-<full-backup-content>
- <!-- Exclude specific shared preferences that contain GCM registration Id -->
-</full-backup-content>
<?xml version="1.0" encoding="utf-8"?><!--
- ~ Copyright © 2019 Damyan Ivanov.
+ ~ Copyright © 2024 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
-->
<network-security-config>
- <base-config cleartextTrafficPermitted="true" />
+ <base-config cleartextTrafficPermitted="true">
+ <trust-anchors>
+ <certificates src="system" />
+ <certificates src="user" />
+ </trust-anchors>
+ </base-config>
</network-security-config>
\ No newline at end of file
mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:7.1.2'
+ classpath 'com.android.tools.build:gradle:8.0.2'
// NOTE: Do not place your application dependencies here; they belong
#
-# Copyright © 2019 Damyan Ivanov.
+# Copyright © 2024 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
# along with MoLe. If not, see <https://www.gnu.org/licenses/>.
#
-# Project-wide Gradle settings.
-# IDE (e.g. Android Studio) users:
-# Gradle settings configured through the IDE *will override*
-# any settings specified in this file.
-# For more details on how to configure your build environment visit
+## For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
+#
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
-org.gradle.jvmargs=-Xmx1536m
+# Default value: -Xmx1024m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+#
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
+#Sun Mar 17 11:29:00 EET 2024
android.debug.obsoleteApi=true
+android.defaults.buildfeatures.buildconfig=true
+android.enableJetifier=false
+android.enableR8.fullMode=false
+android.nonFinalResIds=false
+android.nonTransitiveRClass=true
android.useAndroidX=true
-android.enableJetifier=true
\ No newline at end of file
+org.gradle.jvmargs=-Xmx1024M -Dkotlin.daemon.jvm.options\="-Xmx1536M"
+org.gradle.unsafe.configuration-cache=true
-#Sat Nov 28 08:24:27 EET 2020
+#
+# Copyright © 2024 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/>.
+#
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
--- /dev/null
+* ПОПРАВКИ
+ + коригирани версии на gradle
+* ДРУГИ
+ + обновени версии на множество библиотеки
+ + прицелване във версия 31 на платформата
--- /dev/null
+* ПОПРАВКИ
+ + отстранен проблем със съвместимостта с hledger-web 1.23+ при изпращане на нови транзакции. Благодарности на Faye Duxovni за поправката!
+ + поправен срив при изтриване на шаблони
+ + поправен рядък срив при изпращане на транзакции, съдържащи повече от една сметка без сума и нулев остатъчен баланс
--- /dev/null
+* ПОПРАВКИ
+ + отстранен проблем с централизираното резервно копие на настройките
--- /dev/null
+* ПОПРАВКИ
+ + отстранен проблем при изпращане на транзакции към hledger-web 1.23+
--- /dev/null
+* ПОПРАВКИ
+ + Позволяване на потребителски сертификати в настройките за сигурността на мрежовите връзки
+* ДРУГИ
+ + Обновена версия на gradle
--- /dev/null
+* FIXES
+ + sync gradle version requirements
+* OTHERS
+ + bump version of several dependent libraries
+ + bump SDK version to 31
+ + adjust deprecated constructor usage
--- /dev/null
+* FIXES
+ + fix compatibility wuth hledger-web 1.23+ when submitting new transactions. Thanks to Faye Duxovni for the patch!
+ + fix a crash when deleting templates
+ + fix a rare crash when submitting transactions with multiple accounts with no amounts with zero remaining balance
--- /dev/null
+* FIXES
+ + fix cloud backup
--- /dev/null
+* FIXES:
+ + fixed sending of transactions to hledger-web 1.23+
--- /dev/null
+* FIXES:
+ + allow user certificates in network security config
+* OTHERS:
+ + bump gradle version