mirror of
				https://github.com/m2049r/xmrwallet
				synced 2025-11-04 04:52:30 +01:00 
			
		
		
		
	Compare commits
	
		
			25 Commits
		
	
	
		
			v4.0.7
			...
			update/dep
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					1754b48936 | ||
| 
						 | 
					ef3a19b66e | ||
| 
						 | 
					9fd382e622 | ||
| 
						 | 
					f7c1cc9713 | ||
| 
						 | 
					30023e85bb | ||
| 
						 | 
					bf8780c874 | ||
| 
						 | 
					9379269f89 | ||
| 
						 | 
					a9b4abc01f | ||
| 
						 | 
					899ac775b1 | ||
| 
						 | 
					8b7c5a2450 | ||
| 
						 | 
					05c1ca5082 | ||
| 
						 | 
					6abf11841e | ||
| 
						 | 
					d46b8bf79a | ||
| 
						 | 
					149fd6376e | ||
| 
						 | 
					de70d64eb8 | ||
| 
						 | 
					232f7b801e | ||
| 
						 | 
					8962bd3050 | ||
| 
						 | 
					989d52b33d | ||
| 
						 | 
					a3c0ca7ebe | ||
| 
						 | 
					c9132d7d97 | ||
| 
						 | 
					758b042680 | ||
| 
						 | 
					84ce392192 | ||
| 
						 | 
					4ebcda2b14 | ||
| 
						 | 
					c49351a8a9 | ||
| 
						 | 
					41e84f2e29 | 
@@ -8,8 +8,8 @@ android {
 | 
			
		||||
        compileSdk 35
 | 
			
		||||
        minSdkVersion 21
 | 
			
		||||
        targetSdkVersion 35
 | 
			
		||||
        versionCode 4007
 | 
			
		||||
        versionName "4.0.7 'Sidekick'"
 | 
			
		||||
        versionCode 4106
 | 
			
		||||
        versionName "4.1.6 'Exolix'"
 | 
			
		||||
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
 | 
			
		||||
        externalNativeBuild {
 | 
			
		||||
            cmake {
 | 
			
		||||
@@ -58,7 +58,7 @@ android {
 | 
			
		||||
            applicationIdSuffix ".debug"
 | 
			
		||||
        }
 | 
			
		||||
        applicationVariants.all { variant ->
 | 
			
		||||
            variant.buildConfigField "String", "ID_A", "\"" + getId("ID_A") + "\""
 | 
			
		||||
            variant.buildConfigField "String", "ID_F", "\"" + getId("ID_F") + "\""
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -108,8 +108,8 @@ android {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    compileOptions {
 | 
			
		||||
        sourceCompatibility JavaVersion.VERSION_1_8
 | 
			
		||||
        targetCompatibility JavaVersion.VERSION_1_8
 | 
			
		||||
        sourceCompatibility = JavaVersion.VERSION_17
 | 
			
		||||
        targetCompatibility = JavaVersion.VERSION_17
 | 
			
		||||
    }
 | 
			
		||||
    namespace 'com.m2049r.xmrwallet'
 | 
			
		||||
    buildFeatures {
 | 
			
		||||
@@ -123,6 +123,10 @@ android {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    lint {
 | 
			
		||||
        disable "MissingTranslation" // Translation is crowd-sourced.
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static def getId(name) {
 | 
			
		||||
@@ -132,40 +136,42 @@ static def getId(name) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.22"))
 | 
			
		||||
    implementation(platform('org.jetbrains.kotlin:kotlin-bom:2.1.21'))
 | 
			
		||||
 | 
			
		||||
    implementation 'androidx.core:core:1.13.1'
 | 
			
		||||
    implementation 'androidx.appcompat:appcompat:1.7.0'
 | 
			
		||||
    implementation 'androidx.core:core:1.16.0'
 | 
			
		||||
    implementation 'androidx.appcompat:appcompat:1.7.1'
 | 
			
		||||
    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
 | 
			
		||||
    implementation 'androidx.recyclerview:recyclerview:1.3.2'
 | 
			
		||||
    implementation 'androidx.recyclerview:recyclerview:1.4.0'
 | 
			
		||||
    implementation 'androidx.cardview:cardview:1.0.0'
 | 
			
		||||
    implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
 | 
			
		||||
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
 | 
			
		||||
    implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
 | 
			
		||||
    implementation 'androidx.preference:preference:1.2.1'
 | 
			
		||||
 | 
			
		||||
    implementation 'com.google.android.material:material:1.12.0'
 | 
			
		||||
 | 
			
		||||
    implementation 'com.google.guava:guava:33.4.8-android'
 | 
			
		||||
 | 
			
		||||
    implementation 'me.dm7.barcodescanner:zxing:1.9.8'
 | 
			
		||||
    implementation "com.squareup.okhttp3:okhttp:4.12.0"
 | 
			
		||||
    implementation "io.github.rburgst:okhttp-digest:3.1.0"
 | 
			
		||||
    implementation 'io.github.rburgst:okhttp-digest:3.1.1'
 | 
			
		||||
    implementation "com.jakewharton.timber:timber:5.0.1"
 | 
			
		||||
 | 
			
		||||
    implementation 'info.guardianproject.netcipher:netcipher:2.1.0'
 | 
			
		||||
    //implementation 'info.guardianproject.netcipher:netcipher-okhttp3:2.1.0'
 | 
			
		||||
    implementation fileTree(dir: 'libs/classes', include: ['*.jar'])
 | 
			
		||||
    implementation 'com.nulab-inc:zxcvbn:1.8.2'
 | 
			
		||||
    implementation 'com.nulab-inc:zxcvbn:1.9.0'
 | 
			
		||||
 | 
			
		||||
    implementation 'dnsjava:dnsjava:3.5.3'
 | 
			
		||||
    implementation 'org.slf4j:slf4j-nop:2.0.11'
 | 
			
		||||
    implementation 'dnsjava:dnsjava:3.6.3'
 | 
			
		||||
    implementation 'org.slf4j:slf4j-nop:2.0.17'
 | 
			
		||||
    implementation 'com.github.brnunes:swipeablerecyclerview:1.0.2'
 | 
			
		||||
 | 
			
		||||
    //noinspection GradleDependency
 | 
			
		||||
    testImplementation "junit:junit:4.13.2"
 | 
			
		||||
    testImplementation "org.mockito:mockito-all:1.10.19"
 | 
			
		||||
    testImplementation "com.squareup.okhttp3:mockwebserver:4.12.0"
 | 
			
		||||
    testImplementation 'org.json:json:20231013'
 | 
			
		||||
    testImplementation 'org.json:json:20250517'
 | 
			
		||||
    testImplementation 'net.jodah:concurrentunit:0.4.6'
 | 
			
		||||
 | 
			
		||||
    compileOnly 'org.projectlombok:lombok:1.18.30'
 | 
			
		||||
    annotationProcessor 'org.projectlombok:lombok:1.18.30'
 | 
			
		||||
    compileOnly 'org.projectlombok:lombok:1.18.38'
 | 
			
		||||
    annotationProcessor 'org.projectlombok:lombok:1.18.38'
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -99,7 +99,7 @@ public class GenerateReviewFragment extends Fragment {
 | 
			
		||||
    private String walletPath;
 | 
			
		||||
    private String walletName;
 | 
			
		||||
 | 
			
		||||
    private OnBackPressedCallback backPressedCallback = new OnBackPressedCallback(false) {
 | 
			
		||||
    private final OnBackPressedCallback backPressedCallback = new OnBackPressedCallback(false) {
 | 
			
		||||
        @Override
 | 
			
		||||
        public void handleOnBackPressed() {
 | 
			
		||||
            // nothing
 | 
			
		||||
@@ -164,6 +164,7 @@ public class GenerateReviewFragment extends Fragment {
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        Bundle args = getArguments();
 | 
			
		||||
        assert args != null;
 | 
			
		||||
        type = args.getString(REQUEST_TYPE);
 | 
			
		||||
        walletPath = args.getString(REQUEST_PATH);
 | 
			
		||||
        localPassword = args.getString(REQUEST_PASSWORD);
 | 
			
		||||
 
 | 
			
		||||
@@ -22,13 +22,14 @@ import android.view.View;
 | 
			
		||||
import android.view.ViewGroup;
 | 
			
		||||
import android.widget.FrameLayout;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import androidx.fragment.app.Fragment;
 | 
			
		||||
 | 
			
		||||
import timber.log.Timber;
 | 
			
		||||
 | 
			
		||||
public class LockFragment extends Fragment {
 | 
			
		||||
    @Override
 | 
			
		||||
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
 | 
			
		||||
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
 | 
			
		||||
        Timber.d("onCreateView");
 | 
			
		||||
        final FrameLayout frame = new FrameLayout(requireContext());
 | 
			
		||||
        frame.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
 | 
			
		||||
 
 | 
			
		||||
@@ -619,6 +619,7 @@ public class LoginActivity extends BaseActivity
 | 
			
		||||
        try {
 | 
			
		||||
            GenerateReviewFragment detailsFragment = (GenerateReviewFragment)
 | 
			
		||||
                    getSupportFragmentManager().findFragmentById(R.id.fragment_container);
 | 
			
		||||
            assert detailsFragment != null;
 | 
			
		||||
            AlertDialog dialog = detailsFragment.createChangePasswordDialog();
 | 
			
		||||
            if (dialog != null) {
 | 
			
		||||
                Helper.showKeyboard(dialog);
 | 
			
		||||
@@ -896,6 +897,7 @@ public class LoginActivity extends BaseActivity
 | 
			
		||||
        try {
 | 
			
		||||
            GenerateFragment genFragment = (GenerateFragment)
 | 
			
		||||
                    getSupportFragmentManager().findFragmentById(R.id.fragment_container);
 | 
			
		||||
            assert genFragment != null;
 | 
			
		||||
            genFragment.walletGenerateError();
 | 
			
		||||
        } catch (ClassCastException ex) {
 | 
			
		||||
            Timber.e("walletGenerateError() but not in GenerateFragment");
 | 
			
		||||
@@ -1261,7 +1263,7 @@ public class LoginActivity extends BaseActivity
 | 
			
		||||
            if (usbManager.hasPermission(device)) {
 | 
			
		||||
                connectLedger(usbManager, device);
 | 
			
		||||
            } else {
 | 
			
		||||
                ContextCompat.registerReceiver(this, usbPermissionReceiver, new IntentFilter(ACTION_USB_PERMISSION), ContextCompat.RECEIVER_NOT_EXPORTED);
 | 
			
		||||
                ContextCompat.registerReceiver(this, usbPermissionReceiver, new IntentFilter(ACTION_USB_PERMISSION), ContextCompat.RECEIVER_EXPORTED);
 | 
			
		||||
                usbManager.requestPermission(device,
 | 
			
		||||
                        PendingIntent.getBroadcast(this, 0,
 | 
			
		||||
                                new Intent(ACTION_USB_PERMISSION),
 | 
			
		||||
@@ -1366,7 +1368,7 @@ public class LoginActivity extends BaseActivity
 | 
			
		||||
    private void registerDetachReceiver() {
 | 
			
		||||
        detachReceiver = new BroadcastReceiver() {
 | 
			
		||||
            public void onReceive(Context context, Intent intent) {
 | 
			
		||||
                if (intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
 | 
			
		||||
                if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(intent.getAction())) {
 | 
			
		||||
                    unregisterDetachReceiver();
 | 
			
		||||
                    final UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
 | 
			
		||||
                    Timber.i("Ledger detached!");
 | 
			
		||||
@@ -1380,7 +1382,7 @@ public class LoginActivity extends BaseActivity
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        ContextCompat.registerReceiver(this, detachReceiver, new IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED), ContextCompat.RECEIVER_NOT_EXPORTED);
 | 
			
		||||
        ContextCompat.registerReceiver(this, detachReceiver, new IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED), ContextCompat.RECEIVER_EXPORTED);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void onLedgerAction() {
 | 
			
		||||
@@ -1409,6 +1411,7 @@ public class LoginActivity extends BaseActivity
 | 
			
		||||
        Timber.d("onDeviceConnected: %s", connectedDeviceName);
 | 
			
		||||
        try {
 | 
			
		||||
            SidekickConnectFragment f = (SidekickConnectFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_container);
 | 
			
		||||
            if (f == null) return;
 | 
			
		||||
            f.allowClick();
 | 
			
		||||
        } catch (ClassCastException ex) {
 | 
			
		||||
            // ignore it
 | 
			
		||||
 
 | 
			
		||||
@@ -152,7 +152,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
 | 
			
		||||
        showNetwork();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
 | 
			
		||||
    private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
 | 
			
		||||
        @Override
 | 
			
		||||
        public void handleOnBackPressed() {
 | 
			
		||||
            animateFAB();
 | 
			
		||||
@@ -283,7 +283,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // remove information of non-existent wallet
 | 
			
		||||
        Set<String> removedWallets = getActivity()
 | 
			
		||||
        Set<String> removedWallets = requireActivity()
 | 
			
		||||
                .getSharedPreferences(KeyStoreHelper.SecurityConstants.WALLET_PASS_PREFS_NAME, Context.MODE_PRIVATE)
 | 
			
		||||
                .getAll().keySet();
 | 
			
		||||
        for (WalletManager.WalletInfo s : walletList) {
 | 
			
		||||
@@ -445,7 +445,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void setSubtext(String status) {
 | 
			
		||||
        final Context ctx = getContext();
 | 
			
		||||
        final Context ctx = requireContext();
 | 
			
		||||
        final Spanned text = Html.fromHtml(ctx.getString(R.string.status,
 | 
			
		||||
                Integer.toHexString(ThemeHelper.getThemedColor(ctx, R.attr.positiveColor) & 0xFFFFFF),
 | 
			
		||||
                Integer.toHexString(ThemeHelper.getThemedColor(ctx, android.R.attr.colorBackground) & 0xFFFFFF),
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,7 @@ import android.widget.TextView;
 | 
			
		||||
import android.widget.Toast;
 | 
			
		||||
 | 
			
		||||
import androidx.activity.OnBackPressedCallback;
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import androidx.annotation.Nullable;
 | 
			
		||||
import androidx.appcompat.app.AlertDialog;
 | 
			
		||||
import androidx.fragment.app.Fragment;
 | 
			
		||||
@@ -68,7 +69,7 @@ public class NodeFragment extends Fragment
 | 
			
		||||
 | 
			
		||||
    static private int NODES_TO_FIND = 10;
 | 
			
		||||
 | 
			
		||||
    static private NumberFormat FORMATTER = NumberFormat.getInstance();
 | 
			
		||||
    static private final NumberFormat FORMATTER = NumberFormat.getInstance();
 | 
			
		||||
 | 
			
		||||
    private SwipeRefreshLayout pullToRefresh;
 | 
			
		||||
    private TextView tvPull;
 | 
			
		||||
@@ -104,7 +105,7 @@ public class NodeFragment extends Fragment
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onAttach(Context context) {
 | 
			
		||||
    public void onAttach(@NonNull Context context) {
 | 
			
		||||
        super.onAttach(context);
 | 
			
		||||
        if (context instanceof Listener) {
 | 
			
		||||
            this.activityCallback = (Listener) context;
 | 
			
		||||
@@ -146,7 +147,7 @@ public class NodeFragment extends Fragment
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
 | 
			
		||||
    private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
 | 
			
		||||
        @Override
 | 
			
		||||
        public void handleOnBackPressed() {
 | 
			
		||||
            Toast.makeText(requireActivity(), getString(R.string.node_refresh_wait), Toast.LENGTH_LONG).show();
 | 
			
		||||
@@ -210,7 +211,7 @@ public class NodeFragment extends Fragment
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
 | 
			
		||||
    public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater inflater) {
 | 
			
		||||
        inflater.inflate(R.menu.node_menu, menu);
 | 
			
		||||
        super.onCreateOptionsMenu(menu, inflater);
 | 
			
		||||
    }
 | 
			
		||||
@@ -454,7 +455,7 @@ public class NodeFragment extends Fragment
 | 
			
		||||
 | 
			
		||||
        private void closeDialog() {
 | 
			
		||||
            if (editDialog == null) throw new IllegalStateException();
 | 
			
		||||
            Helper.hideKeyboardAlways(getActivity());
 | 
			
		||||
            Helper.hideKeyboardAlways(requireActivity());
 | 
			
		||||
            editDialog.dismiss();
 | 
			
		||||
            editDialog = null;
 | 
			
		||||
            NodeFragment.this.editDialog = null;
 | 
			
		||||
 
 | 
			
		||||
@@ -32,12 +32,10 @@ import android.view.KeyEvent;
 | 
			
		||||
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 android.view.inputmethod.EditorInfo;
 | 
			
		||||
import android.widget.EditText;
 | 
			
		||||
import android.widget.ImageButton;
 | 
			
		||||
import android.widget.ImageView;
 | 
			
		||||
import android.widget.ProgressBar;
 | 
			
		||||
import android.widget.TextView;
 | 
			
		||||
@@ -84,8 +82,6 @@ public class ReceiveFragment extends Fragment {
 | 
			
		||||
    private ImageView ivQrCode;
 | 
			
		||||
    private ImageView ivQrCodeFull;
 | 
			
		||||
    private EditText etDummy;
 | 
			
		||||
    private ImageButton bCopyAddress;
 | 
			
		||||
    private MenuItem shareItem;
 | 
			
		||||
 | 
			
		||||
    private Wallet wallet = null;
 | 
			
		||||
    private boolean isMyWallet = false;
 | 
			
		||||
@@ -116,11 +112,10 @@ public class ReceiveFragment extends Fragment {
 | 
			
		||||
        tvQrCode = view.findViewById(R.id.tvQrCode);
 | 
			
		||||
        ivQrCodeFull = view.findViewById(R.id.qrCodeFull);
 | 
			
		||||
        etDummy = view.findViewById(R.id.etDummy);
 | 
			
		||||
        bCopyAddress = view.findViewById(R.id.bCopyAddress);
 | 
			
		||||
 | 
			
		||||
        etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
 | 
			
		||||
 | 
			
		||||
        bCopyAddress.setOnClickListener(v -> copyAddress());
 | 
			
		||||
        view.findViewById(R.id.bCopyAddress).setOnClickListener(v -> copyAddress());
 | 
			
		||||
 | 
			
		||||
        evAmount.setOnNewAmountListener(xmr -> {
 | 
			
		||||
            Timber.d("new amount = %s", xmr);
 | 
			
		||||
@@ -211,8 +206,7 @@ public class ReceiveFragment extends Fragment {
 | 
			
		||||
        inflater.inflate(R.menu.receive_menu, menu);
 | 
			
		||||
        super.onCreateOptionsMenu(menu, inflater);
 | 
			
		||||
 | 
			
		||||
        shareItem = menu.findItem(R.id.menu_item_share);
 | 
			
		||||
        shareItem.setOnMenuItemClickListener(item -> {
 | 
			
		||||
        menu.findItem(R.id.menu_item_share).setOnMenuItemClickListener(item -> {
 | 
			
		||||
            if (shareRequested) return true;
 | 
			
		||||
            shareRequested = true;
 | 
			
		||||
            if (!qrValid) {
 | 
			
		||||
@@ -238,7 +232,7 @@ public class ReceiveFragment extends Fragment {
 | 
			
		||||
    private boolean saveQrCode() {
 | 
			
		||||
        if (!qrValid) throw new IllegalStateException("trying to save null qr code!");
 | 
			
		||||
 | 
			
		||||
        File cachePath = new File(getActivity().getCacheDir(), "images");
 | 
			
		||||
        File cachePath = new File(requireActivity().getCacheDir(), "images");
 | 
			
		||||
        if (!cachePath.exists())
 | 
			
		||||
            if (!cachePath.mkdirs()) throw new IllegalStateException("cannot create images folder");
 | 
			
		||||
        File png = new File(cachePath, "QR.png");
 | 
			
		||||
@@ -452,7 +446,7 @@ public class ReceiveFragment extends Fragment {
 | 
			
		||||
                    .withEndAction(resetSize).start();
 | 
			
		||||
        }
 | 
			
		||||
        subaddress = newSubaddress;
 | 
			
		||||
        final Context context = getContext();
 | 
			
		||||
        final Context context = requireContext();
 | 
			
		||||
        Spanned label = Html.fromHtml(context.getString(R.string.receive_subaddress,
 | 
			
		||||
                Integer.toHexString(ThemeHelper.getThemedColor(context, R.attr.positiveColor) & 0xFFFFFF),
 | 
			
		||||
                Integer.toHexString(ThemeHelper.getThemedColor(context, android.R.attr.colorBackground) & 0xFFFFFF),
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,7 @@ import android.view.View;
 | 
			
		||||
import android.view.ViewGroup;
 | 
			
		||||
import android.widget.Toast;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import androidx.fragment.app.Fragment;
 | 
			
		||||
 | 
			
		||||
import com.google.zxing.BarcodeFormat;
 | 
			
		||||
@@ -43,7 +44,7 @@ public class ScannerFragment extends Fragment implements ZXingScannerView.Result
 | 
			
		||||
    private ZXingScannerView mScannerView;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
 | 
			
		||||
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
 | 
			
		||||
        Timber.d("onCreateView");
 | 
			
		||||
        mScannerView = new ZXingScannerView(getActivity());
 | 
			
		||||
        return mScannerView;
 | 
			
		||||
@@ -85,7 +86,7 @@ public class ScannerFragment extends Fragment implements ZXingScannerView.Result
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onAttach(Context context) {
 | 
			
		||||
    public void onAttach(@NonNull Context context) {
 | 
			
		||||
        super.onAttach(context);
 | 
			
		||||
        if (context instanceof OnScannedListener) {
 | 
			
		||||
            this.onScannedListener = (OnScannedListener) context;
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ import android.content.Context;
 | 
			
		||||
import android.content.SharedPreferences;
 | 
			
		||||
import android.os.Bundle;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import androidx.annotation.StyleRes;
 | 
			
		||||
import androidx.preference.ListPreference;
 | 
			
		||||
import androidx.preference.PreferenceFragmentCompat;
 | 
			
		||||
@@ -60,7 +61,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
 | 
			
		||||
    private SettingsFragment.Listener activity;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onAttach(Context context) {
 | 
			
		||||
    public void onAttach(@NonNull Context context) {
 | 
			
		||||
        super.onAttach(context);
 | 
			
		||||
        if (context instanceof SettingsFragment.Listener) {
 | 
			
		||||
            activity = (SettingsFragment.Listener) context;
 | 
			
		||||
 
 | 
			
		||||
@@ -20,15 +20,18 @@ import android.bluetooth.BluetoothAdapter;
 | 
			
		||||
import android.bluetooth.BluetoothClass;
 | 
			
		||||
import android.bluetooth.BluetoothDevice;
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.content.pm.PackageManager;
 | 
			
		||||
import android.os.Bundle;
 | 
			
		||||
import android.view.LayoutInflater;
 | 
			
		||||
import android.view.Menu;
 | 
			
		||||
import android.view.MenuInflater;
 | 
			
		||||
import android.view.View;
 | 
			
		||||
import android.view.ViewGroup;
 | 
			
		||||
import android.widget.Toast;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import androidx.annotation.Nullable;
 | 
			
		||||
import androidx.core.app.ActivityCompat;
 | 
			
		||||
import androidx.fragment.app.Fragment;
 | 
			
		||||
import androidx.recyclerview.widget.RecyclerView;
 | 
			
		||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
 | 
			
		||||
@@ -73,10 +76,13 @@ public class SidekickConnectFragment extends Fragment
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onPause() {
 | 
			
		||||
        Timber.d("onPause()");
 | 
			
		||||
        if (bluetoothAdapter.isDiscovering()) {
 | 
			
		||||
            bluetoothAdapter.cancelDiscovery();
 | 
			
		||||
        if (ActivityCompat.checkSelfPermission(requireContext(), android.Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
 | 
			
		||||
            Toast.makeText(requireContext(), "Bluetooth permission not granted", Toast.LENGTH_LONG).show();
 | 
			
		||||
        } else {
 | 
			
		||||
            if (bluetoothAdapter.isDiscovering()) {
 | 
			
		||||
                bluetoothAdapter.cancelDiscovery();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // the the activity we are connected? why? it can ask the bluetoothservice...
 | 
			
		||||
        super.onPause();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -112,6 +118,10 @@ public class SidekickConnectFragment extends Fragment
 | 
			
		||||
 | 
			
		||||
    private void populateList() {
 | 
			
		||||
        List<BluetoothInfo> items = new ArrayList<>();
 | 
			
		||||
        if (ActivityCompat.checkSelfPermission(requireContext(), android.Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
 | 
			
		||||
            Toast.makeText(requireContext(), "Bluetooth permission not granted", Toast.LENGTH_LONG).show();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        for (BluetoothDevice device : bluetoothAdapter.getBondedDevices()) {
 | 
			
		||||
            final int deviceCLass = device.getBluetoothClass().getDeviceClass();
 | 
			
		||||
            switch (deviceCLass) {
 | 
			
		||||
@@ -152,6 +162,10 @@ public class SidekickConnectFragment extends Fragment
 | 
			
		||||
 | 
			
		||||
        // Make sure we're not doing discovery anymore
 | 
			
		||||
        if (bluetoothAdapter != null) {
 | 
			
		||||
            if (ActivityCompat.checkSelfPermission(requireContext(), android.Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
 | 
			
		||||
                Toast.makeText(requireContext(), "Bluetooth permission not granted", Toast.LENGTH_LONG).show();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            bluetoothAdapter.cancelDiscovery();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -159,6 +173,8 @@ public class SidekickConnectFragment extends Fragment
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onInteraction(final View view, final BluetoothInfo item) {
 | 
			
		||||
        Timber.d("onInteraction %s", item);
 | 
			
		||||
        if (ActivityCompat.checkSelfPermission(requireContext(), android.Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED)
 | 
			
		||||
            throw new IllegalStateException("Bluetooth permission not granted");
 | 
			
		||||
        bluetoothAdapter.cancelDiscovery();
 | 
			
		||||
 | 
			
		||||
        final BluetoothFragment btFragment = (BluetoothFragment) getChildFragmentManager().findFragmentById(R.id.bt_fragment);
 | 
			
		||||
 
 | 
			
		||||
@@ -226,7 +226,7 @@ public class SubaddressFragment extends Fragment implements SubaddressInfoAdapte
 | 
			
		||||
 | 
			
		||||
    // Callbacks from SubaddressInfoAdapter
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onInteraction(final View view, final Subaddress subaddress) {
 | 
			
		||||
    public void onInteraction(final View view, @NonNull final Subaddress subaddress) {
 | 
			
		||||
        if (managerMode)
 | 
			
		||||
            activityCallback.showSubaddress(view, subaddress.getAddressIndex());
 | 
			
		||||
        else
 | 
			
		||||
 
 | 
			
		||||
@@ -52,7 +52,6 @@ public class SubaddressInfoFragment extends Fragment
 | 
			
		||||
    private Subaddress subaddress;
 | 
			
		||||
 | 
			
		||||
    private TextInputLayout etName;
 | 
			
		||||
    private TextView tvAddress;
 | 
			
		||||
    private TextView tvTxLabel;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
@@ -61,7 +60,6 @@ public class SubaddressInfoFragment extends Fragment
 | 
			
		||||
        View view = inflater.inflate(R.layout.fragment_subaddressinfo, container, false);
 | 
			
		||||
 | 
			
		||||
        etName = view.findViewById(R.id.etName);
 | 
			
		||||
        tvAddress = view.findViewById(R.id.tvAddress);
 | 
			
		||||
        tvTxLabel = view.findViewById(R.id.tvTxLabel);
 | 
			
		||||
 | 
			
		||||
        final RecyclerView list = view.findViewById(R.id.list);
 | 
			
		||||
@@ -71,11 +69,13 @@ public class SubaddressInfoFragment extends Fragment
 | 
			
		||||
        final Wallet wallet = activityCallback.getWallet();
 | 
			
		||||
 | 
			
		||||
        Bundle b = getArguments();
 | 
			
		||||
        assert b != null;
 | 
			
		||||
        final int subaddressIndex = b.getInt("subaddressIndex");
 | 
			
		||||
        subaddress = wallet.getSubaddressObject(subaddressIndex);
 | 
			
		||||
 | 
			
		||||
        etName.getEditText().setText(subaddress.getDisplayLabel());
 | 
			
		||||
        tvAddress.setText(getContext().getString(R.string.subbaddress_info_subtitle,
 | 
			
		||||
        final TextView tvAddress = view.findViewById(R.id.tvAddress);
 | 
			
		||||
        tvAddress.setText(requireContext().getString(R.string.subbaddress_info_subtitle,
 | 
			
		||||
                subaddress.getAddressIndex(), subaddress.getAddress()));
 | 
			
		||||
 | 
			
		||||
        etName.getEditText().setOnFocusChangeListener((v, hasFocus) -> {
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,6 @@ import android.annotation.SuppressLint;
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.content.Intent;
 | 
			
		||||
import android.graphics.Paint;
 | 
			
		||||
import android.net.Uri;
 | 
			
		||||
import android.os.Bundle;
 | 
			
		||||
import android.text.Html;
 | 
			
		||||
import android.text.InputType;
 | 
			
		||||
@@ -34,6 +33,7 @@ import android.widget.ImageView;
 | 
			
		||||
import android.widget.TextView;
 | 
			
		||||
import android.widget.Toast;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import androidx.annotation.Nullable;
 | 
			
		||||
import androidx.fragment.app.Fragment;
 | 
			
		||||
import androidx.transition.Transition;
 | 
			
		||||
@@ -44,6 +44,8 @@ import com.m2049r.xmrwallet.data.UserNotes;
 | 
			
		||||
import com.m2049r.xmrwallet.model.TransactionInfo;
 | 
			
		||||
import com.m2049r.xmrwallet.model.Transfer;
 | 
			
		||||
import com.m2049r.xmrwallet.model.Wallet;
 | 
			
		||||
import com.m2049r.xmrwallet.service.shift.ShiftService;
 | 
			
		||||
import com.m2049r.xmrwallet.service.shift.api.ShiftApi;
 | 
			
		||||
import com.m2049r.xmrwallet.util.Helper;
 | 
			
		||||
import com.m2049r.xmrwallet.util.ThemeHelper;
 | 
			
		||||
import com.m2049r.xmrwallet.widget.Toolbar;
 | 
			
		||||
@@ -61,6 +63,7 @@ public class TxFragment extends Fragment {
 | 
			
		||||
 | 
			
		||||
    static public final String ARG_INFO = "info";
 | 
			
		||||
 | 
			
		||||
    @SuppressLint("SimpleDateFormat")
 | 
			
		||||
    private final SimpleDateFormat TS_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
 | 
			
		||||
 | 
			
		||||
    public TxFragment() {
 | 
			
		||||
@@ -106,6 +109,7 @@ public class TxFragment extends Fragment {
 | 
			
		||||
        tvTxAmountBtc = view.findViewById(R.id.tvTxAmountBtc);
 | 
			
		||||
        tvXmrToSupport = view.findViewById(R.id.tvXmrToSupport);
 | 
			
		||||
        tvXmrToKeyLabel = view.findViewById(R.id.tvXmrToKeyLabel);
 | 
			
		||||
 | 
			
		||||
        tvXmrToLogo = view.findViewById(R.id.tvXmrToLogo);
 | 
			
		||||
 | 
			
		||||
        tvAccount = view.findViewById(R.id.tvAccount);
 | 
			
		||||
@@ -127,18 +131,20 @@ public class TxFragment extends Fragment {
 | 
			
		||||
        tvWarning = view.findViewById(R.id.tvWarning);
 | 
			
		||||
 | 
			
		||||
        tvTxXmrToKey.setOnClickListener(v -> {
 | 
			
		||||
            Helper.clipBoardCopy(getActivity(), getString(R.string.label_copy_xmrtokey), tvTxXmrToKey.getText().toString());
 | 
			
		||||
            Helper.clipBoardCopy(requireActivity(), getString(R.string.label_copy_xmrtokey), tvTxXmrToKey.getText().toString());
 | 
			
		||||
            Toast.makeText(getActivity(), getString(R.string.message_copy_xmrtokey), Toast.LENGTH_SHORT).show();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        info = getArguments().getParcelable(ARG_INFO);
 | 
			
		||||
        final Bundle args = getArguments();
 | 
			
		||||
        assert args != null;
 | 
			
		||||
        info = args.getParcelable(ARG_INFO);
 | 
			
		||||
        show();
 | 
			
		||||
        return view;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void shareTxInfo() {
 | 
			
		||||
        if (this.info == null) return;
 | 
			
		||||
        StringBuffer sb = new StringBuffer();
 | 
			
		||||
        StringBuilder sb = new StringBuilder();
 | 
			
		||||
 | 
			
		||||
        sb.append(getString(R.string.tx_timestamp)).append(":\n");
 | 
			
		||||
        sb.append(TS_FORMATTER.format(new Date(info.timestamp * 1000))).append("\n\n");
 | 
			
		||||
@@ -216,7 +222,7 @@ public class TxFragment extends Fragment {
 | 
			
		||||
 | 
			
		||||
    private void showSubaddressLabel() {
 | 
			
		||||
        final Subaddress subaddress = activityCallback.getWalletSubaddress(info.accountIndex, info.addressIndex);
 | 
			
		||||
        final Context ctx = getContext();
 | 
			
		||||
        final Context ctx = requireContext();
 | 
			
		||||
        Spanned label = Html.fromHtml(ctx.getString(R.string.tx_account_formatted,
 | 
			
		||||
                info.accountIndex, info.addressIndex,
 | 
			
		||||
                Integer.toHexString(ThemeHelper.getThemedColor(ctx, R.attr.positiveColor) & 0xFFFFFF),
 | 
			
		||||
@@ -264,16 +270,17 @@ public class TxFragment extends Fragment {
 | 
			
		||||
            tvTxFee.setVisibility(View.GONE);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        final Context ctx = requireContext();
 | 
			
		||||
        if (info.isFailed) {
 | 
			
		||||
            tvTxAmount.setText(getString(R.string.tx_list_amount_failed, Wallet.getDisplayAmount(info.amount)));
 | 
			
		||||
            tvTxFee.setText(getString(R.string.tx_list_failed_text));
 | 
			
		||||
            setTxColour(ThemeHelper.getThemedColor(getContext(), R.attr.neutralColor));
 | 
			
		||||
            setTxColour(ThemeHelper.getThemedColor(ctx, R.attr.neutralColor));
 | 
			
		||||
        } else if (info.isPending) {
 | 
			
		||||
            setTxColour(ThemeHelper.getThemedColor(getContext(), R.attr.neutralColor));
 | 
			
		||||
            setTxColour(ThemeHelper.getThemedColor(ctx, R.attr.neutralColor));
 | 
			
		||||
        } else if (info.direction == TransactionInfo.Direction.Direction_In) {
 | 
			
		||||
            setTxColour(ThemeHelper.getThemedColor(getContext(), R.attr.positiveColor));
 | 
			
		||||
            setTxColour(ThemeHelper.getThemedColor(ctx, R.attr.positiveColor));
 | 
			
		||||
        } else {
 | 
			
		||||
            setTxColour(ThemeHelper.getThemedColor(getContext(), R.attr.negativeColor));
 | 
			
		||||
            setTxColour(ThemeHelper.getThemedColor(ctx, R.attr.negativeColor));
 | 
			
		||||
        }
 | 
			
		||||
        Set<String> destinations = new HashSet<>();
 | 
			
		||||
        StringBuilder sb = new StringBuilder();
 | 
			
		||||
@@ -328,31 +335,29 @@ public class TxFragment extends Fragment {
 | 
			
		||||
    void showBtcInfo() {
 | 
			
		||||
        if (userNotes.xmrtoKey != null) {
 | 
			
		||||
            cvXmrTo.setVisibility(View.VISIBLE);
 | 
			
		||||
            String key = userNotes.xmrtoKey;
 | 
			
		||||
            if ("xmrto".equals(userNotes.xmrtoTag)) { // legacy xmr.to service :(
 | 
			
		||||
                key = "xmrto-" + key;
 | 
			
		||||
            }
 | 
			
		||||
            tvTxXmrToKey.setText(key);
 | 
			
		||||
 | 
			
		||||
            ShiftService service = ShiftService.findWithTag(userNotes.xmrtoTag);
 | 
			
		||||
            tvXmrToKeyLabel.setText(getString(R.string.label_send_btc_xmrto_key_lb, service.getLabel()));
 | 
			
		||||
            if (service.getIconId() == 0)
 | 
			
		||||
                tvXmrToLogo.setVisibility(View.GONE);
 | 
			
		||||
            else
 | 
			
		||||
                tvXmrToLogo.setImageResource(service.getLogoId());
 | 
			
		||||
 | 
			
		||||
            tvTxXmrToKey.setText(userNotes.xmrtoKey);
 | 
			
		||||
 | 
			
		||||
            tvDestinationBtc.setText(userNotes.xmrtoDestination);
 | 
			
		||||
            tvTxAmountBtc.setText(userNotes.xmrtoAmount + " " + userNotes.xmrtoCurrency);
 | 
			
		||||
            switch (userNotes.xmrtoTag) {
 | 
			
		||||
                case "xmrto":
 | 
			
		||||
                    tvXmrToSupport.setVisibility(View.GONE);
 | 
			
		||||
                    tvXmrToKeyLabel.setVisibility(View.INVISIBLE);
 | 
			
		||||
                    tvXmrToLogo.setImageResource(R.drawable.ic_xmrto_logo);
 | 
			
		||||
                    break;
 | 
			
		||||
                case "side": // defaults in layout - just add underline
 | 
			
		||||
                    tvXmrToSupport.setPaintFlags(tvXmrToSupport.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
 | 
			
		||||
                    tvXmrToSupport.setOnClickListener(v -> {
 | 
			
		||||
                        Uri uri = Uri.parse("https://sideshift.ai/orders/" + userNotes.xmrtoKey);
 | 
			
		||||
                        Intent intent = new Intent(Intent.ACTION_VIEW, uri);
 | 
			
		||||
                        startActivity(intent);
 | 
			
		||||
                    });
 | 
			
		||||
                    break;
 | 
			
		||||
                default:
 | 
			
		||||
                    tvXmrToSupport.setVisibility(View.GONE);
 | 
			
		||||
                    tvXmrToKeyLabel.setVisibility(View.INVISIBLE);
 | 
			
		||||
                    tvXmrToLogo.setVisibility(View.GONE);
 | 
			
		||||
 | 
			
		||||
            ShiftApi shiftApi = service.getShiftApi();
 | 
			
		||||
            if (shiftApi != null) {
 | 
			
		||||
                tvXmrToSupport.setText(getString(R.string.label_send_btc_xmrto_info, service.getLabel()));
 | 
			
		||||
                tvXmrToSupport.setPaintFlags(tvXmrToSupport.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
 | 
			
		||||
                tvXmrToSupport.setOnClickListener(v -> {
 | 
			
		||||
                    startActivity(new Intent(Intent.ACTION_VIEW, shiftApi.getQueryOrderUri(userNotes.xmrtoKey)));
 | 
			
		||||
                });
 | 
			
		||||
            } else {
 | 
			
		||||
                tvXmrToSupport.setVisibility(View.GONE);
 | 
			
		||||
                tvXmrToKeyLabel.setVisibility(View.INVISIBLE);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            cvXmrTo.setVisibility(View.GONE);
 | 
			
		||||
@@ -369,7 +374,7 @@ public class TxFragment extends Fragment {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
 | 
			
		||||
    public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater inflater) {
 | 
			
		||||
        inflater.inflate(R.menu.tx_info_menu, menu);
 | 
			
		||||
        super.onCreateOptionsMenu(menu, inflater);
 | 
			
		||||
    }
 | 
			
		||||
@@ -397,7 +402,7 @@ public class TxFragment extends Fragment {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onAttach(Context context) {
 | 
			
		||||
    public void onAttach(@NonNull Context context) {
 | 
			
		||||
        super.onAttach(context);
 | 
			
		||||
        if (context instanceof TxFragment.Listener) {
 | 
			
		||||
            this.activityCallback = (TxFragment.Listener) context;
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,6 @@ import android.widget.TextView;
 | 
			
		||||
import android.widget.Toast;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import androidx.annotation.Nullable;
 | 
			
		||||
import androidx.appcompat.app.ActionBarDrawerToggle;
 | 
			
		||||
import androidx.appcompat.app.AlertDialog;
 | 
			
		||||
import androidx.core.view.GravityCompat;
 | 
			
		||||
@@ -348,7 +347,10 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Bundle extras = getIntent().getExtras();
 | 
			
		||||
        if (extras == null) finish(); // we need extras!
 | 
			
		||||
        if (extras == null) {
 | 
			
		||||
            finish(); // we need extras!
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        String walletId = extras.getString(REQUEST_ID);
 | 
			
		||||
        requestStreetMode = extras.getBoolean(REQUEST_STREETMODE);
 | 
			
		||||
@@ -1179,7 +1181,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onSubaddressSelected(@Nullable final Subaddress subaddress) {
 | 
			
		||||
    public void onSubaddressSelected(@NonNull final Subaddress subaddress) {
 | 
			
		||||
        selectedSubaddressIndex = subaddress.getAddressIndex();
 | 
			
		||||
        getOnBackPressedDispatcher().onBackPressed();
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -51,6 +51,7 @@ import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback;
 | 
			
		||||
import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate;
 | 
			
		||||
import com.m2049r.xmrwallet.util.Helper;
 | 
			
		||||
import com.m2049r.xmrwallet.util.ServiceHelper;
 | 
			
		||||
import com.m2049r.xmrwallet.util.StickyFiatHelper;
 | 
			
		||||
import com.m2049r.xmrwallet.util.ThemeHelper;
 | 
			
		||||
import com.m2049r.xmrwallet.widget.Toolbar;
 | 
			
		||||
 | 
			
		||||
@@ -112,7 +113,7 @@ public class WalletFragment extends Fragment
 | 
			
		||||
        flExchange = view.findViewById(R.id.flExchange);
 | 
			
		||||
        ((ProgressBar) view.findViewById(R.id.pbExchange)).getIndeterminateDrawable().
 | 
			
		||||
                setColorFilter(
 | 
			
		||||
                        ThemeHelper.getThemedColor(getContext(), com.google.android.material.R.attr.colorPrimaryVariant),
 | 
			
		||||
                        ThemeHelper.getThemedColor(requireContext(), com.google.android.material.R.attr.colorPrimaryVariant),
 | 
			
		||||
                        android.graphics.PorterDuff.Mode.MULTIPLY);
 | 
			
		||||
 | 
			
		||||
        tvProgress = view.findViewById(R.id.tvProgress);
 | 
			
		||||
@@ -131,6 +132,7 @@ public class WalletFragment extends Fragment
 | 
			
		||||
        ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(requireContext(), R.layout.item_spinner_balance, currencies);
 | 
			
		||||
        spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
 | 
			
		||||
        sCurrency.setAdapter(spinnerAdapter);
 | 
			
		||||
        StickyFiatHelper.setPreferredCurrencyPosition(sCurrency);
 | 
			
		||||
 | 
			
		||||
        bSend = view.findViewById(R.id.bSend);
 | 
			
		||||
        bReceive = view.findViewById(R.id.bReceive);
 | 
			
		||||
@@ -182,6 +184,7 @@ public class WalletFragment extends Fragment
 | 
			
		||||
        sCurrency.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) {
 | 
			
		||||
                StickyFiatHelper.setPreferredFiatSymbol(requireContext(), (String) sCurrency.getSelectedItem());
 | 
			
		||||
                refreshBalance();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -315,13 +318,7 @@ public class WalletFragment extends Fragment
 | 
			
		||||
            balanceCurrency = Helper.BASE_CRYPTO;
 | 
			
		||||
            balanceRate = 1.0;
 | 
			
		||||
        } else {
 | 
			
		||||
            int spinnerPosition = ((ArrayAdapter) sCurrency.getAdapter()).getPosition(exchangeRate.getQuoteCurrency());
 | 
			
		||||
            if (spinnerPosition < 0) { // requested currency not in list
 | 
			
		||||
                Timber.e("Requested currency not in list %s", exchangeRate.getQuoteCurrency());
 | 
			
		||||
                sCurrency.setSelection(0, true);
 | 
			
		||||
            } else {
 | 
			
		||||
                sCurrency.setSelection(spinnerPosition, true);
 | 
			
		||||
            }
 | 
			
		||||
            StickyFiatHelper.setCurrencyPosition(sCurrency, exchangeRate.getQuoteCurrency());
 | 
			
		||||
            balanceCurrency = exchangeRate.getQuoteCurrency();
 | 
			
		||||
            balanceRate = exchangeRate.getRate();
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,6 @@ import android.os.Build;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
 | 
			
		||||
import com.m2049r.xmrwallet.BuildConfig;
 | 
			
		||||
import com.m2049r.xmrwallet.model.NetworkType;
 | 
			
		||||
import com.m2049r.xmrwallet.util.LocaleHelper;
 | 
			
		||||
import com.m2049r.xmrwallet.util.NetCipherHelper;
 | 
			
		||||
 
 | 
			
		||||
@@ -24,43 +24,38 @@ import java.net.URI;
 | 
			
		||||
import java.net.URISyntaxException;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.HashSet;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
 | 
			
		||||
import lombok.Getter;
 | 
			
		||||
import lombok.ToString;
 | 
			
		||||
import timber.log.Timber;
 | 
			
		||||
 | 
			
		||||
@Getter
 | 
			
		||||
@ToString
 | 
			
		||||
public class BarcodeData {
 | 
			
		||||
 | 
			
		||||
    public enum Security {
 | 
			
		||||
        NORMAL,
 | 
			
		||||
        OA_NO_DNSSEC,
 | 
			
		||||
        OA_DNSSEC
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    final public Crypto asset;
 | 
			
		||||
    final public List<Crypto> ambiguousAssets;
 | 
			
		||||
    final public String address;
 | 
			
		||||
    final public String addressName;
 | 
			
		||||
    final public String amount;
 | 
			
		||||
    final public String description;
 | 
			
		||||
    final public Security security;
 | 
			
		||||
    final private Set<Crypto> possibleAssets = new HashSet<>();
 | 
			
		||||
    private String address = null;
 | 
			
		||||
    private String addressName = null;
 | 
			
		||||
    private String amount = null;
 | 
			
		||||
    private String description = null;
 | 
			
		||||
    private Security security = null;
 | 
			
		||||
 | 
			
		||||
    public BarcodeData(List<Crypto> assets, String address) {
 | 
			
		||||
        if (assets.isEmpty())
 | 
			
		||||
            throw new IllegalArgumentException("no assets specified");
 | 
			
		||||
        this.addressName = null;
 | 
			
		||||
        this.description = null;
 | 
			
		||||
        this.amount = null;
 | 
			
		||||
        this.security = Security.NORMAL;
 | 
			
		||||
        security = Security.NORMAL;
 | 
			
		||||
        this.address = address;
 | 
			
		||||
        if (assets.size() == 1) {
 | 
			
		||||
            this.asset = assets.get(0);
 | 
			
		||||
            this.ambiguousAssets = null;
 | 
			
		||||
        } else {
 | 
			
		||||
            this.asset = null;
 | 
			
		||||
            this.ambiguousAssets = assets;
 | 
			
		||||
        }
 | 
			
		||||
        possibleAssets.addAll(assets);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public BarcodeData(Crypto asset, String address, String description, String amount) {
 | 
			
		||||
@@ -68,8 +63,7 @@ public class BarcodeData {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public BarcodeData(Crypto asset, String address, String addressName, String description, String amount, Security security) {
 | 
			
		||||
        this.ambiguousAssets = null;
 | 
			
		||||
        this.asset = asset;
 | 
			
		||||
        possibleAssets.add(asset);
 | 
			
		||||
        this.address = address;
 | 
			
		||||
        this.addressName = addressName;
 | 
			
		||||
        this.description = description;
 | 
			
		||||
@@ -82,7 +76,7 @@ public class BarcodeData {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getUriString() {
 | 
			
		||||
        if (asset != Crypto.XMR) throw new IllegalStateException("We can only do XMR stuff!");
 | 
			
		||||
        if (getAsset() != Crypto.XMR) throw new IllegalStateException("We can only do XMR stuff!");
 | 
			
		||||
        StringBuilder sb = new StringBuilder();
 | 
			
		||||
        sb.append(Crypto.XMR.getUriScheme())
 | 
			
		||||
                .append(':')
 | 
			
		||||
@@ -227,6 +221,20 @@ public class BarcodeData {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean isAmbiguous() {
 | 
			
		||||
        return ambiguousAssets != null;
 | 
			
		||||
        return possibleAssets.size() > 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Crypto getAsset() {
 | 
			
		||||
        if (possibleAssets.size() == 1) {
 | 
			
		||||
            return possibleAssets.iterator().next();
 | 
			
		||||
        } else {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // return true if we still have possible assets
 | 
			
		||||
    public boolean filter(Set<Crypto> assets) {
 | 
			
		||||
        possibleAssets.retainAll(assets);
 | 
			
		||||
        return !possibleAssets.isEmpty();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,3 +1,19 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (c) 2024 m2049r
 | 
			
		||||
 *
 | 
			
		||||
 * 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 com.m2049r.xmrwallet.data;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
@@ -5,35 +21,30 @@ import androidx.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
import com.m2049r.xmrwallet.R;
 | 
			
		||||
import com.m2049r.xmrwallet.model.Wallet;
 | 
			
		||||
import com.m2049r.xmrwallet.util.validator.BitcoinAddressType;
 | 
			
		||||
import com.m2049r.xmrwallet.util.validator.BitcoinAddressValidator;
 | 
			
		||||
import com.m2049r.xmrwallet.util.validator.EthAddressValidator;
 | 
			
		||||
 | 
			
		||||
import java.util.regex.Pattern;
 | 
			
		||||
 | 
			
		||||
import lombok.Getter;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
 | 
			
		||||
@RequiredArgsConstructor
 | 
			
		||||
public enum Crypto {
 | 
			
		||||
    XMR("XMR", true, "monero:tx_amount:recipient_name:tx_description", R.id.ibXMR, R.drawable.ic_monero, R.drawable.ic_monero_bw, Wallet::isAddressValid),
 | 
			
		||||
    BTC("BTC", true, "bitcoin:amount:label:message", R.id.ibBTC, R.drawable.ic_xmrto_btc, R.drawable.ic_xmrto_btc_off, address -> {
 | 
			
		||||
        return BitcoinAddressValidator.validate(address, BitcoinAddressType.BTC);
 | 
			
		||||
    }),
 | 
			
		||||
    DASH("DASH", true, "dash:amount:label:message", R.id.ibDASH, R.drawable.ic_xmrto_dash, R.drawable.ic_xmrto_dash_off, address -> {
 | 
			
		||||
        return BitcoinAddressValidator.validate(address, BitcoinAddressType.DASH);
 | 
			
		||||
    }),
 | 
			
		||||
    DOGE("DOGE", true, "dogecoin:amount:label:message", R.id.ibDOGE, R.drawable.ic_xmrto_doge, R.drawable.ic_xmrto_doge_off, address -> {
 | 
			
		||||
        return BitcoinAddressValidator.validate(address, BitcoinAddressType.DOGE);
 | 
			
		||||
    }),
 | 
			
		||||
    ETH("ETH", false, "ethereum:amount:label:message", R.id.ibETH, R.drawable.ic_xmrto_eth, R.drawable.ic_xmrto_eth_off, EthAddressValidator::validate),
 | 
			
		||||
    LTC("LTC", true, "litecoin:amount:label:message", R.id.ibLTC, R.drawable.ic_xmrto_ltc, R.drawable.ic_xmrto_ltc_off, address -> {
 | 
			
		||||
        return BitcoinAddressValidator.validate(address, BitcoinAddressType.LTC);
 | 
			
		||||
    });
 | 
			
		||||
    XMR("XMR", "XMR", "XMR", "monero:tx_amount:recipient_name:tx_description", R.id.ibXMR, R.drawable.ic_monero, R.drawable.ic_monero_bw, Pattern.compile("^[48][a-zA-Z|\\d]{94}([a-zA-Z|\\d]{11})?$")),
 | 
			
		||||
    BTC("BTC", "BTC", "BTC", "bitcoin:amount:label:message", R.id.ibBTC, R.drawable.ic_xmrto_btc, R.drawable.ic_xmrto_btc_off, Pattern.compile("^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$|^(bc1q)|(bc1p)[0-9A-Za-z]{37,62}$")),
 | 
			
		||||
    LTC("LTC", "LTC", "LTC", "litecoin:amount:label:message", R.id.ibLTC, R.drawable.ic_xmrto_ltc, R.drawable.ic_xmrto_ltc_off, Pattern.compile("^([LM3])[A-Za-z0-9]{33}$|^(ltc1)[0-9A-Za-z]{39}$")),
 | 
			
		||||
    ETH("ETH", "ETH", "ETH", "ethereum:amount:label:message", R.id.ibETH, R.drawable.ic_xmrto_eth, R.drawable.ic_xmrto_eth_off, Pattern.compile("^(0x)[0-9A-Fa-f]{40}$")),
 | 
			
		||||
    USDT("USDT", "TRX", "USDT(TRC20)", "usdt:amount:label:message", R.id.ibUSDT, R.drawable.ic_xmrto_usdt_trc20, R.drawable.ic_xmrto_usdt_trc20_off, Pattern.compile("^T[1-9A-HJ-NP-Za-km-z]{33}$")),
 | 
			
		||||
    SOLANA("SOL", "SOL", "SOL", "solana:amount:label:message", R.id.ibSOL, R.drawable.ic_xmrto_sol, R.drawable.ic_xmrto_sol_off, Pattern.compile("^[1-9A-HJ-NP-Za-km-z]{32,44}$"));
 | 
			
		||||
 | 
			
		||||
    @Getter
 | 
			
		||||
    @NonNull
 | 
			
		||||
    private final String symbol;
 | 
			
		||||
    @Getter
 | 
			
		||||
    private final boolean casefull;
 | 
			
		||||
    @NonNull
 | 
			
		||||
    private final String network;
 | 
			
		||||
    @Getter
 | 
			
		||||
    @NonNull
 | 
			
		||||
    private final String label;
 | 
			
		||||
    @NonNull
 | 
			
		||||
    private final String uriSpec;
 | 
			
		||||
    @Getter
 | 
			
		||||
@@ -43,7 +54,7 @@ public enum Crypto {
 | 
			
		||||
    @Getter
 | 
			
		||||
    private final int iconDisabledId;
 | 
			
		||||
    @NonNull
 | 
			
		||||
    private final Validator validator;
 | 
			
		||||
    private final Pattern regex;
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public static Crypto withScheme(@NonNull String scheme) {
 | 
			
		||||
@@ -62,10 +73,6 @@ public enum Crypto {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    interface Validator {
 | 
			
		||||
        boolean validate(String address);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TODO maybe cache these segments
 | 
			
		||||
    String getUriScheme() {
 | 
			
		||||
        return uriSpec.split(":")[0];
 | 
			
		||||
@@ -83,7 +90,8 @@ public enum Crypto {
 | 
			
		||||
        return uriSpec.split(":")[3];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    boolean validate(String address) {
 | 
			
		||||
        return validator.validate(address);
 | 
			
		||||
    public boolean validate(String address) {
 | 
			
		||||
        if (this == XMR) return Wallet.isAddressValid(address);
 | 
			
		||||
        return regex.matcher(address).find();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,29 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (c) 2024 m2049r
 | 
			
		||||
 *
 | 
			
		||||
 * 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 com.m2049r.xmrwallet.data;
 | 
			
		||||
 | 
			
		||||
import lombok.Value;
 | 
			
		||||
 | 
			
		||||
@Value
 | 
			
		||||
public class CryptoAmount {
 | 
			
		||||
    Crypto crypto;
 | 
			
		||||
    double amount;
 | 
			
		||||
 | 
			
		||||
    public CryptoAmount newWithAmount(double amount) {
 | 
			
		||||
        return new CryptoAmount(this.crypto, amount);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -23,7 +23,7 @@ import lombok.Getter;
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
public enum DefaultNodes {
 | 
			
		||||
    AGORIST("xmr.agor.ist:18089/mainnet/agor.ist"),
 | 
			
		||||
    BOLDSUCK("xmr-de.boldsuck.org:18080/mainnet/boldsuck.org"),
 | 
			
		||||
    BOLDSUCK("xmr-de.boldsuck.org:18081/mainnet/boldsuck.org"),
 | 
			
		||||
    boldsuck("6dsdenp6vjkvqzy4wzsnzn6wixkdzihx3khiumyzieauxuxslmcaeiad.onion:18081/mainnet/boldsuck.onion"),
 | 
			
		||||
    CAKE("xmr-node.cakewallet.com:18081/mainnet/cakewallet.com"),
 | 
			
		||||
    DS_JETZT("monero.ds-jetzt.de:18089/mainnet/ds-jetzt.de"),
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,7 @@
 | 
			
		||||
 | 
			
		||||
package com.m2049r.xmrwallet.data;
 | 
			
		||||
 | 
			
		||||
import com.google.common.net.HostAndPort;
 | 
			
		||||
import com.m2049r.xmrwallet.model.NetworkType;
 | 
			
		||||
import com.m2049r.xmrwallet.model.WalletManager;
 | 
			
		||||
import com.m2049r.xmrwallet.util.OnionHelper;
 | 
			
		||||
@@ -169,10 +170,10 @@ public class Node {
 | 
			
		||||
            throw new IllegalArgumentException("Too many '/' or too few");
 | 
			
		||||
 | 
			
		||||
        daemonAddress = daParts[0];
 | 
			
		||||
        String[] da = daemonAddress.split(":");
 | 
			
		||||
        if ((da.length > 2) || (da.length < 1))
 | 
			
		||||
            throw new IllegalArgumentException("Too many ':' or too few");
 | 
			
		||||
        String host = da[0];
 | 
			
		||||
        HostAndPort hostAndPort = HostAndPort.fromString(daemonAddress)
 | 
			
		||||
                .withDefaultPort(getDefaultRpcPort())
 | 
			
		||||
                .requireBracketsForIPv6();
 | 
			
		||||
        String host = hostAndPort.getHost();
 | 
			
		||||
 | 
			
		||||
        if (daParts.length == 1) {
 | 
			
		||||
            networkType = NetworkType.NetworkType_Mainnet;
 | 
			
		||||
@@ -204,22 +205,12 @@ public class Node {
 | 
			
		||||
        }
 | 
			
		||||
        this.name = name;
 | 
			
		||||
 | 
			
		||||
        int port;
 | 
			
		||||
        if (da.length == 2) {
 | 
			
		||||
            try {
 | 
			
		||||
                port = Integer.parseInt(da[1]);
 | 
			
		||||
            } catch (NumberFormatException ex) {
 | 
			
		||||
                throw new IllegalArgumentException("Port not numeric");
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            port = getDefaultRpcPort();
 | 
			
		||||
        }
 | 
			
		||||
        try {
 | 
			
		||||
            setHost(host);
 | 
			
		||||
        } catch (UnknownHostException ex) {
 | 
			
		||||
            throw new IllegalArgumentException("cannot resolve host " + host);
 | 
			
		||||
        }
 | 
			
		||||
        this.rpcPort = port;
 | 
			
		||||
        this.rpcPort = hostAndPort.getPort();
 | 
			
		||||
        this.levinPort = getDefaultLevinPort();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -233,7 +224,8 @@ public class Node {
 | 
			
		||||
        if (!username.isEmpty() && !password.isEmpty()) {
 | 
			
		||||
            sb.append(username).append(":").append(password).append("@");
 | 
			
		||||
        }
 | 
			
		||||
        sb.append(host).append(":").append(rpcPort);
 | 
			
		||||
        HostAndPort address = HostAndPort.fromParts(host, rpcPort);
 | 
			
		||||
        sb.append(address.toString());
 | 
			
		||||
        sb.append("/");
 | 
			
		||||
        switch (networkType) {
 | 
			
		||||
            case NetworkType_Mainnet:
 | 
			
		||||
@@ -271,7 +263,7 @@ public class Node {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getAddress() {
 | 
			
		||||
        return getHostAddress() + ":" + rpcPort;
 | 
			
		||||
        return HostAndPort.fromParts(getHostAddress(), rpcPort).toString();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getHostAddress() {
 | 
			
		||||
 
 | 
			
		||||
@@ -201,8 +201,13 @@ public class NodeInfo extends Node {
 | 
			
		||||
                .port(port)
 | 
			
		||||
                .addPathSegment("json_rpc")
 | 
			
		||||
                .build();
 | 
			
		||||
        final String json = "{\"jsonrpc\":\"2.0\",\"id\":\"0\",\"method\":\"getlastblockheader\"}";
 | 
			
		||||
        return new Request(url, json, getUsername(), getPassword());
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            final JSONObject json = new JSONObject("{\"jsonrpc\":\"2.0\",\"id\":\"0\",\"method\":\"getlastblockheader\"}");
 | 
			
		||||
            return new Request(url, json, getUsername(), getPassword());
 | 
			
		||||
        } catch (JSONException ex) {
 | 
			
		||||
            throw new IllegalStateException(ex);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private boolean testRpcService(int port) {
 | 
			
		||||
 
 | 
			
		||||
@@ -20,22 +20,21 @@ import android.os.Parcel;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
 | 
			
		||||
import com.m2049r.xmrwallet.service.shift.ShiftService;
 | 
			
		||||
import com.m2049r.xmrwallet.service.shift.api.RequestQuote;
 | 
			
		||||
 | 
			
		||||
import lombok.Getter;
 | 
			
		||||
import lombok.Setter;
 | 
			
		||||
 | 
			
		||||
@Setter
 | 
			
		||||
@Getter
 | 
			
		||||
public class TxDataBtc extends TxData {
 | 
			
		||||
    @Getter
 | 
			
		||||
    @Setter
 | 
			
		||||
    private ShiftService shiftService;
 | 
			
		||||
    private String btcSymbol; // the actual non-XMR thing we're sending
 | 
			
		||||
    @Getter
 | 
			
		||||
    @Setter
 | 
			
		||||
    private String xmrtoOrderId; // shown in success screen
 | 
			
		||||
    @Getter
 | 
			
		||||
    @Setter
 | 
			
		||||
    private String btcAddress;
 | 
			
		||||
    @Getter
 | 
			
		||||
    @Setter
 | 
			
		||||
    private double btcAmount;
 | 
			
		||||
    private CryptoAmount shiftAmount; // what we want to send
 | 
			
		||||
    private String xmrtoQueryOrderToken; // used for queryOrder API
 | 
			
		||||
 | 
			
		||||
    public TxDataBtc() {
 | 
			
		||||
        super();
 | 
			
		||||
@@ -47,7 +46,9 @@ public class TxDataBtc extends TxData {
 | 
			
		||||
        out.writeString(btcSymbol);
 | 
			
		||||
        out.writeString(xmrtoOrderId);
 | 
			
		||||
        out.writeString(btcAddress);
 | 
			
		||||
        out.writeDouble(btcAmount);
 | 
			
		||||
        out.writeString(shiftAmount.getCrypto().name());
 | 
			
		||||
        out.writeDouble(shiftAmount.getAmount());
 | 
			
		||||
        out.writeString(xmrtoQueryOrderToken);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // this is used to regenerate your object. All Parcelables must have a CREATOR that implements these two methods
 | 
			
		||||
@@ -66,7 +67,8 @@ public class TxDataBtc extends TxData {
 | 
			
		||||
        btcSymbol = in.readString();
 | 
			
		||||
        xmrtoOrderId = in.readString();
 | 
			
		||||
        btcAddress = in.readString();
 | 
			
		||||
        btcAmount = in.readDouble();
 | 
			
		||||
        shiftAmount = new CryptoAmount(Crypto.valueOf(in.readString()), in.readDouble());
 | 
			
		||||
        xmrtoQueryOrderToken = in.readString();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @NonNull
 | 
			
		||||
@@ -79,19 +81,33 @@ public class TxDataBtc extends TxData {
 | 
			
		||||
        sb.append(btcSymbol);
 | 
			
		||||
        sb.append(",btcAddress:");
 | 
			
		||||
        sb.append(btcAddress);
 | 
			
		||||
        sb.append(",btcAmount:");
 | 
			
		||||
        sb.append(btcAmount);
 | 
			
		||||
        sb.append(",amount:");
 | 
			
		||||
        sb.append(shiftAmount);
 | 
			
		||||
        sb.append(",xmrtoQueryOrderToken:");
 | 
			
		||||
        sb.append(xmrtoQueryOrderToken);
 | 
			
		||||
        return sb.toString();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean validateAddress(@NonNull String address) {
 | 
			
		||||
        if ((btcSymbol == null) || (btcAddress == null)) return false;
 | 
			
		||||
        final Crypto crypto = Crypto.withSymbol(btcSymbol);
 | 
			
		||||
        if (crypto == null) return false;
 | 
			
		||||
        if (crypto.isCasefull()) { // compare as-is
 | 
			
		||||
            return address.equals(btcAddress);
 | 
			
		||||
        } else { // normalize & compare (e.g. ETH with and without checksum capitals
 | 
			
		||||
            return address.toLowerCase().equals(btcAddress.toLowerCase());
 | 
			
		||||
        return address.equalsIgnoreCase(btcAddress);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public double getBtcAmount() {
 | 
			
		||||
        return (shiftAmount.getCrypto() == Crypto.XMR) ? 0 : shiftAmount.getAmount();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public double getXmrAmount() {
 | 
			
		||||
        return (shiftAmount.getCrypto() == Crypto.XMR) ? shiftAmount.getAmount() : 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean validate(RequestQuote quote) {
 | 
			
		||||
        if (shiftAmount.getCrypto() == Crypto.XMR) {
 | 
			
		||||
            return (quote.getXmrAmount() == getXmrAmount());
 | 
			
		||||
        } else {
 | 
			
		||||
            return (quote.getBtcAmount() == getBtcAmount());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@
 | 
			
		||||
 | 
			
		||||
package com.m2049r.xmrwallet.data;
 | 
			
		||||
 | 
			
		||||
import com.m2049r.xmrwallet.service.shift.sideshift.api.CreateOrder;
 | 
			
		||||
import com.m2049r.xmrwallet.service.shift.api.CreateOrder;
 | 
			
		||||
import com.m2049r.xmrwallet.util.Helper;
 | 
			
		||||
 | 
			
		||||
import java.util.regex.Matcher;
 | 
			
		||||
@@ -61,7 +61,7 @@ public class UserNotes {
 | 
			
		||||
 | 
			
		||||
    public void setXmrtoOrder(CreateOrder order) {
 | 
			
		||||
        if (order != null) {
 | 
			
		||||
            xmrtoTag = order.TAG;
 | 
			
		||||
            xmrtoTag = order.getTag();
 | 
			
		||||
            xmrtoKey = order.getOrderId();
 | 
			
		||||
            xmrtoAmount = Helper.getDisplayAmount(order.getBtcAmount());
 | 
			
		||||
            xmrtoCurrency = order.getBtcCurrency();
 | 
			
		||||
 
 | 
			
		||||
@@ -20,10 +20,10 @@ import android.app.Dialog;
 | 
			
		||||
import android.content.DialogInterface;
 | 
			
		||||
import android.os.Bundle;
 | 
			
		||||
import android.text.Html;
 | 
			
		||||
import android.view.LayoutInflater;
 | 
			
		||||
import android.view.View;
 | 
			
		||||
import android.widget.TextView;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import androidx.fragment.app.DialogFragment;
 | 
			
		||||
import androidx.fragment.app.Fragment;
 | 
			
		||||
import androidx.fragment.app.FragmentManager;
 | 
			
		||||
@@ -57,13 +57,14 @@ public class AboutFragment extends DialogFragment {
 | 
			
		||||
        AboutFragment.newInstance().show(ft, TAG);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @NonNull
 | 
			
		||||
    @Override
 | 
			
		||||
    public Dialog onCreateDialog(Bundle savedInstanceState) {
 | 
			
		||||
        final View view = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_about, null);
 | 
			
		||||
        final View view = getLayoutInflater().inflate(R.layout.fragment_about, null);
 | 
			
		||||
        ((TextView) view.findViewById(R.id.tvHelp)).setText(Html.fromHtml(getLicencesHtml()));
 | 
			
		||||
        ((TextView) view.findViewById(R.id.tvVersion)).setText(getString(R.string.about_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE));
 | 
			
		||||
 | 
			
		||||
        MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getActivity())
 | 
			
		||||
        MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity())
 | 
			
		||||
                .setView(view)
 | 
			
		||||
                .setNegativeButton(R.string.about_close,
 | 
			
		||||
                        new DialogInterface.OnClickListener() {
 | 
			
		||||
@@ -77,7 +78,7 @@ public class AboutFragment extends DialogFragment {
 | 
			
		||||
 | 
			
		||||
    private String getLicencesHtml() {
 | 
			
		||||
        try (BufferedReader reader = new BufferedReader(
 | 
			
		||||
                new InputStreamReader(getContext().getAssets().open("licenses.html"), StandardCharsets.UTF_8))) {
 | 
			
		||||
                new InputStreamReader(requireContext().getAssets().open("licenses.html"), StandardCharsets.UTF_8))) {
 | 
			
		||||
            StringBuilder sb = new StringBuilder();
 | 
			
		||||
            String line;
 | 
			
		||||
            while ((line = reader.readLine()) != null)
 | 
			
		||||
 
 | 
			
		||||
@@ -20,10 +20,10 @@ import android.app.Dialog;
 | 
			
		||||
import android.content.DialogInterface;
 | 
			
		||||
import android.os.Bundle;
 | 
			
		||||
import android.text.Html;
 | 
			
		||||
import android.view.LayoutInflater;
 | 
			
		||||
import android.view.View;
 | 
			
		||||
import android.widget.TextView;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import androidx.fragment.app.DialogFragment;
 | 
			
		||||
import androidx.fragment.app.Fragment;
 | 
			
		||||
import androidx.fragment.app.FragmentManager;
 | 
			
		||||
@@ -49,13 +49,14 @@ public class CreditsFragment extends DialogFragment {
 | 
			
		||||
        CreditsFragment.newInstance().show(ft, TAG);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @NonNull
 | 
			
		||||
    @Override
 | 
			
		||||
    public Dialog onCreateDialog(Bundle savedInstanceState) {
 | 
			
		||||
        final View view = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_credits, null);
 | 
			
		||||
        final View view = getLayoutInflater().inflate(R.layout.fragment_credits, null);
 | 
			
		||||
 | 
			
		||||
        ((TextView) view.findViewById(R.id.tvCredits)).setText(Html.fromHtml(getString(R.string.credits_text)));
 | 
			
		||||
 | 
			
		||||
        MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getActivity())
 | 
			
		||||
        MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity())
 | 
			
		||||
                .setView(view)
 | 
			
		||||
                .setNegativeButton(R.string.about_close,
 | 
			
		||||
                        new DialogInterface.OnClickListener() {
 | 
			
		||||
 
 | 
			
		||||
@@ -16,13 +16,13 @@
 | 
			
		||||
 | 
			
		||||
package com.m2049r.xmrwallet.dialog;
 | 
			
		||||
 | 
			
		||||
import android.annotation.SuppressLint;
 | 
			
		||||
import android.app.Dialog;
 | 
			
		||||
import android.graphics.drawable.Drawable;
 | 
			
		||||
import android.os.Build;
 | 
			
		||||
import android.os.Bundle;
 | 
			
		||||
import android.text.Html;
 | 
			
		||||
import android.text.Spanned;
 | 
			
		||||
import android.view.LayoutInflater;
 | 
			
		||||
import android.view.View;
 | 
			
		||||
import android.widget.TextView;
 | 
			
		||||
 | 
			
		||||
@@ -65,9 +65,10 @@ public class HelpFragment extends DialogFragment {
 | 
			
		||||
 | 
			
		||||
    private Spanned getHtml(String html, double textSize) {
 | 
			
		||||
        final Html.ImageGetter imageGetter = source -> {
 | 
			
		||||
            final int imageId = getResources().getIdentifier(source.replace("/", ""), "drawable", requireActivity().getPackageName());
 | 
			
		||||
            @SuppressLint("DiscouragedApi") final int imageId = getResources().getIdentifier(source.replace("/", ""), "drawable", requireActivity().getPackageName());
 | 
			
		||||
            // Don't die if we don't find the image - use a heart instead
 | 
			
		||||
            final Drawable drawable = ContextCompat.getDrawable(requireActivity(), imageId > 0 ? imageId : R.drawable.ic_favorite_24dp);
 | 
			
		||||
            assert drawable != null;
 | 
			
		||||
            final double f = textSize / drawable.getIntrinsicHeight();
 | 
			
		||||
            drawable.setBounds(0, 0, (int) (f * drawable.getIntrinsicWidth()), (int) textSize);
 | 
			
		||||
            return drawable;
 | 
			
		||||
@@ -82,7 +83,7 @@ public class HelpFragment extends DialogFragment {
 | 
			
		||||
    @NonNull
 | 
			
		||||
    @Override
 | 
			
		||||
    public Dialog onCreateDialog(Bundle savedInstanceState) {
 | 
			
		||||
        final View view = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_help, null);
 | 
			
		||||
        final View view = getLayoutInflater().inflate(R.layout.fragment_help, null);
 | 
			
		||||
 | 
			
		||||
        int helpId = 0;
 | 
			
		||||
        boolean torButton = false;
 | 
			
		||||
@@ -100,7 +101,7 @@ public class HelpFragment extends DialogFragment {
 | 
			
		||||
                        .setView(view);
 | 
			
		||||
        if (torButton) {
 | 
			
		||||
            builder.setNegativeButton(R.string.help_nok,
 | 
			
		||||
                    (dialog, id) -> dialog.dismiss())
 | 
			
		||||
                            (dialog, id) -> dialog.dismiss())
 | 
			
		||||
                    .setPositiveButton(R.string.help_getorbot,
 | 
			
		||||
                            (dialog, id) -> {
 | 
			
		||||
                                dialog.dismiss();
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,6 @@ package com.m2049r.xmrwallet.dialog;
 | 
			
		||||
 | 
			
		||||
import android.app.Dialog;
 | 
			
		||||
import android.os.Bundle;
 | 
			
		||||
import android.view.LayoutInflater;
 | 
			
		||||
import android.view.View;
 | 
			
		||||
import android.widget.TextView;
 | 
			
		||||
 | 
			
		||||
@@ -66,7 +65,7 @@ public class PocketChangeFragment extends DialogFragment implements Slider.OnCha
 | 
			
		||||
    @NonNull
 | 
			
		||||
    @Override
 | 
			
		||||
    public Dialog onCreateDialog(Bundle savedInstanceState) {
 | 
			
		||||
        final View view = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_pocketchange_setting, null);
 | 
			
		||||
        final View view = getLayoutInflater().inflate(R.layout.fragment_pocketchange_setting, null);
 | 
			
		||||
        boolean enabled = false;
 | 
			
		||||
        int progress = 0;
 | 
			
		||||
        Bundle arguments = getArguments();
 | 
			
		||||
 
 | 
			
		||||
@@ -20,10 +20,10 @@ import android.app.Dialog;
 | 
			
		||||
import android.content.DialogInterface;
 | 
			
		||||
import android.os.Bundle;
 | 
			
		||||
import android.text.Html;
 | 
			
		||||
import android.view.LayoutInflater;
 | 
			
		||||
import android.view.View;
 | 
			
		||||
import android.widget.TextView;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import androidx.fragment.app.DialogFragment;
 | 
			
		||||
import androidx.fragment.app.Fragment;
 | 
			
		||||
import androidx.fragment.app.FragmentManager;
 | 
			
		||||
@@ -49,13 +49,14 @@ public class PrivacyFragment extends DialogFragment {
 | 
			
		||||
        PrivacyFragment.newInstance().show(ft, TAG);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @NonNull
 | 
			
		||||
    @Override
 | 
			
		||||
    public Dialog onCreateDialog(Bundle savedInstanceState) {
 | 
			
		||||
        final View view = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_privacy_policy, null);
 | 
			
		||||
        final View view = getLayoutInflater().inflate(R.layout.fragment_privacy_policy, null);
 | 
			
		||||
 | 
			
		||||
        ((TextView) view.findViewById(R.id.tvCredits)).setText(Html.fromHtml(getString(R.string.privacy_policy)));
 | 
			
		||||
 | 
			
		||||
        MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getActivity())
 | 
			
		||||
        MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity())
 | 
			
		||||
                .setView(view)
 | 
			
		||||
                .setNegativeButton(R.string.about_close,
 | 
			
		||||
                        new DialogInterface.OnClickListener() {
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,7 @@ package com.m2049r.xmrwallet.dialog;
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import android.annotation.SuppressLint;
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.os.Bundle;
 | 
			
		||||
import android.view.LayoutInflater;
 | 
			
		||||
@@ -31,6 +32,7 @@ import com.m2049r.xmrwallet.R;
 | 
			
		||||
import com.m2049r.xmrwallet.util.Helper;
 | 
			
		||||
 | 
			
		||||
import java.util.Locale;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
 | 
			
		||||
import timber.log.Timber;
 | 
			
		||||
 | 
			
		||||
@@ -56,7 +58,7 @@ public class ProgressDialog extends AlertDialog {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onCreate(Bundle savedInstanceState) {
 | 
			
		||||
        final View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_ledger_progress, null);
 | 
			
		||||
        @SuppressLint("InflateParams") final View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_ledger_progress, null);
 | 
			
		||||
        pbCircle = view.findViewById(R.id.pbCircle);
 | 
			
		||||
        tvMessage = view.findViewById(R.id.tvMessage);
 | 
			
		||||
        rlProgressBar = view.findViewById(R.id.rlProgressBar);
 | 
			
		||||
@@ -78,7 +80,7 @@ public class ProgressDialog extends AlertDialog {
 | 
			
		||||
        super.onCreate(savedInstanceState);
 | 
			
		||||
 | 
			
		||||
        if (Helper.preventScreenshot()) {
 | 
			
		||||
            getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
 | 
			
		||||
            Objects.requireNonNull(getWindow()).setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,16 @@
 | 
			
		||||
package com.m2049r.xmrwallet.fragment.send;
 | 
			
		||||
 | 
			
		||||
import com.m2049r.xmrwallet.data.CryptoAmount;
 | 
			
		||||
import com.m2049r.xmrwallet.service.shift.api.QueryOrderParameters;
 | 
			
		||||
 | 
			
		||||
public interface PreShifter {
 | 
			
		||||
    CryptoAmount getAmount();
 | 
			
		||||
 | 
			
		||||
    void onOrderParametersError(final Exception ex);
 | 
			
		||||
 | 
			
		||||
    void onOrderParametersReceived(final QueryOrderParameters orderParameters);
 | 
			
		||||
 | 
			
		||||
    boolean isActive();
 | 
			
		||||
 | 
			
		||||
    void showProgress();
 | 
			
		||||
}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user