diff --git a/CMakeLists.txt b/CMakeLists.txt index 9663bf6..990a3a8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,11 +11,13 @@ set(VERSION "beta-2") option(FETCH_DEPS "Download dependencies if they are not found" ON) option(XMRIG "Include XMRig module" ON) -option(OPENVR "Include OpenVR support" OFF) -option(QML "Include QtQuick (QML)" OFF) -option(TOR_BIN "Path to Tor binary to embed inside WOWlet" OFF) +option(OPENVR "Include OpenVR support") +option(QML "Include QtQuick (QML)") +option(ANDROID "Android deployment") +option(ANDROID_DEBUG "View the Android app on desktop") +option(TOR_BIN "Path to Tor binary to embed inside WOWlet") option(STATIC "Link libraries statically, requires static Qt") -option(USE_DEVICE_TREZOR "Trezor support compilation" OFF) +option(USE_DEVICE_TREZOR "Trezor support compilation") option(DONATE_BEG "Prompt donation window every once in a while" ON) list(INSERT CMAKE_MODULE_PATH 0 "${CMAKE_SOURCE_DIR}/cmake") include(CheckCCompilerFlag) @@ -28,11 +30,11 @@ include(CheckSymbolExists) set(WOWNERO_HEAD "f611d5c9e32bc62f1735f6571b0bdb95cc020531") set(BUILD_GUI_DEPS ON) -set(ARCH "x86-64") -set(BUILD_64 ON) +set(ARCH "x86-64" CACHE STRING "Target architecture") +set(BUILD_64 ON CACHE BOOL "Build 64-bit binaries") set(INSTALL_VENDORED_LIBUNBOUND ${STATIC}) set(USE_SINGLE_BUILDDIR ON) -if(OPENVR) +if(OPENVR OR ANDROID_DEBUG) set(QML ON) endif() @@ -41,10 +43,7 @@ set(_CMAKE_BUILD_TYPE "") string(TOUPPER "${CMAKE_BUILD_TYPE}" _CMAKE_BUILD_TYPE) if("${_CMAKE_BUILD_TYPE}" STREQUAL "DEBUG") set(DEBUG ON) - set(CMAKE_VERBOSE_MAKEFILE ON) - message(STATUS "OPENVR: ${OPENVR}") - message(STATUS "QML: ${QML}") endif() check_include_file(sys/prctl.h HAVE_SYS_PRCTL_H) @@ -173,7 +172,7 @@ find_package(Boost 1.58 REQUIRED COMPONENTS program_options locale) -if(UNIX AND NOT APPLE) +if(UNIX AND NOT APPLE AND NOT ANDROID) if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug") # https://github.com/monero-project/monero-gui/issues/3142#issuecomment-705940446 set(CMAKE_SKIP_RPATH ON) @@ -195,6 +194,17 @@ if("$ENV{DRONE}" STREQUAL "true") message(STATUS "We are inside a static compile with Drone CI") endif() +if(UNIX) + if(NOT CMAKE_PREFIX_PATH AND DEFINED ENV{CMAKE_PREFIX_PATH}) + message(STATUS "Using CMAKE_PREFIX_PATH environment variable: '$ENV{CMAKE_PREFIX_PATH}'") + set(CMAKE_PREFIX_PATH $ENV{CMAKE_PREFIX_PATH}) + endif() + if(APPLE AND NOT CMAKE_PREFIX_PATH) + execute_process(COMMAND brew --prefix qt5 OUTPUT_VARIABLE QT5_DIR OUTPUT_STRIP_TRAILING_WHITESPACE) + list(APPEND CMAKE_PREFIX_PATH ${QT5_DIR}) + endif() +endif() + # To build WOWlet with embedded (and static) Tor, pass CMake -DTOR_BIN=/path/to/tor if(TOR_BIN) if(APPLE) @@ -249,7 +259,7 @@ if(MINGW) set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wa,-mbig-obj") set(EXTRA_LIBRARIES mswsock;ws2_32;iphlpapi;crypt32;bcrypt) if(DEPENDS) - set(ICU_LIBRARIES iconv) + set(ICU_LIBRARIES iconv) else() set(ICU_LIBRARIES icuio icuin icuuc icudt icutu iconv) endif() @@ -264,7 +274,7 @@ elseif(DRAGONFLY) set(EXTRA_LIBRARIES execinfo ${COMPAT}) elseif(CMAKE_SYSTEM_NAME MATCHES "(SunOS|Solaris)") set(EXTRA_LIBRARIES socket nsl resolv) -elseif(NOT MSVC AND NOT DEPENDS) +elseif(NOT MSVC AND NOT DEPENDS AND NOT ANDROID) find_library(RT rt) set(EXTRA_LIBRARIES ${RT}) endif() @@ -384,10 +394,6 @@ if(OPENVR) add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/contrib/openvr") endif() -if(APPLE) - add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/contrib/KDMacTouchBar") -endif() - if(WITH_SCANNER) add_library(quirc STATIC contrib/quirc/lib/decode.c diff --git a/Dockerfile.android b/Dockerfile.android new file mode 100644 index 0000000..17bd918 --- /dev/null +++ b/Dockerfile.android @@ -0,0 +1,245 @@ +FROM debian:stretch + +ARG THREADS=1 +ARG ANDROID_NDK_REVISION=21d +ARG ANDROID_NDK_HASH=bcf4023eb8cb6976a4c7cff0a8a8f145f162bf4d +ARG ANDROID_SDK_REVISION=4333796 +ARG ANDROID_SDK_HASH=92ffee5a1d98d856634e8b71132e8a95d96c83a63fde1099be3d86df3106def9 +ARG QT_VERSION=5.15.2 + +WORKDIR /opt/android +ENV WORKDIR=/opt/android + +ENV ANDROID_NATIVE_API_LEVEL=28 +ENV ANDROID_API=android-${ANDROID_NATIVE_API_LEVEL} +ENV ANDROID_CLANG=aarch64-linux-android${ANDROID_NATIVE_API_LEVEL}-clang +ENV ANDROID_CLANGPP=aarch64-linux-android${ANDROID_NATIVE_API_LEVEL}-clang++ +ENV ANDROID_NDK_ROOT=${WORKDIR}/android-ndk-r${ANDROID_NDK_REVISION} +ENV ANDROID_SDK_ROOT=${WORKDIR}/tools +ENV JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 +ENV PATH=${JAVA_HOME}/bin:${PATH} +ENV PREFIX=${WORKDIR}/prefix +ENV TOOLCHAIN_DIR=${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64 + +RUN apt-get update \ + && apt-get install -y ant automake build-essential ca-certificates-java file gettext git libc6 libncurses5 \ + libssl-dev libstdc++6 libtinfo5 libtool libz1 openjdk-8-jdk-headless openjdk-8-jre-headless pkg-config python3 \ + unzip wget + +RUN wget -q https://dl.google.com/android/repository/sdk-tools-linux-${ANDROID_SDK_REVISION}.zip \ + && unzip -q sdk-tools-linux-${ANDROID_SDK_REVISION}.zip \ + && rm -f sdk-tools-linux-${ANDROID_SDK_REVISION}.zip + +RUN wget -q https://dl.google.com/android/repository/android-ndk-r${ANDROID_NDK_REVISION}-linux-x86_64.zip \ + && unzip -q android-ndk-r${ANDROID_NDK_REVISION}-linux-x86_64.zip \ + && rm -f android-ndk-r${ANDROID_NDK_REVISION}-linux-x86_64.zip + +RUN cd ${ANDROID_SDK_ROOT} && echo y | ./bin/sdkmanager "platform-tools" "platforms;${ANDROID_API}" "tools" > /dev/null +RUN cp -r ${WORKDIR}/platforms ${WORKDIR}/platform-tools ${ANDROID_SDK_ROOT} + +ENV HOST_PATH=${PATH} +ENV PATH=${TOOLCHAIN_DIR}/aarch64-linux-android/bin:${TOOLCHAIN_DIR}/bin:${PATH} + +ARG ZLIB_VERSION=1.2.11 +ARG ZLIB_HASH=c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1 +RUN wget -q https://zlib.net/zlib-${ZLIB_VERSION}.tar.gz \ + && tar -xzf zlib-${ZLIB_VERSION}.tar.gz \ + && rm zlib-${ZLIB_VERSION}.tar.gz \ + && cd zlib-${ZLIB_VERSION} \ + && CC=${ANDROID_CLANG} CXX=${ANDROID_CLANGPP} ./configure --prefix=${PREFIX} --static \ + && make -j${THREADS} \ + && make -j${THREADS} install \ + && rm -rf $(pwd) + +RUN git clone git://code.qt.io/qt/qt5.git -b ${QT_VERSION} --depth 1 \ + && cd qt5 \ + && perl init-repository --module-subset=default,-qtwebengine \ + && PATH=${HOST_PATH} ./configure -v -developer-build -release \ + -xplatform android-clang \ + -android-ndk-platform ${ANDROID_API} \ + -android-ndk ${ANDROID_NDK_ROOT} \ + -android-sdk ${ANDROID_SDK_ROOT} \ + -android-ndk-host linux-x86_64 \ + -no-dbus \ + -opengl es2 \ + -no-use-gold-linker \ + -no-sql-mysql \ + -opensource -confirm-license \ + -android-arch arm64-v8a \ + -prefix ${PREFIX} \ + -nomake tools -nomake tests -nomake examples \ + -skip qtwebengine \ + -skip qtserialport \ + -skip qtconnectivity \ + -skip qttranslations \ + -skip qtpurchasing \ + -skip qtgamepad -skip qtscript -skip qtdoc \ + -no-warnings-are-errors \ + && sed -i '213,215d' qtbase/src/3rdparty/pcre2/src/sljit/sljitConfigInternal.h \ + && PATH=${HOST_PATH} make -j${THREADS} \ + && PATH=${HOST_PATH} make -j${THREADS} install \ + && cd qttools/src/linguist/lrelease \ + && ../../../../qtbase/bin/qmake \ + && PATH=${HOST_PATH} make -j${THREADS} install \ + && cd ../../../.. \ + && rm -rf $(pwd) + +ARG ICONV_VERSION=1.16 +ARG ICONV_HASH=e6a1b1b589654277ee790cce3734f07876ac4ccfaecbee8afa0b649cf529cc04 +RUN wget -q http://ftp.gnu.org/pub/gnu/libiconv/libiconv-${ICONV_VERSION}.tar.gz \ + && echo "${ICONV_HASH} libiconv-${ICONV_VERSION}.tar.gz" | sha256sum -c \ + && tar -xzf libiconv-${ICONV_VERSION}.tar.gz \ + && rm -f libiconv-${ICONV_VERSION}.tar.gz \ + && cd libiconv-${ICONV_VERSION} \ + && CC=${ANDROID_CLANG} CXX=${ANDROID_CLANGPP} ./configure --build=x86_64-linux-gnu --host=aarch64 --prefix=${PREFIX} --disable-rpath \ + && make -j${THREADS} \ + && make -j${THREADS} install + +ARG BOOST_VERSION=1_74_0 +ARG BOOST_VERSION_DOT=1.74.0 +ARG BOOST_HASH=83bfc1507731a0906e387fc28b7ef5417d591429e51e788417fe9ff025e116b1 +RUN wget -q https://dl.bintray.com/boostorg/release/${BOOST_VERSION_DOT}/source/boost_${BOOST_VERSION}.tar.bz2 \ + && echo "${BOOST_HASH} boost_${BOOST_VERSION}.tar.bz2" | sha256sum -c \ + && tar -xf boost_${BOOST_VERSION}.tar.bz2 \ + && rm -f boost_${BOOST_VERSION}.tar.bz2 \ + && cd boost_${BOOST_VERSION} \ + && PATH=${HOST_PATH} ./bootstrap.sh --prefix=${PREFIX} \ + && PATH=${TOOLCHAIN_DIR}/bin:${HOST_PATH} ./b2 --build-type=minimal link=static runtime-link=static \ + --with-chrono --with-date_time --with-filesystem --with-program_options --with-regex --with-serialization \ + --with-system --with-thread --with-locale --build-dir=android --stagedir=android toolset=clang threading=multi \ + threadapi=pthread target-os=android -sICONV_PATH=${PREFIX} \ + cflags='--target=aarch64-linux-android' \ + cxxflags='--target=aarch64-linux-android' \ + linkflags='--target=aarch64-linux-android --sysroot=${ANDROID_NDK_ROOT}/platforms/${ANDROID_API}/arch-arm64 ${ANDROID_NDK_ROOT}/sources/cxx-stl/llvm-libc++/libs/arm64-v8a/libc++_shared.so -nostdlib++' \ + install -j${THREADS} \ + && rm -rf $(pwd) + +ARG OPENSSL_VERSION=1.1.1g +ARG OPENSSL_HASH=ddb04774f1e32f0c49751e21b67216ac87852ceb056b75209af2443400636d46 +RUN wget -q https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz \ + && tar -xzf openssl-${OPENSSL_VERSION}.tar.gz \ + && rm openssl-${OPENSSL_VERSION}.tar.gz \ + && cd openssl-${OPENSSL_VERSION} \ + && ANDROID_NDK_HOME=${ANDROID_NDK_ROOT} ./Configure CC=${ANDROID_CLANG} CXX=${ANDROID_CLANGPP} \ + android-arm64 no-asm no-shared --static \ + --with-zlib-include=${PREFIX}/include --with-zlib-lib=${PREFIX}/lib \ + --prefix=${PREFIX} --openssldir=${PREFIX} \ + && sed -i 's/CNF_EX_LIBS=-ldl -pthread//g;s/BIN_CFLAGS=-pie $(CNF_CFLAGS) $(CFLAGS)//g' Makefile \ + && ANDROID_NDK_HOME=${ANDROID_NDK_ROOT} make -j${THREADS} \ + && make -j${THREADS} install \ + && rm -rf $(pwd) + +ARG ZMQ_VERSION=v4.3.3 +ARG ZMQ_HASH=04f5bbedee58c538934374dc45182d8fc5926fa3 +RUN git clone https://github.com/zeromq/libzmq.git -b ${ZMQ_VERSION} --depth 1 \ + && cd libzmq \ + && git checkout ${ZMQ_HASH} \ + && ./autogen.sh \ + && CC=${ANDROID_CLANG} CXX=${ANDROID_CLANGPP} ./configure --prefix=${PREFIX} --host=aarch64-linux-android \ + --enable-static --disable-shared \ + && make -j${THREADS} \ + && make -j${THREADS} install \ + && rm -rf $(pwd) + +ARG SODIUM_VERSION=1.0.18 +ARG SODIUM_HASH=4f5e89fa84ce1d178a6765b8b46f2b6f91216677 +RUN set -ex \ + && git clone https://github.com/jedisct1/libsodium.git -b ${SODIUM_VERSION} --depth 1 \ + && cd libsodium \ + && test `git rev-parse HEAD` = ${SODIUM_HASH} || exit 1 \ + && ./autogen.sh \ + && CC=${ANDROID_CLANG} CXX=${ANDROID_CLANGPP} ./configure --prefix=${PREFIX} --host=aarch64-linux-android --enable-static --disable-shared \ + && make -j${THREADS} install \ + && rm -rf $(pwd) + +RUN git clone -b libgpg-error-1.38 --depth 1 git://git.gnupg.org/libgpg-error.git \ + && cd libgpg-error \ + && git reset --hard 71d278824c5fe61865f7927a2ed1aa3115f9e439 \ + && ./autogen.sh \ + && CC=${ANDROID_CLANG} CXX=${ANDROID_CLANGPP} ./configure --host=aarch64-linux-android --prefix=${PREFIX} --disable-rpath --disable-shared --enable-static --disable-doc --disable-tests \ + && PATH=${TOOLCHAIN_DIR}/bin:${HOST_PATH} make -j${THREADS} \ + && make -j${THREADS} install \ + && rm -rf $(pwd) + +RUN git clone -b libgcrypt-1.8.5 --depth 1 git://git.gnupg.org/libgcrypt.git \ + && cd libgcrypt \ + && git reset --hard 56606331bc2a80536db9fc11ad53695126007298 \ + && ./autogen.sh \ + && CC=${ANDROID_CLANG} CXX=${ANDROID_CLANGPP} ./configure --host=aarch64-linux-android --prefix=${PREFIX} --with-gpg-error-prefix=${PREFIX} --disable-shared --enable-static --disable-doc --disable-tests \ + && PATH=${TOOLCHAIN_DIR}/bin:${HOST_PATH} make -j${THREADS} \ + && make -j${THREADS} install \ + && rm -rf $(pwd) + +RUN cd tools \ + && wget -q http://dl-ssl.google.com/android/repository/tools_r25.2.5-linux.zip \ + && unzip -q tools_r25.2.5-linux.zip \ + && rm -f tools_r25.2.5-linux.zip \ + && echo y | ${ANDROID_SDK_ROOT}/tools/android update sdk --no-ui --all --filter build-tools-28.0.3 + +RUN git clone -b v3.19.7 --depth 1 https://github.com/Kitware/CMake \ + && cd CMake \ + && git reset --hard 22612dd53a46c7f9b4c3f4b7dbe5c78f9afd9581 \ + && PATH=${HOST_PATH} ./bootstrap \ + && PATH=${HOST_PATH} make -j${THREADS} \ + && PATH=${HOST_PATH} make -j${THREADS} install \ + && rm -rf $(pwd) + +RUN git clone -b v1.6.35 --depth 1 https://github.com/glennrp/libpng.git && \ + cd libpng && \ + git reset --hard c17d164b4467f099b4484dfd4a279da0bc1dbd4a \ + && CC=${ANDROID_CLANG} CXX=${ANDROID_CLANGPP} ./configure --with-zlib-prefix="${PREFIX}" --host=aarch64-linux-android --prefix=${PREFIX} --disable-shared --enable-static \ + && PATH=${TOOLCHAIN_DIR}/bin:${HOST_PATH} make -j${THREADS} \ + && make -j${THREADS} install \ + && rm -rf $(pwd) + +# @TODO: don't hardcode ANDROID_PLATFORM +RUN git clone -b v4.0.2 --depth 1 https://github.com/fukuchi/libqrencode.git && \ + cd libqrencode && \ + git reset --hard 59ee597f913fcfda7a010a6e106fbee2595f68e4 && \ + CC=${ANDROID_CLANG} CXX=${ANDROID_CLANGPP} cmake \ + -DCMAKE_TOOLCHAIN_FILE="${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake" \ + -DANDROID_PLATFORM="28" \ + -DBUILD_SHARED_LIBS=OFF \ + -DARCH="armv8-a" \ + -DANDROID_ABI="arm64-v8a" \ + -DANDROID_TOOLCHAIN=clang \ + -DCMAKE_PREFIX_PATH="${PREFIX}" \ + -DPNG_PNG_INCLUDE_DIR="${PREFIX}/include/libpng16/" \ + -DPNG_LIBRARY="${PREFIX}/lib/libqtlibpng_arm64-v8a.a" \ + -DICONV_LIBRARY=/opt/android/prefix/lib/libiconv.a \ + -DICONV_INCLUDE_DIR=/opt/android/prefix/include/ \ + -DCMAKE_INSTALL_PREFIX="${PREFIX}" && \ + make -j$THREADS && \ + make -j$THREADS install && \ + rm -rf $(pwd) + +RUN ls -al && uname -a + +# @TODO: switch to Release +CMD set -ex \ + && cd /wowlet \ + && mkdir -p build/Android/release \ + && cd build/Android/release \ + && E=1 cmake \ + -DCMAKE_TOOLCHAIN_FILE="${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake" \ + -DCMAKE_PREFIX_PATH="${PREFIX}" \ + -DCMAKE_FIND_ROOT_PATH="${PREFIX}" \ + -DCMAKE_BUILD_TYPE=Release \ + -DARCH="armv8-a" \ + -DANDROID_NATIVE_API_LEVEL=${ANDROID_NATIVE_API_LEVEL} \ + -DANDROID_ABI="arm64-v8a" \ + -DANDROID_TOOLCHAIN=clang \ + -DBoost_USE_STATIC_RUNTIME=ON \ + -DLRELEASE_PATH="${PREFIX}/bin" \ + -DQT_ANDROID_APPLICATION_BINARY="wowlet" \ + -DWITH_SCANNER=ON \ + -DUSE_DEVICE_TREZOR=OFF \ + -DUSE_SINGLE_BUILDDIR=ON \ + -DMANUAL_SUBMODULES=1 \ + -DUSE_SINGLE_BUILDDIR=ON \ + -DQML=ON \ + -DANDROID=ON \ + ../../.. \ + && PATH=${HOST_PATH} make generate_translations_header \ + && make -j${THREADS} -C src \ + && make -j${THREADS} apk diff --git a/docs/HACKING.md b/docs/HACKING.md index 6b8fe78..2b7c08e 100644 --- a/docs/HACKING.md +++ b/docs/HACKING.md @@ -28,7 +28,7 @@ by running this command: `pandoc wowlet.1.md -s -t man -o wowlet.1 && gzip wowle apt install -y git cmake libqrencode-dev build-essential cmake libboost-all-dev \ miniupnpc libunbound-dev graphviz doxygen libunwind8-dev pkg-config libssl-dev \ libzmq3-dev libsodium-dev libhidapi-dev libnorm-dev libusb-1.0-0-dev libpgm-dev \ -libprotobuf-dev protobuf-compiler libgcrypt20-dev +libprotobuf-dev protobuf-compiler libgcrypt20-dev libpng-dev ``` ## Mac OS @@ -107,6 +107,4 @@ To skip the wizards and open a wallet directly use `--wallet-file`: ./wowlet --use-local-tor --wallet-file /home/user/Wownero/wallets/bla.keys ``` -It is recommended that you use `--stagenet` for development. Testnet is also possible, -but you'll have to provide Wownero a testnet node of your own. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6d477ee..9aa901f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -57,6 +57,15 @@ if(OPENVR) list(APPEND SOURCE_FILES ${SOURCE_FILES_QML}) endif() +if(ANDROID OR ANDROID_DEBUG) + qt5_add_resources(RESOURCES mobile/qml.qrc) + file(GLOB SOURCE_FILES_QML + "mobile/*.h" + "mobile/*.cpp" + ) + list(APPEND SOURCE_FILES ${SOURCE_FILES_QML}) +endif() + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wno-deprecated-declarations") # @TODO: removeme add_subdirectory(libwalletqt) @@ -98,11 +107,16 @@ if(APPLE) list(APPEND RESOURCES ${ICON}) endif() -add_executable(wowlet ${EXECUTABLE_FLAG} main.cpp - ${SOURCE_FILES} - ${RESOURCES} - ${ASSETS_TOR} -) +if(NOT ANDROID) + add_executable(wowlet ${EXECUTABLE_FLAG} main.cpp + ${SOURCE_FILES} + ${RESOURCES} + ${ASSETS_TOR} + ) +else() + add_library(wowlet SHARED ${SOURCE_FILES} ${RESOURCES}) + set_target_properties(wowlet PROPERTIES COMPILE_DEFINITIONS "ANDROID") +endif() # mac os bundle set_target_properties(wowlet PROPERTIES @@ -162,6 +176,14 @@ if(XMRIG) target_compile_definitions(wowlet PRIVATE HAS_XMRIG=1) endif() +if(ANDROID) + target_compile_definitions(wowlet PRIVATE HAS_ANDROID=1) +endif() + +if(ANDROID_DEBUG) + target_compile_definitions(wowlet PRIVATE HAS_ANDROID_DEBUG=1) +endif() + if(OPENVR) target_compile_definitions(wowlet PRIVATE HAS_OPENVR=1) target_compile_definitions(wowlet PUBLIC VR_API_PUBLIC) @@ -220,6 +242,13 @@ else() target_link_libraries(wowlet PUBLIC monero-seed::monero-seed) endif() +if(ANDROID) + # yolo some hardcoded paths + target_include_directories(wowlet PUBLIC + /opt/android/prefix/include/QtAndroidExtras/ + ) +endif() + # Link Wownero core libraries target_link_libraries(wowlet PUBLIC wallet_merged @@ -261,6 +290,33 @@ else() Qt5::WebSockets) endif() +if(ANDROID) + # yolo some hardcoded paths + target_link_libraries(wowlet PUBLIC + /opt/android/prefix/lib/libQt5QuickTemplates2_arm64-v8a.so + /opt/android/prefix/lib/libQt5Quick_arm64-v8a.so + /opt/android/prefix/lib/libQt5QmlModels_arm64-v8a.so + /opt/android/prefix/lib/libQt5Qml_arm64-v8a.so + /opt/android/prefix/lib/libQt5Svg_arm64-v8a.so + /opt/android/prefix/lib/libQt5Widgets_arm64-v8a.so + /opt/android/prefix/lib/libQt5Gui_arm64-v8a.so + /opt/android/prefix/lib/libQt5Xml_arm64-v8a.so + /opt/android/prefix/lib/libQt5XmlPatterns_arm64-v8a.so + /opt/android/prefix/lib/libQt5Network_arm64-v8a.so + /opt/android/prefix/lib/libQt5Core_arm64-v8a.so + /opt/android/prefix/lib/libQt5VirtualKeyboard_arm64-v8a.so + /opt/android/prefix/lib/libQt5AndroidExtras_arm64-v8a.so + /opt/android/prefix/plugins/bearer/libplugins_bearer_qandroidbearer_arm64-v8a.so + GLESv2 + log + z + jnigraphics + android + EGL + c++_shared + ) +endif() + # Link random other stuff target_link_libraries(wowlet PUBLIC ${ICU_LIBRARIES} @@ -294,7 +350,7 @@ if(OPENVR) endif() if(APPLE) - target_link_libraries(wowlet + target_link_libraries(wowlet PUBLIC KDMacTouchBar ) target_include_directories(wowlet @@ -329,3 +385,19 @@ endif() install(TARGETS wowlet DESTINATION ${CMAKE_INSTALL_PREFIX} ) + + +message(STATUS "=============================================") +message(STATUS "VERSION_MAJOR: ${VERSION_MAJOR}") +message(STATUS "VERSION_MINOR: ${VERSION_MINOR}") +message(STATUS "VERSION_REVISION: ${VERSION_REVISION}") +message(STATUS "STATIC: ${STATIC}") +message(STATUS "Include QtQuick (QML): ${QML}") +message(STATUS "VERSION: ${VERSION}") +message(STATUS "Include the XMRIG tab: ${XMRIG}") +message(STATUS "Include Valve's OpenVR library: ${OPENVR}") +message(STATUS "This build is for Android: ${ANDROID}") +message(STATUS "This build is for testing the Android app on desktop: ${ANDROID_DEBUG}") +message(STATUS "TOR_BIN: ${TOR_BIN}") +message(STATUS "DONATE_BEG: ${DONATE_BEG}") +message(STATUS "=============================================") \ No newline at end of file diff --git a/src/appcontext.cpp b/src/appcontext.cpp index 1a01197..d1e6ce5 100644 --- a/src/appcontext.cpp +++ b/src/appcontext.cpp @@ -30,77 +30,69 @@ AppContext::AppContext(QCommandLineParser *cmdargs) { this->cmdargs = cmdargs; AppContext::isQML = false; + // OS & env #if defined(Q_OS_MAC) + this->isMac = true; this->isTorSocks = qgetenv("DYLD_INSERT_LIBRARIES").indexOf("libtorsocks") >= 0; +#elif __ANDROID__ + this->isAndroid = true; #elif defined(Q_OS_LINUX) + this->isLinux = true; this->isTorSocks = qgetenv("LD_PRELOAD").indexOf("libtorsocks") >= 0; + this->isTails = TailsOS::detect(); + this->isWhonix = WhonixOS::detect(); #elif defined(Q_OS_WIN) + this->isWindows = true; this->isTorSocks = false; #endif + this->androidDebug = cmdargs->isSet("android-debug"); - this->isTails = TailsOS::detect(); - this->isWhonix = WhonixOS::detect(); - - //Paths + // Paths + this->pathGenericData = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); this->configRoot = QDir::homePath(); - if (isTails) { // #if defined(PORTABLE) - QString portablePath = []{ - QString appImagePath = qgetenv("APPIMAGE"); - if (appImagePath.isEmpty()) { - qDebug() << "Not an appimage, using currentPath()"; - return QDir::currentPath() + "/.wowlet"; - } - - QFileInfo appImageDir(appImagePath); - return appImageDir.absoluteDir().path() + "/.wowlet"; - }(); - - - if (QDir().mkpath(portablePath)) { - this->configRoot = portablePath; - } else { - qCritical() << "Unable to create portable directory: " << portablePath; - } - } - this->accountName = Utils::getUnixAccountName(); this->homeDir = QDir::homePath(); + this->configDirectory = QString("%1/.config/wowlet/").arg(this->configRoot); + this->configDirectoryVR = QString("%1%2").arg(this->configDirectory, "vr"); + + if (isTails) this->setupPathsTails(); + QString walletDir = config()->get(Config::walletDirectory).toString(); - if (walletDir.isEmpty()) { -#if defined(Q_OS_LINUX) or defined(Q_OS_MAC) - this->defaultWalletDir = QString("%1/Wownero/wallets").arg(this->configRoot); - this->defaultWalletDirRoot = QString("%1/Wownero").arg(this->configRoot); -#elif defined(Q_OS_WIN) - this->defaultWalletDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/Wownero"; - this->defaultWalletDirRoot = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); -#endif + if(walletDir.isEmpty()) { + if (isAndroid && !androidDebug) setupPathsAndroid(); + else if (isWindows) setupPathsWindows(); + else if (isLinux || isMac) setupPathsUnix(); } else { this->defaultWalletDir = walletDir; this->defaultWalletDirRoot = walletDir; } +#ifdef __ANDROID__ + // can haz disk I/O? + QVector perms = { + "android.permission.WRITE_EXTERNAL_STORAGE", + "android.permission.READ_EXTERNAL_STORAGE" + }; + Utils::androidAskPermissions(perms); +#endif + // Create wallet dirs + qDebug() << "creating " << defaultWalletDir; if (!QDir().mkpath(defaultWalletDir)) qCritical() << "Unable to create dir: " << defaultWalletDir; - this->configDirectory = QString("%1/.config/wowlet/").arg(this->configRoot); -#if defined(Q_OS_UNIX) - if(!this->configDirectory.endsWith('/')) - this->configDirectory = QString("%1/").arg(this->configDirectory); -#endif - this->configDirectoryVR = QString("%1%2").arg(this->configDirectory, "vr"); - // Create some directories createConfigDirectory(this->configDirectory); - -// if(this->cmdargs->isSet("stagenet")) -// this->networkType = NetworkType::STAGENET; -// else if(this->cmdargs->isSet("testnet")) -// this->networkType = NetworkType::TESTNET; -// else this->networkType = NetworkType::MAINNET; + qDebug() << "configRoot: " << this->configRoot; + qDebug() << "homeDir: " << this->homeDir; + qDebug() << "customWalletDir: " << walletDir; + qDebug() << "defaultWalletDir: " << this->defaultWalletDir; + qDebug() << "defaultWalletDirRoot: " << this->defaultWalletDirRoot; + qDebug() << "configDirectory: " << this->configDirectory; + // auto nodeSourceUInt = config()->get(Config::nodeSource).toUInt(); // AppContext::nodeSource = static_cast(nodeSourceUInt); this->nodes = new Nodes(this, this->networkClearnet); @@ -558,12 +550,14 @@ void AppContext::createConfigDirectory(const QString &dir) { } } +#ifdef HAS_OPENVR auto config_dir_vr = QString("%1%2").arg(dir, "vr"); if(!Utils::dirExists(config_dir_vr)) { qDebug() << QString("Creating directory: %1").arg(config_dir_vr); if (!QDir().mkpath(config_dir_vr)) throw std::runtime_error("Could not create directory " + config_dir_vr.toStdString()); } +#endif } void AppContext::createWalletWithoutSpecifyingSeed(const QString &name, const QString &password) { @@ -949,3 +943,38 @@ void AppContext::refreshModels() { this->currentWallet->coins()->refresh(this->currentWallet->currentSubaddressAccount()); // Todo: set timer for refreshes } + +void AppContext::setupPathsUnix() { + this->defaultWalletDir = QString("%1/Wownero/wallets").arg(this->configRoot); + this->defaultWalletDirRoot = QString("%1/Wownero").arg(this->configRoot); +} + +void AppContext::setupPathsWindows() { + this->defaultWalletDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/Wownero"; + this->defaultWalletDirRoot = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); +} + +void AppContext::setupPathsAndroid() { + this->defaultWalletDir = QString("%1/Wownero/wallets").arg(this->pathGenericData); + this->defaultWalletDirRoot = QString("%1/Wownero").arg(this->pathGenericData); +} + +void AppContext::setupPathsTails() { + QString portablePath = []{ + QString appImagePath = qgetenv("APPIMAGE"); + if (appImagePath.isEmpty()) { + qDebug() << "Not an appimage, using currentPath()"; + return QDir::currentPath() + "/.wowlet"; + } + + QFileInfo appImageDir(appImagePath); + return appImageDir.absoluteDir().path() + "/.wowlet"; + }(); + + if (QDir().mkpath(portablePath)) { + this->configRoot = portablePath; + } else { + qCritical() << "Unable to create portable directory: " << portablePath; + } +} + diff --git a/src/appcontext.h b/src/appcontext.h index ad334ef..9aec9b6 100644 --- a/src/appcontext.h +++ b/src/appcontext.h @@ -39,7 +39,12 @@ public: ~AppContext() override; bool isTails = false; bool isWhonix = false; + bool isAndroid = false; + bool isLinux = false; + bool isMac = false; + bool isWindows = false; bool isDebug = false; + bool androidDebug = false; // Donation config const QString donationAddress = "Wo3MWeKwtA918DU4c69hVSNgejdWFCRCuWjShRY66mJkU2Hv58eygJWDJS1MNa2Ge5M1WjUkGHuLqHkweDxwZZU42d16v94mP"; @@ -50,6 +55,7 @@ public: QString coinName = "wownero"; bool isTorSocks = false; + QString pathGenericData; QString homeDir; QString accountName; QString configRoot; @@ -215,6 +221,11 @@ private: WalletKeysFilesModel *m_walletKeysFilesModel; const int m_donationBoundary = 15; QTimer m_storeTimer; + + void setupPathsUnix(); + void setupPathsWindows(); + void setupPathsAndroid(); + void setupPathsTails(); }; #endif //WOWLET_APPCONTEXT_H diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp index d5ea6ad..5fb2721 100644 --- a/src/libwalletqt/Wallet.cpp +++ b/src/libwalletqt/Wallet.cpp @@ -1,6 +1,8 @@ // SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2014-2021, The Monero Project. +#include + #include "Wallet.h" #include "TransactionHistory.h" diff --git a/src/main.cpp b/src/main.cpp index 7654e4e..c54a072 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,6 +14,12 @@ #include "vr/main.h" #endif +#ifdef HAS_ANDROID_DEBUG +#include "mobile/main.h" +#elif HAS_ANDROID +#include "mobile/main.h" +#endif + #if defined(Q_OS_WIN) #include #endif @@ -44,6 +50,10 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) { argv_ << QString::fromStdString(argv[i]); } + QCoreApplication::setApplicationName("wowlet"); + QCoreApplication::setOrganizationDomain("wownero.org"); + QCoreApplication::setOrganizationName("wownero.org"); + QCommandLineParser parser; parser.setApplicationDescription("wowlet"); parser.addHelpOption(); @@ -91,9 +101,12 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) { QCommandLineOption openVROption(QStringList() << "openvr", "Start Wowlet OpenVR"); parser.addOption(openVROption); - QCommandLineOption openVRDebugOption(QStringList() << "openvr-debug", "Start the Wowlet VR interface without initializing OpenVR - for debugging purposes."); + QCommandLineOption openVRDebugOption(QStringList() << "openvr-debug", "Start the Wowlet VR interface without initializing OpenVR - for debugging purposes. Requires -DOPENVR=ON CMake definition."); parser.addOption(openVRDebugOption); + QCommandLineOption androidDebugOption(QStringList() << "android-debug", "Start the Android interface without actually running on Android - for debugging purposes. Requires -DANDROID_DEBUG=ON CMake definition."); + parser.addOption(androidDebugOption); + auto parsed = parser.parse(argv_); if(!parsed) { qCritical() << parser.errorText(); @@ -111,13 +124,29 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) { bool backgroundAddressEnabled = parser.isSet(backgroundOption); bool openVREnabled = parser.isSet(openVROption); bool cliMode = exportContacts || exportTxHistory || backgroundAddressEnabled; - + bool androidDebug = parser.isSet(androidDebugOption); + bool android = false; +#ifdef __ANDROID__ + android = true; +#endif qRegisterMetaType>(); #ifdef HAS_QML qputenv("QML_DISABLE_DISK_CACHE", "1"); #endif + if(android || androidDebug) { +#ifndef HAS_QML + qCritical() << "Wowlet compiled without QML support. Try -DQML=ON"; + return 1; +#endif + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QGuiApplication mobile_app(argc, argv); + auto *ctx = new AppContext(&parser); + auto *mobile = new mobile::Mobile(ctx, &parser, &mobile_app); + return mobile_app.exec(); + } + if(openVREnabled) { #ifdef HAS_OPENVR QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); @@ -138,9 +167,6 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) { if(cliMode) { auto *ctx = new AppContext(&parser); QCoreApplication cli_app(argc, argv); - QCoreApplication::setApplicationName("wowlet"); - QCoreApplication::setOrganizationDomain("wownero.org"); - QCoreApplication::setOrganizationName("wownero.org"); ctx->applicationPath = QString(argv[0]); ctx->isDebug = debugMode; @@ -191,10 +217,6 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) { QApplication app(argc, argv); - QApplication::setApplicationName("wowlet"); - QApplication::setOrganizationDomain("wownero.org"); - QApplication::setOrganizationName("wownero.org"); - parser.process(app); // Parse again for --help and --version if(!quiet) { @@ -204,8 +226,10 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) { if (stagenet) info["Mode"] = "Stagenet"; else if (testnet) info["Mode"] = "Testnet"; else info["Mode"] = "Mainnet"; +#ifndef QT_NO_SSL info["SSL"] = QSslSocket::sslLibraryVersionString(); info["SSL build"] = QSslSocket::sslLibraryBuildVersionString(); +#endif for (const auto &k: info.keys()) qWarning().nospace().noquote() << QString("%1: %2").arg(k).arg(info[k]); } diff --git a/src/mobile/README.md b/src/mobile/README.md new file mode 100644 index 0000000..250e21c --- /dev/null +++ b/src/mobile/README.md @@ -0,0 +1,45 @@ +# Wowlet Mobile (Android) + +This directory contains an unfinished QML application that will show: + +![https://i.imgur.com/yhCsSgj.jpg](https://i.imgur.com/yhCsSgj.jpg) + +## Building + +Credits go to Monero GUI team for providing the initial work on Qt5+Android. + +Build a Docker image: + +```bash +docker build --tag wowlet:android --build-arg THREADS=14 --file Dockerfile.android . +``` + +Building Wowlet for arm64-v8a: + +```Bash +docker run --rm -it -v $PWD:/wowlet -w /wowlet -e THREADS=6 wowlet:android +``` + +Installing the resulting `.apk` on your device: + +```bash +adb install build/Android/release/android-build//build/outputs/apk/debug/android-build-debug.apk +``` + +Viewing debug logs: + +```bash +adb logcat | grep --line-buffered "D wowlet" +``` + +# Development + +To show this on desktop, you will need the following CMake definitions: + +`-DANDROID_DEBUG=ON -DWITH_SCANNER=ON` + +Start wowlet with the `--android-debug` flag: + +```bash +./wowlet --android-debug +``` diff --git a/src/mobile/main.cpp b/src/mobile/main.cpp new file mode 100644 index 0000000..5c2a761 --- /dev/null +++ b/src/mobile/main.cpp @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2020-2021, The Monero Project. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libwalletqt/TransactionInfo.h" +#include "libwalletqt/TransactionHistory.h" +#include "model/TransactionHistoryModel.h" +#include "model/TransactionHistoryProxyModel.h" +#include "libwalletqt/WalletManager.h" + +#include "utils/keysfiles.h" +#include "mobile/main.h" + +namespace mobile { + + Mobile::Mobile(AppContext *ctx, QCommandLineParser *parser, QObject *parent) : + QObject(parent), ctx(ctx), m_parser(parser) { + AppContext::isQML = true; + m_pClipboard = QGuiApplication::clipboard(); + desktopMode = true; + + // turn on auto tx commits + ctx->autoCommitTx = true; + + // QR code scanning from screenshots + m_qrScreenshotPreviewPath = ctx->configDirectoryVR + "/screenshot_preview"; + m_qrScreenshotImagePath = ctx->configDirectoryVR + "/screenshot"; + m_qrScreenshotTimer.setSingleShot(true); + + qDebug() << "QMLSCENE_DEVICE: " << qgetenv("QMLSCENE_DEVICE"); + + m_engine.rootContext()->setContextProperty("homePath", QDir::homePath()); + m_engine.rootContext()->setContextProperty("applicationDirectory", QApplication::applicationDirPath()); + m_engine.rootContext()->setContextProperty("idealThreadCount", QThread::idealThreadCount()); + m_engine.rootContext()->setContextProperty("qtRuntimeVersion", qVersion()); + m_engine.rootContext()->setContextProperty("ctx", ctx); + + m_engine.rootContext()->setContextProperty("Mobile", this); + qRegisterMetaType(); + qmlRegisterType("wowlet.NetworkType", 1, 0, "NetworkType"); + + qmlRegisterUncreatableType("wowlet.WalletKeysFiles", 1, 0, "WalletKeysFiles", "WalletKeysFiles can't be instantiated directly"); + qmlRegisterUncreatableType("wowlet.Wallet", 1, 0, "Wallet", "Wallet can't be instantiated directly"); + qmlRegisterType("wowlet.WalletManager", 1, 0, "WalletManager"); + + qmlRegisterUncreatableType("wowlet.TransactionHistoryProxyModel", 1, 0, "TransactionHistoryProxyModel", "TransactionHistoryProxyModel can't be instantiated directly"); + qmlRegisterUncreatableType("wowlet.TransactionHistoryModel", 1, 0, "TransactionHistoryModel", "TransactionHistoryModel can't be instantiated directly"); + qmlRegisterUncreatableType("wowlet.TransactionInfo", 1, 0, "TransactionInfo", "TransactionHistory can't be instantiated directly"); + qmlRegisterUncreatableType("wowlet.TransactionHistory", 1, 0, "TransactionHistory", "TransactionHistory can't be instantiated directly"); + + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + + auto widgetUrl = QUrl(QStringLiteral("qrc:///main")); + m_engine.load(widgetUrl); + if (m_engine.rootObjects().isEmpty()) + { + qCritical() << "Error: no root objects"; + return; + } + QObject *rootObject = m_engine.rootObjects().first(); + if (!rootObject) + { + qCritical() << "Error: no root objects"; + return; + } + + + int wege = 1; + } + + void Mobile::takeQRScreenshot() { + + } + + void Mobile::onCheckQRScreenshot() { + + } + + QString Mobile::checkQRScreenshotResults(std::vector results) { + + } + + Mobile::~Mobile() { + // bla + int wegeg = 1; + } +} diff --git a/src/mobile/main.h b/src/mobile/main.h new file mode 100644 index 0000000..284d755 --- /dev/null +++ b/src/mobile/main.h @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2020-2021, The Monero Project. + +#ifndef WOWLET_MAIN_H +#define WOWLET_MAIN_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "appcontext.h" +#include "utils/config.h" +#include "QR-Code-scanner/Decoder.h" + +namespace mobile { + + class Mobile : public QObject { + Q_OBJECT + public: + explicit Mobile(AppContext *ctx, QCommandLineParser *cmdargs, QObject *parent = nullptr); + ~Mobile() override; + + QList errors; + + Q_INVOKABLE double cdiv(double amount) { return amount / globals::cdiv; } + Q_INVOKABLE double add(double x, double y) const { return Utils::roundUp(x + y, 4); } // round ceil 4 decimals + Q_INVOKABLE double sub(double x, double y) const { return Utils::roundUp(x - y, 4); } // round ceil 4 decimals + + Q_INVOKABLE void onCreateTransaction(const QString &address, const QString &amount_str, const QString description, bool all) { + auto amount = WalletManager::amountFromString(amount_str); + ctx->onCreateTransaction(address, amount, description, false); + } + + Q_INVOKABLE void setClipboard(const QString &text) { + m_pClipboard->setText(text, QClipboard::Clipboard); + m_pClipboard->setText(text, QClipboard::Selection); + } + + Q_INVOKABLE QString preferredFiat() { + return config()->get(Config::preferredFiatCurrency).toString(); + } + + Q_INVOKABLE QString fiatToWow(double amount) { + auto preferredFiatCurrency = config()->get(Config::preferredFiatCurrency).toString(); + if (amount <= 0) return QString("0.00"); + + double conversionAmount = AppContext::prices->convert(preferredFiatCurrency, "WOW", amount); + return QString("%1").arg(QString::number(conversionAmount, 'f', 2)); + } + + Q_INVOKABLE QString wowToFiat(double amount) { + auto preferredFiatCurrency = config()->get(Config::preferredFiatCurrency).toString(); + if (amount <= 0) return QString("0.00"); + + double conversionAmount = AppContext::prices->convert("WOW", preferredFiatCurrency, amount); + if(conversionAmount <= 0) return QString("0.00"); + return QString("~%1").arg(QString::number(conversionAmount, 'f', 2)); + } + + Q_INVOKABLE void takeQRScreenshot(); + + signals: + void qrScreenshotFailed(QString error); + void qrScreenshotSuccess(QString address); + + private slots: + void onCheckQRScreenshot(); + + private: + AppContext *ctx; + QQmlApplicationEngine m_engine; + + bool desktopMode = false; + QString m_qrScreenshotPreviewPath; + QString m_qrScreenshotImagePath; + + QCommandLineParser *m_parser; + QClipboard *m_pClipboard; + QTimer m_qrScreenshotTimer; + QrDecoder m_qrDecoder; + + static QString checkQRScreenshotResults(std::vector results); + }; + +} + +#endif //WOWLET_MAIN_H diff --git a/src/mobile/main.qml b/src/mobile/main.qml new file mode 100644 index 0000000..5e29737 --- /dev/null +++ b/src/mobile/main.qml @@ -0,0 +1,35 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.0 +import QtQuick.Layouts 1.2 +import QtGraphicalEffects 1.0 +import QtQuick.Window 2.0 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Dialogs 1.2 +import QtGraphicalEffects 1.0 + +import "." + +import wowlet.Wallet 1.0 +import wowlet.WalletManager 1.0 + +ApplicationWindow { + visible: true + id: appWindow + width: 1080 + height: 2400 + color: "#2C3539" + + MouseArea { + anchors.fill: parent + onClicked: { + Qt.quit(); + } + } + + Text { + text: "Wowlet" + color: "white" + anchors.centerIn: parent + font.pointSize: 62 + } +} diff --git a/src/mobile/qml.qrc b/src/mobile/qml.qrc new file mode 100644 index 0000000..5a38622 --- /dev/null +++ b/src/mobile/qml.qrc @@ -0,0 +1,5 @@ + + + main.qml + + \ No newline at end of file diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index c62cd84..ca9aee9 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -256,11 +256,14 @@ QStandardItem *Utils::qStandardItem(const QString& text, QFont &font) { } QString Utils::getUnixAccountName() { +#ifdef __ANDROID__ + return ""; +#endif QString accountName = qgetenv("USER"); // mac/linux if (accountName.isEmpty()) accountName = qgetenv("USERNAME"); // Windows if (accountName.isEmpty()) - throw std::runtime_error("Could derive system account name from env vars: USER or USERNAME"); + throw std::runtime_error("Could not derive system account name from env vars: USER or USERNAME"); return accountName; } @@ -454,4 +457,27 @@ QTextCharFormat Utils::addressTextFormat(const SubaddressIndex &index) { return rec; } return QTextCharFormat(); -} \ No newline at end of file +} + +#ifdef __ANDROID__ +bool Utils::androidAskPermissions(const QVector &permissions) { + bool rtn = true; + if(QtAndroid::androidSdkVersion() >= 23) { + for(const QString &permission : permissions) { + auto result = QtAndroid::checkPermission(permission); + if(result != QtAndroid::PermissionResult::Granted) { + auto resultHash = QtAndroid::requestPermissionsSync(QStringList({permission})); + if(resultHash[permission] != QtAndroid::PermissionResult::Granted) { + qDebug() << "Fail to get permission" << permission; + rtn = false; + } else { + qDebug() << "Permission" << permission << "granted!"; + } + } else { + qDebug() << "Permission" << permission << "already granted!"; + } + } + } + return rtn; +} +#endif \ No newline at end of file diff --git a/src/utils/utils.h b/src/utils/utils.h index 9cee19f..444e6f8 100644 --- a/src/utils/utils.h +++ b/src/utils/utils.h @@ -8,6 +8,9 @@ #include #include #include +#ifdef __ANDROID__ +#include +#endif #include @@ -80,10 +83,13 @@ public: static QTextCharFormat addressTextFormat(const SubaddressIndex &index); template - static QString QtEnumToString (const QEnum value) - { + static QString QtEnumToString (const QEnum value) { return QString::fromStdString(std::string(QMetaEnum::fromType().valueToKey(value))); } + +#ifdef __ANDROID__ + static bool androidAskPermissions(const QVector &permissions); +#endif }; class AppContext; // forward declaration