Skip to content

Commit

Permalink
Add the ability to set a custom note to a transaction
Browse files Browse the repository at this point in the history
  • Loading branch information
hsjoberg committed Sep 3, 2020
1 parent 17856a3 commit c1f91c0
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 53 deletions.
37 changes: 33 additions & 4 deletions src/components/BlurModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,23 @@ import React from "react";
import Modal from "react-native-modal";
import { useNavigation } from "@react-navigation/native";
import RealTimeBlur from "../react-native-realtimeblur";
import { KeyboardAvoidingView, View, StyleSheet } from "react-native";
import { TouchableWithoutFeedback } from "react-native-gesture-handler";

export interface ITransactionDetailsProps {
children: any;
useModalComponent?: boolean;
goBackByClickingOutside?: boolean
}
export default function BlurModal({ children, useModalComponent }: ITransactionDetailsProps) {
export default function BlurModal({ children, useModalComponent, goBackByClickingOutside }: ITransactionDetailsProps) {
const navigation = useNavigation();
const useModal = useModalComponent ?? true;
goBackByClickingOutside = goBackByClickingOutside ?? true;

const goBack = () => {
navigation.goBack();
if (goBackByClickingOutside) {
navigation.goBack();
}
};

return (
Expand All @@ -22,7 +28,14 @@ export default function BlurModal({ children, useModalComponent }: ITransactionD
blurRadius={15}
>
{!useModal
? children
? <View style={{ flex: 1 }}>
<TouchableWithoutFeedback style={{ width:"100%", height:"100%" }} onPress={goBack}></TouchableWithoutFeedback>
<View style={style.container}>
<View style={style.inner}>
{children}
</View>
</View>
</View>
: <Modal
onBackdropPress={goBack}
onRequestClose={goBack}
Expand All @@ -33,4 +46,20 @@ export default function BlurModal({ children, useModalComponent }: ITransactionD
}
</RealTimeBlur>
);
};
};

const style = StyleSheet.create({
container: {
position: "absolute",
width: "100%",
height: "100%",
flex: 1,
justifyContent: "center",
},
inner: {
margin: 12,
padding: 0,
justifyContent: "flex-start",
flexDirection: "column",
},
})
34 changes: 19 additions & 15 deletions src/components/TransactionCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ interface IProps {
export default function TransactionCard({ onPress, transaction, unit }: IProps) {
const { date, value, amtPaidSat, status, tlvRecordName } = transaction;
const positive = value.isPositive();
const { name, description } = extractDescription(transaction.description);
let { name, description } = extractDescription(transaction.description);

const fiatUnit = useStoreState((store) => store.settings.fiatUnit);
const currentRate = useStoreState((store) => store.fiat.currentRate);
Expand All @@ -34,25 +34,29 @@ export default function TransactionCard({ onPress, transaction, unit }: IProps)
}

// const start = performance.now();
// const image = getTransactionImage(transaction);
const lightningService = getLightningService(transaction);
// console.log("Time: " + (performance.now() - start));

let recipientOrSender;
if (lightningService) {
recipientOrSender = lightningService.title;
if (transaction.note) {
description = transaction.note;
}
else if (transaction.website) {
recipientOrSender = transaction.website;
}
else if (transaction.value.lessThan(0) && name) {
recipientOrSender = name;
}
else if (transaction.value.greaterThanOrEqual(0) && tlvRecordName) {
recipientOrSender = tlvRecordName;
}
else if (transaction.value.greaterThanOrEqual(0) && transaction.payer) {
recipientOrSender = transaction.payer;
else {
if (lightningService) {
recipientOrSender = lightningService.title;
}
else if (transaction.website) {
recipientOrSender = transaction.website;
}
else if (transaction.value.lessThan(0) && name) {
recipientOrSender = name;
}
else if (transaction.value.greaterThanOrEqual(0) && tlvRecordName) {
recipientOrSender = tlvRecordName;
}
else if (transaction.value.greaterThanOrEqual(0) && transaction.payer) {
recipientOrSender = transaction.payer;
}
}

return (
Expand Down
6 changes: 6 additions & 0 deletions src/migration/app-migration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,10 @@ export const appMigration: IAppMigration[] = [
await setItemObject<boolean>(StorageItem.screenTransitionsEnabled, true);
},
},
// Version 19
{
async beforeLnd(db, i) {
await db.executeSql("ALTER TABLE tx ADD note TEXT NULL");
},
},
];
3 changes: 2 additions & 1 deletion src/storage/database/sqlite-migrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ const schema = [
type TEXT NOT NULL DEFAULT "NORMAL",
preimage TEXT NOT NULL DEFAULT "00",
lnurlPayResponse TEXT NULL,
identifiedService TEXT NULL
identifiedService TEXT NULL,
note TEXT NULL
)`,

`CREATE TABLE tx_hops (
Expand Down
14 changes: 11 additions & 3 deletions src/storage/database/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export interface IDBTransaction {
preimage: string,
lnurlPayResponse: string | null;
identifiedService: string | null;
note: string | null;
}

export interface ITransaction {
Expand Down Expand Up @@ -63,6 +64,7 @@ export interface ITransaction {
preimage: Uint8Array,
lnurlPayResponse: ILNUrlPayResponse | null;
identifiedService: keyof ILightningServices | null;
note?: string | null;

hops: ITransactionHop[];
}
Expand Down Expand Up @@ -118,7 +120,8 @@ export const createTransaction = async (db: SQLiteDatabase, transaction: ITransa
type,
preimage,
lnurlPayResponse,
identifiedService
identifiedService,
note
)
VALUES
(
Expand Down Expand Up @@ -147,6 +150,7 @@ export const createTransaction = async (db: SQLiteDatabase, transaction: ITransa
?,
?,
?,
?,
?
)`,
[
Expand Down Expand Up @@ -176,6 +180,7 @@ export const createTransaction = async (db: SQLiteDatabase, transaction: ITransa
bytesToHexString(transaction.preimage),
transaction.lnurlPayResponse ? JSON.stringify(transaction.lnurlPayResponse) : null,
transaction.identifiedService,
transaction.note ?? null,
],
);

Expand Down Expand Up @@ -234,7 +239,8 @@ export const updateTransaction = async (db: SQLiteDatabase, transaction: ITransa
type = ?,
preimage = ?,
lnurlPayResponse = ?,
identifiedService = ?
identifiedService = ?,
note = ?
WHERE id = ?`,
[
transaction.date.toString(),
Expand All @@ -261,6 +267,7 @@ export const updateTransaction = async (db: SQLiteDatabase, transaction: ITransa
bytesToHexString(transaction.preimage),
transaction.lnurlPayResponse ? JSON.stringify(transaction.lnurlPayResponse) : null,
transaction.identifiedService,
transaction.note ?? null,
transaction.id,
],
);
Expand Down Expand Up @@ -325,7 +332,8 @@ const convertDBTransaction = (transaction: IDBTransaction): ITransaction => {
type: transaction.type as ITransaction["type"] || "NORMAL",
preimage: transaction.preimage ? hexToUint8Array(transaction.preimage) : new Uint8Array([0]),
lnurlPayResponse,
identifiedService: transaction.identifiedService,
identifiedService: transaction.identifiedService as ITransaction["identifiedService"],
note: transaction.note,
hops: [],
};
};
2 changes: 1 addition & 1 deletion src/windows/Settings/LndMobileHelpCenter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ export default function LndMobileHelpCenter({ navigation }) {
};

return (
<Blurmodal useModalComponent={false}>
<Blurmodal useModalComponent={false} goBackByClickingOutside={false}>
<View style={style.container}>
<Card style={style.card}>
<CardItem style={style.cardStyle}>
Expand Down
95 changes: 66 additions & 29 deletions src/windows/TransactionDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, { useState } from "react";
import { StyleSheet, Share } from "react-native";
import { StyleSheet, Share, Alert, KeyboardAvoidingView } from "react-native";
import DialogAndroid from "react-native-dialogs";
import Clipboard from "@react-native-community/react-native-clipboard";
import { Body, Card, Text, CardItem, H1, View, Button } from "native-base";
import { Body, Card, Text, CardItem, H1, View, Button, Icon } from "native-base";
import { fromUnixTime } from "date-fns";
import MapView, { PROVIDER_GOOGLE, Marker } from "react-native-maps";

Expand All @@ -16,6 +17,7 @@ import CopyAddress from "../components/CopyAddress";
import { MapStyle } from "../utils/google-maps";
import TextLink from "../components/TextLink";
import { ITransaction } from "../storage/database/transaction";
import { blixtTheme } from "../../native-base-theme/variables/commonColor";

interface IMetaDataProps {
title: string;
Expand Down Expand Up @@ -49,6 +51,8 @@ export default function TransactionDetails({ route, navigation }: ITransactionDe
const cancelInvoice = useStoreActions((store) => store.receive.cancelInvoice);
const bitcoinUnit = useStoreState((store) => store.settings.bitcoinUnit);
const transactionGeolocationMapStyle = useStoreState((store) => store.settings.transactionGeolocationMapStyle);
const [mapActive, setMapActive] = useState(false);
const syncTransaction = useStoreActions((store) => store.transaction.syncTransaction);

const [currentScreen, setCurrentScreen] = useState<"Overview" | "Map">("Overview");

Expand All @@ -70,17 +74,6 @@ export default function TransactionDetails({ route, navigation }: ITransactionDe
toast("Copied to clipboard", undefined, "warning");
};

let transactionValue: Long;
let direction: "send" | "receive";
if (isLong(transaction.value) && transaction.value.greaterThanOrEqual(0)) {
direction = "receive";
transactionValue = transaction.value;
}
else {
direction = "send";
transactionValue = transaction.value;
}

const onPressCancelInvoice = async () => {
// There's a LayoutAnimation.configureNext() in the Transaction store
// to animate the removal of the invoice if hideArchivedInvoices is `true`.
Expand All @@ -93,30 +86,65 @@ export default function TransactionDetails({ route, navigation }: ITransactionDe
await checkOpenTransactions();
}, 35);
navigation.pop();
};

const onPressSetNote = async () => {
const result = await DialogAndroid.prompt(null, "Set a note for this transaction", {
defaultValue: transaction.note,
});
if (result.action === DialogAndroid.actionPositive) {
await syncTransaction({
...transaction,
note: result.text?.trim() || null,
});
}
}

let transactionValue: Long;
let direction: "send" | "receive";
if (isLong(transaction.value) && transaction.value.greaterThanOrEqual(0)) {
direction = "receive";
transactionValue = transaction.value;
}
else {
direction = "send";
transactionValue = transaction.value;
}

if (currentScreen === "Overview") {
return (
<Blurmodal>
<Blurmodal useModalComponent={false} goBackByClickingOutside={true}>
<Card style={style.card}>
<CardItem>
<Body>
<View style={{ display: "flex", flexDirection: "row", justifyContent: "space-between", width: "100%" }}>
<H1 style={style.header}>
Transaction
</H1>
{transaction.status === "OPEN" &&
<Button small danger onPress={onPressCancelInvoice}>
<Text style={{ fontSize: 9 }}>Cancel invoice</Text>
<View style={{ flexDirection: "row" }}>
<Button small style={style.actionBarButton} onPress={onPressSetNote}>
<Text style={style.actionBarButtonText}>Set note</Text>
</Button>
}
{transaction.locationLat && transaction.locationLat &&
<Button small={true} onPress={() => setCurrentScreen("Map")}>
<Text style={{ fontSize: 9 }}>Show map</Text>
</Button>
}
{transaction.status === "OPEN" &&
<Button small danger onPress={onPressCancelInvoice} style={style.actionBarButton}>
<Text style={style.actionBarButtonText}>Cancel invoice</Text>
</Button>
}
{transaction.locationLat && transaction.locationLat &&
<Button small={true} onPress={() => {
setCurrentScreen("Map");
setTimeout(() => {
setMapActive(true);
}, 200);
}} style={style.actionBarButton}>
<Text style={style.actionBarButtonText}>Show map</Text>
</Button>
}
</View>
</View>

<MetaData title="Date" data={formatISO(fromUnixTime(transaction.date.toNumber()))} />
{transaction.note && <MetaData title="Note" data={transaction.note} />}
{transaction.website && <MetaData title="Website" data={transaction.website} url={"https://" + transaction.website} />}
{transaction.type !== "NORMAL" && <MetaData title="Type" data={transaction.type} />}
{(transaction.type === "LNURL" && transaction.lnurlPayResponse && transaction.lnurlPayResponse.successAction) && <LNURLMetaData transaction={transaction} />}
Expand All @@ -137,9 +165,7 @@ export default function TransactionDetails({ route, navigation }: ITransactionDe
<View style={{ width: "100%", alignItems: "center", justifyContent: "center" }}>
<QrCode size={smallScreen ? 220 : 280} data={transaction.paymentRequest.toUpperCase()} onPress={onQrPress} border={25} />
</View>
<View style={{ alignItems: "center", justifyContent: "center" }}>
<CopyAddress text={transaction.paymentRequest} onPress={onPaymentRequestTextPress} />
</View>
<CopyAddress text={transaction.paymentRequest} onPress={onPaymentRequestTextPress} />
</>
}
</Body>
Expand All @@ -158,15 +184,20 @@ export default function TransactionDetails({ route, navigation }: ITransactionDe
<H1 style={style.header}>
Transaction
</H1>
<Button small={true} onPress={() => setCurrentScreen("Overview")}>
<Button small={true} onPress={() => {
setCurrentScreen("Overview");
setMapActive(false);
}}>
<Text style={{ fontSize: 9 }}>Go back</Text>
</Button>
</View>
<MapView
provider={PROVIDER_GOOGLE}
provider={PROVIDER_GOOGLE}
style={{
width: "100%",
height: 450,
backgroundColor:blixtTheme.gray,
opacity: mapActive ? 1 : 0,
}}
initialRegion={{
longitude: transaction.locationLong!,
Expand Down Expand Up @@ -228,7 +259,7 @@ function LNURLMetaData({ transaction }: IWebLNMetaDataProps) {
const style = StyleSheet.create({
card: {
padding: 5,
width: "100%",
// width: "100%",
minHeight: "55%",
},
header: {
Expand All @@ -243,5 +274,11 @@ const style = StyleSheet.create({
paddingTop: 4,
paddingLeft: 18,
paddingRight: 18,
},
actionBarButton: {
marginLeft: 7,
},
actionBarButtonText: {
fontSize: 9,
}
});

0 comments on commit c1f91c0

Please sign in to comment.