diff --git a/Gruntfile.js b/Gruntfile.js index f9ed59621..7ae74ed17 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -8,6 +8,21 @@ module.exports = function(grunt) { grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), exec: { + get_nwjs_for_pkg: { + command: 'if [ ! -d ./cache/0.19.5-pkg/osx64/nwjs.app ]; then cd ./cache; curl https://dl.nwjs.io/v0.19.5-mas-beta/nwjs-mas-v0.19.5-osx-x64.zip --output nwjs.zip; unzip nwjs.zip; mkdir -p ./0.19.5-pkg/osx64; cp -R ./nwjs-mas-v0.19.5-osx-x64/nwjs.app ./0.19.5-pkg/osx64/; fi' + }, + create_others_dist: { + command: 'sh webkitbuilds/create-others-dist.sh "<%= pkg.name %>" "<%= pkg.fullVersion %>" "<%= pkg.nameCaseNoSpace %>" "<%= pkg.title %>"' + }, + create_dmg_dist: { + command: 'sh webkitbuilds/create-dmg-dist.sh "<%= pkg.name %>" "<%= pkg.fullVersion %>" "<%= pkg.nameCaseNoSpace %>" "<%= pkg.title %>"' + }, + create_pkg_dist: { + command: 'sh webkitbuilds/create-pkg-dist.sh "<%= pkg.name %>" "<%= pkg.fullVersion %>" "<%= pkg.nameCaseNoSpace %>" "<%= pkg.title %>"' + }, + sign_desktop_dist: { + command: 'sh webkitbuilds/sign-desktop-dist.sh "<%= pkg.name %>" "<%= pkg.fullVersion %>"' + }, appConfig: { command: 'node ./util/buildAppConfig.js' }, @@ -20,9 +35,6 @@ module.exports = function(grunt) { cordovaclean: { command: 'make -C cordova clean' }, - macos: { - command: 'sh webkitbuilds/build-macos.sh sign' - }, coveralls: { command: 'cat coverage/report-lcov/lcov.info |./node_modules/coveralls/bin/coveralls.js' }, @@ -61,7 +73,7 @@ module.exports = function(grunt) { stdin: true, }, desktopsign: { - cmd: 'gpg -u E0AE67E7 --output webkitbuilds/<%= pkg.title %>-linux.zip.sig --detach-sig webkitbuilds/<%= pkg.title %>-linux.zip ; gpg -u E0AE67E7 --output webkitbuilds/<%= pkg.title %>.exe.sig --detach-sig webkitbuilds/<%= pkg.title %>.exe' + cmd: 'gpg -u E0AE67E7 --output webkitbuilds/others/<%= pkg.title %>-linux.zip.sig --detach-sig webkitbuilds/others/<%= pkg.title %>-linux.zip ; gpg -u E0AE67E7 --output webkitbuilds/others/<%= pkg.title %>.exe.sig --detach-sig webkitbuilds/others/<%= pkg.title %>.exe' }, desktopverify: { cmd: 'gpg --verify webkitbuilds/<%= pkg.title %>-linux.zip.sig webkitbuilds/<%= pkg.title %>-linux.zip; gpg --verify webkitbuilds/<%= pkg.title %>.exe.sig webkitbuilds/<%= pkg.title %>.exe' @@ -124,6 +136,7 @@ module.exports = function(grunt) { }, angular: { src: [ + 'src/shim/shim.js', 'bower_components/qrcode-generator/js/qrcode.js', 'bower_components/qrcode-generator/js/qrcode_UTF8.js', 'bower_components/moment/min/moment-with-locales.js', @@ -152,6 +165,7 @@ module.exports = function(grunt) { src: [ 'src/js/app.js', 'src/js/routes.js', + 'src/js/decorators/*.js', 'src/js/directives/*.js', '!src/js/directives/*.spec.js', @@ -233,38 +247,78 @@ module.exports = function(grunt) { expand: true, cwd: 'webkitbuilds/', src: ['.desktop', '../www/img/app/favicon.ico', '../resources/<%= pkg.name %>/linux/512x512.png'], - dest: 'webkitbuilds/<%= pkg.title %>/linux64/', + dest: 'webkitbuilds/others/<%= pkg.title %>/linux64/', flatten: true, filter: 'isFile' }], } }, nwjs: { - options: { - appName: '<%= pkg.title %>', - platforms: ['win64', 'osx64', 'linux64'], - buildDir: './webkitbuilds', - version: '0.19.5', - macIcns: './resources/<%= pkg.name %>/mac/app.icns', - exeIco: './www/img/app/logo.ico', - macPlist: { - 'CFBundleURLTypes': [ - { - 'CFBundleURLName': 'URI Handler', - 'CFBundleURLSchemes': ['bitcoin', '<%= pkg.name %>'] - } - ] - } + others: { + options: { + appName: '<%= pkg.nameCaseNoSpace %>', + platforms: ['win64', 'linux64'], + buildDir: './webkitbuilds/others', + version: '0.19.5', + exeIco: './www/img/app/logo.ico' + }, + src: ['./package.json', './www/**/*'] + }, + dmg: { + options: { + appName: '<%= pkg.nameCaseNoSpace %>', + platforms: ['osx64'], + buildDir: './webkitbuilds/dmg', + version: '0.19.5', + macIcns: './resources/<%= pkg.name %>/mac/app.icns', + exeIco: './www/img/app/logo.ico', + macPlist: { + 'CFBundleDisplayName': '<%= pkg.title %>', + 'CFBundleShortVersionString': '<%= pkg.version %>', + 'CFBundleVersion': '<%= pkg.androidVersion %>', + 'LSApplicationCategoryType': 'public.app-category.finance', + 'CFBundleURLTypes': [ + { + 'CFBundleURLName': 'URI Handler', + 'CFBundleURLSchemes': ['bitcoin', '<%= pkg.name %>'] + } + ] + } + }, + src: ['./package.json', './www/**/*'] + }, + pkg: { + options: { + appName: '<%= pkg.nameCaseNoSpace %>', + platforms: ['osx64'], + buildDir: './webkitbuilds/pkg', + version: '0.19.5', + macIcns: './resources/<%= pkg.name %>/mac/pkg/app.icns', + exeIco: './www/img/app/logo.ico', + macPlist: { + 'CFBundleIdentifier': 'com.bitcoin.mwallet.mac', + 'CFBundleDisplayName': '<%= pkg.title %>', + 'CFBundleShortVersionString': '<%= pkg.version %>', + 'CFBundleVersion': '<%= pkg.androidVersion %>', + 'LSApplicationCategoryType': 'public.app-category.finance', + 'CFBundleURLTypes': [ + { + 'CFBundleURLName': 'URI Handler', + 'CFBundleURLSchemes': ['bitcoin', '<%= pkg.name %>'] + } + ] + } + }, + src: ['./package.json', './www/**/*'] }, - src: ['./package.json', './www/**/*'] }, compress: { linux: { options: { - archive: './webkitbuilds/<%= pkg.title %>-linux.zip' + archive: './webkitbuilds/others/<%= pkg.title %>-linux.zip' }, expand: true, - cwd: './webkitbuilds/<%= pkg.title %>/linux64/', + cwd: './webkitbuilds/others/<%= pkg.title %>/linux64/', src: ['**/*'], dest: '<%= pkg.title %>-linux/' } @@ -283,9 +337,6 @@ module.exports = function(grunt) { grunt.registerTask('default', ['nggettext_compile', 'exec:appConfig', 'exec:externalServices', 'browserify', 'sass', 'concat', 'copy:ionic_fonts', 'copy:ionic_js']); grunt.registerTask('prod', ['default', 'uglify']); grunt.registerTask('translate', ['nggettext_extract']); - grunt.registerTask('desktop', ['prod', 'nwjs', 'copy:linux', 'compress:linux']); - grunt.registerTask('osx', ['prod', 'nwjs', 'exec:macos', 'exec:osxsign']); - grunt.registerTask('osx-debug', ['default', 'nwjs']); grunt.registerTask('chrome', ['default','exec:chrome']); grunt.registerTask('wp', ['prod', 'exec:wp']); grunt.registerTask('wp-copy', ['default', 'exec:wpcopy']); @@ -297,6 +348,23 @@ module.exports = function(grunt) { grunt.registerTask('android-debug', ['exec:androiddebug', 'exec:androidrun']); grunt.registerTask('android', ['exec:android']); grunt.registerTask('android-release', ['prod', 'exec:android', 'exec:androidsign']); - grunt.registerTask('desktopsign', ['exec:desktopsign', 'exec:desktopverify']); + grunt.registerTask('desktopsign', ['exec:desktopsign', 'exec:desktopverify']); + // Build desktop + grunt.registerTask('desktop-build', ['desktop-others', 'desktop-osx-dmg', 'desktop-osx-pkg']); + + // Build desktop win64 & linux64 + grunt.registerTask('desktop-others', ['prod', 'nwjs:others', 'copy:linux', 'exec:create_others_dist']); + + // Build desktop osx pkg + grunt.registerTask('desktop-osx-pkg', ['prod', 'exec:get_nwjs_for_pkg', 'nwjs:pkg', 'exec:create_pkg_dist']); + + // Build desktop osx dmg + grunt.registerTask('desktop-osx-dmg', ['prod', 'nwjs:dmg', 'exec:create_dmg_dist']); + + // Sign desktop + grunt.registerTask('desktop-sign', ['exec:sign_desktop_dist']); + + // Release desktop + grunt.registerTask('desktop-release', ['desktop-build', 'desktop-sign']); }; diff --git a/app-template/apply.js b/app-template/apply.js index 1aaee94de..143cf57a8 100755 --- a/app-template/apply.js +++ b/app-template/apply.js @@ -11,7 +11,10 @@ var templates = { 'ionic.config.json': '/', '.desktop': 'webkitbuilds/', 'setup-win.iss': 'webkitbuilds/', - 'build-macos.sh': 'webkitbuilds/', + 'create-dmg-dist.sh': 'webkitbuilds/', + 'create-others-dist.sh': 'webkitbuilds/', + 'create-pkg-dist.sh': 'webkitbuilds/', + 'sign-desktop-dist.sh': 'webkitbuilds/', 'manifest.json': 'chrome-app/', // 'bower.json': '/', }; diff --git a/app-template/bitcoincom/appConfig.json b/app-template/bitcoincom/appConfig.json index 7fba677c4..2e9f82d29 100644 --- a/app-template/bitcoincom/appConfig.json +++ b/app-template/bitcoincom/appConfig.json @@ -2,7 +2,7 @@ "packageName": "bitcoin.com", "packageDescription": "Bitcoin.com Wallet", "packageNameId": "com.bitcoin.mwallet", - "userVisibleName": "Bitcoin.com", + "userVisibleName": "Bitcoin.com Wallet", "purposeLine": "Bitcoin.com Wallet", "bundleName": "bitcoincom", "appUri": "bitcoincom", @@ -18,7 +18,7 @@ "appDescription": "Bitcoin.com Wallet", "winAppName": "BitcoinWallet", "WindowsStoreIdentityName": "18C7659D.Bitcoin.com-SecureBitcoinWallet", - "WindowsStoreDisplayName": "Bitcoin.com - Secure Bitcoin Wallet", + "WindowsStoreDisplayName": "Bitcoin.com Wallet", "wpPublisherId": "{31cdd08b-457c-413d-b440-f6665eec847d}", "wpProductId": "{5381aa50-9069-11e4-84cc-293caf9cbdc8}", "windowsAppId": "804636ee-b017-4cad-8719-e58ac97ffa5c", diff --git a/app-template/config-template.xml b/app-template/config-template.xml index 239f78d6c..1c7f5a30a 100644 --- a/app-template/config-template.xml +++ b/app-template/config-template.xml @@ -75,8 +75,6 @@ - - diff --git a/app-template/build-macos.sh b/app-template/create-dmg-dist.sh old mode 100755 new mode 100644 similarity index 84% rename from app-template/build-macos.sh rename to app-template/create-dmg-dist.sh index 5f09bd0b2..5b004e66a --- a/app-template/build-macos.sh +++ b/app-template/create-dmg-dist.sh @@ -1,11 +1,5 @@ #!/bin/bash -SHOULD_SIGN=$1 -if [ "$SHOULD_SIGN" ] -then - echo "Will sign the APP" -fi - # by Andy Maloney # http://asmaloney.com/2013/07/howto/packaging-a-mac-os-x-application-using-a-dmg/ @@ -16,21 +10,25 @@ if [ -d "$dir" ]; then fi # set up your app name, architecture, and background image file name -APP_NAME="*USERVISIBLENAME*" +APP_PACKAGE=$1 +APP_VERSION=$2 +APP_NAME=$3 +APP_FULLNAME=$4 + rm dmg-background.tiff -ln -s ../resources/*PACKAGENAME*/mac/dmg-background.tiff dmg-background.tiff +ln -s ../resources/bitcoin.com/mac/dmg-background.tiff dmg-background.tiff rm volume-icon.icns -ln -s ../resources/*PACKAGENAME*/mac/volume-icon.icns volume-icon.icns +ln -s ../resources/bitcoin.com/mac/volume-icon.icns volume-icon.icns DMG_VOLUME_ICON="volume-icon.icns" DMG_BACKGROUND_IMG="dmg-background.tiff" -PATH_NAME="${APP_NAME}/osx64/" +PATH_NAME="dmg/${APP_NAME}/osx64/" # you should not need to change these APP_EXE="${PATH_NAME}${APP_NAME}.app/Contents/MacOS/nwjs" VOL_NAME="${APP_NAME}" -DMG_TMP="${VOL_NAME}-temp.dmg" -DMG_FINAL="${VOL_NAME}.dmg" +DMG_TMP="dmg/${VOL_NAME}-temp.dmg" +DMG_FINAL="dmg/${VOL_NAME}.dmg" STAGING_DIR="tmp" # Check the background image DPI and convert it if it isn't 72x72 @@ -66,25 +64,6 @@ SIZE=250 if [ $? -ne 0 ]; then echo "Error: Cannot compute size of staging dir" exit - fi - -# Sign Code (MATIAS) -if [ $SHOULD_SIGN ] -then - echo "Signing ${APP_NAME} DMG" - - export IDENTITY="3rd Party Mac Developer Application: BitPay, Inc. (884JRH5R93)" - - # not need for 'out of app store' distribution (?) -# export PARENT_PLIST=parent.plist -# export CHILD_PLIST=child.plist - export APP_PATH=${STAGING_DIR}/${APP_NAME}.app - - codesign --deep -s "${IDENTITY}" $APP_PATH"/Contents/Versions/52.0.2743.82/nwjs Helper.app" && echo "Sign 1" - codesign --deep -s "${IDENTITY}" $APP_PATH"/Contents/Versions/52.0.2743.82/nwjs Framework.framework/Resources/app_mode_loader.app" && echo "Sign 2" - codesign --deep -s "${IDENTITY}" $APP_PATH && echo "Sign 3" - echo "Signing Done" - fi # create the temp DMG file @@ -175,6 +154,14 @@ hdiutil detach "${DEVICE}" echo "Creating compressed image" hdiutil convert "${DMG_TMP}" -format UDZO -imagekey zlib-level=9 -o "${DMG_FINAL}" +export DIST_PATH="dist" + +if [ ! -d $DIST_PATH ]; then + mkdir $DIST_PATH +fi + +cp -vR "${DMG_FINAL}" "$DIST_PATH/${APP_PACKAGE}-wallet-${APP_VERSION}-osx.dmg" + # clean up rm -rf "${DMG_TMP}" rm -rf "${STAGING_DIR}" diff --git a/app-template/create-others-dist.sh b/app-template/create-others-dist.sh new file mode 100644 index 000000000..c9244b3ba --- /dev/null +++ b/app-template/create-others-dist.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +# make sure we are in the correct dir when we double-click a .command file +dir=${0%/*} +if [ -d "$dir" ]; then + cd "$dir" +fi + +# set up your app name, architecture, and background image file name +APP_PACKAGE=$1 +APP_VERSION=$2 +APP_NAME=$3 +APP_FULLNAME=$4 + +export APP_LINUX_PATH="others/${APP_NAME}/linux64" +export APP_WIN_PATH="others/${APP_NAME}/win64" +export DIST_PATH="dist" + +if [ ! -d $DIST_PATH ]; then + mkdir $DIST_PATH +fi + +## +# LINUX + +echo "Building Linux..." + +# Building package +cp -R $APP_LINUX_PATH bitcoin-com-wallet +tar -cvzf "$DIST_PATH/${APP_PACKAGE}-wallet-${APP_VERSION}-linux-x64.tar.gz" bitcoin-com-wallet + +# Clean +rm -R bitcoin-com-wallet + +echo "Linux Done." + + +## +# WINDOWS + +echo "Building Windows..." + +# Building package +cp -R $APP_WIN_PATH bitcoin-com-wallet +zip -r "$DIST_PATH/${APP_PACKAGE}-wallet-${APP_VERSION}-win-x64.zip" bitcoin-com-wallet + +# Clean +rm -R bitcoin-com-wallet + +echo "Windows Done." + +echo "Done." + +exit diff --git a/app-template/create-pkg-dist.sh b/app-template/create-pkg-dist.sh new file mode 100644 index 000000000..c0b4d266d --- /dev/null +++ b/app-template/create-pkg-dist.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +# make sure we are in the correct dir when we double-click a .command file +dir=${0%/*} +if [ -d "$dir" ]; then + cd "$dir" +fi + +# set up your app name, architecture, and background image file name +APP_PACKAGE=$1 +APP_VERSION=$2 +APP_NAME=$3 +APP_FULLNAME=$4 + +rm entitlements-child.plist +ln -s ../resources/bitcoin.com/mac/pkg/entitlements-child.plist entitlements-child.plist + +rm entitlements-parent.plist +ln -s ../resources/bitcoin.com/mac/pkg/entitlements-parent.plist entitlements-parent.plist + +rm build.cfg +ln -s ../resources/bitcoin.com/mac/pkg/build.cfg build.cfg + +rm build_mas.py +ln -s ../resources/bitcoin.com/mac/pkg/build_mas.py build_mas.py + +echo "Signing ${APP_NAME}" +export APP_PATH="pkg/${APP_NAME}/osx64/${APP_NAME}" +export TMP_PATH="tmp" +export DIST_PATH="dist" + +rm -rf $TMP_PATH +mkdir $TMP_PATH + +if [ ! -d $DIST_PATH ]; then + mkdir $DIST_PATH +fi + +python build_mas.py -C build.cfg -O "${TMP_PATH}/${APP_NAME}.app" -I "${APP_PATH}.app" -P "$DIST_PATH/${APP_PACKAGE}-wallet-${APP_VERSION}-osx.pkg" + +echo "Signing Done" + +echo "Done." + +exit diff --git a/app-template/package-template.json b/app-template/package-template.json index d679b0b24..232ac7e1f 100644 --- a/app-template/package-template.json +++ b/app-template/package-template.json @@ -3,6 +3,7 @@ "description": "*DESCRIPTION*", "author": "BitPay", "version": "*VERSION*", + "androidVersion": "*ANDROIDVERSION*", "fullVersion": "*FULLVERSION*", "keywords": [ "bitcoin", @@ -14,8 +15,9 @@ ], "main": "www/index.html", "title": "*USERVISIBLENAME*", + "nameCaseNoSpace": "*NAMECASENOSPACE*", "window": { - "title": "*USERVISIBLENAME* - *PURPOSELINE*", + "title": "*USERVISIBLENAME*", "icon": "www/img/app/icon.png", "toolbar": false, "show": true, @@ -69,6 +71,8 @@ "grunt-angular-gettext": "^2.2.3", "grunt-browserify": "^5.0.0", "grunt-cli": "^1.2.0", + "grunt-curl": "^2.4.1", + "grunt-zip": "^0.17.1", "grunt-contrib-compress": "^1.3.0", "grunt-contrib-concat": "^1.0.1", "grunt-contrib-copy": "^1.0.0", @@ -99,15 +103,19 @@ "build:ios-release": "cordova prepare ios && cordova build ios --release", "build:android-release": "cordova prepare android && cordova build android --release", "build:windows-release": "cordova prepare windows && cordova build windows --release --arch=\"ARM\"", - "build:desktop": "grunt desktop", - "build:osx": "grunt osx", + "build:desktop-release": "grunt desktop-release", + "build:desktop": "grunt desktop-build", + "build:osx-pkg": "grunt desktop-osx-pkg", + "build:osx-dmg": "grunt desktop-osx-dmg", + "build:others": "grunt desktop-others", + "sign:desktop": "grunt desktop-sign", "open:ios": "open platforms/ios/*.xcodeproj", "open:android": "open -a open -a /Applications/Android\\ Studio.app platforms/android", "final:www": "npm run build:www-release", "final:ios": "npm run final:www && npm run build:ios-release && npm run open:ios", "final:android": "npm run final:www && npm run build:android-release && npm run sign:android && npm run run:android-release", "final:windows": "npm run final:www && npm run build:windows-release", - "final:desktop": "npm run build:desktop && npm run build:osx", + "final:desktop": "npm run final:www && npm run build:desktop-release", "run:android": "cordova run android --device", "run:android-release": "cordova run android --device --release", "log:android": "adb logcat | grep chromium", diff --git a/app-template/sign-desktop-dist.sh b/app-template/sign-desktop-dist.sh new file mode 100644 index 000000000..e1e5c603c --- /dev/null +++ b/app-template/sign-desktop-dist.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +# make sure we are in the correct dir when we double-click a .command file +dir=${0%/*} +if [ -d "$dir" ]; then + cd "$dir" +fi + +APP_PACKAGE=$1 +APP_VERSION=$2 +export DIST_PATH="dist" + +## +# INIT GPG (YOU NEED THE PRIVATE KEY CONNECTED TO YOUR DESKTOP) +# gpg --card-edit + +## +# LINUX + +# Sig tar.gz +gpg --yes --output "$DIST_PATH/${APP_PACKAGE}-wallet-${APP_VERSION}-linux-x64.tar.gz.sig" --detach-sig "$DIST_PATH/${APP_PACKAGE}-wallet-${APP_VERSION}-linux-x64.tar.gz" +gpg --verify "$DIST_PATH/${APP_PACKAGE}-wallet-${APP_VERSION}-linux-x64.tar.gz.sig" "$DIST_PATH/${APP_PACKAGE}-wallet-${APP_VERSION}-linux-x64.tar.gz" + +## +# WINDOWS + +# Sig zip +gpg --yes --output "$DIST_PATH/${APP_PACKAGE}-wallet-${APP_VERSION}-win-x64.zip.sig" --detach-sig "$DIST_PATH/${APP_PACKAGE}-wallet-${APP_VERSION}-win-x64.zip" +gpg --verify "$DIST_PATH/${APP_PACKAGE}-wallet-${APP_VERSION}-win-x64.zip.sig" "$DIST_PATH/${APP_PACKAGE}-wallet-${APP_VERSION}-win-x64.zip" + +## +# OSX + +# Sig dmg +gpg --yes --output "$DIST_PATH/${APP_PACKAGE}-wallet-${APP_VERSION}-osx.dmg.sig" --detach-sig "$DIST_PATH/${APP_PACKAGE}-wallet-${APP_VERSION}-osx.dmg" +gpg --verify "$DIST_PATH/${APP_PACKAGE}-wallet-${APP_VERSION}-osx.dmg.sig" "$DIST_PATH/${APP_PACKAGE}-wallet-${APP_VERSION}-osx.dmg" + +# Sig pkg +gpg --yes --output "$DIST_PATH/${APP_PACKAGE}-wallet-${APP_VERSION}-osx.pkg.sig" --detach-sig "$DIST_PATH/${APP_PACKAGE}-wallet-${APP_VERSION}-osx.pkg" +gpg --verify "$DIST_PATH/${APP_PACKAGE}-wallet-${APP_VERSION}-osx.pkg.sig" "$DIST_PATH/${APP_PACKAGE}-wallet-${APP_VERSION}-osx.pkg" \ No newline at end of file diff --git a/resources/bitcoin.com/mac/pkg/app.icns b/resources/bitcoin.com/mac/pkg/app.icns new file mode 100644 index 000000000..40aa3ea77 Binary files /dev/null and b/resources/bitcoin.com/mac/pkg/app.icns differ diff --git a/resources/bitcoin.com/mac/pkg/build.cfg b/resources/bitcoin.com/mac/pkg/build.cfg new file mode 100644 index 000000000..3594a2688 --- /dev/null +++ b/resources/bitcoin.com/mac/pkg/build.cfg @@ -0,0 +1,32 @@ +[Sign] +## [REQUIRED] Your Application Certificate Identity +ApplicationIdentity = 3rd Party Mac Developer Application: Saint Bitts LLC (299HJ3G3BP) +## [OPTIONAL] Parent entitlements file +ParentEntitlements = entitlements-parent.plist +## [OPTIONAL] Child entitlements file +ChildEntitlements = entitlements-child.plist +## [OPTIONAL] Sandbox. Default: Yes +Sandbox = Yes + +[Package] +## [REQUIRED for --pkg] Your Installer Certificate Identity +InstallerIdentity = 3rd Party Mac Developer Installer: Saint Bitts LLC (299HJ3G3BP) +## [OPTIONAL for --pkg] Installation path +InstallPath = /Applications + +[Info.plist] +## [OPTIONAL] Your app bundle identifier +CFBundleIdentifier = com.bitcoin.mwallet.mac +## [REQUIRED] Team ID obtained from Apple Developer -> Membership -> Team ID +NWTeamID = 299HJ3G3BP +## Properties of Info.plist will be overwritten in this section. + +[Resources] +## [OPTIONAL] Your custom icon file +Icon = ../resources/bitcoin.com/mac/pkg/app.icns +## [OPTIONAL] Locales +## If Locales is not set, all current locales are preserved. +## If comma separated locale list (e.g. en,fr,zh_CN) is given, you should have +## additional [Locale locale_name] section for each locale containing localized strings. +## Locales not in the list will be removed. +Locales = en \ No newline at end of file diff --git a/resources/bitcoin.com/mac/pkg/build_mas.py b/resources/bitcoin.com/mac/pkg/build_mas.py new file mode 100755 index 000000000..d067abacd --- /dev/null +++ b/resources/bitcoin.com/mac/pkg/build_mas.py @@ -0,0 +1,253 @@ +#!/usr/bin/env python + +import argparse +import ConfigParser +import shutil +import os +import fnmatch +import plistlib +import tempfile +from datetime import datetime +import sys +import io + +bundleid = None +verbose = False + +def info(msg): + global verbose + if verbose: + print '[INFO] %s' % msg + +def error(msg): + print '[ERROR] %s' % msg + print '\nFailed.' + sys.exit(1) + +def system(cmd): + info(cmd) + os.system(cmd) + +def check_options(config, section, required_options, msg): + missed_options = [] + + for option in required_options: + if not config.has_option(section, option): + missed_options.append(option) + + if len(missed_options) != 0: + error(msg % (section, ', '.join(missed_options))) + +def glob(pathname, pattern, returnOnFound=False): + matches = [] + for root, dirnames, filenames in os.walk(pathname): + for dirname in fnmatch.filter(dirnames, pattern): + if returnOnFound: + return os.path.join(root, dirname) + matches.append(os.path.join(root, dirname)) + for filename in fnmatch.filter(filenames, pattern): + if returnOnFound: + return os.path.join(root, filename) + matches.append(os.path.join(root, filename)) + return matches + +def get_bundle_id(args): + global bundleid + if bundleid is None: + plist = plistlib.readPlist(os.path.join(args.output, 'Contents/Info.plist')) + bundleid = plist['CFBundleIdentifier'] + return bundleid + +def get_from_info_plist(args, key, default=None): + plist = plistlib.readPlist(os.path.join(args.output, 'Contents/Info.plist')) + if key in plist: + return plist[key] + else: + return default + +def patch_info_plist_file(file, replaces): + plist = plistlib.readPlist(file) + for (key, val) in replaces: + plist[key] = val + plistlib.writePlist(plist, file) + +def generate_infoplist_strings_file(file, items): + with io.open(file, 'w', encoding='utf-16') as fd: + for item in items: + fd.write(unicode('%s = "%s";\n' % item, 'utf-8')) + +def read_config(args): + print '\nParsing config file %s' % args.config_file + if not os.path.isfile(args.config_file): + error('%s does not exist' % args.config_file) + config = ConfigParser.SafeConfigParser() + config.optionxform = str # set to str to prevent transforming into lower cases + config.read(args.config_file) + check_options(config, 'Sign', ['ApplicationIdentity'], 'Missed options in [%s]: %s') + if args.pkg: + check_options(config, 'Package', ['InstallerIdentity'], 'Missed options for --pkg in [%s]: %s') + return config + +def copy_to_output(args): + print '\nCopying %s to %s' % (args.input, args.output) + shutil.rmtree(args.output, ignore_errors=True) + shutil.copytree(args.input, args.output, symlinks=True) # symblic links are required + +def patch_info_plist(config, args): + print '\nPatching Info.plist files' + + replaces = [] + for (key, val) in config.items('Info.plist'): + replaces.append((key, val)) + + file = os.path.join(args.output, 'Contents/Info.plist') + info(file) + patch_info_plist_file(file, replaces) + + info_plist_files = glob(os.path.join(args.output, 'Contents/Versions'), 'Info.plist') + for file in info_plist_files: + if 'nwjs Framework' in file: + tmp_replaces = [('CFBundleIdentifier', '%s.framework' % get_bundle_id(args))] + elif 'nwjs Helper' in file: + tmp_replaces = [('CFBundleIdentifier', '%s.helper' % get_bundle_id(args))] + else: + error('Cannot patch unknown Info.plist %s' % file) + info(file) + patch_info_plist_file(file, tmp_replaces) + +def patch_locales(config, args): + print '\nPatching locales' + locales = config.get('Resources', 'Locales').split(',') + removed_locales = [] + generated_locales = [] + for infoplist_strings_file in glob(os.path.join(args.output, 'Contents/Resources'), 'InfoPlist.strings'): + locale_dir = os.path.dirname(infoplist_strings_file) + (locale, _) = os.path.splitext(os.path.basename(locale_dir)) + if locale not in locales: + removed_locales.append(locale) + shutil.rmtree(locale_dir) + elif config.has_section('Locale %s' % locale): + generated_locales.append(locale) + generate_infoplist_strings_file(infoplist_strings_file, config.items('Locale %s' % locale)) + else: + error('Missing [Locale %s] section' % locale) + + if len(generated_locales) > 0: + info('Generated locales for %s' % ', '.join(generated_locales)) + if len(removed_locales) > 0: + info('Removed locales for %s' % ', '.join(removed_locales)) + + removed_paks = [] + for local_pak in glob(os.path.join(args.output, 'Contents/Versions'), 'locale.pak'): + locale_dir = os.path.dirname(local_pak) + (locale, _) = os.path.splitext(os.path.basename(locale_dir)) + if locale != 'en' and locale not in locales: + removed_paks.append(locale) + shutil.rmtree(locale_dir) + + if len(removed_paks) > 0: + info('Removed .pak files for %s' % ', '.join(removed_locales)) + +def patch_icon(config, args): + plist = plistlib.readPlist(os.path.join(args.output, 'Contents/Info.plist')) + icon = os.path.join(os.path.dirname(args.config_file), config.get('Resources', 'Icon')) + dest_icon = os.path.join(args.output, 'Contents/Resources/%s' % plist['CFBundleIconFile']) + info('Copying icon from %s to %s' % (icon, dest_icon)) + shutil.copy2(icon, dest_icon) + +def codesign_app(config, args): + print '\nCodesigning' + + bundleid = get_bundle_id(args) + + identity = config.get('Sign', 'ApplicationIdentity') + sandbox = True + if config.has_option('Sign', 'Sandbox'): + sandbox = config.getboolean('Sign', 'Sandbox') + + ## sign child frameworks and helpers + (_, tmp_child_entitlements) = tempfile.mkstemp() + if config.has_option('Sign', 'ChildEntitlements'): + child = config.get('Sign', 'ChildEntitlements') + child_entitlements = plistlib.readPlist(child) + else: + child_entitlements = { + 'com.apple.security.app-sandbox' : sandbox, + 'com.apple.security.inherit' : True + } + + plistlib.writePlist(child_entitlements, tmp_child_entitlements) + info('Child entitlements: %s' % tmp_child_entitlements) + framework = glob(args.output, 'nwjs Framework.framework', returnOnFound=True) + system('codesign -f --verbose -s "%s" --entitlements %s --deep "%s"' % (identity, tmp_child_entitlements, framework)) + helperApp = glob(args.output, 'nwjs Helper.app', returnOnFound=True) + system('codesign -f --verbose -s "%s" --entitlements %s --deep "%s"' % (identity, tmp_child_entitlements, helperApp)) + + ## sign parent app + (_, tmp_parent_entitlements) = tempfile.mkstemp() + if config.has_option('Sign', 'ParentEntitlements'): + parent = config.get('Sign', 'ParentEntitlements') + parent_entitlements = plistlib.readPlist(parent) + else: + parent_entitlements = {} + teamid = get_from_info_plist(args, 'NWTeamID', default=None) + if teamid is None: + groupid = bundleid + else: + groupid = '%s.%s' % (teamid, bundleid) + parent_entitlements['com.apple.security.app-sandbox'] = sandbox + parent_entitlements['com.apple.security.application-groups'] = [groupid] + plistlib.writePlist(parent_entitlements, tmp_parent_entitlements) + + info('Parent entitlements: %s' % tmp_parent_entitlements) + system('codesign -f --verbose -s "%s" --entitlements %s --deep "%s"' % (identity, tmp_parent_entitlements, args.output)) + +def productbuild(config, args): + print '\nRunning productbuild' + installer_identity = config.get('Package', 'InstallerIdentity') + if config.has_option('Package', 'InstallPath'): + install_path = config.get('Package', 'InstallPath') + else: + install_path = '/Applications' + system('productbuild --component "%s" "%s" --sign "%s" "%s"' % (args.output, install_path, installer_identity, args.pkg)) + +def main(): + parser = argparse.ArgumentParser(description='Signing tool for NW.js app') + parser.add_argument('-C', '--config-file', default='build.cfg', help='config file. (default: build.cfg)') + parser.add_argument('-I', '--input', default='nwjs.app', help='path to input app. (default: nwjs.app)') + parser.add_argument('-O', '--output', default='nwjs_output.app', help='path to output app. (default: nwjs_output.app)') + parser.add_argument('-S', '--skip-patching', default=False, help='run codesign without patching the app. (default: False)', action='store_true') + parser.add_argument('-P', '--pkg', default=None, help='run productbuild to generate .pkg after codesign. (default: None)') + parser.add_argument('-V', '--verbose', default=False, help='display detailed information. (default: False)', action='store_true') + args = parser.parse_args() + + global verbose + verbose = args.verbose + + # read config file + config = read_config(args) + + # make a copy + copy_to_output(args) + + if not args.skip_patching: + # patch Info.plist + patch_info_plist(config, args) + + # process resources & locales + if config.has_section('Resources'): + if config.has_option('Resources', 'Locales'): + patch_locales(config, args) + if config.has_option('Resources', 'Icon'): + patch_icon(config, args) + + # codesign + codesign_app(config, args) + + if args.pkg: + productbuild(config, args) + + print '\nDone.' + +if __name__ == "__main__": + main() diff --git a/resources/bitcoin.com/mac/pkg/entitlements-child.plist b/resources/bitcoin.com/mac/pkg/entitlements-child.plist new file mode 100644 index 000000000..635e25aac --- /dev/null +++ b/resources/bitcoin.com/mac/pkg/entitlements-child.plist @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.inherit + + + \ No newline at end of file diff --git a/resources/bitcoin.com/mac/pkg/entitlements-parent.plist b/resources/bitcoin.com/mac/pkg/entitlements-parent.plist new file mode 100644 index 000000000..12d6997e3 --- /dev/null +++ b/resources/bitcoin.com/mac/pkg/entitlements-parent.plist @@ -0,0 +1,16 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.application-groups + $GROUPID + com.apple.security.files.user-selected.read-only + + com.apple.security.network.client + + com.apple.security.device.camera + + + diff --git a/src/js/controllers/confirm.js b/src/js/controllers/confirm.js index c8dea6047..f1fab5b02 100644 --- a/src/js/controllers/confirm.js +++ b/src/js/controllers/confirm.js @@ -287,7 +287,10 @@ angular.module('copayApp.controllers').controller('confirmController', function( tx.amountValueStr = tx.amountStr.split(' ')[0]; tx.amountUnitStr = tx.amountStr.split(' ')[1]; txFormatService.formatAlternativeStr(wallet.coin, tx.toAmount, function(v) { + var parts = v.split(' '); tx.alternativeAmountStr = v; + tx.alternativeAmountValueStr = parts[0]; + tx.alternativeAmountUnitStr = (parts.length > 0) ? parts[1] : ''; }); } @@ -426,6 +429,8 @@ angular.module('copayApp.controllers').controller('confirmController', function( function showSendMaxWarning(wallet, sendMaxInfo) { + var feeAlternative = '', + msg = ''; function verifyExcludedUtxos() { var warningMsg = []; @@ -443,9 +448,18 @@ angular.module('copayApp.controllers').controller('confirmController', function( return warningMsg.join('\n'); }; - var msg = gettextCatalog.getString("{{fee}} will be deducted for bitcoin networking fees.", { - fee: txFormatService.formatAmountStr(wallet.coin, sendMaxInfo.fee) - }); + feeAlternative = txFormatService.formatAlternativeStr(wallet.coin, sendMaxInfo.fee); + if (feeAlternative) { + msg = gettextCatalog.getString("{{feeAlternative}} will be deducted for bitcoin networking fees ({{fee}}).", { + fee: txFormatService.formatAmountStr(wallet.coin, sendMaxInfo.fee), + feeAlternative: feeAlternative + }); + } else { + msg = gettextCatalog.getString("{{fee}} will be deducted for bitcoin networking fees).", { + fee: txFormatService.formatAmountStr(wallet.coin, sendMaxInfo.fee) + }); + } + var warningMsg = verifyExcludedUtxos(); if (!lodash.isEmpty(warningMsg)) diff --git a/src/js/controllers/tab-receive.js b/src/js/controllers/tab-receive.js index 5c9cef70f..629e59b51 100644 --- a/src/js/controllers/tab-receive.js +++ b/src/js/controllers/tab-receive.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.controllers').controller('tabReceiveController', function($rootScope, $scope, $timeout, $log, $ionicModal, $state, $ionicHistory, $ionicPopover, storageService, platformInfo, walletService, profileService, configService, lodash, gettextCatalog, popupService, bwcError, bitcoinCashJsService, $ionicNavBarDelegate, txFormatService, soundService) { +angular.module('copayApp.controllers').controller('tabReceiveController', function($rootScope, $scope, $timeout, $log, $ionicModal, $state, $ionicHistory, $ionicPopover, storageService, platformInfo, walletService, profileService, configService, lodash, gettextCatalog, popupService, bwcError, bitcoinCashJsService, $ionicNavBarDelegate, txFormatService, soundService, clipboardService) { var listeners = []; $scope.bchAddressType = { type: 'cashaddr' }; @@ -58,6 +58,12 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi paymentSubscriptionObj.addr = $scope.addr } + try { + clipboardService.copyToClipboard($scope.wallet.coin == 'bch' && $scope.bchAddressType.type == 'cashaddr' ? 'bitcoincash:' + $scope.addr : $scope.addr); + } catch (error) { + $log.debug("Error copying to clipboard:"); + $log.debug(error); + } // create subscription var msg = JSON.stringify(paymentSubscriptionObj); currentAddressSocket.onopen = function (event) { @@ -127,6 +133,12 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi for (var i = 0; i < data.x.out.length; i++) { if (data.x.out[i].addr == watchAddress) { $scope.paymentReceivedAmount = txFormatService.formatAmount(data.x.out[i].value, 'full'); + $scope.paymentReceivedAlternativeAmount = ''; // For when a subsequent payment is received. + txFormatService.formatAlternativeStr($scope.wallet.coin, data.x.out[i].value, function(alternativeStr){ + if (alternativeStr) { + $scope.paymentReceivedAlternativeAmount = alternativeStr; + } + }); } } $scope.paymentReceivedCoin = $scope.wallet.coin; @@ -229,6 +241,7 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi }); $scope.$on("$ionicView.enter", function(event, data) { + $scope.showingPaymentReceived = false; $ionicNavBarDelegate.showBar(true); }); diff --git a/src/js/controllers/tab-scan.js b/src/js/controllers/tab-scan.js index a96591a25..4a654d91d 100644 --- a/src/js/controllers/tab-scan.js +++ b/src/js/controllers/tab-scan.js @@ -122,8 +122,11 @@ angular.module('copayApp.controllers').controller('tabScanController', function( scannerService.openSettings(); }; + $scope.reactivationCount = 0; $scope.attemptToReactivate = function(){ - scannerService.reinitialize(); + scannerService.reinitialize(function(){ + $scope.reactivationCount++; + }); }; $scope.toggleLight = function(){ diff --git a/src/js/controllers/tab-send.js b/src/js/controllers/tab-send.js index 29f1749cb..2282ab878 100644 --- a/src/js/controllers/tab-send.js +++ b/src/js/controllers/tab-send.js @@ -76,8 +76,11 @@ angular.module('copayApp.controllers').controller('tabSendController', function( var walletList = []; lodash.each(walletsToTransfer, function(v) { var displayBalanceAsFiat = - v.status.alternativeBalanceAvailable && - config.wallet.settings.priceDisplay === 'fiat'; + // BD got v.status as undefined here once during development, just + // after creating a new wallet. + v.status && + v.status.alternativeBalanceAvailable && + config.wallet.settings.priceDisplay === 'fiat'; walletList.push({ color: v.color, diff --git a/src/js/decorators/displayLogDebug.js b/src/js/decorators/displayLogDebug.js new file mode 100644 index 000000000..4eacf34b3 --- /dev/null +++ b/src/js/decorators/displayLogDebug.js @@ -0,0 +1,15 @@ + angular.module('copayApp') + .config(['$provide', '$logProvider', function($provide, $logProvider) { + // expose a provider to reach debugEnabled in $log + $provide.value('$logProvider', $logProvider); +}]) +.decorator('$log', ['$logProvider', '$delegate', function($logProvider, $delegate) { + // override $log.debug to display in Chrome + $delegate.debug = function () { + if ($logProvider.debugEnabled()) { + $delegate.info.apply($delegate, arguments); + } + }; + + return $delegate; +}]); \ No newline at end of file diff --git a/src/js/directives/copyToClipboard.js b/src/js/directives/copyToClipboard.js index 5de40f23e..c81e0bd60 100644 --- a/src/js/directives/copyToClipboard.js +++ b/src/js/directives/copyToClipboard.js @@ -1,38 +1,26 @@ 'use strict'; angular.module('copayApp.directives') - .directive('copyToClipboard', function(platformInfo, nodeWebkitService, gettextCatalog, ionicToast, clipboard) { + .directive('copyToClipboard', function(clipboardService, ionicToast, gettextCatalog) { return { restrict: 'A', scope: { copyToClipboard: '=copyToClipboard' }, link: function(scope, elem, attrs, ctrl) { - var isCordova = platformInfo.isCordova; - var isChromeApp = platformInfo.isChromeApp; - var isNW = platformInfo.isNW; elem.bind('mouseover', function() { elem.css('cursor', 'pointer'); }); - var msg = gettextCatalog.getString('Copied to clipboard'); elem.bind('click', function() { var data = scope.copyToClipboard; - if (!data) return; + clipboardService.copyToClipboard(data); - if (isCordova) { - cordova.plugins.clipboard.copy(data); - } else if (isNW) { - nodeWebkitService.writeToClipboard(data); - } else if (clipboard.supported) { - clipboard.copyText(data); - } else { - // No supported - return; - } - scope.$apply(function() { + var msg = gettextCatalog.getString('Copied to clipboard'); + scope.$apply(function () { ionicToast.show(msg, 'bottom', false, 1000); }); + }); } } diff --git a/src/js/services/clipboardService.js b/src/js/services/clipboardService.js new file mode 100644 index 000000000..e2e0e5fb3 --- /dev/null +++ b/src/js/services/clipboardService.js @@ -0,0 +1,24 @@ +'use strict'; + +angular.module('copayApp.services').factory('clipboardService', function ($http, $log, platformInfo, nodeWebkitService, gettextCatalog, ionicToast, clipboard) { + var root = {}; + + root.copyToClipboard = function (data) { + if (!data) return; + + $log.debug("Copy '"+data+"' to clipboard"); + if (platformInfo.isCordova) { + cordova.plugins.clipboard.copy(data); + } else if (platformInfo.isNW) { + nodeWebkitService.writeToClipboard(data); + } else if (clipboard.supported) { + clipboard.copyText(data); + } else { + // No supported + return; + } + + }; + + return root; +}); \ No newline at end of file diff --git a/src/js/services/configService.js b/src/js/services/configService.js index e8ed93d88..72cc4825f 100644 --- a/src/js/services/configService.js +++ b/src/js/services/configService.js @@ -107,7 +107,7 @@ angular.module('copayApp.services').factory('configService', function(storageSer enabled: false, }, - soundsEnabled: false, + soundsEnabled: true, log: { filter: 'debug', diff --git a/src/js/services/scannerService.js b/src/js/services/scannerService.js index ddf62895d..e09662396 100644 --- a/src/js/services/scannerService.js +++ b/src/js/services/scannerService.js @@ -103,6 +103,7 @@ angular.module('copayApp.services').service('scannerService', function($log, $ti _completeInitialization(status, callback); }); } else { + isAvailable = true; // XX SP: Availability can change after permissions are granted after being denied. _completeInitialization(status, callback); } }); diff --git a/src/js/services/storageService.js b/src/js/services/storageService.js index a2d85950b..10f0cdd76 100644 --- a/src/js/services/storageService.js +++ b/src/js/services/storageService.js @@ -1,6 +1,6 @@ 'use strict'; angular.module('copayApp.services') - .factory('storageService', function(appConfigService, logHeader, fileStorageService, localStorageService, sjcl, $log, lodash, platformInfo, secureStorageService, $timeout) { + .factory('storageService', function(appConfigService, logHeader, fileStorageService, localStorageService, sjcl, $log, lodash, platformInfo, $timeout) { var root = {}; var storage; @@ -121,11 +121,7 @@ angular.module('copayApp.services') root.storeProfile = function(profile, cb) { var profileString = profile.toObj(); - if (platformInfo.isNW) { - storage.set('profile', profileString, cb); - } else { - secureStorageService.set('profile', profileString, cb); - } + storage.set('profile', profileString, cb); }; /** @@ -205,48 +201,19 @@ angular.module('copayApp.services') * @param {getProfileCallback} cb */ root.getProfile = function(cb) { - if (platformInfo.isNW) { - storage.get('profile', function(getErr, getStr) { - _onOldProfileRetrieved(getErr, getStr, cb); - }); - return - } - - secureStorageService.get('profile', function(secureErr, secureStr) { - var secureProfile; - var oldProfile; - - if (secureErr) { - return cb(secureErr, null); + storage.get('profile', function(getErr, getStr) { + if (getErr) { + cb(getErr, null); + return; } - if (secureStr) { - try { - secureProfile = Profile.fromString(secureStr); - $log.debug('profile: ' + JSON.stringify(secureProfile)); - } catch (e) { - $log.error(e); - return cb(e, null); - } + if (!getStr) { + cb(null, null); + return; } - - storage.get('profile', function(getErr, getStr) { - _onOldProfileRetrieved(getErr, getStr, function(oldErr, oldProfile){ - if (oldErr) { - return cb(oldErr, null); - } - - if (!oldProfile) { - if (secureProfile) { - return cb(null, secureProfile); - } else { - // No profiles found. No errors either. - return cb(null, null); - } - } - _migrateProfiles(oldProfile, secureProfile, cb); - }); - }); + + var profile = Profile.fromString(getStr); + cb(null, profile); }); }; diff --git a/src/js/services/txFormatService.js b/src/js/services/txFormatService.js index 5817c1a27..ebcb3886a 100644 --- a/src/js/services/txFormatService.js +++ b/src/js/services/txFormatService.js @@ -72,11 +72,19 @@ angular.module('copayApp.services').factory('txFormatService', function($filter, var config = configService.getSync().wallet.settings; var val = function() { - var v1 = parseFloat((rateService.toFiat(satoshis, config.alternativeIsoCode, coin)).toFixed(2)); - v1 = $filter('formatFiatAmount')(v1); + var fiatAmount = rateService.toFiat(satoshis, config.alternativeIsoCode, coin); + var roundedStr = fiatAmount.toFixed(2); + var roundedNum = parseFloat(roundedStr); + var subcent = roundedNum === 0 && fiatAmount > 0; + var lessThanPrefix = ''; + if (subcent) { + roundedNum = 0.01; + lessThanPrefix = '< '; + } + var v1 = $filter('formatFiatAmount')(roundedNum); if (!v1) return null; - return v1 + ' ' + config.alternativeIsoCode; + return lessThanPrefix + v1 + ' ' + config.alternativeIsoCode; }; // Async version diff --git a/src/js/services/txFormatService.spec.js b/src/js/services/txFormatService.spec.js new file mode 100644 index 000000000..c67e86f21 --- /dev/null +++ b/src/js/services/txFormatService.spec.js @@ -0,0 +1,68 @@ +describe('txFormatService', function(){ + var configServiceMock, + rateServiceMock, + txFormatService; + + beforeEach(function(){ + module('ngLodash'); + module('bwcModule'); + module('copayApp.filters'); + module('copayApp.services'); + + configServiceMock = { + getSync: jasmine.createSpy() + }; + + rateServiceMock = { + isAvailable: jasmine.createSpy(), + toFiat: jasmine.createSpy() + }; + + module(function($provide) { + $provide.value('configService', configServiceMock); + $provide.value('rateService', rateServiceMock); + }); + + inject(function($injector){ + txFormatService = $injector.get('txFormatService'); + }); + + }); + + it('formatAlternativeStr 0.49 cents.', function() { + + configServiceMock.getSync.and.returnValue({ + wallet: { + settings: { + alternativeIsoCode: 'USD' + } + } + }); + + rateServiceMock.isAvailable.and.returnValue(true); + rateServiceMock.toFiat.and.returnValue(0.00499); + + var formatted = txFormatService.formatAlternativeStr('bch', 123); + + expect(formatted).toBe('< 0.01 USD'); + }); + + it('formatAlternativeStr 0.5 cents.', function() { + + configServiceMock.getSync.and.returnValue({ + wallet: { + settings: { + alternativeIsoCode: 'USD' + } + } + }); + + rateServiceMock.isAvailable.and.returnValue(true); + rateServiceMock.toFiat.and.returnValue(0.005); + + var formatted = txFormatService.formatAlternativeStr('bch', 123); + + expect(formatted).toBe('0.01 USD'); + }); + +}); \ No newline at end of file diff --git a/src/sass/main.scss b/src/sass/main.scss index cb5e7118f..7b3e46291 100644 --- a/src/sass/main.scss +++ b/src/sass/main.scss @@ -5,6 +5,7 @@ @import "icons"; @import "buttons"; @import "forms"; +@import "qr"; @import "mixins/mixins"; @import "views/views"; @import "directives/directives"; diff --git a/src/sass/qr.scss b/src/sass/qr.scss new file mode 100644 index 000000000..3f8c9f104 --- /dev/null +++ b/src/sass/qr.scss @@ -0,0 +1,20 @@ +qrcode { + &.qr-icon { + &::before { + content: ""; + background-size: 100% 100%; + display: block; + margin-left: calc(50% - 22px); + margin-top: 88px; + width: 44px; + height: 44px; + position: absolute; + } + &--bch::before { + background-image: url('../img/qr-overlay-bch.png'); + } + &--btc::before { + background-image: url('../img/qr-overlay-btc.png'); + } + } +} \ No newline at end of file diff --git a/src/sass/views/includes/txp-details.scss b/src/sass/views/includes/txp-details.scss index c32faaacd..240ee444b 100644 --- a/src/sass/views/includes/txp-details.scss +++ b/src/sass/views/includes/txp-details.scss @@ -36,6 +36,11 @@ .amount-label{ line-height: 30px; .amount{ + font-size: 16px; + color: #9B9B9B; + font-family: "Roboto-Light"; + } + .alternative { font-size: 38px; margin-bottom: .5rem; @@ -43,11 +48,6 @@ font-family: "Roboto-Light"; } } - .alternative { - font-size: 16px; - font-family: "Roboto-Light"; - color: #9B9B9B; - } } } .item { diff --git a/src/shim/shim.js b/src/shim/shim.js new file mode 100644 index 000000000..495848f05 --- /dev/null +++ b/src/shim/shim.js @@ -0,0 +1,11 @@ +//--------------------------------------------------------------------- +// +// Add components what are missing in old JavaScript Engine +// +//--------------------------------------------------------------------- + +if (!ArrayBuffer['isView']) { + ArrayBuffer.isView = function(a) { + return a !== null && typeof(a) === "object" && a['buffer'] instanceof ArrayBuffer; + }; +} \ No newline at end of file diff --git a/www/img/qr-overlay-bch.png b/www/img/qr-overlay-bch.png new file mode 100644 index 000000000..566789663 Binary files /dev/null and b/www/img/qr-overlay-bch.png differ diff --git a/www/img/qr-overlay-btc.png b/www/img/qr-overlay-btc.png new file mode 100644 index 000000000..3973b0dc5 Binary files /dev/null and b/www/img/qr-overlay-btc.png differ diff --git a/www/index.html b/www/index.html index 76827f685..47dc27d2a 100644 --- a/www/index.html +++ b/www/index.html @@ -11,7 +11,7 @@ - Bitcoin.com - Bitcoin.com Wallet + Bitcoin.com Wallet diff --git a/www/views/confirm.html b/www/views/confirm.html index 443043d49..e54837f34 100644 --- a/www/views/confirm.html +++ b/www/views/confirm.html @@ -16,8 +16,8 @@ Sending maximum amount
+
{{tx.alternativeAmountValueStr || '...'}} {{tx.alternativeAmountUnitStr}}
{{tx.amountValueStr || '...'}} {{tx.amountUnitStr}}
-
{{tx.alternativeAmountStr || '...'}}
@@ -77,9 +77,9 @@
{{'Fee:' | translate}} {{tx.feeLevelName | translate}} - {{tx.txp[wallet.id].feeStr || '...'}} + {{tx.txp[wallet.id].alternativeFeeStr || '...'}} - {{tx.txp[wallet.id].alternativeFeeStr || '...'}}  + {{tx.txp[wallet.id].feeStr || '...'}}  ·   {{tx.txp[wallet.id].feeRatePerStr}} of the sending amount diff --git a/www/views/tab-receive.html b/www/views/tab-receive.html index 0eb598096..824ad0d0a 100644 --- a/www/views/tab-receive.html +++ b/www/views/tab-receive.html @@ -41,16 +41,16 @@ - +
{{addr}}
-
-
-
+
+
+
@@ -61,7 +61,8 @@


Payment Received! - {{ paymentReceivedAmount }} {{ paymentReceivedCoin }} + {{ paymentReceivedAmount }} {{ paymentReceivedCoin }} + {{ paymentReceivedAlternativeAmount }} Return To Address

diff --git a/www/views/tab-scan.html b/www/views/tab-scan.html index 1445adeb8..54c5efab3 100644 --- a/www/views/tab-scan.html +++ b/www/views/tab-scan.html @@ -16,7 +16,7 @@
You can scan bitcoin addresses, payment requests, paper wallets, and more.
Enable the camera to get started.
-
Enable camera access in your device settings to get started.
+
Enable camera access in your device settings to get started.
Please connect a camera to get started.