first alpha
43
.gitignore
vendored
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
migrate_working_dir/
|
||||||
|
|
||||||
|
# IntelliJ related
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
|
# is commented out by default.
|
||||||
|
#.vscode/
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
**/doc/api/
|
||||||
|
**/ios/Flutter/.last_build_id
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
.pub-cache/
|
||||||
|
.pub/
|
||||||
|
/build/
|
||||||
|
|
||||||
|
# Symbolication related
|
||||||
|
app.*.symbols
|
||||||
|
|
||||||
|
# Obfuscation related
|
||||||
|
app.*.map.json
|
||||||
|
|
||||||
|
# Android Studio will place build artifacts here
|
||||||
|
/android/app/debug
|
||||||
|
/android/app/profile
|
||||||
|
/android/app/release
|
30
.metadata
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# This file tracks properties of this Flutter project.
|
||||||
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
|
#
|
||||||
|
# This file should be version controlled and should not be manually edited.
|
||||||
|
|
||||||
|
version:
|
||||||
|
revision: "761747bfc538b5af34aa0d3fac380f1bc331ec49"
|
||||||
|
channel: "stable"
|
||||||
|
|
||||||
|
project_type: app
|
||||||
|
|
||||||
|
# Tracks metadata for the flutter migrate command
|
||||||
|
migration:
|
||||||
|
platforms:
|
||||||
|
- platform: root
|
||||||
|
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||||
|
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||||
|
- platform: android
|
||||||
|
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||||
|
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||||
|
|
||||||
|
# User provided section
|
||||||
|
|
||||||
|
# List of Local paths (relative to this file) that should be
|
||||||
|
# ignored by the migrate tool.
|
||||||
|
#
|
||||||
|
# Files that are not part of the templates will be ignored by default.
|
||||||
|
unmanaged_files:
|
||||||
|
- 'lib/main.dart'
|
||||||
|
- 'ios/Runner.xcodeproj/project.pbxproj'
|
619
LICENSE
Normal file
|
@ -0,0 +1,619 @@
|
||||||
|
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 19 November 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU Affero General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works, specifically designed to ensure
|
||||||
|
cooperation with the community in the case of network server software.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
our General Public Licenses are intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
Developers that use our General Public Licenses protect your rights
|
||||||
|
with two steps: (1) assert copyright on the software, and (2) offer
|
||||||
|
you this License which gives you legal permission to copy, distribute
|
||||||
|
and/or modify the software.
|
||||||
|
|
||||||
|
A secondary benefit of defending all users' freedom is that
|
||||||
|
improvements made in alternate versions of the program, if they
|
||||||
|
receive widespread use, become available for other developers to
|
||||||
|
incorporate. Many developers of free software are heartened and
|
||||||
|
encouraged by the resulting cooperation. However, in the case of
|
||||||
|
software used on network servers, this result may fail to come about.
|
||||||
|
The GNU General Public License permits making a modified version and
|
||||||
|
letting the public access it on a server without ever releasing its
|
||||||
|
source code to the public.
|
||||||
|
|
||||||
|
The GNU Affero General Public License is designed specifically to
|
||||||
|
ensure that, in such cases, the modified source code becomes available
|
||||||
|
to the community. It requires the operator of a network server to
|
||||||
|
provide the source code of the modified version running there to the
|
||||||
|
users of that server. Therefore, public use of a modified version, on
|
||||||
|
a publicly accessible server, gives the public access to the source
|
||||||
|
code of the modified version.
|
||||||
|
|
||||||
|
An older license, called the Affero General Public License and
|
||||||
|
published by Affero, was designed to accomplish similar goals. This is
|
||||||
|
a different license, not a version of the Affero GPL, but Affero has
|
||||||
|
released a new version of the Affero GPL which permits relicensing under
|
||||||
|
this license.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, if you modify the
|
||||||
|
Program, your modified version must prominently offer all users
|
||||||
|
interacting with it remotely through a computer network (if your version
|
||||||
|
supports such interaction) an opportunity to receive the Corresponding
|
||||||
|
Source of your version by providing access to the Corresponding Source
|
||||||
|
from a network server at no charge, through some standard or customary
|
||||||
|
means of facilitating copying of software. This Corresponding Source
|
||||||
|
shall include the Corresponding Source for any work covered by version 3
|
||||||
|
of the GNU General Public License that is incorporated pursuant to the
|
||||||
|
following paragraph.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the work with which it is combined will remain governed by version
|
||||||
|
3 of the GNU General Public License.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU Affero General Public License from time to time. Such new versions
|
||||||
|
will be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU Affero General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU Affero General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU Affero General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
101
README.md
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
# Haveno App
|
||||||
|
|
||||||
|
<img src="https://i.ibb.co/J7J9qV4/Screenshot-20240817-203316.jpg" width=150 /> <img src="https://i.ibb.co/Btt17Vg/Screenshot-20240817-203341.jpg" width=150 /> <img src="https://i.ibb.co/1L09NT6/Screenshot-20240817-203431.jpg" width=150 /> <img src="https://i.ibb.co/QDPyJp9/Screenshot-20240817-203535.jpg" width=150 /> <img src="https://i.ibb.co/L011YGW/Screenshot-20240817-203150.jpg" width=150 /> <img src="https://i.ibb.co/64YQR1S/Screenshot-20240817-204709.jpg" width=150 />
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
1. [Prerequisites](#prerequisites)
|
||||||
|
4. [Project Status](#project-status)
|
||||||
|
- [Network Endorsements](#network-endorsements)
|
||||||
|
6. [Project Activity](#project-activity)
|
||||||
|
7. [Roadmap](#roadmap)
|
||||||
|
8. [Contributing](#contributing)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prerequisites (For Testing)
|
||||||
|
|
||||||
|
Before you begin, you'll need to set up a testing environment:
|
||||||
|
|
||||||
|
- **Android Device or Emulator:** You can test on a physical Android phone or use an Android simulator via [Android Studio](https://studio.android.com) for advanced users. [BlueStacks](https://www.bluestacks.com/download.html) is another option with a gentler learning curve.
|
||||||
|
- **Latest Pre-release Builds:** Obtain the latest pre-release builds from the [Releases](https://github.com/KewbitXMR/haveno-app/releases) page. These are typically updated weekly.
|
||||||
|
|
||||||
|
**Important:** Follow the instructions in this guide carefully for Haveno Plus to function correctly.
|
||||||
|
|
||||||
|
## Setup Your Mobile Device
|
||||||
|
|
||||||
|
### Install Tor VPN Relay
|
||||||
|
|
||||||
|
To ensure all traffic is securely routed through Tor, you must install and activate a Tor VPN relay on your mobile device. The recommended apps are:
|
||||||
|
|
||||||
|
- **[Orbot](https://play.google.com/store/apps/details?id=org.torproject.android):** Officially supported by The Tor Project.
|
||||||
|
- [Sourcecode & Releases](https://github.com/guardianproject/orbot/releases/tag/17.3.2-RC-1-tor-0.4.8.12)
|
||||||
|
- **[InviZible](https://play.google.com/store/apps/details?id=pan.alexander.tordnscrypt.gp):** A popular community alternative.
|
||||||
|
- [Sourcecode & Releases](https://github.com/Gedsh/InviZible/releases/tag/v2.3.0-beta)
|
||||||
|
|
||||||
|
**Steps:**
|
||||||
|
1. Download [Orbot on Google Play](https://play.google.com/store/apps/details?id=org.torproject.android) or [InviZible on Google Play](https://play.google.com/store/apps/details?id=pan.alexander.tordnscrypt.gp).
|
||||||
|
- Alternatively, download [InviZible on F-Droid](https://f-droid.org/packages/pan.alexander.tordnscrypt.stable/).
|
||||||
|
2. Open the app of your choice and follow the on-screen instructions to activate it. Ensure that Tor is enabled and the VPN is activated.
|
||||||
|
3. Configure the VPN relay to route your Haveno Plus app traffic through Tor. The app will not load if a VPN relay is not configured first, by design, for your security.
|
||||||
|
|
||||||
|
### Haveno Install Guide
|
||||||
|
|
||||||
|
The Haveno Plus app is available as alpha pre-release builds for Android and Windows. Download the app from the [Releases](https://github.com/KewbitXMR/haveno-app/releases) page. The desktop clients are designed to be user-friendly, with custom installers for quick setup.
|
||||||
|
|
||||||
|
**Note:** Haveno Plus is currently configured to use the stagenet (a test network) for at least the next 2 months. It is not intended for real-life trading.
|
||||||
|
|
||||||
|
## Setup Your Desktop or Server
|
||||||
|
|
||||||
|
- **Windows:** (Coming soon)
|
||||||
|
- **MacOS:** (Coming soon)
|
||||||
|
- **Android** Alpha (testing)
|
||||||
|
- **iOS** (Coming soon)
|
||||||
|
- **Linux:** Alpha (testing)
|
||||||
|
- **Docker:** (Coming soon)
|
||||||
|
|
||||||
|
|
||||||
|
### Step-by-Step Guides
|
||||||
|
1. [How to Install Haveno on Desktop](https://haveno.com/documentation/installing-haveno-on-desktop/)
|
||||||
|
2. [How to Install Haveno on Mobile](https://haveno.com/documentation/install-haveno-on-a-mobile-device/)
|
||||||
|
3. [How to Install Haveno on Server with Docker](https://haveno.com/documentation/installing-the-haveno-daemon-with-docker-securely/)
|
||||||
|
4. [How to Setup your own Haveno Network](https://haveno.com/documentation/setup-a-custom-haveno-network-seednode-with-docker/)
|
||||||
|
|
||||||
|
|
||||||
|
## Project Status
|
||||||
|
|
||||||
|
Milestone 1: Protocol Interface ✅
|
||||||
|
Milestone 2: Complete UI + Providers + lots more ✅
|
||||||
|
Extras not in CCS:
|
||||||
|
- Caching system to ease the load on the daemon SQLite
|
||||||
|
- AES encryption on shared shared preferences and DB (not tested, will including on wallet too if nessesary)
|
||||||
|
|
||||||
|
The project is currently currently in the testing peroid of Milestone 2 having completed it.
|
||||||
|
|
||||||
|
### Network Endorsements
|
||||||
|
|
||||||
|
Haveno does not endorse or denounce any particular network. The choice of network will be available upon official release.
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
|
||||||
|
- Dart SDK API ✅ [Haveno Dart SDK](https://pub.dev/packages/haveno)
|
||||||
|
- Complete UI ✅ (tweaks needed)
|
||||||
|
- Linux desktop support ✅
|
||||||
|
- Windows desktop Support
|
||||||
|
- MacOS desktop support
|
||||||
|
- Android mobile Support
|
||||||
|
- Complete full arbitration scope.
|
||||||
|
- Add client authentication for onion-hosted daemons.
|
||||||
|
- iOS support.
|
||||||
|
- Easy whitelisting and fund transfers to Cake Wallet or similar.
|
||||||
|
- Biometric security for mobile devices, with PIN or password protection for those without biometric options.
|
||||||
|
- Standalone version not requiring desktop or server (considerable work; community support may be needed).
|
||||||
|
- Support for Monero Atomic Swaps
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Testing on old phones or laptops and providing high-quality feedback is the best way to contribute. A discussion section will be set up for initial feedback and contributions.
|
||||||
|
|
||||||
|
## Disclaimer
|
||||||
|
Kewbit the maintainers blog is at [Kewbit.org](https://kewbit.org/) official sources for this are located at [https://git.haveno.com/haveno/](Haveno.com's Gitlab). **HAVENO.COM represents the official haveno app website and services as a client to a Haveno Daemon only**, and **HAVENO.EXCHANGE represents everything else, including not not limited to the p2p server network protocol, daemon nodes and pricenodes**, there are now also lots of app-specific guides located at [haveno documentation](https://haveno.com/documentation/) section of the site, which are atuned towards the new app.
|
||||||
|
|
||||||
|
None of the code in this repository (haveno-app) is intrinically holding custody of philosophy in what may be considered 'crypto-assets' OR transmitting any such 'crypto-assets' or other financial services across the the wire, network or the general internet.
|
31
analysis_options.yaml
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# This file configures the analyzer, which statically analyzes Dart code to
|
||||||
|
# check for errors, warnings, and lints.
|
||||||
|
#
|
||||||
|
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||||
|
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||||
|
# invoked from the command line by running `flutter analyze`.
|
||||||
|
|
||||||
|
# The following line activates a set of recommended lints for Flutter apps,
|
||||||
|
# packages, and plugins designed to encourage good coding practices.
|
||||||
|
analyzer:
|
||||||
|
errors:
|
||||||
|
avoid_print: ignore
|
||||||
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
linter:
|
||||||
|
# The lint rules applied to this project can be customized in the
|
||||||
|
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||||
|
# included above or to enable additional rules. A list of all available lints
|
||||||
|
# and their documentation is published at https://dart.dev/lints.
|
||||||
|
#
|
||||||
|
# Instead of disabling a lint rule for the entire project in the
|
||||||
|
# section below, it can also be suppressed for a single line of code
|
||||||
|
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||||
|
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||||
|
# producing the lint.
|
||||||
|
rules:
|
||||||
|
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||||
|
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||||
|
|
||||||
|
# Additional information about this file can be found at
|
||||||
|
# https://dart.dev/guides/language/analysis-options
|
13
android/.gitignore
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
gradle-wrapper.jar
|
||||||
|
/.gradle
|
||||||
|
/captures/
|
||||||
|
/gradlew
|
||||||
|
/gradlew.bat
|
||||||
|
/local.properties
|
||||||
|
GeneratedPluginRegistrant.java
|
||||||
|
|
||||||
|
# Remember to never publicly share your keystore.
|
||||||
|
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
|
||||||
|
key.properties
|
||||||
|
**/*.keystore
|
||||||
|
**/*.jks
|
67
android/app/build.gradle
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
plugins {
|
||||||
|
id "com.android.application"
|
||||||
|
id "kotlin-android"
|
||||||
|
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||||
|
id "dev.flutter.flutter-gradle-plugin"
|
||||||
|
}
|
||||||
|
|
||||||
|
def localProperties = new Properties()
|
||||||
|
def localPropertiesFile = rootProject.file("local.properties")
|
||||||
|
if (localPropertiesFile.exists()) {
|
||||||
|
localPropertiesFile.withReader("UTF-8") { reader ->
|
||||||
|
localProperties.load(reader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterVersionCode = localProperties.getProperty("flutter.versionCode")
|
||||||
|
if (flutterVersionCode == null) {
|
||||||
|
flutterVersionCode = "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterVersionName = localProperties.getProperty("flutter.versionName")
|
||||||
|
if (flutterVersionName == null) {
|
||||||
|
flutterVersionName = "1.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.haveno.haveno"
|
||||||
|
compileSdk = flutter.compileSdkVersion
|
||||||
|
ndkVersion = flutter.ndkVersion
|
||||||
|
|
||||||
|
splits {
|
||||||
|
abi {
|
||||||
|
enable false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
|
applicationId = "com.haveno.haveno"
|
||||||
|
// You can update the following values to match your application needs.
|
||||||
|
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
||||||
|
minSdk = flutter.minSdkVersion
|
||||||
|
targetSdk = flutter.targetSdkVersion
|
||||||
|
versionCode = flutterVersionCode.toInteger()
|
||||||
|
versionName = flutterVersionName
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
// TODO: Add your own signing config for the release build.
|
||||||
|
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||||
|
signingConfig = signingConfigs.debug
|
||||||
|
shrinkResources false
|
||||||
|
zipAlignEnabled false
|
||||||
|
minifyEnabled false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flutter {
|
||||||
|
source = "../.."
|
||||||
|
}
|
7
android/app/src/debug/AndroidManifest.xml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
|
the Flutter tool needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
46
android/app/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<application
|
||||||
|
android:label="haveno"
|
||||||
|
android:name="${applicationName}"
|
||||||
|
android:extractNativeLibs="true"
|
||||||
|
android:icon="@mipmap/ic_launcher">
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:taskAffinity=""
|
||||||
|
android:theme="@style/LaunchTheme"
|
||||||
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
|
android:hardwareAccelerated="true"
|
||||||
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
|
the Android process has started. This theme is visible to the user
|
||||||
|
while the Flutter UI initializes. After that, this theme continues
|
||||||
|
to determine the Window background behind the Flutter UI. -->
|
||||||
|
<meta-data
|
||||||
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
|
android:resource="@style/NormalTheme"
|
||||||
|
/>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<!-- Don't delete the meta-data below.
|
||||||
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
|
<meta-data
|
||||||
|
android:name="flutterEmbedding"
|
||||||
|
android:value="2" />
|
||||||
|
</application>
|
||||||
|
<!-- Required to query activities that can process text, see:
|
||||||
|
https://developer.android.com/training/package-visibility and
|
||||||
|
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||||
|
|
||||||
|
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||||
|
<queries>
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||||
|
<data android:mimeType="text/plain"/>
|
||||||
|
</intent>
|
||||||
|
</queries>
|
||||||
|
</manifest>
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.haveno.haveno
|
||||||
|
|
||||||
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
|
||||||
|
class MainActivity: FlutterActivity()
|
12
android/app/src/main/res/drawable-v21/launch_background.xml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="?android:colorBackground" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
12
android/app/src/main/res/drawable-v214e19131
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="?android:colorBackground" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
12
android/app/src/main/res/drawable/launch_background.xml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@android:color/white" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 544 B |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 442 B |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 721 B |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
18
android/app/src/main/res/values-night/styles.xml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
the Flutter engine draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
18
android/app/src/main/res/values/styles.xml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
the Flutter engine draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
7
android/app/src/profile/AndroidManifest.xml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
|
the Flutter tool needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
18
android/build.gradle
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.buildDir = "../build"
|
||||||
|
subprojects {
|
||||||
|
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||||
|
}
|
||||||
|
subprojects {
|
||||||
|
project.evaluationDependsOn(":app")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register("clean", Delete) {
|
||||||
|
delete rootProject.buildDir
|
||||||
|
}
|
4
android/gradle.properties
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError
|
||||||
|
android.useAndroidX=true
|
||||||
|
android.enableJetifier=true
|
||||||
|
android.bundle.enableUncompressedNativeLibs=false
|
5
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip
|
25
android/settings.gradle
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
pluginManagement {
|
||||||
|
def flutterSdkPath = {
|
||||||
|
def properties = new Properties()
|
||||||
|
file("local.properties").withInputStream { properties.load(it) }
|
||||||
|
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||||
|
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||||
|
return flutterSdkPath
|
||||||
|
}()
|
||||||
|
|
||||||
|
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||||
|
id "com.android.application" version "7.3.0" apply false
|
||||||
|
id "org.jetbrains.kotlin.android" version "1.9.20" apply false
|
||||||
|
}
|
||||||
|
|
||||||
|
include ":app"
|
BIN
assets/arbitration-logo.png
Normal file
After Width: | Height: | Size: 25 KiB |
94
assets/config/android/i2p/i2pd.conf
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
## Configuration file for a typical i2pd user
|
||||||
|
## See https://i2pd.readthedocs.io/en/latest/user-guide/configuration/
|
||||||
|
## for more options you can use in this file.
|
||||||
|
|
||||||
|
#logfile = /sdcard/i2pd/i2pd.log
|
||||||
|
loglevel = none
|
||||||
|
#tunnelsdir = /sdcard/i2pd/tunnels.d
|
||||||
|
|
||||||
|
# host = 1.2.3.4
|
||||||
|
# port = 4567
|
||||||
|
|
||||||
|
ipv4 = true
|
||||||
|
ipv6 = false
|
||||||
|
|
||||||
|
ssu = false
|
||||||
|
|
||||||
|
bandwidth = L
|
||||||
|
# share = 100
|
||||||
|
|
||||||
|
# notransit = true
|
||||||
|
# floodfill = true
|
||||||
|
|
||||||
|
[ntcp2]
|
||||||
|
enabled = true
|
||||||
|
|
||||||
|
[ssu2]
|
||||||
|
enabled = true
|
||||||
|
published = true
|
||||||
|
|
||||||
|
[http]
|
||||||
|
enabled = true
|
||||||
|
address = 127.0.0.1
|
||||||
|
port = 7070
|
||||||
|
# auth = true
|
||||||
|
# user = i2pd
|
||||||
|
# pass = changeme
|
||||||
|
|
||||||
|
[httpproxy]
|
||||||
|
enabled = true
|
||||||
|
address = 127.0.0.1
|
||||||
|
port = 4444
|
||||||
|
inbound.length = 1
|
||||||
|
inbound.quantity = 5
|
||||||
|
outbound.length = 1
|
||||||
|
outbound.quantity = 5
|
||||||
|
signaturetype=7
|
||||||
|
i2cp.leaseSetType=3
|
||||||
|
i2cp.leaseSetEncType=0,4
|
||||||
|
keys = proxy-keys.dat
|
||||||
|
# addresshelper = true
|
||||||
|
# outproxy = http://false.i2p
|
||||||
|
## httpproxy section also accepts I2CP parameters, like "inbound.length" etc.
|
||||||
|
|
||||||
|
[socksproxy]
|
||||||
|
enabled = true
|
||||||
|
address = 127.0.0.1
|
||||||
|
port = 4447
|
||||||
|
keys = proxy-keys.dat
|
||||||
|
# outproxy.enabled = false
|
||||||
|
# outproxy = 127.0.0.1
|
||||||
|
# outproxyport = 9050
|
||||||
|
## socksproxy section also accepts I2CP parameters, like "inbound.length" etc.
|
||||||
|
|
||||||
|
[sam]
|
||||||
|
enabled = false
|
||||||
|
# address = 127.0.0.1
|
||||||
|
# port = 7656
|
||||||
|
|
||||||
|
[precomputation]
|
||||||
|
elgamal = false
|
||||||
|
|
||||||
|
[upnp]
|
||||||
|
enabled = true
|
||||||
|
# name = I2Pd
|
||||||
|
|
||||||
|
[reseed]
|
||||||
|
verify = true
|
||||||
|
## Path to local reseed data file (.su3) for manual reseeding
|
||||||
|
# file = /path/to/i2pseeds.su3
|
||||||
|
## or HTTPS URL to reseed from
|
||||||
|
# file = https://legit-website.com/i2pseeds.su3
|
||||||
|
## Path to local ZIP file or HTTPS URL to reseed from
|
||||||
|
# zipfile = /path/to/netDb.zip
|
||||||
|
## If you run i2pd behind a proxy server, set proxy server for reseeding here
|
||||||
|
## Should be http://address:port or socks://address:port
|
||||||
|
# proxy = http://127.0.0.1:8118
|
||||||
|
## Minimum number of known routers, below which i2pd triggers reseeding. 25 by default
|
||||||
|
# threshold = 25
|
||||||
|
|
||||||
|
[limits]
|
||||||
|
transittunnels = 50
|
||||||
|
|
||||||
|
[persist]
|
||||||
|
profiles = false
|
33
assets/config/android/i2p/tunnels.conf
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
#[IRC-IRC2P]
|
||||||
|
#type = client
|
||||||
|
#address = 127.0.0.1
|
||||||
|
#port = 6668
|
||||||
|
#destination = irc.postman.i2p
|
||||||
|
#destinationport = 6667
|
||||||
|
#keys = irc-keys.dat
|
||||||
|
|
||||||
|
#[IRC-ILITA]
|
||||||
|
#type = client
|
||||||
|
#address = 127.0.0.1
|
||||||
|
#port = 6669
|
||||||
|
#destination = irc.ilita.i2p
|
||||||
|
#destinationport = 6667
|
||||||
|
#keys = irc-keys.dat
|
||||||
|
|
||||||
|
#[SMTP]
|
||||||
|
#type = client
|
||||||
|
#address = 127.0.0.1
|
||||||
|
#port = 7659
|
||||||
|
#destination = smtp.postman.i2p
|
||||||
|
#destinationport = 25
|
||||||
|
#keys = smtp-keys.dat
|
||||||
|
|
||||||
|
#[POP3]
|
||||||
|
#type = client
|
||||||
|
#address = 127.0.0.1
|
||||||
|
#port = 7660
|
||||||
|
#destination = pop.postman.i2p
|
||||||
|
#destinationport = 110
|
||||||
|
#keys = pop3-keys.dat
|
||||||
|
|
||||||
|
# see more examples at https://i2pd.readthedocs.io/en/latest/user-guide/tunnels/
|
22
assets/config/default/torrc
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
DataDirectory ./data
|
||||||
|
HiddenServiceDir ../daemon_service
|
||||||
|
HiddenServicePort 80 127.0.0.1:3201
|
||||||
|
HTTPTunnelPort 8888
|
||||||
|
SocksPort 9066
|
||||||
|
ControlPort 9077
|
||||||
|
HashedControlPassword 16:7FD95C8D50BA159760AA90C98DEA2924F0539C6B939F8A07AA68265FF4
|
||||||
|
Log notice stdout
|
||||||
|
#Log notice file ./logs/notice.log
|
||||||
|
KeepalivePeriod 60
|
||||||
|
ConstrainedSockets 1
|
||||||
|
ConstrainedSockSize 8192
|
||||||
|
#GeoIPFile ./lib/geoip
|
||||||
|
#GeoIPv6File ./lib/geoip6
|
||||||
|
ClientDNSRejectInternalAddresses 1
|
||||||
|
Nickname HAx000000
|
||||||
|
ContactInfo uuidv4@something.com
|
||||||
|
UseEntryGuards 1
|
||||||
|
NumEntryGuards 3
|
||||||
|
|
||||||
|
### EXPERIMENTAL FUTURE STUFF DONT ENABLE THESE YET IT COULD BE DISASTEROUS
|
||||||
|
#HiddenServiceSingleHopMode 1
|
0
assets/config/macos/org.kewbit.havenoDaemon.plist
Normal file
0
assets/config/macos/org.kewbit.havenoTorDaemon.plist
Normal file
BIN
assets/getting-started-logo.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
assets/haveno-logo.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
assets/icon/app_icon.ico
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
assets/icon/app_icon.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
assets/icon/app_icon_smaller.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
assets/libraries/windows/sqlite3.dll
Normal file
BIN
assets/tor-logo.png
Normal file
After Width: | Height: | Size: 36 KiB |
17
assets/versions.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"haveno-core": {
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
"tor": {
|
||||||
|
"default": "14.5a1",
|
||||||
|
"otherwise_default_model": "tor_find_latest"
|
||||||
|
},
|
||||||
|
"monero": {
|
||||||
|
"default": "0.18.3.4",
|
||||||
|
"otherwise_default_model": ""
|
||||||
|
},
|
||||||
|
"java": {
|
||||||
|
"default": "21.0.4+7",
|
||||||
|
"otherwise_default_model": ""
|
||||||
|
}
|
||||||
|
}
|
3
devtools_options.yaml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
description: This file stores settings for Dart & Flutter DevTools.
|
||||||
|
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
||||||
|
extensions:
|
304
lib/background_services.dart
Normal file
|
@ -0,0 +1,304 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:flutter_background_service/flutter_background_service.dart';
|
||||||
|
import 'package:haveno/enums.dart';
|
||||||
|
import 'package:haveno/haveno_client.dart';
|
||||||
|
import 'package:haveno/haveno_service.dart';
|
||||||
|
import 'package:haveno_app/models/haveno_daemon_config.dart';
|
||||||
|
import 'package:haveno_app/models/schema.dart';
|
||||||
|
import 'package:haveno_app/services/local_notification_service.dart';
|
||||||
|
import 'package:haveno_app/services/secure_storage_service.dart';
|
||||||
|
|
||||||
|
// This is for Mobile background services only (for now) fuck off if you wanted something else. But we could run native Tor on Desktop too!
|
||||||
|
|
||||||
|
class BackgroundServiceListener {
|
||||||
|
final FlutterBackgroundService service = FlutterBackgroundService();
|
||||||
|
final EventDispatcher dispatcher;
|
||||||
|
|
||||||
|
BackgroundServiceListener(this.dispatcher);
|
||||||
|
|
||||||
|
Future<void> startListening() async {
|
||||||
|
service.on('updateTorStatus').listen((event) {
|
||||||
|
if (event != null) {
|
||||||
|
dispatcher.dispatch(BackgroundEvent(
|
||||||
|
type: BackgroundEventType.updateTorStatus,
|
||||||
|
data: event,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
service.on('updateDaemonStatus').listen((event) {
|
||||||
|
if (event != null) {
|
||||||
|
dispatcher.dispatch(BackgroundEvent(
|
||||||
|
type: BackgroundEventType.updateDaemonStatus,
|
||||||
|
data: event,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
service.on('torStdOutLog').listen((event) {
|
||||||
|
if (event != null) {
|
||||||
|
dispatcher.dispatch(BackgroundEvent(
|
||||||
|
type: BackgroundEventType.torStdOutLog,
|
||||||
|
data: event,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
service.on('torStdErrLog').listen((event) {
|
||||||
|
if (event != null) {
|
||||||
|
dispatcher.dispatch(BackgroundEvent(
|
||||||
|
type: BackgroundEventType.torStdErrLog,
|
||||||
|
data: event,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void startBackgroundService() {
|
||||||
|
final service = FlutterBackgroundService();
|
||||||
|
service.startService();
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopBackgroundService() {
|
||||||
|
final service = FlutterBackgroundService();
|
||||||
|
service.invoke("stop");
|
||||||
|
}
|
||||||
|
|
||||||
|
void markServiceNotificationSeen() {
|
||||||
|
final service = FlutterBackgroundService(); // just notify the UI to show the notificiation instead???
|
||||||
|
service.invoke("mark_notification_as_seen");
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> startLongRunningForegroundService(ServiceInstance service) async {
|
||||||
|
//await _startTor(service);
|
||||||
|
LocalNotificationsService localNotificationsService = LocalNotificationsService();
|
||||||
|
localNotificationsService.init();
|
||||||
|
|
||||||
|
NotificationsService notificationsService =
|
||||||
|
NotificationsService();
|
||||||
|
|
||||||
|
notificationsService.addListener(
|
||||||
|
NotificationMessage_NotificationType.APP_INITIALIZED,
|
||||||
|
(object) {
|
||||||
|
localNotificationsService.updateForegroundServiceNotification(
|
||||||
|
title: 'Status',
|
||||||
|
body: 'Connected',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
notificationsService.addListener(
|
||||||
|
NotificationMessage_NotificationType.TRADE_UPDATE,
|
||||||
|
(object) {
|
||||||
|
localNotificationsService.showNotification(
|
||||||
|
id: object.hashCode,
|
||||||
|
title: 'Trade Update',
|
||||||
|
body: object.message,
|
||||||
|
payload: jsonEncode({
|
||||||
|
'action': 'route_to_active_trades_screen',
|
||||||
|
'tradeProtobufAsJson': jsonEncode(object.trade.toProto3Json())
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
notificationsService.addListener(
|
||||||
|
NotificationMessage_NotificationType.CHAT_MESSAGE,
|
||||||
|
(object) {
|
||||||
|
var payload = {
|
||||||
|
'action': 'route_to_chat_screen',
|
||||||
|
'chatMessageProtobufAsJson':
|
||||||
|
jsonEncode(object.chatMessage.toProto3Json())
|
||||||
|
};
|
||||||
|
print(jsonEncode(object.trade.toProto3Json()));
|
||||||
|
|
||||||
|
localNotificationsService.showNotification(
|
||||||
|
id: object.hashCode,
|
||||||
|
title: object.chatMessage.type == SupportType.TRADE
|
||||||
|
? 'New Message'
|
||||||
|
: 'New Support Message',
|
||||||
|
body: object.chatMessage.message,
|
||||||
|
payload: jsonEncode(payload));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
notificationsService.listen();
|
||||||
|
|
||||||
|
localNotificationsService.updateForegroundServiceNotification(
|
||||||
|
title: 'Status',
|
||||||
|
body: 'Attempting to connect...',
|
||||||
|
);
|
||||||
|
|
||||||
|
//_connectToHavenoDaemonHeadless(service);
|
||||||
|
|
||||||
|
service.on("stop").listen((event) {
|
||||||
|
// We may need to just trigger this when the app is opened, and when its closed, enable it again
|
||||||
|
service.stopSelf();
|
||||||
|
// Set something in database for this or in secure storage, if it stops we need to make sure ios background fetch is running at least
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// For iOS to update notifications and state (mainly notifications)
|
||||||
|
Future<void> startShortLivedBackgroundFetch(ServiceInstance service) async {
|
||||||
|
LocalNotificationsService localNotificationsService = LocalNotificationsService();
|
||||||
|
localNotificationsService.init(); // This whole thing might not complete in time for iOS
|
||||||
|
// if (!Tor.instance.enabled) {
|
||||||
|
// await _startTor(service);
|
||||||
|
// } else {
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
// await _connectToHavenoDaemonHeadless(service, retryUntilSuccess: true, failWhenNoDaemonConfig: true);
|
||||||
|
|
||||||
|
// Check for new trades and chat messages only then send notification
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
//Future<void> _startTor(ServiceInstance service) async {
|
||||||
|
// try {
|
||||||
|
// if (!Tor.instance.started) {
|
||||||
|
// service.invoke("updateTorStatus", {
|
||||||
|
// "status": "intializing",
|
||||||
|
// "details": "Tor is is now initializing..."
|
||||||
|
// });
|
||||||
|
// await Tor.init();
|
||||||
|
// service.invoke("updateTorStatus", {
|
||||||
|
// "status": "initialized",
|
||||||
|
// "details": "Tor has now been initialized. (Not yet started)"
|
||||||
|
// });
|
||||||
|
// service.invoke("updateTorStatus", {
|
||||||
|
// "status": "starting",
|
||||||
|
// "details": "Start is now starting..."
|
||||||
|
// }
|
||||||
|
// );
|
||||||
|
// if (!Tor.instance.enabled) {
|
||||||
|
// await Tor.instance.enable();
|
||||||
|
// }
|
||||||
|
// await Tor.instance.start();
|
||||||
|
// while (!Tor.instance.bootstrapped) {
|
||||||
|
// await Future.delayed(const Duration(seconds: 1));
|
||||||
|
// }
|
||||||
|
// service.invoke("updateTorStatus", {
|
||||||
|
// "status": "started",
|
||||||
|
// "port": Tor.instance.port,
|
||||||
|
// "details": "Tor service successfully started on port ${Tor.instance.port}"
|
||||||
|
// });
|
||||||
|
// } else {
|
||||||
|
// service.invoke("updateTorStatus", {
|
||||||
|
// "status": "started",
|
||||||
|
// "port": Tor.instance.port,
|
||||||
|
// "details": "Tor service successfully started on port ${Tor.instance.port}"
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// } catch (e) {
|
||||||
|
// service.invoke("updateTorStatus", {
|
||||||
|
// "status": "error",
|
||||||
|
// "details": e.toString()
|
||||||
|
// });
|
||||||
|
// rethrow;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// StreamSubscription<dynamic> subscription = Tor.instance.events.stream.listen( /// might have to make sure this is close or reopenable on closure automatically
|
||||||
|
// (event) {
|
||||||
|
// // Handle each event
|
||||||
|
// service.invoke("torStdOutLog", {
|
||||||
|
// "details": event.toString(),
|
||||||
|
// });
|
||||||
|
// },
|
||||||
|
// onError: (error) {
|
||||||
|
// // Handle any errors that occur during the stream
|
||||||
|
// service.invoke("torStdErrLog", {
|
||||||
|
// "details": error.toString(),
|
||||||
|
// });
|
||||||
|
// },
|
||||||
|
// onDone: () {
|
||||||
|
// // Handle when the stream is closed or completed
|
||||||
|
// service.invoke("updateTorStatus", {
|
||||||
|
// "status": "Tor stream closed",
|
||||||
|
// });
|
||||||
|
// },
|
||||||
|
// cancelOnError: false, // If true, the stream will cancel on the first error
|
||||||
|
// );
|
||||||
|
|
||||||
|
//}
|
||||||
|
|
||||||
|
Future<void> _connectToHavenoDaemonHeadless(ServiceInstance service, {bool retryUntilSuccess = true, bool failWhenNoDaemonConfig = false}) async {
|
||||||
|
HavenoChannel havenoService = HavenoChannel();
|
||||||
|
HavenoDaemonConfig? daemonConfig;
|
||||||
|
while (daemonConfig == null && retryUntilSuccess) {
|
||||||
|
service.invoke("updateDaemonStatus", {
|
||||||
|
"status": "aquiringRemoteDaemonConfig",
|
||||||
|
"details": "Aquiring remote daemon configuration from shared preferences..."
|
||||||
|
});
|
||||||
|
daemonConfig = await SecureStorageService().readHavenoDaemonConfig();
|
||||||
|
if (daemonConfig == null) {
|
||||||
|
service.invoke("updateDaemonStatus", {
|
||||||
|
"status": "remoteDaemonConfigNotFound",
|
||||||
|
"details": "No remote daemon configuration in shared preferences..."
|
||||||
|
});
|
||||||
|
if (failWhenNoDaemonConfig) {
|
||||||
|
service.invoke("updateDaemonStatus", {
|
||||||
|
"status": "stopped",
|
||||||
|
"details": "No daemon config found and it is configured to fail in this case and not retry..."
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
service.invoke("updateDaemonStatus", {
|
||||||
|
"status": "retryingConfig",
|
||||||
|
"details": "Scheduled to check for config again in 20 seconds..."
|
||||||
|
});
|
||||||
|
await Future.delayed(const Duration(seconds: 10));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
service.invoke("updateDaemonStatus", {
|
||||||
|
"status": "foundDaemonConfig",
|
||||||
|
"details": "Found remote daemon configuration from shared preferences..."
|
||||||
|
});
|
||||||
|
if (havenoService.isConnected) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
service.invoke("updateDaemonStatus", {
|
||||||
|
"status": "connecting",
|
||||||
|
"details": "Connecting to the daemon..."
|
||||||
|
});
|
||||||
|
await havenoService.connect(daemonConfig.host, daemonConfig.port,
|
||||||
|
daemonConfig.clientAuthPassword);
|
||||||
|
service.invoke("updateDaemonStatus", {
|
||||||
|
"status": "connected",
|
||||||
|
"details": "Connects to the daemon..."
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
service.invoke("updateDaemonStatus", {
|
||||||
|
"status": "unknownError",
|
||||||
|
"details": "Failure: ${e.toString()}"
|
||||||
|
});
|
||||||
|
|
||||||
|
await Future.delayed(const Duration(seconds: 10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
lib/background_tasks/desktop/fetch_data.dart
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
@ -0,0 +1,61 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/* import 'package:haveno_app/background_tasks/mobile_notification_workers/schema.dart';
|
||||||
|
import 'package:haveno_app/proto/compiled/pb.pb.dart';
|
||||||
|
import 'package:haveno_app/proto/compiled/grpc.pb.dart';
|
||||||
|
import 'package:haveno_app/services/haveno_grpc_clients/trades_client_service.dart';
|
||||||
|
|
||||||
|
class CheckNewTradeMessagesTask extends MobileTask {
|
||||||
|
@override
|
||||||
|
Future<void> run() async {
|
||||||
|
TradesClientService tradesClient = TradesClientService(havenoService: havenoService);
|
||||||
|
List<TradeInfo>? trades = await tradesClient.getTrades();
|
||||||
|
|
||||||
|
if (trades != null) {
|
||||||
|
for (var trade in trades) {
|
||||||
|
List<ChatMessage>? chatMessages = await tradesClient.getChatMessages(trade.tradeId);
|
||||||
|
for (var message in chatMessages!) {
|
||||||
|
bool isNewMessage = await db.isTradeChatMessageNew(message.uid);
|
||||||
|
await db.insertTradeChatMessage(message, trade.tradeId);
|
||||||
|
|
||||||
|
if (!trade.role.contains('buyer') && (trade.tradePeerNodeAddress != trade.contract.buyerNodeAddress)) {
|
||||||
|
if (isNewMessage) {
|
||||||
|
await sendNotification(
|
||||||
|
id: message.uid.hashCode,
|
||||||
|
title: 'New Trade Message',
|
||||||
|
body: message.message,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//await Future.delayed(const Duration(minutes: 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> updateState() {
|
||||||
|
// Implement state update logic if needed
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
|
@ -0,0 +1,61 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/* // mobile_tasks/check_new_trades.dart
|
||||||
|
|
||||||
|
import 'package:haveno_app/background_tasks/mobile_notification_workers/schema.dart';
|
||||||
|
|
||||||
|
import 'package:haveno_app/proto/compiled/grpc.pb.dart';
|
||||||
|
import 'package:haveno_app/services/haveno_grpc_clients/trades_client_service.dart';
|
||||||
|
import 'package:haveno_app/utils/payment_utils.dart';
|
||||||
|
|
||||||
|
class CheckNewTradesTask extends MobileTask {
|
||||||
|
|
||||||
|
CheckNewTradesTask() : super(minIntervalDuration: const Duration(minutes: 10));
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> run() async {
|
||||||
|
TradesClientService tradesClient = TradesClientService(havenoService: havenoService);
|
||||||
|
List<TradeInfo>? trades = await tradesClient.getTrades();
|
||||||
|
|
||||||
|
if (trades != null) {
|
||||||
|
for (var trade in trades) {
|
||||||
|
bool isNewTrade = await db.isTradeNew(trade.tradeId);
|
||||||
|
await db.insertTrade(trade);
|
||||||
|
|
||||||
|
if (isNewTrade) {
|
||||||
|
await sendNotification(
|
||||||
|
id: trade.tradeId.hashCode,
|
||||||
|
title: 'New Trade',
|
||||||
|
body: 'You have a new trade for ${formatXmr(trade.amount)} XMR',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> updateState() {
|
||||||
|
// Implement state update logic if needed
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
106
lib/background_tasks/mobile_notification_workers/schema.dart
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import 'package:haveno/haveno_client.dart';
|
||||||
|
import 'package:haveno_app/models/haveno_daemon_config.dart';
|
||||||
|
import 'package:haveno_app/services/connection_checker_service.dart';
|
||||||
|
import 'package:haveno_app/services/secure_storage_service.dart';
|
||||||
|
import 'package:haveno_app/services/local_notification_service.dart';
|
||||||
|
import 'package:haveno_app/utils/database_helper.dart';
|
||||||
|
|
||||||
|
abstract class AbstractMobileTask {
|
||||||
|
late Duration minIntervalDuration;
|
||||||
|
|
||||||
|
Future<void> run();
|
||||||
|
Future<void> sendNotification({required int id, required String title, required String body});
|
||||||
|
Future<void> updateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class MobileTask implements AbstractMobileTask {
|
||||||
|
final SecureStorageService secureStorage = SecureStorageService();
|
||||||
|
final HavenoChannel havenoChannel = HavenoChannel();
|
||||||
|
late final DatabaseHelper db;
|
||||||
|
HavenoDaemonConfig? havenoDaemonConfig;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Duration minIntervalDuration;
|
||||||
|
|
||||||
|
// Constructor with a default value for minIntervalDuration
|
||||||
|
MobileTask({this.minIntervalDuration = const Duration(minutes: 5)}) {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common initialization logic
|
||||||
|
Future<void> init() async {
|
||||||
|
// Get remote daemon config
|
||||||
|
havenoDaemonConfig = await secureStorage.readHavenoDaemonConfig();
|
||||||
|
|
||||||
|
// Init database
|
||||||
|
db = DatabaseHelper.instance;
|
||||||
|
|
||||||
|
// Check if connected to Tor
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
if ((!await ConnectionCheckerService().isTorConnected())) {
|
||||||
|
throw Exception("Not yet connected to Tor...");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print(e.toString());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
if ((!havenoChannel.isConnected)) {
|
||||||
|
await havenoChannel.connect(
|
||||||
|
havenoDaemonConfig!.host,
|
||||||
|
havenoDaemonConfig!.port,
|
||||||
|
havenoDaemonConfig!.clientAuthPassword,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to connect to Haveno instance: $e");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> run() async {
|
||||||
|
throw UnimplementedError("Subclasses should implement this method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> sendNotification({required int id, required String title, required String body}) async {
|
||||||
|
// Default implementation of sendNotification
|
||||||
|
LocalNotificationsService().showNotification(
|
||||||
|
id: id,
|
||||||
|
title: title,
|
||||||
|
body: body,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> updateState() async {
|
||||||
|
// Default implementation of updateState
|
||||||
|
// Can be overridden by subclasses if needed
|
||||||
|
}
|
||||||
|
}
|
10
lib/data/crypto_currencies.json
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"code": "BTC",
|
||||||
|
"label": "Bitcoin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "LTC",
|
||||||
|
"label": "Litecoin"
|
||||||
|
}
|
||||||
|
]
|
0
lib/data/fiat_currencies.json
Normal file
645
lib/data/mock_data.dart
Normal file
|
@ -0,0 +1,645 @@
|
||||||
|
import 'package:interactive_chart/interactive_chart.dart';
|
||||||
|
|
||||||
|
class MockDataTesla {
|
||||||
|
static const List<dynamic> _rawData = [
|
||||||
|
// (Price data for Tesla Inc, taken from Yahoo Finance)
|
||||||
|
// timestamp, open, high, low, close, volume
|
||||||
|
[1555939800, 53.80, 53.94, 52.50, 52.55, 60735500],
|
||||||
|
[1556026200, 52.03, 53.12, 51.15, 52.78, 54719500],
|
||||||
|
[1556112600, 52.77, 53.06, 51.60, 51.73, 53637500],
|
||||||
|
[1556199000, 51.00, 51.80, 49.21, 49.53, 109247000],
|
||||||
|
[1556285400, 49.30, 49.34, 46.23, 47.03, 111803500],
|
||||||
|
[1556544600, 47.17, 48.80, 46.43, 48.29, 83572500],
|
||||||
|
[1556631000, 48.41, 48.84, 47.40, 47.74, 47323000],
|
||||||
|
[1556717400, 47.77, 48.00, 46.30, 46.80, 53522000],
|
||||||
|
[1556803800, 49.10, 49.43, 47.54, 48.82, 90796500],
|
||||||
|
[1556890200, 48.77, 51.32, 48.70, 51.01, 118534000],
|
||||||
|
[1557149400, 50.00, 51.67, 49.70, 51.07, 54169500],
|
||||||
|
[1557235800, 51.36, 51.44, 49.02, 49.41, 50657000],
|
||||||
|
[1557322200, 49.39, 50.12, 48.84, 48.97, 30882000],
|
||||||
|
[1557408600, 48.40, 48.74, 47.39, 48.40, 33557000],
|
||||||
|
[1557495000, 47.95, 48.40, 47.20, 47.90, 35041500],
|
||||||
|
[1557754200, 46.40, 46.49, 44.90, 45.40, 54174000],
|
||||||
|
[1557840600, 45.86, 46.90, 45.60, 46.46, 36262000],
|
||||||
|
[1557927000, 45.86, 46.49, 45.05, 46.39, 36480000],
|
||||||
|
[1558013400, 45.90, 46.20, 45.30, 45.67, 37416500],
|
||||||
|
[1558099800, 44.39, 44.45, 41.78, 42.21, 88933500],
|
||||||
|
[1558359000, 40.56, 41.20, 39.05, 41.07, 102631000],
|
||||||
|
[1558445400, 39.55, 41.48, 39.21, 41.02, 90019500],
|
||||||
|
[1558531800, 39.82, 40.79, 38.36, 38.55, 93426000],
|
||||||
|
[1558618200, 38.87, 39.89, 37.24, 39.10, 132735500],
|
||||||
|
[1558704600, 39.97, 40.00, 37.75, 38.13, 70683000],
|
||||||
|
[1559050200, 38.24, 39.00, 37.57, 37.74, 51564500],
|
||||||
|
[1559136600, 37.42, 38.48, 37.01, 37.97, 59843000],
|
||||||
|
[1559223000, 37.75, 38.45, 37.40, 37.64, 39632500],
|
||||||
|
[1559309400, 37.02, 37.98, 36.82, 37.03, 52033500],
|
||||||
|
[1559568600, 37.10, 37.34, 35.40, 35.79, 65322000],
|
||||||
|
[1559655000, 36.22, 38.80, 35.92, 38.72, 69037500],
|
||||||
|
[1559741400, 39.74, 40.26, 38.37, 39.32, 67554000],
|
||||||
|
[1559827800, 40.89, 42.20, 40.36, 41.19, 101211000],
|
||||||
|
[1559914200, 41.00, 42.17, 40.70, 40.90, 80017500],
|
||||||
|
[1560173400, 42.05, 43.39, 41.80, 42.58, 52925000],
|
||||||
|
[1560259800, 43.83, 44.18, 42.70, 43.42, 58267500],
|
||||||
|
[1560346200, 44.59, 44.68, 41.80, 41.85, 75987500],
|
||||||
|
[1560432600, 42.08, 42.98, 41.50, 42.78, 40841500],
|
||||||
|
[1560519000, 42.25, 43.33, 42.08, 42.98, 37167000],
|
||||||
|
[1560778200, 43.10, 45.40, 42.85, 45.01, 61584000],
|
||||||
|
[1560864600, 45.74, 46.95, 44.51, 44.95, 63579000],
|
||||||
|
[1560951000, 45.02, 45.55, 44.21, 45.29, 32875500],
|
||||||
|
[1561037400, 44.60, 45.38, 43.27, 43.92, 59317500],
|
||||||
|
[1561123800, 43.24, 44.44, 43.10, 44.37, 41010500],
|
||||||
|
[1561383000, 44.65, 45.17, 44.20, 44.73, 28754000],
|
||||||
|
[1561469400, 44.88, 45.07, 43.90, 43.95, 30910500],
|
||||||
|
[1561555800, 44.06, 45.45, 43.62, 43.85, 42536000],
|
||||||
|
[1561642200, 43.89, 44.58, 43.47, 44.57, 31698500],
|
||||||
|
[1561728600, 44.20, 45.03, 44.16, 44.69, 34257000],
|
||||||
|
[1561987800, 46.04, 46.62, 45.26, 45.43, 41067000],
|
||||||
|
[1562074200, 45.78, 45.83, 44.44, 44.91, 46295000],
|
||||||
|
[1562160600, 47.88, 48.31, 46.90, 46.98, 71005500],
|
||||||
|
[1562333400, 46.91, 47.09, 46.16, 46.62, 35328500],
|
||||||
|
[1562592600, 46.25, 46.45, 45.73, 46.07, 29402500],
|
||||||
|
[1562679000, 45.79, 46.20, 45.46, 46.01, 30954000],
|
||||||
|
[1562765400, 46.83, 47.79, 46.63, 47.78, 45728500],
|
||||||
|
[1562851800, 47.63, 48.30, 47.16, 47.72, 37572000],
|
||||||
|
[1562938200, 47.95, 49.08, 47.94, 49.02, 46002500],
|
||||||
|
[1563197400, 49.60, 50.88, 48.97, 50.70, 55000500],
|
||||||
|
[1563283800, 49.86, 50.71, 49.59, 50.48, 40745000],
|
||||||
|
[1563370200, 51.13, 51.66, 50.67, 50.97, 48823500],
|
||||||
|
[1563456600, 51.01, 51.15, 50.38, 50.71, 23793000],
|
||||||
|
[1563543000, 51.14, 51.99, 50.92, 51.64, 35242000],
|
||||||
|
[1563802200, 51.75, 52.43, 50.84, 51.14, 34212000],
|
||||||
|
[1563888600, 51.34, 52.10, 50.90, 52.03, 25115500],
|
||||||
|
[1563975000, 51.83, 53.21, 51.63, 52.98, 55364000],
|
||||||
|
[1564061400, 46.70, 46.90, 45.11, 45.76, 112091500],
|
||||||
|
[1564147800, 45.38, 46.05, 44.45, 45.61, 50138500],
|
||||||
|
[1564407000, 45.42, 47.19, 45.21, 47.15, 46366500],
|
||||||
|
[1564493400, 46.58, 48.67, 46.44, 48.45, 40545000],
|
||||||
|
[1564579800, 48.60, 49.34, 47.33, 48.32, 45891000],
|
||||||
|
[1564666200, 48.53, 48.90, 46.35, 46.77, 41297500],
|
||||||
|
[1564752600, 46.27, 47.25, 45.85, 46.87, 30682500],
|
||||||
|
[1565011800, 45.92, 46.27, 45.16, 45.66, 35141500],
|
||||||
|
[1565098200, 46.38, 46.50, 45.15, 46.15, 27821000],
|
||||||
|
[1565184600, 45.30, 46.71, 45.16, 46.68, 23882500],
|
||||||
|
[1565271000, 46.89, 47.96, 46.53, 47.66, 26371500],
|
||||||
|
[1565357400, 47.21, 47.79, 46.76, 47.00, 19491000],
|
||||||
|
[1565616600, 46.60, 47.15, 45.75, 45.80, 23319500],
|
||||||
|
[1565703000, 45.76, 47.20, 45.51, 47.00, 24240500],
|
||||||
|
[1565789400, 46.24, 46.30, 43.34, 43.92, 47813000],
|
||||||
|
[1565875800, 44.17, 44.31, 42.31, 43.13, 40798000],
|
||||||
|
[1565962200, 43.33, 44.45, 43.20, 43.99, 25492500],
|
||||||
|
[1566221400, 44.84, 45.57, 44.34, 45.37, 26548000],
|
||||||
|
[1566307800, 45.52, 45.82, 44.91, 45.17, 20626000],
|
||||||
|
[1566394200, 44.40, 44.64, 43.52, 44.17, 38971500],
|
||||||
|
[1566480600, 44.56, 45.08, 43.64, 44.43, 32795000],
|
||||||
|
[1566567000, 43.99, 44.23, 42.20, 42.28, 42693000],
|
||||||
|
[1566826200, 42.72, 43.00, 42.31, 43.00, 25259500],
|
||||||
|
[1566912600, 43.15, 43.76, 42.41, 42.82, 27081000],
|
||||||
|
[1566999000, 42.74, 43.45, 42.46, 43.12, 16127500],
|
||||||
|
[1567085400, 43.80, 44.68, 43.60, 44.34, 25897500],
|
||||||
|
[1567171800, 45.83, 46.49, 44.84, 45.12, 46603000],
|
||||||
|
[1567517400, 44.82, 45.79, 44.63, 45.00, 26770500],
|
||||||
|
[1567603800, 45.38, 45.69, 43.84, 44.14, 28805000],
|
||||||
|
[1567690200, 44.50, 45.96, 44.17, 45.92, 36976500],
|
||||||
|
[1567776600, 45.44, 45.93, 45.03, 45.49, 20947000],
|
||||||
|
[1568035800, 46.00, 46.75, 45.85, 46.36, 24013500],
|
||||||
|
[1568122200, 46.16, 47.11, 45.79, 47.11, 24418500],
|
||||||
|
[1568208600, 47.48, 49.63, 47.20, 49.42, 50214000],
|
||||||
|
[1568295000, 49.54, 50.70, 48.88, 49.17, 42906000],
|
||||||
|
[1568381400, 49.39, 49.69, 48.97, 49.04, 26565500],
|
||||||
|
[1568640600, 49.20, 49.49, 48.23, 48.56, 23640500],
|
||||||
|
[1568727000, 48.49, 49.12, 48.07, 48.96, 19327000],
|
||||||
|
[1568813400, 49.00, 49.63, 48.47, 48.70, 20851000],
|
||||||
|
[1568899800, 49.20, 49.59, 48.97, 49.32, 23979000],
|
||||||
|
[1568986200, 49.30, 49.39, 47.63, 48.12, 31765000],
|
||||||
|
[1569245400, 48.00, 49.04, 47.84, 48.25, 21701000],
|
||||||
|
[1569331800, 48.30, 48.40, 44.52, 44.64, 64457500],
|
||||||
|
[1569418200, 44.91, 45.80, 43.67, 45.74, 47135500],
|
||||||
|
[1569504600, 46.13, 48.66, 45.48, 48.51, 59422500],
|
||||||
|
[1569591000, 48.44, 49.74, 47.75, 48.43, 55582000],
|
||||||
|
[1569850200, 48.60, 48.80, 47.22, 48.17, 29399000],
|
||||||
|
[1569936600, 48.30, 49.19, 47.83, 48.94, 30813000],
|
||||||
|
[1570023000, 48.66, 48.93, 47.89, 48.63, 28157000],
|
||||||
|
[1570109400, 46.37, 46.90, 44.86, 46.61, 75422500],
|
||||||
|
[1570195800, 46.32, 46.96, 45.61, 46.29, 39975000],
|
||||||
|
[1570455000, 45.96, 47.71, 45.71, 47.54, 40321000],
|
||||||
|
[1570541400, 47.17, 48.79, 46.90, 48.01, 43391000],
|
||||||
|
[1570627800, 48.26, 49.46, 48.13, 48.91, 34472000],
|
||||||
|
[1570714200, 49.06, 49.86, 48.32, 48.95, 31416500],
|
||||||
|
[1570800600, 49.43, 50.22, 49.36, 49.58, 42377000],
|
||||||
|
[1571059800, 49.58, 51.71, 49.43, 51.39, 51025000],
|
||||||
|
[1571146200, 51.54, 52.00, 50.82, 51.58, 32164000],
|
||||||
|
[1571232600, 51.48, 52.42, 51.38, 51.95, 33420500],
|
||||||
|
[1571319000, 52.50, 52.96, 52.03, 52.39, 23846500],
|
||||||
|
[1571405400, 52.14, 52.56, 51.02, 51.39, 28749000],
|
||||||
|
[1571664600, 51.67, 51.90, 50.04, 50.70, 25101500],
|
||||||
|
[1571751000, 50.86, 51.67, 50.17, 51.12, 23004000],
|
||||||
|
[1571837400, 50.90, 51.23, 50.27, 50.94, 26305500],
|
||||||
|
[1571923800, 59.67, 60.99, 57.84, 59.94, 148604500],
|
||||||
|
[1572010200, 59.54, 66.00, 59.22, 65.63, 150030500],
|
||||||
|
[1572269400, 65.51, 68.17, 64.52, 65.54, 94351500],
|
||||||
|
[1572355800, 64.00, 64.86, 62.95, 63.24, 63421500],
|
||||||
|
[1572442200, 62.60, 63.76, 61.99, 63.00, 48209000],
|
||||||
|
[1572528600, 62.62, 63.80, 62.60, 62.98, 25335000],
|
||||||
|
[1572615000, 63.26, 63.30, 61.96, 62.66, 31919500],
|
||||||
|
[1572877800, 62.96, 64.39, 61.85, 63.49, 43935000],
|
||||||
|
[1572964200, 63.92, 64.70, 63.22, 63.44, 34717000],
|
||||||
|
[1573050600, 63.60, 65.34, 62.90, 65.32, 39704500],
|
||||||
|
[1573137000, 65.83, 68.30, 65.60, 67.11, 72336500],
|
||||||
|
[1573223400, 66.90, 67.49, 66.50, 67.43, 30346000],
|
||||||
|
[1573482600, 68.79, 69.84, 68.40, 69.02, 49933500],
|
||||||
|
[1573569000, 69.38, 70.07, 68.81, 69.99, 36797000],
|
||||||
|
[1573655400, 71.00, 71.27, 69.04, 69.22, 42100500],
|
||||||
|
[1573741800, 69.22, 70.77, 68.58, 69.87, 32324500],
|
||||||
|
[1573828200, 70.13, 70.56, 69.67, 70.43, 24045000],
|
||||||
|
[1574087400, 70.58, 70.63, 69.22, 70.00, 22002000],
|
||||||
|
[1574173800, 70.35, 72.00, 69.56, 71.90, 38624000],
|
||||||
|
[1574260200, 72.00, 72.24, 69.91, 70.44, 33625500],
|
||||||
|
[1574346600, 70.90, 72.17, 70.80, 70.97, 30550000],
|
||||||
|
[1574433000, 68.03, 68.20, 66.00, 66.61, 84353000],
|
||||||
|
[1574692200, 68.86, 68.91, 66.89, 67.27, 61697500],
|
||||||
|
[1574778600, 67.05, 67.10, 65.42, 65.78, 39737000],
|
||||||
|
[1574865000, 66.22, 66.79, 65.71, 66.26, 27778000],
|
||||||
|
[1575037800, 66.22, 66.25, 65.50, 65.99, 12328000],
|
||||||
|
[1575297000, 65.88, 67.28, 65.74, 66.97, 30372500],
|
||||||
|
[1575383400, 66.52, 67.58, 66.44, 67.24, 32868500],
|
||||||
|
[1575469800, 67.55, 67.57, 66.57, 66.61, 27665000],
|
||||||
|
[1575556200, 66.57, 66.88, 65.45, 66.07, 18623000],
|
||||||
|
[1575642600, 67.00, 67.77, 66.95, 67.18, 38062000],
|
||||||
|
[1575901800, 67.32, 68.89, 67.02, 67.91, 45115500],
|
||||||
|
[1575988200, 67.99, 70.15, 67.86, 69.77, 44141500],
|
||||||
|
[1576074600, 70.38, 71.44, 70.22, 70.54, 34489000],
|
||||||
|
[1576161000, 70.98, 72.55, 70.65, 71.94, 38819500],
|
||||||
|
[1576247400, 72.21, 73.04, 70.93, 71.68, 32854500],
|
||||||
|
[1576506600, 72.51, 76.72, 72.50, 76.30, 90871000],
|
||||||
|
[1576593000, 75.80, 77.10, 75.18, 75.80, 42484000],
|
||||||
|
[1576679400, 76.13, 79.04, 76.12, 78.63, 70605000],
|
||||||
|
[1576765800, 79.46, 81.37, 79.30, 80.81, 90535500],
|
||||||
|
[1576852200, 82.06, 82.60, 80.04, 81.12, 73763500],
|
||||||
|
[1577111400, 82.36, 84.40, 82.00, 83.84, 66598000],
|
||||||
|
[1577197800, 83.67, 85.09, 82.54, 85.05, 40273500],
|
||||||
|
[1577370600, 85.58, 86.70, 85.27, 86.19, 53169500],
|
||||||
|
[1577457000, 87.00, 87.06, 85.22, 86.08, 49728500],
|
||||||
|
[1577716200, 85.76, 85.80, 81.85, 82.94, 62932000],
|
||||||
|
[1577802600, 81.00, 84.26, 80.42, 83.67, 51428500],
|
||||||
|
[1577975400, 84.90, 86.14, 84.34, 86.05, 47660500],
|
||||||
|
[1578061800, 88.10, 90.80, 87.38, 88.60, 88892500],
|
||||||
|
[1578321000, 88.09, 90.31, 88.00, 90.31, 50665000],
|
||||||
|
[1578407400, 92.28, 94.33, 90.67, 93.81, 89410500],
|
||||||
|
[1578493800, 94.74, 99.70, 93.65, 98.43, 155721500],
|
||||||
|
[1578580200, 99.42, 99.76, 94.57, 96.27, 142202000],
|
||||||
|
[1578666600, 96.36, 96.99, 94.74, 95.63, 64797500],
|
||||||
|
[1578925800, 98.70, 105.13, 98.40, 104.97, 132588000],
|
||||||
|
[1579012200, 108.85, 109.48, 104.98, 107.58, 144981000],
|
||||||
|
[1579098600, 105.95, 107.57, 103.36, 103.70, 86844000],
|
||||||
|
[1579185000, 98.75, 102.89, 98.43, 102.70, 108683500],
|
||||||
|
[1579271400, 101.52, 103.13, 100.63, 102.10, 68145500],
|
||||||
|
[1579617000, 106.05, 109.72, 105.68, 109.44, 89017500],
|
||||||
|
[1579703400, 114.38, 118.90, 111.82, 113.91, 156845000],
|
||||||
|
[1579789800, 112.85, 116.40, 111.12, 114.44, 98255000],
|
||||||
|
[1579876200, 114.13, 114.77, 110.85, 112.96, 71768000],
|
||||||
|
[1580135400, 108.40, 112.89, 107.86, 111.60, 68040500],
|
||||||
|
[1580221800, 113.70, 115.36, 111.62, 113.38, 58942500],
|
||||||
|
[1580308200, 115.14, 117.96, 113.49, 116.20, 89007500],
|
||||||
|
[1580394600, 126.48, 130.18, 123.60, 128.16, 145028500],
|
||||||
|
[1580481000, 128.00, 130.60, 126.50, 130.11, 78596500],
|
||||||
|
[1580740200, 134.74, 157.23, 134.70, 156.00, 235325000],
|
||||||
|
[1580826600, 176.59, 193.80, 166.78, 177.41, 304694000],
|
||||||
|
[1580913000, 164.65, 169.20, 140.82, 146.94, 242119000],
|
||||||
|
[1580999400, 139.98, 159.17, 137.40, 149.79, 199404000],
|
||||||
|
[1581085800, 146.11, 153.95, 146.00, 149.61, 85317500],
|
||||||
|
[1581345000, 160.00, 164.00, 150.48, 154.26, 123446000],
|
||||||
|
[1581431400, 153.76, 156.70, 151.60, 154.88, 58487500],
|
||||||
|
[1581517800, 155.57, 157.95, 152.67, 153.46, 60112500],
|
||||||
|
[1581604200, 148.37, 163.60, 147.00, 160.80, 131446500],
|
||||||
|
[1581690600, 157.44, 162.59, 157.10, 160.01, 78468500],
|
||||||
|
[1582036200, 168.32, 172.00, 166.47, 171.68, 81908500],
|
||||||
|
[1582122600, 184.70, 188.96, 180.20, 183.48, 127115000],
|
||||||
|
[1582209000, 182.39, 182.40, 171.99, 179.88, 88174500],
|
||||||
|
[1582295400, 181.40, 182.61, 176.09, 180.20, 71574000],
|
||||||
|
[1582554600, 167.80, 172.70, 164.44, 166.76, 75961000],
|
||||||
|
[1582641000, 169.80, 171.32, 157.40, 159.98, 86452500],
|
||||||
|
[1582727400, 156.50, 162.66, 155.22, 155.76, 70427500],
|
||||||
|
[1582813800, 146.00, 147.95, 133.80, 135.80, 121386000],
|
||||||
|
[1582900200, 125.94, 138.10, 122.30, 133.60, 121114500],
|
||||||
|
[1583159400, 142.25, 148.74, 137.33, 148.72, 100975000],
|
||||||
|
[1583245800, 161.00, 161.40, 143.22, 149.10, 128920000],
|
||||||
|
[1583332200, 152.79, 153.30, 144.95, 149.90, 75245000],
|
||||||
|
[1583418600, 144.75, 149.15, 143.61, 144.91, 54263500],
|
||||||
|
[1583505000, 138.00, 141.40, 136.85, 140.70, 63314500],
|
||||||
|
[1583760600, 121.08, 132.60, 121.00, 121.60, 85368500],
|
||||||
|
[1583847000, 131.89, 133.60, 121.60, 129.07, 77972000],
|
||||||
|
[1583933400, 128.04, 130.72, 122.60, 126.85, 66612500],
|
||||||
|
[1584019800, 116.18, 118.90, 109.25, 112.11, 94545500],
|
||||||
|
[1584106200, 119.00, 121.51, 100.40, 109.32, 113201500],
|
||||||
|
[1584365400, 93.90, 98.97, 88.43, 89.01, 102447500],
|
||||||
|
[1584451800, 88.00, 94.37, 79.20, 86.04, 119973000],
|
||||||
|
[1584538200, 77.80, 80.97, 70.10, 72.24, 118931000],
|
||||||
|
[1584624600, 74.94, 90.40, 71.69, 85.53, 150977500],
|
||||||
|
[1584711000, 87.64, 95.40, 85.16, 85.51, 141427500],
|
||||||
|
[1584970200, 86.72, 88.40, 82.10, 86.86, 82272500],
|
||||||
|
[1585056600, 95.46, 102.74, 94.80, 101.00, 114476000],
|
||||||
|
[1585143000, 109.05, 111.40, 102.22, 107.85, 106113500],
|
||||||
|
[1585229400, 109.48, 112.00, 102.45, 105.63, 86903500],
|
||||||
|
[1585315800, 101.00, 105.16, 98.81, 102.87, 71887000],
|
||||||
|
[1585575000, 102.05, 103.33, 98.25, 100.43, 59990500],
|
||||||
|
[1585661400, 100.25, 108.59, 99.40, 104.80, 88857500],
|
||||||
|
[1585747800, 100.80, 102.79, 95.02, 96.31, 66766000],
|
||||||
|
[1585834200, 96.21, 98.85, 89.28, 90.89, 99292000],
|
||||||
|
[1585920600, 101.90, 103.10, 93.68, 96.00, 112810500],
|
||||||
|
[1586179800, 102.24, 104.20, 99.59, 103.25, 74509000],
|
||||||
|
[1586266200, 109.00, 113.00, 106.47, 109.09, 89599000],
|
||||||
|
[1586352600, 110.84, 111.44, 106.67, 109.77, 63280000],
|
||||||
|
[1586439000, 112.42, 115.04, 111.42, 114.60, 68250000],
|
||||||
|
[1586784600, 118.03, 130.40, 116.11, 130.19, 112377000],
|
||||||
|
[1586871000, 139.79, 148.38, 138.49, 141.98, 152882500],
|
||||||
|
[1586957400, 148.40, 150.63, 142.00, 145.97, 117885000],
|
||||||
|
[1587043800, 143.39, 151.89, 141.34, 149.04, 103289500],
|
||||||
|
[1587130200, 154.46, 154.99, 149.53, 150.78, 65641000],
|
||||||
|
[1587389400, 146.54, 153.11, 142.44, 149.27, 73733000],
|
||||||
|
[1587475800, 146.02, 150.67, 134.76, 137.34, 101045500],
|
||||||
|
[1587562200, 140.80, 146.80, 137.74, 146.42, 70827500],
|
||||||
|
[1587648600, 145.52, 146.80, 140.63, 141.13, 66183500],
|
||||||
|
[1587735000, 142.16, 146.15, 139.64, 145.03, 66060000],
|
||||||
|
[1587994200, 147.52, 159.90, 147.00, 159.75, 103407000],
|
||||||
|
[1588080600, 159.13, 161.00, 151.34, 153.82, 76110000],
|
||||||
|
[1588167000, 158.03, 160.64, 156.63, 160.10, 81080000],
|
||||||
|
[1588253400, 171.04, 173.96, 152.70, 156.38, 142359500],
|
||||||
|
[1588339800, 151.00, 154.55, 136.61, 140.26, 162659000],
|
||||||
|
[1588599000, 140.20, 152.40, 139.60, 152.24, 96185500],
|
||||||
|
[1588685400, 157.96, 159.78, 152.44, 153.64, 84958500],
|
||||||
|
[1588771800, 155.30, 157.96, 152.22, 156.52, 55616000],
|
||||||
|
[1588858200, 155.44, 159.28, 154.47, 156.01, 57638500],
|
||||||
|
[1588944600, 158.75, 164.80, 157.40, 163.88, 80432500],
|
||||||
|
[1589203800, 158.10, 164.80, 157.00, 162.26, 82598000],
|
||||||
|
[1589290200, 165.40, 168.66, 161.60, 161.88, 79534500],
|
||||||
|
[1589376600, 164.17, 165.20, 152.66, 158.19, 95327500],
|
||||||
|
[1589463000, 156.00, 160.67, 152.80, 160.67, 68411000],
|
||||||
|
[1589549400, 158.07, 161.01, 157.31, 159.83, 52592000],
|
||||||
|
[1589808600, 165.56, 166.94, 160.78, 162.73, 58329000],
|
||||||
|
[1589895000, 163.03, 164.41, 161.22, 161.60, 48182500],
|
||||||
|
[1589981400, 164.10, 165.20, 162.36, 163.11, 36546500],
|
||||||
|
[1590067800, 163.20, 166.50, 159.20, 165.52, 61273000],
|
||||||
|
[1590154200, 164.43, 166.36, 162.40, 163.38, 49937500],
|
||||||
|
[1590499800, 166.90, 166.92, 163.14, 163.77, 40448500],
|
||||||
|
[1590586200, 164.17, 165.54, 157.00, 164.05, 57747500],
|
||||||
|
[1590672600, 162.70, 164.95, 160.34, 161.16, 36278000],
|
||||||
|
[1590759000, 161.75, 167.00, 160.84, 167.00, 58822500],
|
||||||
|
[1591018200, 171.60, 179.80, 170.82, 179.62, 74697500],
|
||||||
|
[1591104600, 178.94, 181.73, 174.20, 176.31, 67828000],
|
||||||
|
[1591191000, 177.62, 179.59, 176.02, 176.59, 39747500],
|
||||||
|
[1591277400, 177.98, 179.15, 171.69, 172.88, 44438500],
|
||||||
|
[1591363800, 175.57, 177.30, 173.24, 177.13, 39059500],
|
||||||
|
[1591623000, 183.80, 190.00, 181.83, 189.98, 70873500],
|
||||||
|
[1591709400, 188.00, 190.89, 184.79, 188.13, 56941000],
|
||||||
|
[1591795800, 198.38, 205.50, 196.50, 205.01, 92817000],
|
||||||
|
[1591882200, 198.04, 203.79, 194.40, 194.57, 79582500],
|
||||||
|
[1591968600, 196.00, 197.60, 182.52, 187.06, 83817000],
|
||||||
|
[1592227800, 183.56, 199.77, 181.70, 198.18, 78486000],
|
||||||
|
[1592314200, 202.37, 202.58, 192.48, 196.43, 70255500],
|
||||||
|
[1592400600, 197.54, 201.00, 196.51, 198.36, 49454000],
|
||||||
|
[1592487000, 200.60, 203.84, 198.89, 200.79, 48759500],
|
||||||
|
[1592573400, 202.56, 203.19, 198.27, 200.18, 43398500],
|
||||||
|
[1592832600, 199.99, 201.78, 198.00, 198.86, 31812000],
|
||||||
|
[1592919000, 199.78, 202.40, 198.80, 200.36, 31826500],
|
||||||
|
[1593005400, 198.82, 200.18, 190.63, 192.17, 54798000],
|
||||||
|
[1593091800, 190.85, 197.20, 187.43, 197.20, 46272500],
|
||||||
|
[1593178200, 198.96, 199.00, 190.97, 191.95, 44274500],
|
||||||
|
[1593437400, 193.80, 202.00, 189.70, 201.87, 45132000],
|
||||||
|
[1593523800, 201.30, 217.54, 200.75, 215.96, 84592500],
|
||||||
|
[1593610200, 216.60, 227.07, 216.10, 223.93, 66634500],
|
||||||
|
[1593696600, 244.30, 245.60, 237.12, 241.73, 86250500],
|
||||||
|
[1594042200, 255.34, 275.56, 253.21, 274.32, 102849500],
|
||||||
|
[1594128600, 281.00, 285.90, 267.34, 277.97, 107448500],
|
||||||
|
[1594215000, 281.00, 283.45, 262.27, 273.18, 81556500],
|
||||||
|
[1594301400, 279.40, 281.71, 270.26, 278.86, 58588000],
|
||||||
|
[1594387800, 279.20, 309.78, 275.20, 308.93, 116688000],
|
||||||
|
[1594647000, 331.80, 359.00, 294.22, 299.41, 194927000],
|
||||||
|
[1594733400, 311.20, 318.00, 286.20, 303.36, 117090500],
|
||||||
|
[1594819800, 308.60, 310.00, 291.40, 309.20, 81839000],
|
||||||
|
[1594906200, 295.43, 306.34, 293.20, 300.13, 71504000],
|
||||||
|
[1594992600, 302.69, 307.50, 298.00, 300.17, 46650000],
|
||||||
|
[1595251800, 303.80, 330.00, 297.60, 328.60, 85607000],
|
||||||
|
[1595338200, 327.99, 335.00, 311.60, 313.67, 80536000],
|
||||||
|
[1595424600, 319.80, 325.28, 312.40, 318.47, 70805500],
|
||||||
|
[1595511000, 335.79, 337.80, 296.15, 302.61, 121642500],
|
||||||
|
[1595597400, 283.20, 293.00, 273.31, 283.40, 96983000],
|
||||||
|
[1595856600, 287.00, 309.59, 282.60, 307.92, 80243500],
|
||||||
|
[1595943000, 300.80, 312.94, 294.88, 295.30, 79043500],
|
||||||
|
[1596029400, 300.20, 306.96, 297.40, 299.82, 47134500],
|
||||||
|
[1596115800, 297.60, 302.65, 294.20, 297.50, 38105000],
|
||||||
|
[1596202200, 303.00, 303.41, 284.20, 286.15, 61041000],
|
||||||
|
[1596461400, 289.84, 301.96, 288.88, 297.00, 44046500],
|
||||||
|
[1596547800, 299.00, 305.48, 292.40, 297.40, 42075000],
|
||||||
|
[1596634200, 298.60, 299.97, 293.66, 297.00, 24739000],
|
||||||
|
[1596720600, 298.17, 303.46, 295.45, 297.92, 29961500],
|
||||||
|
[1596807000, 299.91, 299.95, 283.00, 290.54, 44482000],
|
||||||
|
[1597066200, 289.60, 291.50, 277.17, 283.71, 37611500],
|
||||||
|
[1597152600, 279.20, 284.00, 273.00, 274.88, 43129000],
|
||||||
|
[1597239000, 294.00, 317.00, 287.00, 310.95, 109147000],
|
||||||
|
[1597325400, 322.20, 330.24, 313.45, 324.20, 102126500],
|
||||||
|
[1597411800, 333.00, 333.76, 325.33, 330.14, 62888000],
|
||||||
|
[1597671000, 335.40, 369.17, 334.57, 367.13, 101211500],
|
||||||
|
[1597757400, 379.80, 384.78, 369.02, 377.42, 82372500],
|
||||||
|
[1597843800, 373.00, 382.20, 368.24, 375.71, 61026500],
|
||||||
|
[1597930200, 372.14, 404.40, 371.41, 400.37, 103059000],
|
||||||
|
[1598016600, 408.95, 419.10, 405.01, 410.00, 107448000],
|
||||||
|
[1598275800, 425.26, 425.80, 385.50, 402.84, 100318000],
|
||||||
|
[1598362200, 394.98, 405.59, 393.60, 404.67, 53294500],
|
||||||
|
[1598448600, 412.00, 433.20, 410.73, 430.63, 71197000],
|
||||||
|
[1598535000, 436.09, 459.12, 428.50, 447.75, 118465000],
|
||||||
|
[1598621400, 459.02, 463.70, 437.30, 442.68, 100406000],
|
||||||
|
[1598880600, 444.61, 500.14, 440.11, 498.32, 118374400],
|
||||||
|
[1598967000, 502.14, 502.49, 470.51, 475.05, 89841100],
|
||||||
|
[1599053400, 478.99, 479.04, 405.12, 447.37, 96176100],
|
||||||
|
[1599139800, 407.23, 431.80, 402.00, 407.00, 87596100],
|
||||||
|
[1599226200, 402.81, 428.00, 372.02, 418.32, 110321900],
|
||||||
|
[1599571800, 356.00, 368.74, 329.88, 330.21, 115465700],
|
||||||
|
[1599658200, 356.60, 369.00, 341.51, 366.28, 79465800],
|
||||||
|
[1599744600, 386.21, 398.99, 360.56, 371.34, 84930600],
|
||||||
|
[1599831000, 381.94, 382.50, 360.50, 372.72, 60717500],
|
||||||
|
[1600090200, 380.95, 420.00, 373.30, 419.62, 83020600],
|
||||||
|
[1600176600, 436.56, 461.94, 430.70, 449.76, 97298200],
|
||||||
|
[1600263000, 439.87, 457.79, 435.31, 441.76, 72279300],
|
||||||
|
[1600349400, 415.60, 437.79, 408.00, 423.43, 76779200],
|
||||||
|
[1600435800, 447.94, 451.00, 428.80, 442.15, 86406800],
|
||||||
|
[1600695000, 453.13, 455.68, 407.07, 449.39, 109476800],
|
||||||
|
[1600781400, 429.60, 437.76, 417.60, 424.23, 79580800],
|
||||||
|
[1600867800, 405.16, 412.15, 375.88, 380.36, 95074200],
|
||||||
|
[1600954200, 363.80, 399.50, 351.30, 387.79, 96561100],
|
||||||
|
[1601040600, 393.47, 408.73, 391.30, 407.34, 67208500],
|
||||||
|
[1601299800, 424.62, 428.08, 415.55, 421.20, 49719600],
|
||||||
|
[1601386200, 416.00, 428.50, 411.60, 419.07, 50219300],
|
||||||
|
[1601472600, 421.32, 433.93, 420.47, 429.01, 48145600],
|
||||||
|
[1601559000, 440.76, 448.88, 434.42, 448.16, 50741500],
|
||||||
|
[1601645400, 421.39, 439.13, 415.00, 415.09, 71430000],
|
||||||
|
[1601904600, 423.35, 433.64, 419.33, 425.68, 44722800],
|
||||||
|
[1601991000, 423.79, 428.78, 406.05, 413.98, 49146300],
|
||||||
|
[1602077400, 419.87, 429.90, 413.85, 425.30, 43127700],
|
||||||
|
[1602163800, 438.44, 439.00, 425.30, 425.92, 40421100],
|
||||||
|
[1602250200, 430.13, 434.59, 426.46, 434.00, 28925700],
|
||||||
|
[1602509400, 442.00, 448.74, 438.58, 442.30, 38791100],
|
||||||
|
[1602595800, 443.35, 448.89, 436.60, 446.65, 34463700],
|
||||||
|
[1602682200, 449.78, 465.90, 447.35, 461.30, 47879700],
|
||||||
|
[1602768600, 450.31, 456.57, 442.50, 448.88, 35672400],
|
||||||
|
[1602855000, 454.44, 455.95, 438.85, 439.67, 32775900],
|
||||||
|
[1603114200, 446.24, 447.00, 428.87, 430.83, 36287800],
|
||||||
|
[1603200600, 431.75, 431.75, 419.05, 421.94, 31656300],
|
||||||
|
[1603287000, 422.70, 432.95, 421.25, 422.64, 32370500],
|
||||||
|
[1603373400, 441.92, 445.23, 424.51, 425.79, 39993200],
|
||||||
|
[1603459800, 421.84, 422.89, 407.38, 420.63, 33717000],
|
||||||
|
[1603719000, 411.63, 425.76, 410.00, 420.28, 28239200],
|
||||||
|
[1603805400, 423.76, 430.50, 420.10, 424.68, 22686500],
|
||||||
|
[1603891800, 416.48, 418.60, 406.00, 406.02, 25451400],
|
||||||
|
[1603978200, 409.96, 418.06, 406.46, 410.83, 22655300],
|
||||||
|
[1604064600, 406.90, 407.59, 379.11, 388.04, 42511300],
|
||||||
|
[1604327400, 394.00, 406.98, 392.30, 400.51, 29021100],
|
||||||
|
[1604413800, 409.73, 427.77, 406.69, 423.90, 34351700],
|
||||||
|
[1604500200, 430.62, 435.40, 417.10, 420.98, 32143100],
|
||||||
|
[1604586600, 428.30, 440.00, 424.00, 438.09, 28414500],
|
||||||
|
[1604673000, 436.10, 436.57, 424.28, 429.95, 21706000],
|
||||||
|
[1604932200, 439.50, 452.50, 421.00, 421.26, 34833000],
|
||||||
|
[1605018600, 420.09, 420.09, 396.03, 410.36, 30284200],
|
||||||
|
[1605105000, 416.45, 418.70, 410.58, 417.13, 17357700],
|
||||||
|
[1605191400, 415.05, 423.00, 409.52, 411.76, 19855100],
|
||||||
|
[1605277800, 410.85, 412.53, 401.66, 408.50, 19771100],
|
||||||
|
[1605537000, 408.93, 412.45, 404.09, 408.09, 26838600],
|
||||||
|
[1605623400, 460.17, 462.00, 433.01, 441.61, 61188300],
|
||||||
|
[1605709800, 448.35, 496.00, 443.50, 486.64, 78044000],
|
||||||
|
[1605796200, 492.00, 508.61, 487.57, 499.27, 62475300],
|
||||||
|
[1605882600, 497.99, 502.50, 489.06, 489.61, 32911900],
|
||||||
|
[1606141800, 503.50, 526.00, 501.79, 521.85, 50260300],
|
||||||
|
[1606228200, 540.40, 559.99, 526.20, 555.38, 53648500],
|
||||||
|
[1606314600, 550.06, 574.00, 545.37, 574.00, 48930200],
|
||||||
|
[1606487400, 581.16, 598.78, 578.45, 585.76, 37561100],
|
||||||
|
[1606746600, 602.21, 607.80, 554.51, 567.60, 63003100],
|
||||||
|
[1606833000, 597.59, 597.85, 572.05, 584.76, 40103500],
|
||||||
|
[1606919400, 556.44, 571.54, 541.21, 568.82, 47775700],
|
||||||
|
[1607005800, 590.02, 598.97, 582.43, 593.38, 42552000],
|
||||||
|
[1607092200, 591.01, 599.04, 585.50, 599.04, 29401300],
|
||||||
|
[1607351400, 604.92, 648.79, 603.05, 641.76, 56309700],
|
||||||
|
[1607437800, 625.51, 651.28, 618.50, 649.88, 64265000],
|
||||||
|
[1607524200, 653.69, 654.32, 588.00, 604.48, 71291200],
|
||||||
|
[1607610600, 574.37, 627.75, 566.34, 627.07, 67083200],
|
||||||
|
[1607697000, 615.01, 624.00, 596.80, 609.99, 46475000],
|
||||||
|
[1607956200, 619.00, 642.75, 610.20, 639.83, 52040600],
|
||||||
|
[1608042600, 643.28, 646.90, 623.80, 633.25, 45071500],
|
||||||
|
[1608129000, 628.23, 632.50, 605.00, 622.77, 42095800],
|
||||||
|
[1608215400, 628.19, 658.82, 619.50, 655.90, 56270100],
|
||||||
|
[1608301800, 668.90, 695.00, 628.54, 695.00, 222126200],
|
||||||
|
[1608561000, 666.24, 668.50, 646.07, 649.86, 58045300],
|
||||||
|
[1608647400, 648.00, 649.88, 614.23, 640.34, 51716000],
|
||||||
|
[1608733800, 632.20, 651.50, 622.57, 645.98, 33173000],
|
||||||
|
[1608820200, 642.99, 666.09, 641.00, 661.77, 22865600],
|
||||||
|
[1609165800, 674.51, 681.40, 660.80, 663.69, 32278600],
|
||||||
|
[1609252200, 661.00, 669.90, 655.00, 665.99, 22910800],
|
||||||
|
[1609338600, 672.00, 696.60, 668.36, 694.78, 42846000],
|
||||||
|
[1609425000, 699.99, 718.72, 691.12, 705.67, 49649900],
|
||||||
|
[1609770600, 719.46, 744.49, 717.19, 729.77, 48638200],
|
||||||
|
[1609857000, 723.66, 740.84, 719.20, 735.11, 32245200],
|
||||||
|
[1609943400, 758.49, 774.00, 749.10, 755.98, 44700000],
|
||||||
|
[1610029800, 777.63, 816.99, 775.20, 816.04, 51498900],
|
||||||
|
[1610116200, 856.00, 884.49, 838.39, 880.02, 75055500],
|
||||||
|
[1610375400, 849.40, 854.43, 803.62, 811.19, 59301600],
|
||||||
|
[1610461800, 831.00, 868.00, 827.34, 849.44, 46270700],
|
||||||
|
[1610548200, 852.76, 860.47, 832.00, 854.41, 33312500],
|
||||||
|
[1610634600, 843.39, 863.00, 838.75, 845.00, 31266300],
|
||||||
|
[1610721000, 852.00, 859.90, 819.10, 826.16, 38777600],
|
||||||
|
[1611066600, 837.80, 850.00, 833.00, 844.55, 25367000],
|
||||||
|
[1611153000, 858.74, 859.50, 837.28, 850.45, 25665900],
|
||||||
|
[1611239400, 855.00, 855.72, 841.42, 844.99, 20521100],
|
||||||
|
[1611325800, 834.31, 848.00, 828.62, 846.64, 20066500],
|
||||||
|
[1611585000, 855.00, 900.40, 838.82, 880.80, 41173400],
|
||||||
|
[1611671400, 891.38, 895.90, 871.60, 883.09, 23131600],
|
||||||
|
[1611757800, 870.35, 891.50, 858.66, 864.16, 27334000],
|
||||||
|
[1611844200, 820.00, 848.00, 801.00, 835.43, 26378000],
|
||||||
|
[1611930600, 830.00, 842.41, 780.10, 793.53, 34990800],
|
||||||
|
[1612189800, 814.29, 842.00, 795.56, 839.81, 25391400],
|
||||||
|
[1612276200, 844.68, 880.50, 842.20, 872.79, 24346200],
|
||||||
|
[1612362600, 877.02, 878.08, 853.06, 854.69, 18343500],
|
||||||
|
[1612449000, 855.00, 856.50, 833.42, 849.99, 15812700],
|
||||||
|
[1612535400, 845.00, 864.77, 838.97, 852.23, 18566600],
|
||||||
|
[1612794600, 869.67, 877.77, 854.75, 863.42, 20161700],
|
||||||
|
[1612881000, 855.12, 859.80, 841.75, 849.46, 15157700],
|
||||||
|
[1612967400, 843.64, 844.82, 800.02, 804.82, 36216100],
|
||||||
|
[1613053800, 812.44, 829.88, 801.73, 811.66, 21622800],
|
||||||
|
[1613140200, 801.26, 817.33, 785.33, 816.12, 23768300],
|
||||||
|
[1613485800, 818.00, 821.00, 792.44, 796.22, 19802300],
|
||||||
|
[1613572200, 779.09, 799.84, 762.01, 798.15, 25996500],
|
||||||
|
[1613658600, 780.90, 794.69, 776.27, 787.38, 17957100],
|
||||||
|
[1613745000, 795.00, 796.79, 777.37, 781.30, 18958300],
|
||||||
|
[1614004200, 762.64, 768.50, 710.20, 714.50, 37269700],
|
||||||
|
[1614090600, 662.13, 713.61, 619.00, 698.84, 66606900],
|
||||||
|
[1614177000, 711.85, 745.00, 694.17, 742.02, 36767000],
|
||||||
|
[1614263400, 726.15, 737.21, 670.58, 682.22, 39023900],
|
||||||
|
[1614349800, 700.00, 706.70, 659.51, 675.50, 41089200],
|
||||||
|
[1614609000, 690.11, 719.00, 685.05, 718.43, 27136200],
|
||||||
|
[1614695400, 718.28, 721.11, 685.00, 686.44, 23732200],
|
||||||
|
[1614781800, 687.99, 700.70, 651.71, 653.20, 30208000],
|
||||||
|
[1614868200, 655.80, 668.45, 600.00, 621.44, 65919500],
|
||||||
|
[1614954600, 626.06, 627.84, 539.49, 597.95, 89396500],
|
||||||
|
[1615213800, 600.55, 620.13, 558.79, 563.00, 51787000],
|
||||||
|
[1615300200, 608.18, 678.09, 595.21, 673.58, 67523300],
|
||||||
|
[1615386600, 700.30, 717.85, 655.06, 668.06, 60605700],
|
||||||
|
[1615473000, 699.40, 702.50, 677.18, 699.60, 36253900],
|
||||||
|
[1615559400, 670.00, 694.88, 666.14, 693.73, 33583800],
|
||||||
|
[1615815000, 694.09, 713.18, 684.04, 707.94, 29335600],
|
||||||
|
[1615901400, 703.35, 707.92, 671.00, 676.88, 32195700],
|
||||||
|
[1615987800, 656.87, 703.73, 651.01, 701.81, 40372500],
|
||||||
|
[1616074200, 684.29, 689.23, 652.00, 653.16, 33224800],
|
||||||
|
[1616160600, 646.60, 657.23, 624.62, 654.87, 42894000],
|
||||||
|
[1616419800, 684.59, 699.62, 668.75, 670.00, 39512200],
|
||||||
|
[1616506200, 675.77, 677.80, 657.51, 662.16, 30491900],
|
||||||
|
[1616592600, 667.91, 668.02, 630.11, 630.27, 33795200],
|
||||||
|
[1616679000, 613.00, 645.50, 609.50, 640.39, 39224900],
|
||||||
|
[1616765400, 641.87, 643.82, 599.89, 618.71, 33852800],
|
||||||
|
[1617024600, 615.64, 616.48, 596.02, 611.29, 28637000],
|
||||||
|
[1617111000, 601.75, 637.66, 591.01, 635.62, 39432400],
|
||||||
|
[1617197400, 646.62, 672.00, 641.11, 667.93, 33337300],
|
||||||
|
[1617283800, 688.37, 692.42, 659.42, 661.75, 35298400],
|
||||||
|
[1617629400, 707.71, 708.16, 684.70, 691.05, 41842800],
|
||||||
|
[1617715800, 690.30, 696.55, 681.37, 691.62, 28271800],
|
||||||
|
[1617802200, 687.00, 691.38, 667.84, 670.97, 26309400],
|
||||||
|
[1617888600, 677.38, 689.55, 671.65, 683.80, 23924300],
|
||||||
|
[1617975000, 677.77, 680.97, 669.43, 677.02, 21437100],
|
||||||
|
[1618234200, 685.70, 704.80, 682.09, 701.98, 29135700],
|
||||||
|
[1618320600, 712.70, 763.00, 710.66, 762.32, 44652800],
|
||||||
|
[1618407000, 770.70, 780.79, 728.03, 732.23, 49017400],
|
||||||
|
[1618493400, 743.10, 743.69, 721.31, 738.85, 27848900],
|
||||||
|
[1618579800, 728.65, 749.41, 724.60, 739.78, 27979500],
|
||||||
|
[1618839000, 719.60, 725.40, 691.80, 714.63, 39686200],
|
||||||
|
[1618925400, 717.42, 737.25, 710.69, 718.99, 35609000],
|
||||||
|
[1619011800, 704.77, 744.84, 698.00, 744.12, 31215500],
|
||||||
|
[1619098200, 741.50, 753.77, 718.04, 719.69, 35590300],
|
||||||
|
[1619184600, 719.80, 737.36, 715.46, 729.40, 28370000],
|
||||||
|
[1619443800, 741.00, 749.30, 732.61, 738.20, 31038500],
|
||||||
|
[1619530200, 717.96, 724.00, 703.35, 704.74, 29437000],
|
||||||
|
[1619616600, 696.41, 708.50, 693.60, 694.40, 22271000],
|
||||||
|
[1619703000, 699.51, 702.25, 668.50, 677.00, 28845400],
|
||||||
|
[1619789400, 667.59, 715.47, 666.14, 709.44, 40758700],
|
||||||
|
[1620048600, 703.80, 706.00, 680.50, 684.90, 27043100],
|
||||||
|
[1620135000, 678.94, 683.45, 657.70, 673.60, 29739300],
|
||||||
|
[1620221400, 681.06, 685.30, 667.34, 670.94, 21901900],
|
||||||
|
[1620307800, 680.76, 681.02, 650.00, 663.54, 27784600],
|
||||||
|
[1620394200, 665.80, 690.00, 660.22, 672.37, 23469200],
|
||||||
|
[1620653400, 664.90, 665.05, 627.61, 629.04, 31392400],
|
||||||
|
[1620739800, 599.24, 627.10, 595.60, 617.20, 46503900],
|
||||||
|
[1620826200, 602.49, 620.41, 586.77, 589.89, 33823600],
|
||||||
|
[1620912600, 601.54, 606.46, 559.65, 571.69, 44184900],
|
||||||
|
[1620999000, 583.41, 592.87, 570.46, 589.74, 33370900],
|
||||||
|
[1621258200, 575.55, 589.73, 561.20, 576.83, 32390400],
|
||||||
|
[1621344600, 568.00, 596.25, 563.38, 577.87, 36830600],
|
||||||
|
[1621431000, 552.55, 566.21, 546.98, 563.46, 39578400],
|
||||||
|
[1621517400, 575.00, 588.85, 571.07, 586.78, 30821100],
|
||||||
|
[1621603800, 596.11, 596.68, 580.00, 580.88, 26030600],
|
||||||
|
[1621863000, 581.60, 614.48, 573.65, 606.44, 34558100],
|
||||||
|
[1621949400, 607.31, 613.99, 595.71, 604.69, 28005900],
|
||||||
|
[1622035800, 607.56, 626.17, 601.50, 619.13, 28639300],
|
||||||
|
[1622122200, 620.24, 631.13, 616.21, 630.85, 26370600],
|
||||||
|
[1622208600, 628.50, 635.59, 622.38, 625.22, 22737000],
|
||||||
|
[1622554200, 627.80, 633.80, 620.55, 623.90, 18084900],
|
||||||
|
[1622640600, 620.13, 623.36, 599.14, 605.12, 23302800],
|
||||||
|
[1622727000, 601.80, 604.55, 571.22, 572.84, 30111900],
|
||||||
|
[1622813400, 579.71, 600.61, 577.20, 599.05, 24036900],
|
||||||
|
[1623072600, 591.83, 610.00, 582.88, 605.13, 22543700],
|
||||||
|
[1623159000, 623.01, 623.09, 595.50, 603.59, 26053400],
|
||||||
|
[1623245400, 602.17, 611.79, 597.63, 598.78, 16584600],
|
||||||
|
[1623331800, 603.88, 616.59, 600.50, 610.12, 23919600],
|
||||||
|
[1623418200, 610.23, 612.56, 601.52, 609.89, 16205300],
|
||||||
|
[1623677400, 612.23, 625.49, 609.18, 617.69, 20424000],
|
||||||
|
[1623763800, 616.69, 616.79, 598.23, 599.36, 17764100],
|
||||||
|
[1623850200, 597.54, 608.50, 593.50, 604.87, 22144100],
|
||||||
|
[1623936600, 601.89, 621.47, 601.34, 616.60, 22701400],
|
||||||
|
[1624023000, 613.37, 628.35, 611.80, 623.31, 24560900],
|
||||||
|
[1624282200, 624.48, 631.39, 608.88, 620.83, 24812700],
|
||||||
|
[1624368600, 618.25, 628.57, 615.50, 623.71, 19158900],
|
||||||
|
[1624455000, 632.00, 657.20, 630.04, 656.57, 31099200],
|
||||||
|
[1624541400, 674.99, 697.62, 667.61, 679.82, 45982400],
|
||||||
|
[1624627800, 689.58, 693.81, 668.70, 671.87, 32496700],
|
||||||
|
[1624887000, 671.64, 694.70, 670.32, 688.72, 21628200],
|
||||||
|
[1624973400, 684.65, 687.51, 675.89, 680.76, 17381300],
|
||||||
|
[1625059800, 679.77, 692.81, 678.14, 679.70, 18924900],
|
||||||
|
[1625146200, 683.92, 687.99, 672.80, 677.92, 18634500],
|
||||||
|
[1625232600, 678.98, 700.00, 673.26, 678.90, 27054500],
|
||||||
|
[1625578200, 681.71, 684.00, 651.40, 659.58, 23284500],
|
||||||
|
[1625664600, 664.27, 665.70, 638.32, 644.65, 18792000],
|
||||||
|
[1625751000, 628.37, 654.43, 620.46, 652.81, 22773300],
|
||||||
|
[1625837400, 653.18, 658.91, 644.69, 656.95, 18140500],
|
||||||
|
[1626096600, 662.20, 687.24, 662.16, 685.70, 25927000],
|
||||||
|
[1626183000, 686.32, 693.28, 666.30, 668.54, 20966100],
|
||||||
|
[1626269400, 670.75, 678.61, 652.84, 653.38, 21641200],
|
||||||
|
[1626355800, 658.39, 666.14, 637.88, 650.60, 20209600],
|
||||||
|
[1626442200, 654.68, 656.70, 642.20, 644.22, 16371000],
|
||||||
|
[1626701400, 629.89, 647.20, 621.29, 646.22, 21297100],
|
||||||
|
[1626787800, 651.99, 662.39, 640.50, 660.50, 15487100],
|
||||||
|
[1626874200, 659.61, 664.86, 650.29, 655.29, 13953300],
|
||||||
|
[1626960600, 656.44, 662.17, 644.60, 649.26, 15105700],
|
||||||
|
[1627047000, 646.36, 648.80, 637.30, 643.38, 14604900],
|
||||||
|
[1627306200, 650.97, 668.20, 647.11, 657.62, 25336600],
|
||||||
|
[1627392600, 663.40, 666.50, 627.24, 644.78, 32813300],
|
||||||
|
[1627479000, 647.00, 654.97, 639.40, 646.98, 16006600],
|
||||||
|
[1627565400, 649.79, 683.69, 648.80, 677.35, 30394600],
|
||||||
|
[1627651800, 671.76, 697.53, 669.00, 687.20, 29600500],
|
||||||
|
[1627911000, 700.00, 726.94, 698.40, 709.67, 33615800],
|
||||||
|
[1627997400, 719.00, 722.65, 701.01, 709.74, 21620300],
|
||||||
|
[1628083800, 711.00, 724.90, 708.93, 710.92, 17002600],
|
||||||
|
[1628170200, 716.00, 720.95, 711.41, 714.63, 12919600],
|
||||||
|
[1628256600, 711.90, 716.33, 697.63, 699.10, 15576200],
|
||||||
|
[1628515800, 710.17, 719.03, 705.13, 713.76, 14715300],
|
||||||
|
[1628602200, 713.99, 716.59, 701.88, 709.99, 13432300],
|
||||||
|
[1628688600, 712.71, 715.18, 704.21, 707.82, 9800600],
|
||||||
|
[1628775000, 706.34, 722.80, 699.40, 722.25, 17459100],
|
||||||
|
[1628861400, 723.71, 729.90, 714.34, 717.17, 16698900],
|
||||||
|
[1629120600, 705.07, 709.50, 676.40, 686.17, 22677400],
|
||||||
|
[1629207000, 672.66, 674.58, 648.84, 665.71, 23721300],
|
||||||
|
[1629293400, 669.75, 695.77, 669.35, 688.99, 20349400],
|
||||||
|
[1629379800, 678.21, 686.55, 667.59, 673.47, 14313500],
|
||||||
|
[1629466200, 682.85, 692.13, 673.70, 680.26, 14781800],
|
||||||
|
[1629725400, 685.44, 712.13, 680.75, 706.30, 20264900],
|
||||||
|
[1629811800, 710.68, 715.22, 702.64, 708.49, 13083100],
|
||||||
|
[1629898200, 707.03, 716.97, 704.00, 711.20, 12645600],
|
||||||
|
[1629984600, 708.31, 715.40, 697.62, 701.16, 13214300],
|
||||||
|
[1630071000, 705.00, 715.00, 702.10, 711.92, 13762100],
|
||||||
|
[1630330200, 714.72, 731.00, 712.73, 730.91, 18604200],
|
||||||
|
[1630416600, 733.00, 740.39, 726.44, 735.72, 20855400],
|
||||||
|
[1630503000, 734.08, 741.99, 731.27, 734.09, 13204300],
|
||||||
|
[1630589400, 734.50, 740.97, 730.54, 732.39, 12777300],
|
||||||
|
[1630675800, 732.25, 734.00, 724.20, 733.57, 15246100],
|
||||||
|
[1631021400, 740.00, 760.20, 739.26, 752.92, 20039800],
|
||||||
|
[1631107800, 761.58, 764.45, 740.77, 753.87, 18793000],
|
||||||
|
[1631194200, 753.41, 762.10, 751.63, 754.86, 14077700],
|
||||||
|
[1631280600, 759.60, 762.61, 734.52, 736.27, 15114300],
|
||||||
|
[1631539800, 740.21, 744.78, 708.85, 743.00, 22952500],
|
||||||
|
[1631626200, 742.57, 754.47, 736.40, 744.49, 18524900],
|
||||||
|
[1631712600, 745.00, 756.86, 738.36, 755.83, 15357700],
|
||||||
|
[1631799000, 752.83, 758.91, 747.61, 756.99, 13923400],
|
||||||
|
[1631885400, 757.15, 761.04, 750.00, 759.49, 28204200],
|
||||||
|
[1632144600, 734.56, 742.00, 718.62, 730.17, 24757700],
|
||||||
|
[1632231000, 734.79, 744.74, 730.44, 739.38, 16330700],
|
||||||
|
[1632317400, 743.53, 753.67, 739.12, 751.94, 15126300],
|
||||||
|
[1632403800, 755.00, 758.20, 747.92, 753.64, 11947500],
|
||||||
|
[1632490200, 745.89, 774.80, 744.56, 774.39, 21373000],
|
||||||
|
[1632749400, 773.12, 799.00, 769.31, 791.36, 28070700],
|
||||||
|
[1632835800, 787.20, 795.64, 766.18, 777.56, 25381400],
|
||||||
|
[1632922200, 779.80, 793.50, 770.68, 781.31, 20942900],
|
||||||
|
[1633008600, 781.00, 789.13, 775.00, 775.48, 17956000],
|
||||||
|
[1633095000, 778.40, 780.78, 763.59, 775.22, 17031400],
|
||||||
|
[1633354200, 796.50, 806.97, 776.12, 781.53, 30483300],
|
||||||
|
[1633440600, 784.80, 797.31, 774.20, 780.59, 18432600],
|
||||||
|
[1633527000, 776.20, 786.66, 773.22, 782.75, 14632800],
|
||||||
|
[1633613400, 785.46, 805.00, 783.38, 793.61, 19195800],
|
||||||
|
[1633699800, 796.21, 796.38, 780.91, 785.49, 16711100],
|
||||||
|
[1633959000, 787.65, 801.24, 785.50, 791.94, 14175800],
|
||||||
|
[1634064472, 800.93, 812.32, 796.57, 811.41, 17289281],
|
||||||
|
];
|
||||||
|
|
||||||
|
static List<CandleData> get candles => _rawData
|
||||||
|
.map((row) => CandleData(
|
||||||
|
timestamp: row[0] * 1000,
|
||||||
|
open: row[1]?.toDouble(),
|
||||||
|
high: row[2]?.toDouble(),
|
||||||
|
low: row[3]?.toDouble(),
|
||||||
|
close: row[4]?.toDouble(),
|
||||||
|
volume: row[5]?.toDouble(),
|
||||||
|
))
|
||||||
|
.toList();
|
||||||
|
}
|
64
lib/data/public_monero_nodes.json
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
"http://xmrnode2fjwmltxetalulcryyvdos437fnyoj5gzg3ecn22uhnwxgyad.onion:18081",
|
||||||
|
"http://moneronodeuomqv3jcb2jqslmcnrtkiqslv6hwh3cu57ugk6t55xchid.onion:18081",
|
||||||
|
"http://sneed5kqm3m2jlt2j3shagzz7zmsppicmuf4fmfwryy45kmsrvjfpdid.onion:18089",
|
||||||
|
"http://monerob2hm5lns5m4maiv3dauroadpofdrpftp5wd2cfqt332ydyeyid.onion:18089",
|
||||||
|
"http://3qnnb5zicqahxtn7bie6sizdjru3pmtwmzzgwhei7v66s36tafdjp7ad.onion:18081",
|
||||||
|
"http://l72pup26odlrbhsx543m3f5s4m6opwpkeklvbddddwo6b4qyche7zcyd.onion:18081",
|
||||||
|
"http://rd4mzz6vxhchnbhz66w6u76632yhgkzypmvpcfbgrfbacmr5ob2f4zqd.onion:18089",
|
||||||
|
"http://aclc4e2jhhtr44guufbnwk5bzwhaecinax4yip4wr4tjn27sjsfg6zqd.onion:18089",
|
||||||
|
"http://n7jagir3cwgylxhhwc63hu5ptlj6kunofckf25fv5pizx2rmjvlholid.onion:18089",
|
||||||
|
"http://monerof5eqykw3hfrjmb66v7r4ft4hlilfzjw5q4huwolttutjuc5dyd.onion:18089",
|
||||||
|
"http://zqitog3olj5jmeu76jcpthuksplnzlcoucyhska74ny2vdcyeln33bid.onion:18089",
|
||||||
|
"http://gtbjuomix62tppqmpnb65ak2vqv2fxaca4g3ijmxboy5j6bseu5d4sad.onion:18089",
|
||||||
|
"http://mexymskdcpunttj3kozziuw62aryumjggulew2kafk7rmvkmegrrgfyd.onion:18081",
|
||||||
|
"http://fkb5pynm4nr5mb54fw6eep3os7ux5ax7v36jgjcrq7cpilehwd5lgiad.onion:18083",
|
||||||
|
"http://4egxv4e3idoh2xihko5wkhx5s2i5mmrw3cc3bia3cgc5ttwgqhtgi7ad.onion:18089",
|
||||||
|
"http://xrft3jrcdrfwvry4aocxcswvwturaqxelesmtdk34j4646tajmc6rxyd.onion:18089",
|
||||||
|
"http://lrtrju7tz72422sjmwakygfu7xgskaawiqmfulmssfzx7aofatfkmvid.onion:18089",
|
||||||
|
"http://monerujods7mbghwe6cobdr6ujih6c22zu5rl7zshmizz2udf7v7fsad.onion:18081",
|
||||||
|
"http://hn3lysds52kplnso5eyiimbmakr67c3owqhakimc7xio5nzb5plbl6yd.onion:18089",
|
||||||
|
"http://so55gc5hiftcat5xagflyiuyaw3uc7jtj7ornxf757xwxhyea2clvzqd.onion:18089",
|
||||||
|
"http://fz2lbxvjob6ifeonngaep2xvf2ypxjjn23i3ncblcxjreovev56ubyyd.onion:18089",
|
||||||
|
"https://libertytmtitynvmnto2k42liys5fenb3wabaozmmmksyrc7jvgmjiqd.onion:18089",
|
||||||
|
"http://glnoqirjvs4cofkj3zy3ttq2s7fzcj67dfcletshrh6gwpkkhtbulpyd.onion:18089",
|
||||||
|
"http://libertytmtitynvmnto2k42liys5fenb3wabaozmmmksyrc7jvgmjiqd.onion:18089",
|
||||||
|
"http://ncbkcouzbqzcskr3dy26bvgzztpt7ywtjd2ly2bpv5c5zfxoht7gthad.onion:18081",
|
||||||
|
"http://moneroexnovtlp4datcwbgjznnulgm7q34wcl6r4gcvccruhkceb2xyd.onion:18089",
|
||||||
|
"http://plowsof3t5hogddwabaeiyrno25efmzfxyro2vligremt7sxpsclfaid.onion:18089",
|
||||||
|
"http://plowsoffjexmxalw73tkjmf422gq6575fc7vicuu4javzn2ynnte6tyd.onion:18089",
|
||||||
|
"http://ode6i5zdrm4xjqeubacrwmgnihzpfsfvpdf4kvcphhnrqkmrhq5idxyd.onion:18089",
|
||||||
|
"http://r6ou6dckycorsauaelpej2k4z2e2jkk62pbkskco34anmnkgl2tlaiqd.onion:18081",
|
||||||
|
"http://ulmlrardljg3r6urejlm6c2tikp3krvkupmdnueahmj33vl5ztez7jqd.onion:18081",
|
||||||
|
"http://2wi527wss2ysexkmyjveki7ypaikz5x567eiqnnl76om2zuyd4d3dpqd.onion:18081",
|
||||||
|
"http://43n3jitkea7hvx72xjj5ey2yxhevdlwmv3nail5fp7wzigt6ptega5qd.onion:18089",
|
||||||
|
"http://sneedxmr3mur3ypoto7o3xzqyvodidto5zlosxeesu4upunax5xnskid.onion:18089",
|
||||||
|
"https://plowsof3t5hogddwabaeiyrno25efmzfxyro2vligremt7sxpsclfaid.onion:18089",
|
||||||
|
"http://v5zluoefmjk6c24r3o4qsjzzyux233c5v332vrozu454xoohron5moid.onion:18089",
|
||||||
|
"http://zkvdadvagi5r757w7ewvpomhjgxab3d4xh3npo5rcxc4d7mwuee635qd.onion:18081",
|
||||||
|
"http://daturab6drmkhyeia4ch5gvfc2f3wgo6bhjrv3pz6n7kxmvoznlkq4yd.onion:18081",
|
||||||
|
"http://plowsofe6cleftfmk2raiw5h2x66atrik3nja4bfd3zrfa2hdlgworad.onion:18089",
|
||||||
|
"http://3brwzfhssrqacnufjwvr2vpbawpx6ifzqhd7nzfwa3twaivn443z4nad.onion:18089",
|
||||||
|
"http://6jvn5tinwxnp723vnsvekroaniq7qkag7nkdqmcwlbinxnpeonaowayd.onion:18089",
|
||||||
|
"http://kbs3vzc6cwxmrw7ig5r4wgt5brxl6km2ccr7y2mqf3aznriz42pvwgyd.onion:18089",
|
||||||
|
"http://waovyhgbeorma64p6cbomev4zfydua4s35gd3xdc5igtjqetzvwbtkyd.onion:18089",
|
||||||
|
"http://twiy7ceov5gskxlsqfsmlubbivse5duo3ro7xmrimrgfp3whwfmzi4id.onion:18081",
|
||||||
|
"http://rlz6pa5tfs6hxbnsvzad32gt3ck3ahvgs5a73clfztezz42eo664mzid.onion:18089",
|
||||||
|
"http://azj7425hcyxzw6jxtvf3a4aw6t4rbdazywngq7jtrsoysk24btfycvad.onion:18089",
|
||||||
|
"http://dtrnd4in2igrtfx2c45ghf2drns3doddmcsfy6b5gjw5iinukd33slqd.onion:18081",
|
||||||
|
"http://moneronkvv2hu2anvcc5b4qd5y7strnc2ob6khqsrtikmhocyvjpdjyd.onion:18089",
|
||||||
|
"http://xmrpoker6fqd7kh6gxv4dtr4kaue3pdmuzbaqorvlan4as3oh3f2ftqd.onion:18081",
|
||||||
|
"http://vfp7tc36qcyd2x7r3k4twsixkrubvipp3zm5zt7wgzrjrbaon7vwgkyd.onion:18081",
|
||||||
|
"http://nj4lnfbo2mkguxv6wvzpmnjwibz2jipy7kyma4qkposr6qef7suqf3ad.onion:18081",
|
||||||
|
"http://csxmritzk2qdgqmou2vwyrwu65xabimvmeniestaartks4fhlocfoeyd.onion:18081",
|
||||||
|
"http://qvb4rowgpqzz2m4vayovmtx2ag6l2lsdjhhbzb2nvg3ikybclb4ttdad.onion:18081",
|
||||||
|
"http://monero3x5yrb7tsalxx64tr2qhfw54xy3eudhswvpaskfvsdk2tzb3id.onion:18089",
|
||||||
|
"http://6dsdenp6vjkvqzy4wzsnzn6wixkdzihx3khiumyzieauxuxslmcaeiad.onion:18081",
|
||||||
|
"http://h5oepjpydwyacbks5vnanx2qw4rboqz53eksljflbkn5zrwcz2575xqd.onion:18089",
|
||||||
|
"http://hashvaultsvg2rinvxz7kos77hdfm6zrd5yco3tx2yh2linsmusfwyad.onion:18081",
|
||||||
|
"http://nrf3tly3ypsow73lfijhpx4cqll3l4xiuf3r4xbfzkdkgzy4gq642sad.onion:18089",
|
||||||
|
"http://27ihnchx3loqzue7hekggodxpazfb3npcosfxzrar5fom4zapd6rgcad.onion:18081",
|
||||||
|
"http://4kzkwyooeth3lxajxs7pmcriq6cgvnbex2vojum2uqflczsci4dlreyd.onion:18089"
|
||||||
|
]
|
||||||
|
}
|
155
lib/haveno_app.dart
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_background_service/flutter_background_service.dart';
|
||||||
|
import 'package:haveno_app/main.dart';
|
||||||
|
import 'package:haveno_app/services/secure_storage_service.dart';
|
||||||
|
import 'package:haveno_app/views/desktop_lifecycle.dart';
|
||||||
|
import 'package:haveno_app/views/mobile_lifecycle.dart';
|
||||||
|
import 'package:haveno_app/views/screens/onboarding_screen.dart';
|
||||||
|
import 'package:haveno_app/views/screens/seednode_setup_screen.dart';
|
||||||
|
|
||||||
|
class HavenoApp extends StatefulWidget {
|
||||||
|
const HavenoApp({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_HavenoAppState createState() => _HavenoAppState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HavenoAppState extends State<HavenoApp> with WidgetsBindingObserver {
|
||||||
|
bool? _onboardingComplete; // Nullable
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
|
||||||
|
// Load the onboarding status asynchronously and update the state
|
||||||
|
SecureStorageService().readOnboardingStatus().then((onboardingComplete) {
|
||||||
|
setState(() {
|
||||||
|
_onboardingComplete = onboardingComplete ?? false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildAppContent() {
|
||||||
|
// Check if _onboardingComplete is null (while loading)
|
||||||
|
if (_onboardingComplete == null) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
|
||||||
|
return MaterialApp(
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
|
navigatorKey: navigatorKey,
|
||||||
|
theme: ThemeData(
|
||||||
|
useMaterial3: true,
|
||||||
|
colorScheme: ColorScheme.fromSeed(
|
||||||
|
primary: const Color(0xFFF4511E),
|
||||||
|
seedColor: const Color(0xFFF4511E),
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
),
|
||||||
|
scaffoldBackgroundColor: const Color(0xFF303030),
|
||||||
|
appBarTheme: const AppBarTheme(
|
||||||
|
backgroundColor: Color(0xFF303030),
|
||||||
|
),
|
||||||
|
drawerTheme: const DrawerThemeData(
|
||||||
|
backgroundColor: Color(0xFF303030),
|
||||||
|
),
|
||||||
|
navigationBarTheme: NavigationBarThemeData(
|
||||||
|
backgroundColor: const Color(0xFF303030).withOpacity(0.5),
|
||||||
|
indicatorShape: const StadiumBorder(),
|
||||||
|
),
|
||||||
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: const Color(0xFFF4511E),
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
cardTheme: const CardTheme(
|
||||||
|
color: Color(0xFF424242),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Use _onboardingComplete to decide the home widget
|
||||||
|
home: _onboardingComplete == true ? SeedNodeSetupScreen() : OnboardingScreen(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (Platform.isAndroid || Platform.isIOS) {
|
||||||
|
return MobileLifecycleWidget(
|
||||||
|
child: _buildAppContent(),
|
||||||
|
builder: (context, child) => child,
|
||||||
|
);
|
||||||
|
} else if (Platform.isMacOS || Platform.isLinux || Platform.isWindows) {
|
||||||
|
return DesktopLifecycleWidget(
|
||||||
|
child: _buildAppContent(),
|
||||||
|
builder: (context, child) => child,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
case AppLifecycleState.resumed:
|
||||||
|
if (Platform.isAndroid || Platform.isIOS) {
|
||||||
|
final service = FlutterBackgroundService();
|
||||||
|
bool isRunning = await service.isRunning();
|
||||||
|
if (!isRunning) {
|
||||||
|
service.startService();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print("App resumed");
|
||||||
|
break;
|
||||||
|
case AppLifecycleState.paused:
|
||||||
|
print("App paused");
|
||||||
|
break;
|
||||||
|
case AppLifecycleState.inactive:
|
||||||
|
print("App inactive");
|
||||||
|
break;
|
||||||
|
case AppLifecycleState.detached:
|
||||||
|
print("App detached");
|
||||||
|
break;
|
||||||
|
case AppLifecycleState.hidden:
|
||||||
|
print("App hidden");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the method always returns a Future
|
||||||
|
return Future.value();
|
||||||
|
}
|
||||||
|
}
|
195
lib/main.dart
Normal file
|
@ -0,0 +1,195 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:ui';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_background_service/flutter_background_service.dart';
|
||||||
|
import 'package:haveno/haveno_client.dart';
|
||||||
|
import 'package:haveno_app/haveno_app.dart';
|
||||||
|
import 'package:haveno_app/background_services.dart';
|
||||||
|
import 'package:haveno_app/models/schema.dart';
|
||||||
|
import 'package:haveno_app/providers/haveno_client_providers/xmr_connections_provider.dart';
|
||||||
|
import 'package:haveno_app/providers/haveno_daemon_provider.dart';
|
||||||
|
import 'package:haveno_app/providers/haveno_client_providers/dispute_agents_provider.dart';
|
||||||
|
import 'package:haveno_app/providers/haveno_client_providers/disputes_provider.dart';
|
||||||
|
import 'package:haveno_app/providers/haveno_client_providers/offers_provider.dart';
|
||||||
|
import 'package:haveno_app/providers/haveno_client_providers/payment_accounts_provider.dart';
|
||||||
|
import 'package:haveno_app/providers/haveno_client_providers/price_provider.dart';
|
||||||
|
import 'package:haveno_app/providers/haveno_client_providers/trade_statistics_provider.dart';
|
||||||
|
import 'package:haveno_app/providers/haveno_providers/settings_provider.dart';
|
||||||
|
import 'package:haveno_app/providers/haveno_client_providers/trades_provider.dart';
|
||||||
|
import 'package:haveno_app/providers/haveno_client_providers/wallets_provider.dart';
|
||||||
|
import 'package:haveno_app/services/local_notification_service.dart';
|
||||||
|
import 'package:haveno_app/services/secure_storage_service.dart';
|
||||||
|
import 'package:haveno_app/system_tray.dart';
|
||||||
|
import 'package:haveno_app/versions.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:haveno_app/providers/haveno_client_providers/version_provider.dart';
|
||||||
|
import 'package:haveno_app/providers/haveno_client_providers/account_provider.dart';
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||||
|
import 'package:flutter_socks_proxy/socks_proxy.dart';
|
||||||
|
|
||||||
|
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
await Versions().load();
|
||||||
|
|
||||||
|
// Initialize event dispatcher
|
||||||
|
final eventDispatcher = EventDispatcher();
|
||||||
|
|
||||||
|
// Initialize the tor status service
|
||||||
|
//final torStatusService = TorStatusService();
|
||||||
|
//torStatusService.startListening(eventDispatcher);
|
||||||
|
|
||||||
|
// Initialise the tor log service
|
||||||
|
//final torLogService = TorLogService();
|
||||||
|
//torLogService.startListening(eventDispatcher);
|
||||||
|
|
||||||
|
// Initializt notifications
|
||||||
|
await LocalNotificationsService().init();
|
||||||
|
|
||||||
|
// Set the default for desktop (overrites later when connected for mobile)
|
||||||
|
|
||||||
|
if (!Platform.isIOS) {
|
||||||
|
SocksProxy.initProxy(proxy: 'SOCKS5 127.0.0.1:9050');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start Orbot on iOS
|
||||||
|
//if (Platform.isIOS) {
|
||||||
|
// await OrbotApi().startOrbot();
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) {
|
||||||
|
intializeSystemTray();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup background/foreground services (for fetching data for notifications and state updates in SQL)
|
||||||
|
if (Platform.isIOS || Platform.isAndroid) {
|
||||||
|
FlutterBackgroundService? mobileBackgroundService;
|
||||||
|
mobileBackgroundService = FlutterBackgroundService();
|
||||||
|
|
||||||
|
// Start the background service listener
|
||||||
|
final backgroundServiceListener = BackgroundServiceListener(eventDispatcher);
|
||||||
|
backgroundServiceListener.startListening();
|
||||||
|
|
||||||
|
await mobileBackgroundService.configure(
|
||||||
|
iosConfiguration: IosConfiguration(
|
||||||
|
autoStart: true,
|
||||||
|
//onForeground: onStart,
|
||||||
|
onBackground: onIosBackground,
|
||||||
|
),
|
||||||
|
androidConfiguration: AndroidConfiguration(
|
||||||
|
autoStart: true,
|
||||||
|
onStart: onStart,
|
||||||
|
isForegroundMode: true,
|
||||||
|
autoStartOnBoot: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final secureStorageService = SecureStorageService();
|
||||||
|
final havenoChannel = HavenoChannel();
|
||||||
|
|
||||||
|
await SentryFlutter.init((options) {
|
||||||
|
options.dsn =
|
||||||
|
'https://ddf883d1a885ae8d619a923d1c80350f@o4507901830299648.ingest.us.sentry.io/4507901840457728';
|
||||||
|
options.tracesSampleRate = 1.0;
|
||||||
|
options.profilesSampleRate = 1.0;
|
||||||
|
if (Platform.isAndroid || Platform.isIOS) {
|
||||||
|
options.proxy = SentryProxy(type: SentryProxyType.socks, host: '127.0.0.1', port: 9050);
|
||||||
|
} else {
|
||||||
|
options.proxy = SentryProxy(type: SentryProxyType.socks, host: '127.0.0.1', port: 9066);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
appRunner: () => runApp(
|
||||||
|
MultiProvider(
|
||||||
|
providers: [
|
||||||
|
Provider(create: (_) => havenoChannel),
|
||||||
|
//ChangeNotifierProvider(
|
||||||
|
// create: (context) => TorStatusProvider(torStatusService),
|
||||||
|
//),
|
||||||
|
//ChangeNotifierProvider(
|
||||||
|
// create: (context) => TorLogProvider(torLogService),
|
||||||
|
//),
|
||||||
|
ChangeNotifierProvider(
|
||||||
|
create: (context) =>
|
||||||
|
HavenoDaemonProvider(secureStorageService),
|
||||||
|
),
|
||||||
|
ChangeNotifierProvider(
|
||||||
|
create: (context) => SettingsProvider(secureStorageService),
|
||||||
|
),
|
||||||
|
ChangeNotifierProvider(
|
||||||
|
create: (context) => GetVersionProvider(havenoChannel),
|
||||||
|
),
|
||||||
|
ChangeNotifierProvider(
|
||||||
|
create: (context) => AccountProvider(),
|
||||||
|
),
|
||||||
|
ChangeNotifierProvider(
|
||||||
|
create: (context) => WalletsProvider(havenoChannel),
|
||||||
|
),
|
||||||
|
ChangeNotifierProvider(
|
||||||
|
create: (context) => OffersProvider(havenoChannel),
|
||||||
|
),
|
||||||
|
ChangeNotifierProvider(
|
||||||
|
create: (context) => TradesProvider(havenoChannel),
|
||||||
|
),
|
||||||
|
ChangeNotifierProvider(
|
||||||
|
create: (context) => PaymentAccountsProvider(havenoChannel),
|
||||||
|
),
|
||||||
|
ChangeNotifierProvider(
|
||||||
|
create: (context) => PricesProvider(havenoChannel),
|
||||||
|
),
|
||||||
|
ChangeNotifierProvider(
|
||||||
|
create: (context) => TradeStatisticsProvider(havenoChannel),
|
||||||
|
),
|
||||||
|
ChangeNotifierProvider(
|
||||||
|
create: (context) => DisputesProvider(),
|
||||||
|
),
|
||||||
|
ChangeNotifierProvider(
|
||||||
|
create: (context) => DisputeAgentsProvider(),
|
||||||
|
),
|
||||||
|
ChangeNotifierProvider(
|
||||||
|
create: (context) => XmrConnectionsProvider(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: HavenoApp(),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@pragma('vm:entry-point')
|
||||||
|
Future<bool> onIosBackground(ServiceInstance service) async {
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
DartPluginRegistrant.ensureInitialized();
|
||||||
|
startShortLivedBackgroundFetch(service);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@pragma('vm:entry-point')
|
||||||
|
void onStart(ServiceInstance service) async {
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
startLongRunningForegroundService(service);
|
||||||
|
}
|
138
lib/models/candlestick.dart
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
/* import 'dart:convert';
|
||||||
|
import 'package:fixnum/fixnum.dart';
|
||||||
|
import 'package:haveno_app/proto/compiled/pb.pb.dart';
|
||||||
|
|
||||||
|
class CandlestickData {
|
||||||
|
final int timestamp;
|
||||||
|
final int open;
|
||||||
|
final int high;
|
||||||
|
final int low;
|
||||||
|
final int close;
|
||||||
|
final int volume;
|
||||||
|
|
||||||
|
CandlestickData({
|
||||||
|
required this.timestamp,
|
||||||
|
required this.open,
|
||||||
|
required this.high,
|
||||||
|
required this.low,
|
||||||
|
required this.close,
|
||||||
|
required this.volume,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<CandlestickData>> getCandlestickData(String period) async {
|
||||||
|
final db = await instance.database;
|
||||||
|
|
||||||
|
// Determine the start and end timestamps for the specified period
|
||||||
|
int startTime;
|
||||||
|
int endTime;
|
||||||
|
|
||||||
|
DateTime now = DateTime.now();
|
||||||
|
|
||||||
|
switch (period.toLowerCase()) {
|
||||||
|
case 'daily':
|
||||||
|
startTime = DateTime(now.year, now.month, now.day).millisecondsSinceEpoch;
|
||||||
|
endTime = DateTime(now.year, now.month, now.day + 1).millisecondsSinceEpoch - 1;
|
||||||
|
break;
|
||||||
|
case 'weekly':
|
||||||
|
startTime = DateTime(now.year, now.month, now.day - now.weekday + 1).millisecondsSinceEpoch;
|
||||||
|
endTime = DateTime(now.year, now.month, now.day - now.weekday + 8).millisecondsSinceEpoch - 1;
|
||||||
|
break;
|
||||||
|
case 'monthly':
|
||||||
|
startTime = DateTime(now.year, now.month, 1).millisecondsSinceEpoch;
|
||||||
|
endTime = DateTime(now.year, now.month + 1, 1).millisecondsSinceEpoch - 1;
|
||||||
|
break;
|
||||||
|
case 'yearly':
|
||||||
|
startTime = DateTime(now.year, 1, 1).millisecondsSinceEpoch;
|
||||||
|
endTime = DateTime(now.year + 1, 1, 1).millisecondsSinceEpoch - 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw ArgumentError('Invalid period specified: $period');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query the database for trade statistics within the specified period
|
||||||
|
final List<Map<String, dynamic>> maps = await db.query(
|
||||||
|
'trade_statistics',
|
||||||
|
where: 'date >= ? AND date <= ?',
|
||||||
|
whereArgs: [startTime, endTime],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Group the trade statistics by the period and calculate OHLC data
|
||||||
|
Map<int, List<TradeStatistics3>> groupedData = {};
|
||||||
|
|
||||||
|
for (var map in maps) {
|
||||||
|
final tradeStatisticJson = map['data'];
|
||||||
|
final tradeStatistic = TradeStatistics3.create()
|
||||||
|
..mergeFromProto3Json(jsonDecode(tradeStatisticJson));
|
||||||
|
|
||||||
|
// Determine the period timestamp (e.g., start of the day, week, etc.)
|
||||||
|
DateTime date = DateTime.fromMillisecondsSinceEpoch(tradeStatistic.date.toInt());
|
||||||
|
int periodTimestamp;
|
||||||
|
|
||||||
|
switch (period.toLowerCase()) {
|
||||||
|
case 'daily':
|
||||||
|
periodTimestamp = DateTime(date.year, date.month, date.day).millisecondsSinceEpoch;
|
||||||
|
break;
|
||||||
|
case 'weekly':
|
||||||
|
periodTimestamp = DateTime(date.year, date.month, date.day - date.weekday + 1).millisecondsSinceEpoch;
|
||||||
|
break;
|
||||||
|
case 'monthly':
|
||||||
|
periodTimestamp = DateTime(date.year, date.month, 1).millisecondsSinceEpoch;
|
||||||
|
break;
|
||||||
|
case 'yearly':
|
||||||
|
periodTimestamp = DateTime(date.year, 1, 1).millisecondsSinceEpoch;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw ArgumentError('Invalid period specified: $period');
|
||||||
|
}
|
||||||
|
|
||||||
|
groupedData.putIfAbsent(periodTimestamp, () => []).add(tradeStatistic);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate OHLC data for each group
|
||||||
|
List<CandlestickData> candlestickData = groupedData.entries.map((entry) {
|
||||||
|
final trades = entry.value;
|
||||||
|
final sortedTrades = trades..sort((a, b) => a.date.compareTo(b.date));
|
||||||
|
|
||||||
|
Int64 open = sortedTrades.first.price;
|
||||||
|
Int64 close = sortedTrades.last.price;
|
||||||
|
Int64 high = sortedTrades.map((trade) => trade.price).reduce((a, b) => a > b ? a : b);
|
||||||
|
Int64 low = sortedTrades.map((trade) => trade.price).reduce((a, b) => a < b ? a : b);
|
||||||
|
Int64 volume = sortedTrades.map((trade) => trade.amount).reduce((a, b) => a + b);
|
||||||
|
|
||||||
|
return CandlestickData(
|
||||||
|
timestamp: entry.key,
|
||||||
|
open: open.toInt(),
|
||||||
|
high: open.,
|
||||||
|
high: ,
|
||||||
|
low: low,
|
||||||
|
close: close,
|
||||||
|
volume: volume,
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
return candlestickData;
|
||||||
|
}
|
||||||
|
*/
|
48
lib/models/haveno/p2p/haveno_network
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
//
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
class HavenoNetwork {
|
||||||
|
final String name;
|
||||||
|
final String description;
|
||||||
|
|
||||||
|
HavenoNetwork({
|
||||||
|
required this.name,
|
||||||
|
required this.description,
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'name': name,
|
||||||
|
'description': description,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
factory HavenoNetwork.fromJson(Map<String, dynamic> json) {
|
||||||
|
return HavenoNetwork(
|
||||||
|
name: json['name'],
|
||||||
|
description: json['description'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory HavenoNetwork.getDefault() {
|
||||||
|
return HavenoNetwork(
|
||||||
|
name: '',
|
||||||
|
description: 'A new Haveno network',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
17
lib/models/haveno/p2p/haveno_node.dart
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
//
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
20
lib/models/haveno/p2p/haveno_peer.dart
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
57
lib/models/haveno/p2p/haveno_seednode.dart
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
//
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import 'package:haveno_app/models/haveno/p2p/haveno_network';
|
||||||
|
|
||||||
|
class HavenoSeedNode {
|
||||||
|
final String onionHost;
|
||||||
|
final int port;
|
||||||
|
final HavenoNetwork network;
|
||||||
|
|
||||||
|
HavenoSeedNode({
|
||||||
|
required this.onionHost,
|
||||||
|
required this.port,
|
||||||
|
required this.network,
|
||||||
|
}) {
|
||||||
|
_validateOnionAddress(onionHost);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _validateOnionAddress(String host) {
|
||||||
|
final pattern = RegExp(r'^[a-zA-Z0-9]{56}\.onion$');
|
||||||
|
if (!pattern.hasMatch(host)) {
|
||||||
|
throw const FormatException('Invalid v3 onion address');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'onionHost': onionHost,
|
||||||
|
'port': port,
|
||||||
|
'network': network.toJson(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
factory HavenoSeedNode.fromJson(Map<String, dynamic> json) {
|
||||||
|
return HavenoSeedNode(
|
||||||
|
onionHost: json['onionHost'],
|
||||||
|
port: json['port'],
|
||||||
|
network: HavenoNetwork.fromJson(json['network']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
83
lib/models/haveno_daemon_config.dart
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
class HavenoDaemonConfig {
|
||||||
|
final String host;
|
||||||
|
final int port;
|
||||||
|
final String clientAuthPassword;
|
||||||
|
final Uri fullUri;
|
||||||
|
bool isVerified; // Has had at least one successful GRPC call
|
||||||
|
|
||||||
|
HavenoDaemonConfig({
|
||||||
|
required this.fullUri,
|
||||||
|
String? clientAuthPassword,
|
||||||
|
}) : clientAuthPassword = clientAuthPassword ?? _parsePassword(fullUri),
|
||||||
|
host = _parseHost(fullUri),
|
||||||
|
port = _parsePort(fullUri),
|
||||||
|
isVerified = false {
|
||||||
|
_validateOnionAddress(fullUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
static String _parseHost(Uri uri) {
|
||||||
|
return uri.host;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _parsePort(Uri uri) {
|
||||||
|
return uri.hasPort ? uri.port : 80;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String _parsePassword(Uri uri) {
|
||||||
|
final password = uri.queryParameters['password'];
|
||||||
|
if (password == null) {
|
||||||
|
throw const FormatException('Missing password query parameter');
|
||||||
|
}
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _validateOnionAddress(Uri uri) {
|
||||||
|
final pattern = RegExp(r'^[a-zA-Z0-9]{56}\.onion$');
|
||||||
|
if (!pattern.hasMatch(uri.host)) {
|
||||||
|
throw const FormatException('Invalid v3 onion address');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setVerified(bool verified) {
|
||||||
|
isVerified = verified;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'host': host,
|
||||||
|
'port': port,
|
||||||
|
'fullUri': fullUri.toString(),
|
||||||
|
'clientAuthPassword': clientAuthPassword,
|
||||||
|
'isVerified': isVerified
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
factory HavenoDaemonConfig.fromJson(Map<String, dynamic> json) {
|
||||||
|
return HavenoDaemonConfig(
|
||||||
|
fullUri: Uri.parse(json['fullUri']),
|
||||||
|
clientAuthPassword: json['clientAuthPassword'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
208
lib/models/monero/monero_node.dart
Normal file
|
@ -0,0 +1,208 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
class MoneroNode {
|
||||||
|
final int adjustedTime;
|
||||||
|
final int altBlocksCount;
|
||||||
|
final int blockSizeLimit;
|
||||||
|
final int blockSizeMedian;
|
||||||
|
final int blockWeightLimit;
|
||||||
|
final int blockWeightMedian;
|
||||||
|
final String bootstrapDaemonAddress;
|
||||||
|
final bool busySyncing;
|
||||||
|
final int credits;
|
||||||
|
final int cumulativeDifficulty;
|
||||||
|
final int cumulativeDifficultyTop64;
|
||||||
|
final int databaseSize;
|
||||||
|
final int difficulty;
|
||||||
|
final int difficultyTop64;
|
||||||
|
final int freeSpace;
|
||||||
|
final int greyPeerlistSize;
|
||||||
|
final int height;
|
||||||
|
final int heightWithoutBootstrap;
|
||||||
|
final int incomingConnectionsCount;
|
||||||
|
final bool mainnet;
|
||||||
|
final String nettype;
|
||||||
|
final bool offline;
|
||||||
|
final int outgoingConnectionsCount;
|
||||||
|
final bool restricted;
|
||||||
|
final int rpcConnectionsCount;
|
||||||
|
final bool stagenet;
|
||||||
|
final int startTime;
|
||||||
|
final String status;
|
||||||
|
final bool synchronized;
|
||||||
|
final int target;
|
||||||
|
final int targetHeight;
|
||||||
|
final bool testnet;
|
||||||
|
final String topBlockHash;
|
||||||
|
final String topHash;
|
||||||
|
final int txCount;
|
||||||
|
final int txPoolSize;
|
||||||
|
final bool untrusted;
|
||||||
|
final bool updateAvailable;
|
||||||
|
final String version;
|
||||||
|
final bool wasBootstrapEverUsed;
|
||||||
|
final int whitePeerlistSize;
|
||||||
|
final String wideCumulativeDifficulty;
|
||||||
|
final String wideDifficulty;
|
||||||
|
|
||||||
|
MoneroNode({
|
||||||
|
required this.adjustedTime,
|
||||||
|
required this.altBlocksCount,
|
||||||
|
required this.blockSizeLimit,
|
||||||
|
required this.blockSizeMedian,
|
||||||
|
required this.blockWeightLimit,
|
||||||
|
required this.blockWeightMedian,
|
||||||
|
required this.bootstrapDaemonAddress,
|
||||||
|
required this.busySyncing,
|
||||||
|
required this.credits,
|
||||||
|
required this.cumulativeDifficulty,
|
||||||
|
required this.cumulativeDifficultyTop64,
|
||||||
|
required this.databaseSize,
|
||||||
|
required this.difficulty,
|
||||||
|
required this.difficultyTop64,
|
||||||
|
required this.freeSpace,
|
||||||
|
required this.greyPeerlistSize,
|
||||||
|
required this.height,
|
||||||
|
required this.heightWithoutBootstrap,
|
||||||
|
required this.incomingConnectionsCount,
|
||||||
|
required this.mainnet,
|
||||||
|
required this.nettype,
|
||||||
|
required this.offline,
|
||||||
|
required this.outgoingConnectionsCount,
|
||||||
|
required this.restricted,
|
||||||
|
required this.rpcConnectionsCount,
|
||||||
|
required this.stagenet,
|
||||||
|
required this.startTime,
|
||||||
|
required this.status,
|
||||||
|
required this.synchronized,
|
||||||
|
required this.target,
|
||||||
|
required this.targetHeight,
|
||||||
|
required this.testnet,
|
||||||
|
required this.topBlockHash,
|
||||||
|
required this.topHash,
|
||||||
|
required this.txCount,
|
||||||
|
required this.txPoolSize,
|
||||||
|
required this.untrusted,
|
||||||
|
required this.updateAvailable,
|
||||||
|
required this.version,
|
||||||
|
required this.wasBootstrapEverUsed,
|
||||||
|
required this.whitePeerlistSize,
|
||||||
|
required this.wideCumulativeDifficulty,
|
||||||
|
required this.wideDifficulty,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory MoneroNode.fromJson(Map<String, dynamic> json) {
|
||||||
|
return MoneroNode(
|
||||||
|
adjustedTime: json['adjusted_time'],
|
||||||
|
altBlocksCount: json['alt_blocks_count'],
|
||||||
|
blockSizeLimit: json['block_size_limit'],
|
||||||
|
blockSizeMedian: json['block_size_median'],
|
||||||
|
blockWeightLimit: json['block_weight_limit'],
|
||||||
|
blockWeightMedian: json['block_weight_median'],
|
||||||
|
bootstrapDaemonAddress: json['bootstrap_daemon_address'],
|
||||||
|
busySyncing: json['busy_syncing'],
|
||||||
|
credits: json['credits'],
|
||||||
|
cumulativeDifficulty: json['cumulative_difficulty'],
|
||||||
|
cumulativeDifficultyTop64: json['cumulative_difficulty_top64'],
|
||||||
|
databaseSize: json['database_size'],
|
||||||
|
difficulty: json['difficulty'],
|
||||||
|
difficultyTop64: json['difficulty_top64'],
|
||||||
|
freeSpace: json['free_space'],
|
||||||
|
greyPeerlistSize: json['grey_peerlist_size'],
|
||||||
|
height: json['height'],
|
||||||
|
heightWithoutBootstrap: json['height_without_bootstrap'],
|
||||||
|
incomingConnectionsCount: json['incoming_connections_count'],
|
||||||
|
mainnet: json['mainnet'],
|
||||||
|
nettype: json['nettype'],
|
||||||
|
offline: json['offline'],
|
||||||
|
outgoingConnectionsCount: json['outgoing_connections_count'],
|
||||||
|
restricted: json['restricted'],
|
||||||
|
rpcConnectionsCount: json['rpc_connections_count'],
|
||||||
|
stagenet: json['stagenet'],
|
||||||
|
startTime: json['start_time'],
|
||||||
|
status: json['status'],
|
||||||
|
synchronized: json['synchronized'],
|
||||||
|
target: json['target'],
|
||||||
|
targetHeight: json['target_height'],
|
||||||
|
testnet: json['testnet'],
|
||||||
|
topBlockHash: json['top_block_hash'],
|
||||||
|
topHash: json['top_hash'],
|
||||||
|
txCount: json['tx_count'],
|
||||||
|
txPoolSize: json['tx_pool_size'],
|
||||||
|
untrusted: json['untrusted'],
|
||||||
|
updateAvailable: json['update_available'],
|
||||||
|
version: json['version'],
|
||||||
|
wasBootstrapEverUsed: json['was_bootstrap_ever_used'],
|
||||||
|
whitePeerlistSize: json['white_peerlist_size'],
|
||||||
|
wideCumulativeDifficulty: json['wide_cumulative_difficulty'],
|
||||||
|
wideDifficulty: json['wide_difficulty'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'adjusted_time': adjustedTime,
|
||||||
|
'alt_blocks_count': altBlocksCount,
|
||||||
|
'block_size_limit': blockSizeLimit,
|
||||||
|
'block_size_median': blockSizeMedian,
|
||||||
|
'block_weight_limit': blockWeightLimit,
|
||||||
|
'block_weight_median': blockWeightMedian,
|
||||||
|
'bootstrap_daemon_address': bootstrapDaemonAddress,
|
||||||
|
'busy_syncing': busySyncing,
|
||||||
|
'credits': credits,
|
||||||
|
'cumulative_difficulty': cumulativeDifficulty,
|
||||||
|
'cumulative_difficulty_top64': cumulativeDifficultyTop64,
|
||||||
|
'database_size': databaseSize,
|
||||||
|
'difficulty': difficulty,
|
||||||
|
'difficulty_top64': difficultyTop64,
|
||||||
|
'free_space': freeSpace,
|
||||||
|
'grey_peerlist_size': greyPeerlistSize,
|
||||||
|
'height': height,
|
||||||
|
'height_without_bootstrap': heightWithoutBootstrap,
|
||||||
|
'incoming_connections_count': incomingConnectionsCount,
|
||||||
|
'mainnet': mainnet,
|
||||||
|
'nettype': nettype,
|
||||||
|
'offline': offline,
|
||||||
|
'outgoing_connections_count': outgoingConnectionsCount,
|
||||||
|
'restricted': restricted,
|
||||||
|
'rpc_connections_count': rpcConnectionsCount,
|
||||||
|
'stagenet': stagenet,
|
||||||
|
'start_time': startTime,
|
||||||
|
'status': status,
|
||||||
|
'synchronized': synchronized,
|
||||||
|
'target': target,
|
||||||
|
'target_height': targetHeight,
|
||||||
|
'testnet': testnet,
|
||||||
|
'top_block_hash': topBlockHash,
|
||||||
|
'top_hash': topHash,
|
||||||
|
'tx_count': txCount,
|
||||||
|
'tx_pool_size': txPoolSize,
|
||||||
|
'untrusted': untrusted,
|
||||||
|
'update_available': updateAvailable,
|
||||||
|
'version': version,
|
||||||
|
'was_bootstrap_ever_used': wasBootstrapEverUsed,
|
||||||
|
'white_peerlist_size': whitePeerlistSize,
|
||||||
|
'wide_cumulative_difficulty': wideCumulativeDifficulty,
|
||||||
|
'wide_difficulty': wideDifficulty,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
248
lib/models/schema.dart
Normal file
|
@ -0,0 +1,248 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:haveno/grpc_models.dart';
|
||||||
|
import 'package:haveno/profobuf_models.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
abstract class DeviceManagerAutoInitialization {
|
||||||
|
Future<void> init();
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class PollingProvider with ChangeNotifier {
|
||||||
|
Timer? _timer;
|
||||||
|
bool _isPolling = false;
|
||||||
|
final Duration maxPollingInterval;
|
||||||
|
|
||||||
|
PollingProvider(this.maxPollingInterval);
|
||||||
|
|
||||||
|
// Method to be implemented by subclasses to define the polling action
|
||||||
|
Future<void> pollAction();
|
||||||
|
|
||||||
|
// Method to start polling with a custom interval
|
||||||
|
void startPolling([Duration? interval]) {
|
||||||
|
if (_isPolling) return; // Prevent multiple polling tasks
|
||||||
|
|
||||||
|
_isPolling = true;
|
||||||
|
_timer = Timer.periodic(interval ?? maxPollingInterval, (Timer t) async {
|
||||||
|
await pollAction();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to stop polling
|
||||||
|
void stopPolling() {
|
||||||
|
_timer?.cancel();
|
||||||
|
_isPolling = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
stopPolling();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SyncTask {
|
||||||
|
final Future<void> Function() taskFunction;
|
||||||
|
final Duration cooldown;
|
||||||
|
final List<SyncTask> dependencies;
|
||||||
|
DateTime _lastRun;
|
||||||
|
|
||||||
|
SyncTask({
|
||||||
|
required this.taskFunction,
|
||||||
|
required this.cooldown,
|
||||||
|
this.dependencies = const [], // Default to no dependencies
|
||||||
|
}) : _lastRun = DateTime.fromMillisecondsSinceEpoch(0); // Initialize to a time far in the past
|
||||||
|
|
||||||
|
bool shouldRun() {
|
||||||
|
// Check if all dependencies have been run
|
||||||
|
bool dependenciesMet = dependencies.every((task) => task.hasRun);
|
||||||
|
|
||||||
|
// Check if cooldown period has passed and dependencies are met
|
||||||
|
return dependenciesMet && DateTime.now().difference(_lastRun) >= cooldown;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> run() async {
|
||||||
|
if (shouldRun()) {
|
||||||
|
await taskFunction();
|
||||||
|
_lastRun = DateTime.now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get hasRun => _lastRun.isAfter(DateTime.fromMillisecondsSinceEpoch(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
class SyncManager {
|
||||||
|
final List<SyncTask> _tasks = [];
|
||||||
|
final Duration checkInterval;
|
||||||
|
Timer? _timer;
|
||||||
|
|
||||||
|
SyncManager({required this.checkInterval});
|
||||||
|
|
||||||
|
void addTask(SyncTask task) {
|
||||||
|
_tasks.add(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
void start() {
|
||||||
|
_timer = Timer.periodic(checkInterval, (timer) async {
|
||||||
|
// Iterate over tasks, making sure dependencies are resolved
|
||||||
|
for (var task in _tasks) {
|
||||||
|
await task.run();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop() {
|
||||||
|
_timer?.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mixin CooldownMixin {
|
||||||
|
final Map<String, Duration> _cooldownDurations = {};
|
||||||
|
|
||||||
|
// Initialize cooldown durations
|
||||||
|
void setCooldownDurations(Map<String, Duration> durations) {
|
||||||
|
_cooldownDurations.addAll(durations);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the cooldown is valid by comparing the current time with the stored last run time
|
||||||
|
Future<bool> isCooldownValid(String key) async {
|
||||||
|
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
final lastRunTimestamp = prefs.getInt('cooldown_$key');
|
||||||
|
|
||||||
|
if (lastRunTimestamp == null) return false; // No previous run recorded, so it's invalid.
|
||||||
|
|
||||||
|
final lastRunTime = DateTime.fromMillisecondsSinceEpoch(lastRunTimestamp);
|
||||||
|
final duration = _cooldownDurations[key] ?? const Duration(minutes: 5);
|
||||||
|
|
||||||
|
return DateTime.now().isBefore(lastRunTime.add(duration)); // Valid if current time is before cooldown expires.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the cooldown with the current time and store it in shared_preferences
|
||||||
|
Future<void> updateCooldown(String key) async {
|
||||||
|
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
final now = DateTime.now();
|
||||||
|
final duration = _cooldownDurations[key] ?? const Duration(minutes: 5); // Default to 5 minutes if not defined
|
||||||
|
|
||||||
|
final cooldownEndTime = now.add(duration);
|
||||||
|
await prefs.setInt('cooldown_$key', cooldownEndTime.millisecondsSinceEpoch); // Store the expiration time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optionally, you can add a method to clear cooldowns for testing or reset purposes
|
||||||
|
Future<void> clearCooldown(String key) async {
|
||||||
|
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.remove('cooldown_$key');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
abstract class PlatformLifecycleWidget extends StatefulWidget {
|
||||||
|
final Widget child;
|
||||||
|
final Widget Function(BuildContext context, Widget child) builder;
|
||||||
|
|
||||||
|
const PlatformLifecycleWidget({
|
||||||
|
super.key,
|
||||||
|
required this.child,
|
||||||
|
required this.builder,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class PlatformLifecycleState<T extends PlatformLifecycleWidget> extends State<T> {
|
||||||
|
Future<void> initPlatform();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
initPlatform();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return widget.builder(context, widget.child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum BackgroundEventType {
|
||||||
|
updateTorStatus,
|
||||||
|
updateDaemonStatus,
|
||||||
|
torStdOutLog,
|
||||||
|
torStdErrLog,
|
||||||
|
}
|
||||||
|
|
||||||
|
class BackgroundEvent {
|
||||||
|
final BackgroundEventType type;
|
||||||
|
final Map<String, dynamic> data;
|
||||||
|
|
||||||
|
BackgroundEvent({required this.type, required this.data});
|
||||||
|
}
|
||||||
|
|
||||||
|
class EventDispatcher {
|
||||||
|
final Map<BackgroundEventType, List<Function(Map<String, dynamic>)>> _listeners = {};
|
||||||
|
|
||||||
|
void subscribe(BackgroundEventType eventType, Function(Map<String, dynamic>) callback) {
|
||||||
|
_listeners[eventType] ??= [];
|
||||||
|
_listeners[eventType]!.add(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void unsubscribe(BackgroundEventType eventType, Function(Map<String, dynamic>) callback) {
|
||||||
|
_listeners[eventType]?.remove(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispatch(BackgroundEvent event) {
|
||||||
|
if (_listeners[event.type] != null) {
|
||||||
|
for (var listener in _listeners[event.type]!) {
|
||||||
|
listener(event.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mixin StreamListenerProviderMixin on ChangeNotifier {
|
||||||
|
StreamSubscription? _subscription;
|
||||||
|
|
||||||
|
// A helper function to manage stream subscriptions
|
||||||
|
void listenToStream(Stream<void> stream, VoidCallback onData) {
|
||||||
|
_subscription = stream.listen((_) {
|
||||||
|
onData(); // Perform action when data arrives (e.g., notifyListeners)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_subscription?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Notification callbacks
|
||||||
|
typedef NewChatMessageCallback = void Function(ChatMessage chatMessage);
|
||||||
|
typedef TradeUpdateCallback = void Function(TradeInfo trade, bool isNewTrade);
|
||||||
|
|
||||||
|
// Background service callbacks
|
||||||
|
typedef UpdateTorStatusBackgroundCallback = void Function(String status, String detail);
|
||||||
|
typedef UpdateDaemonStatusBackgroundCallback = void Function(String status, String detail);
|
||||||
|
typedef TorStdOutLogBackgroundCallback = void Function(String details);
|
||||||
|
typedef TorStdErrLogBackgroundCallback = void Function(String details);
|
377
lib/models/system_processes/base_system_process.dart
Normal file
|
@ -0,0 +1,377 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:archive/archive_io.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:path/path.dart' as path;
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
|
abstract class SystemProcess {
|
||||||
|
final String key;
|
||||||
|
final String displayName;
|
||||||
|
final String? bundleAssetKey;
|
||||||
|
final String? windowsInstallationPath;
|
||||||
|
final String? linuxInstallationPath;
|
||||||
|
final String? macOsInstallationPath;
|
||||||
|
final bool useInstallationPathAsWorkingDirectory;
|
||||||
|
final String? windowsExecutableName;
|
||||||
|
final String? linuxExecutableName;
|
||||||
|
final String? macOsExecutableName;
|
||||||
|
final bool startOnLaunch;
|
||||||
|
final String versionMinor;
|
||||||
|
final String versionMajor;
|
||||||
|
List<String>? executionArgs = [''];
|
||||||
|
final Uri downloadUrl;
|
||||||
|
final bool runAsDaemon;
|
||||||
|
final int? internalPort;
|
||||||
|
final int? externalPort;
|
||||||
|
final bool installedByDistribution;
|
||||||
|
final String? pidFilePath;
|
||||||
|
Process? process;
|
||||||
|
String? processStartPath;
|
||||||
|
|
||||||
|
SystemProcess(
|
||||||
|
{required this.key,
|
||||||
|
required this.displayName,
|
||||||
|
required this.bundleAssetKey,
|
||||||
|
required this.windowsInstallationPath,
|
||||||
|
required this.linuxInstallationPath,
|
||||||
|
required this.macOsInstallationPath,
|
||||||
|
required this.useInstallationPathAsWorkingDirectory,
|
||||||
|
required this.windowsExecutableName,
|
||||||
|
required this.linuxExecutableName,
|
||||||
|
required this.macOsExecutableName,
|
||||||
|
required this.startOnLaunch,
|
||||||
|
required this.versionMinor,
|
||||||
|
required this.versionMajor,
|
||||||
|
required this.executionArgs,
|
||||||
|
required this.downloadUrl,
|
||||||
|
required this.runAsDaemon,
|
||||||
|
required this.internalPort,
|
||||||
|
required this.externalPort,
|
||||||
|
required this.installedByDistribution,
|
||||||
|
required this.pidFilePath});
|
||||||
|
|
||||||
|
Future<Directory> getApplicationDirectory() async {
|
||||||
|
Directory appDir = await getApplicationSupportDirectory();
|
||||||
|
if (Platform.isWindows) {
|
||||||
|
appDir = Directory('${appDir.path}\\$key');
|
||||||
|
} else if (Platform.isMacOS) {
|
||||||
|
appDir = Directory('${appDir.path}/$key');
|
||||||
|
} else if (Platform.isLinux) {
|
||||||
|
appDir = Directory('${appDir.path}/$key');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!await appDir.exists()) {
|
||||||
|
await appDir.create(recursive: true);
|
||||||
|
}
|
||||||
|
return appDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
String? get executableName {
|
||||||
|
if (Platform.isWindows) {
|
||||||
|
return windowsExecutableName;
|
||||||
|
} else if (Platform.isLinux) {
|
||||||
|
return linuxExecutableName;
|
||||||
|
} else if (Platform.isMacOS) {
|
||||||
|
return macOsExecutableName;
|
||||||
|
} else {
|
||||||
|
throw Exception("Operating system not supported for this service.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String? get installationPath {
|
||||||
|
if (Platform.isWindows) {
|
||||||
|
return windowsInstallationPath;
|
||||||
|
} else if (Platform.isLinux) {
|
||||||
|
return linuxInstallationPath;
|
||||||
|
} else if (Platform.isMacOS) {
|
||||||
|
return macOsInstallationPath;
|
||||||
|
} else {
|
||||||
|
throw Exception("Operating system not supported for this service.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> isInstalled() async {
|
||||||
|
print("Is installed was called");
|
||||||
|
final directory = await getApplicationDirectory();
|
||||||
|
final defaultSystemProcessDirectory = path.join(directory.path, key);
|
||||||
|
|
||||||
|
String executablePath;
|
||||||
|
if (Platform.isWindows) {
|
||||||
|
executablePath = windowsInstallationPath != null &&
|
||||||
|
windowsInstallationPath!.isNotEmpty
|
||||||
|
? path.join(windowsInstallationPath!, windowsExecutableName!)
|
||||||
|
: path.join(defaultSystemProcessDirectory, windowsExecutableName!);
|
||||||
|
} else if (Platform.isLinux) {
|
||||||
|
executablePath =
|
||||||
|
linuxInstallationPath != null && linuxInstallationPath!.isNotEmpty
|
||||||
|
? path.join(linuxInstallationPath!, linuxExecutableName!)
|
||||||
|
: path.join(defaultSystemProcessDirectory, linuxExecutableName!);
|
||||||
|
} else if (Platform.isMacOS) {
|
||||||
|
executablePath =
|
||||||
|
macOsInstallationPath != null && macOsInstallationPath!.isNotEmpty
|
||||||
|
? path.join(macOsInstallationPath!, macOsExecutableName!)
|
||||||
|
: path.join(defaultSystemProcessDirectory, macOsExecutableName!);
|
||||||
|
} else {
|
||||||
|
throw Exception("Operating system not supported for this service.");
|
||||||
|
}
|
||||||
|
print("Path to system process $executablePath");
|
||||||
|
return File(executablePath).existsSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> install() async {
|
||||||
|
if (installedByDistribution) {
|
||||||
|
throw Exception(
|
||||||
|
"Installation is not supported where installed by distribution, you may try to upgrade.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downloadUrl.toString().isNotEmpty ||
|
||||||
|
(bundleAssetKey != null && bundleAssetKey!.isNotEmpty)) {
|
||||||
|
String? specifiedInstallPath;
|
||||||
|
if (Platform.isWindows) {
|
||||||
|
if (windowsInstallationPath != null &&
|
||||||
|
windowsInstallationPath!.isNotEmpty) {
|
||||||
|
specifiedInstallPath = windowsInstallationPath;
|
||||||
|
}
|
||||||
|
} else if (Platform.isMacOS) {
|
||||||
|
if (macOsInstallationPath != null &&
|
||||||
|
macOsInstallationPath!.isNotEmpty) {
|
||||||
|
specifiedInstallPath = macOsInstallationPath;
|
||||||
|
}
|
||||||
|
} else if (Platform.isLinux) {
|
||||||
|
if (linuxInstallationPath != null &&
|
||||||
|
linuxInstallationPath!.isNotEmpty) {
|
||||||
|
specifiedInstallPath = linuxInstallationPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final Directory installPath;
|
||||||
|
if (specifiedInstallPath == null || specifiedInstallPath.isEmpty) {
|
||||||
|
final directory = await getApplicationDirectory();
|
||||||
|
final defaultInstallPath = path.join(directory.path, key);
|
||||||
|
installPath = Directory(defaultInstallPath);
|
||||||
|
} else {
|
||||||
|
installPath = Directory(specifiedInstallPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
final filePath = path.join(installPath.path, executableName!);
|
||||||
|
|
||||||
|
// Check if the main executable already exists
|
||||||
|
final file = File(filePath);
|
||||||
|
if (file.existsSync()) {
|
||||||
|
print('$displayName is already installed.');
|
||||||
|
print('Main executable was found at $filePath');
|
||||||
|
throw Exception("Already installed");
|
||||||
|
} else {
|
||||||
|
// Either download or extract from rootBundle
|
||||||
|
if (downloadUrl.isScheme('HTTP') || downloadUrl.isScheme('HTTPS')) {
|
||||||
|
// Handle file download (you need to implement this part)
|
||||||
|
await _downloadAndExtractFile(downloadUrl, installPath);
|
||||||
|
} else if (bundleAssetKey != null && bundleAssetKey!.isNotEmpty) {
|
||||||
|
await _extractAssetBundle(bundleAssetKey!, installPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw Exception("No source or remote file defined for install");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _downloadAndExtractFile(
|
||||||
|
Uri downloadUrl, Directory installPath) async {
|
||||||
|
// Implement the download and extraction logic
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _extractAssetBundle(
|
||||||
|
String assetKey, Directory installPath) async {
|
||||||
|
ByteData byteData;
|
||||||
|
try {
|
||||||
|
byteData = await rootBundle.load(assetKey);
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception(
|
||||||
|
"Invalid bundle asset key supplied, the file could not be found. $e");
|
||||||
|
}
|
||||||
|
|
||||||
|
String? archiveType;
|
||||||
|
if (assetKey.endsWith('.zip')) {
|
||||||
|
archiveType = 'ZIP';
|
||||||
|
} else if (assetKey.endsWith('.7z')) {
|
||||||
|
throw Exception("The installer cannot support .7z files currently");
|
||||||
|
} else if (assetKey.endsWith('.tar.gz')) {
|
||||||
|
archiveType = 'TARGZ';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (archiveType != null) {
|
||||||
|
await _extractArchive(byteData, archiveType, installPath);
|
||||||
|
} else {
|
||||||
|
await _writeSingleFile(byteData, installPath, path.basename(assetKey));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _extractArchive(
|
||||||
|
ByteData byteData, String archiveType, Directory installPath) async {
|
||||||
|
if (archiveType == 'ZIP') {
|
||||||
|
final archive = ZipDecoder().decodeBytes(byteData.buffer
|
||||||
|
.asUint8List(byteData.offsetInBytes, byteData.lengthInBytes));
|
||||||
|
extractArchiveToDisk(archive, installPath.absolute.path);
|
||||||
|
} else if (archiveType == 'TARGZ') {
|
||||||
|
final tarBytes = GZipDecoder().decodeBytes(byteData.buffer
|
||||||
|
.asUint8List(byteData.offsetInBytes, byteData.lengthInBytes));
|
||||||
|
final archive = TarDecoder().decodeBytes(tarBytes);
|
||||||
|
extractArchiveToDisk(archive, installPath.absolute.path);
|
||||||
|
} else {
|
||||||
|
throw Exception("Unsupported archive type: $archiveType");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _writeSingleFile(
|
||||||
|
ByteData byteData, Directory installPath, String fileName) async {
|
||||||
|
final file = File(path.join(installPath.path, fileName));
|
||||||
|
try {
|
||||||
|
await file.writeAsBytes(byteData.buffer
|
||||||
|
.asUint8List(byteData.offsetInBytes, byteData.lengthInBytes));
|
||||||
|
print("Successfully installed ${file.absolute.path}");
|
||||||
|
} catch (e) {
|
||||||
|
print(
|
||||||
|
"Failed to write a single binary file as bytes to the installation path ${file.absolute.path}");
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> update() async {
|
||||||
|
// #TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Process?> start() async {
|
||||||
|
print("$displayName service is starting...");
|
||||||
|
final defaultApplicationDirectory = await getApplicationDirectory();
|
||||||
|
final defaultSystemProcessDirectory = defaultApplicationDirectory.path;
|
||||||
|
process = null;
|
||||||
|
File pidFile;
|
||||||
|
|
||||||
|
if (Platform.isWindows) {
|
||||||
|
if (windowsInstallationPath != null &&
|
||||||
|
windowsInstallationPath!.isNotEmpty &&
|
||||||
|
windowsExecutableName != null) {
|
||||||
|
processStartPath =
|
||||||
|
path.join(windowsInstallationPath!, windowsExecutableName!);
|
||||||
|
if (useInstallationPathAsWorkingDirectory) {
|
||||||
|
process = await Process.start(processStartPath!, executionArgs!,
|
||||||
|
mode: ProcessStartMode.detachedWithStdio,
|
||||||
|
workingDirectory: windowsInstallationPath);
|
||||||
|
} else {
|
||||||
|
process = await Process.start(processStartPath!, executionArgs!,
|
||||||
|
mode: ProcessStartMode.detachedWithStdio);
|
||||||
|
}
|
||||||
|
pidFile = File(path.join(windowsInstallationPath!, "$key.pid"));
|
||||||
|
await pidFile.writeAsString(process!.pid.toString());
|
||||||
|
} else {
|
||||||
|
processStartPath =
|
||||||
|
path.join(defaultSystemProcessDirectory, windowsExecutableName!);
|
||||||
|
if (useInstallationPathAsWorkingDirectory) {
|
||||||
|
process = await Process.start(processStartPath!, executionArgs!,
|
||||||
|
mode: ProcessStartMode.detachedWithStdio,
|
||||||
|
workingDirectory: defaultSystemProcessDirectory);
|
||||||
|
} else {
|
||||||
|
process = await Process.start(processStartPath!, executionArgs!,
|
||||||
|
mode: ProcessStartMode.detachedWithStdio);
|
||||||
|
}
|
||||||
|
pidFile = File(path.join(defaultSystemProcessDirectory, "$key.pid"));
|
||||||
|
await pidFile.writeAsString(process!.pid.toString());
|
||||||
|
}
|
||||||
|
} else if (Platform.isLinux) {
|
||||||
|
if (linuxInstallationPath != null &&
|
||||||
|
linuxInstallationPath!.isNotEmpty &&
|
||||||
|
linuxExecutableName != null) {
|
||||||
|
processStartPath =
|
||||||
|
path.join(linuxInstallationPath!, linuxExecutableName!);
|
||||||
|
if (useInstallationPathAsWorkingDirectory) {
|
||||||
|
process = await Process.start(processStartPath!, executionArgs!,
|
||||||
|
mode: ProcessStartMode.detachedWithStdio,
|
||||||
|
workingDirectory: linuxInstallationPath);
|
||||||
|
} else {
|
||||||
|
process = await Process.start(processStartPath!, executionArgs!,
|
||||||
|
mode: ProcessStartMode.detachedWithStdio);
|
||||||
|
}
|
||||||
|
pidFile = File(path.join(linuxInstallationPath!, "$key.pid"));
|
||||||
|
await pidFile.writeAsString(process!.pid.toString());
|
||||||
|
} else {
|
||||||
|
processStartPath =
|
||||||
|
path.join(defaultSystemProcessDirectory, linuxExecutableName!);
|
||||||
|
if (useInstallationPathAsWorkingDirectory) {
|
||||||
|
process = await Process.start(processStartPath!, executionArgs!,
|
||||||
|
mode: ProcessStartMode.detachedWithStdio,
|
||||||
|
workingDirectory: defaultSystemProcessDirectory);
|
||||||
|
} else {
|
||||||
|
process = await Process.start(processStartPath!, executionArgs!,
|
||||||
|
mode: ProcessStartMode.detachedWithStdio);
|
||||||
|
}
|
||||||
|
pidFile = File(path.join(defaultSystemProcessDirectory, "$key.pid"));
|
||||||
|
await pidFile.writeAsString(process!.pid.toString());
|
||||||
|
}
|
||||||
|
} else if (Platform.isMacOS) {
|
||||||
|
if (macOsInstallationPath != null &&
|
||||||
|
macOsInstallationPath!.isNotEmpty &&
|
||||||
|
macOsExecutableName != null) {
|
||||||
|
processStartPath =
|
||||||
|
path.join(macOsInstallationPath!, macOsExecutableName!);
|
||||||
|
if (useInstallationPathAsWorkingDirectory) {
|
||||||
|
process = await Process.start(processStartPath!, executionArgs!,
|
||||||
|
mode: ProcessStartMode.detachedWithStdio,
|
||||||
|
workingDirectory: macOsInstallationPath);
|
||||||
|
} else {
|
||||||
|
process = await Process.start(processStartPath!, executionArgs!,
|
||||||
|
mode: ProcessStartMode.detachedWithStdio);
|
||||||
|
}
|
||||||
|
pidFile = File(path.join(macOsInstallationPath!, "$key.pid"));
|
||||||
|
await pidFile.writeAsString(process!.pid.toString());
|
||||||
|
} else {
|
||||||
|
processStartPath =
|
||||||
|
path.join(defaultSystemProcessDirectory, macOsExecutableName!);
|
||||||
|
if (useInstallationPathAsWorkingDirectory) {
|
||||||
|
process = await Process.start(processStartPath!, executionArgs!,
|
||||||
|
mode: ProcessStartMode.normal,
|
||||||
|
workingDirectory: defaultSystemProcessDirectory);
|
||||||
|
} else {
|
||||||
|
process = await Process.start(processStartPath!, executionArgs!,
|
||||||
|
mode: ProcessStartMode.normal);
|
||||||
|
}
|
||||||
|
pidFile = File(path.join(defaultSystemProcessDirectory, "$key.pid"));
|
||||||
|
await pidFile.writeAsString(process!.pid.toString());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw Exception(
|
||||||
|
"Operating system not supported for running this service, desktops only.");
|
||||||
|
}
|
||||||
|
if (process != null) {
|
||||||
|
print(
|
||||||
|
"The start script for $displayName was created with PID ${process?.pid.toString()} using command '$processStartPath ${executionArgs!.join(' ').trimRight()}'");
|
||||||
|
return process;
|
||||||
|
} else {
|
||||||
|
print("The process for $displayName was not created");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
181
lib/models/system_processes/haveno_daemon_system_process.dart
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:haveno_app/utils/nssm_manager.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
|
import 'base_system_process.dart';
|
||||||
|
|
||||||
|
class HavenoDaemonSystemProcess extends SystemProcess {
|
||||||
|
final String? password;
|
||||||
|
|
||||||
|
HavenoDaemonSystemProcess({this.password})
|
||||||
|
: super(
|
||||||
|
key: 'Haveno Daemon',
|
||||||
|
displayName: 'Haveno Daemon',
|
||||||
|
bundleAssetKey: '', // Specify the correct asset path here
|
||||||
|
windowsInstallationPath:
|
||||||
|
'', // Define specific installation paths if required
|
||||||
|
linuxInstallationPath:
|
||||||
|
'', // Define specific installation paths if required
|
||||||
|
macOsInstallationPath:
|
||||||
|
'', // Define specific installation paths if required
|
||||||
|
useInstallationPathAsWorkingDirectory: true,
|
||||||
|
windowsExecutableName: 'daemon-all.jar',
|
||||||
|
linuxExecutableName: 'daemon-all.jar',
|
||||||
|
macOsExecutableName: 'daemon-all.jar',
|
||||||
|
startOnLaunch: true,
|
||||||
|
versionMinor: '0.17.1.9',
|
||||||
|
versionMajor: '0.17',
|
||||||
|
executionArgs: [
|
||||||
|
'--baseCurrencyNetwork=XMR_STAGENET',
|
||||||
|
'--useLocalhostForP2P=false',
|
||||||
|
'--useDevPrivilegeKeys=false',
|
||||||
|
'--nodePort=9999',
|
||||||
|
'--apiPort=3201',
|
||||||
|
'--appName=haveno_app_stagenet',
|
||||||
|
'--useNativeXmrWallet=false',
|
||||||
|
'--torControlHost=127.0.0.1',
|
||||||
|
'--torControlPort=9077',
|
||||||
|
'--torControlPassword=boner',
|
||||||
|
'--appDataDir=data/'
|
||||||
|
],
|
||||||
|
downloadUrl: Uri.parse(''), // Specify the download URL if required
|
||||||
|
runAsDaemon: true,
|
||||||
|
internalPort: 9050,
|
||||||
|
externalPort: null,
|
||||||
|
installedByDistribution: false,
|
||||||
|
pidFilePath: null) {
|
||||||
|
if (password != null && password!.isNotEmpty) {
|
||||||
|
executionArgs!.add('--apiPassword=$password');
|
||||||
|
//executionArgs!.add('--passwordRequired=true');
|
||||||
|
} else {
|
||||||
|
//executionArgs!.add('--passwordRequired=false');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Process?> start() async {
|
||||||
|
print("$displayName service is starting...");
|
||||||
|
final defaultApplicationDirectory = await getApplicationDirectory();
|
||||||
|
final defaultSystemProcessDirectory = defaultApplicationDirectory.path;
|
||||||
|
process = null;
|
||||||
|
File? javaBinaryFile = await getJavaBinaryDirectory();
|
||||||
|
File? havenoDaemonJarFile = await getHavenoDaemonJarFile();
|
||||||
|
|
||||||
|
if (javaBinaryFile == null) {
|
||||||
|
print("The java binary file was not found, cannot start the daemon");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (havenoDaemonJarFile == null) {
|
||||||
|
print(
|
||||||
|
"The haveno daemon file was not found, cannot start the daemon.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the paths
|
||||||
|
String javaPath = javaBinaryFile.path;
|
||||||
|
String jarPath = havenoDaemonJarFile.path;
|
||||||
|
|
||||||
|
Map<String, String> environment = {
|
||||||
|
'JAVA_HOME': path.dirname(path.dirname(javaPath)),
|
||||||
|
'PATH': Platform.environment['PATH'] ?? '',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Platform.isWindows) {
|
||||||
|
Directory appSupportDirectory = await getApplicationSupportDirectory();
|
||||||
|
|
||||||
|
String nssmPath = path.join(appSupportDirectory.path, 'nssm.exe');
|
||||||
|
|
||||||
|
print("We are trying to load NSSM from: $nssmPath");
|
||||||
|
|
||||||
|
var nssmManager = NSSMServiceManager(nssmPath);
|
||||||
|
try {
|
||||||
|
await nssmManager.setServiceParameters('HavenoPlusDaemonService',
|
||||||
|
['-jar', '"$jarPath"', ...executionArgs!]);
|
||||||
|
print("Set service parameters for HavenoPlusDaemonService");
|
||||||
|
await Future.delayed(Duration(seconds: 2));
|
||||||
|
await nssmManager.serviceStop('HavenoPlusDaemonService');
|
||||||
|
// Wait 5 seconds
|
||||||
|
await Future.delayed(Duration(seconds: 2)); // Added wait period
|
||||||
|
await nssmManager.serviceStart('HavenoPlusDaemonService');
|
||||||
|
print("Restarted HavenoPlusDaemonService");
|
||||||
|
} catch (e) {
|
||||||
|
print("Error setting service parameters or restarting: $e");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Platform.isLinux) {
|
||||||
|
process = await Process.start(
|
||||||
|
javaPath,
|
||||||
|
['-jar', jarPath, ...executionArgs!],
|
||||||
|
mode: ProcessStartMode.normal,
|
||||||
|
environment: environment,
|
||||||
|
);
|
||||||
|
} else if (Platform.isMacOS) {
|
||||||
|
process = await Process.start(
|
||||||
|
javaPath,
|
||||||
|
['-jar', jarPath, ...executionArgs!],
|
||||||
|
mode: ProcessStartMode.normal,
|
||||||
|
workingDirectory: defaultSystemProcessDirectory,
|
||||||
|
environment: environment,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return process;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> stop() async {
|
||||||
|
if (Platform.isWindows) {
|
||||||
|
Directory appSupportDirectory = await getApplicationSupportDirectory();
|
||||||
|
String nssmPath = path.join(appSupportDirectory.path, 'nssm.exe');
|
||||||
|
await NSSMServiceManager(nssmPath).serviceStop('HavenoPlusDaemonService');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<File?> getJavaBinaryDirectory() async {
|
||||||
|
Directory appSupportDirectory = await getApplicationSupportDirectory();
|
||||||
|
File javaBinaryFile;
|
||||||
|
|
||||||
|
if (Platform.isWindows) {
|
||||||
|
javaBinaryFile = File(path.join(
|
||||||
|
appSupportDirectory.path, 'Java', 'jdk-21.0.4', 'bin', 'java.exe'));
|
||||||
|
} else if (Platform.isMacOS) {
|
||||||
|
javaBinaryFile = File(path.join(appSupportDirectory.path, 'Java',
|
||||||
|
'21.0.4+7', 'Contents', 'Home', 'bin', 'java'));
|
||||||
|
} else if (Platform.isLinux) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await javaBinaryFile.exists() ? javaBinaryFile : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<File?> getHavenoDaemonJarFile() async {
|
||||||
|
Directory havenoHomeDirectory = await getApplicationDirectory();
|
||||||
|
String havenoJarFile = path.join(havenoHomeDirectory.path, executableName);
|
||||||
|
File file = File(havenoJarFile);
|
||||||
|
return await file.exists() ? file : null;
|
||||||
|
}
|
||||||
|
}
|
48
lib/models/system_processes/java_system_process.dart
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import 'base_system_process.dart';
|
||||||
|
|
||||||
|
class JavaSystemProcess extends SystemProcess {
|
||||||
|
JavaSystemProcess()
|
||||||
|
: super(
|
||||||
|
key: 'Java',
|
||||||
|
displayName: 'Java',
|
||||||
|
bundleAssetKey: null,
|
||||||
|
windowsInstallationPath: '',
|
||||||
|
linuxInstallationPath: '',
|
||||||
|
macOsInstallationPath: '',
|
||||||
|
useInstallationPathAsWorkingDirectory: false,
|
||||||
|
windowsExecutableName: 'java.exe',
|
||||||
|
linuxExecutableName: 'java',
|
||||||
|
macOsExecutableName: 'java',
|
||||||
|
startOnLaunch: false,
|
||||||
|
versionMinor: '21.0.4+7',
|
||||||
|
versionMajor: '21',
|
||||||
|
executionArgs: [''],
|
||||||
|
downloadUrl: Uri.parse(''), // Add the appropriate download URL if needed
|
||||||
|
runAsDaemon: false,
|
||||||
|
internalPort: null,
|
||||||
|
externalPort: null,
|
||||||
|
installedByDistribution: true,
|
||||||
|
pidFilePath: null
|
||||||
|
);
|
||||||
|
}
|
48
lib/models/system_processes/tor_system_process.dart
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import 'base_system_process.dart';
|
||||||
|
|
||||||
|
class TorSystemProcess extends SystemProcess {
|
||||||
|
TorSystemProcess()
|
||||||
|
: super(
|
||||||
|
key: 'Tor',
|
||||||
|
displayName: 'Tor Daemon',
|
||||||
|
bundleAssetKey: '', // Specify the correct asset path here
|
||||||
|
windowsInstallationPath: '', // Define specific installation paths if required
|
||||||
|
linuxInstallationPath: '', // Define specific installation paths if required
|
||||||
|
macOsInstallationPath: '', // Define specific installation paths if required
|
||||||
|
useInstallationPathAsWorkingDirectory: true, // Assuming the working directory should be the installation path
|
||||||
|
windowsExecutableName: 'tor.exe',
|
||||||
|
linuxExecutableName: 'tor',
|
||||||
|
macOsExecutableName: 'tor',
|
||||||
|
startOnLaunch: true,
|
||||||
|
versionMinor: '0.17.1.9',
|
||||||
|
versionMajor: '0.17',
|
||||||
|
executionArgs: ['-f', 'torrc'],
|
||||||
|
downloadUrl: Uri.parse(''), // Specify the download URL if required
|
||||||
|
runAsDaemon: true,
|
||||||
|
internalPort: 9050,
|
||||||
|
externalPort: null,
|
||||||
|
installedByDistribution: true,
|
||||||
|
pidFilePath: 'tor.pid'
|
||||||
|
);
|
||||||
|
}
|
54
lib/models/tor/hsv3_onion_config.dart
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import 'package:cryptography/cryptography.dart';
|
||||||
|
|
||||||
|
class HSV3OnionConfig {
|
||||||
|
final List<int> privateKeyBytes;
|
||||||
|
final SimplePublicKey publicKey;
|
||||||
|
final int internalPort;
|
||||||
|
final int externalPort;
|
||||||
|
|
||||||
|
HSV3OnionConfig(
|
||||||
|
{required this.privateKeyBytes,
|
||||||
|
required this.publicKey,
|
||||||
|
required this.internalPort,
|
||||||
|
required this.externalPort});
|
||||||
|
|
||||||
|
get privateKey => null;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'privateKeyBytes': privateKeyBytes,
|
||||||
|
'publicKey': publicKey,
|
||||||
|
'internalPort': internalPort,
|
||||||
|
'externalPort': externalPort
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
factory HSV3OnionConfig.fromJson(Map<String, dynamic> json) {
|
||||||
|
return HSV3OnionConfig(
|
||||||
|
privateKeyBytes: json['privateKeyBytes'],
|
||||||
|
publicKey: json['publicKey'],
|
||||||
|
internalPort: json['internalPort'],
|
||||||
|
externalPort: json['externalPortal']);
|
||||||
|
}
|
||||||
|
}
|
75
lib/models/tor/tor_daemon_config.dart
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
class TorDaemonConfig {
|
||||||
|
final String host;
|
||||||
|
final int controlPort;
|
||||||
|
final List<int> socks5ProxyPorts;
|
||||||
|
final List<int> httpProxyPorts;
|
||||||
|
final String? hashedPassword;
|
||||||
|
|
||||||
|
TorDaemonConfig({
|
||||||
|
required this.host,
|
||||||
|
required this.controlPort,
|
||||||
|
this.socks5ProxyPorts = const [9050],
|
||||||
|
this.httpProxyPorts = const [8118],
|
||||||
|
this.hashedPassword,
|
||||||
|
}) {
|
||||||
|
_validateOnionAddress(host);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _validateOnionAddress(String host) {
|
||||||
|
print(host);
|
||||||
|
final pattern = RegExp(r'^[a-zA-Z0-9]{56}\.onion$');
|
||||||
|
if (!pattern.hasMatch(host)) {
|
||||||
|
throw const FormatException('Invalid v3 onion address');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'host': host,
|
||||||
|
'controlPort': controlPort,
|
||||||
|
'socks5ProxyPorts': socks5ProxyPorts,
|
||||||
|
'httpProxyPorts': httpProxyPorts,
|
||||||
|
'hashedPassword': hashedPassword,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
factory TorDaemonConfig.fromJson(Map<String, dynamic> json) {
|
||||||
|
return TorDaemonConfig(
|
||||||
|
host: json['host'],
|
||||||
|
controlPort: json['controlPort'],
|
||||||
|
socks5ProxyPorts: List<int>.from(json['socks5ProxyPorts'] ?? [9050]),
|
||||||
|
httpProxyPorts: List<int>.from(json['httpProxyPorts'] ?? [8118]),
|
||||||
|
hashedPassword: json['hashedPassword'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory TorDaemonConfig.getDefault() {
|
||||||
|
return TorDaemonConfig(
|
||||||
|
host: '127.0.0.1',
|
||||||
|
controlPort: 9051,
|
||||||
|
socks5ProxyPorts: [9050],
|
||||||
|
httpProxyPorts: [8118],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
78
lib/providers/haveno_client_providers/account_provider.dart
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:haveno/haveno_client.dart';
|
||||||
|
import 'package:haveno/haveno_service.dart';
|
||||||
|
|
||||||
|
class AccountProvider with ChangeNotifier {
|
||||||
|
final HavenoChannel _havenoChannel = HavenoChannel();
|
||||||
|
|
||||||
|
bool? _accountExists;
|
||||||
|
DateTime? _lastCreatedAccount;
|
||||||
|
|
||||||
|
AccountProvider();
|
||||||
|
|
||||||
|
bool? get accountExists => _accountExists;
|
||||||
|
DateTime? get lastCreatedAccount => _lastCreatedAccount;
|
||||||
|
|
||||||
|
Future<void> requestAccountExists() async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
_accountExists = await AccountService().accountExists();
|
||||||
|
_lastCreatedAccount = DateTime.now();
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to check if account exists: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> sendCreateAccount(password) async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
await AccountService().createAccount(password);
|
||||||
|
} catch (e) {
|
||||||
|
print("Error while creating account: $e");
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PasswordProvider with ChangeNotifier {
|
||||||
|
final HavenoChannel _havenoChannel;
|
||||||
|
DateTime? _lastPasswordChange = DateTime.now();
|
||||||
|
|
||||||
|
PasswordProvider(this._havenoChannel);
|
||||||
|
|
||||||
|
DateTime? get lastPasswordChange => _lastPasswordChange;
|
||||||
|
|
||||||
|
Future<void> sendChangePassword(oldPassword, newPassword) async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
await AccountService().changePassword(oldPassword, newPassword);
|
||||||
|
_lastPasswordChange = DateTime.now();
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
print("Error changing account password: $e");
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:haveno/haveno_client.dart';
|
||||||
|
import 'package:haveno/haveno_service.dart';
|
||||||
|
|
||||||
|
class DisputeAgentsProvider with ChangeNotifier {
|
||||||
|
final HavenoChannel _havenoChannel = HavenoChannel();
|
||||||
|
|
||||||
|
DisputeAgentsProvider();
|
||||||
|
|
||||||
|
Future<void> registerDisputeAgent(String disputeAgentType, String registrationKey) async {
|
||||||
|
try {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
|
||||||
|
await DisputeAgentService().registerDisputeAgent(
|
||||||
|
disputeAgentType,
|
||||||
|
registrationKey
|
||||||
|
);
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to register dispute agent: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> unregisterDisputeAgent(String disputeAgentType) async {
|
||||||
|
try {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
|
||||||
|
await DisputeAgentService().unregisterDisputeAgent(
|
||||||
|
disputeAgentType
|
||||||
|
);
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to unregister dispute agent: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
358
lib/providers/haveno_client_providers/disputes_provider.dart
Normal file
|
@ -0,0 +1,358 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:fixnum/fixnum.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:haveno/haveno_client.dart';
|
||||||
|
import 'package:haveno/haveno_service.dart';
|
||||||
|
import 'package:haveno/profobuf_models.dart';
|
||||||
|
|
||||||
|
class DisputesProvider with ChangeNotifier {
|
||||||
|
final HavenoChannel _havenoChannel = HavenoChannel();
|
||||||
|
List<Dispute?> _disputes = [];
|
||||||
|
|
||||||
|
// Map to hold StreamControllers for each chat
|
||||||
|
final Map<String, StreamController<List<ChatMessage>>> _chatControllers = {};
|
||||||
|
|
||||||
|
// Map to store unique chat messages for each dispute
|
||||||
|
final Map<String, List<ChatMessage>> _chatMessages = {};
|
||||||
|
|
||||||
|
// Map to store tradeId to disputeId mapping
|
||||||
|
final Map<String, String> _disputeToTradeIdMap = {};
|
||||||
|
|
||||||
|
// TradeID to dispute map
|
||||||
|
final Map<String, Dispute> _tradeIdToDisputeMap = {};
|
||||||
|
|
||||||
|
DisputesProvider(); //: super(const Duration(minutes: 1));
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
// Close all StreamControllers when the provider is disposed
|
||||||
|
_chatControllers.forEach((_, controller) => controller.close());
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to get or create a StreamController for a specific chat
|
||||||
|
Stream<List<ChatMessage>> chatMessagesStream(String disputeId) {
|
||||||
|
if (!_chatControllers.containsKey(disputeId)) {
|
||||||
|
_chatControllers[disputeId] = StreamController<List<ChatMessage>>.broadcast();
|
||||||
|
|
||||||
|
// Start polling for new messages (if needed)
|
||||||
|
_startPolling(disputeId);
|
||||||
|
}
|
||||||
|
return _chatControllers[disputeId]!.stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load initial messages independently of stream creation
|
||||||
|
Future<void> loadInitialMessages(String disputeId) async {
|
||||||
|
// Retrieve the tradeId from the mapping
|
||||||
|
final tradeId = _disputeToTradeIdMap[disputeId];
|
||||||
|
if (tradeId == null) return;
|
||||||
|
|
||||||
|
// Retrieve the dispute from the _disputes list using the disputeId
|
||||||
|
Dispute? dispute = _disputes.firstWhere((d) => d!.id == disputeId, orElse: () => null);
|
||||||
|
|
||||||
|
if (dispute != null) {
|
||||||
|
print("Setting chat messages for dispute: $disputeId with ${dispute.chatMessage.length} messages");
|
||||||
|
|
||||||
|
// Store the chat messages in _chatMessages for this disputeId
|
||||||
|
_chatMessages[disputeId] = dispute.chatMessage;
|
||||||
|
|
||||||
|
// If a stream controller exists for this disputeId, add the messages to the stream
|
||||||
|
if (_chatControllers.containsKey(disputeId)) {
|
||||||
|
_chatControllers[disputeId]!.add(_chatMessages[disputeId]!);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Handle case where no dispute is found
|
||||||
|
print("No dispute found for ID: $disputeId");
|
||||||
|
_chatMessages[disputeId] = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ChatMessage> getInitialChatMessages(String disputeId) {
|
||||||
|
print("Getting initial chat messages for dispute: $disputeId");
|
||||||
|
return _chatMessages[disputeId] ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Probsblt just redo the polling when not tired
|
||||||
|
void _startPolling(String disputeId) {
|
||||||
|
Timer.periodic(const Duration(minutes: 2), (timer) async {
|
||||||
|
print("POLLIN FROM DISPUTES PROVIER");
|
||||||
|
// Get the tradeId from the mapping using disputeId
|
||||||
|
final tradeId = _disputeToTradeIdMap[disputeId];
|
||||||
|
if (tradeId == null) {
|
||||||
|
print("No tradeId found for disputeId: $disputeId");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dispute? dispute = await getDispute(tradeId);
|
||||||
|
if (dispute != null && dispute.id == disputeId) {
|
||||||
|
for (var message in dispute.chatMessage) {
|
||||||
|
if (message.senderIsTrader) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!_chatMessages[disputeId]!.contains(message)) {
|
||||||
|
_chatMessages[disputeId]!.add(message);
|
||||||
|
if (_chatControllers.containsKey(disputeId)) {
|
||||||
|
_chatControllers[disputeId]!.add(_chatMessages[disputeId]!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void debugPrintTradeIdToDisputeMap() {
|
||||||
|
if (_tradeIdToDisputeMap.isEmpty) {
|
||||||
|
print("The _tradeIdToDisputeMap is currently empty.");
|
||||||
|
} else {
|
||||||
|
//_tradeIdToDisputeMap.forEach((tradeId, dispute) {
|
||||||
|
//print('Trade ID: $tradeId, Dispute ID: ${dispute.id}');
|
||||||
|
//});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Dispute? getDisputeByTradeId(String tradeId) {
|
||||||
|
print("Attempting to retrieve dispute for Trade ID: $tradeId");
|
||||||
|
|
||||||
|
if (_tradeIdToDisputeMap.containsKey(tradeId)) {
|
||||||
|
print("Dispute found for Trade ID: $tradeId");
|
||||||
|
debugPrintTradeIdToDisputeMap(); // Print the entire map for debugging
|
||||||
|
return _tradeIdToDisputeMap[tradeId];
|
||||||
|
} else {
|
||||||
|
print("No dispute found for Trade ID: $tradeId");
|
||||||
|
//debugPrintTradeIdToDisputeMap(); // Print the entire map for debugging
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Dispute>?> getDisputes() async {
|
||||||
|
List<Dispute> disputes = [];
|
||||||
|
// Attempt to retrieve disputes from the service
|
||||||
|
try {
|
||||||
|
var disputeClient = DisputeService();
|
||||||
|
disputes = await disputeClient.getDisputes();
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the list of disputes
|
||||||
|
List<Dispute> disputesList = disputes;
|
||||||
|
|
||||||
|
// Check if the disputes list is empty
|
||||||
|
if (disputesList.isEmpty) {
|
||||||
|
print("No disputes found.");
|
||||||
|
} else {
|
||||||
|
// Iterate through each dispute and map the tradeId to the dispute
|
||||||
|
for (var dispute in disputesList) {
|
||||||
|
_disputeToTradeIdMap[dispute.id] = dispute.tradeId;
|
||||||
|
_tradeIdToDisputeMap[dispute.tradeId] = dispute;
|
||||||
|
|
||||||
|
// Debugging output to verify the mapping
|
||||||
|
print("Mapping added: Trade ID ${dispute.tradeId} -> Dispute ID ${dispute.id}");
|
||||||
|
print("Current _tradeIdToDisputeMap contents:");
|
||||||
|
_tradeIdToDisputeMap.forEach((tradeId, mappedDispute) {
|
||||||
|
print("Trade ID: $tradeId, Dispute ID: ${mappedDispute.id}");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign disputes to the internal list and notify listeners
|
||||||
|
_disputes = disputesList;
|
||||||
|
notifyListeners();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Dispute?> getDispute(String tradeId) async {
|
||||||
|
try {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
Dispute? dispute;
|
||||||
|
var disputeClient = DisputeService();
|
||||||
|
dispute = await disputeClient.getDispute(tradeId);
|
||||||
|
|
||||||
|
Dispute? newDispute = dispute;
|
||||||
|
|
||||||
|
if (newDispute != null) {
|
||||||
|
_disputeToTradeIdMap[newDispute.id] = tradeId;
|
||||||
|
_tradeIdToDisputeMap[newDispute.tradeId] = newDispute;
|
||||||
|
|
||||||
|
// Update the _disputes list or map if necessary
|
||||||
|
final int existingIndex = _disputes.indexWhere((d) => d!.id == newDispute.id);
|
||||||
|
|
||||||
|
if (existingIndex != -1) {
|
||||||
|
_disputes[existingIndex] = newDispute;
|
||||||
|
} else {
|
||||||
|
_disputes.add(newDispute);
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyListeners();
|
||||||
|
return newDispute;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to get dispute: $e");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> resolveDispute(String tradeId, DisputeResult_Winner? winner, DisputeResult_Reason? reason, String? summaryNotes, Int64? customPayoutAmount) async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
|
||||||
|
Dispute? dispute = await getDispute(tradeId);
|
||||||
|
if (!dispute!.isOpener) {
|
||||||
|
throw Exception("You can't close a dispute you didn't open!");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
DisputeService().resolveDispute(
|
||||||
|
tradeId,
|
||||||
|
winner,
|
||||||
|
reason,
|
||||||
|
summaryNotes,
|
||||||
|
customPayoutAmount,
|
||||||
|
);
|
||||||
|
|
||||||
|
getDisputes();
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to resolve dispute: $e");
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Dispute?> openDispute(String tradeId) async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
await DisputeService().openDispute(tradeId);
|
||||||
|
await getDisputes();
|
||||||
|
Dispute? dispute = _disputes.firstWhere((d) => d!.tradeId == tradeId, orElse: () => null);
|
||||||
|
return dispute;
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to open dispute: $e");
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> sendDisputeChatMessage(String disputeId, String message, Iterable<Attachment> attachments) async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
await DisputeService().sendDisputeChatMessage(
|
||||||
|
disputeId,
|
||||||
|
message,
|
||||||
|
attachments,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Might need to create a fake protobuf message and add it to the stream controller just to conform (so the message appears instantly)
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to send dispute chat message: $e");
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional: Method to close a specific chat stream
|
||||||
|
void closeChat(String disputeId) {
|
||||||
|
if (_chatControllers.containsKey(disputeId)) {
|
||||||
|
_chatControllers[disputeId]!.close();
|
||||||
|
_chatControllers.remove(disputeId);
|
||||||
|
_chatMessages.remove(disputeId);
|
||||||
|
_disputeToTradeIdMap.remove(disputeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void addChatMessage(ChatMessage chatMessage) {
|
||||||
|
// do something special!!!
|
||||||
|
}
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// Future<void> pollAction() async {
|
||||||
|
// getDisputes();
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class DisputeChatProvider with ChangeNotifier {
|
||||||
|
final HavenoChannel _havenoChannel;
|
||||||
|
|
||||||
|
// Map to store StreamControllers for each chat
|
||||||
|
final Map<String, StreamController<List<ChatMessage>>> _chatControllers = {};
|
||||||
|
|
||||||
|
// Map to store chat messages
|
||||||
|
final Map<String, List<ChatMessage>> _chatMessages = {};
|
||||||
|
|
||||||
|
DisputeChatProvider(this._havenoChannel);
|
||||||
|
|
||||||
|
// Get or create a StreamController for specific chat
|
||||||
|
Stream<List<ChatMessage>> chatMessagesStream(String disputeId) {
|
||||||
|
if (!_chatControllers.containsKey(disputeId)) {
|
||||||
|
_chatControllers[disputeId] = StreamController<List<ChatMessage>>.broadcast();
|
||||||
|
// Load initial messages and start polling if needed
|
||||||
|
_startPolling(disputeId);
|
||||||
|
}
|
||||||
|
return _chatControllers[disputeId]!.stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load initial messages for the dispute
|
||||||
|
Future<void> loadInitialMessages(String disputeId, Dispute dispute) async {
|
||||||
|
_chatMessages[disputeId] = dispute.chatMessage;
|
||||||
|
if (_chatControllers.containsKey(disputeId)) {
|
||||||
|
_chatControllers[disputeId]!.add(_chatMessages[disputeId]!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a new message
|
||||||
|
void addChatMessage(String disputeId, ChatMessage message) {
|
||||||
|
if (!_chatMessages.containsKey(disputeId)) {
|
||||||
|
_chatMessages[disputeId] = [];
|
||||||
|
}
|
||||||
|
_chatMessages[disputeId]!.add(message);
|
||||||
|
_chatControllers[disputeId]?.add(_chatMessages[disputeId]!);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> sendDisputeChatMessage(String disputeId, String message, Iterable<Attachment> attachments) async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
await DisputeService().sendDisputeChatMessage(
|
||||||
|
disputeId,
|
||||||
|
message,
|
||||||
|
attachments,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to send dispute chat message: $e");
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startPolling(String disputeId) {
|
||||||
|
Timer.periodic(const Duration(minutes: 2), (timer) async {
|
||||||
|
// Implement polling logic to fetch new messages periodically
|
||||||
|
print("Polling for new messages for dispute: $disputeId");
|
||||||
|
// Fetch dispute or chat messages and update the stream
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_chatControllers.forEach((_, controller) => controller.close());
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
43
lib/providers/haveno_client_providers/help_provider.dart
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:haveno/haveno_client.dart';
|
||||||
|
import 'package:haveno/haveno_service.dart';
|
||||||
|
|
||||||
|
class HelpProvider with ChangeNotifier {
|
||||||
|
final HavenoChannel _havenoChannel = HavenoChannel();
|
||||||
|
|
||||||
|
HelpProvider(); //: super(const Duration(minutes: 1));
|
||||||
|
|
||||||
|
Future<String?> getMethodHelp(String methodName) async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
final helpService = HelpService();
|
||||||
|
return await helpService.getMethodHelp(methodName);
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed get method help: $e");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:grpc/grpc.dart';
|
||||||
|
import 'package:haveno/grpc_models.dart';
|
||||||
|
import 'package:haveno/haveno_service.dart';
|
||||||
|
|
||||||
|
class NotificationsProvider with ChangeNotifier {
|
||||||
|
|
||||||
|
NotificationsProvider();
|
||||||
|
|
||||||
|
Future<void> listen() async {
|
||||||
|
ResponseStream<NotificationMessage> responseStream = NotificationsService() as ResponseStream<NotificationMessage>;
|
||||||
|
|
||||||
|
// Listen to the stream of notifications
|
||||||
|
responseStream.listen(
|
||||||
|
(notification) {
|
||||||
|
// Handle the notification, for example, print it to the debug console
|
||||||
|
print('Received notification: ${notification.toString()}');
|
||||||
|
|
||||||
|
// You can also add custom logic to handle different types of notifications here
|
||||||
|
},
|
||||||
|
onError: (error) {
|
||||||
|
// Handle errors from the stream
|
||||||
|
print('Error receiving notifications: $error');
|
||||||
|
},
|
||||||
|
onDone: () {
|
||||||
|
// Handle the completion of the stream
|
||||||
|
print('Notification stream closed');
|
||||||
|
},
|
||||||
|
cancelOnError: true, // Optionally cancel the subscription if an error occurs
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
225
lib/providers/haveno_client_providers/offers_provider.dart
Normal file
|
@ -0,0 +1,225 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
import 'dart:async'; // Import the async library for Timer
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:grpc/grpc.dart';
|
||||||
|
import 'package:haveno/grpc_models.dart';
|
||||||
|
import 'package:haveno/haveno_client.dart';
|
||||||
|
import 'package:haveno_app/models/schema.dart';
|
||||||
|
import 'package:fixnum/fixnum.dart' as fixnum;
|
||||||
|
import 'package:haveno_app/utils/database_helper.dart';
|
||||||
|
|
||||||
|
class OffersProvider with ChangeNotifier, CooldownMixin {
|
||||||
|
final HavenoChannel _havenoChannel;
|
||||||
|
final DatabaseHelper _databaseHelper = DatabaseHelper.instance;
|
||||||
|
List<OfferInfo> _offers = [];
|
||||||
|
OfferInfo? _lastCreatedOffer;
|
||||||
|
String? _lastCancelledOfferId;
|
||||||
|
List<OfferInfo> _myOffers = []; // Timer to periodically call getOffers
|
||||||
|
|
||||||
|
OffersProvider(this._havenoChannel) {
|
||||||
|
setCooldownDurations({
|
||||||
|
'getOffers': const Duration(minutes: 1), // 2 minutes cooldown for getOffers
|
||||||
|
'getMyOffers': const Duration(minutes: 2), // 2 minutes seconds cooldown for getMyOffers
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
List<OfferInfo>? get offers => _offers;
|
||||||
|
List<OfferInfo>? get marketBuyOffers =>
|
||||||
|
_offers.where((offer) => offer.direction == 'SELL' && !offer.isMyOffer).toList();
|
||||||
|
List<OfferInfo>? get marketSellOffers =>
|
||||||
|
_offers.where((offer) => offer.direction == 'BUY' && !offer.isMyOffer).toList();
|
||||||
|
OfferInfo? get lastCreatedOffer => _lastCreatedOffer;
|
||||||
|
String? get lastCancelledOffer => _lastCancelledOfferId;
|
||||||
|
List<OfferInfo>? get myOffers => _myOffers;
|
||||||
|
List<OfferInfo>? get mySellOffers =>
|
||||||
|
_myOffers.where((offer) => offer.direction == 'SELL' && offer.isMyOffer).toList(); // It's reversed because if an offer is your own, then it's not a sell offer to you
|
||||||
|
List<OfferInfo>? get myBuyOffers =>
|
||||||
|
_myOffers.where((offer) => offer.direction == 'BUY' && offer.isMyOffer).toList(); // It's reversed because if an offer is your own, then it's not a buy offer to you
|
||||||
|
|
||||||
|
|
||||||
|
Future<void> getAllOffers() async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
await getOffers();
|
||||||
|
await Future.delayed(const Duration(seconds: 3));
|
||||||
|
await getMyOffers();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<OfferInfo>> getOffers() async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
|
||||||
|
// Check if the cooldown has expired, if so, fetch from server
|
||||||
|
if (!await isCooldownValid('getOffers')) {
|
||||||
|
try {
|
||||||
|
// Fetch from the server
|
||||||
|
final getOffersReply = await _havenoChannel.offersClient!.getOffers(GetOffersRequest());
|
||||||
|
final fetchedOffers = getOffersReply.offers;
|
||||||
|
|
||||||
|
if (fetchedOffers.isEmpty) {
|
||||||
|
await _databaseHelper.deleteOffers(null, isMyOffer: false);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save to local database and update the local cache
|
||||||
|
_offers = fetchedOffers;
|
||||||
|
List<OfferInfo> peerTradeOffers = [];
|
||||||
|
for (var offer in fetchedOffers) {
|
||||||
|
if (offer.hasIsMyOffer() && offer.isMyOffer != true) {
|
||||||
|
offer.isMyOffer = false;
|
||||||
|
}
|
||||||
|
peerTradeOffers.add(offer);
|
||||||
|
}
|
||||||
|
print("Returning ${fetchedOffers.length} offers from peers found on the daemon");
|
||||||
|
|
||||||
|
await _databaseHelper.deleteOffers(null, isMyOffer: false);
|
||||||
|
await _databaseHelper.insertOffers(peerTradeOffers);
|
||||||
|
|
||||||
|
updateCooldown('getOffers'); // Update the cooldown after fetching
|
||||||
|
notifyListeners(); // Notify listeners if applicable
|
||||||
|
return _offers;
|
||||||
|
} catch (e) {
|
||||||
|
updateCooldown('getOffers');
|
||||||
|
print("Failed to fetch peer offers from server: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If cooldown is active or no offers were fetched, return offers from the database or cache
|
||||||
|
if (_offers.isEmpty) {
|
||||||
|
_offers = await _databaseHelper.getOffers();
|
||||||
|
print("Returning ${_offers.length} of my own offers from the local database.");
|
||||||
|
} else {
|
||||||
|
print("Returning ${_offers.length} of my own offers from cache due to cooldown.");
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyListeners(); // Notify listeners if applicable
|
||||||
|
return _offers;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<OfferInfo>> getMyOffers() async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
|
||||||
|
// Check if cooldown has expired, if so, fetch from server
|
||||||
|
if (!await isCooldownValid('getMyOffers')) {
|
||||||
|
// Fetch from the server
|
||||||
|
final getMyOffersReply = await _havenoChannel.offersClient!.getMyOffers(GetMyOffersRequest());
|
||||||
|
_myOffers = getMyOffersReply.offers;
|
||||||
|
updateCooldown('getMyOffers');
|
||||||
|
|
||||||
|
if (_myOffers.isNotEmpty) {
|
||||||
|
// Save to local database
|
||||||
|
List<OfferInfo> myTradeOffers = [];
|
||||||
|
try {
|
||||||
|
for (var myOffer in _myOffers) {
|
||||||
|
myOffer.isMyOffer = true;
|
||||||
|
myTradeOffers.add(myOffer);
|
||||||
|
}
|
||||||
|
print("Returning ${_myOffers.length} of my own offers requested from the daemon.");
|
||||||
|
|
||||||
|
await _databaseHelper.deleteOffers(null, isMyOffer: true);
|
||||||
|
await _databaseHelper.insertOffers(myTradeOffers);
|
||||||
|
} catch (e) {
|
||||||
|
updateCooldown('getMyOffers');
|
||||||
|
print("Failed to save one or more of my own offers from the daemon locally to DB: ${e.toString()}");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print("None of my own offers found on daemon...");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print("Returning ${_myOffers.length} my own offers from the database or cache due to cooldown");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the offers, whether fetched or from the cache
|
||||||
|
return _myOffers;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> postOffer({
|
||||||
|
required String currencyCode,
|
||||||
|
required String direction,
|
||||||
|
required String price,
|
||||||
|
required bool useMarketBasedPrice,
|
||||||
|
double? marketPriceMarginPct,
|
||||||
|
required fixnum.Int64 amount,
|
||||||
|
required fixnum.Int64 minAmount,
|
||||||
|
required double buyerSecurityDepositPct,
|
||||||
|
String? triggerPrice,
|
||||||
|
required bool reserveExactAmount,
|
||||||
|
required String paymentAccountId,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final postOfferResponse = await _havenoChannel.offersClient!.postOffer(
|
||||||
|
PostOfferRequest(
|
||||||
|
currencyCode: currencyCode,
|
||||||
|
direction: direction,
|
||||||
|
price: price,
|
||||||
|
useMarketBasedPrice: useMarketBasedPrice,
|
||||||
|
marketPriceMarginPct: marketPriceMarginPct,
|
||||||
|
amount: amount,
|
||||||
|
minAmount: minAmount,
|
||||||
|
buyerSecurityDepositPct: buyerSecurityDepositPct,
|
||||||
|
triggerPrice: triggerPrice,
|
||||||
|
reserveExactAmount: reserveExactAmount,
|
||||||
|
paymentAccountId: paymentAccountId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final postedOffer = postOfferResponse.offer;
|
||||||
|
postedOffer.isMyOffer = true;
|
||||||
|
debugPrint(postedOffer.state);
|
||||||
|
debugPrint("IsActive = postedOffer.isActivated");
|
||||||
|
_lastCreatedOffer = postedOffer;
|
||||||
|
_myOffers.add(postedOffer);
|
||||||
|
await _databaseHelper.insertOffer(postedOffer);
|
||||||
|
notifyListeners();
|
||||||
|
} on GrpcError catch (e) {
|
||||||
|
print("Failed to post offer: $e");
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> cancelOffer(String offerId) async {
|
||||||
|
try {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
|
||||||
|
await _havenoChannel.offersClient
|
||||||
|
!.cancelOffer(CancelOfferRequest(id: offerId));
|
||||||
|
_lastCancelledOfferId = offerId;
|
||||||
|
_myOffers.removeWhere((offer) => offer.id == offerId);
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to cancel offer: $e");
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> editOffer({required String offerId, double? marketPriceMarginPct, String? triggerPrice}) async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
//not implemented at daemon
|
||||||
|
} catch(e) {
|
||||||
|
//not implemented at daemon
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String offerToString(OfferInfo offer) {
|
||||||
|
return 'Offer(id: ${offer.id}, direction: ${offer.direction}, price: ${offer.price}, amount: ${offer.amount}, minAmount: ${offer.minAmount}, volume: ${offer.volume}, minVolume: ${offer.minVolume}, baseCurrencyCode: ${offer.baseCurrencyCode}, date: ${offer.date}, state: ${offer.state}, paymentAccountId: ${offer.paymentAccountId}, paymentMethodId: ${offer.paymentMethodId})';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,222 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:haveno/grpc_models.dart';
|
||||||
|
import 'package:haveno/haveno_client.dart';
|
||||||
|
import 'package:haveno/profobuf_models.dart';
|
||||||
|
import 'package:haveno_app/models/schema.dart';
|
||||||
|
import 'package:haveno_app/utils/database_helper.dart';
|
||||||
|
|
||||||
|
class PaymentAccountsProvider with ChangeNotifier, CooldownMixin {
|
||||||
|
final HavenoChannel _havenoChannel;
|
||||||
|
final DatabaseHelper _databaseHelper = DatabaseHelper.instance;
|
||||||
|
List<PaymentMethod> _paymentMethods = [];
|
||||||
|
List<PaymentMethod> _cryptoCurrencyPaymentMethods = [];
|
||||||
|
List<PaymentAccount> _paymentAccounts = [];
|
||||||
|
final List<PaymentAccountForm> _paymentAccountForms = [];
|
||||||
|
final Map<String, PaymentAccountForm_FormId> _paymentMethodIdToPaymentAccountFormIdMap = {};
|
||||||
|
final Map<PaymentAccountForm_FormId, PaymentAccountForm> _paymentAccountFormIdToPaymentAccountFormMap = {};
|
||||||
|
PaymentAccountsProvider(this._havenoChannel) {
|
||||||
|
setCooldownDurations({
|
||||||
|
'getPaymentAccounts': const Duration(minutes: 1), // 2 minutes cooldown for getOffers
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
List<PaymentMethod> get paymentMethods => _paymentMethods;
|
||||||
|
List<PaymentMethod> get cryptoCurrencyPaymentMethods => _cryptoCurrencyPaymentMethods;
|
||||||
|
List<PaymentAccount> get paymentAccounts => _paymentAccounts;
|
||||||
|
|
||||||
|
List<PaymentAccountForm>? get paymentAccountForms => _paymentAccountForms.isNotEmpty ? _paymentAccountForms : null;
|
||||||
|
|
||||||
|
Future<List<PaymentMethod>?> getPaymentMethods() async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Attempt to load payment methods from the local database
|
||||||
|
_paymentMethods = await _databaseHelper.getAllPaymentMethods();
|
||||||
|
|
||||||
|
if (_paymentMethods.isNotEmpty) {
|
||||||
|
return _paymentMethods;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to load payment methods from the local database: $e");
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the local database does not have the data, fetch it from the remote service
|
||||||
|
try {
|
||||||
|
final getPaymentMethodsReply = await _havenoChannel.paymentAccountsClient!
|
||||||
|
.getPaymentMethods(GetPaymentMethodsRequest());
|
||||||
|
_paymentMethods = getPaymentMethodsReply.paymentMethods;
|
||||||
|
|
||||||
|
// Optionally, save the retrieved payment methods to the local database
|
||||||
|
if (_paymentMethods.isNotEmpty) {
|
||||||
|
for (var paymentMethod in _paymentMethods) {
|
||||||
|
await _databaseHelper.insertPaymentMethod(paymentMethod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to get payment methods from the remote service: $e");
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _paymentMethods;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<PaymentAccount>> getPaymentAccounts() async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
if (await isCooldownValid('getPaymentAccounts')) {
|
||||||
|
try {
|
||||||
|
// Attempt to load payment accounts from the local database
|
||||||
|
_paymentAccounts = await _databaseHelper.getAllPaymentAccounts();
|
||||||
|
if (_paymentAccounts.isNotEmpty) {
|
||||||
|
return _paymentAccounts;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to load payment accounts from the local database: $e");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
try {
|
||||||
|
final getPaymentAccountsReply = await _havenoChannel
|
||||||
|
.paymentAccountsClient!
|
||||||
|
.getPaymentAccounts(GetPaymentAccountsRequest());
|
||||||
|
_paymentAccounts = getPaymentAccountsReply.paymentAccounts;
|
||||||
|
print("Found ${_paymentAccounts.length} payment accounts on the daemon, will try caching them...");
|
||||||
|
updateCooldown('getPaymentAccounts');
|
||||||
|
} catch (e) {
|
||||||
|
updateCooldown('getPaymentAccounts');
|
||||||
|
print("Failed to get payment accounts from daemon: $e");
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (var paymentAccount in _paymentAccounts) {
|
||||||
|
await _databaseHelper.insertPaymentAccount(paymentAccount);
|
||||||
|
}
|
||||||
|
print("Successfully synced ${_paymentAccounts.length} payment accounts from the daemon to local storage");
|
||||||
|
} catch (e) {
|
||||||
|
print("Could not add the payment accounts from the daemon to the local database: ${e.toString()}");
|
||||||
|
}
|
||||||
|
return _paymentAccounts;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Future<List<PaymentMethod>?> getCryptoCurrencyPaymentMethods() async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
final getCryptoCurrencyPaymentMethodsReply = await _havenoChannel
|
||||||
|
.paymentAccountsClient!
|
||||||
|
.getCryptoCurrencyPaymentMethods(
|
||||||
|
GetCryptoCurrencyPaymentMethodsRequest());
|
||||||
|
_cryptoCurrencyPaymentMethods = getCryptoCurrencyPaymentMethodsReply.paymentMethods;
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to get payment accounts: $e");
|
||||||
|
}
|
||||||
|
return paymentMethods;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<PaymentAccountForm?> getPaymentAccountForm(String paymentMethodId) async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
PaymentAccountForm? paymentAccountForm;
|
||||||
|
bool didLoadFromDb = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Attempt to load payment account form from the local database
|
||||||
|
paymentAccountForm = await _databaseHelper.getPaymentAccountFormByPaymentMethodId(paymentMethodId);
|
||||||
|
if (paymentAccountForm != null) {
|
||||||
|
didLoadFromDb = true;
|
||||||
|
print("Loaded payment account form from local database for paymentMethod ID: $paymentMethodId.");
|
||||||
|
return paymentAccountForm;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to load payment account form from the local database: $e");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!didLoadFromDb) {
|
||||||
|
try {
|
||||||
|
// Fetch from the remote service if not found locally
|
||||||
|
final paymentAccountFormReply = await _havenoChannel.paymentAccountsClient!.getPaymentAccountForm(
|
||||||
|
GetPaymentAccountFormRequest(paymentMethodId: paymentMethodId),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (paymentAccountFormReply.hasPaymentAccountForm()) {
|
||||||
|
paymentAccountForm = paymentAccountFormReply.paymentAccountForm;
|
||||||
|
print("Loaded payment account form from remote service for paymentMethod ID: $paymentMethodId.");
|
||||||
|
|
||||||
|
// Store the fetched form in the local database for future use
|
||||||
|
await _databaseHelper.insertPaymentAccountForm(paymentMethodId, paymentAccountForm);
|
||||||
|
|
||||||
|
// Add a delay to respect network cooldown limitations, but this needs improving #TODO CooldownManager, or
|
||||||
|
// EVEN BETTER, remove cooldowns from the daemon completely, I'mm a
|
||||||
|
await Future.delayed(const Duration(seconds: 4));
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to get the payment form from remote service: $e");
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return paymentAccountForm;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<PaymentAccountForm>?> getAllPaymentAccountForms() async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
if (_paymentMethods.isEmpty) {
|
||||||
|
_paymentMethods = (await getPaymentMethods())!;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var paymentMethod in _paymentMethods) {
|
||||||
|
var paymentAccountForm = await getPaymentAccountForm(paymentMethod.id);
|
||||||
|
if (paymentAccountForm == null) continue;
|
||||||
|
|
||||||
|
if (!_paymentAccountForms.contains(paymentAccountForm)) {
|
||||||
|
_paymentAccountForms.add(paymentAccountForm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print("There are currently ${(await _databaseHelper.getAllPaymentAccountForms())?.length ?? 'an unknown number of'} payment account forms in the database.");
|
||||||
|
return _paymentAccountForms;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<PaymentAccount?> createPaymentAccount(String paymentMethodId, PaymentAccountForm form) async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
notifyListeners();
|
||||||
|
final createdPaymentAccount = await _havenoChannel.paymentAccountsClient!
|
||||||
|
.createPaymentAccount(
|
||||||
|
CreatePaymentAccountRequest(paymentAccountForm: form));
|
||||||
|
var paymentAccount = createdPaymentAccount.paymentAccount;
|
||||||
|
print("Created Payment Account: $paymentAccount");
|
||||||
|
notifyListeners();
|
||||||
|
return paymentAccount;
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to create payment account: $e");
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
55
lib/providers/haveno_client_providers/price_provider.dart
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:haveno/grpc_models.dart';
|
||||||
|
import 'package:haveno/haveno_client.dart';
|
||||||
|
|
||||||
|
class PricesProvider with ChangeNotifier {
|
||||||
|
final HavenoChannel _havenoChannel;
|
||||||
|
List<MarketPriceInfo> _marketPrices = [];
|
||||||
|
Timer? _timer;
|
||||||
|
|
||||||
|
PricesProvider(this._havenoChannel);
|
||||||
|
|
||||||
|
List<MarketPriceInfo> get prices => _marketPrices;
|
||||||
|
|
||||||
|
Future<void> getXmrMarketPrices() async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
final getMarketPricesReply = await _havenoChannel.priceClient
|
||||||
|
!.getMarketPrices(MarketPricesRequest());
|
||||||
|
_marketPrices = getMarketPricesReply.marketPrice;
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to get prices: $e");
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_timer?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:haveno/haveno_client.dart';
|
||||||
|
import 'package:haveno/haveno_service.dart';
|
||||||
|
import 'package:haveno/profobuf_models.dart';
|
||||||
|
import 'package:haveno_app/utils/database_helper.dart';
|
||||||
|
|
||||||
|
class TradeStatisticsProvider with ChangeNotifier {
|
||||||
|
final HavenoChannel _havenoChannel;
|
||||||
|
final DatabaseHelper _databaseHelper = DatabaseHelper.instance;
|
||||||
|
List<TradeStatistics3> _tradeStatistics = [];
|
||||||
|
|
||||||
|
TradeStatisticsProvider(this._havenoChannel);
|
||||||
|
|
||||||
|
List<TradeStatistics3>? get tradeStatisticsList => _tradeStatistics;
|
||||||
|
|
||||||
|
Future<void> getTradeStatistics() async {
|
||||||
|
try {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
|
||||||
|
if (_tradeStatistics.isEmpty) {
|
||||||
|
_tradeStatistics = await _databaseHelper.getTradeStatistics(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
TradeStatisticsService? tradeStatisticsService = TradeStatisticsService();
|
||||||
|
List<TradeStatistics3>? tradeStatistics3 = await tradeStatisticsService.getTradeStatistics();
|
||||||
|
|
||||||
|
if (tradeStatistics3 != null && tradeStatistics3.isNotEmpty) {
|
||||||
|
_tradeStatistics = tradeStatistics3;
|
||||||
|
try {
|
||||||
|
for (var tradeStatistic in _tradeStatistics) {
|
||||||
|
_databaseHelper.insertTradeStatistic(tradeStatistic);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print(
|
||||||
|
"Error adding one ore more trade statistic records to the database: ${e.toString()}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to get trade statistics: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
325
lib/providers/haveno_client_providers/trades_provider.dart
Normal file
|
@ -0,0 +1,325 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:fixnum/fixnum.dart' as fixnum;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:haveno/grpc_models.dart';
|
||||||
|
import 'package:haveno/haveno_client.dart';
|
||||||
|
import 'package:haveno/haveno_service.dart';
|
||||||
|
import 'package:haveno/profobuf_models.dart';
|
||||||
|
import 'package:haveno_app/models/schema.dart';
|
||||||
|
import 'package:haveno_app/utils/database_helper.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
|
class TradesProvider with ChangeNotifier, CooldownMixin {
|
||||||
|
final HavenoChannel _havenoChannel;
|
||||||
|
final DatabaseHelper _databaseHelper = DatabaseHelper.instance;
|
||||||
|
TradeUpdateCallback? onTradeUpdate;
|
||||||
|
NewChatMessageCallback? onNewChatMessage;
|
||||||
|
List<TradeInfo> _trades = [];
|
||||||
|
TradeInfo? _currentTrade;
|
||||||
|
final Map<String, List<ChatMessage>> _chatMessages = {};
|
||||||
|
final Map<String, DateTime> _estimatedConfirmationTimes = {};
|
||||||
|
|
||||||
|
TradesProvider(this._havenoChannel) {
|
||||||
|
setCooldownDurations({
|
||||||
|
'getTrades':
|
||||||
|
const Duration(seconds: 10), // 10 seconds cooldown for getTrades
|
||||||
|
'getTrade': const Duration(seconds: 10)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
List<TradeInfo>? _newUnreadTrades;
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
List<TradeInfo>? get newUnreadTrades => _newUnreadTrades;
|
||||||
|
|
||||||
|
List<TradeInfo> get trades => _trades;
|
||||||
|
List<TradeInfo> get activeTrades => _trades
|
||||||
|
.where((trade) =>
|
||||||
|
trade.state != 'SELLER_SENT_PAYMENT_RECEIVED_MSG' &&
|
||||||
|
trade.state != 'SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG')
|
||||||
|
.toList();
|
||||||
|
List<TradeInfo>? get completedTrades => _trades
|
||||||
|
.where((trade) =>
|
||||||
|
trade.state == 'SELLER_SENT_PAYMENT_RECEIVED_MSG' ||
|
||||||
|
trade.state == 'SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG')
|
||||||
|
.toList();
|
||||||
|
List<TradeInfo>? get cancelledTrades => _trades;
|
||||||
|
List<TradeInfo>? get expiredTrades =>
|
||||||
|
_trades.where((trade) => trade.disputeState == 'something').toList();
|
||||||
|
List<TradeInfo>? get disputedTrades =>
|
||||||
|
_trades.where((trade) => trade.disputeState != 'NO_DISPUTE').toList();
|
||||||
|
TradeInfo? get currentTrade => _currentTrade;
|
||||||
|
Map<String, List<ChatMessage>> get chatMessages => _chatMessages;
|
||||||
|
Map<String, DateTime> get estimatedConfirmationTimes =>
|
||||||
|
_estimatedConfirmationTimes;
|
||||||
|
|
||||||
|
Future<void> getTrades() async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
if (!await isCooldownValid('getTrades')) {
|
||||||
|
await updateCooldown('getTrades');
|
||||||
|
try {
|
||||||
|
final tradesClient = TradesService();
|
||||||
|
final fetchedTrades = await tradesClient.getTrades();
|
||||||
|
|
||||||
|
// Deep compare the list of trades
|
||||||
|
final tradesAreEqual =
|
||||||
|
const DeepCollectionEquality().equals(_trades, fetchedTrades);
|
||||||
|
|
||||||
|
if (!tradesAreEqual) {
|
||||||
|
_trades = fetchedTrades!;
|
||||||
|
|
||||||
|
// Insert or update trades in the database
|
||||||
|
await _databaseHelper.insertTrades(
|
||||||
|
fetchedTrades); // Assuming this handles insert or update
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to get trades: $e");
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var tradesBefore = _trades;
|
||||||
|
_trades = await _databaseHelper.getAllTrades();
|
||||||
|
|
||||||
|
final tradesAreEqual =
|
||||||
|
const DeepCollectionEquality().equals(tradesBefore, _trades);
|
||||||
|
|
||||||
|
if (!tradesAreEqual) {
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
print(
|
||||||
|
"Returned ${_trades.length} trades from the server since cooldown is active");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> getTrade(String tradeId) async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
|
||||||
|
TradeInfo? existingTrade;
|
||||||
|
|
||||||
|
// Check if the cooldown is valid
|
||||||
|
if (!await isCooldownValid('getTrade')) {
|
||||||
|
await updateCooldown('getTrade');
|
||||||
|
try {
|
||||||
|
final getTradeReply = await _havenoChannel.tradesClient!
|
||||||
|
.getTrade(GetTradeRequest(tradeId: tradeId));
|
||||||
|
final fetchedTrade = getTradeReply.trade;
|
||||||
|
|
||||||
|
// Find the corresponding trade in the current list of trades
|
||||||
|
try {
|
||||||
|
existingTrade =
|
||||||
|
_trades.firstWhere((trade) => trade.tradeId == tradeId);
|
||||||
|
} catch (e) {
|
||||||
|
print(
|
||||||
|
"No existing trade for $tradeId, very odd we're fetching it? $e");
|
||||||
|
existingTrade = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare the existing trade with the fetched one
|
||||||
|
if (!const DeepCollectionEquality()
|
||||||
|
.equals(existingTrade, fetchedTrade)) {
|
||||||
|
// Update the trade in the _trades list
|
||||||
|
_trades = _trades
|
||||||
|
.map((trade) => trade.tradeId == tradeId ? fetchedTrade : trade)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
// Update the trade in the database
|
||||||
|
await _databaseHelper.insertTrade(
|
||||||
|
fetchedTrade); // Assuming this handles insert or update
|
||||||
|
onTradeUpdate?.call(fetchedTrade, false);
|
||||||
|
// Update cooldown and notify listeners
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to get trade: $e");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If cooldown is active, fetch the trade from the database
|
||||||
|
final tradeFromDb = await _databaseHelper.getTradeById(tradeId);
|
||||||
|
|
||||||
|
if (tradeFromDb != null) {
|
||||||
|
// Find the corresponding trade in the current list of trades
|
||||||
|
try {
|
||||||
|
existingTrade =
|
||||||
|
_trades.firstWhere((trade) => trade.tradeId == tradeId);
|
||||||
|
} catch (e) {
|
||||||
|
existingTrade = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!const DeepCollectionEquality()
|
||||||
|
.equals(existingTrade, tradeFromDb)) {
|
||||||
|
// Update the _trades list and notify listeners if there's a change
|
||||||
|
_trades = _trades.map((trade) {
|
||||||
|
return trade.tradeId == tradeId ? tradeFromDb : trade;
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print(
|
||||||
|
"Returned trade $tradeId from the database since cooldown is active");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<TradeInfo?> takeOffer(
|
||||||
|
String? offerId, String? paymentAccountId, fixnum.Int64 amount) async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
final takeOfferReply = await _havenoChannel.tradesClient!.takeOffer(
|
||||||
|
TakeOfferRequest(
|
||||||
|
offerId: offerId,
|
||||||
|
paymentAccountId: paymentAccountId,
|
||||||
|
amount: amount));
|
||||||
|
_currentTrade = takeOfferReply.trade;
|
||||||
|
notifyListeners();
|
||||||
|
return _currentTrade;
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to take offer: $e");
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> sendChatMessage(String? tradeId, String? message) async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
await _havenoChannel.tradesClient!.sendChatMessage(
|
||||||
|
SendChatMessageRequest(tradeId: tradeId, message: message));
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to send trade chat message: $e");
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> getChatMessages(String tradeId) async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
final getChatMessagesReply = await _havenoChannel.tradesClient!
|
||||||
|
.getChatMessages(GetChatMessagesRequest(tradeId: tradeId));
|
||||||
|
_chatMessages[tradeId] = getChatMessagesReply.message;
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to get trade chat messages: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> confirmPaymentSent(String tradeId) async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
await _havenoChannel.tradesClient!
|
||||||
|
.confirmPaymentSent(ConfirmPaymentSentRequest(tradeId: tradeId));
|
||||||
|
await getTrade(tradeId);
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to confirm payment sent: $e");
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> confirmPaymentReceived(String tradeId) async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
await _havenoChannel.tradesClient!.confirmPaymentReceived(
|
||||||
|
ConfirmPaymentReceivedRequest(tradeId: tradeId));
|
||||||
|
await getTrade(tradeId);
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to confirm payment received: $e");
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> completeTrade(String? tradeId) async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
await _havenoChannel.tradesClient!
|
||||||
|
.completeTrade(CompleteTradeRequest(tradeId: tradeId));
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to complete trade: $e");
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> withdrawFunds(
|
||||||
|
String? tradeId, String? address, String? memo) async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
await _havenoChannel.tradesClient!.withdrawFunds(
|
||||||
|
WithdrawFundsRequest(tradeId: tradeId, address: address, memo: memo));
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to withdraw funds from trade: $e");
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used by the listener to add remote messages, dont use to post message to network
|
||||||
|
void addChatMessage(ChatMessage chatMessage) {
|
||||||
|
// Ensure the tradeId exists in the _chatMessages map
|
||||||
|
if (_chatMessages.containsKey(chatMessage.tradeId)) {
|
||||||
|
// Check if a message with the same UID already exists
|
||||||
|
bool messageExists = _chatMessages[chatMessage.tradeId]!
|
||||||
|
.any((msg) => msg.uid == chatMessage.uid);
|
||||||
|
|
||||||
|
// If the message does not exist, add it to the list
|
||||||
|
if (!messageExists) {
|
||||||
|
_chatMessages[chatMessage.tradeId]!.add(chatMessage);
|
||||||
|
onNewChatMessage?.call(chatMessage);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If the tradeId does not exist, create a new entry
|
||||||
|
_chatMessages[chatMessage.tradeId] = [chatMessage];
|
||||||
|
onNewChatMessage?.call(chatMessage);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used by the listener
|
||||||
|
void createOrUpdateTrade(TradeInfo trade) {
|
||||||
|
// Find the index of the trade with the same tradeId in the _trades list
|
||||||
|
final index = _trades
|
||||||
|
.indexWhere((existingTrade) => existingTrade.tradeId == trade.tradeId);
|
||||||
|
|
||||||
|
if (index != -1) {
|
||||||
|
// If the trade exists, update it
|
||||||
|
_trades[index] = trade;
|
||||||
|
} else {
|
||||||
|
// If the trade does not exist, add it to the list
|
||||||
|
_trades.add(trade);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the trade in the database
|
||||||
|
_databaseHelper.insertTrade(trade);
|
||||||
|
|
||||||
|
// Get deep trade
|
||||||
|
getTrade(trade.tradeId);
|
||||||
|
|
||||||
|
// Trigger the callback
|
||||||
|
onTradeUpdate?.call(trade, !(index != -1));
|
||||||
|
|
||||||
|
// Notify listeners that the trade list has been updated
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
45
lib/providers/haveno_client_providers/version_provider.dart
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:haveno/haveno_client.dart';
|
||||||
|
import 'package:haveno/haveno_service.dart';
|
||||||
|
|
||||||
|
class GetVersionProvider with ChangeNotifier {
|
||||||
|
final HavenoChannel _havenoChannel;
|
||||||
|
String? _version;
|
||||||
|
|
||||||
|
GetVersionProvider(this._havenoChannel);
|
||||||
|
|
||||||
|
String? get version => _version;
|
||||||
|
|
||||||
|
Future<void> fetchVersion() async {
|
||||||
|
try {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
final getVersionClient = GetVersionService();
|
||||||
|
_version = await getVersionClient.fetchVersion();
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to get Haveno version: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
180
lib/providers/haveno_client_providers/wallets_provider.dart
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:haveno/grpc_models.dart';
|
||||||
|
import 'package:haveno/haveno_client.dart';
|
||||||
|
import 'package:haveno/haveno_service.dart';
|
||||||
|
import 'package:haveno_app/models/schema.dart';
|
||||||
|
|
||||||
|
class WalletsProvider with ChangeNotifier, CooldownMixin {
|
||||||
|
final HavenoChannel _havenoChannel;
|
||||||
|
final WalletsService _walletsService = WalletsService();
|
||||||
|
BalancesInfo? _balances;
|
||||||
|
List<XmrTx> _xmrTxs = [];
|
||||||
|
String? _xmrPrimaryAddress;
|
||||||
|
List<XmrIncomingTransfer> _xmrIncomingTransfers = [];
|
||||||
|
List<XmrOutgoingTransfer> _xmrOutgoingTransfers = [];
|
||||||
|
|
||||||
|
WalletsProvider(this._havenoChannel) {
|
||||||
|
setCooldownDurations({
|
||||||
|
'getBalances': const Duration(seconds: 15),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
BalancesInfo? get balances => _balances;
|
||||||
|
String? get xmrPrimaryAddress => _xmrPrimaryAddress;
|
||||||
|
List<XmrTx>? get xmrTxs => _xmrTxs;
|
||||||
|
List<XmrIncomingTransfer> get xmrIncomingTransfers => _xmrIncomingTransfers;
|
||||||
|
List<XmrOutgoingTransfer> get xmrOutgoingTransfers => _xmrOutgoingTransfers;
|
||||||
|
|
||||||
|
Future<void> getBalances() async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
if (!await isCooldownValid('getBalances')) {
|
||||||
|
try {
|
||||||
|
_balances = await _walletsService.getBalances();
|
||||||
|
updateCooldown('getBalances');
|
||||||
|
print("Loaded balances...");
|
||||||
|
notifyListeners();
|
||||||
|
return;
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to get balances: $e");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print("Tried to get getBalances when cooldown is active");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> getXmrPrimaryAddress() async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
final getXmrPrimaryAddressReply = await _havenoChannel.walletsClient!
|
||||||
|
.getXmrPrimaryAddress(GetXmrPrimaryAddressRequest());
|
||||||
|
_xmrPrimaryAddress = getXmrPrimaryAddressReply.primaryAddress;
|
||||||
|
print("Primary Address: $_xmrPrimaryAddress");
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to get primary address: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> getXmrTxs() async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
final getXmrTxsReply =
|
||||||
|
await _havenoChannel.walletsClient!.getXmrTxs(GetXmrTxsRequest());
|
||||||
|
_xmrTxs = getXmrTxsReply.txs;
|
||||||
|
_xmrIncomingTransfers = [];
|
||||||
|
_xmrOutgoingTransfers = [];
|
||||||
|
|
||||||
|
for (var xmrTx in _xmrTxs) {
|
||||||
|
_xmrIncomingTransfers.addAll(xmrTx.incomingTransfers);
|
||||||
|
if (xmrTx.hasOutgoingTransfer()) {
|
||||||
|
_xmrOutgoingTransfers.add(xmrTx.outgoingTransfer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to get XMR transactions: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> createXmrTx(Iterable<XmrDestination> destinations) async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
final createXmrTxsReply = await _havenoChannel.walletsClient!
|
||||||
|
.createXmrTx(CreateXmrTxRequest(destinations: destinations));
|
||||||
|
var tx = createXmrTxsReply.tx;
|
||||||
|
// Check if tx with the same hash already exists
|
||||||
|
bool exists = _xmrTxs.any((existingTx) => existingTx.hash == tx.hash);
|
||||||
|
if (!exists) {
|
||||||
|
_xmrTxs.add(tx);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to create an XMR transaction: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> relayXmrTx(String metadata) async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
await _havenoChannel.walletsClient!
|
||||||
|
.relayXmrTx(RelayXmrTxRequest(metadata: metadata));
|
||||||
|
} catch (e) {
|
||||||
|
print("Error relaying transaciton: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> getXmrSeed() async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
final getXmrSeedReply =
|
||||||
|
await _havenoChannel.walletsClient!.getXmrSeed(GetXmrSeedRequest());
|
||||||
|
return getXmrSeedReply.seed;
|
||||||
|
} catch (e) {
|
||||||
|
print("Error getting seed phrase: $e");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setWalletPassword(String newPassword, String? password) async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
await _havenoChannel.walletsClient!.setWalletPassword(
|
||||||
|
SetWalletPasswordRequest(
|
||||||
|
password: password, newPassword: newPassword));
|
||||||
|
} catch (e) {
|
||||||
|
print("Error setting wallet password: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> lockWallet(String password) async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
await _havenoChannel.walletsClient!.lockWallet(LockWalletRequest());
|
||||||
|
} catch (e) {
|
||||||
|
print("Error setting wallet password: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> unlockWallet(String password) async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
await _havenoChannel.walletsClient!.unlockWallet(UnlockWalletRequest());
|
||||||
|
} catch (e) {
|
||||||
|
print("Error setting wallet password: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> removeWalletPassword(String password) async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
await _havenoChannel.walletsClient!.removeWalletPassword(
|
||||||
|
RemoveWalletPasswordRequest(password: password));
|
||||||
|
} catch (e) {
|
||||||
|
print("Error removing wallet password: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,155 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:haveno/grpc_models.dart';
|
||||||
|
import 'package:haveno/haveno_client.dart';
|
||||||
|
import 'package:haveno/haveno_service.dart';
|
||||||
|
|
||||||
|
class XmrConnectionsProvider with ChangeNotifier {
|
||||||
|
final HavenoChannel _havenoChannel = HavenoChannel();
|
||||||
|
|
||||||
|
List<UrlConnection> _xmrUrlConnections = [];
|
||||||
|
UrlConnection? _xmrActiveConnection;
|
||||||
|
|
||||||
|
XmrConnectionsProvider();
|
||||||
|
|
||||||
|
// Expose the list of connections and the active node
|
||||||
|
List<UrlConnection> get xmrNodeConnections => _xmrUrlConnections;
|
||||||
|
UrlConnection? get xmrActiveConnection => _xmrActiveConnection;
|
||||||
|
|
||||||
|
// Fetch connections from the server
|
||||||
|
Future<void> getXmrConnectionSettings() async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
final xmrConnectionsClient = XmrConnectionsService();
|
||||||
|
final response = await xmrConnectionsClient.getXmrConnectionSettings();
|
||||||
|
_xmrUrlConnections = response;
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to fetch connections: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch connections from the server
|
||||||
|
Future<void> checkConnections() async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
final response = await _havenoChannel.xmrConnectionsClient!.checkConnections(CheckConnectionsRequest());
|
||||||
|
_xmrUrlConnections = response.connections;
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to check connections: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the active node on the server and locally
|
||||||
|
Future<void> setConnection(UrlConnection connection) async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
await _havenoChannel.xmrConnectionsClient!.setConnection(SetConnectionRequest(connection: connection));
|
||||||
|
_xmrActiveConnection = connection; // Set the new active node locally
|
||||||
|
notifyListeners(); // Notify the UI to update
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to set active connection: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the currently active connection from the server
|
||||||
|
Future<void> getActiveConnection() async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
final response = await _havenoChannel.xmrConnectionsClient!.getConnection(GetConnectionRequest());
|
||||||
|
_xmrActiveConnection = response.connection;
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to get active connection: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to check if the selected connection is online (optional, if you need this)
|
||||||
|
Future<bool> checkConnection() async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
final response = await _havenoChannel.xmrConnectionsClient!.checkConnection(CheckConnectionRequest());
|
||||||
|
return response.connection.onlineStatus == UrlConnection_OnlineStatus.ONLINE;
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to check connection status: $e");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Add a connection to the list and notify
|
||||||
|
Future<bool> addConnection(UrlConnection connection) async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
await _havenoChannel.xmrConnectionsClient!.addConnection(AddConnectionRequest(connection: connection));
|
||||||
|
_xmrUrlConnections.add(connection); // Add to local list
|
||||||
|
notifyListeners(); // Notify listeners/UI
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to add connection: $e");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove a connection from the list and notify
|
||||||
|
Future<bool> removeConnection(String url) async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
await _havenoChannel.xmrConnectionsClient!.removeConnection(RemoveConnectionRequest(url: url));
|
||||||
|
_xmrUrlConnections.removeWhere((connection) => connection.url == url); // Remove from local list
|
||||||
|
notifyListeners(); // Notify listeners/UI
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to remove connection: $e");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove a connection from the list and notify
|
||||||
|
Future<bool> setAutoSwitchBestConnection(bool autoSwitch) async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
await _havenoChannel.xmrConnectionsClient!.setAutoSwitch(SetAutoSwitchRequest(autoSwitch: autoSwitch));
|
||||||
|
notifyListeners();
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to remove connection: $e");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove a connection from the list and notify
|
||||||
|
Future<bool> getAutoSwitchBestConnection() async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
final getAutoSwitchReply = await _havenoChannel.xmrConnectionsClient!.getAutoSwitch(GetAutoSwitchRequest());
|
||||||
|
return getAutoSwitchReply.autoSwitch;
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to remove connection: $e");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
51
lib/providers/haveno_client_providers/xmr_node_provider.dart
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:haveno/grpc_models.dart';
|
||||||
|
import 'package:haveno/haveno_client.dart';
|
||||||
|
import 'package:haveno/profobuf_models.dart';
|
||||||
|
|
||||||
|
class XmrNodeProvider with ChangeNotifier {
|
||||||
|
final HavenoChannel _havenoChannel = HavenoChannel();
|
||||||
|
|
||||||
|
XmrNodeSettings? _xmrNodeSettings;
|
||||||
|
|
||||||
|
XmrNodeProvider();
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
XmrNodeSettings? get xmrNodeSettings => _xmrNodeSettings;
|
||||||
|
|
||||||
|
Future<void> getXmrNodeSettings() async {
|
||||||
|
await _havenoChannel.onConnected;
|
||||||
|
try {
|
||||||
|
final getXmrNodeSettingsReply =
|
||||||
|
await _havenoChannel.xmrNodeClient!.getXmrNodeSettings(GetXmrNodeSettingsRequest());
|
||||||
|
_xmrNodeSettings = getXmrNodeSettingsReply.settings;
|
||||||
|
print("Getting XMR node settings from daemon...");
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to get XMR node settings: $e");
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
84
lib/providers/haveno_daemon_provider.dart
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:haveno_app/models/haveno_daemon_config.dart';
|
||||||
|
import 'package:haveno_app/services/secure_storage_service.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class HavenoDaemonProvider with ChangeNotifier {
|
||||||
|
final SecureStorageService _secureStorageService;
|
||||||
|
|
||||||
|
HavenoDaemonConfig? _currentDaemon;
|
||||||
|
// Getters
|
||||||
|
HavenoDaemonConfig? get currentDaemon => _currentDaemon;
|
||||||
|
bool get isPaired => _currentDaemon!.isVerified && _currentDaemon?.host != null;
|
||||||
|
|
||||||
|
HavenoDaemonProvider(this._secureStorageService) {
|
||||||
|
_initializeSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
get lastPing => null;
|
||||||
|
|
||||||
|
Future<void> _initializeSettings() async {
|
||||||
|
_currentDaemon = await _secureStorageService.readHavenoDaemonConfig();
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns how many seconds ago the last ping to Haveno Daemon was
|
||||||
|
Future<int?> ping() async {
|
||||||
|
if (_currentDaemon != null) {
|
||||||
|
try {
|
||||||
|
var httpClient = HttpClient();
|
||||||
|
var request = await httpClient.headUrl(Uri.http('${_currentDaemon!.host}:${_currentDaemon!.port}', '/'));
|
||||||
|
var response = await request.close();
|
||||||
|
httpClient.close();
|
||||||
|
|
||||||
|
if (response.statusCode == HttpStatus.ok) {
|
||||||
|
// If the response status is OK, the server is available
|
||||||
|
_currentDaemon!.setVerified(true);
|
||||||
|
_secureStorageService.writeHavenoDaemonConfig(_currentDaemon!);
|
||||||
|
return DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||||
|
} else {
|
||||||
|
// Handle non-OK response statuses if necessary
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} on SocketException catch (e) {
|
||||||
|
// Handle socket exceptions (e.g., server not available)
|
||||||
|
print('SocketException: $e');
|
||||||
|
return null;
|
||||||
|
} on HttpException catch (e) {
|
||||||
|
// Handle HTTP exceptions
|
||||||
|
print('HttpException: $e');
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
// Handle any other exceptions
|
||||||
|
print('Exception: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
20
lib/providers/haveno_providers/mobile_wallet_provider.dart
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
137
lib/providers/haveno_providers/settings_provider.dart
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:haveno_app/services/secure_storage_service.dart';
|
||||||
|
|
||||||
|
class SettingsProvider extends ChangeNotifier {
|
||||||
|
final SecureStorageService _secureStorageService;
|
||||||
|
|
||||||
|
// Preferences
|
||||||
|
String? _preferredLanguage;
|
||||||
|
String? _country;
|
||||||
|
String? _preferredCurrency;
|
||||||
|
String? _blockchainExplorer;
|
||||||
|
String? _maxDeviationFromMarketPrice;
|
||||||
|
|
||||||
|
// Display Options
|
||||||
|
bool _hideNonSupportedPaymentMethods = false;
|
||||||
|
bool _sortMarketListsByNumberOfOffersTrades = false;
|
||||||
|
bool _useDarkMode = false;
|
||||||
|
bool _autoWithdrawToNewStealthAddress = false;
|
||||||
|
|
||||||
|
// Supported currencies
|
||||||
|
final List<String> _supportedCurrencies = [
|
||||||
|
'CHF', 'MXN', 'CLP', 'ZAR', 'VND', 'AUD', 'ILS', 'IDR', 'TRY', 'AED', 'HKD', 'TWD', 'EUR', 'DKK',
|
||||||
|
'BCH', 'CAD', 'MYR', 'MMK', 'NOK', 'GEL', 'BTC', 'LKR', 'NGN', 'CZK', 'PKR', 'SEK', 'LTC', 'UAH',
|
||||||
|
'BHD', 'ARS', 'SAR', 'INR', 'CNY', 'THB', 'KRW', 'JPY', 'BDT', 'PLN', 'GBP', 'BMD', 'HUF', 'KWD',
|
||||||
|
'PHP', 'RUB', 'USD', 'SGD', 'ETH', 'NZD', 'BRL'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
String? get preferredLanguage => _preferredLanguage;
|
||||||
|
String? get country => _country;
|
||||||
|
String? get preferredCurrency => _preferredCurrency;
|
||||||
|
String? get blockchainExplorer => _blockchainExplorer;
|
||||||
|
String? get maxDeviationFromMarketPrice => _maxDeviationFromMarketPrice;
|
||||||
|
List<String> get supportedCurrencies => _supportedCurrencies;
|
||||||
|
|
||||||
|
bool get hideNonSupportedPaymentMethods => _hideNonSupportedPaymentMethods;
|
||||||
|
bool get sortMarketListsByNumberOfOffersTrades => _sortMarketListsByNumberOfOffersTrades;
|
||||||
|
bool get useDarkMode => _useDarkMode;
|
||||||
|
bool get autoWithdrawToNewStealthAddress => _autoWithdrawToNewStealthAddress;
|
||||||
|
|
||||||
|
SettingsProvider(this._secureStorageService) {
|
||||||
|
_initializeSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _initializeSettings() async {
|
||||||
|
_preferredLanguage = await _secureStorageService.readSettingsPreferredLanguage();
|
||||||
|
_country = await _secureStorageService.readSettingsCountry();
|
||||||
|
_preferredCurrency = await _secureStorageService.readSettingsPreferredCurrency() ?? 'USD';
|
||||||
|
_blockchainExplorer = await _secureStorageService.readSettingsBlockchainExplorer();
|
||||||
|
_maxDeviationFromMarketPrice = await _secureStorageService.readSettingsMaxDeviationFromMarketPrice();
|
||||||
|
|
||||||
|
_hideNonSupportedPaymentMethods = await _secureStorageService.readSettingsHideNonSupportedPaymentMethods() ?? false;
|
||||||
|
_sortMarketListsByNumberOfOffersTrades = await _secureStorageService.readSettingsSortMarketListsByNumberOfOffersTrades() ?? false;
|
||||||
|
_useDarkMode = await _secureStorageService.readSettingsUseDarkMode() ?? false;
|
||||||
|
_autoWithdrawToNewStealthAddress = await _secureStorageService.readSettingsAutoWithdrawToNewStealthAddress() ?? false;
|
||||||
|
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setters
|
||||||
|
Future<void> setPreferredLanguage(String languageCode) async {
|
||||||
|
await _secureStorageService.writeSettingsPreferredLanguage(languageCode);
|
||||||
|
_preferredLanguage = languageCode;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setCountry(String country) async {
|
||||||
|
await _secureStorageService.writeSettingsCountry(country);
|
||||||
|
_country = country;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setPreferredCurrency(String currencyCode) async {
|
||||||
|
await _secureStorageService.writeSettingsPreferredCurrency(currencyCode);
|
||||||
|
_preferredCurrency = currencyCode;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setBlockchainExplorer(String explorer) async {
|
||||||
|
await _secureStorageService.writeSettingsBlockchainExplorer(explorer);
|
||||||
|
_blockchainExplorer = explorer;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setMaxDeviationFromMarketPrice(String deviation) async {
|
||||||
|
await _secureStorageService.writeSettingsMaxDeviationFromMarketPrice(deviation);
|
||||||
|
_maxDeviationFromMarketPrice = deviation;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setHideNonSupportedPaymentMethods(bool value) async {
|
||||||
|
await _secureStorageService.writeSettingsHideNonSupportedPaymentMethods(value);
|
||||||
|
_hideNonSupportedPaymentMethods = value;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setSortMarketListsByNumberOfOffersTrades(bool value) async {
|
||||||
|
await _secureStorageService.writeSettingsSortMarketListsByNumberOfOffersTrades(value);
|
||||||
|
_sortMarketListsByNumberOfOffersTrades = value;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setUseDarkMode(bool value) async {
|
||||||
|
await _secureStorageService.writeSettingsUseDarkMode(value);
|
||||||
|
_useDarkMode = value;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setAutoWithdrawToNewStealthAddress(bool value) async {
|
||||||
|
await _secureStorageService.writeSettingsAutoWithdrawToNewStealthAddress(value);
|
||||||
|
_autoWithdrawToNewStealthAddress = value;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
39
lib/providers/haveno_providers/tor_log_provider.dart
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
/* import 'package:flutter/material.dart';
|
||||||
|
import 'package:haveno_app/models/schema.dart';
|
||||||
|
import 'package:haveno_app/services/tor/tor_log_service.dart';
|
||||||
|
|
||||||
|
class TorLogProvider with ChangeNotifier, StreamListenerProviderMixin {
|
||||||
|
final TorLogService _torLogService;
|
||||||
|
|
||||||
|
List<String> get stdOutLogs => _torLogService.stdOutLogs;
|
||||||
|
List<String> get stdErrLogs => _torLogService.stdErrLogs;
|
||||||
|
|
||||||
|
TorLogProvider(this._torLogService) {
|
||||||
|
// Listen for stdout and stderr log streams
|
||||||
|
listenToStream(_torLogService.stdOutStream, notifyListeners);
|
||||||
|
listenToStream(_torLogService.stdErrStream, notifyListeners);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
38
lib/providers/haveno_providers/tor_status_provder.dart
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
/* import 'package:flutter/material.dart';
|
||||||
|
import 'package:haveno_app/models/schema.dart';
|
||||||
|
import 'package:haveno_app/services/tor/tor_status_service.dart';
|
||||||
|
|
||||||
|
class TorStatusProvider with ChangeNotifier, StreamListenerProviderMixin {
|
||||||
|
final TorStatusService _torStatusService;
|
||||||
|
|
||||||
|
TorStatusProvider(this._torStatusService) {
|
||||||
|
listenToStream(_torStatusService.torStatusStream, notifyListeners);
|
||||||
|
}
|
||||||
|
|
||||||
|
int? get port => _torStatusService.torPort;
|
||||||
|
String get torStatus => _torStatusService.torStatus;
|
||||||
|
String get torDetails => _torStatusService.torDetails;
|
||||||
|
}
|
||||||
|
*/
|
84
lib/services/connection_checker_service.dart
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import 'package:haveno/haveno_client.dart';
|
||||||
|
import 'package:haveno_app/services/secure_storage_service.dart';
|
||||||
|
import 'package:haveno_app/services/tor_interface.dart';
|
||||||
|
import 'package:internet_connection_checker_plus/internet_connection_checker_plus.dart';
|
||||||
|
|
||||||
|
class ConnectionCheckerService {
|
||||||
|
// Private named constructor
|
||||||
|
ConnectionCheckerService._internal();
|
||||||
|
|
||||||
|
// The single instance of the class
|
||||||
|
static final ConnectionCheckerService _instance = ConnectionCheckerService._internal();
|
||||||
|
|
||||||
|
// Factory constructor to return the same instance
|
||||||
|
factory ConnectionCheckerService() {
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
final SecureStorageService secureStorageService = SecureStorageService();
|
||||||
|
final HavenoChannel havenoService = HavenoChannel();
|
||||||
|
|
||||||
|
// Cache for isTorConnected status
|
||||||
|
bool? _torConnectedCache;
|
||||||
|
DateTime? _torCacheTimestamp;
|
||||||
|
final Duration _torCacheDuration = const Duration(minutes: 3);
|
||||||
|
|
||||||
|
// Method to check if the cache is still valid
|
||||||
|
bool _isTorCacheValid() {
|
||||||
|
if (_torCacheTimestamp == null) return false;
|
||||||
|
return DateTime.now().difference(_torCacheTimestamp!) < _torCacheDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cached version of isTorConnected
|
||||||
|
Future<bool> isTorConnected() async {
|
||||||
|
// If cache is valid, return the cached value
|
||||||
|
if (_isTorCacheValid() && _torConnectedCache == true) {
|
||||||
|
print("Returning cached Tor connection status: $_torConnectedCache");
|
||||||
|
return _torConnectedCache!;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, perform the check
|
||||||
|
bool isConnected = await TorService.isTorConnected();
|
||||||
|
|
||||||
|
// If Tor is connected, cache the result and timestamp
|
||||||
|
if (isConnected) {
|
||||||
|
_torConnectedCache = true;
|
||||||
|
_torCacheTimestamp = DateTime.now();
|
||||||
|
print("Caching successful Tor connection status.");
|
||||||
|
} else {
|
||||||
|
// If not connected, invalidate the cache
|
||||||
|
_torConnectedCache = false;
|
||||||
|
_torCacheTimestamp = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isConnected;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> isHavenoDaemonConnected() async {
|
||||||
|
print("Haveno Daemon Connection Status: ${havenoService.isConnected}");
|
||||||
|
return havenoService.isConnected;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> isInternetConnected() async => await InternetConnection().hasInternetAccess;
|
||||||
|
}
|
86
lib/services/desktop_manager_service.dart
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:haveno/haveno_client.dart';
|
||||||
|
import 'package:haveno_app/models/haveno/p2p/haveno_seednode.dart';
|
||||||
|
import 'package:haveno_app/services/platform_system_service/schema.dart';
|
||||||
|
import 'package:haveno_app/services/secure_storage_service.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
|
class DesktopManagerService {
|
||||||
|
final SecureStorageService secureStorageService = SecureStorageService();
|
||||||
|
final HavenoChannel havenoService = HavenoChannel();
|
||||||
|
Uri? _desktopDaemonNodeUri;
|
||||||
|
late PlatformService platformService;
|
||||||
|
late String daemonPassword;
|
||||||
|
|
||||||
|
DesktopManagerService();
|
||||||
|
|
||||||
|
Future<Uri?> getDesktopDaemonNodeUri() async {
|
||||||
|
String? daemonPassword =
|
||||||
|
await secureStorageService.readHavenoDaemonPassword();
|
||||||
|
try {
|
||||||
|
Directory applicationSupportDirectory =
|
||||||
|
await getApplicationSupportDirectory();
|
||||||
|
String torPath = path.join(applicationSupportDirectory.path, 'Tor',
|
||||||
|
'daemon_service', 'hostname');
|
||||||
|
File hiddenServiceHostnameFile = File(torPath);
|
||||||
|
if (await hiddenServiceHostnameFile.exists()) {
|
||||||
|
List<String> hostnameFileLines =
|
||||||
|
await hiddenServiceHostnameFile.readAsLines();
|
||||||
|
String? hostname = hostnameFileLines.first;
|
||||||
|
_desktopDaemonNodeUri = Uri.parse(hostname);
|
||||||
|
|
||||||
|
if (daemonPassword != null && daemonPassword.isNotEmpty) {
|
||||||
|
_desktopDaemonNodeUri =
|
||||||
|
_desktopDaemonNodeUri!.replace(queryParameters: {
|
||||||
|
..._desktopDaemonNodeUri!.queryParameters,
|
||||||
|
'password': daemonPassword,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return _desktopDaemonNodeUri;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("Error getting daemon node address: $e");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool?> isSeednodeConfigured() async {
|
||||||
|
List<HavenoSeedNode> storedSeedNodes = await secureStorageService.readHavenoSeedNodes();
|
||||||
|
if (storedSeedNodes.isNotEmpty) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setInitialSeednode(HavenoSeedNode seedNode) async {
|
||||||
|
await secureStorageService.writeHavenoSeedNodes([seedNode]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
56
lib/services/http_service.dart
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:socks5_proxy/socks_client.dart';
|
||||||
|
|
||||||
|
class HttpService {
|
||||||
|
final HttpClient _client;
|
||||||
|
|
||||||
|
HttpService({String proxyHost = '127.0.0.1', int proxyPort = 8118})
|
||||||
|
: _client = HttpClient() {
|
||||||
|
SocksTCPClient.assignToHttpClient(_client, [
|
||||||
|
ProxySettings(InternetAddress(proxyHost), proxyPort),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<HttpClientResponse> request(String method, String url,
|
||||||
|
{Map<String, String>? headers, dynamic body}) async {
|
||||||
|
final request = await _client.openUrl(method, Uri.parse(url));
|
||||||
|
|
||||||
|
headers?.forEach((key, value) {
|
||||||
|
request.headers.set(key, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (body != null) {
|
||||||
|
request.add(utf8.encode(json.encode(body)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return await request.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void close() {
|
||||||
|
_client.close();
|
||||||
|
}
|
||||||
|
}
|
249
lib/services/local_notification_service.dart
Normal file
|
@ -0,0 +1,249 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
|
import 'package:haveno/profobuf_models.dart';
|
||||||
|
import 'package:haveno_app/main.dart';
|
||||||
|
import 'package:haveno_app/views/screens/dispute_chat_screen.dart';
|
||||||
|
import 'package:haveno_app/views/screens/home_screen.dart';
|
||||||
|
import 'package:haveno_app/views/screens/trade_chat_screen.dart';
|
||||||
|
import 'package:timezone/timezone.dart' as tz;
|
||||||
|
import 'package:timezone/data/latest_all.dart' as tz;
|
||||||
|
|
||||||
|
class LocalNotificationsService {
|
||||||
|
final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin =
|
||||||
|
FlutterLocalNotificationsPlugin();
|
||||||
|
final int foregroundServiceNotificationId = 888;
|
||||||
|
bool _initialized = false;
|
||||||
|
|
||||||
|
static final LocalNotificationsService _instance = LocalNotificationsService._internal();
|
||||||
|
|
||||||
|
factory LocalNotificationsService() => _instance;
|
||||||
|
|
||||||
|
LocalNotificationsService._internal();
|
||||||
|
|
||||||
|
Future<void> init() async {
|
||||||
|
if (_initialized) return; // Prevent re-initialization
|
||||||
|
_initialized = true;
|
||||||
|
|
||||||
|
print("Initializing Timezones...");
|
||||||
|
tz.initializeTimeZones();
|
||||||
|
print("Timezones Initialized.");
|
||||||
|
|
||||||
|
const LinuxInitializationSettings initializationSettingsLinux =
|
||||||
|
LinuxInitializationSettings(defaultActionName: 'Haveno');
|
||||||
|
|
||||||
|
const AndroidInitializationSettings initializationSettingsAndroid =
|
||||||
|
AndroidInitializationSettings('@mipmap/ic_launcher');
|
||||||
|
|
||||||
|
const DarwinInitializationSettings initializationSettingsDarwin =
|
||||||
|
DarwinInitializationSettings(
|
||||||
|
requestAlertPermission: true,
|
||||||
|
requestBadgePermission: true,
|
||||||
|
requestSoundPermission: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
const DarwinInitializationSettings macOsInitializationSettingsDarwin =
|
||||||
|
DarwinInitializationSettings(
|
||||||
|
requestAlertPermission: false,
|
||||||
|
requestBadgePermission: false,
|
||||||
|
requestSoundPermission: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
const InitializationSettings initializationSettings = InitializationSettings(
|
||||||
|
android: initializationSettingsAndroid,
|
||||||
|
iOS: initializationSettingsDarwin,
|
||||||
|
macOS: macOsInitializationSettingsDarwin,
|
||||||
|
linux: initializationSettingsLinux
|
||||||
|
);
|
||||||
|
|
||||||
|
print("Initializing FlutterLocalNotificationsPlugin...");
|
||||||
|
await _flutterLocalNotificationsPlugin.initialize(
|
||||||
|
initializationSettings,
|
||||||
|
onDidReceiveNotificationResponse: _onNotificationResponse,
|
||||||
|
);
|
||||||
|
print("FlutterLocalNotificationsPlugin Initialized.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> showForegroundServiceNotification({
|
||||||
|
required String title,
|
||||||
|
required String body,
|
||||||
|
String? payload,
|
||||||
|
}) async {
|
||||||
|
const AndroidNotificationDetails androidPlatformChannelSpecifics =
|
||||||
|
AndroidNotificationDetails(
|
||||||
|
'haveno',
|
||||||
|
'Haveno Plus Service',
|
||||||
|
channelDescription: 'Haveno service running in the background',
|
||||||
|
importance: Importance.max,
|
||||||
|
priority: Priority.high,
|
||||||
|
ticker: 'ticker',
|
||||||
|
ongoing: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
const DarwinNotificationDetails iOSPlatformChannelSpecifics = DarwinNotificationDetails();
|
||||||
|
|
||||||
|
const DarwinNotificationDetails macOSPlatformChannelSpecifics = DarwinNotificationDetails();
|
||||||
|
|
||||||
|
const NotificationDetails platformChannelSpecifics = NotificationDetails(
|
||||||
|
android: androidPlatformChannelSpecifics,
|
||||||
|
iOS: iOSPlatformChannelSpecifics,
|
||||||
|
macOS: macOSPlatformChannelSpecifics
|
||||||
|
);
|
||||||
|
|
||||||
|
await _flutterLocalNotificationsPlugin.show(
|
||||||
|
foregroundServiceNotificationId,
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
platformChannelSpecifics,
|
||||||
|
payload: payload,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateForegroundServiceNotification({
|
||||||
|
required String title,
|
||||||
|
required String body,
|
||||||
|
String? payload,
|
||||||
|
}) async {
|
||||||
|
await showForegroundServiceNotification(title: title, body: body, payload: payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> showNotification({
|
||||||
|
required int id,
|
||||||
|
required String title,
|
||||||
|
required String body,
|
||||||
|
String? payload,
|
||||||
|
}) async {
|
||||||
|
const AndroidNotificationDetails androidPlatformChannelSpecifics =
|
||||||
|
AndroidNotificationDetails(
|
||||||
|
'haveno_notifications',
|
||||||
|
'Haveno Plus Notifications',
|
||||||
|
channelDescription: 'Notifications for Haveno Plus events',
|
||||||
|
importance: Importance.max,
|
||||||
|
priority: Priority.high,
|
||||||
|
ticker: 'ticker',
|
||||||
|
);
|
||||||
|
|
||||||
|
const DarwinNotificationDetails iOSPlatformChannelSpecifics = DarwinNotificationDetails();
|
||||||
|
|
||||||
|
const DarwinNotificationDetails macOSPlatformChannelSpecifics = DarwinNotificationDetails(
|
||||||
|
presentAlert: true,
|
||||||
|
presentSound: true
|
||||||
|
);
|
||||||
|
|
||||||
|
const NotificationDetails platformChannelSpecifics = NotificationDetails(
|
||||||
|
android: androidPlatformChannelSpecifics,
|
||||||
|
iOS: iOSPlatformChannelSpecifics,
|
||||||
|
macOS: macOSPlatformChannelSpecifics
|
||||||
|
);
|
||||||
|
|
||||||
|
await _flutterLocalNotificationsPlugin.show(
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
platformChannelSpecifics,
|
||||||
|
payload: payload,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> scheduleNotification({
|
||||||
|
required int id,
|
||||||
|
required String title,
|
||||||
|
required String body,
|
||||||
|
required DateTime scheduledDate,
|
||||||
|
String? payload,
|
||||||
|
}) async {
|
||||||
|
final tz.TZDateTime tzScheduledDate = tz.TZDateTime.from(scheduledDate, tz.local);
|
||||||
|
|
||||||
|
const AndroidNotificationDetails androidPlatformChannelSpecifics =
|
||||||
|
AndroidNotificationDetails(
|
||||||
|
'haveno_notifications',
|
||||||
|
'Haveno Plus Notifications',
|
||||||
|
channelDescription: 'Notifications for Haveno Plus events',
|
||||||
|
importance: Importance.max,
|
||||||
|
priority: Priority.high,
|
||||||
|
ticker: 'ticker',
|
||||||
|
);
|
||||||
|
|
||||||
|
const DarwinNotificationDetails iOSPlatformChannelSpecifics =
|
||||||
|
DarwinNotificationDetails();
|
||||||
|
|
||||||
|
const NotificationDetails platformChannelSpecifics = NotificationDetails(
|
||||||
|
android: androidPlatformChannelSpecifics,
|
||||||
|
iOS: iOSPlatformChannelSpecifics,
|
||||||
|
);
|
||||||
|
|
||||||
|
await _flutterLocalNotificationsPlugin.zonedSchedule(
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
tzScheduledDate,
|
||||||
|
platformChannelSpecifics,
|
||||||
|
payload: payload,
|
||||||
|
androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
|
||||||
|
uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> cancelNotification(int id) async {
|
||||||
|
await _flutterLocalNotificationsPlugin.cancel(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> cancelAllNotifications() async {
|
||||||
|
await _flutterLocalNotificationsPlugin.cancelAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onNotificationResponse(NotificationResponse notificationResponse) async {
|
||||||
|
print('Notification tapped with payload: ${notificationResponse.payload}');
|
||||||
|
var object = jsonDecode(notificationResponse.payload!);
|
||||||
|
|
||||||
|
switch (object['action']) {
|
||||||
|
case 'route_to_chat_screen':
|
||||||
|
//print(object['chateProtobufAsJson']);
|
||||||
|
var chatMessage = ChatMessage()..mergeFromProto3Json(jsonDecode(object['chatMessageProtobufAsJson']));
|
||||||
|
if (chatMessage.tradeId.isNotEmpty) {
|
||||||
|
|
||||||
|
if (chatMessage.type != SupportType.TRADE) {
|
||||||
|
navigatorKey.currentState?.push(
|
||||||
|
MaterialPageRoute(builder: (context) => DisputeChatScreen(tradeId: chatMessage.tradeId))
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
navigatorKey.currentState?.push(
|
||||||
|
MaterialPageRoute(builder: (context) => TradeChatScreen(tradeId: chatMessage.tradeId))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'route_to_active_trades_screen':
|
||||||
|
navigatorKey.currentState?.push(
|
||||||
|
MaterialPageRoute(builder: (context) => HomeScreen(initialIndex: 3))
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
print('Unknown action: ${object['action']}');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
87
lib/services/mobile_manager_service.dart
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:haveno/grpc_models.dart';
|
||||||
|
import 'package:haveno/haveno_client.dart';
|
||||||
|
import 'package:haveno_app/models/haveno_daemon_config.dart';
|
||||||
|
import 'package:haveno_app/services/platform_system_service/schema.dart';
|
||||||
|
import 'package:haveno_app/services/secure_storage_service.dart';
|
||||||
|
import 'package:haveno_app/services/security.dart';
|
||||||
|
|
||||||
|
class MobileManagerService {
|
||||||
|
final SecureStorageService secureStorageService = SecureStorageService();
|
||||||
|
final HavenoChannel havenoChannel = HavenoChannel();
|
||||||
|
HavenoDaemonConfig? _remoteHavenoDaemonNodeConfig;
|
||||||
|
bool _hasHavenoDaemonNodeConfig = false;
|
||||||
|
late PlatformService platformService;
|
||||||
|
|
||||||
|
MobileManagerService();
|
||||||
|
|
||||||
|
Future<HavenoDaemonConfig?> getRemoteHavenoDaemonNode() async {
|
||||||
|
try {
|
||||||
|
_remoteHavenoDaemonNodeConfig = await secureStorageService.readHavenoDaemonConfig();
|
||||||
|
return _remoteHavenoDaemonNodeConfig;
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to read Haveno Daemon config: $e");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<HavenoDaemonConfig?> setHavenoDaemonNodeConfig(Uri onionUri) async {
|
||||||
|
HavenoDaemonConfig havenoDaemonConfig = HavenoDaemonConfig(fullUri: onionUri);
|
||||||
|
print(jsonEncode(havenoDaemonConfig.toJson()));
|
||||||
|
try {
|
||||||
|
havenoChannel.connect(havenoDaemonConfig.host, havenoDaemonConfig.port, havenoDaemonConfig.clientAuthPassword);
|
||||||
|
} catch (e) {
|
||||||
|
print("Couldn't connect to the URI provided ${e.toString()}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await havenoChannel.versionClient?.getVersion(GetVersionRequest());
|
||||||
|
havenoDaemonConfig.setVerified(true);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error fetching version ${e.toString()}');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await secureStorageService.writeHavenoDaemonConfig(havenoDaemonConfig);
|
||||||
|
_hasHavenoDaemonNodeConfig = true;
|
||||||
|
return havenoDaemonConfig;
|
||||||
|
} catch (e) {
|
||||||
|
print("Couldn't set the new haveno daemon config: $e");
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> logout() async {
|
||||||
|
try {
|
||||||
|
await SecurityService().resetAppData();
|
||||||
|
_hasHavenoDaemonNodeConfig = false;
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
print("Failed to logout: $e");
|
||||||
|
_hasHavenoDaemonNodeConfig = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// Haveno App extends the features of Haveno, supporting mobile devices and more.
|
||||||
|
// Copyright (C) 2024 Kewbit (https://kewbit.org)
|
||||||
|
// Source Code: https://git.haveno.com/haveno/haveno-app.git
|
||||||
|
//
|
||||||
|
// Author: Kewbit
|
||||||
|
// Website: https://kewbit.org
|
||||||
|
// Contact Email: me@kewbit.org
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import 'schema.dart';
|
||||||
|
|
||||||
|
class AndroidPlatformService implements PlatformService {
|
||||||
|
@override
|
||||||
|
Future<void> init() {
|
||||||
|
// TODO: implement init
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> setupHavenoDaemon(String? password) {
|
||||||
|
// TODO: implement setupHavenoDaemon
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> setupTorDaemon() {
|
||||||
|
// TODO: implement setupTorDaemon
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|