2021-07-07 08:40:05 -07:00
import ' dart:isolate ' ;
2021-07-10 22:20:53 -07:00
import ' dart:typed_data ' ;
2021-07-12 04:32:49 -07:00
import ' dart:math ' as math ;
2021-09-05 21:41:32 -07:00
import ' package:flutter/foundation.dart ' ;
2021-08-02 22:58:02 -07:00
import ' package:json_annotation/json_annotation.dart ' ;
2021-07-07 08:40:05 -07:00
2021-06-26 07:30:12 -07:00
import ' package:flutter/material.dart ' ;
import ' package:intl/intl.dart ' ;
import ' package:mobx/mobx.dart ' ;
import ' package:path/path.dart ' ;
import ' package:sqflite/sqflite.dart ' ;
import ' package:warp_api/warp_api.dart ' ;
import ' package:shared_preferences/shared_preferences.dart ' ;
import ' package:http/http.dart ' as http ;
import ' dart:convert ' as convert ;
import ' package:convert/convert.dart ' ;
2021-07-10 22:20:53 -07:00
import ' package:flex_color_scheme/flex_color_scheme.dart ' ;
2021-06-26 07:30:12 -07:00
2021-09-05 21:41:32 -07:00
import ' generated/l10n.dart ' ;
2021-06-26 07:30:12 -07:00
import ' main.dart ' ;
part ' store.g.dart ' ;
class Settings = _Settings with _ $Settings ;
abstract class _Settings with Store {
2021-07-09 06:33:39 -07:00
@ observable
2021-09-10 02:56:15 -07:00
String ldUrl = " " ;
2021-07-09 06:33:39 -07:00
@ observable
2021-09-10 02:56:15 -07:00
String ldUrlChoice = " " ;
2021-07-09 06:33:39 -07:00
@ observable
2021-09-10 02:56:15 -07:00
int anchorOffset = 10 ;
2021-07-09 06:33:39 -07:00
2021-07-09 22:44:34 -07:00
@ observable
2021-09-10 02:56:15 -07:00
bool getTx = true ;
2021-07-09 22:44:34 -07:00
2021-07-10 22:20:53 -07:00
@ observable
2021-09-10 02:56:15 -07:00
int rowsPerPage = 10 ;
2021-07-10 22:20:53 -07:00
@ observable
2021-09-10 02:56:15 -07:00
String theme = " " ;
2021-07-10 22:20:53 -07:00
@ observable
2021-09-10 02:56:15 -07:00
String themeBrightness = " " ;
2021-07-10 22:20:53 -07:00
@ observable
2021-07-12 04:32:49 -07:00
ThemeData themeData = ThemeData . light ( ) ;
2021-07-30 02:45:43 -07:00
@ observable
bool showConfirmations = false ;
2021-08-07 00:32:10 -07:00
@ observable
String currency = " USD " ;
@ observable
List < String > currencies = [ " USD " ] ;
2021-08-16 06:07:16 -07:00
@ observable
String chartRange = ' 1Y ' ;
2021-08-23 05:47:48 -07:00
@ observable
bool shieldBalance = false ;
2021-08-23 08:44:41 -07:00
@ observable
double autoShieldThreshold = 0.0 ;
2021-08-27 02:51:34 -07:00
@ observable
bool useUA = false ;
2021-09-13 02:37:35 -07:00
@ observable
bool autoHide = true ;
2021-06-26 07:30:12 -07:00
@ action
2021-07-12 04:32:49 -07:00
Future < bool > restore ( ) async {
2021-06-26 07:30:12 -07:00
final prefs = await SharedPreferences . getInstance ( ) ;
2021-08-13 20:44:53 -07:00
ldUrlChoice = prefs . getString ( ' lightwalletd_choice ' ) ? ? " Lightwalletd " ;
2021-07-09 06:33:39 -07:00
ldUrl = prefs . getString ( ' lightwalletd_custom ' ) ? ? " " ;
prefs . setString ( ' lightwalletd_choice ' , ldUrlChoice ) ;
prefs . setString ( ' lightwalletd_custom ' , ldUrl ) ;
anchorOffset = prefs . getInt ( ' anchor_offset ' ) ? ? 3 ;
2021-07-09 22:44:34 -07:00
getTx = prefs . getBool ( ' get_txinfo ' ) ? ? true ;
2021-07-10 22:20:53 -07:00
rowsPerPage = prefs . getInt ( ' rows_per_age ' ) ? ? 10 ;
2021-09-16 22:47:31 -07:00
theme = prefs . getString ( ' theme ' ) ? ? " gold " ;
2021-07-10 22:20:53 -07:00
themeBrightness = prefs . getString ( ' theme_brightness ' ) ? ? " dark " ;
2021-07-30 02:45:43 -07:00
showConfirmations = prefs . getBool ( ' show_confirmations ' ) ? ? false ;
2021-08-09 07:13:42 -07:00
currency = prefs . getString ( ' currency ' ) ? ? " USD " ;
2021-08-16 06:07:16 -07:00
chartRange = prefs . getString ( ' chart_range ' ) ? ? " 1Y " ;
2021-08-23 05:47:48 -07:00
shieldBalance = prefs . getBool ( ' shield_balance ' ) ? ? false ;
2021-08-23 08:44:41 -07:00
autoShieldThreshold = prefs . getDouble ( ' autoshield_threshold ' ) ? ? 0.0 ;
2021-08-27 02:51:34 -07:00
useUA = prefs . getBool ( ' use_ua ' ) ? ? false ;
2021-09-13 02:37:35 -07:00
autoHide = prefs . getBool ( ' auto_hide ' ) ? ? true ;
2021-07-10 22:20:53 -07:00
_updateThemeData ( ) ;
2021-08-07 00:32:10 -07:00
Future . microtask ( _loadCurrencies ) ; // lazily
2021-07-12 04:32:49 -07:00
return true ;
2021-06-26 07:30:12 -07:00
}
2021-07-09 06:33:39 -07:00
@ action
Future < void > setURLChoice ( String choice ) async {
ldUrlChoice = choice ;
final prefs = await SharedPreferences . getInstance ( ) ;
prefs . setString ( ' lightwalletd_choice ' , ldUrlChoice ) ;
updateLWD ( ) ;
}
@ action
Future < void > setURL ( String url ) async {
ldUrl = url ;
final prefs = await SharedPreferences . getInstance ( ) ;
prefs . setString ( ' lightwalletd_custom ' , ldUrl ) ;
updateLWD ( ) ;
}
@ action
Future < void > setAnchorOffset ( int offset ) async {
final prefs = await SharedPreferences . getInstance ( ) ;
anchorOffset = offset ;
prefs . setInt ( ' anchor_offset ' , offset ) ;
}
2021-07-10 22:20:53 -07:00
@ action
Future < void > setTheme ( String thm ) async {
final prefs = await SharedPreferences . getInstance ( ) ;
theme = thm ;
prefs . setString ( ' theme ' , thm ) ;
_updateThemeData ( ) ;
}
@ action
Future < void > setThemeBrightness ( String brightness ) async {
final prefs = await SharedPreferences . getInstance ( ) ;
themeBrightness = brightness ;
prefs . setString ( ' theme_brightness ' , brightness ) ;
_updateThemeData ( ) ;
}
void _updateThemeData ( ) {
FlexScheme scheme ;
switch ( theme ) {
2021-09-16 22:47:31 -07:00
case ' gold ' :
2021-07-12 04:32:49 -07:00
scheme = FlexScheme . mango ;
break ;
case ' blue ' :
scheme = FlexScheme . bahamaBlue ;
break ;
case ' pink ' :
scheme = FlexScheme . sakura ;
break ;
2021-09-16 22:47:31 -07:00
case ' purple ' :
scheme = FlexScheme . deepPurple ;
2021-07-12 04:32:49 -07:00
break ;
2021-09-10 02:56:15 -07:00
default :
scheme = FlexScheme . mango ;
2021-07-10 22:20:53 -07:00
}
switch ( themeBrightness ) {
2021-08-16 06:07:16 -07:00
case ' light ' :
themeData = FlexColorScheme . light ( scheme: scheme ) . toTheme ;
break ;
case ' dark ' :
themeData = FlexColorScheme . dark ( scheme: scheme ) . toTheme ;
break ;
2021-07-10 22:20:53 -07:00
}
}
2021-08-16 06:07:16 -07:00
@ action
Future < void > setChartRange ( String v ) async {
final prefs = await SharedPreferences . getInstance ( ) ;
chartRange = v ;
prefs . setString ( ' chart_range ' , chartRange ) ;
2021-09-11 19:27:31 -07:00
accountManager . fetchChartData ( ) ;
2021-08-16 06:07:16 -07:00
}
2021-07-09 06:33:39 -07:00
String getLWD ( ) {
switch ( ldUrlChoice ) {
2021-08-16 06:07:16 -07:00
case " custom " :
return ldUrl ;
default :
return coin . lwd
. firstWhere ( ( lwd ) = > lwd . name = = ldUrlChoice ,
orElse: ( ) = > coin . lwd . first )
. url ;
2021-07-09 06:33:39 -07:00
}
}
void updateLWD ( ) {
WarpApi . updateLWD ( getLWD ( ) ) ;
}
2021-07-09 22:44:34 -07:00
@ action
Future < void > updateGetTx ( bool v ) async {
final prefs = await SharedPreferences . getInstance ( ) ;
getTx = v ;
prefs . setBool ( ' get_txinfo ' , v ) ;
}
2021-07-10 22:20:53 -07:00
@ action
Future < void > setRowsPerPage ( int v ) async {
final prefs = await SharedPreferences . getInstance ( ) ;
rowsPerPage = v ;
prefs . setInt ( ' rows_per_age ' , v ) ;
}
2021-07-30 02:45:43 -07:00
@ action
Future < void > toggleShowConfirmations ( ) async {
final prefs = await SharedPreferences . getInstance ( ) ;
showConfirmations = ! showConfirmations ;
prefs . setBool ( ' show_confirmations ' , showConfirmations ) ;
}
2021-08-07 00:32:10 -07:00
@ action
Future < void > setCurrency ( String newCurrency ) async {
2021-08-09 07:13:42 -07:00
final prefs = await SharedPreferences . getInstance ( ) ;
2021-08-07 00:32:10 -07:00
currency = newCurrency ;
2021-08-09 07:13:42 -07:00
prefs . setString ( ' currency ' , currency ) ;
2021-08-07 00:32:10 -07:00
await priceStore . fetchZecPrice ( ) ;
2021-09-11 19:27:31 -07:00
await accountManager . fetchChartData ( ) ;
2021-08-07 00:32:10 -07:00
}
@ action
Future < void > _loadCurrencies ( ) async {
final base = " api.coingecko.com " ;
final uri = Uri . https ( base , ' /api/v3/simple/supported_vs_currencies ' ) ;
final rep = await http . get ( uri ) ;
if ( rep . statusCode = = 200 ) {
final _currencies = convert . jsonDecode ( rep . body ) as List < dynamic > ;
final c = _currencies . map ( ( v ) = > ( v as String ) . toUpperCase ( ) ) . toList ( ) ;
c . sort ( ) ;
currencies = c ;
}
}
2021-08-23 05:47:48 -07:00
@ action
Future < void > setShieldBalance ( bool v ) async {
final prefs = await SharedPreferences . getInstance ( ) ;
shieldBalance = v ;
prefs . setBool ( ' shield_balance ' , shieldBalance ) ;
}
2021-08-23 08:44:41 -07:00
@ action
Future < void > setAutoShieldThreshold ( double v ) async {
final prefs = await SharedPreferences . getInstance ( ) ;
autoShieldThreshold = v ;
prefs . setDouble ( ' autoshield_threshold ' , autoShieldThreshold ) ;
}
2021-08-27 02:51:34 -07:00
@ action
Future < void > setUseUA ( bool v ) async {
final prefs = await SharedPreferences . getInstance ( ) ;
useUA = v ;
prefs . setBool ( ' use_ua ' , useUA ) ;
}
2021-09-13 02:37:35 -07:00
@ action
Future < void > setAutoHide ( bool v ) async {
final prefs = await SharedPreferences . getInstance ( ) ;
autoHide = v ;
prefs . setBool ( ' auto_hide ' , autoHide ) ;
}
2021-06-26 07:30:12 -07:00
}
class AccountManager = _AccountManager with _ $AccountManager ;
abstract class _AccountManager with Store {
2021-09-10 02:56:15 -07:00
late Database db ;
2021-06-26 07:30:12 -07:00
@ observable
2021-09-10 02:56:15 -07:00
Account active = Account ( 0 , " " , " " , 0 ) ;
2021-06-26 07:30:12 -07:00
@ observable
bool canPay = false ;
@ observable
int balance = 0 ;
@ observable
int unconfirmedBalance = 0 ;
2021-07-09 06:33:39 -07:00
@ observable
String taddress = " " ;
2021-08-07 00:32:10 -07:00
@ observable
bool showTAddr = false ;
2021-07-09 06:33:39 -07:00
@ observable
int tbalance = 0 ;
2021-06-26 07:30:12 -07:00
@ observable
List < Note > notes = [ ] ;
@ observable
List < Tx > txs = [ ] ;
2021-08-16 06:07:16 -07:00
@ observable
int lastTxHeight = 0 ;
2021-07-12 04:32:49 -07:00
@ observable
2021-07-18 08:59:02 -07:00
int dataEpoch = 0 ;
2021-07-12 04:32:49 -07:00
@ observable
List < Spending > spendings = [ ] ;
@ observable
2021-09-10 02:56:15 -07:00
List < TimeSeriesPoint < double > > accountBalances = [ ] ;
2021-07-12 04:32:49 -07:00
2021-08-09 07:13:42 -07:00
@ observable
List < PnL > pnls = [ ] ;
2021-06-26 07:30:12 -07:00
@ observable
2021-07-07 08:40:05 -07:00
List < Account > accounts = [ ] ;
2021-06-26 07:30:12 -07:00
2021-07-12 04:32:49 -07:00
@ observable
2021-09-17 18:14:08 -07:00
SortConfig noteSortConfig = SortConfig ( " " , SortOrder . Unsorted ) ;
2021-07-12 04:32:49 -07:00
@ observable
2021-09-17 18:14:08 -07:00
SortConfig txSortConfig = SortConfig ( " " , SortOrder . Unsorted ) ;
2021-07-12 04:32:49 -07:00
2021-08-09 07:13:42 -07:00
@ observable
int pnlSeriesIndex = 0 ;
2021-08-23 19:04:45 -07:00
@ observable
bool pnlDesc = false ;
2021-09-08 07:14:19 -07:00
Future < void > init ( Database db ) async {
this . db = db ;
2021-06-26 07:30:12 -07:00
await resetToDefaultAccount ( ) ;
}
Future < void > resetToDefaultAccount ( ) async {
await refresh ( ) ;
if ( accounts . isNotEmpty ) {
final prefs = await SharedPreferences . getInstance ( ) ;
final account = prefs . getInt ( ' account ' ) ? ? accounts [ 0 ] . id ;
setActiveAccountId ( account ) ;
}
}
refresh ( ) async {
accounts = await _list ( ) ;
}
@ action
Future < void > setActiveAccount ( Account account ) async {
final prefs = await SharedPreferences . getInstance ( ) ;
prefs . setInt ( ' account ' , account . id ) ;
2021-07-09 06:33:39 -07:00
final List < Map > res1 = await db . rawQuery (
" SELECT address FROM taddrs WHERE account = ?1 " , [ account . id ] ) ;
2021-08-07 00:32:10 -07:00
taddress = res1 . isNotEmpty ? res1 [ 0 ] [ ' address ' ] : " " ;
showTAddr = false ;
2021-07-09 06:33:39 -07:00
2021-06-26 07:30:12 -07:00
WarpApi . setMempoolAccount ( account . id ) ;
2021-07-09 06:33:39 -07:00
final List < Map > res2 = await db . rawQuery (
" SELECT sk FROM accounts WHERE id_account = ?1 " , [ account . id ] ) ;
canPay = res2 . isNotEmpty & & res2 [ 0 ] [ ' sk ' ] ! = null ;
active = account ;
2021-08-22 19:22:38 -07:00
await _fetchData ( account . id , true ) ;
2021-06-26 07:30:12 -07:00
}
@ action
2021-07-09 06:33:39 -07:00
Future < void > setActiveAccountId ( int idAccount ) async {
2021-06-26 07:30:12 -07:00
final account = accounts . firstWhere ( ( account ) = > account . id = = idAccount ,
2021-09-10 02:56:15 -07:00
orElse: ( ) = > accounts [ 0 ] ) ;
2021-07-09 06:33:39 -07:00
await setActiveAccount ( account ) ;
2021-06-26 07:30:12 -07:00
}
2021-07-07 21:22:54 -07:00
String newAddress ( ) {
return WarpApi . newAddress ( active . id ) ;
}
2021-09-11 18:16:53 -07:00
Future < Backup > getBackup ( int account ) async {
2021-07-07 08:40:05 -07:00
final List < Map > res = await db . rawQuery (
" SELECT seed, sk, ivk FROM accounts WHERE id_account = ?1 " ,
2021-09-11 18:16:53 -07:00
[ account ] ) ;
2021-09-10 02:56:15 -07:00
if ( res . isEmpty ) throw Exception ( " Account N/A " ) ;
2021-06-26 07:30:12 -07:00
final row = res [ 0 ] ;
2021-07-12 04:32:49 -07:00
final seed = row [ ' seed ' ] ;
final sk = row [ ' sk ' ] ;
final ivk = row [ ' ivk ' ] ;
2021-09-10 02:56:15 -07:00
int type = 0 ;
2021-08-16 06:07:16 -07:00
if ( seed ! = null )
type = 0 ;
else if ( sk ! = null )
type = 1 ;
2021-07-12 04:32:49 -07:00
else if ( ivk ! = null ) type = 2 ;
return Backup ( type , seed , sk , ivk ) ;
2021-06-26 07:30:12 -07:00
}
2021-07-09 06:33:39 -07:00
Future < int > _getBalance ( int accountId ) async {
2021-07-07 08:40:05 -07:00
final List < Map > res = await db . rawQuery (
" SELECT SUM(value) AS value FROM received_notes WHERE account = ?1 AND (spent IS NULL OR spent = 0) " ,
2021-07-09 06:33:39 -07:00
[ accountId ] ) ;
2021-06-26 07:30:12 -07:00
if ( res . isEmpty ) return 0 ;
return res [ 0 ] [ ' value ' ] ? ? 0 ;
}
Future < int > getBalanceSpendable ( int height ) async {
2021-07-07 08:40:05 -07:00
final List < Map > res = await db . rawQuery (
2021-08-14 01:02:30 -07:00
" SELECT SUM(value) AS value FROM received_notes WHERE account = ?1 AND spent IS NULL "
2021-08-16 06:07:16 -07:00
" AND height <= ?2 AND (excluded IS NULL OR NOT excluded) " ,
2021-07-07 08:40:05 -07:00
[ active . id , height ] ) ;
2021-06-26 07:30:12 -07:00
if ( res . isEmpty ) return 0 ;
return res [ 0 ] [ ' value ' ] ? ? 0 ;
}
@ action
Future < void > updateUnconfirmedBalance ( ) async {
unconfirmedBalance = await WarpApi . mempoolSync ( ) ;
}
isEmpty ( ) async {
2021-07-07 08:40:05 -07:00
final List < Map > res = await db . rawQuery ( " SELECT name FROM accounts " , [ ] ) ;
2021-06-26 07:30:12 -07:00
return res . isEmpty ;
}
Future < List < Account > > _list ( ) async {
final List < Map > res = await db . rawQuery (
2021-07-07 08:40:05 -07:00
" WITH notes AS (SELECT a.id_account, a.name, a.address, CASE WHEN r.spent IS NULL THEN r.value ELSE 0 END AS nv FROM accounts a LEFT JOIN received_notes r ON a.id_account = r.account) "
" SELECT id_account, name, address, COALESCE(sum(nv), 0) AS balance FROM notes GROUP by id_account " ,
[ ] ) ;
return res
. map ( ( r ) = >
Account ( r [ ' id_account ' ] , r [ ' name ' ] , r [ ' address ' ] , r [ ' balance ' ] ) )
. toList ( ) ;
2021-06-26 07:30:12 -07:00
}
@ action
Future < void > delete ( int account ) async {
2021-09-10 02:56:15 -07:00
WarpApi . deleteAccount ( account ) ;
if ( account = = active . id )
2021-09-05 21:41:32 -07:00
resetToDefaultAccount ( ) ;
2021-06-26 07:30:12 -07:00
}
2021-07-19 01:17:23 -07:00
@ action
Future < void > changeAccountName ( String name ) async {
2021-08-16 06:07:16 -07:00
await db . execute ( " UPDATE accounts SET name = ?2 WHERE id_account = ?1 " ,
[ active . id , name ] ) ;
2021-07-19 01:17:23 -07:00
await refresh ( ) ;
await setActiveAccountId ( active . id ) ;
}
2021-07-07 08:40:05 -07:00
@ action
Future < void > updateBalance ( ) async {
2021-07-09 06:33:39 -07:00
balance = await _getBalance ( active . id ) ;
2021-07-07 08:40:05 -07:00
}
@ action
2021-09-05 06:06:54 -07:00
Future < void > fetchAccountData ( bool force ) async {
await _fetchData ( active . id , force ) ;
2021-07-12 04:32:49 -07:00
}
2021-08-07 00:32:10 -07:00
@ action
void toggleShowTAddr ( ) {
showTAddr = ! showTAddr ;
}
2021-08-22 19:22:38 -07:00
Future < void > _fetchData ( int accountId , bool force ) async {
2021-08-16 06:07:16 -07:00
await _updateBalance ( accountId ) ;
2021-08-23 08:44:41 -07:00
2021-08-22 19:22:38 -07:00
final hasNewTx = await _fetchNotesAndHistory ( accountId , force ) ;
2021-08-09 07:13:42 -07:00
int countNewPrices = await WarpApi . syncHistoricalPrices ( settings . currency ) ;
2021-08-16 06:07:16 -07:00
if ( hasNewTx ) {
await _fetchSpending ( accountId ) ;
await _fetchAccountBalanceTimeSeries ( accountId ) ;
}
if ( countNewPrices > 0 | | pnls . isEmpty | | hasNewTx )
await _fetchPNL ( accountId ) ;
2021-07-09 06:33:39 -07:00
}
2021-07-10 22:20:53 -07:00
final DateFormat noteDateFormat = DateFormat ( " yy-MM-dd HH:mm " ) ;
final DateFormat txDateFormat = DateFormat ( " MM-dd HH:mm " ) ;
2021-07-09 06:33:39 -07:00
Future < void > _updateBalance ( int accountId ) async {
2021-08-16 06:07:16 -07:00
final _balance = await _getBalance ( accountId ) ;
if ( _balance = = balance ) return ;
balance = _balance ;
dataEpoch = DateTime . now ( ) . millisecondsSinceEpoch ;
2021-07-09 06:33:39 -07:00
}
2021-08-22 19:22:38 -07:00
Future < bool > _fetchNotesAndHistory ( int accountId , bool force ) async {
2021-08-16 06:07:16 -07:00
final List < Map > res0 = await db . rawQuery (
" SELECT MAX(height) as height FROM transactions WHERE account = ?1 " ,
[ accountId ] ) ;
if ( res0 . isEmpty ) return false ;
final _lastTxHeight = res0 [ 0 ] [ ' height ' ] ? ? 0 ;
2021-08-22 19:22:38 -07:00
if ( ! force & & lastTxHeight = = _lastTxHeight ) return false ;
2021-08-16 06:07:16 -07:00
lastTxHeight = _lastTxHeight ;
2021-06-26 07:30:12 -07:00
final List < Map > res = await db . rawQuery (
2021-08-14 01:02:30 -07:00
" SELECT n.id_note, n.height, n.value, t.timestamp, n.excluded, n.spent FROM received_notes n, transactions t "
2021-08-16 06:07:16 -07:00
" WHERE n.account = ?1 AND (n.spent IS NULL OR n.spent = 0) "
2021-09-17 18:14:08 -07:00
" AND n.tx = t.id_tx ORDER BY n.height DESC " ,
2021-07-09 06:33:39 -07:00
[ accountId ] ) ;
2021-06-26 07:30:12 -07:00
notes = res . map ( ( row ) {
2021-07-12 04:32:49 -07:00
final id = row [ ' id_note ' ] ;
2021-06-26 07:30:12 -07:00
final height = row [ ' height ' ] ;
2021-07-10 22:20:53 -07:00
final timestamp = noteDateFormat
2021-07-07 08:40:05 -07:00
. format ( DateTime . fromMillisecondsSinceEpoch ( row [ ' timestamp ' ] * 1000 ) ) ;
2021-07-12 04:32:49 -07:00
final excluded = ( row [ ' excluded ' ] ? ? 0 ) ! = 0 ;
2021-08-14 01:02:30 -07:00
final spent = row [ ' spent ' ] = = 0 ;
2021-08-16 06:07:16 -07:00
return Note (
id , height , timestamp , row [ ' value ' ] / ZECUNIT , excluded , spent ) ;
2021-06-26 07:30:12 -07:00
} ) . toList ( ) ;
final List < Map > res2 = await db . rawQuery (
2021-09-11 18:16:53 -07:00
" SELECT id_tx, txid, height, timestamp, t.address, c.name, value, memo FROM transactions t "
2021-09-15 21:48:34 -07:00
" LEFT JOIN contacts c ON t.address = c.address WHERE account = ?1 ORDER BY height DESC " ,
2021-07-09 06:33:39 -07:00
[ accountId ] ) ;
2021-06-26 07:30:12 -07:00
txs = res2 . map ( ( row ) {
2021-07-10 22:20:53 -07:00
Uint8List txid = row [ ' txid ' ] ;
final fullTxId = hex . encode ( txid . reversed . toList ( ) ) ;
final shortTxid = fullTxId . substring ( 0 , 8 ) ;
final timestamp = txDateFormat
2021-07-07 08:40:05 -07:00
. format ( DateTime . fromMillisecondsSinceEpoch ( row [ ' timestamp ' ] * 1000 ) ) ;
2021-08-16 06:07:16 -07:00
return Tx ( row [ ' id_tx ' ] , row [ ' height ' ] , timestamp , shortTxid , fullTxId ,
2021-09-12 02:05:17 -07:00
row [ ' value ' ] / ZECUNIT , row [ ' address ' ] ? ? " " , row [ ' name ' ] , row [ ' memo ' ] ? ? " " ) ;
2021-06-26 07:30:12 -07:00
} ) . toList ( ) ;
2021-08-16 06:07:16 -07:00
dataEpoch = DateTime . now ( ) . millisecondsSinceEpoch ;
return true ;
}
@ computed
List < Note > get sortedNotes {
var notes2 = [ . . . notes ] ;
2021-09-17 18:14:08 -07:00
switch ( noteSortConfig . field ) {
case " time " : return _sort ( notes2 , ( Note note ) = > note . height , noteSortConfig . order ) ;
case " amount " : return _sort ( notes2 , ( Note note ) = > note . value , noteSortConfig . order ) ;
}
return notes2 ;
2021-07-12 04:32:49 -07:00
}
@ action
2021-09-17 18:14:08 -07:00
Future < void > sortNotes ( String field ) async {
noteSortConfig . sortOn ( field ) ;
2021-07-12 04:32:49 -07:00
}
2021-08-16 06:07:16 -07:00
List < Note > _sortNoteAmount ( List < Note > notes , SortOrder order ) {
2021-07-12 04:32:49 -07:00
switch ( order ) {
case SortOrder . Ascending:
notes . sort ( ( a , b ) = > a . value . compareTo ( b . value ) ) ;
break ;
case SortOrder . Descending:
notes . sort ( ( a , b ) = > - a . value . compareTo ( b . value ) ) ;
break ;
case SortOrder . Unsorted:
2021-07-30 02:45:43 -07:00
notes . sort ( ( a , b ) = > - a . height . compareTo ( b . height ) ) ;
2021-07-12 04:32:49 -07:00
break ;
}
2021-08-16 06:07:16 -07:00
return notes ;
}
@ computed
List < Tx > get sortedTxs {
var txs2 = [ . . . txs ] ;
2021-09-17 18:14:08 -07:00
switch ( txSortConfig . field ) {
case " time " : return _sort ( txs2 , ( Tx tx ) = > tx . height , txSortConfig . order ) ;
case " amount " : return _sort ( txs2 , ( Tx tx ) = > tx . value , txSortConfig . order ) ;
case " txid " : return _sort ( txs2 , ( Tx tx ) = > tx . txid , txSortConfig . order ) ;
case " address " : return _sort ( txs2 , ( Tx tx ) = > tx . contact ? ? tx . address , txSortConfig . order ) ;
case " memo " : return _sort ( txs2 , ( Tx tx ) = > tx . memo , txSortConfig . order ) ;
2021-09-15 19:40:11 -07:00
}
return txs2 ;
2021-07-12 04:32:49 -07:00
}
@ action
2021-09-15 19:40:11 -07:00
Future < void > sortTx ( String field ) async {
2021-09-17 18:14:08 -07:00
txSortConfig . sortOn ( field ) ;
2021-07-12 04:32:49 -07:00
}
2021-09-17 18:14:08 -07:00
List < C > _sort < C extends HasHeight , T extends Comparable > ( List < C > txs , T Function ( C ) project , SortOrder order ) {
2021-07-12 04:32:49 -07:00
switch ( order ) {
case SortOrder . Ascending:
2021-09-15 19:40:11 -07:00
txs . sort ( ( a , b ) = > project ( a ) . compareTo ( project ( b ) ) ) ;
2021-07-12 04:32:49 -07:00
break ;
case SortOrder . Descending:
2021-09-15 19:40:11 -07:00
txs . sort ( ( a , b ) = > - project ( a ) . compareTo ( project ( b ) ) ) ;
2021-07-12 04:32:49 -07:00
break ;
case SortOrder . Unsorted:
2021-07-30 02:45:43 -07:00
txs . sort ( ( a , b ) = > - a . height . compareTo ( b . height ) ) ;
2021-07-12 04:32:49 -07:00
break ;
}
2021-08-16 06:07:16 -07:00
return txs ;
2021-07-12 04:32:49 -07:00
}
2021-09-11 19:27:31 -07:00
TimeRange getChartRange ( ) {
2021-09-14 17:29:41 -07:00
final now = DateTime . now ( ) . toUtc ( ) ;
2021-09-11 19:27:31 -07:00
final today = DateTime . utc ( now . year , now . month , now . day ) ;
final start = today . add ( Duration ( days: - chartRangeInt ( ) ) ) ;
final cutoff = start . millisecondsSinceEpoch ;
return TimeRange ( cutoff , today . millisecondsSinceEpoch ) ;
}
2021-07-12 04:32:49 -07:00
Future < void > _fetchSpending ( int accountId ) async {
2021-09-11 19:27:31 -07:00
final range = getChartRange ( ) ;
2021-07-12 04:32:49 -07:00
final List < Map > res = await db . rawQuery (
2021-09-11 18:16:53 -07:00
" SELECT SUM(value) as v, t.address, c.name FROM transactions t LEFT JOIN contacts c ON t.address = c.address "
" WHERE account = ?1 AND timestamp >= ?2 AND value < 0 GROUP BY t.address ORDER BY v ASC LIMIT 5 " ,
2021-09-11 19:27:31 -07:00
[ accountId , range . start ~ / 1000 ] ) ;
2021-07-12 04:32:49 -07:00
spendings = res . map ( ( row ) {
final address = row [ ' address ' ] ? ? " " ;
final value = - row [ ' v ' ] / ZECUNIT ;
2021-09-11 18:16:53 -07:00
final contact = row [ ' name ' ] ;
return Spending ( address , value , contact ) ;
2021-07-12 04:32:49 -07:00
} ) . toList ( ) ;
}
Future < void > _fetchAccountBalanceTimeSeries ( int accountId ) async {
2021-09-11 19:27:31 -07:00
final range = getChartRange ( ) ;
2021-07-12 04:32:49 -07:00
final List < Map > res = await db . rawQuery (
" SELECT timestamp, value FROM transactions WHERE account = ?1 AND timestamp >= ?2 ORDER BY timestamp DESC " ,
2021-09-11 19:27:31 -07:00
[ accountId , range . start ~ / 1000 ] ) ;
2021-08-16 06:07:16 -07:00
List < AccountBalance > _accountBalances = [ ] ;
2021-07-12 04:32:49 -07:00
var b = balance ;
2021-08-16 06:07:16 -07:00
_accountBalances . add ( AccountBalance ( DateTime . now ( ) , b / ZECUNIT ) ) ;
2021-07-12 04:32:49 -07:00
for ( var row in res ) {
2021-08-16 06:07:16 -07:00
final timestamp =
DateTime . fromMillisecondsSinceEpoch ( row [ ' timestamp ' ] * 1000 ) ;
2021-09-10 02:56:15 -07:00
final value = row [ ' value ' ] as int ;
2021-07-12 04:32:49 -07:00
final ab = AccountBalance ( timestamp , b / ZECUNIT ) ;
2021-08-16 06:07:16 -07:00
_accountBalances . add ( ab ) ;
2021-07-12 04:32:49 -07:00
b - = value ;
}
2021-09-11 19:27:31 -07:00
_accountBalances . add ( AccountBalance ( DateTime . fromMillisecondsSinceEpoch ( range . start ) , b / ZECUNIT ) ) ;
2021-08-16 06:07:16 -07:00
_accountBalances = _accountBalances . reversed . toList ( ) ;
accountBalances = sampleDaily < AccountBalance , double , double > (
_accountBalances ,
2021-09-11 19:27:31 -07:00
range . start ,
range . end ,
2021-08-16 06:07:16 -07:00
( AccountBalance ab ) = > ab . time . millisecondsSinceEpoch ~ / DAY_MS ,
( AccountBalance ab ) = > ab . balance ,
( acc , v ) = > v ,
0.0 ) ;
2021-06-26 07:30:12 -07:00
}
2021-07-07 08:40:05 -07:00
2021-08-16 06:07:16 -07:00
@ action
2021-09-11 19:27:31 -07:00
Future < void > fetchChartData ( ) async {
2021-08-16 06:07:16 -07:00
await _fetchPNL ( active . id ) ;
2021-09-11 19:27:31 -07:00
await _fetchSpending ( active . id ) ;
await _fetchAccountBalanceTimeSeries ( active . id ) ;
2021-08-16 06:07:16 -07:00
}
2021-09-11 19:27:31 -07:00
int chartRangeInt ( ) {
2021-08-16 06:07:16 -07:00
switch ( settings . chartRange ) {
2021-09-11 19:27:31 -07:00
case ' 1M ' :
return 30 ;
case ' 3M ' :
return 90 ;
case ' 6M ' :
return 180 ;
2021-08-16 06:07:16 -07:00
}
2021-09-11 19:27:31 -07:00
return 365 ;
}
Future < void > _fetchPNL ( int accountId ) async {
final range = getChartRange ( ) ;
2021-08-16 06:07:16 -07:00
final List < Map > res1 = await db . rawQuery (
" SELECT timestamp, value FROM transactions WHERE timestamp >= ?2 AND account = ?1 " ,
2021-09-11 19:27:31 -07:00
[ accountId , range . start ~ / 1000 ] ) ;
2021-08-16 06:07:16 -07:00
final List < Trade > trades = [ ] ;
for ( var row in res1 ) {
final dt = DateTime . fromMillisecondsSinceEpoch ( row [ ' timestamp ' ] * 1000 ) ;
final qty = row [ ' value ' ] / ZECUNIT ;
trades . add ( Trade ( dt , qty ) ) ;
}
final portfolioTimeSeries = sampleDaily < Trade , Trade , double > (
trades ,
2021-09-11 19:27:31 -07:00
range . start ,
range . end ,
2021-08-16 06:07:16 -07:00
( t ) = > t . dt . millisecondsSinceEpoch ~ / DAY_MS ,
( t ) = > t ,
( acc , t ) = > acc + t . qty ,
0.0 ) ;
final List < Map > res2 = await db . rawQuery (
" SELECT timestamp, price FROM historical_prices WHERE timestamp >= ?2 AND currency = ?1 " ,
2021-09-11 19:27:31 -07:00
[ settings . currency , range . start ~ / 1000 ] ) ;
2021-08-16 06:07:16 -07:00
final List < Quote > quotes = [ ] ;
for ( var row in res2 ) {
final dt = DateTime . fromMillisecondsSinceEpoch ( row [ ' timestamp ' ] * 1000 ) ;
final price = row [ ' price ' ] ;
quotes . add ( Quote ( dt , price ) ) ;
}
var prevBalance = 0.0 ;
2021-08-09 07:13:42 -07:00
var cash = 0.0 ;
var realized = 0.0 ;
2021-08-16 06:07:16 -07:00
final List < PnL > _pnls = [ ] ;
2021-08-21 05:41:05 -07:00
final len = math . min ( quotes . length , portfolioTimeSeries . length ) ;
for ( var i = 0 ; i < len ; i + + ) {
2021-08-16 06:07:16 -07:00
final dt = quotes [ i ] . dt ;
final price = quotes [ i ] . price ;
final balance = portfolioTimeSeries [ i ] . value ;
final qty = balance - prevBalance ;
final closeQty = qty * balance < 0
? math . min ( qty . abs ( ) , prevBalance . abs ( ) ) * qty . sign
: 0.0 ;
final openQty = qty - closeQty ;
final avgPrice = prevBalance ! = 0 ? cash / prevBalance : 0.0 ;
2021-08-09 07:13:42 -07:00
cash + = openQty * price + closeQty * avgPrice ;
2021-08-16 06:07:16 -07:00
realized + = closeQty * ( avgPrice - price ) ;
2021-08-09 07:13:42 -07:00
final unrealized = price * balance - cash ;
2021-08-16 06:07:16 -07:00
final pnl = PnL ( dt , price , balance , realized , unrealized ) ;
_pnls . add ( pnl ) ;
2021-08-09 07:13:42 -07:00
2021-08-16 06:07:16 -07:00
prevBalance = balance ;
}
2021-08-09 07:13:42 -07:00
pnls = _pnls ;
}
2021-08-23 19:04:45 -07:00
@ action
void togglePnlDesc ( ) {
pnlDesc = ! pnlDesc ;
}
@ computed
List < PnL > get pnlSorted {
if ( pnlDesc ) {
var _pnls = [ . . . pnls . reversed ] ;
return _pnls ;
}
return pnls ;
}
2021-07-07 08:40:05 -07:00
@ action
Future < void > convertToWatchOnly ( ) async {
2021-07-09 06:33:39 -07:00
await db . rawUpdate (
" UPDATE accounts SET seed = NULL, sk = NULL WHERE id_account = ?1 " ,
[ active . id ] ) ;
2021-07-07 08:40:05 -07:00
canPay = false ;
}
2021-07-09 06:33:39 -07:00
2021-07-12 04:32:49 -07:00
@ action
Future < void > excludeNote ( Note note ) async {
await db . execute (
" UPDATE received_notes SET excluded = ?2 WHERE id_note = ?1 " ,
[ note . id , note . excluded ] ) ;
}
2021-07-09 06:33:39 -07:00
void updateTBalance ( ) {
int balance = WarpApi . getTBalance ( active . id ) ;
if ( balance ! = tbalance ) tbalance = balance ;
2021-08-23 19:04:45 -07:00
if ( settings . autoShieldThreshold ! = 0.0 & & tbalance / ZECUNIT > = settings . autoShieldThreshold ) {
2021-08-23 08:44:41 -07:00
WarpApi . shieldTAddr ( active . id ) ;
}
2021-07-09 06:33:39 -07:00
}
2021-07-18 08:59:02 -07:00
2021-08-09 07:13:42 -07:00
@ action
void setPnlSeriesIndex ( int index ) {
pnlSeriesIndex = index ;
}
2021-09-16 18:13:58 -07:00
Future < Map < int , int > > getAllTBalances ( ) async {
final Map < int , int > balances = { } ;
for ( var a in accounts ) {
final b = await WarpApi . getTBalanceAsync ( a . id ) ;
balances [ a . id ] = b ;
}
return balances ;
}
2021-06-26 07:30:12 -07:00
}
class Account {
final int id ;
final String name ;
final String address ;
final int balance ;
Account ( this . id , this . name , this . address , this . balance ) ;
}
class PriceStore = _PriceStore with _ $PriceStore ;
abstract class _PriceStore with Store {
@ observable
double zecPrice = 0.0 ;
2021-07-07 08:40:05 -07:00
2021-06-26 07:30:12 -07:00
@ action
Future < void > fetchZecPrice ( ) async {
2021-08-07 00:32:10 -07:00
final base = " api.coingecko.com " ;
2021-08-16 06:07:16 -07:00
final uri = Uri . https ( base , ' /api/v3/simple/price ' ,
{ ' ids ' : coin . currency , ' vs_currencies ' : settings . currency } ) ;
2021-06-26 07:30:12 -07:00
final rep = await http . get ( uri ) ;
if ( rep . statusCode = = 200 ) {
final json = convert . jsonDecode ( rep . body ) as Map < String , dynamic > ;
2021-08-13 20:44:53 -07:00
final p = json [ coin . currency ] [ settings . currency . toLowerCase ( ) ] ;
2021-08-09 07:13:42 -07:00
zecPrice = ( p is double ) ? p : ( p as int ) . toDouble ( ) ;
2021-08-16 06:07:16 -07:00
} else
zecPrice = 0.0 ;
2021-06-26 07:30:12 -07:00
}
}
class SyncStatus = _SyncStatus with _ $SyncStatus ;
abstract class _SyncStatus with Store {
2021-09-10 02:56:15 -07:00
late Database _db ;
2021-06-26 07:30:12 -07:00
init ( ) async {
var databasesPath = await getDatabasesPath ( ) ;
final path = join ( databasesPath , ' zec.db ' ) ;
_db = await openDatabase ( path ) ;
await update ( ) ;
}
2021-09-05 21:41:32 -07:00
@ observable
bool accountRestored = false ;
@ observable
bool syncing = false ;
2021-06-26 07:30:12 -07:00
@ observable
int syncedHeight = - 1 ;
@ observable
int latestHeight = 0 ;
bool isSynced ( ) {
return syncedHeight < 0 | | syncedHeight = = latestHeight ;
}
@ action
setSyncHeight ( int height ) {
2021-07-07 08:40:05 -07:00
syncedHeight = height ;
2021-06-26 07:30:12 -07:00
}
@ action
Future < bool > update ( ) async {
2021-07-19 01:17:23 -07:00
latestHeight = await WarpApi . getLatestHeight ( ) ;
2021-06-26 07:30:12 -07:00
final _syncedHeight = Sqflite . firstIntValue (
2021-07-07 08:40:05 -07:00
await _db . rawQuery ( " SELECT MAX(height) FROM blocks " ) ) ? ?
2021-06-26 07:30:12 -07:00
0 ;
if ( _syncedHeight > 0 ) syncedHeight = _syncedHeight ;
return syncedHeight = = latestHeight ;
}
2021-09-05 21:41:32 -07:00
@ action
Future < void > sync ( BuildContext context ) async {
2021-09-09 08:23:10 -07:00
eta . reset ( ) ;
2021-09-05 21:41:32 -07:00
syncing = true ;
final snackBar =
SnackBar ( content: Text ( S
. of ( context )
. rescanRequested ) ) ;
2021-09-10 02:56:15 -07:00
rootScaffoldMessengerKey . currentState ? . showSnackBar ( snackBar ) ;
2021-09-05 21:41:32 -07:00
syncStatus . setSyncHeight ( 0 ) ;
WarpApi . rewindToHeight ( 0 ) ;
2021-09-08 07:14:19 -07:00
WarpApi . truncateData ( ) ;
contacts . markContactsDirty ( false ) ;
2021-09-05 21:41:32 -07:00
await syncStatus . update ( ) ;
final params = SyncParams ( settings . getTx , settings . anchorOffset , syncPort . sendPort ) ;
await compute ( WarpApi . warpSync , params ) ;
syncing = false ;
2021-09-09 08:23:10 -07:00
eta . reset ( ) ;
2021-09-05 21:41:32 -07:00
}
@ action
void setAccountRestored ( bool v ) {
accountRestored = v ;
}
2021-06-26 07:30:12 -07:00
}
2021-08-02 22:58:02 -07:00
class MultiPayStore = _MultiPayStore with _ $MultiPayStore ;
abstract class _MultiPayStore with Store {
@ observable
ObservableList < Recipient > recipients = ObservableList . of ( [ ] ) ;
@ action
void addRecipient ( Recipient recipient ) {
recipients . add ( recipient ) ;
}
@ action
void removeRecipient ( int index ) {
recipients . removeAt ( index ) ;
}
@ action
void clear ( ) {
recipients . clear ( ) ;
}
}
2021-09-05 06:06:54 -07:00
class ETAStore = _ETAStore with _ $ETAStore ;
abstract class _ETAStore with Store {
@ observable
2021-09-10 02:56:15 -07:00
ETACheckpoint ? prev ;
2021-09-05 06:06:54 -07:00
@ observable
2021-09-10 02:56:15 -07:00
ETACheckpoint ? current ;
2021-09-05 06:06:54 -07:00
2021-09-09 08:23:10 -07:00
@ action
void reset ( ) {
prev = null ;
current = null ;
}
2021-09-05 06:06:54 -07:00
@ action
void checkpoint ( int height , DateTime timestamp ) {
prev = current ;
current = ETACheckpoint ( height , timestamp ) ;
}
@ computed
String get eta {
2021-09-10 02:56:15 -07:00
final p = prev ;
final c = current ;
if ( p = = null | | c = = null ) return " " ;
if ( c . timestamp . millisecondsSinceEpoch = = p . timestamp . millisecondsSinceEpoch ) return " " ;
final speed = ( c . height - p . height ) / ( c . timestamp . millisecondsSinceEpoch - p . timestamp . millisecondsSinceEpoch ) ;
2021-09-05 06:06:54 -07:00
if ( speed = = 0 ) return " " ;
2021-09-10 02:56:15 -07:00
final eta = ( syncStatus . latestHeight - c . height ) / speed ;
2021-09-05 06:06:54 -07:00
if ( eta < = 0 ) return " " ;
final duration = Duration ( milliseconds: eta . floor ( ) ) . toString ( ) . split ( ' . ' ) [ 0 ] ;
return " (ETA: $ duration ) " ;
}
}
2021-09-08 07:14:19 -07:00
class ContactStore = _ContactStore with _ $ContactStore ;
abstract class _ContactStore with Store {
2021-09-10 02:56:15 -07:00
late Database db ;
2021-09-08 07:14:19 -07:00
@ observable
bool dirty = false ;
@ observable
ObservableList < Contact > contacts = ObservableList < Contact > . of ( [ ] ) ;
2021-09-10 02:56:15 -07:00
Future < void > init ( Database db ) async {
2021-09-08 07:14:19 -07:00
this . db = db ;
final prefs = await SharedPreferences . getInstance ( ) ;
dirty = prefs . getBool ( ' contacts_dirty ' ) ? ? false ;
}
@ action
Future < void > fetchContacts ( ) async {
await _fetchContacts ( ) ;
}
Future < void > _fetchContacts ( ) async {
List < Map > res = await db . rawQuery (
" SELECT id, name, address FROM contacts WHERE address <> '' ORDER BY name " ) ;
contacts . clear ( ) ;
for ( var c in res ) {
final contact = Contact ( c [ ' id ' ] , c [ ' name ' ] , c [ ' address ' ] ) ;
contacts . add ( contact ) ;
}
}
@ action
Future < void > markContactsDirty ( bool v ) async {
final prefs = await SharedPreferences . getInstance ( ) ;
dirty = v ;
prefs . setBool ( ' contacts_dirty ' , dirty ) ;
}
@ action
Future < void > add ( Contact c ) async {
WarpApi . storeContact ( c . id , c . name , c . address , true ) ;
await markContactsDirty ( true ) ;
await _fetchContacts ( ) ;
}
@ action
Future < void > remove ( Contact c ) async {
contacts . removeWhere ( ( contact ) = > contact . id = = c . id ) ;
WarpApi . storeContact ( c . id , c . name , " " , true ) ;
await markContactsDirty ( true ) ;
await _fetchContacts ( ) ;
}
}
2021-09-05 06:06:54 -07:00
class ETACheckpoint {
int height ;
DateTime timestamp ;
ETACheckpoint ( this . height , this . timestamp ) ;
}
2021-07-07 08:40:05 -07:00
var progressPort = ReceivePort ( ) ;
var progressStream = progressPort . asBroadcastStream ( ) ;
2021-08-31 08:02:56 -07:00
var syncPort = ReceivePort ( ) ;
var syncStream = syncPort . asBroadcastStream ( ) ;
2021-09-17 18:14:08 -07:00
abstract class HasHeight {
int height = 0 ;
}
class Note extends HasHeight {
2021-07-12 04:32:49 -07:00
int id ;
2021-06-26 07:30:12 -07:00
int height ;
String timestamp ;
double value ;
2021-07-12 04:32:49 -07:00
bool excluded ;
2021-08-14 01:02:30 -07:00
bool spent ;
2021-06-26 07:30:12 -07:00
2021-08-16 06:07:16 -07:00
Note ( this . id , this . height , this . timestamp , this . value , this . excluded ,
this . spent ) ;
2021-06-26 07:30:12 -07:00
}
2021-09-17 18:14:08 -07:00
class Tx extends HasHeight {
2021-07-09 22:44:34 -07:00
int id ;
2021-06-26 07:30:12 -07:00
int height ;
String timestamp ;
String txid ;
2021-07-09 22:44:34 -07:00
String fullTxId ;
2021-06-26 07:30:12 -07:00
double value ;
2021-07-09 22:44:34 -07:00
String address ;
2021-09-11 18:16:53 -07:00
String ? contact ;
2021-07-09 22:44:34 -07:00
String memo ;
2021-06-26 07:30:12 -07:00
2021-08-16 06:07:16 -07:00
Tx ( this . id , this . height , this . timestamp , this . txid , this . fullTxId , this . value ,
2021-09-11 18:16:53 -07:00
this . address , this . contact , this . memo ) ;
2021-06-26 07:30:12 -07:00
}
2021-07-12 04:32:49 -07:00
class Spending {
final String address ;
final double amount ;
2021-09-11 18:16:53 -07:00
final String ? contact ;
2021-07-12 04:32:49 -07:00
2021-09-11 18:16:53 -07:00
Spending ( this . address , this . amount , this . contact ) ;
2021-07-12 04:32:49 -07:00
}
class AccountBalance {
final DateTime time ;
final double balance ;
AccountBalance ( this . time , this . balance ) ;
}
class Backup {
int type ;
2021-09-10 23:05:48 -07:00
final String ? seed ;
final String ? sk ;
2021-07-12 04:32:49 -07:00
final String ivk ;
Backup ( this . type , this . seed , this . sk , this . ivk ) ;
String value ( ) {
switch ( type ) {
2021-08-16 06:07:16 -07:00
case 0 :
2021-09-10 23:05:48 -07:00
return seed ! ;
2021-08-16 06:07:16 -07:00
case 1 :
2021-09-10 23:05:48 -07:00
return sk ! ;
2021-08-16 06:07:16 -07:00
case 2 :
return ivk ;
2021-07-12 04:32:49 -07:00
}
return " " ;
}
}
2021-07-18 08:59:02 -07:00
class Contact {
2021-09-08 07:14:19 -07:00
final int id ;
2021-07-18 08:59:02 -07:00
final String name ;
final String address ;
2021-09-08 07:14:19 -07:00
Contact ( this . id , this . name , this . address ) ;
factory Contact . empty ( ) = > Contact ( 0 , " " , " " ) ;
2021-07-18 08:59:02 -07:00
}
2021-07-12 04:32:49 -07:00
enum SortOrder {
Unsorted ,
Ascending ,
Descending ,
}
2021-08-16 06:07:16 -07:00
SortOrder nextSortOrder ( SortOrder order ) = >
SortOrder . values [ ( order . index + 1 ) % 3 ] ;
2021-07-18 08:59:02 -07:00
2021-08-02 22:58:02 -07:00
@ JsonSerializable ( )
class Recipient {
final String address ;
final int amount ;
final String memo ;
Recipient ( this . address , this . amount , this . memo ) ;
2021-08-16 06:07:16 -07:00
factory Recipient . fromJson ( Map < String , dynamic > json ) = >
_ $RecipientFromJson ( json ) ;
2021-08-02 22:58:02 -07:00
Map < String , dynamic > toJson ( ) = > _ $RecipientToJson ( this ) ;
}
2021-08-09 07:13:42 -07:00
class PnL {
final DateTime timestamp ;
final double price ;
final double amount ;
final double realized ;
final double unrealized ;
PnL ( this . timestamp , this . price , this . amount , this . realized , this . unrealized ) ;
2021-08-16 06:07:16 -07:00
@ override
String toString ( ) {
return " $ timestamp $ price $ amount $ realized $ unrealized " ;
}
}
class TimeSeriesPoint < V > {
final int day ;
final V value ;
TimeSeriesPoint ( this . day , this . value ) ;
}
class Trade {
final DateTime dt ;
final qty ;
Trade ( this . dt , this . qty ) ;
}
class Portfolio {
final DateTime dt ;
final qty ;
Portfolio ( this . dt , this . qty ) ;
}
class Quote {
final DateTime dt ;
final price ;
Quote ( this . dt , this . price ) ;
2021-08-09 07:13:42 -07:00
}
2021-09-11 19:27:31 -07:00
class TimeRange {
final int start ;
final int end ;
TimeRange ( this . start , this . end ) ;
2021-09-15 19:40:11 -07:00
}
2021-09-17 18:14:08 -07:00
class SortConfig {
2021-09-15 19:40:11 -07:00
@ observable
2021-09-17 18:14:08 -07:00
String field ;
2021-09-15 19:40:11 -07:00
@ observable
2021-09-17 18:14:08 -07:00
SortOrder order ;
2021-09-15 19:40:11 -07:00
2021-09-17 18:14:08 -07:00
SortConfig ( this . field , this . order ) ;
2021-09-15 19:40:11 -07:00
2021-09-17 18:14:08 -07:00
@ action
void sortOn ( String field ) {
if ( field ! = this . field )
order = SortOrder . Ascending ;
else
order = nextSortOrder ( order ) ;
this . field = field ;
}
String getIndicator ( String field ) {
if ( this . field ! = field ) return ' ' ;
if ( order = = SortOrder . Ascending )
return ' \u2191 ' ;
if ( order = = SortOrder . Descending )
return ' \u2193 ' ;
return ' ' ;
}
}