Repository: facebook/FBNotifications
Branch: master
Commit: 672f61c935e8
Files: 195
Total size: 616.0 KB
Directory structure:
gitextract_ihx0v_r0/
├── .gitignore
├── .gitmodules
├── .travis.yml
├── Android/
│ ├── .gitignore
│ ├── build.gradle
│ ├── gradle/
│ │ └── wrapper/
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
│ ├── gradle.properties
│ ├── gradlew
│ ├── gradlew.bat
│ ├── notifications/
│ │ ├── build.gradle
│ │ ├── proguard-rules.pro
│ │ └── src/
│ │ └── main/
│ │ ├── AndroidManifest.xml
│ │ └── java/
│ │ └── com/
│ │ └── facebook/
│ │ └── notifications/
│ │ ├── NotificationCardResult.java
│ │ ├── NotificationsManager.java
│ │ └── internal/
│ │ ├── activity/
│ │ │ └── CardActivity.java
│ │ ├── appevents/
│ │ │ └── AppEventsLogger.java
│ │ ├── asset/
│ │ │ ├── Asset.java
│ │ │ ├── AssetManager.java
│ │ │ ├── ParcelableAssetHandler.java
│ │ │ ├── cache/
│ │ │ │ ├── CacheOperation.java
│ │ │ │ ├── ContentCache.java
│ │ │ │ ├── ContentDownloader.java
│ │ │ │ └── DiskCache.java
│ │ │ └── handlers/
│ │ │ ├── BitmapAssetHandler.java
│ │ │ ├── ColorAssetHandler.java
│ │ │ └── GifAssetHandler.java
│ │ ├── configuration/
│ │ │ ├── ActionConfiguration.java
│ │ │ ├── ActionsConfiguration.java
│ │ │ ├── BodyConfiguration.java
│ │ │ ├── CardConfiguration.java
│ │ │ └── HeroConfiguration.java
│ │ ├── content/
│ │ │ ├── Content.java
│ │ │ ├── ContentManager.java
│ │ │ └── TextContent.java
│ │ ├── utilities/
│ │ │ ├── EnumCreator.java
│ │ │ ├── FontUtilities.java
│ │ │ ├── GifDecoder.java
│ │ │ ├── InvalidParcelException.java
│ │ │ ├── JSONObjectVisitor.java
│ │ │ ├── RoundedViewHelper.java
│ │ │ ├── TransparentStateListDrawable.java
│ │ │ └── Version.java
│ │ └── view/
│ │ ├── ActionButton.java
│ │ ├── ActionsView.java
│ │ ├── AssetView.java
│ │ ├── BodyView.java
│ │ ├── CardView.java
│ │ ├── ContentView.java
│ │ ├── GifView.java
│ │ └── HeroView.java
│ ├── notifications-example/
│ │ ├── build.gradle
│ │ ├── google-services.json
│ │ ├── proguard-rules.pro
│ │ └── src/
│ │ └── main/
│ │ ├── AndroidManifest.xml
│ │ ├── assets/
│ │ │ ├── example1.json
│ │ │ ├── example2.json
│ │ │ ├── example3.json
│ │ │ ├── example4.json
│ │ │ └── example5.json
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── facebook/
│ │ │ └── notifications/
│ │ │ └── sample/
│ │ │ ├── MainActivity.java
│ │ │ ├── MyGcmListenerService.java
│ │ │ ├── MyInstanceIDListenerService.java
│ │ │ └── RegistrationIntentService.java
│ │ └── res/
│ │ ├── layout/
│ │ │ ├── activity_main.xml
│ │ │ └── content_main.xml
│ │ ├── menu/
│ │ │ └── menu_main.xml
│ │ ├── values/
│ │ │ ├── colors.xml
│ │ │ ├── dimens.xml
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ │ ├── values-v21/
│ │ │ └── styles.xml
│ │ └── values-w820dp/
│ │ └── dimens.xml
│ └── settings.gradle
├── CHANGELOG-Android.md
├── CHANGELOG-iOS.md
├── CONTRIBUTING.md
├── FBNotifications.podspec
├── Format/
│ ├── Format-1.0.md
│ └── Format-Changelog.md
├── LICENSE
├── LICENSE-specification
├── README.md
└── iOS/
├── .gitignore
├── FBNotifications/
│ ├── Configurations/
│ │ ├── FBNotifications-iOS-Dynamic.xcconfig
│ │ └── FBNotifications-iOS.xcconfig
│ ├── FBNotifications/
│ │ ├── FBNCardViewController.h
│ │ ├── FBNCardViewController.m
│ │ ├── FBNConstants.h
│ │ ├── FBNConstants.m
│ │ ├── FBNotifications.h
│ │ ├── FBNotificationsManager.h
│ │ ├── FBNotificationsManager.m
│ │ ├── Internal/
│ │ │ ├── AppEvents/
│ │ │ │ ├── FBNCardAppEventsLogger.h
│ │ │ │ └── FBNCardAppEventsLogger.m
│ │ │ ├── Asset/
│ │ │ │ ├── Cache/
│ │ │ │ │ ├── FBNAssetContentCache.h
│ │ │ │ │ ├── FBNAssetContentCache.m
│ │ │ │ │ ├── FBNAssetContentCacheOperation.h
│ │ │ │ │ └── FBNAssetContentCacheOperation.m
│ │ │ │ ├── Color/
│ │ │ │ │ ├── FBNColorAsset.h
│ │ │ │ │ ├── FBNColorAsset.m
│ │ │ │ │ ├── FBNColorAssetController.h
│ │ │ │ │ ├── FBNColorAssetController.m
│ │ │ │ │ ├── FBNColorAssetViewController.h
│ │ │ │ │ └── FBNColorAssetViewController.m
│ │ │ │ ├── Common/
│ │ │ │ │ ├── FBNAsset.h
│ │ │ │ │ └── FBNAssetController.h
│ │ │ │ ├── FBNAssetsController.h
│ │ │ │ ├── FBNAssetsController.m
│ │ │ │ ├── GIF/
│ │ │ │ │ ├── FBNGIFAsset.h
│ │ │ │ │ ├── FBNGIFAsset.m
│ │ │ │ │ ├── FBNGIFAssetController.h
│ │ │ │ │ ├── FBNGIFAssetController.m
│ │ │ │ │ ├── FBNIGIFAssetViewController.h
│ │ │ │ │ └── FBNIGIFAssetViewController.m
│ │ │ │ └── Image/
│ │ │ │ ├── FBNImageAsset.h
│ │ │ │ ├── FBNImageAsset.m
│ │ │ │ ├── FBNImageAssetController.h
│ │ │ │ ├── FBNImageAssetController.m
│ │ │ │ ├── FBNImageAssetViewController.h
│ │ │ │ └── FBNImageAssetViewController.m
│ │ │ ├── FBNCardViewController_Internal.h
│ │ │ ├── Models/
│ │ │ │ ├── Actions/
│ │ │ │ │ ├── FBNCardActionConfiguration.h
│ │ │ │ │ ├── FBNCardActionConfiguration.m
│ │ │ │ │ ├── FBNCardActionsConfiguration.h
│ │ │ │ │ ├── FBNCardActionsConfiguration.m
│ │ │ │ │ ├── FBNCardActionsStyle.h
│ │ │ │ │ ├── FBNCardActionsStyle.m
│ │ │ │ │ └── FBNCardButtonAction.h
│ │ │ │ ├── Body/
│ │ │ │ │ ├── FBNCardBodyConfiguration.h
│ │ │ │ │ └── FBNCardBodyConfiguration.m
│ │ │ │ ├── Card/
│ │ │ │ │ ├── FBNCardConfiguration.h
│ │ │ │ │ ├── FBNCardConfiguration.m
│ │ │ │ │ ├── FBNCardDisplayOptions.h
│ │ │ │ │ ├── FBNCardDisplayOptions.m
│ │ │ │ │ ├── FBNCardSize.h
│ │ │ │ │ └── FBNCardSize.m
│ │ │ │ ├── Components/
│ │ │ │ │ ├── FBNAnimatedImage.h
│ │ │ │ │ ├── FBNAnimatedImage.m
│ │ │ │ │ ├── FBNCardBackground.h
│ │ │ │ │ ├── FBNCardBackground.m
│ │ │ │ │ ├── FBNCardColor.h
│ │ │ │ │ ├── FBNCardColor.m
│ │ │ │ │ ├── FBNCardFont.h
│ │ │ │ │ ├── FBNCardFont.m
│ │ │ │ │ ├── FBNCardTextAlignment.h
│ │ │ │ │ ├── FBNCardTextAlignment.m
│ │ │ │ │ ├── FBNCardTextContent.h
│ │ │ │ │ └── FBNCardTextContent.m
│ │ │ │ └── Hero/
│ │ │ │ ├── FBNCardHeroConfiguration.h
│ │ │ │ └── FBNCardHeroConfiguration.m
│ │ │ ├── Utilities/
│ │ │ │ ├── FBNCardError.h
│ │ │ │ ├── FBNCardError.m
│ │ │ │ ├── FBNCardHash.h
│ │ │ │ ├── FBNCardHash.m
│ │ │ │ ├── FBNCardPayload.h
│ │ │ │ ├── FBNCardPayload.m
│ │ │ │ ├── FBNCardViewUtilities.h
│ │ │ │ ├── FBNCardViewUtilities.m
│ │ │ │ └── FBNContentSizeProvider.h
│ │ │ └── Views/
│ │ │ ├── Components/
│ │ │ │ ├── FBNCardDismissButton.h
│ │ │ │ ├── FBNCardDismissButton.m
│ │ │ │ ├── FBNCardLabel.h
│ │ │ │ └── FBNCardLabel.m
│ │ │ ├── FBNCardActionButton.h
│ │ │ ├── FBNCardActionButton.m
│ │ │ ├── FBNCardActionsViewController.h
│ │ │ ├── FBNCardActionsViewController.m
│ │ │ ├── FBNCardBodyViewController.h
│ │ │ ├── FBNCardBodyViewController.m
│ │ │ ├── FBNCardHeroViewController.h
│ │ │ └── FBNCardHeroViewController.m
│ │ └── Resources/
│ │ └── Info.plist
│ └── FBNotifications.xcodeproj/
│ ├── project.pbxproj
│ └── xcshareddata/
│ └── xcschemes/
│ ├── FBNotifications-iOS-Dynamic.xcscheme
│ └── FBNotifications-iOS.xcscheme
├── FBNotifications.xcworkspace/
│ └── contents.xcworkspacedata
├── FBNotificationsExample/
│ ├── .gitignore
│ ├── FBNotificationsExample/
│ │ ├── Resources/
│ │ │ ├── Assets.xcassets/
│ │ │ │ └── AppIcon.appiconset/
│ │ │ │ └── Contents.json
│ │ │ ├── Base.lproj/
│ │ │ │ ├── LaunchScreen.storyboard
│ │ │ │ └── Main.storyboard
│ │ │ ├── Examples/
│ │ │ │ ├── example1.json
│ │ │ │ ├── example2.json
│ │ │ │ ├── example3.json
│ │ │ │ ├── example4.json
│ │ │ │ └── example5.json
│ │ │ └── Info.plist
│ │ └── Source/
│ │ ├── AppDelegate.h
│ │ ├── AppDelegate.m
│ │ ├── ViewController.h
│ │ ├── ViewController.m
│ │ └── main.m
│ └── FBNotificationsExample.xcodeproj/
│ ├── project.pbxproj
│ └── xcshareddata/
│ └── xcschemes/
│ └── FBNotificationsExample.xcscheme
└── Rakefile
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
## OS X
.DS_Store
================================================
FILE: .gitmodules
================================================
[submodule "iOS/Vendor/xctoolchain"]
path = iOS/Vendor/xctoolchain
url = https://github.com/ParsePlatform/xctoolchain.git
================================================
FILE: .travis.yml
================================================
branches:
only:
- master
matrix:
include:
- language: objective-c
os: osx
osx_image: xcode7.3
env: TEST_TYPE=iOS
- language: objective-c
os: osx
osx_image: xcode7.3
env: TEST_TYPE=CocoaPods
- language: objective-c
os: osx
osx_image: xcode7.3
env: TEST_TYPE=Carthage
- language: android
sudo: false
env: TEST_TYPE=Android
android:
components:
- tools
- build-tools-23.0.3
- android-23
- doc-23
- extra-android-support
- extra-android-m2repository
- extra-google-m2repository
- extra-google-google_play_services
install:
- |
if [ "$TEST_TYPE" = "iOS" ]; then
gem install xcpretty -N --no-ri --no-rdoc
elif [ "$TEST_TYPE" = Carthage ]; then
brew install carthage || brew upgrade carthage
fi
script:
- |
if [ "$TEST_TYPE" = "iOS" ]; then
cd iOS
set -o pipefail
xcodebuild build -workspace FBNotifications.xcworkspace -scheme FBNotifications-iOS -sdk iphonesimulator -configuration Release | xcpretty -c
xcodebuild build -workspace FBNotifications.xcworkspace -scheme FBNotificationsExample -sdk iphonesimulator -configuration Release | xcpretty -c
elif [ "$TEST_TYPE" = Android ]; then
cd Android
./gradlew clean assemble -p notifications
elif [ "$TEST_TYPE" = CocoaPods ]; then
pod lib lint FBNotifications.podspec
pod lib lint --use-libraries FBNotifications.podspec
elif [ "$TEST_TYPE" = Carthage ]; then
carthage build --no-skip-current
fi
================================================
FILE: Android/.gitignore
================================================
# built application files
*.apk
*.ap_
# files for the dex VM
*.dex
# Java class files
*.class
# generated files
bin/
gen/
# Local configuration file (sdk path, etc)
local.properties
# Android Studio
.idea
*.iml
*.ipr
*.iws
classes
gen-external-apklibs
# Gradle
.gradle
build
# Other
.metadata
*/bin/*
*/gen/*
================================================
FILE: Android/build.gradle
================================================
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.1.0'
classpath 'com.google.gms:google-services:1.3.0'
}
}
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
================================================
FILE: Android/gradle/wrapper/gradle-wrapper.properties
================================================
#Mon Feb 08 14:52:05 PST 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
================================================
FILE: Android/gradle.properties
================================================
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
================================================
FILE: Android/gradlew
================================================
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
================================================
FILE: Android/gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: Android/notifications/build.gradle
================================================
apply plugin: 'com.android.library'
group = 'com.facebook.android'
buildscript {
repositories {
mavenCentral()
}
}
android {
compileSdkVersion 23
buildToolsVersion "23.0.3"
defaultConfig {
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName project.version
}
}
configurations {
javadoc
}
dependencies {
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.2.1'
javadoc 'com.android.support:appcompat-v7:23.2.1'
}
apply plugin: 'maven'
apply plugin: 'signing'
def isSnapshot = version.endsWith('-SNAPSHOT')
def ossrhUsername = hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : System.getenv('CI_NEXUS_USERNAME')
def ossrhPassword = hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : System.getenv('CI_NEXUS_PASSWORD')
task setVersion {
// The version will be derived from source
project.version = null
def sdkVersionFile = file('src/main/java/com/facebook/notifications/NotificationsManager.java')
sdkVersionFile.eachLine{
def matcher = (it =~ /(?:.*LIBRARY_VERSION = \")(.*)(?:\".*)/)
if (matcher.matches()) {
project.version = matcher[0][1]
return
}
}
if (project.version.is('unspecified')) {
throw new GradleScriptException('Version could not be found.', null)
}
}
uploadArchives {
repositories.mavenDeployer {
beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
authentication(userName: ossrhUsername, password: ossrhPassword)
}
snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") {
authentication(userName: ossrhUsername, password: ossrhPassword)
}
pom.project {
name 'In-App Notifications Framework'
artifactId = 'notifications'
packaging 'aar'
description 'Facebook In-App Notifications Framework for Android'
url 'https://github.com/facebook/FBNotifications'
scm {
connection 'scm:git@github.com:facebook/FBNotifications.git'
developerConnection 'scm:git@github.com:facebook/FBNotifications.git'
url 'https://github.com/facebook/FBNotifications'
}
licenses {
license {
name 'Facebook Platform License'
url 'https://github.com/facebook/FBNotifications/blob/master/LICENSE'
distribution 'repo'
}
}
developers {
developer {
id 'facebook'
name 'Facebook'
}
}
}
}
}
uploadArchives.dependsOn(setVersion)
signing {
required { !isSnapshot && gradle.taskGraph.hasTask("uploadArchives") }
sign configurations.archives
}
task androidJavadocs(type: Javadoc) {
source = android.sourceSets.main.java.srcDirs
ext.androidJar = "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar"
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
classpath += configurations.javadoc
options.linksOffline("http://d.android.com/reference", "${android.sdkDirectory}/docs/reference")
// JDK 1.8 is more strict then 1.7. Have JDK 1.8 behave like 1.7 for javadoc generation
if (org.gradle.internal.jvm.Jvm.current().getJavaVersion() == JavaVersion.VERSION_1_8) {
options.addStringOption('Xdoclint:none', '-quiet')
}
exclude '**/internal/**'
}
task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
classifier = 'javadoc'
from androidJavadocs.destinationDir
}
task androidSourcesJar(type: Jar) {
classifier = 'sources'
from android.sourceSets.main.java.sourceFiles
}
artifacts {
archives androidSourcesJar
archives androidJavadocsJar
}
================================================
FILE: Android/notifications/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/richardross/Library/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
================================================
FILE: Android/notifications/src/main/AndroidManifest.xml
================================================
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/NotificationCardResult.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.Nullable;
public class NotificationCardResult implements Parcelable {
public static final Creator CREATOR = new Creator() {
@Override
public NotificationCardResult createFromParcel(Parcel source) {
return new NotificationCardResult(source);
}
@Override
public NotificationCardResult[] newArray(int size) {
return new NotificationCardResult[size];
}
};
private final @Nullable Uri actionUri;
public NotificationCardResult(@Nullable Uri actionUri) {
this.actionUri = actionUri;
}
private NotificationCardResult(Parcel parcel) {
this.actionUri = parcel.readParcelable(getClass().getClassLoader());
}
@Nullable
public Uri getActionUri() {
return actionUri;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(actionUri, flags);
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/NotificationsManager.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import com.facebook.notifications.internal.activity.CardActivity;
import com.facebook.notifications.internal.appevents.AppEventsLogger;
import com.facebook.notifications.internal.asset.Asset;
import com.facebook.notifications.internal.asset.AssetManager;
import com.facebook.notifications.internal.asset.handlers.BitmapAssetHandler;
import com.facebook.notifications.internal.asset.handlers.ColorAssetHandler;
import com.facebook.notifications.internal.asset.handlers.GifAssetHandler;
import com.facebook.notifications.internal.configuration.CardConfiguration;
import com.facebook.notifications.internal.content.ContentManager;
import com.facebook.notifications.internal.utilities.Version;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Manages incoming remote notifications for push card presentation.
*/
public final class NotificationsManager {
/**
* Represents handlers to be invoked when preparation of a card is completed.
*/
public interface PrepareCallback {
void onPrepared(@NonNull Intent presentationIntent);
void onError(@NonNull Exception exception);
}
/**
* Allows for customizing of notifications before they are displayed.
*/
public interface NotificationExtender {
Notification.Builder extendNotification(@NonNull Notification.Builder notification);
}
/**
* The request code to use for intents returned by {@link PrepareCallback}
*/
public static final int REQUEST_CODE = 0xCA4D; // CARD
/**
* The intent extra key to be used for pending intents used in notifications created by
* `presentNotification()`
*/
public static final String EXTRA_PAYLOAD_INTENT = "notification_push_payload_intent";
/**
* The highest supported payload version by this version of the Notifications SDK
*/
public static final String PAYLOAD_VERSION = "1.0";
/**
* The version of the In-App Notifications Library
*/
public static final String LIBRARY_VERSION = "1.0.2";
private static final @NonNull Version PAYLOAD_VERSION_OBJECT = new Version(1, 0, 0);
private static final String LOG_TAG = NotificationsManager.class.getCanonicalName();
private static final String NOTIFICATION_TAG = "fb_notification_tag";
private static final String PUSH_PAYLOAD_KEY = "fb_push_payload";
private static final String CARD_PAYLOAD_KEY = "fb_push_card";
private static final AssetManager ASSET_MANAGER = new AssetManager();
private static final ContentManager CONTENT_MANAGER = new ContentManager();
static {
ASSET_MANAGER.registerHandler(BitmapAssetHandler.TYPE, new BitmapAssetHandler());
ASSET_MANAGER.registerHandler(ColorAssetHandler.TYPE, new ColorAssetHandler());
ASSET_MANAGER.registerHandler(GifAssetHandler.TYPE, new GifAssetHandler());
}
private NotificationsManager() {
}
@Nullable
private static JSONObject getPushJSON(@NonNull Bundle bundle) throws JSONException {
String pushPayload = bundle.getString(PUSH_PAYLOAD_KEY);
if (pushPayload == null) {
return null;
}
return new JSONObject(pushPayload);
}
@Nullable
private static JSONObject getCardJSON(@NonNull Bundle bundle) throws JSONException {
String cardPayload = bundle.getString(CARD_PAYLOAD_KEY);
if (cardPayload == null) {
return null;
}
return new JSONObject(cardPayload);
}
@Nullable
private static Intent intentForBundle(
@NonNull Context context,
@Nullable JSONObject pushJSON,
@NonNull JSONObject cardJSON,
@NonNull AssetManager assetManager,
@NonNull ContentManager contentManager
) throws JSONException {
Version cardVersion = Version.parse(cardJSON.optString("version"));
if (cardVersion == null || cardVersion.compareTo(PAYLOAD_VERSION_OBJECT) > 0) {
return null;
}
Intent intent = new Intent(context, CardActivity.class);
if (pushJSON != null) {
String campaignIdentifier = AppEventsLogger.getCampaignIdentifier(pushJSON);
if (campaignIdentifier != null) {
intent.putExtra(CardActivity.EXTRA_CAMPAIGN_IDENTIFIER, campaignIdentifier);
}
}
intent.putExtra(CardActivity.EXTRA_ASSET_MANAGER, assetManager);
intent.putExtra(CardActivity.EXTRA_CONTENT_MANAGER, contentManager);
intent.putExtra(CardActivity.EXTRA_CARD_PAYLOAD, cardJSON.toString());
return intent;
}
@NonNull
private static AssetManager getAssetManager(@NonNull Context context) {
AssetManager manager = new AssetManager(ASSET_MANAGER);
manager.setContext(context);
return manager;
}
@NonNull
private static ContentManager getContentManager(@NonNull Context context) {
ContentManager manager = new ContentManager(CONTENT_MANAGER);
manager.setContext(context);
return manager;
}
/**
* Returns whether or not a notification bundle has a valid push payload.
*
* @param notificationBundle The bundle to check for a push payload
*/
public static boolean canPresentCard(@NonNull Bundle notificationBundle) {
return notificationBundle.containsKey(CARD_PAYLOAD_KEY);
}
/**
* Present an intent from a given activity to show the notification bundle contained in notificationBundle.
*
* @param activity The activity to present from
* @param notificationBundle The bundle containing the notification payload to present
* @return whether or not the activity could successfully be presented
*/
public static boolean presentCard(@NonNull Activity activity, @NonNull Bundle notificationBundle) {
try {
JSONObject cardJSON = getCardJSON(notificationBundle);
if (cardJSON == null) {
return false;
}
JSONObject pushJSON = getPushJSON(notificationBundle);
Intent presentationIntent = intentForBundle(activity,
pushJSON, cardJSON,
getAssetManager(activity), getContentManager(activity)
);
if (presentationIntent == null) {
return false;
}
activity.startActivityForResult(presentationIntent, REQUEST_CODE);
return true;
} catch (JSONException ex) {
Log.e(LOG_TAG, "Error while parsing JSON", ex);
return false;
}
}
/**
* Prepare and pre-load a notification bundle into memory.
*
* @param context The current context of your program. Usually an activity, application, or
* service context.
* @param notificationBundle The bundle containing the notification payload to present
* @param callback The callback to invoke once preparation is complete. This is guaranteed to be
* invoked on the same thread as this method is invoked from.
*/
public static void prepareCard(
@NonNull final Context context,
@NonNull final Bundle notificationBundle,
@NonNull final PrepareCallback callback
) {
final Handler handler = new Handler();
final AssetManager assetManager = getAssetManager(context);
final ContentManager contentManager = getContentManager(context);
// Cache and prepare in background.
new Thread() {
@Override
public void run() {
try {
JSONObject cardJSON = getCardJSON(notificationBundle);
if (cardJSON == null) {
throw new NullPointerException("No content present in the notification bundle.");
}
Version cardVersion = Version.parse(cardJSON.optString("version"));
if (cardVersion == null || cardVersion.compareTo(PAYLOAD_VERSION_OBJECT) > 0) {
throw new Exception("Payload version " + cardVersion + " not supported by this version of the notifications SDK.");
}
assetManager.cachePayload(cardJSON, new AssetManager.CacheCompletionCallback() {
@Override
public void onCacheCompleted(@NonNull JSONObject payload) {
assetManager.stopCaching();
try {
JSONObject cardJSON = getCardJSON(notificationBundle);
final Intent presentIntent = intentForBundle(context, getPushJSON(notificationBundle), cardJSON, assetManager, contentManager);
if (presentIntent == null) {
throw new NullPointerException("presentIntent was null, this should never happen!");
}
CardConfiguration configuration = new CardConfiguration(cardJSON, assetManager, contentManager);
presentIntent.putExtra(CardActivity.EXTRA_CONFIGURATION, configuration);
handler.post(new Runnable() {
@Override
public void run() {
callback.onPrepared(presentIntent);
}
});
} catch (final Exception ex) {
handler.post(new Runnable() {
@Override
public void run() {
callback.onError(ex);
}
});
}
}
});
} catch (final Exception ex) {
handler.post(new Runnable() {
@Override
public void run() {
callback.onError(ex);
}
});
}
}
}.start();
}
/**
* Handle the result of an activity started using
* {@code prepare(Context, Bundle, PrepareCallback)} or {@code present(Activity, Bundle)}.
*
* @param requestCode The request code used to start the activity
* @param resultCode The result code returned by the activity
* @param data The data returned by the activity
* @return The notification card result of the activity if it exists, or null if it does not.
*/
public static NotificationCardResult handleActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (requestCode != REQUEST_CODE) {
return null;
}
if (resultCode != Activity.RESULT_OK || data == null) {
return null;
}
return data.getParcelableExtra(CardActivity.EXTRA_NOTIFICATION_CARD_RESULT);
}
/**
* Present a {@link Notification} to be presented from a GCM push bundle.
*
* This does not present a notification immediately, instead it caches the assets from the
* notification bundle, and then presents a notification to the user. This allows for a smoother
* interaction without loading indicators for the user.
*
* Note that only one notification can be created for a specific push bundle, should you attempt
* to present a new notification with the same payload bundle as an existing notification, it will
* replace and update the old notification.
*
* @param context The context to send the notification from
* @param notificationBundle The content of the push notification
* @param launcherIntent The launcher intent that contains your Application's activity.
* This will be modified with the FLAG_ACTIVITY_CLEAR_TOP and
* FLAG_ACTIVITY_SINGLE_TOP flags, in order to properly show the
* notification in an already running application.
*
* Should you not want this behavior, you may use the notificationExtender
* parameter to customize the contentIntent of the notification before
* presenting it.
*/
public static boolean presentNotification(
@NonNull Context context,
@NonNull Bundle notificationBundle,
@NonNull Intent launcherIntent) {
return presentNotification(context, notificationBundle, launcherIntent, null);
}
/**
* Present a {@link Notification} to be presented from a GCM push bundle.
*
* This does not present a notification immediately, instead it caches the assets from the
* notification bundle, and then presents a notification to the user. This allows for a smoother
* interaction without loading indicators for the user.
*
* Note that only one notification can be created for a specific push bundle, should you attempt
* to present a new notification with the same payload bundle as an existing notification, it will
* replace and update the old notification.
*
* @param context The context to send the notification from
* @param notificationBundle The content of the push notification
* @param launcherIntent The launcher intent that contains your Application's activity.
* This will be modified with the FLAG_ACTIVITY_CLEAR_TOP and
* FLAG_ACTIVITY_SINGLE_TOP flags, in order to properly show the
* notification in an already running application.
*
* Should you not want this behavior, you may use the notificationExtender
* parameter to customize the contentIntent of the notification before
* presenting it.
* @param notificationExtender A nullable argument that allows you to customize the notification
* before displaying it. Use this to configure Icons, text, sounds,
* etc. before we pass the notification off to the OS.
*/
public static boolean presentNotification(
@NonNull final Context context,
@NonNull final Bundle notificationBundle,
@NonNull final Intent launcherIntent,
@Nullable final NotificationExtender notificationExtender) {
final JSONObject alert;
final int payloadHash;
try {
String payload = notificationBundle.getString(CARD_PAYLOAD_KEY);
if (payload == null) {
return false;
}
payloadHash = payload.hashCode();
JSONObject payloadObject = new JSONObject(payload);
alert = payloadObject.optJSONObject("alert") != null
? payloadObject.optJSONObject("alert")
: new JSONObject();
} catch (JSONException ex) {
Log.e(LOG_TAG, "Error while parsing notification bundle JSON", ex);
return false;
}
final boolean[] success = new boolean[1];
final Thread backgroundThread = new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
prepareCard(context, notificationBundle, new PrepareCallback() {
@Override
public void onPrepared(@NonNull Intent presentationIntent) {
Intent contentIntent = new Intent(launcherIntent);
contentIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
contentIntent.putExtra(EXTRA_PAYLOAD_INTENT, presentationIntent);
NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
Notification.Builder builder = new Notification.Builder(context)
.setSmallIcon(android.R.drawable.ic_dialog_alert)
.setContentTitle(alert.optString("title"))
.setContentText(alert.optString("body"))
.setAutoCancel(true)
.setContentIntent(
PendingIntent.getActivity(
context.getApplicationContext(),
payloadHash,
contentIntent,
PendingIntent.FLAG_ONE_SHOT
)
);
if (notificationExtender != null) {
builder = notificationExtender.extendNotification(builder);
}
manager.notify(NOTIFICATION_TAG, payloadHash, builder.getNotification());
success[0] = true;
Looper.myLooper().quit();
}
@Override
public void onError(@NonNull Exception exception) {
Log.e(LOG_TAG, "Error while preparing card", exception);
Looper.myLooper().quit();
}
});
Looper.loop();
}
});
backgroundThread.start();
try {
backgroundThread.join();
} catch (InterruptedException ex) {
Log.e(LOG_TAG, "Failed to wait for background thread", ex);
return false;
}
return success[0];
}
/**
* Present a card from the notification this activity
* was created from, if the notification exists.
*
* @param activity The activity to present from.
* @return Whether or not a card was presented.
*/
public static boolean presentCardFromNotification(@NonNull Activity activity) {
return presentCardFromNotification(activity, activity.getIntent());
}
/**
* Present a card from the notification this activity
* was relaunched from, if the notification exists.
*
* @param activity The activity to present from.
* @param intent Intent that was used to re-launch the activity.
* @return Whether or not a card was presented.
*/
public static boolean presentCardFromNotification(@NonNull Activity activity,
@NonNull Intent intent) {
Intent notificationIntent = intent.getParcelableExtra(EXTRA_PAYLOAD_INTENT);
if (notificationIntent == null) {
return false;
}
activity.startActivityForResult(notificationIntent, REQUEST_CODE);
return true;
}
/**
* Registers an Asset Handler for use.
*
* @param assetType The type of the asset to register for.
* @param assetHandler The asset handler to register.
*/
private static void registerAssetHandler(
@NonNull String assetType,
@NonNull AssetManager.AssetHandler extends Asset> assetHandler
) {
ASSET_MANAGER.registerHandler(assetType, assetHandler);
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/activity/CardActivity.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.activity;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.Gravity;
import android.widget.FrameLayout;
import android.widget.FrameLayout.LayoutParams;
import android.widget.ProgressBar;
import com.facebook.notifications.NotificationCardResult;
import com.facebook.notifications.internal.appevents.AppEventsLogger;
import com.facebook.notifications.internal.asset.AssetManager;
import com.facebook.notifications.internal.asset.handlers.ColorAssetHandler;
import com.facebook.notifications.internal.configuration.CardConfiguration;
import com.facebook.notifications.internal.content.ContentManager;
import com.facebook.notifications.internal.view.ActionButton;
import com.facebook.notifications.internal.view.ActionsView;
import com.facebook.notifications.internal.view.CardView;
import org.json.JSONException;
import org.json.JSONObject;
/**
* An activity which displays a push card.
*/
public class CardActivity extends Activity implements ActionsView.Delegate {
/**
* The intent extra key to be set with the JSON payload of your push card payload.
*/
public static final String EXTRA_CARD_PAYLOAD = "fb_push_card_payload";
/**
* If the configuration has already been cached and parsed, you may pass simply the entire
* configuration object to the activity. This is significantly faster than re-parsing the entire
* JSON object.
*/
public static final String EXTRA_CONFIGURATION = "fb_push_card_configuration";
/**
* The intent extra key to be set with the push campaign identifier.
*/
public static final String EXTRA_CAMPAIGN_IDENTIFIER = "fb_push_campaign";
/**
* The intent extra key to be set with an instance of {@link AssetManager}
*/
public static final String EXTRA_ASSET_MANAGER = "fb_push_card_asset_manager";
/**
* The intent extra key to be set with an instance of {@link ContentManager}
*/
public static final String EXTRA_CONTENT_MANAGER = "fb_push_card_content_manager";
/**
* The intent extra key to be set with the card result.
*/
public static final String EXTRA_NOTIFICATION_CARD_RESULT = "fb_notification_card_result";
private static final String LOG_TAG = CardActivity.class.getCanonicalName();
private @Nullable String campaignIdentifier;
private @Nullable JSONObject configurationPayload;
private @NonNull AssetManager assetManager;
private @NonNull ContentManager contentManager;
private @NonNull AppEventsLogger appEventsLogger;
private @Nullable CardView cardView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
appEventsLogger = new AppEventsLogger(this);
Intent intent = getIntent();
campaignIdentifier = intent.getStringExtra(EXTRA_CAMPAIGN_IDENTIFIER);
String payloadString = intent.getStringExtra(EXTRA_CARD_PAYLOAD);
CardConfiguration configuration = intent.getParcelableExtra(EXTRA_CONFIGURATION);
AssetManager assetManager = intent.getParcelableExtra(EXTRA_ASSET_MANAGER);
ContentManager contentManager = intent.getParcelableExtra(EXTRA_CONTENT_MANAGER);
assetManager = assetManager != null ? assetManager : new AssetManager();
contentManager = contentManager != null ? contentManager : new ContentManager();
assetManager.setContext(this);
contentManager.setContext(this);
this.assetManager = assetManager;
this.contentManager = contentManager;
try {
configurationPayload = new JSONObject(payloadString);
} catch (JSONException ex) {
Log.e(LOG_TAG, "Error parsing JSON payload", ex);
}
if (configuration == null) {
beginLoadingContent();
} else {
displayConfiguration(configuration);
}
appEventsLogger.logPushOpen(campaignIdentifier);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (assetManager == null) {
return;
}
assetManager.stopCaching();
if (configurationPayload == null) {
return;
}
assetManager.clearCache(configurationPayload);
}
private void beginLoadingContent() {
if (assetManager == null || contentManager == null) {
Log.e(LOG_TAG, "Asset & content manager should be available!");
return;
}
if (configurationPayload == null) {
Log.e(LOG_TAG, "No card payload is available!");
return;
}
ProgressBar loadingView = new ProgressBar(this);
loadingView.setIndeterminate(true);
int backgroundColor = ColorAssetHandler.fromRGBAHex(configurationPayload.optString("backdropColor"));
FrameLayout loadingViewFrame = new FrameLayout(this);
loadingViewFrame.setBackgroundColor(backgroundColor);
LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
layoutParams.gravity = Gravity.CENTER;
loadingViewFrame.addView(loadingView, layoutParams);
setContentView(loadingViewFrame);
final Handler handler = new Handler();
if (configurationPayload == null) {
return;
}
assetManager.cachePayload(configurationPayload, new AssetManager.CacheCompletionCallback() {
@Override
public void onCacheCompleted(@NonNull JSONObject payload) {
final CardConfiguration configuration;
try {
configuration = new CardConfiguration(configurationPayload, assetManager, contentManager);
} catch (JSONException ex) {
Log.e(LOG_TAG, "Error while parsing JSON", ex);
return;
}
handler.post(new Runnable() {
@Override
public void run() {
displayConfiguration(configuration);
}
});
}
});
}
private void displayConfiguration(CardConfiguration configuration) {
if (assetManager == null || contentManager == null) {
Log.e(LOG_TAG, "Asset or content manager has unloaded since beginLoadingContent()!");
return;
}
cardView = new CardView(this, assetManager, contentManager, this, configuration);
setContentView(cardView);
}
@Override
public void onBackPressed() {
appEventsLogger.logButtonAction(ActionButton.Type.Dismiss, campaignIdentifier);
Intent resultIntent = new Intent();
resultIntent.putExtra(EXTRA_NOTIFICATION_CARD_RESULT, new NotificationCardResult(null));
setResult(RESULT_OK, resultIntent);
finish();
}
@Override
public void actionButtonClicked(ActionButton.Type type, @Nullable Uri actionUri) {
appEventsLogger.logButtonAction(type, campaignIdentifier);
Intent resultIntent = new Intent();
resultIntent.putExtra(EXTRA_NOTIFICATION_CARD_RESULT, new NotificationCardResult(actionUri));
setResult(RESULT_OK, resultIntent);
finish();
if (actionUri == null) {
return;
}
Intent actionIntent = new Intent(Intent.ACTION_VIEW, actionUri);
startActivity(actionIntent);
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/appevents/AppEventsLogger.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.appevents;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import com.facebook.notifications.internal.view.ActionButton;
import org.json.JSONObject;
import java.lang.reflect.Method;
/**
* Simple class to call into core Facebook SDK and create/invoke the logger from there to report
* App Events that have been triggered by a push card.
*/
public class AppEventsLogger {
private static final String LOG_TAG = AppEventsLogger.class.getCanonicalName();
private static final String PUSH_OPEN_EVENT = "fb_mobile_push_opened";
private static final String PUSH_CAMPAIGN_KEY = "fb_push_campaign";
private @Nullable Object fbSDKLogger;
private @Nullable Method fbSDKLogMethod;
public AppEventsLogger(@NonNull Context context) {
try {
Class> loggerClass = Class.forName("com.facebook.appevents.AppEventsLogger");
Method instantiateMethod = loggerClass.getMethod("newLogger", Context.class);
fbSDKLogMethod = loggerClass.getMethod("logEvent", String.class, Bundle.class);
fbSDKLogger = instantiateMethod.invoke(null, context);
} catch (Exception ex) {
Log.w(
LOG_TAG,
"Failed to initialize AppEventsLogger. " +
"Did you forget to include the Facebook SDK in your application?",
ex
);
}
}
@Nullable
public static String getCampaignIdentifier(@NonNull JSONObject payload) {
return payload.optString("campaign", null);
}
@NonNull
private static String getAppEventName(ActionButton.Type action) {
switch (action) {
case Primary:
return "fb_mobile_push_card_action_primary";
case Secondary:
return "fb_mobile_push_card_action_secondary";
case Dismiss:
return "fb_mobile_push_card_action_dismiss";
default:
throw new RuntimeException("Unknown action type: " + action);
}
}
public void logPushOpen(@Nullable String campaignIdentifier) {
logEvent(PUSH_OPEN_EVENT, campaignIdentifier);
}
public void logButtonAction(ActionButton.Type action, @Nullable String campaignIdentifier) {
logEvent(getAppEventName(action), campaignIdentifier);
}
private void logEvent(@NonNull String eventName, @Nullable String campaignIdentifier) {
if (campaignIdentifier == null || campaignIdentifier.equals("") ||
fbSDKLogger == null || fbSDKLogMethod == null) {
return;
}
Bundle parameters = new Bundle();
parameters.putString(PUSH_CAMPAIGN_KEY, campaignIdentifier);
try {
fbSDKLogMethod.invoke(fbSDKLogger, eventName, parameters);
} catch (Exception ex) {
Log.w(
LOG_TAG,
"Failed to invoke AppEventsLogger." +
"Did you forget to include the Facebook SDK in your application?",
ex
);
}
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/asset/Asset.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.asset;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import com.facebook.notifications.internal.utilities.InvalidParcelException;
/**
* A 'marker' interface used for identifying an object as a resource. On its own, doesn't do much.
*/
public interface Asset extends Parcelable {
/**
* Get the type of this Asset. Used by {@link AssetManager} to find the proper
* {@link AssetManager.AssetHandler} for this asset.
*
* @return The asset type of the asset.
*/
@NonNull
String getType();
/**
* Validate that all of the content for this asset has been properly cached and prepared.
*
* @throws InvalidParcelException
*/
void validate() throws InvalidParcelException;
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/asset/AssetManager.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.asset;
import android.content.Context;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.View;
import com.facebook.notifications.internal.asset.cache.ContentCache;
import com.facebook.notifications.internal.utilities.InvalidParcelException;
import com.facebook.notifications.internal.utilities.JSONObjectVisitor;
import org.json.JSONObject;
import java.io.File;
import java.net.URL;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* Creates different Asset types based on the contents of a JSON payload
*/
public class AssetManager implements Parcelable {
/**
* Allows for the fetching of previously-fetched content from an on-disk cache.
*/
public interface AssetCache {
/**
* Gets the on-disk file for a previously cached URL.
*
* @param url The url to fetch from the cache
* @return The file which contains the contents of the url, or null if the URL was not
* previously cached, or if caching failed
*/
File getCachedFile(URL url);
}
/**
* An interface that allows different types of {@link Asset} to be displayed and used by the push
* card.
*
* This must be either {@link Parcelable} or constructable with a default constructor.
*/
public interface AssetHandler {
/**
* Invoked by {@link AssetManager} when an asset is being cached
*
* @param payload The payload to cache
* @return A set of URLs that contains the content to download into the asset cache, or null if
* no URLs exist to cache.
*/
@Nullable
Set getCacheURLs(@NonNull JSONObject payload);
/**
* Invoked by {@link AssetManager} when an asset should be inflated from a JSON payload
* All of the URLs returned by `getCacheURLs()` are guaranteed to have been fully downloaded
*
* @param payload The payload to inflate from
* @param cache The cache which contains the URLs that have been requested to be downloaded
* @return The fully inflated asset, or null if inflation fails
*/
@Nullable
AssetType createAsset(@NonNull JSONObject payload, @NonNull AssetCache cache);
/**
* Invoked by {@link AssetManager} when an asset from this handler has been requested to be
* displayed
*
* @param asset The asset to create a view for
* @param context The context to create a view for
* @return A new view
*/
@NonNull
View createView(@NonNull AssetType asset, @NonNull Context context);
}
/**
* An interface for receiving a callback when the caching of a set of resources has completed.
*/
public interface CacheCompletionCallback {
/**
* Invoked by the {@link AssetManager} whenever caching has finished.
*
* @param payload The payload which has been cached.
*/
void onCacheCompleted(@NonNull JSONObject payload);
}
public static final Creator CREATOR = new Creator() {
@Override
public AssetManager createFromParcel(Parcel source) {
try {
AssetManager assetManager = new AssetManager(source);
assetManager.validate();
return assetManager;
} catch (InvalidParcelException ex) {
Log.w(LOG_TAG, "Failed to decode asset manager", ex);
return null;
}
}
@Override
public AssetManager[] newArray(int size) {
return new AssetManager[size];
}
};
private static final String LOG_TAG = AssetManager.class.getCanonicalName();
private @Nullable ContentCache contentCache;
private final @NonNull Map registeredHandlers;
public AssetManager() {
registeredHandlers = new ConcurrentHashMap<>();
}
/**
* Creates a new AssetManager from another manager's handlers, but without a context set.
* @param other The asset manager to clone
*/
public AssetManager(AssetManager other) {
registeredHandlers = new ConcurrentHashMap<>(other.registeredHandlers);
}
public AssetManager(@NonNull Parcel parcel) {
registeredHandlers = new ConcurrentHashMap<>();
Bundle handlersBundle = parcel.readBundle(getClass().getClassLoader());
for (String type : handlersBundle.keySet()) {
registeredHandlers.put(type, (ParcelableAssetHandler)handlersBundle.getParcelable(type));
}
}
public void setContext(@NonNull Context context) {
if (contentCache != null) {
throw new UnsupportedOperationException("Can only call setContext() once on an AssetManager!");
}
contentCache = new ContentCache(context);
}
private void validate() throws InvalidParcelException {
for (ParcelableAssetHandler handler : registeredHandlers.values()) {
handler.validate();
}
}
public void registerHandler(@NonNull String assetType, @NonNull AssetHandler extends Asset> handler) {
registeredHandlers.put(assetType, new ParcelableAssetHandler(handler));
}
@Nullable
public Asset inflateAsset(@Nullable JSONObject payload) {
if (contentCache == null) {
throw new UnsupportedOperationException("Cannot call inflateAsset() before setContext() has been called!");
}
if (payload == null) {
return null;
}
String type = payload.optString("_type");
AssetHandler extends Asset> handler = registeredHandlers.get(type);
if (handler == null) {
return null;
}
return handler.createAsset(payload, contentCache);
}
@NonNull
public View inflateView(@NonNull AssetType asset, @NonNull Context context) {
String type = asset.getType();
@SuppressWarnings("unchecked")
AssetHandler handler = (AssetHandler) registeredHandlers.get(type);
if (handler == null) {
throw new IllegalArgumentException("Asset type \"" + type + "\" not registered!");
}
return handler.createView(asset, context);
}
/**
* Caches a given JSON payload in the background.
*
* @param payload The payload to cache
* @param callback The callback to be invoked when caching completes.
*/
public void cachePayload(final @NonNull JSONObject payload, final @NonNull CacheCompletionCallback callback) {
if (contentCache == null) {
throw new UnsupportedOperationException("Cannot call cachePayload() before setContext() has been called!");
}
contentCache.cache(getCacheURLs(payload), new ContentCache.CompletionCallback() {
@Override
public void onCacheCompleted(@NonNull Set urlsToCache) {
callback.onCacheCompleted(payload);
}
});
}
/**
* Clears the cache for a given JSON payload.
*
* @param payload The payload to clear the cache for.
*/
public void clearCache(@NonNull JSONObject payload) {
if (contentCache == null) {
throw new UnsupportedOperationException("Cannot call cachePayload() before setContext() has been called!");
}
contentCache.clear(getCacheURLs(payload));
}
/**
* Stops any caching that may be occurring in the background.
*/
public void stopCaching() {
if (contentCache == null) {
throw new UnsupportedOperationException("Cannot call stopCaching() before setContext() has been called!");
}
contentCache.stop();
}
@NonNull
private Set getCacheURLs(@Nullable JSONObject payload) {
if (contentCache == null) {
throw new UnsupportedOperationException("Cannot call stopCaching() before setContext() has been called!");
}
if (payload == null) {
return new HashSet<>();
}
final Set cacheURLs = new HashSet<>();
JSONObjectVisitor.walk(payload, new JSONObjectVisitor() {
@Override
protected void visit(JSONObject object) {
super.visit(object);
String type = object.optString("_type");
AssetHandler extends Asset> handler = registeredHandlers.get(type);
if (handler == null) {
return;
}
Set newURLs = handler.getCacheURLs(object);
if (newURLs == null) {
return;
}
cacheURLs.addAll(newURLs);
}
});
return cacheURLs;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
Bundle bundle = new Bundle();
for (Map.Entry entry : registeredHandlers.entrySet()) {
bundle.putParcelable(entry.getKey(), entry.getValue());
}
dest.writeBundle(bundle);
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/asset/ParcelableAssetHandler.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.asset;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.View;
import com.facebook.notifications.internal.utilities.InvalidParcelException;
import org.json.JSONObject;
import java.net.URL;
import java.util.Set;
/**
* Represents a parcelable entry in {@link AssetManager}'s handler list.
*/
class ParcelableAssetHandler implements AssetManager.AssetHandler, Parcelable {
public static final Creator CREATOR = new Creator() {
@Override
public ParcelableAssetHandler createFromParcel(Parcel source) {
return new ParcelableAssetHandler(source);
}
@Override
public ParcelableAssetHandler[] newArray(int size) {
return new ParcelableAssetHandler[size];
}
};
private final @Nullable AssetManager.AssetHandler handler;
private final @Nullable InvalidParcelException exception;
@SuppressWarnings("unchecked")
public ParcelableAssetHandler(@NonNull AssetManager.AssetHandler extends Asset> handler) {
this.handler = (AssetManager.AssetHandler) handler;
this.exception = null;
}
@SuppressWarnings("unchecked")
public ParcelableAssetHandler(Parcel source) {
boolean isParcelable = source.readInt() != 0;
if (isParcelable) {
handler = source.readParcelable(getClass().getClassLoader());
exception = null;
} else {
AssetManager.AssetHandler handler = null;
InvalidParcelException exception = null;
try {
Class assetHandlerClass = (Class) Class.forName(source.readString(), true, getClass().getClassLoader());
handler = (AssetManager.AssetHandler) assetHandlerClass.newInstance();
} catch (Exception ex) {
exception = new InvalidParcelException(ex);
}
this.handler = handler;
this.exception = exception;
}
}
public void validate() throws InvalidParcelException {
if (exception != null) {
throw exception;
}
if (handler == null) {
throw new IllegalStateException("AssetHandler should not be null when parceling if no exception was thrown!");
}
}
@Nullable
@Override
public Set getCacheURLs(@NonNull JSONObject payload) {
if (handler == null) {
throw new IllegalStateException("AssetHandler should not be null, did you forget to call validate()?");
}
return handler.getCacheURLs(payload);
}
@Nullable
@Override
public Asset createAsset(@NonNull JSONObject payload, @NonNull AssetManager.AssetCache cache) {
if (handler == null) {
throw new IllegalStateException("AssetHandler should not be null, did you forget to call validate()?");
}
return handler.createAsset(payload, cache);
}
@NonNull
@Override
public View createView(@NonNull Asset asset, @NonNull Context context) {
if (handler == null) {
throw new IllegalStateException("AssetHandler should not be null, did you forget to call validate()?");
}
return handler.createView(asset, context);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
if (handler == null) {
throw new IllegalStateException("AssetHandler should not be null, did you forget to call validate()?");
}
if (handler instanceof Parcelable) {
dest.writeInt(1);
dest.writeParcelable((Parcelable)handler, flags);
} else {
dest.writeInt(0);
dest.writeString(handler.getClass().getName());
}
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/asset/cache/CacheOperation.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.asset.cache;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.io.File;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;
class CacheOperation implements ContentDownloader.DownloadCallback {
private final @NonNull Object mutex;
private final @NonNull Set urlsToCache;
private final @NonNull Set remainingURLs;
private final @NonNull ContentCache.CompletionCallback completion;
public CacheOperation(@NonNull Set urlsToCache, @NonNull ContentCache.CompletionCallback completion) {
this.mutex = new Object();
this.urlsToCache = urlsToCache;
this.remainingURLs = new HashSet<>(urlsToCache);
this.completion = completion;
}
@NonNull
public Set getUrlsToCache() {
return urlsToCache;
}
@NonNull
public ContentCache.CompletionCallback getCompletion() {
return completion;
}
@Override
public void onResourceDownloaded(@NonNull URL url, @Nullable File targetFile) {
boolean invoke;
synchronized (mutex) {
remainingURLs.remove(url);
invoke = remainingURLs.size() == 0;
}
if (invoke) {
completion.onCacheCompleted(urlsToCache);
}
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/asset/cache/ContentCache.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.asset.cache;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.facebook.notifications.internal.asset.Asset;
import com.facebook.notifications.internal.asset.AssetManager;
import java.io.File;
import java.net.URL;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* Manages the caching of an {@link Asset}'s resources
* from the internet
*/
public class ContentCache implements AssetManager.AssetCache {
public interface CompletionCallback {
void onCacheCompleted(@NonNull Set urlsToCache);
}
private static final String LOG_TAG = ContentCache.class.getCanonicalName();
private final @NonNull Context context;
private final @NonNull ContentDownloader downloader;
private final @NonNull Thread downloadThread;
private final @NonNull DiskCache diskCache;
private final @NonNull Object synchronizationMutex;
private final @NonNull Map> cacheOperations;
private @Nullable Set cachedKeys;
public ContentCache(@NonNull Context context) {
this.context = context;
downloader = new ContentDownloader();
diskCache = new DiskCache(context);
downloadThread = new Thread(downloader);
synchronizationMutex = new Object();
cacheOperations = new HashMap<>();
downloadThread.start();
}
@NonNull
private static String getCacheKey(@NonNull URL url) {
String urlString = url.toString();
try {
MessageDigest MD5 = MessageDigest.getInstance("MD5");
Charset UTF8 = Charset.forName("UTF-8");
byte[] toDigest = urlString.getBytes(UTF8);
byte[] digested = MD5.digest(toDigest);
return String.format(
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
digested[0], digested[1], digested[2], digested[3], digested[4], digested[5], digested[6],
digested[7], digested[8], digested[9], digested[10], digested[11], digested[12],
digested[13], digested[14], digested[15]);
} catch (NoSuchAlgorithmException ex) {
// If for some ungodly reason MD5 doesn't exist in this JVM, use the string's built-in hash
// code.
return Integer.toHexString(urlString.hashCode());
}
}
public void stop() {
downloadThread.interrupt();
try {
downloadThread.join();
} catch (InterruptedException ex) {
// If we were interrupted while waiting to have our children die, re-interrupt the
// child thread and hope for the best.
downloadThread.interrupt();
}
}
@NonNull
public Context getContext() {
return context;
}
public void cache(@NonNull Set urlsToCache, @NonNull CompletionCallback completion) {
CacheOperation operation = new CacheOperation(urlsToCache, completion);
synchronized (synchronizationMutex) {
int scheduledCount = 0;
for (Iterator urlIterator = operation.getUrlsToCache().iterator(); urlIterator.hasNext(); ) {
URL url = urlIterator.next();
final String hashKey = getCacheKey(url);
if (hasCachedData(hashKey)) {
urlIterator.remove();
continue;
}
if (cacheOperations.containsKey(hashKey)) {
Set existingOperations = cacheOperations.get(hashKey);
existingOperations.add(operation);
continue;
}
final Set newOperations = new HashSet<>();
newOperations.add(operation);
cacheOperations.put(hashKey, newOperations);
downloader.downloadAsync(url, diskCache.fetch(getCacheKey(url)), new ContentDownloader.DownloadCallback() {
@Override
public void onResourceDownloaded(@NonNull URL url, @Nullable File file) {
Set operations;
synchronized (synchronizationMutex) {
operations = new HashSet<>(newOperations);
}
for (CacheOperation operation : operations) {
operation.onResourceDownloaded(url, file);
}
}
});
scheduledCount++;
}
if (scheduledCount == 0) {
completion.onCacheCompleted(urlsToCache);
}
}
}
public void clear(@NonNull Set urlsToClear) {
synchronized (synchronizationMutex) {
for (URL url : urlsToClear) {
String cacheKey = getCacheKey(url);
diskCache.remove(cacheKey);
if (cachedKeys != null) {
cachedKeys.remove(cacheKey);
}
}
}
}
@Nullable
@Override
public File getCachedFile(@NonNull URL contentURL) {
File file = diskCache.fetch(getCacheKey(contentURL));
if (!file.exists()) {
return null;
}
return file;
}
private boolean hasCachedData(@NonNull String cacheKey) {
synchronized (synchronizationMutex) {
if (cachedKeys == null) {
cachedKeys = diskCache.getCacheKeys();
}
return cachedKeys.contains(cacheKey);
}
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/asset/cache/ContentDownloader.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.asset.cache;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
class ContentDownloader implements Runnable {
public interface DownloadCallback {
void onResourceDownloaded(@NonNull URL url, @Nullable File file);
}
private interface DownloadOperation extends Runnable {
}
private static final String LOG_TAG = ContentDownloader.class.getCanonicalName();
private final @NonNull BlockingQueue operations;
public ContentDownloader() {
operations = new LinkedBlockingQueue<>();
}
@Override
public void run() {
while (true) {
try {
DownloadOperation operation = operations.take();
operation.run();
} catch (InterruptedException ex) {
// This means our parent thread requested us killed, RIP.
return;
}
}
}
public void downloadAsync(final @NonNull URL url, final @NonNull File targetFile, final @NonNull DownloadCallback callback) {
operations.offer(new DownloadOperation() {
private File download(@NonNull URL url) {
HttpURLConnection connection = null;
InputStream inputStream = null;
OutputStream outputStream = null;
try {
connection = (HttpURLConnection) url.openConnection();
if (connection.getResponseCode() != 200) {
return null;
}
inputStream = connection.getInputStream();
outputStream = new FileOutputStream(targetFile);
byte[] buffer = new byte[4096];
while (true) {
int read = inputStream.read(buffer);
if (read == -1) {
break;
}
outputStream.write(buffer, 0, read);
}
return targetFile;
} catch (Exception ex) {
Log.e(LOG_TAG, "Failed to download content for url " + url, ex);
return null;
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
} catch (IOException ex) {
Log.e(LOG_TAG, "Failed to close streams", ex);
}
if (connection != null) {
connection.disconnect();
}
}
}
@Override
public void run() {
callback.onResourceDownloaded(url, download(url));
}
});
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/asset/cache/DiskCache.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.asset.cache;
import android.content.Context;
import android.support.annotation.NonNull;
import android.util.Log;
import java.io.File;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
class DiskCache {
private static final String LOG_TAG = DiskCache.class.getCanonicalName();
private final @NonNull File cacheDirectory;
public DiskCache(@NonNull Context context) {
cacheDirectory = context.getCacheDir();
}
public void remove(final @NonNull String key) {
File diskFile = new File(cacheDirectory, key);
if (!diskFile.delete()) {
Log.w(LOG_TAG, "Failed to delete cache file \"" + diskFile.getAbsolutePath() + "\"");
}
}
@NonNull
public File fetch(@NonNull String key) {
return new File(cacheDirectory, key);
}
@NonNull
public Set getCacheKeys() {
String[] files = cacheDirectory.list();
Set keys = new HashSet<>(files.length);
Collections.addAll(keys, files);
return keys;
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/asset/handlers/BitmapAssetHandler.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.asset.handlers;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Parcel;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import com.facebook.notifications.internal.asset.Asset;
import com.facebook.notifications.internal.asset.AssetManager;
import com.facebook.notifications.internal.utilities.InvalidParcelException;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;
/**
* Handles assets of the bitmap type
*/
public class BitmapAssetHandler implements AssetManager.AssetHandler {
/**
* A resource implementation for Bitmaps read from disk
*/
static class BitmapAsset implements Asset {
public static final Creator CREATOR = new Creator() {
@Override
public BitmapAsset createFromParcel(Parcel source) {
return new BitmapAsset(source);
}
@Override
public BitmapAsset[] newArray(int size) {
return new BitmapAsset[size];
}
};
private final @NonNull File createdFrom;
private transient @Nullable Bitmap bitmap;
private BitmapAsset(@NonNull File createdFrom) {
this.createdFrom = createdFrom;
}
private BitmapAsset(@NonNull Parcel parcel) {
createdFrom = new File(parcel.readString());
}
@Nullable
private static Bitmap decodeBitmap(@NonNull File file) {
try {
// NOTE: We must be careful when decoding images on android. If a malicious push sends down
// a payload image that is too large for us to reasonably decode, we must ensure that we can
// safely fall back to a lower resolution if we don't have the memory for it.
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 1;
while (true) {
InputStream cachedData = new FileInputStream(file);
try {
return BitmapFactory.decodeStream(cachedData, null, options);
} catch (OutOfMemoryError ex) {
// Ignore out of memory, try loading with less requested resolution.
System.gc();
options.inSampleSize *= 2;
} finally {
cachedData.close();
}
}
} catch (IOException ex) {
Log.e(LOG_TAG, "IO Exception!", ex);
return null;
}
}
@NonNull
public File getCreatedFrom() {
return createdFrom;
}
@NonNull
public Bitmap getBitmap() {
if (bitmap == null) {
bitmap = decodeBitmap(createdFrom);
if (bitmap == null) {
throw new RuntimeException("Failed to decode bitmap from file");
}
}
return bitmap;
}
@NonNull
@Override
public String getType() {
return TYPE;
}
@Override
public void validate() throws InvalidParcelException {
if (!createdFrom.exists()) {
throw new InvalidParcelException(
new FileNotFoundException(
"Bitmap cache file does not exist: " + createdFrom.getAbsolutePath()
)
);
}
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(createdFrom.getAbsolutePath());
}
}
public static final String TYPE = "Image";
private static final String LOG_TAG = BitmapAssetHandler.class.getCanonicalName();
@Nullable
@Override
public Set getCacheURLs(@NonNull JSONObject payload) {
try {
URL url = new URL(payload.getString("url"));
Set set = new HashSet<>();
set.add(url);
return set;
} catch (MalformedURLException ex) {
return null;
} catch (JSONException ex) {
return null;
}
}
@Nullable
@Override
public BitmapAsset createAsset(@NonNull JSONObject payload, @NonNull AssetManager.AssetCache cache) {
try {
URL url = new URL(payload.getString("url"));
File cacheFile = cache.getCachedFile(url);
if (cacheFile == null) {
return null;
}
return new BitmapAsset(cacheFile);
} catch (MalformedURLException ex) {
Log.e(LOG_TAG, "JSON key 'url' was not a valid URL", ex);
return null;
} catch (JSONException ex) {
Log.e(LOG_TAG, "JSON exception", ex);
return null;
}
}
@NonNull
@Override
public View createView(@NonNull BitmapAsset asset, @NonNull Context context) {
ImageView imageView = new ImageView(context);
imageView.setImageBitmap(asset.getBitmap());
imageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
imageView.setAdjustViewBounds(true);
return imageView;
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/asset/handlers/ColorAssetHandler.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.asset.handlers;
import android.content.Context;
import android.os.Parcel;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.View;
import android.widget.FrameLayout;
import com.facebook.notifications.internal.asset.Asset;
import com.facebook.notifications.internal.asset.AssetManager;
import com.facebook.notifications.internal.utilities.InvalidParcelException;
import org.json.JSONException;
import org.json.JSONObject;
import java.net.URL;
import java.util.Set;
/**
* Handles assets of Color type.
*/
public class ColorAssetHandler implements AssetManager.AssetHandler {
/**
* An asset implementation for a single static color
*/
static class ColorAsset implements Asset {
public static final Creator CREATOR = new Creator() {
@Override
public ColorAsset createFromParcel(Parcel source) {
return new ColorAsset(source);
}
@Override
public ColorAsset[] newArray(int size) {
return new ColorAsset[size];
}
};
private final int color;
private ColorAsset(int color) {
this.color = color;
}
private ColorAsset(@NonNull Parcel parcel) {
color = parcel.readInt();
}
public int getColor() {
return color;
}
@NonNull
@Override
public String getType() {
return TYPE;
}
@Override
public void validate() throws InvalidParcelException {
// Nothing to validate
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(color);
}
}
@SuppressWarnings("ViewConstructor")
private static class ColorView extends FrameLayout {
public ColorView(Context context, int color) {
super(context);
setBackgroundColor(color);
}
}
public static final String TYPE = "Color";
private static final String LOG_TAG = ColorAssetHandler.class.getCanonicalName();
/**
* Parses a string in the format '#RRGGBBAA', and returns it as an android-compatible color
*
* @param input The string to parse
* @return The color, in an android-compatible format
*/
public static int fromRGBAHex(@Nullable String input) {
if (input == null || input.equals("")) {
return 0;
}
input = input.substring(1);
try {
long value = Long.parseLong(input, 16);
// Android color has alpha first, not last. Flip the bytes around.
return (int) ((value >> 8) | ((value & 0xFF) << 24));
} catch (NumberFormatException ex) {
return 0;
}
}
@Nullable
@Override
public Set getCacheURLs(@NonNull JSONObject payload) {
return null;
}
@Nullable
@Override
public ColorAsset createAsset(@NonNull JSONObject payload, @NonNull AssetManager.AssetCache cache) {
try {
return new ColorAsset(fromRGBAHex(payload.getString("rgbaHex")));
} catch (JSONException ex) {
Log.e(LOG_TAG, "JSON Exception", ex);
return null;
}
}
@NonNull
@Override
public View createView(@NonNull ColorAsset asset, @NonNull Context context) {
return new ColorView(context, asset.getColor());
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/asset/handlers/GifAssetHandler.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.asset.handlers;
import android.content.Context;
import android.os.Parcel;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.View;
import com.facebook.notifications.internal.asset.Asset;
import com.facebook.notifications.internal.asset.AssetManager;
import com.facebook.notifications.internal.utilities.GifDecoder;
import com.facebook.notifications.internal.utilities.InvalidParcelException;
import com.facebook.notifications.internal.view.GifView;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;
/**
* Handles assets of the GIF type.
*/
public class GifAssetHandler implements AssetManager.AssetHandler {
/**
* A resource implementation for GIFs read from disk
*/
static class GifAsset implements Asset {
public static final Creator CREATOR = new Creator() {
@Override
public GifAsset createFromParcel(Parcel source) {
return new GifAsset(source);
}
@Override
public GifAsset[] newArray(int size) {
return new GifAsset[size];
}
};
private final @NonNull File createdFrom;
private transient @Nullable GifDecoder decoder;
private GifAsset(@NonNull File createdFrom) {
this.createdFrom = createdFrom;
}
private GifAsset(@NonNull Parcel source) {
createdFrom = new File(source.readString());
}
@Nullable
private static GifDecoder decodeGif(@NonNull File file) {
long fileLength = file.length();
if (fileLength == 0 || fileLength > Integer.MAX_VALUE) {
return null;
}
byte[] bytes = new byte[(int) fileLength];
try {
FileInputStream fileInputStream = new FileInputStream(file);
int fileOffset = 0;
try {
while (true) {
int remaining = (bytes.length - fileOffset);
if (remaining == 0) {
break;
}
int read = fileInputStream.read(bytes, fileOffset, remaining);
if (read == -1) {
throw new IOException("File was shorter than expected!");
}
fileOffset += read;
}
} finally {
fileInputStream.close();
}
} catch (IOException ex) {
Log.e(LOG_TAG, "IO Exception while reading GIF data", ex);
return null;
}
GifDecoder decoder = new GifDecoder();
decoder.read(bytes);
return decoder;
}
@NonNull
public File getCreatedFrom() {
return createdFrom;
}
@NonNull
public GifDecoder getDecoder() {
if (decoder == null) {
decoder = decodeGif(createdFrom);
if (decoder == null) {
throw new RuntimeException("Failed to decode GIF");
}
}
return decoder;
}
@NonNull
@Override
public String getType() {
return TYPE;
}
@Override
public void validate() throws InvalidParcelException {
if (!createdFrom.exists()) {
throw new InvalidParcelException(
new FileNotFoundException(
"GIF cache file does not exist: " + createdFrom.getAbsolutePath()
)
);
}
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(createdFrom.getAbsolutePath());
}
}
public static final String TYPE = "GIF";
private static final String LOG_TAG = GifAssetHandler.class.getCanonicalName();
@Nullable
@Override
public Set getCacheURLs(@NonNull JSONObject payload) {
try {
URL url = new URL(payload.getString("url"));
Set set = new HashSet<>();
set.add(url);
return set;
} catch (MalformedURLException ex) {
return null;
} catch (JSONException ex) {
return null;
}
}
@Nullable
@Override
public GifAsset createAsset(@NonNull JSONObject payload, @NonNull AssetManager.AssetCache cache) {
try {
URL url = new URL(payload.getString("url"));
File cacheFile = cache.getCachedFile(url);
if (cacheFile == null) {
return null;
}
return new GifAsset(cacheFile);
} catch (MalformedURLException ex) {
Log.e(LOG_TAG, "JSON key 'url' was not a valid URL.", ex);
return null;
} catch (JSONException ex) {
Log.e(LOG_TAG, "JSON exception", ex);
return null;
}
}
@NonNull
@Override
public View createView(@NonNull GifAsset asset, @NonNull Context context) {
return new GifView(context, asset.getDecoder());
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/configuration/ActionConfiguration.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.configuration;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.Nullable;
import com.facebook.notifications.internal.content.Content;
import com.facebook.notifications.internal.content.TextContent;
import org.json.JSONException;
import org.json.JSONObject;
import static com.facebook.notifications.internal.asset.handlers.ColorAssetHandler.fromRGBAHex;
public class ActionConfiguration implements Parcelable {
public static final Creator CREATOR = new Creator() {
@Override
public ActionConfiguration createFromParcel(Parcel source) {
return new ActionConfiguration(source);
}
@Override
public ActionConfiguration[] newArray(int size) {
return new ActionConfiguration[size];
}
};
private final int backgroundColor;
private final int borderColor;
private final float borderWidth;
private final @Nullable Content content;
private final @Nullable Uri actionUri;
public ActionConfiguration(JSONObject json) throws JSONException {
backgroundColor = fromRGBAHex(json.optString("backgroundColor"));
borderColor = fromRGBAHex(json.optString("borderColor"));
borderWidth = (float) json.optDouble("borderWidth", 0);
JSONObject contentJSON = json.optJSONObject("content");
content = contentJSON == null ? null : new TextContent(contentJSON);
String jsonUri = json.optString("url", null);
actionUri = jsonUri == null ? null : Uri.parse(jsonUri);
}
private ActionConfiguration(Parcel source) {
ClassLoader classLoader = getClass().getClassLoader();
backgroundColor = source.readInt();
borderColor = source.readInt();
borderWidth = source.readFloat();
content = source.readParcelable(classLoader);
actionUri = source.readParcelable(classLoader);
}
public int getBackgroundColor() {
return backgroundColor;
}
public int getBorderColor() {
return borderColor;
}
public float getBorderWidth() {
return borderWidth;
}
@Nullable
public Content getContent() {
return content;
}
@Nullable
public Uri getActionUri() {
return actionUri;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(backgroundColor);
dest.writeInt(borderColor);
dest.writeFloat(borderWidth);
dest.writeParcelable(content, flags);
dest.writeParcelable(actionUri, flags);
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/configuration/ActionsConfiguration.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.configuration;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.facebook.notifications.internal.asset.Asset;
import com.facebook.notifications.internal.asset.AssetManager;
import com.facebook.notifications.internal.utilities.EnumCreator;
import com.facebook.notifications.internal.utilities.InvalidParcelException;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class ActionsConfiguration implements Parcelable {
public enum ActionsStyle implements Parcelable {
Attached,
Detached;
public static final Creator CREATOR = new EnumCreator<>(ActionsStyle.class, values());
public static ActionsStyle parse(String input) {
switch (input) {
case "attached":
return ActionsStyle.Attached;
case "detached":
return ActionsStyle.Detached;
default:
return ActionsStyle.Attached;
}
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(ordinal());
}
}
public enum ActionsLayoutStyle implements Parcelable {
Vertical,
Horizontal;
public static final Creator CREATOR = new EnumCreator<>(ActionsLayoutStyle.class, values());
public static ActionsLayoutStyle parse(String input) {
switch (input) {
case "vertical":
return ActionsLayoutStyle.Vertical;
case "horizontal":
return ActionsLayoutStyle.Horizontal;
default:
return ActionsLayoutStyle.Vertical;
}
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(ordinal());
}
}
public static final Creator CREATOR = new Creator() {
@Override
public ActionsConfiguration createFromParcel(Parcel source) {
return new ActionsConfiguration(source);
}
@Override
public ActionsConfiguration[] newArray(int size) {
return new ActionsConfiguration[size];
}
};
private final ActionsStyle style;
private final ActionsLayoutStyle layoutStyle;
private final Asset background;
private final float topInset;
private final float contentInset;
private final float cornerRadius;
private final @NonNull ActionConfiguration[] actions;
private final float height;
private ActionsConfiguration(@NonNull JSONObject json, @NonNull AssetManager assetManager) throws JSONException {
style = ActionsStyle.parse(json.optString("style"));
layoutStyle = ActionsLayoutStyle.parse(json.optString("layoutStyle"));
background = assetManager.inflateAsset(json.optJSONObject("background"));
topInset = (float) json.optDouble("topInset", 0);
contentInset = (float) json.optDouble("contentInset", 0);
cornerRadius = (float) json.optDouble("cornerRadius", 0);
JSONArray rawActions = json.getJSONArray("actions");
actions = new ActionConfiguration[rawActions.length()];
for (int jsonIndex = 0; jsonIndex < actions.length; jsonIndex++) {
actions[jsonIndex] = new ActionConfiguration(rawActions.getJSONObject(jsonIndex));
}
height = (float) json.optDouble("height", 44);
}
private ActionsConfiguration(Parcel source) {
ClassLoader loader = getClass().getClassLoader();
style = source.readParcelable(loader);
layoutStyle = source.readParcelable(loader);
background = source.readParcelable(loader);
topInset = source.readFloat();
contentInset = source.readFloat();
cornerRadius = source.readFloat();
actions = source.createTypedArray(ActionConfiguration.CREATOR);
height = source.readFloat();
}
@Nullable
public static ActionsConfiguration fromJSON(@Nullable JSONObject json, @NonNull AssetManager assetManager) throws JSONException {
if (json == null) {
return null;
}
return new ActionsConfiguration(json, assetManager);
}
public void validate() throws InvalidParcelException {
if (background != null) {
background.validate();
}
}
public ActionsStyle getStyle() {
return style;
}
public ActionsLayoutStyle getLayoutStyle() {
return layoutStyle;
}
public Asset getBackground() {
return background;
}
public float getTopInset() {
return topInset;
}
public float getContentInset() {
return contentInset;
}
public float getCornerRadius() {
return cornerRadius;
}
@NonNull
public ActionConfiguration[] getActions() {
return actions;
}
public float getHeight() {
return height;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(style, flags);
dest.writeParcelable(layoutStyle, flags);
dest.writeParcelable(background, flags);
dest.writeFloat(topInset);
dest.writeFloat(contentInset);
dest.writeFloat(cornerRadius);
dest.writeTypedArray(actions, flags);
dest.writeFloat(height);
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/configuration/BodyConfiguration.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.configuration;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.facebook.notifications.internal.asset.Asset;
import com.facebook.notifications.internal.asset.AssetManager;
import com.facebook.notifications.internal.content.Content;
import com.facebook.notifications.internal.content.ContentManager;
import com.facebook.notifications.internal.content.TextContent;
import com.facebook.notifications.internal.utilities.InvalidParcelException;
import org.json.JSONException;
import org.json.JSONObject;
public class BodyConfiguration implements Parcelable {
public static final Creator CREATOR = new Creator() {
@Override
public BodyConfiguration createFromParcel(Parcel source) {
return new BodyConfiguration(source);
}
@Override
public BodyConfiguration[] newArray(int size) {
return new BodyConfiguration[size];
}
};
private final @Nullable Asset background;
private final @Nullable Content content;
private BodyConfiguration(
@NonNull JSONObject json,
@NonNull AssetManager assetManager,
@NonNull ContentManager contentManager
) throws JSONException {
background = assetManager.inflateAsset(json.getJSONObject("background"));
// TODO: Go through content manager
JSONObject contentJSON = json.optJSONObject("content");
content = contentJSON != null
? new TextContent(contentJSON)
: null;
}
private BodyConfiguration(Parcel source) {
ClassLoader loader = getClass().getClassLoader();
background = source.readParcelable(loader);
content = source.readParcelable(loader);
}
@Nullable
public static BodyConfiguration fromJSON(@Nullable JSONObject json, @NonNull AssetManager assetManager, @NonNull ContentManager contentManager) throws JSONException {
if (json == null) {
return null;
}
return new BodyConfiguration(json, assetManager, contentManager);
}
public void validate() throws InvalidParcelException {
if (background != null) {
background.validate();
}
}
@Nullable
public Asset getBackground() {
return background;
}
@Nullable
public Content getContent() {
return content;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(background, flags);
dest.writeParcelable(content, flags);
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/configuration/CardConfiguration.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.configuration;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import com.facebook.notifications.internal.asset.AssetManager;
import com.facebook.notifications.internal.asset.handlers.ColorAssetHandler;
import com.facebook.notifications.internal.content.ContentManager;
import com.facebook.notifications.internal.utilities.EnumCreator;
import com.facebook.notifications.internal.utilities.InvalidParcelException;
import org.json.JSONException;
import org.json.JSONObject;
public class CardConfiguration implements Parcelable {
public enum CardSize implements Parcelable {
Invalid,
Small,
Medium,
Large;
public static final Creator CREATOR = new EnumCreator<>(CardSize.class, values());
public static CardSize parse(String input) {
switch (input) {
case "small":
return CardSize.Small;
case "medium":
return CardSize.Medium;
case "large":
return CardSize.Large;
default:
return CardSize.Invalid;
}
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(ordinal());
}
}
private static final String LOG_TAG = CardConfiguration.class.getCanonicalName();
private final CardSize cardSize;
private final float cornerRadius;
private final float contentInset;
private final int backdropColor;
private final @Nullable HeroConfiguration heroConfiguration;
private final @Nullable BodyConfiguration bodyConfiguration;
private final @Nullable ActionsConfiguration actionsConfiguration;
public static final Creator CREATOR = new Creator() {
@Override
public CardConfiguration createFromParcel(Parcel source) {
try {
CardConfiguration configuration = new CardConfiguration(source);
configuration.validate();
return configuration;
} catch (InvalidParcelException ex) {
Log.w(LOG_TAG, "Failed to decode card configuration", ex);
return null;
}
}
@Override
public CardConfiguration[] newArray(int size) {
return new CardConfiguration[size];
}
};
public CardConfiguration(@NonNull JSONObject jsonObject, @NonNull AssetManager assetManager, @NonNull ContentManager contentManager) throws JSONException {
cardSize = CardSize.parse(jsonObject.getString("size"));
cornerRadius = (float) jsonObject.optDouble("cornerRadius", 0.0);
contentInset = (float) jsonObject.optDouble("contentInset", 10.0);
backdropColor = ColorAssetHandler.fromRGBAHex(jsonObject.getString("backdropColor"));
heroConfiguration = HeroConfiguration.fromJSON(jsonObject.optJSONObject("hero"), assetManager, contentManager);
bodyConfiguration = BodyConfiguration.fromJSON(jsonObject.optJSONObject("body"), assetManager, contentManager);
actionsConfiguration = ActionsConfiguration.fromJSON(jsonObject.optJSONObject("actions"), assetManager);
}
private CardConfiguration(@NonNull Parcel source) {
ClassLoader loader = getClass().getClassLoader();
cardSize = source.readParcelable(loader);
cornerRadius = source.readFloat();
contentInset = source.readFloat();
backdropColor = source.readInt();
heroConfiguration = source.readParcelable(loader);
bodyConfiguration = source.readParcelable(loader);
actionsConfiguration = source.readParcelable(loader);
}
public void validate() throws InvalidParcelException {
if (heroConfiguration != null) {
heroConfiguration.validate();
}
if (bodyConfiguration != null) {
bodyConfiguration.validate();
}
if (actionsConfiguration != null) {
actionsConfiguration.validate();
}
}
public CardSize getCardSize() {
return cardSize;
}
public float getCornerRadius() {
return cornerRadius;
}
public float getContentInset() {
return contentInset;
}
public int getBackdropColor() {
return backdropColor;
}
@Nullable
public HeroConfiguration getHeroConfiguration() {
return heroConfiguration;
}
@Nullable
public BodyConfiguration getBodyConfiguration() {
return bodyConfiguration;
}
@Nullable
public ActionsConfiguration getActionsConfiguration() {
return actionsConfiguration;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(cardSize, flags);
dest.writeFloat(cornerRadius);
dest.writeFloat(contentInset);
dest.writeInt(backdropColor);
dest.writeParcelable(heroConfiguration, flags);
dest.writeParcelable(bodyConfiguration, flags);
dest.writeParcelable(actionsConfiguration, flags);
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/configuration/HeroConfiguration.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.configuration;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.facebook.notifications.internal.asset.Asset;
import com.facebook.notifications.internal.asset.AssetManager;
import com.facebook.notifications.internal.content.Content;
import com.facebook.notifications.internal.content.ContentManager;
import com.facebook.notifications.internal.content.TextContent;
import com.facebook.notifications.internal.utilities.InvalidParcelException;
import org.json.JSONException;
import org.json.JSONObject;
public class HeroConfiguration implements Parcelable {
public static final Creator CREATOR = new Creator() {
@Override
public HeroConfiguration createFromParcel(Parcel source) {
return new HeroConfiguration(source);
}
@Override
public HeroConfiguration[] newArray(int size) {
return new HeroConfiguration[size];
}
};
private final float height;
private final @Nullable Asset background;
private final @Nullable Content content;
private final Content.VerticalAlignment contentVerticalAlignment;
private HeroConfiguration(
@NonNull JSONObject json,
@NonNull AssetManager assetManager,
@NonNull ContentManager contentManager
) throws JSONException {
height = (float) json.optDouble("height", -1);
background = assetManager.inflateAsset(json.getJSONObject("background"));
// TODO: Go through content manager
JSONObject contentJSON = json.optJSONObject("content");
content = contentJSON != null
? new TextContent(contentJSON)
: null;
contentVerticalAlignment = Content.VerticalAlignment.parse(json.optString("contentAlign"));
}
private HeroConfiguration(Parcel parcel) {
ClassLoader loader = getClass().getClassLoader();
height = parcel.readFloat();
background = parcel.readParcelable(loader);
content = parcel.readParcelable(loader);
contentVerticalAlignment = parcel.readParcelable(loader);
}
/**
* Create a hero configuration from a possibly `null` JSON payload
*
* @param json JSON Payload. Can be null
* @param assetManager Asset manager to use for any assets in the hero
* @param contentManager Content manager to use for any content in the hero
* @return A hero configuration if the JSON payload exists, or `null` if it does not
* @throws JSONException if the JSON is in an invalid format
*/
@Nullable
public static HeroConfiguration fromJSON(
@Nullable JSONObject json,
@NonNull AssetManager assetManager,
@NonNull ContentManager contentManager
) throws JSONException {
if (json == null) {
return null;
}
return new HeroConfiguration(json, assetManager, contentManager);
}
public void validate() throws InvalidParcelException {
if (background != null) {
background.validate();
}
}
public float getHeight() {
return height;
}
@Nullable
public Asset getBackground() {
return background;
}
@Nullable
public Content getContent() {
return content;
}
public Content.VerticalAlignment getContentVerticalAlignment() {
return contentVerticalAlignment;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeFloat(height);
dest.writeParcelable(background, flags);
dest.writeParcelable(content, flags);
dest.writeParcelable(contentVerticalAlignment, flags);
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/content/Content.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.content;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.view.View;
import com.facebook.notifications.internal.utilities.EnumCreator;
public interface Content extends Parcelable {
enum VerticalAlignment implements Parcelable {
Top,
Center,
Bottom;
public static final Creator CREATOR = new EnumCreator<>(VerticalAlignment.class, values());
public static VerticalAlignment parse(String input) {
switch (input) {
case "top":
return VerticalAlignment.Top;
case "center":
return VerticalAlignment.Center;
case "bottom":
return VerticalAlignment.Bottom;
default:
return VerticalAlignment.Center;
}
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(ordinal());
}
}
/**
* Attempt to apply this content to a given view
* @param view The view to apply to
*/
void applyTo(@NonNull View view);
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/content/ContentManager.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.content;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import com.facebook.notifications.internal.utilities.InvalidParcelException;
/**
* Manager for content
*/
public class ContentManager implements Parcelable {
public static final Creator CREATOR = new Creator() {
@Override
public ContentManager createFromParcel(Parcel source) {
return new ContentManager(source);
}
@Override
public ContentManager[] newArray(int size) {
return new ContentManager[size];
}
};
// TODO: Extensible like AssetManager, but for content
public ContentManager() {
}
public ContentManager(ContentManager other) {
}
public ContentManager(@NonNull Parcel source) {
}
public void setContext(@NonNull Context context) {
}
public void validate() throws InvalidParcelException {
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/content/TextContent.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.content;
import android.graphics.Typeface;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.Gravity;
import android.view.View;
import android.widget.TextView;
import com.facebook.notifications.internal.asset.handlers.ColorAssetHandler;
import com.facebook.notifications.internal.utilities.EnumCreator;
import com.facebook.notifications.internal.utilities.FontUtilities;
import org.json.JSONException;
import org.json.JSONObject;
public class TextContent implements Content {
public enum Alignment implements Parcelable {
Left,
Right,
Center;
public static final Creator CREATOR = new EnumCreator<>(Alignment.class, values());
public static Alignment parse(@NonNull String input) {
switch (input) {
case "left":
return Left;
case "right":
return Right;
case "center":
return Center;
default:
return Left;
}
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(ordinal());
}
}
public static final Creator CREATOR = new Creator() {
@Override
public TextContent createFromParcel(Parcel source) {
return new TextContent(source);
}
@Override
public TextContent[] newArray(int size) {
return new TextContent[size];
}
};
private final @NonNull String text;
private final int textColor;
private final @Nullable String typeface;
private final float typefaceSize;
private final Alignment textAlignment;
public TextContent(@NonNull JSONObject json) throws JSONException {
text = json.optString("text", "");
textColor = ColorAssetHandler.fromRGBAHex(json.optString("color"));
typeface = json.optString("font");
typefaceSize = (float) json.optDouble("size", 18); // Default to 18sp, or 'medium' size.
textAlignment = Alignment.parse(json.optString("align", "center"));
}
private TextContent(Parcel source) {
text = source.readString();
textColor = source.readInt();
typeface = source.readString();
typefaceSize = source.readFloat();
textAlignment = source.readParcelable(getClass().getClassLoader());
}
@Override
public void applyTo(@NonNull View view) {
if (view instanceof TextView) {
TextView textView = (TextView)view;
textView.setText(getText());
textView.setTextColor(getTextColor());
Typeface typeface = FontUtilities.parseFont(getTypeface());
typeface = typeface != null ? typeface : Typeface.DEFAULT;
textView.setTypeface(typeface);
textView.setTextSize(getTypefaceSize());
switch (getTextAlignment()) {
case Left:
textView.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
break;
case Center:
textView.setGravity(Gravity.CENTER);
break;
case Right:
textView.setGravity(Gravity.RIGHT | Gravity.CENTER_VERTICAL);
break;
}
}
}
@NonNull
public String getText() {
return text;
}
public int getTextColor() {
return textColor;
}
@Nullable
public String getTypeface() {
return typeface;
}
public float getTypefaceSize() {
return typefaceSize;
}
public Alignment getTextAlignment() {
return textAlignment;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(text);
dest.writeInt(textColor);
dest.writeString(typeface);
dest.writeFloat(typefaceSize);
dest.writeParcelable(textAlignment, flags);
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/utilities/EnumCreator.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.utilities;
import android.os.Parcel;
import android.os.Parcelable;
import java.lang.reflect.Array;
public class EnumCreator implements Parcelable.Creator {
private final Class kls;
private final T[] values;
public EnumCreator(Class kls, T[] values) {
this.kls = kls;
this.values = values;
}
@Override
public T createFromParcel(Parcel source) {
return values[source.readInt()];
}
@Override
@SuppressWarnings("unchecked")
public T[] newArray(int size) {
return (T[]) Array.newInstance(kls, size);
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/utilities/FontUtilities.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.utilities;
import android.graphics.Typeface;
import android.support.annotation.Nullable;
public class FontUtilities {
public static @Nullable Typeface parseFont(@Nullable String fontName) {
if (fontName == null) {
return Typeface.DEFAULT;
}
switch (fontName.toLowerCase()) {
case "system-regular": return Typeface.DEFAULT;
case "system-light": return Typeface.create("sans-serif-light", Typeface.NORMAL);
case "system-bold": return Typeface.DEFAULT_BOLD;
case "system-italic": return Typeface.defaultFromStyle(Typeface.ITALIC);
case "system-bolditalic": return Typeface.defaultFromStyle(Typeface.BOLD_ITALIC);
default: return Typeface.create(fontName, Typeface.NORMAL);
}
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/utilities/GifDecoder.java
================================================
/**
* Copyright (c) 2013 Xcellent Creations, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.facebook.notifications.internal.utilities;
import android.graphics.Bitmap;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
/**
* Reads frame data from a GIF image source and decodes it into individual frames
* for animation purposes. Image data can be read from either and InputStream source
* or a byte[].
*
* This class is optimized for running animations with the frames, there
* are no methods to get individual frame images, only to decode the next frame in the
* animation sequence. Instead, it lowers its memory footprint by only housing the minimum
* data necessary to decode the next frame in the animation sequence.
*
* The animation must be manually moved forward using {@link #advance()} before requesting the next
* frame. This method must also be called before you request the first frame or an error will
* occur.
*
* Implementation adapted from sample code published in Lyons. (2004). Java for Programmers,
* republished under the MIT Open Source License
*/
@SuppressWarnings("ALL")
public class GifDecoder {
private static final String TAG = GifDecoder.class.getSimpleName();
/**
* File read status: No errors.
*/
public static final int STATUS_OK = 0;
/**
* File read status: Error decoding file (may be partially decoded)
*/
public static final int STATUS_FORMAT_ERROR = 1;
/**
* File read status: Unable to open source.
*/
public static final int STATUS_OPEN_ERROR = 2;
/**
* max decoder pixel stack size
*/
protected static final int MAX_STACK_SIZE = 4096;
/**
* GIF Disposal Method meaning take no action
*/
private static final int DISPOSAL_UNSPECIFIED = 0;
/**
* GIF Disposal Method meaning leave canvas from previous frame
*/
private static final int DISPOSAL_NONE = 1;
/**
* GIF Disposal Method meaning clear canvas to background color
*/
private static final int DISPOSAL_BACKGROUND = 2;
/**
* GIF Disposal Method meaning clear canvas to frame before last
*/
private static final int DISPOSAL_PREVIOUS = 3;
/**
* Global status code of GIF data parsing
*/
protected int status;
//Global File Header values and parsing flags
protected int width; // full image width
protected int height; // full image height
protected boolean gctFlag; // global color table used
protected int gctSize; // size of global color table
protected int loopCount = 1; // iterations; 0 = repeat forever
protected int[] gct; // global color table
protected int[] act; // active color table
protected int bgIndex; // background color index
protected int bgColor; // background color
protected int pixelAspect; // pixel aspect ratio
protected boolean lctFlag; // local color table flag
protected int lctSize; // local color table size
// Raw GIF data from input source
protected ByteBuffer rawData;
// Raw data read working array
protected byte[] block = new byte[256]; // current data block
protected int blockSize = 0; // block size last graphic control extension info
// LZW decoder working arrays
protected short[] prefix;
protected byte[] suffix;
protected byte[] pixelStack;
protected byte[] mainPixels;
protected int[] mainScratch, copyScratch;
protected ArrayList frames; // frames read from current file
protected GifFrame currentFrame;
protected Bitmap previousImage, currentImage, renderImage;
protected int framePointer;
protected int frameCount;
/**
* Inner model class housing metadata for each frame
*/
private static class GifFrame {
public int ix, iy, iw, ih;
/* Control Flags */
public boolean interlace;
public boolean transparency;
/* Disposal Method */
public int dispose;
/* Transparency Index */
public int transIndex;
/* Delay, in ms, to next frame */
public int delay;
/* Index in the raw buffer where we need to start reading to decode */
public int bufferFrameStart;
/* Local Color Table */
public int[] lct;
}
/**
* Move the animation frame counter forward
*/
public void advance() {
framePointer = (framePointer + 1) % frameCount;
}
/**
* Recycle any bitmaps held by this GIF decoder, as well as any intermediate data. After
* recycling, the decoder can no longer be used.
*/
public void recycle() {
if (previousImage != null) {
previousImage.recycle();
}
if (currentImage != null) {
currentImage.recycle();
}
if (renderImage != null) {
renderImage.recycle();
}
gct = null;
act = null;
block = null;
prefix = null;
suffix = null;
pixelStack = null;
mainPixels = null;
mainScratch = null;
copyScratch = null;
currentFrame = null;
frames = null;
}
/**
* Gets display duration for specified frame.
*
* @param n int index of frame
* @return delay in milliseconds
*/
public int getDelay(int n) {
int delay = -1;
if ((n >= 0) && (n < frameCount)) {
delay = frames.get(n).delay;
}
return delay;
}
/**
* Gets display duration for the upcoming frame
*/
public int getNextDelay() {
if (frameCount <=0 || framePointer < 0) {
return -1;
}
return getDelay(framePointer);
}
/**
* Gets the number of frames read from file.
*
* @return frame count
*/
public int getFrameCount() {
return frameCount;
}
/**
* Gets the current index of the animation frame, or -1 if animation hasn't not yet started
*
* @return frame index
*/
public int getCurrentFrameIndex() {
return framePointer;
}
/**
* Gets the "Netscape" iteration count, if any. A count of 0 means repeat indefinitiely.
*
* @return iteration count if one was specified, else 1.
*/
public int getLoopCount() {
return loopCount;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
/**
* Get the next frame in the animation sequence.
*
* @return Bitmap representation of frame
*/
public Bitmap getNextFrame() {
if (frameCount <= 0 || framePointer < 0 || currentImage == null) {
return null;
}
GifFrame frame = frames.get(framePointer);
//Set the appropriate color table
if (frame.lct == null) {
act = gct;
} else {
act = frame.lct;
if (bgIndex == frame.transIndex) {
bgColor = 0;
}
}
int save = 0;
if (frame.transparency) {
save = act[frame.transIndex];
act[frame.transIndex] = 0; // set transparent color if specified
}
if (act == null) {
Log.w(TAG, "No Valid Color Table");
status = STATUS_FORMAT_ERROR; // no color table defined
return null;
}
setPixels(framePointer); // transfer pixel data to image
// Reset the transparent pixel in the color table
if (frame.transparency) {
act[frame.transIndex] = save;
}
return currentImage;
}
/**
* Reads GIF image from stream
*
* @param is containing GIF file.
* @return read status code (0 = no errors)
*/
public int read(InputStream is, int contentLength) {
long startTime = System.currentTimeMillis();
if (is != null) {
try {
int capacity = (contentLength > 0) ? (contentLength + 4096) : 4096;
ByteArrayOutputStream buffer = new ByteArrayOutputStream(capacity);
int nRead;
byte[] data = new byte[16384];
while ((nRead = is.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
}
buffer.flush();
read(buffer.toByteArray());
} catch (IOException e) {
Log.w(TAG, "Error reading data from stream", e);
}
} else {
status = STATUS_OPEN_ERROR;
}
try {
is.close();
} catch (Exception e) {
Log.w(TAG, "Error closing stream", e);
}
return status;
}
/**
* Reads GIF image from byte array
*
* @param data containing GIF file.
* @return read status code (0 = no errors)
*/
public int read(byte[] data) {
init();
if (data != null) {
//Initiliaze the raw data buffer
rawData = ByteBuffer.wrap(data);
rawData.rewind();
rawData.order(ByteOrder.LITTLE_ENDIAN);
readHeader();
if (!err()) {
readContents();
if (frameCount < 0) {
status = STATUS_FORMAT_ERROR;
}
}
} else {
status = STATUS_OPEN_ERROR;
}
return status;
}
/**
* Creates new frame image from current data (and previous frames as specified by their disposition codes).
*/
protected void setPixels(int frameIndex) {
GifFrame currentFrame = frames.get(frameIndex);
GifFrame previousFrame = null;
int previousIndex = frameIndex - 1;
if (previousIndex >= 0) {
previousFrame = frames.get(previousIndex);
}
// final location of blended pixels
final int[] dest = mainScratch;
// fill in starting image contents based on last image's dispose code
if (previousFrame != null && previousFrame.dispose > DISPOSAL_UNSPECIFIED) {
if (previousFrame.dispose == DISPOSAL_NONE && currentImage != null) {
// Start with the current image
currentImage.getPixels(dest, 0, width, 0, 0, width, height);
}
if (previousFrame.dispose == DISPOSAL_BACKGROUND) {
// Start with a canvas filled with the background color
int c = 0;
if (!currentFrame.transparency) {
c = bgColor;
}
for (int i = 0; i < previousFrame.ih; i++) {
int n1 = (previousFrame.iy + i) * width + previousFrame.ix;
int n2 = n1 + previousFrame.iw;
for (int k = n1; k < n2; k++) {
dest[k] = c;
}
}
}
if (previousFrame.dispose == DISPOSAL_PREVIOUS && previousImage != null) {
// Start with the previous frame
previousImage.getPixels(dest, 0, width, 0, 0, width, height);
}
}
//Decode pixels for this frame into the global pixels[] scratch
decodeBitmapData(currentFrame, mainPixels); // decode pixel data
// copy each source line to the appropriate place in the destination
int pass = 1;
int inc = 8;
int iline = 0;
for (int i = 0; i < currentFrame.ih; i++) {
int line = i;
if (currentFrame.interlace) {
if (iline >= currentFrame.ih) {
pass++;
switch (pass) {
case 2:
iline = 4;
break;
case 3:
iline = 2;
inc = 4;
break;
case 4:
iline = 1;
inc = 2;
break;
default:
break;
}
}
line = iline;
iline += inc;
}
line += currentFrame.iy;
if (line < height) {
int k = line * width;
int dx = k + currentFrame.ix; // start of line in dest
int dlim = dx + currentFrame.iw; // end of dest line
if ((k + width) < dlim) {
dlim = k + width; // past dest edge
}
int sx = i * currentFrame.iw; // start of line in source
while (dx < dlim) {
// map color and insert in destination
int index = ((int) mainPixels[sx++]) & 0xff;
int c = act[index];
if (c != 0) {
dest[dx] = c;
}
dx++;
}
}
}
//Copy pixels into previous image
currentImage.getPixels(copyScratch, 0, width, 0, 0, width, height);
previousImage.setPixels(copyScratch, 0, width, 0, 0, width, height);
//Set pixels for current image
currentImage.setPixels(dest, 0, width, 0, 0, width, height);
}
/**
* Decodes LZW image data into pixel array. Adapted from John Cristy's BitmapMagick.
*/
protected void decodeBitmapData(GifFrame frame, byte[] dstPixels) {
long startTime = System.currentTimeMillis();
long stepOne, stepTwo, stepThree;
if (frame != null) {
//Jump to the frame start position
rawData.position(frame.bufferFrameStart);
}
int nullCode = -1;
int npix = (frame == null) ? width * height : frame.iw * frame.ih;
int available, clear, code_mask, code_size, end_of_information, in_code, old_code, bits, code, count, i, datum, data_size, first, top, bi, pi;
if (dstPixels == null || dstPixels.length < npix) {
dstPixels = new byte[npix]; // allocate new pixel array
}
if (prefix == null) {
prefix = new short[MAX_STACK_SIZE];
}
if (suffix == null) {
suffix = new byte[MAX_STACK_SIZE];
}
if (pixelStack == null) {
pixelStack = new byte[MAX_STACK_SIZE + 1];
}
// Initialize GIF data stream decoder.
data_size = read();
clear = 1 << data_size;
end_of_information = clear + 1;
available = clear + 2;
old_code = nullCode;
code_size = data_size + 1;
code_mask = (1 << code_size) - 1;
for (code = 0; code < clear; code++) {
prefix[code] = 0; // XXX ArrayIndexOutOfBoundsException
suffix[code] = (byte) code;
}
// Decode GIF pixel stream.
datum = bits = count = first = top = pi = bi = 0;
for (i = 0; i < npix; ) {
if (top == 0) {
if (bits < code_size) {
// Load bytes until there are enough bits for a code.
if (count == 0) {
// Read a new data block.
count = readBlock();
if (count <= 0) {
break;
}
bi = 0;
}
datum += (((int) block[bi]) & 0xff) << bits;
bits += 8;
bi++;
count--;
continue;
}
// Get the next code.
code = datum & code_mask;
datum >>= code_size;
bits -= code_size;
// Interpret the code
if ((code > available) || (code == end_of_information)) {
break;
}
if (code == clear) {
// Reset decoder.
code_size = data_size + 1;
code_mask = (1 << code_size) - 1;
available = clear + 2;
old_code = nullCode;
continue;
}
if (old_code == nullCode) {
pixelStack[top++] = suffix[code];
old_code = code;
first = code;
continue;
}
in_code = code;
if (code == available) {
pixelStack[top++] = (byte) first;
code = old_code;
}
while (code > clear) {
pixelStack[top++] = suffix[code];
code = prefix[code];
}
first = ((int) suffix[code]) & 0xff;
// Add a new string to the string table,
if (available >= MAX_STACK_SIZE) {
break;
}
pixelStack[top++] = (byte) first;
prefix[available] = (short) old_code;
suffix[available] = (byte) first;
available++;
if (((available & code_mask) == 0) && (available < MAX_STACK_SIZE)) {
code_size++;
code_mask += available;
}
old_code = in_code;
}
// Pop a pixel off the pixel stack.
top--;
dstPixels[pi++] = pixelStack[top];
i++;
}
for (i = pi; i < npix; i++) {
dstPixels[i] = 0; // clear missing pixels
}
}
/**
* Returns true if an error was encountered during reading/decoding
*/
protected boolean err() {
return status != STATUS_OK;
}
/**
* Initializes or re-initializes reader
*/
protected void init() {
status = STATUS_OK;
frameCount = 0;
framePointer = -1;
frames = new ArrayList();
gct = null;
}
/**
* Reads a single byte from the input stream.
*/
protected int read() {
int curByte = 0;
try {
curByte = (rawData.get() & 0xFF);
} catch (Exception e) {
status = STATUS_FORMAT_ERROR;
}
return curByte;
}
/**
* Reads next variable length block from input.
*
* @return number of bytes stored in "buffer"
*/
protected int readBlock() {
blockSize = read();
int n = 0;
if (blockSize > 0) {
try {
int count;
while (n < blockSize) {
count = blockSize - n;
rawData.get(block, n, count);
n += count;
}
} catch (Exception e) {
Log.w(TAG, "Error Reading Block", e);
status = STATUS_FORMAT_ERROR;
}
}
return n;
}
/**
* Reads color table as 256 RGB integer values
*
* @param ncolors int number of colors to read
* @return int array containing 256 colors (packed ARGB with full alpha)
*/
protected int[] readColorTable(int ncolors) {
int nbytes = 3 * ncolors;
int[] tab = null;
byte[] c = new byte[nbytes];
try {
rawData.get(c);
tab = new int[256]; // max size to avoid bounds checks
int i = 0;
int j = 0;
while (i < ncolors) {
int r = ((int) c[j++]) & 0xff;
int g = ((int) c[j++]) & 0xff;
int b = ((int) c[j++]) & 0xff;
tab[i++] = 0xff000000 | (r << 16) | (g << 8) | b;
}
} catch (BufferUnderflowException e) {
Log.w(TAG, "Format Error Reading Color Table", e);
status = STATUS_FORMAT_ERROR;
}
return tab;
}
/**
* Main file parser. Reads GIF content blocks.
*/
protected void readContents() {
// read GIF file content blocks
boolean done = false;
while (!(done || err())) {
int code = read();
switch (code) {
case 0x2C: // image separator
readBitmap();
break;
case 0x21: // extension
code = read();
switch (code) {
case 0xf9: // graphics control extension
//Start a new frame
currentFrame = new GifFrame();
readGraphicControlExt();
break;
case 0xff: // application extension
readBlock();
String app = "";
for (int i = 0; i < 11; i++) {
app += (char) block[i];
}
if (app.equals("NETSCAPE2.0")) {
readNetscapeExt();
} else {
skip(); // don't care
}
break;
case 0xfe:// comment extension
skip();
break;
case 0x01:// plain text extension
skip();
break;
default: // uninteresting extension
skip();
}
break;
case 0x3b: // terminator
done = true;
break;
case 0x00: // bad byte, but keep going and see what happens break;
default:
status = STATUS_FORMAT_ERROR;
}
}
}
/**
* Reads GIF file header information.
*/
protected void readHeader() {
String id = "";
for (int i = 0; i < 6; i++) {
id += (char) read();
}
if (!id.startsWith("GIF")) {
status = STATUS_FORMAT_ERROR;
return;
}
readLSD();
if (gctFlag && !err()) {
gct = readColorTable(gctSize);
bgColor = gct[bgIndex];
}
}
/**
* Reads Graphics Control Extension values
*/
protected void readGraphicControlExt() {
read(); // block size
int packed = read(); // packed fields
currentFrame.dispose = (packed & 0x1c) >> 2; // disposal method
if (currentFrame.dispose == 0) {
currentFrame.dispose = 1; // elect to keep old image if discretionary
}
currentFrame.transparency = (packed & 1) != 0;
currentFrame.delay = readShort() * 10; // delay in milliseconds
currentFrame.transIndex = read(); // transparent color index
read(); // block terminator
}
/**
* Reads next frame image
*/
protected void readBitmap() {
currentFrame.ix = readShort(); // (sub)image position & size
currentFrame.iy = readShort();
currentFrame.iw = readShort();
currentFrame.ih = readShort();
int packed = read();
lctFlag = (packed & 0x80) != 0; // 1 - local color table flag interlace
lctSize = (int) Math.pow(2, (packed & 0x07) + 1);
// 3 - sort flag
// 4-5 - reserved lctSize = 2 << (packed & 7); // 6-8 - local color
// table size
currentFrame.interlace = (packed & 0x40) != 0;
if (lctFlag) {
currentFrame.lct = readColorTable(lctSize); // read table
} else {
currentFrame.lct = null; //No local color table
}
currentFrame.bufferFrameStart = rawData.position(); //Save this as the decoding position pointer
decodeBitmapData(null, mainPixels); // false decode pixel data to advance buffer
skip();
if (err()) {
return;
}
frameCount++;
frames.add(currentFrame); // add image to frame
}
/**
* Reads Logical Screen Descriptor
*/
protected void readLSD() {
// logical screen size
width = readShort();
height = readShort();
// packed fields
int packed = read();
gctFlag = (packed & 0x80) != 0; // 1 : global color table flag
// 2-4 : color resolution
// 5 : gct sort flag
gctSize = 2 << (packed & 7); // 6-8 : gct size
bgIndex = read(); // background color index
pixelAspect = read(); // pixel aspect ratio
//Now that we know the size, init scratch arrays
mainPixels = new byte[width * height];
mainScratch = new int[width * height];
copyScratch = new int[width * height];
previousImage = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
currentImage = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
}
/**
* Reads Netscape extenstion to obtain iteration count
*/
protected void readNetscapeExt() {
do {
readBlock();
if (block[0] == 1) {
// loop count sub-block
int b1 = ((int) block[1]) & 0xff;
int b2 = ((int) block[2]) & 0xff;
loopCount = (b2 << 8) | b1;
}
} while ((blockSize > 0) && !err());
}
/**
* Reads next 16-bit value, LSB first
*/
protected int readShort() {
// read 16-bit value
return rawData.getShort();
}
/**
* Skips variable length blocks up to and including next zero length block.
*/
protected void skip() {
do {
readBlock();
} while ((blockSize > 0) && !err());
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/utilities/InvalidParcelException.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.utilities;
/**
* Represents an exception that occurs when validating
* the results of a object created from a parcel
*/
public class InvalidParcelException extends Exception {
public InvalidParcelException() {
}
public InvalidParcelException(String detailMessage) {
super(detailMessage);
}
public InvalidParcelException(String detailMessage, Throwable throwable) {
super(detailMessage, throwable);
}
public InvalidParcelException(Throwable throwable) {
super(throwable);
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/utilities/JSONObjectVisitor.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.utilities;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.Iterator;
public abstract class JSONObjectVisitor {
public static void walk(JSONObject object, JSONObjectVisitor visitor) {
visitor.visit(object);
}
public static void walk(JSONArray array, JSONObjectVisitor visitor) {
visitor.visit(array);
}
protected void visit(JSONObject object) {
for (Iterator keys = object.keys(); keys.hasNext(); ) {
String key = keys.next();
Object value = object.opt(key);
if (value instanceof JSONObject) {
visit((JSONObject) value);
}
if (value instanceof JSONArray) {
visit((JSONArray) value);
}
}
}
protected void visit(JSONArray array) {
for (int index = 0; index < array.length(); index++) {
Object value = array.opt(index);
if (value instanceof JSONObject) {
visit((JSONObject) value);
}
if (value instanceof JSONArray) {
visit((JSONArray) value);
}
}
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/utilities/RoundedViewHelper.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.utilities;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.RectF;
import android.support.annotation.NonNull;
public class RoundedViewHelper {
public static final int TOP_LEFT = 1 << 0;
public static final int TOP_RIGHT = 1 << 1;
public static final int BOTTOM_LEFT = 1 << 2;
public static final int BOTTOM_RIGHT = 1 << 3;
public static final int ALL = TOP_LEFT | TOP_RIGHT | BOTTOM_LEFT | BOTTOM_RIGHT;
private final float cornerRadius;
private final int corners;
private final @NonNull RectF rect;
private final @NonNull Path clipPath;
/**
* Create a new {@link RoundedViewHelper}, with a specified corner radius and corners.
*
* @param context The context to help inside (usually an {@link Activity})
* @param cornerRadiusDip The corner radius to use.
* @param cornersMask The corners to automatically round.
*/
public RoundedViewHelper(@NonNull Context context, float cornerRadiusDip, int cornersMask) {
corners = cornersMask;
cornerRadius = context.getResources().getDisplayMetrics().density * cornerRadiusDip;
rect = new RectF();
clipPath = new Path();
}
public void onLayout(boolean changed, int l, int t, int r, int b) {
if (corners == 0 || !changed) {
return;
}
clipPath.reset();
rect.set(0, 0, (r - l), (b - t));
float[] radii = {0, 0, 0, 0, 0, 0, 0, 0};
if ((corners & TOP_LEFT) != 0) {
radii[0] = cornerRadius;
radii[1] = cornerRadius;
}
if ((corners & TOP_RIGHT) != 0) {
radii[2] = cornerRadius;
radii[3] = cornerRadius;
}
if ((corners & BOTTOM_LEFT) != 0) {
radii[4] = cornerRadius;
radii[5] = cornerRadius;
}
if ((corners & BOTTOM_RIGHT) != 0) {
radii[6] = cornerRadius;
radii[7] = cornerRadius;
}
clipPath.addRoundRect(rect, radii, Path.Direction.CW);
}
public void preDraw(Canvas canvas) {
if (corners == 0) {
return;
}
if (canvas.isOpaque()) {
canvas.saveLayerAlpha(0, 0, canvas.getWidth(), canvas.getHeight(), 255, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);
}
canvas.clipPath(clipPath);
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/utilities/TransparentStateListDrawable.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.utilities;
import android.graphics.PixelFormat;
import android.graphics.drawable.StateListDrawable;
/**
* HACK:
*
* Due to an optimization in how canvases work, drawables which have an 'opaque' opacity
* setting actually still get immediately blitted to the screen, regardless of what canvas
* they're drawing in, which means they ignore the clip path of the canvas they're drawing into.
*
* We need a button that can be properly clipped, so we just simply extend the existing, working
* drawable, and make it transparent.
*/
public class TransparentStateListDrawable extends StateListDrawable {
@Override
public int getOpacity() {
return PixelFormat.TRANSPARENT;
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/utilities/Version.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.utilities;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.NoSuchElementException;
import java.util.Scanner;
/**
* Simple version class
*/
public class Version implements Comparable {
private final int major;
private final int minor;
private final int patch;
public Version(int major, int minor, int patch) {
this.major = major;
this.minor = minor;
this.patch = patch;
}
@Nullable
public static Version parse(String input) {
Scanner scanner = new Scanner(input);
scanner.useDelimiter("\\.");
int major = Integer.MIN_VALUE, minor = Integer.MIN_VALUE, patch = 0;
try {
major = scanner.nextInt();
minor = scanner.nextInt();
patch = scanner.nextInt();
} catch (NoSuchElementException ex) {
if (major == Integer.MIN_VALUE && minor == Integer.MIN_VALUE) {
return null;
}
}
return new Version(major, minor, patch);
}
public int getPatch() {
return patch;
}
public int getMinor() {
return minor;
}
public int getMajor() {
return major;
}
@Override
public String toString() {
return "Version{" + major + "." + minor + "." + patch + '}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Version version = (Version) o;
return
major == version.major &&
minor == version.minor &&
patch == version.patch;
}
@Override
public int hashCode() {
int result = major;
result = 31 * result + minor;
result = 31 * result + patch;
return result;
}
@Override
public int compareTo(@NonNull Version another) {
int major = this.major - another.major;
int minor = this.minor - another.minor;
int patch = this.patch - another.patch;
return major != 0 ? major :
minor != 0 ? minor :
patch;
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/view/ActionButton.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.view;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.GradientDrawable;
import android.os.Build;
import android.support.annotation.NonNull;
import android.util.DisplayMetrics;
import android.util.StateSet;
import android.widget.Button;
import com.facebook.notifications.internal.configuration.ActionConfiguration;
import com.facebook.notifications.internal.content.Content;
import com.facebook.notifications.internal.utilities.TransparentStateListDrawable;
@SuppressLint("ViewConstructor")
public class ActionButton extends Button {
public enum Type {
Primary,
Secondary,
Dismiss
}
private final @NonNull ActionConfiguration configuration;
private final Type type;
public ActionButton(@NonNull Context context, @NonNull final ActionConfiguration config, @NonNull Type t, final float cornerRadius) {
super(context, null, android.R.attr.borderlessButtonStyle);
configuration = config;
type = t;
setTransformationMethod(null);
setPadding(0, 0, 0, 0);
final DisplayMetrics metrics = getResources().getDisplayMetrics();
final int backgroundColor = configuration.getBackgroundColor();
final int pressedColor;
final int borderWidth = Math.round(configuration.getBorderWidth() * metrics.density);
Content content = config.getContent();
if (content != null) {
content.applyTo(this);
}
float[] hsv = {0, 0, 0};
Color.colorToHSV(backgroundColor, hsv);
hsv[2] *= 0.5;
pressedColor = Color.HSVToColor(backgroundColor >> 24, hsv);
GradientDrawable backgroundGradient = new GradientDrawable() {{
setCornerRadius(cornerRadius * metrics.density);
setShape(GradientDrawable.RECTANGLE);
setStroke(borderWidth, configuration.getBorderColor());
setColor(backgroundColor);
}};
GradientDrawable pressedGradient = new GradientDrawable() {{
setCornerRadius(cornerRadius * metrics.density);
setShape(RECTANGLE);
setStroke(borderWidth, configuration.getBorderColor());
setColor(pressedColor);
}};
TransparentStateListDrawable stateListDrawable = new TransparentStateListDrawable();
stateListDrawable.addState(new int[]{android.R.attr.state_pressed}, pressedGradient);
stateListDrawable.addState(StateSet.WILD_CARD, backgroundGradient);
setBackgroundDrawable(stateListDrawable);
setWillNotDraw(false);
// ClipPath is not hardware-accelerated before 4.3
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
setLayerType(LAYER_TYPE_SOFTWARE, null);
}
}
@NonNull
public ActionConfiguration getConfiguration() {
return configuration;
}
public Type getType() {
return type;
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/view/ActionsView.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.view;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.net.Uri;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.DisplayMetrics;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import com.facebook.notifications.internal.asset.AssetManager;
import com.facebook.notifications.internal.configuration.ActionConfiguration;
import com.facebook.notifications.internal.configuration.ActionsConfiguration;
import com.facebook.notifications.internal.configuration.CardConfiguration;
import com.facebook.notifications.internal.utilities.RoundedViewHelper;
@SuppressWarnings("ResourceType")
@SuppressLint("ViewConstructor")
public class ActionsView extends RelativeLayout implements View.OnClickListener {
public interface Delegate {
void actionButtonClicked(ActionButton.Type type, @Nullable Uri actionUri);
}
private static final int ASSET_VIEW_ID = 0x78d5c27b;
private static final int BUTTONS_LAYOUT_ID = 0x7cc9a8c8;
private final @NonNull Delegate delegate;
private final @Nullable ActionsConfiguration configuration;
private final @NonNull RoundedViewHelper roundedViewHelper;
private final @NonNull AssetView assetView;
private final @NonNull LinearLayout buttonsLayout;
private final @NonNull ActionButton[] actionButtons;
public ActionsView(@NonNull Context context, @NonNull AssetManager assetManager, @NonNull Delegate del, @NonNull final CardConfiguration config) {
super(context);
delegate = del;
configuration = config.getActionsConfiguration();
if (configuration == null) {
roundedViewHelper = new RoundedViewHelper(context, 0, 0);
assetView = new AssetView(context, assetManager, null);
buttonsLayout = new LinearLayout(context);
actionButtons = new ActionButton[0];
return;
}
roundedViewHelper = new RoundedViewHelper(context, config.getCornerRadius(), getRoundedCorners(config));
assetView = new AssetView(context, assetManager, configuration.getBackground());
ActionConfiguration[] actions = configuration.getActions();
actionButtons = new ActionButton[actions.length];
final DisplayMetrics metrics = getResources().getDisplayMetrics();
int margin = Math.round(configuration.getContentInset() * metrics.density);
int topMargin = Math.round(configuration.getTopInset() * metrics.density);
int marginLeft = 0, marginTop = 0;
int buttonWidth = 0, buttonWeight = 0;
int buttonHeight = Math.round(configuration.getHeight() * metrics.density);
buttonsLayout = new LinearLayout(context);
switch (configuration.getLayoutStyle()) {
case Vertical:
buttonWidth = LayoutParams.MATCH_PARENT;
marginTop = margin;
buttonsLayout.setOrientation(LinearLayout.VERTICAL);
break;
case Horizontal:
buttonWeight = 1;
marginLeft = margin;
buttonsLayout.setOrientation(LinearLayout.HORIZONTAL);
break;
default:
throw new RuntimeException("Unknown layout style: " + configuration.getLayoutStyle());
}
boolean primary = true;
for (int actionIndex = 0; actionIndex < actions.length; actionIndex++) {
ActionConfiguration configuration = actions[actionIndex];
ActionButton.Type buttonType;
if (configuration.getActionUri() != null) {
buttonType = primary ? ActionButton.Type.Primary : ActionButton.Type.Secondary;
primary = false;
} else {
buttonType = ActionButton.Type.Dismiss;
}
actionButtons[actionIndex] = new ActionButton(context, actions[actionIndex], buttonType, this.configuration.getCornerRadius());
actionButtons[actionIndex].setOnClickListener(this);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(buttonWidth, buttonHeight);
layoutParams.weight = buttonWeight;
if (actionIndex > 0) {
layoutParams.setMargins(marginLeft, marginTop, 0, 0);
}
buttonsLayout.addView(actionButtons[actionIndex], layoutParams);
}
assetView.setId(ASSET_VIEW_ID);
buttonsLayout.setId(BUTTONS_LAYOUT_ID);
addView(assetView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) {{
addRule(ALIGN_TOP, BUTTONS_LAYOUT_ID);
addRule(ALIGN_BOTTOM, BUTTONS_LAYOUT_ID);
}});
buttonsLayout.setPadding(margin, topMargin, margin, margin);
addView(buttonsLayout, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
setWillNotDraw(false);
// ClipPath is not hardware-accelerated before 4.3
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
setLayerType(LAYER_TYPE_SOFTWARE, null);
}
}
private static int getRoundedCorners(@NonNull CardConfiguration configuration) {
if (configuration.getActionsConfiguration() == null) {
return 0;
}
int corners = RoundedViewHelper.BOTTOM_LEFT | RoundedViewHelper.BOTTOM_RIGHT;
if (configuration.getHeroConfiguration() == null &&
configuration.getBodyConfiguration() == null) {
corners |= RoundedViewHelper.TOP_LEFT | RoundedViewHelper.TOP_RIGHT;
}
return corners;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
roundedViewHelper.onLayout(changed, l, t, r, b);
}
@Override
public void draw(Canvas canvas) {
roundedViewHelper.preDraw(canvas);
super.draw(canvas);
}
@Override
public void onClick(View v) {
ActionButton button = (ActionButton) v;
delegate.actionButtonClicked(button.getType(), button.getConfiguration().getActionUri());
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/view/AssetView.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.view;
import android.annotation.SuppressLint;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.View;
import android.widget.FrameLayout;
import com.facebook.notifications.internal.asset.Asset;
import com.facebook.notifications.internal.asset.AssetManager;
/**
* Implements a view that draws a asset.
*/
@SuppressLint("ViewConstructor")
public class AssetView extends FrameLayout {
private final @Nullable Asset asset;
public AssetView(@NonNull Context context, @NonNull AssetManager assetManager, @Nullable Asset asset) {
super(context);
this.asset = asset;
if (asset != null) {
View viewForAsset = assetManager.inflateView(asset, context);
addView(viewForAsset, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
}
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/view/BodyView.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.view;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.DisplayMetrics;
import android.widget.RelativeLayout;
import com.facebook.notifications.internal.asset.AssetManager;
import com.facebook.notifications.internal.configuration.ActionsConfiguration;
import com.facebook.notifications.internal.configuration.BodyConfiguration;
import com.facebook.notifications.internal.configuration.CardConfiguration;
import com.facebook.notifications.internal.content.ContentManager;
import com.facebook.notifications.internal.utilities.RoundedViewHelper;
@SuppressWarnings("ResourceType")
@SuppressLint("ViewConstructor")
public class BodyView extends RelativeLayout {
private final int RESOURCE_VIEW_ID = 0xa55d68f4;
private final int CONTENT_VIEW_ID = 0x2facb6c8;
private final @Nullable BodyConfiguration configuration;
private final @NonNull RoundedViewHelper roundedViewHelper;
private final @NonNull AssetView assetView;
private final @NonNull ContentView contentView;
public BodyView(@NonNull Context context, @NonNull AssetManager assetManager, @NonNull ContentManager contentManager, @NonNull CardConfiguration config) {
super(context);
configuration = config.getBodyConfiguration();
ActionsConfiguration actionsConfiguration = config.getActionsConfiguration();
ActionsConfiguration.ActionsStyle actionsStyle = actionsConfiguration != null
? actionsConfiguration.getStyle()
: ActionsConfiguration.ActionsStyle.Detached;
int corners = actionsStyle == ActionsConfiguration.ActionsStyle.Detached
? RoundedViewHelper.BOTTOM_LEFT | RoundedViewHelper.BOTTOM_RIGHT
: 0;
if (config.getHeroConfiguration() == null && configuration != null) {
corners |= RoundedViewHelper.TOP_LEFT | RoundedViewHelper.TOP_RIGHT;
}
roundedViewHelper = new RoundedViewHelper(context, config.getCornerRadius(), corners);
if (configuration == null) {
assetView = new AssetView(context, assetManager, null);
contentView = new ContentView(context, contentManager, null);
return;
}
assetView = new AssetView(context, assetManager, configuration.getBackground());
contentView = new ContentView(context, contentManager, configuration.getContent());
assetView.setId(RESOURCE_VIEW_ID);
contentView.setId(CONTENT_VIEW_ID);
DisplayMetrics metrics = getResources().getDisplayMetrics();
final int padding = Math.round(config.getContentInset() * metrics.density);
contentView.setPadding(padding, padding, padding, padding);
addView(assetView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) {{
addRule(ALIGN_TOP, CONTENT_VIEW_ID);
addRule(ALIGN_BOTTOM, CONTENT_VIEW_ID);
}});
addView(contentView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
setWillNotDraw(false);
// ClipPath is not hardware-accelerated before 4.3
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
setLayerType(LAYER_TYPE_SOFTWARE, null);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
roundedViewHelper.onLayout(changed, l, t, r, b);
}
@Override
public void draw(Canvas canvas) {
roundedViewHelper.preDraw(canvas);
super.draw(canvas);
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/view/CardView.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.view;
import android.annotation.SuppressLint;
import android.content.Context;
import android.support.annotation.NonNull;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.widget.LinearLayout;
import com.facebook.notifications.internal.asset.AssetManager;
import com.facebook.notifications.internal.configuration.CardConfiguration;
import com.facebook.notifications.internal.configuration.HeroConfiguration;
import com.facebook.notifications.internal.content.ContentManager;
/**
* This class is the start of the Push Card hierarchy.
*
* It contains a {@link HeroView}, a {@link BodyView}, and a {@link ActionsView}.
*/
// As this class will only be created via code, suppress the following warning.
@SuppressLint("ViewConstructor")
public class CardView extends LinearLayout {
private final @NonNull CardConfiguration configuration;
private final @NonNull HeroView heroView;
private final @NonNull BodyView bodyView;
private final @NonNull ActionsView actionsView;
/**
* Create a new card view, from the given configuration.
*
* @param context The context of the view (usually an activity).
* @param config The configuration of the push card to display.
*/
public CardView(
@NonNull Context context,
@NonNull AssetManager assetManager,
@NonNull ContentManager contentManager,
@NonNull ActionsView.Delegate actionsDelegate,
@NonNull CardConfiguration config
) {
super(context);
configuration = config;
heroView = new HeroView(context, assetManager, contentManager, configuration);
bodyView = new BodyView(context, assetManager, contentManager, configuration);
actionsView = new ActionsView(context, assetManager, actionsDelegate, configuration);
setOrientation(VERTICAL);
setGravity(Gravity.CENTER_VERTICAL);
addView(heroView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
addView(bodyView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
addView(actionsView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
setBackgroundColor(configuration.getBackdropColor());
}
/**
* Get a card size that fits for the given dimensions
*
* @param sizes An array of two floats (width and height) **measured in DIP**.
* @param cardSize The target card size to fit
* @return A new array with the proper card size for the given dimensions.
*/
private static
@NonNull
float[] sizeForCardSize(@NonNull float[] sizes, CardConfiguration.CardSize cardSize) {
float layoutW = Math.min(400, sizes[0]);
float layoutH = Math.min(700, sizes[1]);
switch (cardSize) {
case Invalid:
return new float[]{0, 0};
case Small:
layoutW *= 0.75;
layoutH *= 0.7;
break;
case Medium:
layoutW *= 0.83;
layoutH *= 0.9;
break;
case Large:
break;
}
return new float[]{layoutW, layoutH};
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
DisplayMetrics metrics = getResources().getDisplayMetrics();
int totalWidth = MeasureSpec.getSize(widthMeasureSpec);
int totalHeight = MeasureSpec.getSize(heightMeasureSpec);
float[] size = {
totalWidth / metrics.density,
totalHeight / metrics.density
};
float[] cardSize = sizeForCardSize(size, configuration.getCardSize());
cardSize[0] *= metrics.density;
cardSize[1] *= metrics.density;
int hPadding = Math.round((totalWidth - cardSize[0]) / 2);
setPadding(hPadding, 0, hPadding, 0);
int measuredContentHeight = Integer.MAX_VALUE;
HeroConfiguration heroConfiguration = configuration.getHeroConfiguration();
float heroHeight = 1;
if (heroConfiguration == null || heroConfiguration.getHeight() == -1) {
// Measure the total hero content size
heroView.getLayoutParams().height = LayoutParams.WRAP_CONTENT;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measuredContentHeight = heroView.getMeasuredHeight();
} else {
heroHeight = heroConfiguration.getHeight();
}
// Reset the hero view's height to zero so we can measure everything else
heroView.getLayoutParams().height = 0;
// NOTE: It is extremely important that this call is in the middle of the function. It feels
// strange, but if we do not call it *after* we set our padding, it will calculate the width of
// our children without taking the padding into account and cause things to flow off of the
// screen.
// Also note that android 6.0+ does not require an explicit second call to `onMeasure`, the View
// subsystem appears to do it automatically whenever the padding is changed.
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int bodyHeight = bodyView.getMeasuredHeight();
int actionsHeight = actionsView.getMeasuredHeight();
int availableHeight = Math.round(cardSize[1]);
int remainingHeight = availableHeight - bodyHeight - actionsHeight;
int maxHeight = Math.round(remainingHeight * Math.abs(heroHeight));
heroView.getLayoutParams().height = Math.min(maxHeight, measuredContentHeight);
// We must explicitly tell the view subsystem to re-calculate with the new hero height,
// otherwise it will assume we want to keep the height of zero that we last measured with.
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/view/ContentView.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.view;
import android.annotation.SuppressLint;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.widget.TextView;
import com.facebook.notifications.internal.content.Content;
import com.facebook.notifications.internal.content.ContentManager;
/**
* Implements a view that draws content.
*/
@SuppressLint("ViewConstructor")
public class ContentView extends TextView {
private final @Nullable Content content;
public ContentView(@NonNull Context context, @NonNull ContentManager assetManager, @Nullable Content content) {
super(context);
this.content = content;
if (content != null) {
content.applyTo(this);
}
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/view/GifView.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.view;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.View;
import com.facebook.notifications.internal.utilities.GifDecoder;
@SuppressLint("ViewConstructor")
public class GifView extends View {
private class DecoderThread extends Thread {
private final @Nullable GifDecoder decoder;
private @Nullable Bitmap currentFrame;
public DecoderThread(@Nullable GifDecoder decoder) {
this.decoder = decoder;
}
@Override
public void run() {
if (decoder == null) {
return;
}
while (true) {
long frameStart = System.nanoTime();
decoder.advance();
int frameDelay = decoder.getNextDelay();
long targetNextFrame = frameStart + (frameDelay * 1000000);
synchronized (this) {
currentFrame = decoder.getNextFrame();
notifyAll();
}
// Naive: assume all frames take the same amount of time to decode.
long currentTime = System.nanoTime();
long frameDecodeTime = currentTime - frameStart;
long sleepTimeNs = (targetNextFrame - currentTime) - frameDecodeTime;
long sleepTimeMs = Math.max(0, sleepTimeNs / 1000000);
postInvalidate();
try {
Thread.sleep(sleepTimeMs);
if (Thread.interrupted()) {
break;
}
} catch (InterruptedException ex) {
// Thread was killed by View, RIP.
break;
}
}
currentFrame = null;
decoder.recycle();
}
}
private static final String LOG_TAG = GifView.class.getCanonicalName();
private final @NonNull DecoderThread decoderThread;
private final @NonNull Paint antiAliasPaint;
private final @NonNull Rect sourceRect;
private final @NonNull RectF targetRect;
public GifView(@NonNull Context context, @Nullable GifDecoder decoder) {
super(context);
setDrawingCacheEnabled(false);
setWillNotCacheDrawing(true);
decoderThread = new DecoderThread(decoder);
antiAliasPaint = new Paint();
sourceRect = new Rect();
targetRect = new RectF();
antiAliasPaint.setAntiAlias(true);
antiAliasPaint.setFilterBitmap(true);
antiAliasPaint.setDither(true);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
decoderThread.start();
}
@Override
protected void onDetachedFromWindow() {
decoderThread.interrupt();
try {
decoderThread.join();
} catch (InterruptedException ex) {
Log.e(LOG_TAG, "Failed to kill decoder thread", ex);
}
super.onDetachedFromWindow();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
GifDecoder decoder = decoderThread.decoder;
if (decoder == null) {
setMeasuredDimension(0, 0);
return;
}
boolean variableWidth = MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY;
boolean variableHeight = MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
int measuredHeight = MeasureSpec.getSize(heightMeasureSpec);
float sourceWidth = decoder.getWidth();
float sourceHeight = decoder.getHeight();
float destWidth = measuredWidth;
float destHeight = measuredHeight;
float heightRatio = destHeight / sourceHeight;
float widthRatio = destWidth / sourceWidth;
if (variableWidth && variableHeight) {
float scale = Math.min(heightRatio, widthRatio);
measuredWidth = Math.round(measuredWidth * scale);
measuredHeight = Math.round(measuredHeight * scale);
} else if (variableWidth) {
measuredWidth = Math.round(measuredHeight * (sourceWidth / sourceHeight));
} else if (variableHeight) {
measuredHeight = Math.round(measuredWidth * (sourceHeight / sourceWidth));
}
setMeasuredDimension(measuredWidth, measuredHeight);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Bitmap currentFrame;
synchronized (decoderThread) {
currentFrame = decoderThread.currentFrame;
if (currentFrame == null) {
try {
decoderThread.wait(100);
currentFrame = decoderThread.currentFrame;
} catch (InterruptedException e) {
Log.e(LOG_TAG, "Failed to wait", e);
}
}
}
if (currentFrame == null) {
return;
}
// Scale the frame that we have already in our bitmap and draw it.
// This is the CENTER_CROP algorithm that we use for displaying images.
float sourceWidth = currentFrame.getWidth();
float sourceHeight = currentFrame.getHeight();
float destWidth = getWidth();
float destHeight = getHeight();
float heightRatio = destHeight / sourceHeight;
float widthRatio = destWidth / sourceWidth;
float scale = Math.max(heightRatio, widthRatio);
float targetWidth = sourceWidth * scale;
float targetHeight = sourceHeight * scale;
float offsetW = (destWidth - targetWidth) / 2f;
float offsetH = (destHeight - targetHeight) / 2f;
sourceRect.set(0, 0, currentFrame.getWidth(), currentFrame.getHeight());
targetRect.set(offsetW, offsetH, offsetW + targetWidth, offsetH + targetHeight);
canvas.drawBitmap(currentFrame, sourceRect, targetRect, antiAliasPaint);
}
}
================================================
FILE: Android/notifications/src/main/java/com/facebook/notifications/internal/view/HeroView.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.internal.view;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.DisplayMetrics;
import android.widget.RelativeLayout;
import com.facebook.notifications.internal.asset.AssetManager;
import com.facebook.notifications.internal.configuration.ActionsConfiguration;
import com.facebook.notifications.internal.configuration.CardConfiguration;
import com.facebook.notifications.internal.configuration.HeroConfiguration;
import com.facebook.notifications.internal.content.ContentManager;
import com.facebook.notifications.internal.utilities.RoundedViewHelper;
@SuppressWarnings("ResourceType")
@SuppressLint("ViewConstructor")
public class HeroView extends RelativeLayout {
// Just some random generated IDs.
private static final int ASSET_VIEW_ID = 0x53ec9fc7;
private static final int CONTENT_VIEW_ID = 0xa5e76c35;
private final @Nullable HeroConfiguration configuration;
private final @NonNull RoundedViewHelper roundedViewHelper;
private final @NonNull AssetView assetView;
private final @NonNull ContentView contentView;
private final int padding;
public HeroView(@NonNull Context context, @NonNull AssetManager assetManager, @NonNull ContentManager contentManager, @NonNull CardConfiguration config) {
super(context);
configuration = config.getHeroConfiguration();
roundedViewHelper = new RoundedViewHelper(context, config.getCornerRadius(), getRoundedCorners(config));
if (configuration == null) {
assetView = new AssetView(context, assetManager, null);
contentView = new ContentView(context, contentManager, null);
padding = 0;
return;
}
assetView = new AssetView(context, assetManager, configuration.getBackground());
contentView = new ContentView(context, contentManager, configuration.getContent());
assetView.setId(ASSET_VIEW_ID);
contentView.setId(CONTENT_VIEW_ID);
DisplayMetrics metrics = getResources().getDisplayMetrics();
padding = Math.round(config.getContentInset() * metrics.density);
addView(assetView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
addView(contentView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) {{
setMargins(padding, padding, padding, padding);
switch (configuration.getContentVerticalAlignment()) {
case Top:
addRule(ALIGN_TOP, ASSET_VIEW_ID);
break;
case Center:
addRule(CENTER_VERTICAL);
break;
case Bottom:
addRule(ALIGN_BOTTOM, ASSET_VIEW_ID);
break;
}
}});
setWillNotDraw(false);
// ClipPath is not hardware-accelerated before 4.3
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
setLayerType(LAYER_TYPE_SOFTWARE, null);
}
}
private static int getRoundedCorners(@NonNull CardConfiguration cardConfiguration) {
if (cardConfiguration.getHeroConfiguration() == null) {
return 0;
}
int corners = RoundedViewHelper.TOP_LEFT | RoundedViewHelper.TOP_RIGHT;
if (cardConfiguration.getBodyConfiguration() == null &&
cardConfiguration.getActionsConfiguration() != null &&
cardConfiguration.getActionsConfiguration().getStyle() == ActionsConfiguration.ActionsStyle.Detached) {
corners |= RoundedViewHelper.BOTTOM_LEFT | RoundedViewHelper.BOTTOM_RIGHT;
}
return corners;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
LayoutParams params = (LayoutParams) assetView.getLayoutParams();
// Params can return null if the assetView is not yet attached to the screen.
if (params == null) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
params.height = LayoutParams.WRAP_CONTENT;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int assetHeight = assetView.getMeasuredHeight();
int contentHeight = contentView.getMeasuredHeight() + (padding * 2);
params.height = Math.max(assetHeight, contentHeight);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
roundedViewHelper.onLayout(changed, l, t, r, b);
}
@Override
public void draw(Canvas canvas) {
roundedViewHelper.preDraw(canvas);
super.draw(canvas);
}
}
================================================
FILE: Android/notifications-example/build.gradle
================================================
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.3"
defaultConfig {
applicationId "com.facebook.notifications.sample"
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
repositories {
mavenCentral()
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:23.1.1'
compile 'com.android.support:design:23.1.1'
compile 'com.google.android.gms:play-services:8.4.0'
compile 'com.facebook.android:facebook-android-sdk:4.+'
compile 'commons-io:commons-io:2.4'
compile project(':notifications')
apply plugin: 'com.google.gms.google-services'
}
================================================
FILE: Android/notifications-example/google-services.json
================================================
{
"project_info": {
"project_id": "fbiantestapp",
"project_number": "903525066365",
"name": "FBIANTestApp"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:903525066365:android:43f558a669a2aaaa",
"client_id": "android:com.facebook.notifications.sample",
"client_type": 1,
"android_client_info": {
"package_name": "com.facebook.notifications.sample",
"certificate_hash": []
}
},
"oauth_client": [],
"api_key": [],
"services": {
"analytics_service": {
"status": 1
},
"cloud_messaging_service": {
"status": 2,
"apns_config": []
},
"appinvite_service": {
"status": 1,
"other_platform_oauth_client": []
},
"google_signin_service": {
"status": 1
},
"ads_service": {
"status": 1
}
}
}
],
"client_info": [],
"ARTIFACT_VERSION": "1"
}
================================================
FILE: Android/notifications-example/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/richardross/Library/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
================================================
FILE: Android/notifications-example/src/main/AndroidManifest.xml
================================================
================================================
FILE: Android/notifications-example/src/main/assets/example1.json
================================================
{
"fb_push_card": {
"version" : "1.0",
"dismissColor": "#334D5CFF",
"size": "small",
"cornerRadius": 12.0,
"contentInset" : 10,
"backdropColor": "#000000CC",
"hero": {
"background": {
"_type": "Color",
"rgbaHex": "#FFFFFFFF"
},
"content": {
"_type": "StyledText",
"size": 17,
"text": "There are item(s) in your cart",
"align": "left",
"color": "#334D5CFF"
},
"contentAlign" : "bottom"
},
"body": {
"background": {
"_type": "Color",
"rgbaHex": "#FFFFFFFF"
},
"content": {
"_type": "StyledText",
"size": 15,
"text": "Oops! It seems like you left some item(s) in your cart. You can remove them or checkout.",
"align": "left",
"color": "#334D5CFF"
}
},
"alert" : {
"title": "Oops!",
"body" : "There are item(s) in your cart!"
},
"actions": {
"style": "attached",
"layoutStyle" : "vertical",
"background": {
"_type": "Color",
"rgbaHex": "#FFFFFFFF"
},
"contentInset" : 10,
"cornerRadius" : 8,
"actions": [
{
"backgroundColor": "#61B6E5FF",
"content": {
"_type": "StyledText",
"size": 18,
"text": "GO TO CART",
"color": "#FFFFFFFF"
},
"url": "https://parse.com/"
}
]
}
}
}
================================================
FILE: Android/notifications-example/src/main/assets/example2.json
================================================
{
"fb_push_payload" : {
"campaign" : "2"
},
"fb_push_card": {
"version": "1.0",
"size": "medium",
"cornerRadius": 12,
"contentInset" : 16,
"backdropColor": "#334D5CF5",
"hero": {
"background": {
"_type": "Color",
"rgbaHex": "#50CCB4FF"
},
"content": {
"_type": "StyledText",
"size": 24,
"text": "Introducing Messaging!",
"font": "system-bold",
"align": "center",
"color": "#FFFFFFFF"
}
},
"body": {
"background": {
"_type": "Color",
"rgbaHex": "#FFFFFFFF"
},
"content": {
"_type": "StyledText",
"size": 16,
"text": "The AnyApp team is proud to launch our in app messaging feature! Give it a try today and message your friends.",
"font": "system-light",
"align": "left",
"color": "#334D5CFF"
}
},
"alert" : {
"body" : "Introducing Messaging!"
},
"actions": {
"style": "attached",
"layoutStyle" : "horizontal",
"actionsHeight" : 60.0,
"actions": [
{
"backgroundColor": "#C6D9E0FF",
"title": "Not Now",
"content": {
"_type": "StyledText",
"size": 18,
"text": "Not Now",
"color": "#FFFFFFFF"
}
},
{
"backgroundColor": "#9167EFFF",
"content": {
"_type": "StyledText",
"size": 18,
"text": "Try It",
"color": "#FFFFFFFF"
},
"url": "https://messenger.com"
}
]
}
}
}
================================================
FILE: Android/notifications-example/src/main/assets/example3.json
================================================
{
"fb_push_payload" : {
"campaign" : "3"
},
"fb_push_card": {
"version": "1.0",
"size": "medium",
"cornerRadius": 16,
"contentInset" : 16,
"backdropColor": "#332D2DF0",
"hero": {
"background": {
"_type": "GIF",
"url": "https://s3.amazonaws.com/f.cl.ly/items/3S3a3I0a0l2w3M2v2h30/fall.gif"
}
},
"body": {
"background": {
"_type": "Color",
"rgbaHex": "#FFFFFFFF"
},
"content": {
"_type": "StyledText",
"size": 14,
"text": "September 22nd marks the first day of fall and the release of our new fall clothing line. We’re celebrating with a 20% off sale!",
"align": "left",
"color": "#33251FFF"
}
},
"alert" : {
"title": "New Clothing Line",
"body" : "20% fall sale!"
},
"actions": {
"style": "attached",
"layoutStyle" : "horizontal",
"contentInset" : 16.0,
"cornerRadius" : 6,
"background": {
"_type": "Color",
"rgbaHex": "#FFFFFFFF"
},
"actions": [
{
"backgroundColor": "#690200FF",
"content": {
"_type": "StyledText",
"size": 18,
"text": "Yes, please!",
"color": "#FFFFFFFF"
},
"url": "https://parse.com/"
},
{
"backgroundColor": "#CCC4BCFF",
"content": {
"_type": "StyledText",
"size": 18,
"text": "No, thanks!",
"color": "#FFFFFFFF"
}
}
]
}
}
}
================================================
FILE: Android/notifications-example/src/main/assets/example4.json
================================================
{
"fb_push_payload" : {
"campaign" : "4"
},
"fb_push_card": {
"version": "1.0",
"size": "medium",
"cornerRadius": 20,
"backdropColor": "#332D2DF0",
"dismissColor": "#000000FF",
"hero": {
"background": {
"_type": "Image",
"url": "https://s3.amazonaws.com/f.cl.ly/items/3n2d2G3V3w2h261x0r0i/pizzzza.png"
}
},
"alert" : {
"title": "Yum!",
"body" : "Bob's now delivers!"
},
"actions": {
"style": "detached",
"layoutStyle" : "horizontal",
"topInset": 20,
"cornerRadius": 20,
"actions": [
{
"borderColor": "#FFC84DFF",
"borderWidth" : 2,
"content": {
"_type": "StyledText",
"size": 18,
"text": "Deliver Me Pizzza",
"color": "#FFC84DFF"
},
"url": "https://facebook.com/"
}
]
}
}
}
================================================
FILE: Android/notifications-example/src/main/assets/example5.json
================================================
{
"fb_push_payload" : {
"campaign" : "5"
},
"fb_push_card": {
"version": "1.0",
"size": "large",
"contentInset" : 20,
"backdropColor": "#FFF9F8F5",
"hero": {
"background": {
"_type": "Image",
"url": "https://s3.amazonaws.com/f.cl.ly/items/2s0J0S1a0B2G1n0b421R/PirateBooty.png"
}
},
"alert" : {
"title": "Yarr!",
"body" : "Get the free pirate expansion!"
},
"actions": {
"style": "attached",
"layoutStyle" : "vertical",
"contentInset" : 10.0,
"height" : 50,
"cornerRadius" : 8,
"actions": [
{
"backgroundColor": "#EF5B5BFF",
"content": {
"_type": "StyledText",
"size": 18,
"text": "Get the free pirate expansion",
"color": "#FFFFFFFF"
},
"url": "http://worth1000.s3.amazonaws.com/submissions/384000/384447_858b_625x1000.jpg"
},
{
"backgroundColor": "#00000000",
"content": {
"_type": "StyledText",
"size": 14,
"text": "Nah, I don't like free",
"color": "#EF5B5BFF"
}
}
]
}
}
}
================================================
FILE: Android/notifications-example/src/main/java/com/facebook/notifications/sample/MainActivity.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.sample;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import com.facebook.FacebookSdk;
import com.facebook.notifications.NotificationCardResult;
import com.facebook.notifications.NotificationsManager;
import org.apache.commons.io.IOUtils;
import org.json.JSONObject;
import java.io.InputStream;
import java.io.StringWriter;
import java.nio.charset.Charset;
public class MainActivity extends AppCompatActivity {
private static final String LOG_TAG = MainActivity.class.getCanonicalName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FacebookSdk.sdkInitialize(getApplicationContext());
NotificationsManager.presentCardFromNotification(this);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// register for GCM
Intent intent = new Intent(this, RegistrationIntentService.class);
startService(intent);
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
NotificationsManager.presentCardFromNotification(this, intent);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
NotificationCardResult result = NotificationsManager.handleActivityResult(requestCode, resultCode, data);
if (result != null) {
Toast.makeText(this, "Result: " + result.getActionUri(), Toast.LENGTH_LONG).show();
}
}
/**
* Mock an example push notification bundle from one of our local example JSON files.
* @param exampleId The example id of the asset to load
* @return a bundle with the contents of the specified example id
*/
@NonNull
private Bundle getBundle(int exampleId) {
try {
InputStream inputStream = getAssets().open("example" + exampleId + ".json");
StringWriter output = new StringWriter();
IOUtils.copy(inputStream, output, Charset.forName("UTF-8"));
JSONObject json = new JSONObject(output.toString());
Bundle bundle = new Bundle();
bundle.putString("fb_push_card", json.getJSONObject("fb_push_card").toString());
JSONObject pushPayload = json.optJSONObject("fb_push_payload");
if (pushPayload != null) {
bundle.putString("fb_push_payload", pushPayload.toString());
}
output.close();
inputStream.close();
return bundle;
} catch (Exception ex) {
Log.e(LOG_TAG, "Error while getting bundle", ex);
return new Bundle();
}
}
public void showExample(View view) {
Bundle exampleBundle = getBundle(Integer.parseInt(view.getTag().toString()));
NotificationsManager.presentCard(this, exampleBundle);
}
public void showNotification(View view) {
Bundle exampleBundle = getBundle(Integer.parseInt(view.getTag().toString()));
NotificationsManager.presentNotification(this, exampleBundle, getIntent());
}
}
================================================
FILE: Android/notifications-example/src/main/java/com/facebook/notifications/sample/MyGcmListenerService.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.sample;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import com.facebook.notifications.NotificationsManager;
import com.google.android.gms.gcm.GcmListenerService;
/**
* Sample GCM listener service
*/
public class MyGcmListenerService extends GcmListenerService {
private static final String LOG_TAG = MyGcmListenerService.class.getCanonicalName();
@Override
public void onMessageReceived(String from, final Bundle data) {
Log.v(LOG_TAG, "onMessageReceived(" + from + ", " + data + ")");
NotificationsManager.presentNotification(
this,
data,
new Intent(getApplicationContext(), MainActivity.class)
);
}
}
================================================
FILE: Android/notifications-example/src/main/java/com/facebook/notifications/sample/MyInstanceIDListenerService.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.sample;
import android.content.Intent;
import com.google.android.gms.iid.InstanceIDListenerService;
public class MyInstanceIDListenerService extends InstanceIDListenerService {
@Override
public void onTokenRefresh() {
Intent intent = new Intent(this, RegistrationIntentService.class);
startService(intent);
}
}
================================================
FILE: Android/notifications-example/src/main/java/com/facebook/notifications/sample/RegistrationIntentService.java
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package com.facebook.notifications.sample;
import android.app.IntentService;
import android.content.Intent;
import android.util.Log;
import com.google.android.gms.gcm.GoogleCloudMessaging;
import com.google.android.gms.iid.InstanceID;
public class RegistrationIntentService extends IntentService {
private static final String LOG_TAG = RegistrationIntentService.class.getCanonicalName();
public RegistrationIntentService() {
super(LOG_TAG);
}
@Override
protected void onHandleIntent(Intent intent) {
try {
synchronized (this) {
InstanceID instanceID = InstanceID.getInstance(this);
String token = instanceID.getToken(getString(R.string.gcm_defaultSenderId), GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);
Log.i(LOG_TAG, "GCM Registration Token: " + token);
}
} catch (Exception e) {
Log.e(LOG_TAG, "Failed to complete token refresh", e);
}
}
}
================================================
FILE: Android/notifications-example/src/main/res/layout/activity_main.xml
================================================
================================================
FILE: Android/notifications-example/src/main/res/layout/content_main.xml
================================================
================================================
FILE: Android/notifications-example/src/main/res/menu/menu_main.xml
================================================
================================================
FILE: Android/notifications-example/src/main/res/values/colors.xml
================================================
#3F51B5
#303F9F
#FF4081
================================================
FILE: Android/notifications-example/src/main/res/values/dimens.xml
================================================
16dp
16dp
16dp
================================================
FILE: Android/notifications-example/src/main/res/values/strings.xml
================================================
FBNotificationsSample
Settings
Show Example #1
Show Example #2
Show Example #3
Show Example #4
Show Example #5
Show Notification #1
Show Notification #2
Show Notification #3
Show Notification #4
Show Notification #5
================================================
FILE: Android/notifications-example/src/main/res/values/styles.xml
================================================
================================================
FILE: Android/notifications-example/src/main/res/values-v21/styles.xml
================================================
>
================================================
FILE: Android/notifications-example/src/main/res/values-w820dp/dimens.xml
================================================
64dp
================================================
FILE: Android/settings.gradle
================================================
include ':notifications', ':notifications-example'
================================================
FILE: CHANGELOG-Android.md
================================================
# Facebook In-App Notifications for Android Changelog
## [1.0.2] - 2016-06-01
### Added
- Added automatic logging for push opened and back button events.
[#9](https://github.com/facebook/FBNotifications/pull/9)
by [Nikita Lutsenko](https://github.com/nlutsenko)
### Updated
- Removed requirement on containing a push campaign identifier in the payload.
[#18](https://github.com/facebook/FBNotifications/pull/18)
by [Nikita Lutsenko](https://github.com/nlutsenko)
- Updated Gradle to latest stable version (2.1.0).
[#18](https://github.com/facebook/FBNotifications/pull/18)
by [Nikita Lutsenko](https://github.com/nlutsenko)
View all issues and pull requests associated with this release [here](https://github.com/facebook/FBNotifications/issues?utf8=✓&q=milestone%3AAndroid-1.0.2)
## [1.0.1] - 2016-04-21
### Added
- Added a method to check if a payload exists for a notification.
[#7](https://github.com/facebook/FBNotifications/pull/7)
by [Richard Ross](https://github.com/richardjrossiii)
- Added convenience method for presenting cards when the activity is relaunched.
[#6](https://github.com/facebook/FBNotifications/pull/6)
by [Nikita Lutsenko](https://github.com/nlutsenko)
### Updated
- Updated Gradle to latest stable version (2.0.0).
[#4](https://github.com/facebook/FBNotifications/pull/4)
by [Nikita Lutsenko](https://github.com/nlutsenko)
### Fixed
- Fixed NullPointerException when there is no HeroView on Android.
[#5](https://github.com/facebook/FBNotifications/pull/5)
by [Nikita Lutsenko](https://github.com/nlutsenko)
View all issues and pull requests associated with this release [here](https://github.com/facebook/FBNotifications/issues?utf8=✓&q=milestone%3AAndroid-1.0.1)
## 1.0 - 2016-04-12
Initial Release
[1.0.1]: (https://github.com/facebook/FBNotifications/releases/tag/android-1.0.1)
================================================
FILE: CHANGELOG-iOS.md
================================================
# Facebook In-App Notifications for iOS Changelog
## [1.0.1] - 2016-05-10
### Added
- Added automatic logging of push opened events on card view appear.
[#10](https://github.com/facebook/FBNotifications/pull/10)
by [Nikita Lutsenko](https://github.com/nlutsenko)
### Fixed
- Fixed incorrect submodule setup blocking installation via Carthage.
[#12](https://github.com/facebook/FBNotifications/pull/12)
by [Nikita Lutsenko](https://github.com/nlutsenko)
View all issues and pull requests associated with this release [here](https://github.com/facebook/FBNotifications/issues?utf8=✓&q=milestone%3AiOS-1.0.1)
## 1.0 - 2016-04-12
Initial Release
[1.0.1]: (https://github.com/facebook/FBNotifications/releases/tag/ios-1.0.1)
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to Facebook In-App Notifications
We want to make contributing to this project as easy and transparent as possible.
## Pull Requests
We actively welcome your pull requests.
1. Fork the repo and create your branch from `master`.
2. If you've added code that should be tested, add tests.
3. If you've changed APIs, update the documentation.
4. Ensure the test suite passes.
5. Make sure your code lints.
6. If you haven't already, complete the Contributor License Agreement ("CLA").
## Contributor License Agreement ("CLA")
In order to accept your pull request, we need you to submit a CLA. You only need
to do this once to work on any of Facebook's open source projects.
Complete your CLA here:
## Issues
We use GitHub issues to track public bugs. Please ensure your description is
clear and has sufficient instructions to be able to reproduce the issue.
Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe
disclosure of security bugs. In those cases, please go through the process
outlined on that page and do not file a public issue.
## Coding Style
* Most importantly, match the existing code style as much as possible.
* Try to keep lines under 140 characters, if possible.
## License
See the `LICENSE` file.
================================================
FILE: FBNotifications.podspec
================================================
Pod::Spec.new do |s|
s.name = 'FBNotifications'
s.version = '1.0.1'
s.license = { :type => 'Facebook Platform License', :file => 'LICENSE' }
s.summary = 'Facebook In-App Notifications Framework'
s.homepage = 'https://developers.facebook.com/products/analytics'
s.authors = { 'Nikita Lutsenko' => 'nlutsenko@me.com' }
s.source = { :git => 'https://github.com/facebook/FBNotifications.git', :tag => s.version.to_s }
s.requires_arc = true
s.ios.deployment_target = '8.0'
s.source_files = 'iOS/FBNotifications/FBNotifications/**/*.{h,m}'
s.public_header_files = 'iOS/FBNotifications/FBNotifications/*.h'
s.frameworks = 'ImageIO', 'MobileCoreServices'
end
================================================
FILE: Format/Format-1.0.md
================================================
# [1.0] Facebook In-App Notifications Format
## Types
We use basic JSON types to describe everything, since it's portable and supported on all platforms.
There are more complex types that form a JSON object by themselves (e.g. %CARD_BACKGROUND%) to eliminate ambiguity on the values as well as to be future compatible. When you see anything that starts and ends with a '%' - it means it's a custom type, that has separate piece of documentation in this document.
## Card Components
These components are attached to specific portions of content and are used only in a single place.
### %CARD_PAYLOAD%
Push Card consists of 3 basic parts: Hero, Body, Actions, which are going to be represented by JSON Objects.
Each one is going to be described in a separate section, in addition to the payload itself.
This is a description for the basic payload, since it has few more things:
```
{
"alert" : %ALERT%,
"version" : "1.0", // "major.minor.patch", where 'patch' is optional.
"size" : "small" // Values: "small", "medium", "large". Non-optional.
"cornerRadius" : 2.0, // Default: 0
"contentInset" : 5.0, // Default: 10.0
"backdropColor" : "#ABABABFF", // RGBA Hex Value. Default: #00000000.
"dismissColor" : "#ABABABFF", // RGBA Hex Value. Default: #000000FF.
"hero" : %CARD_HERO%, // Optional
"body" : %CARD_BODY%, // Optional
"actions" : %CARD_ACTIONS% // Optional
}
```
### %ALERT%
This is used to specify the local system notification title/body.
```
{
"title" : "Yarr!",
"body" : "The quick brown fox jumps over the lazy dawg."
}
```
### %CARD_HERO%
```
{
"height" : 0.5, // 0.0 to 1.0. Optional.
"background" : %IMAGE%, // Values: %IMAGE%/%COLOR%/%GIF%
"content" : %STYLED_TEXT%, // Optional.
"contentAlign" : "center" // Values: "top"/"center"/"bottom". Default: "center".
}
```
### %CARD_BODY%
```
{
"background" : %IMAGE%, // Values: %IMAGE% or %COLOR%
"content" : %STYLED_TEXT% // Optional.
}
```
### %CARD_ACTIONS%
```
{
"style" : "attached", // Values: "attached" or "detached"
"layoutStyle" : "horizontal", // Values: "horizontal" or "vertical"
"background" : %COLOR%, // Values: %IMAGE% or %COLOR%
"height" : 50.0, // Default: 44.0
"topInset" : 5.0, // Default: 0.0
"contentInset" : 5.0, // Default: 0.0
"cornerRadius": 0.0, // Default: 0.0
"actions" : [ %CARD_ACTION% ] // Array of actions
}
```
### %CARD_ACTION%
```
{
"backgroundColor" : "#ABABABFF", // RGBA Hex Value. Optional.
"borderColor" : "#ABABABFF", // RGBA Hex Value. Optional.
"borderWidth" : 2.0, // Default: 0.0
"content" : %STYLED_TEXT%, // Optional
"url" : "https://parse.com/" // Optional. Will dismiss card if none set.
}
```
## Basic Components
These components could be used in different types throughout the payload.
### %IMAGE%
```
{
"_type" : "Image",
"url" : "https://parse.com/favicon.ico"
}
```
### %COLOR%
```
{
"_type" : "Color",
"rgbaHex" : "#101010FF" // RGBA Hex Value
}
```
### %GIF%
```
{
"_type" : "GIF",
"url" : "https://parse.com/yolo.gif"
}
```
### %STYLED_TEXT%
```
{
"_type" : "StyledText",
"align" : "left", // Values: "left" or "center" or "right".
"size" : 15.0,
"font" : %FONT%, // Optional. Default: System Font in Regular weight.
"text" : "Hello World!",
"color" : "#EDEDEDFF" // RGBA Hex Value
}
```
### %FONT%
A string representation of the font name with optional weight.
Built-in values that should be handled are:
```
"system-regular"
"system-light"
"system-bold"
"system-italic"
"system-bolditalic"
```
## Example Payload
```json
{
"fb_push_payload" : {
"campaign" : "3"
},
"fb_push_card": {
"version": "1.0",
"size": "medium",
"cornerRadius": 16,
"contentInset" : 16,
"backdropColor": "#332D2DF0",
"hero": {
"background": {
"_type": "GIF",
"url": "https://s3.amazonaws.com/f.cl.ly/items/3S3a3I0a0l2w3M2v2h30/fall.gif"
}
},
"body": {
"background": {
"_type": "Color",
"rgbaHex": "#FFFFFFFF"
},
"content": {
"_type": "StyledText",
"size": 14,
"text": "September 22nd marks the first day of fall and the release of our new fall clothing line. We’re celebrating with a 20% off sale!",
"align": "left",
"color": "#33251FFF"
}
},
"alert" : {
"title": "New Clothing Line",
"body" : "20% fall sale!"
},
"actions": {
"style": "attached",
"layoutStyle" : "horizontal",
"contentInset" : 16.0,
"cornerRadius" : 6,
"background": {
"_type": "Color",
"rgbaHex": "#FFFFFFFF"
},
"actions": [
{
"backgroundColor": "#690200FF",
"content": {
"_type": "StyledText",
"size": 18,
"text": "Yes, please!",
"color": "#FFFFFFFF"
},
"url": "https://parse.com/"
},
{
"backgroundColor": "#CCC4BCFF",
"content": {
"_type": "StyledText",
"size": 18,
"text": "No, thanks!",
"color": "#FFFFFFFF"
}
}
]
}
}
}
```
================================================
FILE: Format/Format-Changelog.md
================================================
# Facebook In-App Notifications Format Changelog
## 1.0
Initial Release
================================================
FILE: LICENSE
================================================
Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
For Facebook In-App Notifications Framework software
You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
copy, modify, and distribute this software in source code or binary form for use
in connection with the web services and APIs provided by Facebook.
As with any software that integrates with the Facebook platform, your use of
this software is subject to the Facebook Developer Principles and Policies
[http://developers.facebook.com/policy/]. This copyright notice shall be
included in all copies or substantial portions of the software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: LICENSE-specification
================================================
Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
For In-App Notifications Format specification in Format/
Creative Commons Attribution 4.0 International Public License
By exercising the Licensed Rights (defined below), You accept and agree
to be bound by the terms and conditions of this Creative Commons
Attribution 4.0 International Public License ("Public License"). To the
extent this Public License may be interpreted as a contract, You are
granted the Licensed Rights in consideration of Your acceptance of
these terms and conditions, and the Licensor grants You such rights in
consideration of benefits the Licensor receives from making the
Licensed Material available under these terms and conditions.
Section 1 -- Definitions.
a. Adapted Material means material subject to Copyright and Similar
Rights that is derived from or based upon the Licensed Material
and in which the Licensed Material is translated, altered,
arranged, transformed, or otherwise modified in a manner requiring
permission under the Copyright and Similar Rights held by the
Licensor. For purposes of this Public License, where the Licensed
Material is a musical work, performance, or sound recording,
Adapted Material is always produced where the Licensed Material is
synched in timed relation with a moving image.
b. Adapter's License means the license You apply to Your Copyright
and Similar Rights in Your contributions to Adapted Material in
accordance with the terms and conditions of this Public License.
c. Copyright and Similar Rights means copyright and/or similar rights
closely related to copyright including, without limitation,
performance, broadcast, sound recording, and Sui Generis Database
Rights, without regard to how the rights are labeled or
categorized. For purposes of this Public License, the rights
specified in Section 2(b)(1)-(2) are not Copyright and Similar
Rights.
d. Effective Technological Measures means those measures that, in the
absence of proper authority, may not be circumvented under laws
fulfilling obligations under Article 11 of the WIPO Copyright
Treaty adopted on December 20, 1996, and/or similar international
agreements.
e. Exceptions and Limitations means fair use, fair dealing, and/or
any other exception or limitation to Copyright and Similar Rights
that applies to Your use of the Licensed Material.
f. Licensed Material means the artistic or literary work, database,
or other material to which the Licensor applied this Public
License.
g. Licensed Rights means the rights granted to You subject to the
terms and conditions of this Public License, which are limited to
all Copyright and Similar Rights that apply to Your use of the
Licensed Material and that the Licensor has authority to license.
h. Licensor means the individual(s) or entity(ies) granting rights
under this Public License.
i. Share means to provide material to the public by any means or
process that requires permission under the Licensed Rights, such
as reproduction, public display, public performance, distribution,
dissemination, communication, or importation, and to make material
available to the public including in ways that members of the
public may access the material from a place and at a time
individually chosen by them.
j. Sui Generis Database Rights means rights other than copyright
resulting from Directive 96/9/EC of the European Parliament and of
the Council of 11 March 1996 on the legal protection of databases,
as amended and/or succeeded, as well as other essentially
equivalent rights anywhere in the world.
k. You means the individual or entity exercising the Licensed Rights
under this Public License. Your has a corresponding meaning.
Section 2 -- Scope.
a. License grant.
1. Subject to the terms and conditions of this Public License,
the Licensor hereby grants You a worldwide, royalty-free,
non-sublicensable, non-exclusive, irrevocable license to
exercise the Licensed Rights in the Licensed Material to:
a. reproduce and Share the Licensed Material, in whole or
in part; and
b. produce, reproduce, and Share Adapted Material.
2. Exceptions and Limitations. For the avoidance of doubt, where
Exceptions and Limitations apply to Your use, this Public
License does not apply, and You do not need to comply with
its terms and conditions.
3. Term. The term of this Public License is specified in Section
6(a).
4. Media and formats; technical modifications allowed. The
Licensor authorizes You to exercise the Licensed Rights in
all media and formats whether now known or hereafter created,
and to make technical modifications necessary to do so. The
Licensor waives and/or agrees not to assert any right or
authority to forbid You from making technical modifications
necessary to exercise the Licensed Rights, including
technical modifications necessary to circumvent Effective
Technological Measures. For purposes of this Public License,
simply making modifications authorized by this Section 2(a)
(4) never produces Adapted Material.
5. Downstream recipients.
a. Offer from the Licensor -- Licensed Material. Every
recipient of the Licensed Material automatically
receives an offer from the Licensor to exercise the
Licensed Rights under the terms and conditions of this
Public License.
b. No downstream restrictions. You may not offer or impose
any additional or different terms or conditions on, or
apply any Effective Technological Measures to, the
Licensed Material if doing so restricts exercise of the
Licensed Rights by any recipient of the Licensed
Material.
6. No endorsement. Nothing in this Public License constitutes or
may be construed as permission to assert or imply that You
are, or that Your use of the Licensed Material is, connected
with, or sponsored, endorsed, or granted official status by,
the Licensor or others designated to receive attribution as
provided in Section 3(a)(1)(A)(i).
b. Other rights.
1. Moral rights, such as the right of integrity, are not
licensed under this Public License, nor are publicity,
privacy, and/or other similar personality rights; however, to
the extent possible, the Licensor waives and/or agrees not to
assert any such rights held by the Licensor to the limited
extent necessary to allow You to exercise the Licensed
Rights, but not otherwise.
2. Patent and trademark rights are not licensed under this
Public License.
3. To the extent possible, the Licensor waives any right to
collect royalties from You for the exercise of the Licensed
Rights, whether directly or through a collecting society
under any voluntary or waivable statutory or compulsory
licensing scheme. In all other cases the Licensor expressly
reserves any right to collect such royalties.
Section 3 -- License Conditions.
Your exercise of the Licensed Rights is expressly made subject to the
following conditions.
a. Attribution.
1. If You Share the Licensed Material (including in modified
form), You must:
a. retain the following if it is supplied by the Licensor
with the Licensed Material:
i. identification of the creator(s) of the Licensed
Material and any others designated to receive
attribution, in any reasonable manner requested by
the Licensor (including by pseudonym if
designated);
ii. a copyright notice;
iii. a notice that refers to this Public License;
iv. a notice that refers to the disclaimer of
warranties;
v. a URI or hyperlink to the Licensed Material to the
extent reasonably practicable;
b. indicate if You modified the Licensed Material and
retain an indication of any previous modifications; and
c. indicate the Licensed Material is licensed under this
Public License, and include the text of, or the URI or
hyperlink to, this Public License.
2. You may satisfy the conditions in Section 3(a)(1) in any
reasonable manner based on the medium, means, and context in
which You Share the Licensed Material. For example, it may be
reasonable to satisfy the conditions by providing a URI or
hyperlink to a resource that includes the required
information.
3. If requested by the Licensor, You must remove any of the
information required by Section 3(a)(1)(A) to the extent
reasonably practicable.
4. If You Share Adapted Material You produce, the Adapter's
License You apply must not prevent recipients of the Adapted
Material from complying with this Public License.
Section 4 -- Sui Generis Database Rights.
Where the Licensed Rights include Sui Generis Database Rights that
apply to Your use of the Licensed Material:
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
to extract, reuse, reproduce, and Share all or a substantial
portion of the contents of the database;
b. if You include all or a substantial portion of the database
contents in a database in which You have Sui Generis Database
Rights, then the database in which You have Sui Generis Database
Rights (but not its individual contents) is Adapted Material; and
c. You must comply with the conditions in Section 3(a) if You Share
all or a substantial portion of the contents of the database.
For the avoidance of doubt, this Section 4 supplements and does not
replace Your obligations under this Public License where the Licensed
Rights include other Copyright and Similar Rights.
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
c. The disclaimer of warranties and limitation of liability provided
above shall be interpreted in a manner that, to the extent
possible, most closely approximates an absolute disclaimer and
waiver of all liability.
Section 6 -- Term and Termination.
a. This Public License applies for the term of the Copyright and
Similar Rights licensed here. However, if You fail to comply with
this Public License, then Your rights under this Public License
terminate automatically.
b. Where Your right to use the Licensed Material has terminated under
Section 6(a), it reinstates:
1. automatically as of the date the violation is cured, provided
it is cured within 30 days of Your discovery of the
violation; or
2. upon express reinstatement by the Licensor.
For the avoidance of doubt, this Section 6(b) does not affect any
right the Licensor may have to seek remedies for Your violations
of this Public License.
c. For the avoidance of doubt, the Licensor may also offer the
Licensed Material under separate terms or conditions or stop
distributing the Licensed Material at any time; however, doing so
will not terminate this Public License.
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
License.
Section 7 -- Other Terms and Conditions.
a. The Licensor shall not be bound by any additional or different
terms or conditions communicated by You unless expressly agreed.
b. Any arrangements, understandings, or agreements regarding the
Licensed Material not stated herein are separate from and
independent of the terms and conditions of this Public License.
Section 8 -- Interpretation.
a. For the avoidance of doubt, this Public License does not, and
shall not be interpreted to, reduce, limit, restrict, or impose
conditions on any use of the Licensed Material that could lawfully
be made without permission under this Public License.
b. To the extent possible, if any provision of this Public License is
deemed unenforceable, it shall be automatically reformed to the
minimum extent necessary to make it enforceable. If the provision
cannot be reformed, it shall be severed from this Public License
without affecting the enforceability of the remaining terms and
conditions.
c. No term or condition of this Public License will be waived and no
failure to comply consented to unless expressly agreed to by the
Licensor.
d. Nothing in this Public License constitutes or may be interpreted
as a limitation upon, or waiver of, any privileges and immunities
that apply to the Licensor or You, including from the legal
processes of any jurisdiction or authority.
================================================
FILE: README.md
================================================

![Platforms][platforms-svg]
[![Build Status][build-status-svg]][build-status-link]
Facebook In-App Notifications enables you to create rich and customizable in-app notifications and deliver them via push notifications, based on the actions people take in your app. You can use text, photos, animated GIFs, buttons or extend the open format to suit your needs.
## Getting Started on iOS
[![Podspec][podspec-svg]][podspec-link]
[![Carthage compatible][carthage-svg]](carthage-link)
To get started on iOS, install the framework using one of these options:
- **[CocoaPods](https://cocoapods.org)**
Add the following line to your Podfile:
```ruby
pod 'FBNotifications'
```
Run `pod install`, and you should now have the latest framework installed.
- **[Carthage](https://github.com/carthage/carthage)**
Add the following line to your Cartfile:
```
github "facebook/FBNotifications"
```
Run `carthage update`, and you should now have the latest version of the framework in your Carthage folder.
After you've installed the framework, you would need to add the following to your application delegate to present the notification:
Using *Objective-C*:
```objc
/// Present In-App Notification from remote notification (if present).
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(nonnull void (^)(UIBackgroundFetchResult))completionHandler {
FBNotificationsManager *notificationsManager = [FBNotificationsManager sharedManager];
[notificationsManager presentPushCardForRemoteNotificationPayload:userInfo
fromViewController:nil
completion:^(FBNCardViewController * _Nullable viewController, NSError * _Nullable error) {
if (error) {
completionHandler(UIBackgroundFetchResultFailed);
} else {
completionHandler(UIBackgroundFetchResultNewData);
}
}];
}
```
Using *Swift*:
```swift
/// Present In-App Notification from remote notification (if present).
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
FBNotificationsManager.sharedManager().presentPushCardForRemoteNotificationPayload(userInfo, fromViewController: nil) { viewController, error in
if let _ error = error {
completionHandler(.Failed)
} else {
completionHandler(.NewData)
}
}
}
```
## Getting Started on Android
[![Maven Central][maven-svg]][maven-link]
To get started on Android, add the following to your gradle dependencies:
```gradle
compile 'com.facebook.android:notifications:1.+'
```
After you've added the dependency, you'll have to [set up an FCM listener service](https://firebase.google.com/docs/cloud-messaging/android/client), and add the following to your service:
```java
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Bundle data = new Bundle();
for (Map.Entry entry : remoteMessage.getData().entrySet()) {
data.putString(entry.getKey(), entry.getValue());
}
NotificationsManager.presentNotification(
this,
data,
new Intent(getApplicationContext(), MainActivity.class)
);
}
```
Then when all the content for the notification is ready - it will automatically present the notification to the end user with a pending intent to present a card on open.
To hand-off the necessary data from the intent - you need to handle the notification in the `onCreate` function of your Main Activity:
```java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
NotificationsManager.presentCardFromNotification(this);
}
}
```
For more help on getting started, take a look at our [Facebook Push Campaigns Documentation](https://developers.facebook.com/docs/push-campaigns).
## In-App Notifications Format
In-app notifications are powered by a custom format that has an open specification available in this repository.
The format describes all the possible values and combinations of content that can be rendered by the framework.
We are open to accepting contributions to the format and the format is constantly evolving.
Any version of the framework is compatible with any previous version of the format in the same major version scope.
For example:
- Framework `1.0.0` **is** compatible with format version `1.0`
- Framework `1.0.0` **is not** compatible with format version `1.5`
- Framework `1.5.0` **is** compatible with format version `1.5`
- Framework `1.5.1` **is** compatible with format version `1.5`
- Framework `2.0.0` **is not** compatible with format version `1.0`, or `1.5`
## Contributing
We want to make contributing to this project as easy and transparent as possible. Please refer to the [Contribution Guidelines](https://github.com/facebook/FBNotifications/blob/master/CONTRIBUTING.md).
## License
See the `LICENSE` file for source code.
See the `LICENSE-specification` file for In-App Notifications format specification.
[platforms-svg]: https://img.shields.io/badge/platform-iOS%20%7C%20Android-lightgrey.svg
[build-status-svg]: https://img.shields.io/travis/facebook/FBNotifications/master.svg
[build-status-link]: https://travis-ci.org/facebook/FBNotifications/branches
[podspec-svg]: https://img.shields.io/cocoapods/v/FBNotifications.svg
[podspec-link]: https://cocoapods.org/pods/FBNotifications
[carthage-svg]: https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat
[carthage-link]: https://github.com/carthage/carthage
[maven-svg]: https://maven-badges.herokuapp.com/maven-central/com.facebook.android/notifications/badge.svg?style=flat
[maven-link]: https://maven-badges.herokuapp.com/maven-central/com.facebook.android/notifications
================================================
FILE: iOS/.gitignore
================================================
## OS X
.DS_Store
## Build generated
build/
DerivedData
## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
## Other
*.xccheckout
*.moved-aside
*.xcuserstate
*.xcscmblueprint
## Obj-C/Swift specific
*.hmap
*.ipa
## Dependency Managers
Pods/
Carthage/Build
## AppCode
.idea/
================================================
FILE: iOS/FBNotifications/Configurations/FBNotifications-iOS-Dynamic.xcconfig
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "Shared/Platform/iOS.xcconfig"
#include "Shared/Product/Framework.xcconfig"
PRODUCT_NAME = FBNotifications
PRODUCT_BUNDLE_IDENTIFIER = com.facebook.notifications.ios
IPHONEOS_DEPLOYMENT_TARGET = 8.0
MACH_O_TYPE = mh_dylib
DYLIB_INSTALL_NAME_BASE = @rpath
DEFINES_MODULE = YES
INFOPLIST_FILE = $(PROJECT_DIR)/FBNotifications/Resources/Info.plist
OTHER_CFLAGS[sdk=iphoneos9.*] = $(inherited) -fembed-bitcode
================================================
FILE: iOS/FBNotifications/Configurations/FBNotifications-iOS.xcconfig
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "Shared/Platform/iOS.xcconfig"
#include "Shared/Product/Framework.xcconfig"
PRODUCT_NAME = FBNotifications
PRODUCT_BUNDLE_IDENTIFIER = com.facebook.notifications.ios
IPHONEOS_DEPLOYMENT_TARGET = 8.0
MACH_O_TYPE = staticlib
DEFINES_MODULE = YES
INFOPLIST_FILE = $(PROJECT_DIR)/FBNotifications/Resources/Info.plist
OTHER_CFLAGS[sdk=iphoneos9.*] = $(inherited) -fembed-bitcode
================================================
FILE: iOS/FBNotifications/FBNotifications/FBNCardViewController.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
#import
NS_ASSUME_NONNULL_BEGIN
@class FBNCardViewController;
/**
The `FBNCardViewControllerDelegate` defines methods for delegate of `FBNCardViewController`.
*/
@protocol FBNCardViewControllerDelegate
@optional
/**
Called when a view controller is about to dismiss and open a URL.
@param controller The view controller that called this method.
@param url URL that will be open after the view controller is dismissed.
*/
- (void)pushCardViewController:(FBNCardViewController *)controller willDismissWithOpenURL:(NSURL *)url;
/**
Called when a view controller is about to dismiss without opening any URL.
@param controller The view controller that called this method.
*/
- (void)pushCardViewControllerWillDismiss:(FBNCardViewController *)controller;
@end
/**
`FBNCardViewController` is the main entry point for presenting a card that represents In-App Notification from a payload.
This class encapsulates the display and layout of the entire push card and is intended to be presented full screen.
It is invalid to initialize this class yourself, but it's intended to be used from `FBNotificationsManager`.
*/
@interface FBNCardViewController : UIViewController
/**
The delegate of the card view controller.
*/
@property (nonatomic, weak) id delegate;
///--------------------------------------
#pragma mark - Unavailable Methods
///--------------------------------------
/**
Allocates memory and initializes a new instance into it.
@warning This method is unavaialble. Please use `FBNotificationsManager` to create the view controller.
*/
+ (instancetype)new NS_UNAVAILABLE;
/**
Initializes a new instance.
@warning This method is unavaialble. Please use `FBNotificationsManager` to create the view controller.
*/
- (instancetype)init NS_UNAVAILABLE;
/**
Returns an object initiailized from data in a given unarchiver.
@param decoder The unarchiver object.
@warning This method is unavaialble. Please use `FBNotificationsManager` to create the view controller.
*/
- (nullable instancetype)initWithCoder:(NSCoder *)decoder NS_UNAVAILABLE;
/**
Returns a newly initialized view controller with the nib file in the specified bundle.
@param nibNameOrNil The name of the nib file to associate with the view controller or `nil`.
@param nibBundleOrNil he bundle in which to search for the nib file or `nil`.
@warning This method is unavaialble. Please use `FBNotificationsManager` to create the view controller.
*/
- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/FBNCardViewController.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNCardViewController.h"
#import "FBNCardViewController_Internal.h"
#import "FBNAssetsController.h"
#import "FBNAssetContentCache.h"
#import "FBNCardDisplayOptions.h"
#import "FBNCardHeroViewController.h"
#import "FBNCardBodyViewController.h"
#import "FBNCardActionsViewController.h"
#import "FBNCardDismissButton.h"
#import "FBNCardViewUtilities.h"
#import "FBNCardConfiguration.h"
#import "FBNCardHeroConfiguration.h"
#import "FBNCardBodyConfiguration.h"
#import "FBNCardActionsConfiguration.h"
#import "FBNCardActionConfiguration.h"
#import "FBNCardAppEventsLogger.h"
#import "FBNCardColor.h"
@interface FBNCardViewController ()
@property (nullable, nonatomic, copy, readonly) NSString *campaignIdentifier;
@property (nonatomic, strong, readonly) FBNAssetsController *assetsController;
@property (nonatomic, strong, readonly) FBNCardDisplayOptions *cardDisplayOptions;
@property (nullable, nonatomic, strong) FBNCardConfiguration *configuration;
@property (nonatomic, strong) UIView *contentView;
@property (nullable, nonatomic, strong) UIActivityIndicatorView *loadingIndicatorView;
@property (nullable, nonatomic, strong) FBNCardHeroViewController *heroViewController;
@property (nullable, nonatomic, strong) FBNCardBodyViewController *bodyViewController;
@property (nullable, nonatomic, strong) FBNCardActionsViewController *actionsViewController;
@property (nullable, nonatomic, strong) FBNCardDismissButton *dismissButton;
@end
@implementation FBNCardViewController
///--------------------------------------
#pragma mark - Init
///--------------------------------------
- (instancetype)initWithPushCardPayload:(FBNCardPayload *)payload
campaignIdentifier:(nullable NSString *)campaignIdentifier
assetsController:(FBNAssetsController *)assetsController {
self = [super initWithNibName:nil bundle:nil];
if (!self) return self;
_payload = [payload copy];
_cardDisplayOptions = [FBNCardDisplayOptions displayOptionsFromDictionary:payload];
_campaignIdentifier = [campaignIdentifier copy];
_assetsController = assetsController;
[self _reloadConfiguration];
self.modalPresentationStyle = UIModalPresentationOverFullScreen;
self.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
return self;
}
- (void)dealloc {
[self.dismissButton removeTarget:nil action:nil forControlEvents:UIControlEventAllEvents];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
///--------------------------------------
#pragma mark - View
///--------------------------------------
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if ([UIApplication sharedApplication].applicationState != UIApplicationStateBackground) {
[self _logPushOpen];
} else {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(_applicationWillEnterForeground)
name:UIApplicationWillEnterForegroundNotification
object:nil];
}
}
///--------------------------------------
#pragma mark - Configuration
///--------------------------------------
- (void)_reloadConfiguration {
if ([self.assetsController hasCachedContentForCardPayload:self.payload]) {
[FBNCardConfiguration loadFromDictionary:self.payload
withDisplayOptions:self.cardDisplayOptions
assetsController:self.assetsController
completion:^(FBNCardConfiguration * _Nullable configuration) {
dispatch_block_t reloadBlock = ^{
self.configuration = configuration;
if ([self isViewLoaded]) {
[self _reloadSubviews];
}
};
if ([NSThread isMainThread]) {
reloadBlock();
} else {
dispatch_async(dispatch_get_main_queue(), reloadBlock);
}
}];
} else {
__weak typeof(self) wself = self;
[self.assetsController cacheAssetContentForCardPayload:self.payload completion:^{
[wself _reloadConfiguration];
}];
}
}
///--------------------------------------
#pragma mark - Subviews
///--------------------------------------
- (void)_reloadSubviews {
self.view.backgroundColor = self.cardDisplayOptions.backdropColor;
self.contentView = [[UIView alloc] initWithFrame:CGRectZero];
self.contentView.layer.cornerRadius = self.cardDisplayOptions.cornerRadius;
self.contentView.clipsToBounds = YES;
[self.view addSubview:self.contentView];
if (self.configuration) {
[self.loadingIndicatorView stopAnimating];
[self.loadingIndicatorView removeFromSuperview];
self.loadingIndicatorView = nil;
[self _reloadContentViews];
} else {
FBNCardContrastColor contrastColor = FBNCardContrastColorForColor(self.view.backgroundColor);
UIActivityIndicatorViewStyle indicatorViewStyle = (contrastColor == FBNCardContrastColorBlack ?
UIActivityIndicatorViewStyleGray :
UIActivityIndicatorViewStyleWhite);
self.loadingIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:indicatorViewStyle];
[self.view addSubview:self.loadingIndicatorView];
[self.loadingIndicatorView startAnimating];
}
}
- (void)_reloadContentViews {
if (self.configuration.heroConfiguration) {
self.heroViewController = [[FBNCardHeroViewController alloc] initWithAssetsController:self.assetsController
configuration:self.configuration.heroConfiguration
contentInset:self.cardDisplayOptions.contentInset];
[self addChildViewController:self.heroViewController];
[self.contentView addSubview:self.heroViewController.view];
[self.heroViewController didMoveToParentViewController:self];
}
if (self.configuration.bodyConfiguration) {
self.bodyViewController = [[FBNCardBodyViewController alloc] initWithAssetsController:self.assetsController
configuration:self.configuration.bodyConfiguration
contentInset:self.cardDisplayOptions.contentInset];
[self addChildViewController:self.bodyViewController];
[self.contentView addSubview:self.bodyViewController.view];
[self.bodyViewController didMoveToParentViewController:self];
}
if (self.configuration.actionsConfiguration) {
self.actionsViewController = [[FBNCardActionsViewController alloc] initWithAssetsController:self.assetsController
configuration:self.configuration.actionsConfiguration];
self.actionsViewController.delegate = self;
[self addChildViewController:self.actionsViewController];
switch (self.configuration.actionsConfiguration.style) {
case FBNCardActionsStyleAttached:
[self.contentView addSubview:self.actionsViewController.view];
break;
case FBNCardActionsStyleDetached:
[self.view addSubview:self.actionsViewController.view];
break;
default:break;
}
[self.actionsViewController didMoveToParentViewController:self];
}
[self _reloadDismissButton];
}
- (void)_reloadDismissButton {
BOOL showsDismissButton = YES;
for (FBNCardActionConfiguration *action in self.configuration.actionsConfiguration.actions) {
if (!action.actionURL) {
showsDismissButton = NO;
break;
}
}
if (showsDismissButton) {
self.dismissButton = [[FBNCardDismissButton alloc] initWithFrame:CGRectZero];
self.dismissButton.imageColor = self.cardDisplayOptions.dismissButtonColor;
[self.dismissButton addTarget:self action:@selector(_dismissButtonAction) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.dismissButton];
}
}
///--------------------------------------
#pragma mark - UIViewController
///--------------------------------------
- (void)loadView {
[super loadView];
self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self _reloadSubviews];
}
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
CGRect bounds = self.view.bounds;
if (self.cardDisplayOptions.size == FBNCardSizeLarge) {
bounds.size.height -= self.topLayoutGuide.length;
bounds.origin.y += self.topLayoutGuide.length;
}
CGSize availableSize = FBNCardLayoutSizeThatFits(self.cardDisplayOptions.size, bounds.size);
const CGSize bodySize = CGSizeMake(availableSize.width,
[self.bodyViewController contentSizeThatFitsParentContainerSize:availableSize].height);
availableSize.height -= bodySize.height;
const CGSize actionsSize = CGSizeMake(availableSize.width,
[self.actionsViewController contentSizeThatFitsParentContainerSize:availableSize].height);
availableSize.height -= actionsSize.height;
CGSize heroSize = availableSize;
if (self.configuration.heroConfiguration.height == FBNCardHeroHeightUnspecified) {
heroSize.height = [self.heroViewController contentSizeThatFitsParentContainerSize:availableSize].height;
} else {
heroSize.height *= self.configuration.heroConfiguration.height;
}
heroSize = FBNSizeAdjustToScreenScale(heroSize, NSRoundUp);
CGSize contentSize = CGSizeMake(availableSize.width, bodySize.height + heroSize.height);
const CGRect heroFrame = FBNRectMakeWithOriginSize(CGPointZero, heroSize);
const CGRect bodyFrame = FBNRectMakeWithOriginSize(CGPointMake(CGRectGetMinX(heroFrame), CGRectGetMaxY(heroFrame)), bodySize);
CGRect actionsFrame = FBNRectMakeWithOriginSize(CGPointZero, actionsSize);
CGRect contentFrame = CGRectZero;
switch (self.configuration.actionsConfiguration.style) {
case FBNCardActionsStyleAttached: {
actionsFrame.origin = CGPointMake(CGRectGetMinX(heroFrame), CGRectGetMaxY(bodyFrame));
contentSize.height += actionsSize.height;
contentFrame = FBNRectMakeWithSizeCenteredInRect(contentSize, bounds);
}
break;
case FBNCardActionsStyleDetached: {
CGPoint contentOrigin = FBNRectMakeWithSizeCenteredInRect(CGSizeMake(contentSize.width, contentSize.height + actionsSize.height),
bounds).origin;
contentFrame = FBNRectMakeWithOriginSize(contentOrigin, contentSize);
actionsFrame.origin = CGPointMake(CGRectGetMinX(contentFrame), CGRectGetMaxY(contentFrame));
}
break;
default:break;
}
CGRect dismissButtonFrame = FBNRectMakeWithOriginSize(CGPointZero, [self.dismissButton sizeThatFits:bounds.size]);
dismissButtonFrame.origin.x = CGRectGetMaxX(contentFrame) - CGRectGetWidth(dismissButtonFrame) - self.cardDisplayOptions.contentInset;
dismissButtonFrame.origin.y = CGRectGetMinY(contentFrame) + self.cardDisplayOptions.contentInset;
self.contentView.frame = contentFrame;
self.heroViewController.view.frame = heroFrame;
self.bodyViewController.view.frame = bodyFrame;
self.actionsViewController.view.frame = actionsFrame;
self.dismissButton.frame = dismissButtonFrame;
self.loadingIndicatorView.center = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds));
}
///--------------------------------------
#pragma mark - Application State Changes
///--------------------------------------
- (void)_applicationWillEnterForeground {
[self _logPushOpen];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil];
}
///--------------------------------------
#pragma mark - Logging Events
///--------------------------------------
- (void)_logPushOpen {
[FBNCardAppEventsLogger logCardOpenWithCampaignIdentifier:self.campaignIdentifier];
}
///--------------------------------------
#pragma mark - Dismiss
///--------------------------------------
- (void)_dismissFromButtonAction:(FBNCardButtonAction)action withOpenURL:(nullable NSURL *)url {
[FBNCardAppEventsLogger logButtonAction:action forCardWithCampaignIdentifier:self.campaignIdentifier];
id delegate = self.delegate;
if (url) {
if ([delegate respondsToSelector:@selector(pushCardViewController:willDismissWithOpenURL:)]) {
[delegate pushCardViewController:self willDismissWithOpenURL:url];
}
[[UIApplication sharedApplication] openURL:url];
} else {
if ([delegate respondsToSelector:@selector(pushCardViewControllerWillDismiss:)]) {
[delegate pushCardViewControllerWillDismiss:self];
}
}
[self.assetsController clearAssetContentCacheForCardPayload:self.payload];
[self dismissViewControllerAnimated:YES completion:nil];
}
///--------------------------------------
#pragma mark - FBNCardActionsViewControllerDelegate
///--------------------------------------
- (void)actionsViewController:(FBNCardActionsViewController *)viewController
didPerformButtonAction:(FBNCardButtonAction)action
withOpenURL:(nullable NSURL *)url {
[self _dismissFromButtonAction:action withOpenURL:url];
}
///--------------------------------------
#pragma mark - Dismiss Button
///--------------------------------------
- (void)_dismissButtonAction {
[self _dismissFromButtonAction:FBNCardButtonActionDismiss withOpenURL:nil];
}
@end
================================================
FILE: iOS/FBNotifications/FBNotifications/FBNConstants.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
NS_ASSUME_NONNULL_BEGIN
/**
Error domain used for all of the errors in FBNotifications framework.
*/
extern NSString *FBNotificationsErrorDomain;
typedef NS_ENUM(NSUInteger, FBNotificationsErrorCode) {
/**
Error code indicating that notification payload is invalid.
*/
FBNotificationsErrorInvalidPayload = 1,
};
/**
String that represents a highest supported card format by the framework.
*/
extern NSString *FBNotificationsCardFormatVersionString;
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/FBNConstants.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNConstants.h"
NSString *FBNotificationsErrorDomain = @"FBNotificationsErrorDomain";
NSString *FBNotificationsCardFormatVersionString = @"1.0";
================================================
FILE: iOS/FBNotifications/FBNotifications/FBNotifications.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
#import
#import
================================================
FILE: iOS/FBNotifications/FBNotifications/FBNotificationsManager.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
#import
#import
NS_ASSUME_NONNULL_BEGIN
/**
Block type used as a completion for content preparation from remote notification payload.
@param payload Payload for which content was fetched.
@param error An instance of `NSError` that represents the error if it happened, otherwise - `nil`.
*/
typedef void(^FBNCardContentPreparationCompletion)(NSDictionary *_Nullable payload, NSError *_Nullable error);
/**
Block type used as a completion for card view controller presentation from remote notification payload.
@param viewController A card view controller that was presented.
@param error An instance of `NSError` that represents the error if it happened, otherwise - `nil`.
*/
typedef void(^FBNCardPresentationCompletion)(FBNCardViewController *_Nullable viewController, NSError *_Nullable error);
/**
Block type used as a completion for local notification creation from remote notification payload.
@param notification Newly created notification if the operation succeded, otherwise - `nil`.
@param error An instance of `NSError` that represents the error if it happened, otherwise - `nil`.
*/
typedef void(^FBNLocalNotificationCreationCompletion)(UILocalNotification *_Nullable notification, NSError *_Nullable error);
/**
`FBNotificationsManager` is a primary interface for interacting with FBNotifications.framework.
*/
@interface FBNotificationsManager : NSObject
///--------------------------------------
#pragma mark - Creating a Card Manager
///--------------------------------------
/**
Returns a shared notifications manager for the current process.
@return Shared notifications manager.
*/
+ (instancetype)sharedManager;
/**
Initializes a new instance.
@warning This method is unavaialble. Please use `sharedManager`.
*/
- (instancetype)init NS_UNAVAILABLE;
/**
Allocates memory and initializes a new instance into it.
@warning This method is unavaialble. Please use `sharedManager`.
*/
+ (instancetype)new NS_UNAVAILABLE;
///--------------------------------------
#pragma mark - Present from Remote Notification
///--------------------------------------
/**
Fetch push card content from a remote notification payload and cache it for later use.
If the payload doesn't contain a push card - completion block is going to be called with an error.
@param payload Remote notification payload.
@param completion Completion block that will be called when content fetch is done or there was an error.
*/
- (void)preparePushCardContentForRemoteNotificationPayload:(NSDictionary *)payload
completion:(nullable FBNCardContentPreparationCompletion)completion;
/**
Presents a push card from a remote notification if it contains one.
If content is not fully available for a given push card, a loading indicator will be shown instead of a card.
@param payload Remote notification payload.
@param viewController View controller to present a push card from (optional).
@param completion Optional completion block that will be called when a card is visible or there was an error.
*/
- (void)presentPushCardForRemoteNotificationPayload:(NSDictionary *)payload
fromViewController:(nullable UIViewController *)viewController
completion:(nullable FBNCardPresentationCompletion)completion;
/**
Returns a `BOOL` value that designated whether a push notification payload is a valid push card payload.
@param payload Push notification payload.
@return `YES` if payload is a valid push card payload, otherwise - `NO`.
*/
- (BOOL)canPresentPushCardFromRemoteNotificationPayload:(nullable NSDictionary *)payload;
///--------------------------------------
#pragma mark - Present from Local Notification
///--------------------------------------
/**
Creates a local notification from a remote notification payload (if present).
The completion will be called when all content is cached for the in app notification or if there was an error.
@param payload Remote notification payload, usually acquired via `application:didReceiveRemoteNotification:`.
@param completion A block that will be called after the content is ready for presentation or if there was an error.
*/
- (void)createLocalNotificationFromRemoteNotificationPayload:(NSDictionary *)payload
completion:(FBNLocalNotificationCreationCompletion)completion;
/**
Presents a push card from a local notification if it contains one.
@param notification A local notification.
@param viewController View controller to present a push card from (optional).
@param completion Optional completion block that will be called when a card is visible or there was an error.
*/
- (void)presentPushCardForLocalNotification:(UILocalNotification *)notification
fromViewController:(nullable UIViewController *)viewController
completion:(nullable FBNCardPresentationCompletion)completion;
/**
Returns a boolean value that indicates whether a push card can be presented from a local notification.
@param notification Local notification to check.
@return `YES` if push card can be presented, otherwise - `NO`.
*/
- (BOOL)canPresentPushCardFromLocalNotification:(UILocalNotification *)notification;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/FBNotificationsManager.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNotificationsManager.h"
#import "FBNConstants.h"
#import "FBNCardError.h"
#import "FBNAssetContentCache.h"
#import "FBNCardViewUtilities.h"
#import "FBNCardViewController_Internal.h"
#import "FBNCardPayload.h"
#import "FBNCardAppEventsLogger.h"
#import "FBNAssetsController.h"
#import "FBNColorAsset.h"
#import "FBNColorAssetController.h"
#import "FBNImageAsset.h"
#import "FBNImageAssetController.h"
#import "FBNGIFAsset.h"
#import "FBNGIFAssetController.h"
@interface FBNotificationsManager ()
@property (nonnull, nonatomic, strong, readonly) FBNAssetsController *assetsController;
@property (nullable, nonatomic, weak) FBNCardViewController *currentCardViewController;
@end
@implementation FBNotificationsManager
///--------------------------------------
#pragma mark - Creating a Card Manager
///--------------------------------------
- (instancetype)init {
self = [super init];
if (!self) return self;
_assetsController = [self _defaultConfigurationAssetsController];
return self;
}
+ (instancetype)sharedManager {
static FBNotificationsManager *manager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[self alloc] init];
});
return manager;
}
///--------------------------------------
#pragma mark - Assets Controller
///--------------------------------------
- (FBNAssetsController *)_defaultConfigurationAssetsController {
FBNAssetsController *controller = [[FBNAssetsController alloc] init];
[controller registerAssetController:[[FBNColorAssetController alloc] init] forAssetType:FBNColorAssetType];
[controller registerAssetController:[[FBNImageAssetController alloc] init] forAssetType:FBNImageAssetType];
[controller registerAssetController:[[FBNGIFAssetController alloc] init] forAssetType:FBNGIFAssetType];
return controller;
}
///--------------------------------------
#pragma mark - Present from Remote Notification
///--------------------------------------
- (void)preparePushCardContentForRemoteNotificationPayload:(NSDictionary *)payload
completion:(nullable FBNCardContentPreparationCompletion)completion {
if (![self canPresentPushCardFromRemoteNotificationPayload:payload]) {
if (completion) {
completion(nil, [FBNCardError invalidRemoteNotificationPayloadError]);
}
return;
}
[self.assetsController cacheAssetContentForCardPayload:payload completion:^{
if (completion) {
completion(payload, nil);
}
}];
}
- (void)presentPushCardForRemoteNotificationPayload:(NSDictionary *)payload
fromViewController:(nullable UIViewController *)viewController
completion:(nullable FBNCardPresentationCompletion)completion {
if (![self canPresentPushCardFromRemoteNotificationPayload:payload]) {
if (completion) {
completion(nil, [FBNCardError invalidRemoteNotificationPayloadError]);
}
return;
}
FBNCardPayload *cardPayload = FBNCardPayloadFromRemoteNotificationPayload(payload);
NSString *campaignIdentifier = [FBNCardAppEventsLogger campaignIdentifierFromRemoteNotificationPayload:payload];
FBNCardViewController *oldCardViewController = self.currentCardViewController;
// Has the same payload and is visible on screen, meaning that we don't need to re-present it.
if ([oldCardViewController.payload isEqual:cardPayload] &&
oldCardViewController.presentingViewController != nil &&
!oldCardViewController.isBeingDismissed) {
if (completion) {
completion(oldCardViewController, nil);
}
return;
}
// Dismiss the old one.
[oldCardViewController dismissViewControllerAnimated:NO completion:nil];
// Create and present the new one.
FBNCardViewController *cardViewController = [[FBNCardViewController alloc] initWithPushCardPayload:cardPayload
campaignIdentifier:campaignIdentifier
assetsController:self.assetsController];
viewController = viewController ?: FBNApplicationTopMostViewController();
BOOL animated = (oldCardViewController == nil);
[viewController presentViewController:cardViewController animated:animated completion:^{
if (completion) {
completion(cardViewController, nil);
}
}];
// Save the new one into a weak property.
self.currentCardViewController = cardViewController;
}
- (BOOL)canPresentPushCardFromRemoteNotificationPayload:(nullable NSDictionary *)payload {
FBNCardPayload *cardPayload = FBNCardPayloadFromRemoteNotificationPayload(payload);
return (cardPayload != nil && FBNCardPayloadIsCompatibleWithCurrentVersion(cardPayload, FBNotificationsCardFormatVersionString));
}
///--------------------------------------
#pragma mark - Present via Local Notification
///--------------------------------------
- (void)createLocalNotificationFromRemoteNotificationPayload:(NSDictionary *)payload
completion:(FBNLocalNotificationCreationCompletion)completion {
if (![self canPresentPushCardFromRemoteNotificationPayload:payload]) {
NSError *error = [FBNCardError invalidRemoteNotificationPayloadError];
completion(nil, error);
return;
}
FBNCardPayload *cardPayload = FBNCardPayloadFromRemoteNotificationPayload(payload);
[self.assetsController cacheAssetContentForCardPayload:cardPayload completion:^{
UILocalNotification *notification = [self _localNotificationFromPayload:cardPayload];
notification.userInfo = payload;
completion(notification, nil);
}];
}
- (void)presentPushCardForLocalNotification:(UILocalNotification *)notification
fromViewController:(nullable UIViewController *)viewController
completion:(nullable FBNCardPresentationCompletion)completion {
[[UIApplication sharedApplication] cancelLocalNotification:notification];
[self presentPushCardForRemoteNotificationPayload:notification.userInfo
fromViewController:viewController
completion:completion];
}
- (BOOL)canPresentPushCardFromLocalNotification:(UILocalNotification *)notification {
return [self canPresentPushCardFromRemoteNotificationPayload:notification.userInfo];
}
- (nullable UILocalNotification *)_localNotificationFromPayload:(NSDictionary *)payload {
UILocalNotification *notification = [[UILocalNotification alloc] init];
notification.alertTitle = payload[@"alert"][@"title"];
notification.alertBody = payload[@"alert"][@"body"];
return notification;
}
@end
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/AppEvents/FBNCardAppEventsLogger.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
#import "FBNCardButtonAction.h"
NS_ASSUME_NONNULL_BEGIN
@interface FBNCardAppEventsLogger : NSObject
+ (nullable NSString *)campaignIdentifierFromRemoteNotificationPayload:(NSDictionary *)payload;
+ (void)logCardOpenWithCampaignIdentifier:(nullable NSString *)identifier;
+ (void)logButtonAction:(FBNCardButtonAction)action forCardWithCampaignIdentifier:(nullable NSString *)identifier;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/AppEvents/FBNCardAppEventsLogger.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNCardAppEventsLogger.h"
NS_ASSUME_NONNULL_BEGIN
@interface FBSDKAppEvents : NSObject
+ (void)logEvent:(NSString *)eventName parameters:(nullable NSDictionary *)parameters;
@end
@implementation FBNCardAppEventsLogger
///--------------------------------------
#pragma mark - Public
///--------------------------------------
+ (nullable NSString *)campaignIdentifierFromRemoteNotificationPayload:(NSDictionary *)payload {
return payload[@"fb_push_payload"][@"campaign"];
}
+ (void)logCardOpenWithCampaignIdentifier:(nullable NSString *)identifier {
if (!identifier) {
return;
}
[self _logAppEventWithName:@"fb_mobile_push_opened" campaignIdentifier:identifier];
}
+ (void)logButtonAction:(FBNCardButtonAction)action forCardWithCampaignIdentifier:(nullable NSString *)identifier {
NSString *eventName = [self _appEventNameForButtonAction:action];
[self _logAppEventWithName:eventName campaignIdentifier:identifier];
}
///--------------------------------------
#pragma mark - Private
///--------------------------------------
+ (void)_logAppEventWithName:(NSString *)name campaignIdentifier:(NSString *)identifier {
if (!identifier) {
return;
}
Class loggerClass = NSClassFromString(@"FBSDKAppEvents");
if (loggerClass && [loggerClass respondsToSelector:@selector(logEvent:parameters:)]) {
[loggerClass logEvent:name
parameters:@{ @"fb_push_campaign" : identifier }];
}
}
+ (nullable NSString *)_appEventNameForButtonAction:(FBNCardButtonAction)action {
switch (action) {
case FBNCardButtonActionPrimary:
return @"fb_mobile_push_card_action_primary";
case FBNCardButtonActionSecondary:
return @"fb_mobile_push_card_action_secondary";
case FBNCardButtonActionDismiss:
return @"fb_mobile_push_card_action_dismiss";
default:break;
}
return nil;
}
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Asset/Cache/FBNAssetContentCache.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
#import "FBNCardPayload.h"
@class FBNAssetsController;
NS_ASSUME_NONNULL_BEGIN
@interface FBNAssetContentCache : NSObject
- (void)cacheContentForURLs:(NSSet *)urls completion:(dispatch_block_t)completion;
- (void)clearContentForURLs:(nullable NSSet *)urls;
- (nullable NSData *)cachedDataForContentURL:(NSURL *)url;
- (BOOL)hasCachedContentForURLs:(nullable NSSet *)urls;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Asset/Cache/FBNAssetContentCache.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNAssetContentCache.h"
#import
#import "FBNAssetsController.h"
#import "FBNCardHash.h"
#import "FBNAssetContentCacheOperation.h"
@interface FBNAssetContentCache ()
@property (nonatomic, strong, readonly) NSURLSession *session;
@property (nonatomic, copy, readonly) NSMutableDictionary *> *cacheOperations;
@property (nonatomic, copy) NSMutableSet *cachedKeys;
@property (nonatomic, strong, readonly) dispatch_queue_t synchronizationQueue;
@property (nonatomic, assign) UIBackgroundTaskIdentifier currentBackgroundTask;
@end
@implementation FBNAssetContentCache
///--------------------------------------
#pragma mark - Init
///--------------------------------------
- (instancetype)init {
self = [super init];
if (!self) return self;
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
_session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
_cacheOperations = [NSMutableDictionary dictionary];
_synchronizationQueue = dispatch_queue_create("com.facebook.cards.cache.sync", DISPATCH_QUEUE_SERIAL);
_currentBackgroundTask = UIBackgroundTaskInvalid;
return self;
}
///--------------------------------------
#pragma mark - Cache
///--------------------------------------
- (void)cacheContentForURLs:(NSSet *)urls completion:(dispatch_block_t)completion {
[self _beginBackgroundTask];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
FBNAssetContentCacheOperation *operation = [[FBNAssetContentCacheOperation alloc] initWithContentURLs:urls completion:completion];
[self _startDownloadTasksForCacheOperation:operation];
});
}
- (void)clearContentForURLs:(nullable NSSet *)urls {
if (!urls.count) {
return;
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (NSURL *url in urls) {
NSString *cacheKey = [self _cacheKeyForContentURL:url];
[[NSFileManager defaultManager] removeItemAtPath:[self _cacheFilePathForContentWithCacheKey:cacheKey] error:nil];
dispatch_async(_synchronizationQueue, ^{
[self.cachedKeys removeObject:cacheKey];
});
}
});
}
///--------------------------------------
#pragma mark - Getters
///--------------------------------------
- (NSData *)cachedDataForContentURL:(NSURL *)url {
NSString *cacheKey = [self _cacheKeyForContentURL:url];
return [NSData dataWithContentsOfFile:[self _cacheFilePathForContentWithCacheKey:cacheKey]
options:NSDataReadingMappedIfSafe
error:nil];
}
- (BOOL)hasCachedContentForURLs:(nullable NSSet *)urls {
for (NSURL *url in urls) {
NSString *cacheKey = [self _cacheKeyForContentURL:url];
if (![self _hasCachedDataForContentWithCacheKey:cacheKey]) {
return NO;
}
}
return YES;
}
- (BOOL)_hasCachedDataForContentWithCacheKey:(NSString *)key {
return [self.cachedKeys containsObject:key];
}
- (NSString *)_cacheFilesFolderPath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
return [paths.firstObject stringByAppendingPathComponent:@"FBNotifications"];
}
- (NSString *)_cacheFilePathForContentWithCacheKey:(NSString *)key {
return [[self _cacheFilesFolderPath] stringByAppendingPathComponent:key];
}
///--------------------------------------
#pragma mark - Private
///--------------------------------------
///--------------------------------------
#pragma mark - Accessors
///--------------------------------------
- (NSMutableSet *)cachedKeys {
if (!_cachedKeys) {
NSArray *existingFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[self _cacheFilesFolderPath] error:nil];
_cachedKeys = [NSMutableSet setWithArray:existingFiles];
}
return _cachedKeys;
}
///--------------------------------------
#pragma mark - Hash
///--------------------------------------
- (NSString *)_cacheKeyForContentURL:(NSURL *)url {
return FBMD5HashFromString(url.absoluteString);
}
///--------------------------------------
#pragma mark - Content
///--------------------------------------
- (void)_startDownloadTasksForCacheOperation:(FBNAssetContentCacheOperation *)operation {
dispatch_async(_synchronizationQueue, ^{
NSUInteger scheduledCount = 0;
NSSet *cacheURLs = [operation.pendingCacheURLs copy];
for (NSURL *url in cacheURLs) {
NSString *cacheKey = [self _cacheKeyForContentURL:url];
if ([self _hasCachedDataForContentWithCacheKey:cacheKey]) {
[operation.pendingCacheURLs removeObject:url];
continue;
}
NSMutableSet *cacheOperations = self.cacheOperations[cacheKey];
if (cacheOperations) {
[cacheOperations addObject:operation];
} else {
cacheOperations = [NSMutableSet setWithObject:operation];
self.cacheOperations[cacheKey] = cacheOperations;
[[self.session downloadTaskWithURL:url] resume];
}
scheduledCount++;
}
BOOL endBackgroundTask = (self.cacheOperations.count == 0);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if (scheduledCount == 0 && operation.completion) {
dispatch_async(dispatch_get_main_queue(), operation.completion);
}
if (endBackgroundTask) {
[self _endBackgroundTask];
}
});
});
}
- (void)_didDownloadContentWithCacheKey:(NSString *)cacheKey toURL:(NSURL *)url {
NSString *cacheFolderPath = [self _cacheFilesFolderPath];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:cacheFolderPath]) {
[fileManager createDirectoryAtPath:cacheFolderPath withIntermediateDirectories:YES attributes:nil error:nil];
}
NSString *targetPath = [self _cacheFilePathForContentWithCacheKey:cacheKey];
[fileManager moveItemAtPath:url.path toPath:targetPath error:nil]; // TODO: Track Error
}
- (void)_didFinishDownloadTaskWithCacheKey:(NSString *)cacheKey fromURL:(NSURL *)url {
dispatch_async(_synchronizationQueue, ^{
[self.cachedKeys addObject:cacheKey];
NSSet *operations = self.cacheOperations[cacheKey];
NSMutableSet *finishedOperations = [NSMutableSet set];
for (FBNAssetContentCacheOperation *operation in operations) {
[operation.pendingCacheURLs removeObject:url];
if (operation.pendingCacheURLs.count == 0) {
[finishedOperations addObject:operation];
}
}
[self.cacheOperations removeObjectForKey:cacheKey];
BOOL endBackgroundTask = (self.cacheOperations.count == 0);
dispatch_async(dispatch_get_main_queue(), ^{
for (FBNAssetContentCacheOperation *operation in finishedOperations) {
if (operation.completion) {
operation.completion();
}
}
if (endBackgroundTask) {
[self _endBackgroundTask];
}
});
});
}
///--------------------------------------
#pragma mark - NSURLSessionDownloadDelegate
///--------------------------------------
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
NSString *cacheKey = [self _cacheKeyForContentURL:downloadTask.originalRequest.URL];
[self _didDownloadContentWithCacheKey:cacheKey toURL:location];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
NSURL *url = task.originalRequest.URL;
NSString *cacheKey = [self _cacheKeyForContentURL:url];
[self _didFinishDownloadTaskWithCacheKey:cacheKey fromURL:url];
}
///--------------------------------------
#pragma mark - Background Task
///--------------------------------------
- (void)_beginBackgroundTask {
if (self.currentBackgroundTask == UIBackgroundTaskInvalid) {
self.currentBackgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self _endBackgroundTask];
}];
}
}
- (void)_endBackgroundTask {
if (self.currentBackgroundTask != UIBackgroundTaskInvalid) {
UIBackgroundTaskIdentifier task = self.currentBackgroundTask;
self.currentBackgroundTask = UIBackgroundTaskInvalid;
[[UIApplication sharedApplication] endBackgroundTask:task];
}
}
@end
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Asset/Cache/FBNAssetContentCacheOperation.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
NS_ASSUME_NONNULL_BEGIN
typedef NSString FBNAssetContentCacheOperationId;
@interface FBNAssetContentCacheOperation : NSObject
@property (nonatomic, copy, readonly) NSMutableSet *pendingCacheURLs;
@property (nonatomic, copy, readonly) dispatch_block_t completion;
- (instancetype)initWithContentURLs:(NSSet *)urls completion:(dispatch_block_t)completion;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Asset/Cache/FBNAssetContentCacheOperation.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNAssetContentCacheOperation.h"
NS_ASSUME_NONNULL_BEGIN
@implementation FBNAssetContentCacheOperation
- (instancetype)initWithContentURLs:(NSSet *)urls completion:(dispatch_block_t)completion {
self = [super init];
if (!self) return self;
_pendingCacheURLs = [urls mutableCopy];
_completion = completion;
return self;
}
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Asset/Color/FBNColorAsset.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
#import "FBNAsset.h"
NS_ASSUME_NONNULL_BEGIN
extern NSString *const FBNColorAssetType;
@interface FBNColorAsset : NSObject
@property (nonatomic, strong, readonly) UIColor *color;
- (instancetype)initWithRGBAHex:(NSString *)hexColor;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Asset/Color/FBNColorAsset.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNColorAsset.h"
#import "FBNCardColor.h"
NS_ASSUME_NONNULL_BEGIN
NSString *const FBNColorAssetType = @"Color";
@implementation FBNColorAsset
///--------------------------------------
#pragma mark - Init
///--------------------------------------
- (instancetype)initWithRGBAHex:(NSString *)hexColor {
self = [super init];
if (!self) return self;
_color = FBNCardColorFromRGBAHex(hexColor);
return self;
}
///--------------------------------------
#pragma mark - FBNColorAsset
///--------------------------------------
- (NSString *)type {
return FBNColorAssetType;
}
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Asset/Color/FBNColorAssetController.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
#import "FBNAssetController.h"
NS_ASSUME_NONNULL_BEGIN
@interface FBNColorAssetController : NSObject
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Asset/Color/FBNColorAssetController.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNColorAssetController.h"
#import
#import "FBNColorAsset.h"
#import "FBNColorAssetViewController.h"
NS_ASSUME_NONNULL_BEGIN
@implementation FBNColorAssetController
///--------------------------------------
#pragma mark - FBNAssetController
///--------------------------------------
- (void)loadAssetFromDictionary:(NSDictionary *)dictionary
contentCache:(nonnull FBNAssetContentCache *)cache
completion:(void (^)(id _Nullable asset))completion
{
if (![self isValidAssetDictionary:dictionary]) {
return;
}
NSString *rgbaHex = dictionary[@"rgbaHex"];
completion([[FBNColorAsset alloc] initWithRGBAHex:rgbaHex]);
}
- (nullable NSSet *)cacheURLsForAssetDictionary:(NSDictionary *)dictionary {
return nil;
}
- (BOOL)isValidAssetDictionary:(NSDictionary *)dictionary {
if (![dictionary isKindOfClass:[NSDictionary class]] ||
![dictionary[@"_type"] isEqualToString:@"Color"] ||
![dictionary[@"rgbaHex"] isKindOfClass:[NSString class]]) {
return NO;
}
return YES;
}
- (nullable UIViewController *)viewControllerForAsset:(id)asset {
FBNColorAsset *colorAsset = (FBNColorAsset *)asset;
return [[FBNColorAssetViewController alloc] initWithAsset:colorAsset];
}
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Asset/Color/FBNColorAssetViewController.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
#import "FBNContentSizeProvider.h"
@class FBNColorAsset;
NS_ASSUME_NONNULL_BEGIN
@interface FBNColorAssetViewController : UIViewController
- (instancetype)initWithAsset:(FBNColorAsset *)asset;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Asset/Color/FBNColorAssetViewController.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNColorAssetViewController.h"
#import "FBNColorAsset.h"
NS_ASSUME_NONNULL_BEGIN
@interface FBNColorAssetViewController ()
@property (nonatomic, strong, readonly) FBNColorAsset *asset;
@end
@implementation FBNColorAssetViewController
///--------------------------------------
#pragma mark - Init
///--------------------------------------
- (instancetype)initWithAsset:(FBNColorAsset *)asset {
self = [super initWithNibName:nil bundle:nil];
if (!self) return self;
_asset = asset;
return self;
}
///--------------------------------------
#pragma mark - View
///--------------------------------------
- (void)loadView {
[super loadView];
self.view.backgroundColor = self.asset.color;
}
///--------------------------------------
#pragma mark - FBNContentSizeProvider
///--------------------------------------
- (CGSize)contentSizeThatFitsParentContainerSize:(CGSize)fitSize {
return CGSizeZero;
}
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Asset/Common/FBNAsset.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
#import
NS_ASSUME_NONNULL_BEGIN
@protocol FBNAsset
@property (nonatomic, copy, readonly) NSString *type;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Asset/Common/FBNAssetController.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
#import "FBNContentSizeProvider.h"
@protocol FBNAsset;
@class FBNAssetContentCache;
NS_ASSUME_NONNULL_BEGIN
@protocol FBNAssetController
- (void)loadAssetFromDictionary:(NSDictionary *)dictionary
contentCache:(nonnull FBNAssetContentCache *)cache
completion:(void (^)(id _Nullable asset))completion;
- (nullable NSSet *)cacheURLsForAssetDictionary:(NSDictionary *)dictionary;
- (BOOL)isValidAssetDictionary:(NSDictionary *)dictionary;
- (nullable UIViewController *)viewControllerForAsset:(id)asset;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Asset/FBNAssetsController.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
#import "FBNCardPayload.h"
@protocol FBNAsset;
@protocol FBNAssetController;
@class FBNAssetContentCache;
@protocol FBNContentSizeProvider;
NS_ASSUME_NONNULL_BEGIN
@interface FBNAssetsController : NSObject
///--------------------------------------
#pragma mark - Asset Controllers
///--------------------------------------
- (void)registerAssetController:(id)controller forAssetType:(NSString *)type;
- (nullable id)assetControllerForAssetType:(NSString *)type;
///--------------------------------------
#pragma mark - Assets
///--------------------------------------
- (void)loadAssetFromDictionary:(NSDictionary *)dictionary completion:(nonnull void (^)(id _Nullable asset))completion;
- (nullable UIViewController *)viewControllerForAsset:(id)asset;
///--------------------------------------
#pragma mark - Cache
///--------------------------------------
- (void)cacheAssetContentForCardPayload:(FBNCardPayload *)payload completion:(dispatch_block_t)completion;
- (void)clearAssetContentCacheForCardPayload:(FBNCardPayload *)payload;
- (BOOL)hasCachedContentForCardPayload:(FBNCardPayload *)payload;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Asset/FBNAssetsController.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNAssetsController.h"
#import "FBNAsset.h"
#import "FBNAssetContentCache.h"
#import "FBNAssetController.h"
NS_ASSUME_NONNULL_BEGIN
@interface FBNAssetsController ()
@property (nonatomic, strong, readonly) NSMutableDictionary> *assetControllers;
@property (nonatomic, strong, readonly) FBNAssetContentCache *contentCache;
@end
@implementation FBNAssetsController
///--------------------------------------
#pragma mark - Init
///--------------------------------------
- (instancetype)init {
self = [super init];
if (!self) return self;
_assetControllers = [NSMutableDictionary dictionary];
_contentCache = [[FBNAssetContentCache alloc] init];
return self;
}
///--------------------------------------
#pragma mark - Asset Controllers
///--------------------------------------
- (void)registerAssetController:(id)controller forAssetType:(NSString *)type {
NSAssert(!_assetControllers[type], @"Trying to register asset controller for already registered type %@", type);
self.assetControllers[type] = controller;
}
- (nullable id)assetControllerForAssetType:(NSString *)type {
return self.assetControllers[type];
}
///--------------------------------------
#pragma mark - Assets
///--------------------------------------
- (void)loadAssetFromDictionary:(NSDictionary *)dictionary completion:(nonnull void (^)(id _Nullable asset))completion {
NSString *type = [self _assetTypeFromDictionary:dictionary];
if (!type) {
completion(nil);
return;
}
id controller = [self assetControllerForAssetType:type];
[controller loadAssetFromDictionary:dictionary contentCache:self.contentCache completion:completion];
}
- (nullable UIViewController *)viewControllerForAsset:(id)asset {
id controller = [self assetControllerForAssetType:asset.type];
return [controller viewControllerForAsset:asset];
}
- (nullable NSString *)_assetTypeFromDictionary:(NSDictionary *)dictionary {
return dictionary[@"_type"];
}
- (nullable NSSet *)_assetContentURLsFromAssetDictionary:(NSDictionary *)dictionary {
NSString *type = [self _assetTypeFromDictionary:dictionary];
if (!type) {
return nil;
}
id controller = [self assetControllerForAssetType:type];
return [controller cacheURLsForAssetDictionary:dictionary];
}
///--------------------------------------
#pragma mark - Cache
///--------------------------------------
- (void)cacheAssetContentForCardPayload:(FBNCardPayload *)payload completion:(dispatch_block_t)completion {
NSSet *urls = [self _assetContentURLsFromCardPayload:payload];
if (!urls.count) {
completion();
return;
}
[self.contentCache cacheContentForURLs:urls completion:completion];
}
- (void)clearAssetContentCacheForCardPayload:(FBNCardPayload *)payload {
NSSet *urls = [self _assetContentURLsFromCardPayload:payload];
[self.contentCache clearContentForURLs:urls];
}
- (BOOL)hasCachedContentForCardPayload:(FBNCardPayload *)payload {
NSSet *urls = [self _assetContentURLsFromCardPayload:payload];
return [self.contentCache hasCachedContentForURLs:urls];
}
- (nullable NSSet *)_assetContentURLsFromCardPayload:(FBNCardPayload *)payload {
return [self _assetContentURLsFromCollection:payload];
}
- (nullable NSSet *)_assetContentURLsFromCollection:(id)collection {
NSMutableSet *contentURLs = [NSMutableSet set];
void (^block)(NSMutableSet *, id) = ^(NSMutableSet *urls, id obj){
NSSet *assetURLs = nil;
if ([obj isKindOfClass:[NSDictionary class]]) {
assetURLs = [self _assetContentURLsFromAssetDictionary:obj];
if (assetURLs) {
[urls unionSet:assetURLs];
return;
}
}
assetURLs = [self _assetContentURLsFromCollection:obj];
if (assetURLs) {
[urls unionSet:assetURLs];
}
};
if ([collection isKindOfClass:[NSDictionary class]]) {
[collection enumerateKeysAndObjectsUsingBlock:^(NSString *_, id obj, BOOL *__) {
block(contentURLs, obj);
}];
} else if ([collection isKindOfClass:[NSArray class]]) {
for (id obj in collection) {
block(contentURLs, obj);
}
}
return [contentURLs copy];
}
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Asset/GIF/FBNGIFAsset.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
#import "FBNAsset.h"
NS_ASSUME_NONNULL_BEGIN
extern NSString *const FBNGIFAssetType;
@interface FBNGIFAsset : NSObject
@property (nonatomic, strong, readonly) UIImage *image;
- (instancetype)initWithImage:(UIImage *)image;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Asset/GIF/FBNGIFAsset.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNGIFAsset.h"
NS_ASSUME_NONNULL_BEGIN
NSString *const FBNGIFAssetType = @"GIF";
@implementation FBNGIFAsset
///--------------------------------------
#pragma mark - Init
///--------------------------------------
- (instancetype)initWithImage:(UIImage *)image {
self = [super init];
if (!self) return self;
_image = image;
return self;
}
///--------------------------------------
#pragma mark - FBNAsset
///--------------------------------------
- (NSString *)type {
return FBNGIFAssetType;
}
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Asset/GIF/FBNGIFAssetController.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
#import "FBNAssetController.h"
NS_ASSUME_NONNULL_BEGIN
@interface FBNGIFAssetController : NSObject
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Asset/GIF/FBNGIFAssetController.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNGIFAssetController.h"
#import "FBNGIFAsset.h"
#import "FBNIGIFAssetViewController.h"
#import "FBNAssetContentCache.h"
#import "FBNAnimatedImage.h"
NS_ASSUME_NONNULL_BEGIN
@implementation FBNGIFAssetController
///--------------------------------------
#pragma mark - FBNAssetController
///--------------------------------------
- (void)loadAssetFromDictionary:(NSDictionary *)dictionary
contentCache:(nonnull FBNAssetContentCache *)cache
completion:(void (^)(id _Nullable asset))completion {
if (![self isValidAssetDictionary:dictionary]) {
completion(nil);
return;
}
NSURL *url = [NSURL URLWithString:dictionary[@"url"]];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSData *data = [cache cachedDataForContentURL:url];
UIImage *image = FBNAnimatedImageFromData(data);
FBNGIFAsset *asset = [[FBNGIFAsset alloc] initWithImage:image];
completion(asset);
});
}
- (nullable NSSet *)cacheURLsForAssetDictionary:(NSDictionary *)dictionary {
if (![self isValidAssetDictionary:dictionary]) {
return nil;
}
NSURL *url = [NSURL URLWithString:dictionary[@"url"]];
return (url ? [NSSet setWithObject:url] : nil);
}
- (BOOL)isValidAssetDictionary:(NSDictionary *)dictionary {
if (![dictionary isKindOfClass:[NSDictionary class]] ||
![dictionary[@"_type"] isEqualToString:FBNGIFAssetType] ||
![dictionary[@"url"] isKindOfClass:[NSString class]]) {
return NO;
}
return YES;
}
- (nullable UIViewController *)viewControllerForAsset:(id)asset {
FBNGIFAsset *gifAsset = (FBNGIFAsset *)asset;
return [[FBNGIFAssetViewController alloc] initWithAsset:gifAsset];
}
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Asset/GIF/FBNIGIFAssetViewController.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
#import "FBNContentSizeProvider.h"
@class FBNGIFAsset;
NS_ASSUME_NONNULL_BEGIN
@interface FBNGIFAssetViewController : UIViewController
- (instancetype)initWithAsset:(FBNGIFAsset *)asset;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Asset/GIF/FBNIGIFAssetViewController.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNIGIFAssetViewController.h"
#import "FBNGIFAsset.h"
NS_ASSUME_NONNULL_BEGIN
@interface FBNGIFAssetViewController ()
@property (nonatomic, strong, readonly) FBNGIFAsset *asset;
@end
@implementation FBNGIFAssetViewController
///--------------------------------------
#pragma mark - Init
///--------------------------------------
- (instancetype)initWithAsset:(FBNGIFAsset *)asset {
self = [super initWithNibName:nil bundle:nil];
if (!self) return self;
_asset = asset;
return self;
}
///--------------------------------------
#pragma mark - View
///--------------------------------------
- (void)loadView {
//TODO: (nlutsenko) This should be implemented with a custom view, so we can support gifs with custom delays.
UIImageView *view = [[UIImageView alloc] initWithImage:self.asset.image];
view.contentMode = UIViewContentModeScaleAspectFill;
self.view = view;
}
///--------------------------------------
#pragma mark - FBNContentSizeProvider
///--------------------------------------
- (CGSize)contentSizeThatFitsParentContainerSize:(CGSize)fitSize {
if ([self isViewLoaded]) {
return [self.view sizeThatFits:fitSize];
}
return self.asset.image.size;
}
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Asset/Image/FBNImageAsset.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
#import "FBNAsset.h"
NS_ASSUME_NONNULL_BEGIN
extern NSString *const FBNImageAssetType;
@interface FBNImageAsset : NSObject
@property (nonatomic, strong, readonly) UIImage *image;
- (instancetype)initWithImage:(UIImage *)image;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Asset/Image/FBNImageAsset.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNImageAsset.h"
NS_ASSUME_NONNULL_BEGIN
NSString *const FBNImageAssetType = @"Image";
@implementation FBNImageAsset
///--------------------------------------
#pragma mark - Init
///--------------------------------------
- (instancetype)initWithImage:(UIImage *)image {
self = [super init];
if (!self) return self;
if (!image) return nil;
_image = image;
return self;
}
///--------------------------------------
#pragma mark - FBNAsset
///--------------------------------------
- (NSString *)type {
return FBNImageAssetType;
}
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Asset/Image/FBNImageAssetController.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
#import "FBNAssetController.h"
NS_ASSUME_NONNULL_BEGIN
@interface FBNImageAssetController : NSObject
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Asset/Image/FBNImageAssetController.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNImageAssetController.h"
#import "FBNImageAsset.h"
#import "FBNImageAssetViewController.h"
#import "FBNAssetContentCache.h"
NS_ASSUME_NONNULL_BEGIN
@implementation FBNImageAssetController
///--------------------------------------
#pragma mark - FBNAssetController
///--------------------------------------
- (void)loadAssetFromDictionary:(NSDictionary *)dictionary
contentCache:(nonnull FBNAssetContentCache *)cache
completion:(void (^)(id _Nullable asset))completion {
if (![self isValidAssetDictionary:dictionary]) {
completion(nil);
return;
}
NSURL *url = [NSURL URLWithString:dictionary[@"url"]];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSData *data = [cache cachedDataForContentURL:url];
UIImage *image = [[UIImage alloc] initWithData:data];
FBNImageAsset *asset = [[FBNImageAsset alloc] initWithImage:image];
completion(asset);
});
}
- (nullable NSSet *)cacheURLsForAssetDictionary:(NSDictionary *)dictionary {
if (![self isValidAssetDictionary:dictionary]) {
return nil;
}
NSURL *url = [NSURL URLWithString:dictionary[@"url"]];
return (url ? [NSSet setWithObject:url] : nil);
}
- (BOOL)isValidAssetDictionary:(NSDictionary *)dictionary {
if (![dictionary isKindOfClass:[NSDictionary class]] ||
![dictionary[@"_type"] isEqualToString:FBNImageAssetType] ||
![dictionary[@"url"] isKindOfClass:[NSString class]]) {
return NO;
}
return YES;
}
- (nullable UIViewController *)viewControllerForAsset:(id)asset {
FBNImageAsset *imageAsset = (FBNImageAsset *)asset;
return [[FBNImageAssetViewController alloc] initWithAsset:imageAsset];
}
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Asset/Image/FBNImageAssetViewController.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
#import "FBNContentSizeProvider.h"
@class FBNImageAsset;
NS_ASSUME_NONNULL_BEGIN
@interface FBNImageAssetViewController : UIViewController
- (instancetype)initWithAsset:(FBNImageAsset *)asset;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Asset/Image/FBNImageAssetViewController.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNImageAssetViewController.h"
#import "FBNImageAsset.h"
NS_ASSUME_NONNULL_BEGIN
@interface FBNImageAssetViewController ()
@property (nonatomic, strong, readonly) FBNImageAsset *asset;
@end
@implementation FBNImageAssetViewController
///--------------------------------------
#pragma mark - Init
///--------------------------------------
- (instancetype)initWithAsset:(FBNImageAsset *)asset {
self = [super initWithNibName:nil bundle:nil];
if (!self) return self;
_asset = asset;
return self;
}
///--------------------------------------
#pragma mark - View
///--------------------------------------
- (void)loadView {
UIImageView *view = [[UIImageView alloc] initWithImage:self.asset.image];
view.contentMode = UIViewContentModeScaleAspectFill;
self.view = view;
}
///--------------------------------------
#pragma mark - FBNContentSizeProvider
///--------------------------------------
- (CGSize)contentSizeThatFitsParentContainerSize:(CGSize)fitSize {
if ([self isViewLoaded]) {
return [self.view sizeThatFits:fitSize];
}
return self.asset.image.size;
}
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/FBNCardViewController_Internal.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
#import "FBNCardPayload.h"
@class FBNAssetsController;
@class FBNAssetContentCache;
NS_ASSUME_NONNULL_BEGIN
@interface FBNCardViewController ()
@property (nonatomic, copy, readonly) FBNCardPayload *payload;
///--------------------------------------
#pragma mark - Init
///--------------------------------------
- (instancetype)initWithPushCardPayload:(FBNCardPayload *)payload
campaignIdentifier:(nullable NSString *)campaignIdentifier
assetsController:(FBNAssetsController *)assetsController NS_DESIGNATED_INITIALIZER;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Models/Actions/FBNCardActionConfiguration.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
#import
@class FBNCardTextContent;
NS_ASSUME_NONNULL_BEGIN
@interface FBNCardActionConfiguration : NSObject
///--------------------------------------
#pragma mark - Properties
///--------------------------------------
@property (nullable, nonatomic, strong, readonly) UIColor *backgroundColor;
@property (nullable, nonatomic, strong, readonly) UIColor *borderColor;
@property (nonatomic, assign, readonly) CGFloat borderWidth;
@property (nullable, nonatomic, strong, readonly) FBNCardTextContent *content;
@property (nullable, nonatomic, strong, readonly) NSURL *actionURL;
///--------------------------------------
#pragma mark - Init
///--------------------------------------
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
+ (nullable instancetype)configurationFromDictionary:(nullable NSDictionary *)dictionary;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Models/Actions/FBNCardActionConfiguration.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNCardActionConfiguration.h"
#import "FBNCardColor.h"
#import "FBNCardTextContent.h"
#import "FBNCardViewUtilities.h"
@implementation FBNCardActionConfiguration
///--------------------------------------
#pragma mark - Init
///--------------------------------------
- (instancetype)initFromDictionary:(NSDictionary *)dictionary {
self = [super init];
if (!self) return self;
_backgroundColor = FBNCardColorFromRGBAHex(dictionary[@"backgroundColor"]);
_borderColor = FBNCardColorFromRGBAHex(dictionary[@"borderColor"]);
_borderWidth = FBNCGFloatFromNumber(dictionary[@"borderWidth"]);
_content = [FBNCardTextContent contentFromDictionary:dictionary[@"content"]];
_actionURL = [NSURL URLWithString:dictionary[@"url"]];
return self;
}
+ (nullable instancetype)configurationFromDictionary:(nullable NSDictionary *)dictionary {
if (!dictionary) {
return nil;
}
return [[self alloc] initFromDictionary:dictionary];
}
@end
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Models/Actions/FBNCardActionsConfiguration.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
#import
#import "FBNCardActionsStyle.h"
@class FBNAssetsController;
@class FBNCardActionConfiguration;
@protocol FBNAsset;
NS_ASSUME_NONNULL_BEGIN
@interface FBNCardActionsConfiguration : NSObject
///--------------------------------------
#pragma mark - Properties
///--------------------------------------
@property (nonatomic, assign, readonly) FBNCardActionsStyle style;
@property (nonatomic, assign, readonly) FBNCardActionsLayoutStyle layoutStyle;
@property (nullable, nonatomic, strong, readonly) id background;
@property (nonatomic, assign, readonly) CGFloat height;
@property (nonatomic, assign, readonly) CGFloat topInset;
@property (nonatomic, assign, readonly) CGFloat contentInset;
@property (nonatomic, assign, readonly) CGFloat cornerRadius;
@property (nullable, nonatomic, copy, readonly) NSArray *actions;
///--------------------------------------
#pragma mark - Init
///--------------------------------------
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
+ (void)loadFromDictionary:(nullable NSDictionary *)dictionary
assetsController:(FBNAssetsController *)controller
completion:(void (^)(FBNCardActionsConfiguration *_Nullable configuration))completion;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Models/Actions/FBNCardActionsConfiguration.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNCardActionsConfiguration.h"
#import "FBNAssetsController.h"
#import "FBNCardActionConfiguration.h"
#import "FBNCardViewUtilities.h"
NS_ASSUME_NONNULL_BEGIN
@implementation FBNCardActionsConfiguration
///--------------------------------------
#pragma mark - Init
///--------------------------------------
- (instancetype)initFromDictionary:(NSDictionary *)dictionary
withBackground:(nullable id)background {
self = [super init];
if (!self) return self;
_style = FBNCardActionsStyleFromString(dictionary[@"style"]);
_layoutStyle = FBNCardActionsLayoutStyleFromString(dictionary[@"layoutStyle"]);
_background = background;
NSNumber *height = dictionary[@"height"] ?: @(44.0); // Defaults to 44.0
_height = FBNCGFloatFromNumber(height);
_topInset = FBNCGFloatFromNumber(dictionary[@"topInset"]); // Default to 0
_contentInset = FBNCGFloatFromNumber(dictionary[@"contentInset"]); // Defaults to 0
_cornerRadius = FBNCGFloatFromNumber(dictionary[@"cornerRadius"]); // Defaults to 0
NSArray *rawActions = dictionary[@"actions"];
NSMutableArray *actions = [NSMutableArray arrayWithCapacity:rawActions.count];
for (NSDictionary *rawAction in rawActions) {
FBNCardActionConfiguration *action = [FBNCardActionConfiguration configurationFromDictionary:rawAction];
[actions addObject:action];
}
_actions = [actions copy];
return self;
}
+ (void)loadFromDictionary:(nullable NSDictionary *)dictionary
assetsController:(FBNAssetsController *)controller
completion:(void (^)(FBNCardActionsConfiguration * _Nullable configuration))completion {
if (!dictionary) {
completion(nil);
return;
}
[controller loadAssetFromDictionary:dictionary[@"background"] completion:^(id _Nullable asset) {
FBNCardActionsConfiguration *configuration = [[self alloc] initFromDictionary:dictionary withBackground:asset];
completion(configuration);
}];
}
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Models/Actions/FBNCardActionsStyle.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
NS_ASSUME_NONNULL_BEGIN
///--------------------------------------
#pragma mark - Style
///--------------------------------------
typedef NS_ENUM(NSUInteger, FBNCardActionsStyle) {
FBNCardActionsStyleAttached,
FBNCardActionsStyleDetached,
};
extern FBNCardActionsStyle FBNCardActionsStyleFromString(NSString *_Nullable string);
///--------------------------------------
#pragma mark - LayoutStyle
///--------------------------------------
typedef NS_ENUM(NSUInteger, FBNCardActionsLayoutStyle) {
FBNCardActionsLayoutStyleVertical,
FBNCardActionsLayoutStyleHorizontal,
};
extern FBNCardActionsLayoutStyle FBNCardActionsLayoutStyleFromString(NSString *_Nullable string);
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Models/Actions/FBNCardActionsStyle.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNCardActionsStyle.h"
FBNCardActionsStyle FBNCardActionsStyleFromString(NSString *_Nullable string) {
if ([string isEqualToString:@"attached"]) {
return FBNCardActionsStyleAttached;
} else if ([string isEqualToString:@"detached"]) {
return FBNCardActionsStyleDetached;
}
return FBNCardActionsStyleAttached;
}
extern FBNCardActionsLayoutStyle FBNCardActionsLayoutStyleFromString(NSString *_Nullable string) {
if ([string isEqualToString:@"vertical"]) {
return FBNCardActionsLayoutStyleVertical;
} else if ([string isEqualToString:@"horizontal"]) {
return FBNCardActionsLayoutStyleHorizontal;
}
return FBNCardActionsLayoutStyleVertical;
}
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Models/Actions/FBNCardButtonAction.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#ifndef FBNCardButtonAction_h
#define FBNCardButtonAction_h
typedef NS_ENUM(NSUInteger, FBNCardButtonAction) {
FBNCardButtonActionPrimary = 1,
FBNCardButtonActionSecondary,
FBNCardButtonActionDismiss
};
#endif /* FBNCardButtonAction_h */
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Models/Body/FBNCardBodyConfiguration.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
#import
#import "FBNAsset.h"
#import "FBNCardTextContent.h"
@class FBNAssetsController;
NS_ASSUME_NONNULL_BEGIN
@interface FBNCardBodyConfiguration : NSObject
///--------------------------------------
#pragma mark - Properties
///--------------------------------------
@property (nullable, nonatomic, strong, readonly) id background;
@property (nullable, nonatomic, strong, readonly) FBNCardTextContent *content;
///--------------------------------------
#pragma mark - Init
///--------------------------------------
+ (void)loadFromDictionary:(nullable NSDictionary *)dictionary
assetsController:(FBNAssetsController *)controller
completion:(void (^)(FBNCardBodyConfiguration *_Nullable configuration))completion;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Models/Body/FBNCardBodyConfiguration.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNCardBodyConfiguration.h"
#import "FBNAssetsController.h"
#import "FBNCardTextContent.h"
NS_ASSUME_NONNULL_BEGIN
@implementation FBNCardBodyConfiguration
///--------------------------------------
#pragma mark - Init
///--------------------------------------
- (instancetype)initFromDictionary:(NSDictionary *)dictionary
withBackground:(nullable id)background {
self = [super init];
if (!self) return self;
_background = background;
_content = [FBNCardTextContent contentFromDictionary:dictionary[@"content"]];
return self;
}
+ (void)loadFromDictionary:(nullable NSDictionary *)dictionary
assetsController:(FBNAssetsController *)controller
completion:(void (^)(FBNCardBodyConfiguration *_Nullable configuration))completion {
if (!dictionary) {
completion(nil);
return;
}
[controller loadAssetFromDictionary:dictionary[@"background"] completion:^(id _Nullable asset) {
FBNCardBodyConfiguration *configuration = [[self alloc] initFromDictionary:dictionary withBackground:asset];
completion(configuration);
}];
}
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Models/Card/FBNCardConfiguration.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
#import "FBNCardSize.h"
@class FBNAssetsController;
@class FBNCardDisplayOptions;
@class FBNCardHeroConfiguration;
@class FBNCardBodyConfiguration;
@class FBNCardActionsConfiguration;
NS_ASSUME_NONNULL_BEGIN
@interface FBNCardConfiguration : NSObject
@property (nonatomic, strong, readonly) FBNCardDisplayOptions *displayOptions;
@property (nullable, nonatomic, strong, readonly) FBNCardHeroConfiguration *heroConfiguration;
@property (nullable, nonatomic, strong, readonly) FBNCardBodyConfiguration *bodyConfiguration;
@property (nullable, nonatomic, strong, readonly) FBNCardActionsConfiguration *actionsConfiguration;
+ (void)loadFromDictionary:(NSDictionary *)dictionary
withDisplayOptions:(FBNCardDisplayOptions *)displayOptions
assetsController:(FBNAssetsController *)controller
completion:(void (^)(FBNCardConfiguration * _Nullable configuration))completion;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Models/Card/FBNCardConfiguration.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNCardConfiguration.h"
#import "FBNAssetsController.h"
#import "FBNCardHeroConfiguration.h"
#import "FBNCardBodyConfiguration.h"
#import "FBNCardActionsConfiguration.h"
NS_ASSUME_NONNULL_BEGIN
@implementation FBNCardConfiguration
///--------------------------------------
#pragma mark - Init
///--------------------------------------
- (instancetype)initFromDictionary:(NSDictionary *)dictionary
withDisplayOptions:(FBNCardDisplayOptions *)displayOptions
heroConfiguration:(FBNCardHeroConfiguration *)heroConfiguration
bodyConfiguration:(FBNCardBodyConfiguration *)bodyConfiguration
actionsConfiguration:(FBNCardActionsConfiguration *)actionsConfiguration {
self = [super init];
if (!self) return self;
_displayOptions = displayOptions;
_heroConfiguration = heroConfiguration;
_bodyConfiguration = bodyConfiguration;
_actionsConfiguration = actionsConfiguration;
return self;
}
+ (void)loadFromDictionary:(NSDictionary *)dictionary
withDisplayOptions:(FBNCardDisplayOptions *)displayOptions
assetsController:(FBNAssetsController *)controller
completion:(void (^)(FBNCardConfiguration * _Nullable configuration))completion {
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
__block FBNCardHeroConfiguration *heroConfiguration = nil;
[FBNCardHeroConfiguration loadFromDictionary:dictionary[@"hero"]
assetsController:controller
completion:^(FBNCardHeroConfiguration * _Nullable configuration) {
heroConfiguration = configuration;
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
__block FBNCardBodyConfiguration *bodyConfiguration = nil;
[FBNCardBodyConfiguration loadFromDictionary:dictionary[@"body"]
assetsController:controller
completion:^(FBNCardBodyConfiguration * _Nullable configuration) {
bodyConfiguration = configuration;
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
__block FBNCardActionsConfiguration *actionsConfiguration = nil;
[FBNCardActionsConfiguration loadFromDictionary:dictionary[@"actions"]
assetsController:controller
completion:^(FBNCardActionsConfiguration * _Nullable configuration) {
actionsConfiguration = configuration;
dispatch_group_leave(group);
}];
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
FBNCardConfiguration *configuration = [[FBNCardConfiguration alloc] initFromDictionary:dictionary
withDisplayOptions:displayOptions
heroConfiguration:heroConfiguration
bodyConfiguration:bodyConfiguration
actionsConfiguration:actionsConfiguration];
completion(configuration);
});
}
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Models/Card/FBNCardDisplayOptions.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
#import
#import "FBNCardSize.h"
NS_ASSUME_NONNULL_BEGIN
@interface FBNCardDisplayOptions : NSObject
@property (nonatomic, assign, readonly) FBNCardSize size;
@property (nonatomic, assign, readonly) CGFloat cornerRadius;
@property (nonatomic, assign, readonly) CGFloat contentInset;
@property (nullable, nonatomic, strong, readonly) UIColor *backdropColor;
@property (nonatomic, strong, readonly) UIColor *dismissButtonColor;
+ (instancetype)displayOptionsFromDictionary:(NSDictionary *)dictionary;
- (instancetype)init NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Models/Card/FBNCardDisplayOptions.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNCardDisplayOptions.h"
#import "FBNCardColor.h"
#import "FBNCardViewUtilities.h"
NS_ASSUME_NONNULL_BEGIN
@implementation FBNCardDisplayOptions
- (instancetype)initFromDictionary:(NSDictionary *)dictionary {
self = [super init];
if (!self) return nil;
_size = FBNCardSizeFromString(dictionary[@"size"]);
_cornerRadius = FBNCGFloatFromNumber(dictionary[@"cornerRadius"]); // Defaults to 0
_contentInset = FBNCGFloatFromNumber(dictionary[@"contentInset"] ?: @(10.0)); // Defaults to 10.0
_backdropColor = FBNCardColorFromRGBAHex(dictionary[@"backdropColor"]);
_dismissButtonColor = FBNCardColorFromRGBAHex(dictionary[@"dismissColor"]) ?: [UIColor blackColor];
return self;
}
+ (instancetype)displayOptionsFromDictionary:(NSDictionary *)dictionary {
return [[self alloc] initFromDictionary:dictionary];
}
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Models/Card/FBNCardSize.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
#import
typedef NS_ENUM(NSUInteger, FBNCardSize) {
FBNCardSizeInvalid,
FBNCardSizeSmall,
FBNCardSizeMedium,
FBNCardSizeLarge,
};
NS_ASSUME_NONNULL_BEGIN
extern FBNCardSize FBNCardSizeFromString(NSString *_Nullable string);
extern CGSize FBNCardLayoutSizeThatFits(FBNCardSize cardSize, CGSize size);
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Models/Card/FBNCardSize.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNCardSize.h"
#import "FBNCardViewUtilities.h"
FBNCardSize FBNCardSizeFromString(NSString *_Nullable string) {
if ([string isEqualToString:@"small"]) {
return FBNCardSizeSmall;
} else if ([string isEqualToString:@"medium"]) {
return FBNCardSizeMedium;
} else if ([string isEqualToString:@"large"]) {
return FBNCardSizeLarge;
}
return FBNCardSizeInvalid;
}
CGSize FBNCardLayoutSizeThatFits(FBNCardSize cardSize, CGSize size) {
const CGSize maxSize = CGSizeMake(400, 700);
CGSize layoutSize = FBNSizeMin(maxSize, size);
switch (cardSize) {
case FBNCardSizeInvalid:
return CGSizeZero;
case FBNCardSizeSmall: {
layoutSize.width *= 0.75;
layoutSize.height *= 0.7;
}
break;
case FBNCardSizeMedium: {
layoutSize.width *= 0.83;
layoutSize.height *= 0.9;
}
break;
case FBNCardSizeLarge:
default:
break;
}
return FBNSizeAdjustToScreenScale(layoutSize, NSRoundPlain);
}
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Models/Components/FBNAnimatedImage.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
NS_ASSUME_NONNULL_BEGIN
extern UIImage *FBNAnimatedImageFromData(NSData *data);
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Models/Components/FBNAnimatedImage.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNAnimatedImage.h"
#import
#import
extern BOOL FBNImageSourceContainsAnimatedGIF(CGImageSourceRef source);
extern UIImage *FBNAnimatedImageFromImageSource(CGImageSourceRef source);
extern NSTimeInterval FBNImageSourceGetGIFFrameDelay(CGImageSourceRef imageSource, NSUInteger index);
///--------------------------------------
#pragma mark - Public
///--------------------------------------
UIImage *FBNAnimatedImageFromData(NSData *data) {
if (!data) {
return nil;
}
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)(data), NULL);
UIImage *image = nil;
if (FBNImageSourceContainsAnimatedGIF(source)) {
image = FBNAnimatedImageFromImageSource(source);
} else {
image = [UIImage imageWithData:data];
}
if (source) {
CFRelease(source);
}
return image;
}
///--------------------------------------
#pragma mark - Private
///--------------------------------------
BOOL FBNImageSourceContainsAnimatedGIF(CGImageSourceRef source) {
return (source && UTTypeConformsTo(CGImageSourceGetType(source), kUTTypeGIF) && CGImageSourceGetCount(source) > 1);
}
UIImage *FBNAnimatedImageFromImageSource(CGImageSourceRef source) {
CFRetain(source);
NSUInteger numberOfFrames = CGImageSourceGetCount(source);
NSMutableArray *images = [NSMutableArray arrayWithCapacity:numberOfFrames];
NSTimeInterval duration = 0.0;
for (NSUInteger i = 0; i < numberOfFrames; ++i) {
CGImageRef image = CGImageSourceCreateImageAtIndex(source, i, NULL);
if (image) {
UIImage *frameImage = [UIImage imageWithCGImage:image scale:1.0 orientation:UIImageOrientationUp];
[images addObject:frameImage];
CFRelease(image);
} else {
continue;
}
duration += FBNImageSourceGetGIFFrameDelay(source, i);
}
CFRelease(source);
return [UIImage animatedImageWithImages:images duration:duration];
}
NSTimeInterval FBNImageSourceGetGIFFrameDelay(CGImageSourceRef source, NSUInteger index) {
NSTimeInterval frameDelay = 0;
CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(source, index, NULL);
if (!imageProperties) {
return frameDelay;
}
CFDictionaryRef gifProperties = nil;
if (CFDictionaryGetValueIfPresent(imageProperties, kCGImagePropertyGIFDictionary, (const void **)&gifProperties)) {
const void *durationValue = nil;
if (CFDictionaryGetValueIfPresent(gifProperties, kCGImagePropertyGIFUnclampedDelayTime, &durationValue)) {
frameDelay = [(__bridge NSNumber *)durationValue doubleValue];
if (frameDelay <= 0) {
if (CFDictionaryGetValueIfPresent(gifProperties, kCGImagePropertyGIFDelayTime, &durationValue)) {
frameDelay = [(__bridge NSNumber *)durationValue doubleValue];
}
}
}
}
CFRelease(imageProperties);
return frameDelay;
}
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Models/Components/FBNCardBackground.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
#import
NS_ASSUME_NONNULL_BEGIN
extern UIImage *_Nullable FBNCardBackgroundImageWithColor(UIColor *color);
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Models/Components/FBNCardBackground.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNCardBackground.h"
#import "FBNCardViewUtilities.h"
UIImage *_Nullable FBNCardBackgroundImageWithColor(UIColor *color) {
CGSize size = CGSizeMake(1, 1);
UIGraphicsBeginImageContext(size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, color.CGColor);
CGContextFillRect(context, FBNRectMakeWithOriginSize(CGPointZero, size));
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Models/Components/FBNCardColor.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
#import
NS_ASSUME_NONNULL_BEGIN
extern UIColor *_Nullable FBNCardColorFromRGBAHex(NSString *string);
typedef NS_ENUM(uint8_t, FBNCardContrastColor) {
FBNCardContrastColorWhite,
FBNCardContrastColorBlack
};
extern FBNCardContrastColor FBNCardContrastColorForColor(UIColor *color);
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Models/Components/FBNCardColor.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNCardColor.h"
UIColor *_Nullable FBNCardColorFromRGBAHex(NSString *string) {
if (![string isKindOfClass:[NSString class]]) {
return nil;
}
NSString *hexString = [string substringFromIndex:1];
unsigned long long hexValue = 0;
[[NSScanner scannerWithString:hexString] scanHexLongLong:&hexValue];
CGFloat divisor = 255.0;
CGFloat red = ((hexValue & 0xFF000000) >> 24) / divisor;
CGFloat green = ((hexValue & 0x00FF0000) >> 16) / divisor;
CGFloat blue = ((hexValue & 0x0000FF00) >> 8) / divisor;
CGFloat alpha = (hexValue & 0x000000FF) / divisor;
return [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
}
FBNCardContrastColor FBNCardContrastColorForColor(UIColor *color) {
CGFloat red = 0.0, green = 0.0, blue = 0.0;
[color getRed:&red green:&green blue:&blue alpha:NULL];
CGFloat perceptiveLuminance = (red * 255 * 299 + green * 255 * 587 + blue * 255 * 114) / 1000.0f;
return (perceptiveLuminance >= 128 ? FBNCardContrastColorBlack : FBNCardContrastColorWhite);
}
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Models/Components/FBNCardFont.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
NS_ASSUME_NONNULL_BEGIN
extern NSString *const FBNCardFontNameSystemRegular;
extern NSString *const FBNCardFontNameSystemLight;
extern NSString *const FBNCardFontNameSystemItalic;
extern NSString *const FBNCardFontNameSystemBold;
extern NSString *const FBNCardFontNameSystemBoldItalic;
extern UIFont *FBNCardFontWithNameSize(NSString *_Nullable fontName, CGFloat size);
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Models/Components/FBNCardFont.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNCardFont.h"
NS_ASSUME_NONNULL_BEGIN
NSString *const FBNCardFontNameSystemRegular = @"system-regular";
NSString *const FBNCardFontNameSystemLight = @"system-light";
NSString *const FBNCardFontNameSystemItalic = @"system-italic";
NSString *const FBNCardFontNameSystemBold = @"system-bold";
NSString *const FBNCardFontNameSystemBoldItalic = @"system-bolditalic";
UIFont *FBNCardFontWithNameSize(NSString *_Nullable fontName, CGFloat size) {
// Make sure we do case-insensitive comparison.
fontName = fontName.lowercaseString;
UIFont *systemFont = [UIFont systemFontOfSize:size];
UIFontDescriptor *fontDescriptor = nil;
if ([fontName isEqualToString:FBNCardFontNameSystemLight]) {
fontDescriptor = [UIFontDescriptor fontDescriptorWithFontAttributes:@{ UIFontDescriptorFamilyAttribute: systemFont.familyName,
UIFontDescriptorFaceAttribute : @"Light" }];
} else if ([fontName isEqualToString:FBNCardFontNameSystemItalic]) {
fontDescriptor = [systemFont.fontDescriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitItalic];
} else if ([fontName isEqualToString:FBNCardFontNameSystemBold]) {
fontDescriptor = [systemFont.fontDescriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold];
} else if ([fontName isEqualToString:FBNCardFontNameSystemBoldItalic]) {
fontDescriptor = [systemFont.fontDescriptor fontDescriptorWithSymbolicTraits:(UIFontDescriptorTraitBold | UIFontDescriptorTraitItalic)];
} else {
fontDescriptor = systemFont.fontDescriptor;
}
return [UIFont fontWithDescriptor:fontDescriptor size:size];
}
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Models/Components/FBNCardTextAlignment.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
#import
NS_ASSUME_NONNULL_BEGIN
extern NSTextAlignment FBNCardTextAlignmentFromString(NSString *_Nullable string);
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Models/Components/FBNCardTextAlignment.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNCardTextAlignment.h"
NSTextAlignment FBNCardTextAlignmentFromString(NSString *_Nullable string) {
if ([string isEqualToString:@"left"]) {
return NSTextAlignmentLeft;
} else if ([string isEqualToString:@"right"]) {
return NSTextAlignmentRight;
} else if ([string isEqualToString:@"center"]) {
return NSTextAlignmentCenter;
}
return NSTextAlignmentCenter;
}
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Models/Components/FBNCardTextContent.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
#import
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSUInteger, FBNCardContentVerticalAlignment) {
FBNCardContentVerticalAlignmentTop,
FBNCardContentVerticalAlignmentCenter,
FBNCardContentVerticalAlignmentBottom
};
extern FBNCardContentVerticalAlignment FBNCardContentVerticalAlignmentFromString(NSString *_Nullable string);
@interface FBNCardTextContent : NSObject
///--------------------------------------
#pragma mark - Properties
///--------------------------------------
@property (nonatomic, strong, readonly) UIFont *font;
@property (nonatomic, strong, readonly) UIColor *textColor;
@property (nonatomic, assign, readonly) NSTextAlignment textAlignment;
@property (nullable, nonatomic, copy, readonly) NSAttributedString *attributedText;
///--------------------------------------
#pragma mark - Init
///--------------------------------------
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
+ (nullable instancetype)contentFromDictionary:(nullable NSDictionary *)dictionary;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Models/Components/FBNCardTextContent.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNCardTextContent.h"
#import "FBNCardColor.h"
#import "FBNCardTextAlignment.h"
#import "FBNCardViewUtilities.h"
#import "FBNCardFont.h"
FBNCardContentVerticalAlignment FBNCardContentVerticalAlignmentFromString(NSString *_Nullable string) {
if ([string isEqualToString:@"top"]) {
return FBNCardContentVerticalAlignmentTop;
} else if ([string isEqualToString:@"center"]) {
return FBNCardContentVerticalAlignmentCenter;
} else if ([string isEqualToString:@"bottom"]) {
return FBNCardContentVerticalAlignmentBottom;
}
return FBNCardContentVerticalAlignmentCenter;
}
@implementation FBNCardTextContent
///--------------------------------------
#pragma mark - Init
///--------------------------------------
- (instancetype)initFromDictionary:(NSDictionary *)dictionary {
self = [super init];
if (!self) return self;
NSString *fontName = dictionary[@"font"];
NSNumber *fontSize = dictionary[@"size"] ?: @([UIFont systemFontSize]);
_font = FBNCardFontWithNameSize(fontName, FBNCGFloatFromNumber(fontSize));
_textColor = FBNCardColorFromRGBAHex(dictionary[@"color"]) ?: [UIColor blackColor]; // Defaults to Black
_textAlignment = FBNCardTextAlignmentFromString(dictionary[@"align"]);
NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
paragraphStyle.alignment = _textAlignment;
NSDictionary *attributes = @{ NSFontAttributeName : self.font,
NSForegroundColorAttributeName : self.textColor,
NSParagraphStyleAttributeName : paragraphStyle };
_attributedText = [[NSAttributedString alloc] initWithString:dictionary[@"text"] attributes:attributes];
return self;
}
+ (nullable instancetype)contentFromDictionary:(nullable NSDictionary *)dictionary {
if (!dictionary) {
return nil;
}
return [[self alloc] initFromDictionary:dictionary];
}
@end
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Models/Hero/FBNCardHeroConfiguration.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
#import
#import "FBNCardTextContent.h"
#import "FBNAsset.h"
@class FBNAssetsController;
@class FBNAssetContentCache;
NS_ASSUME_NONNULL_BEGIN
extern const CGFloat FBNCardHeroHeightUnspecified;
@interface FBNCardHeroConfiguration : NSObject
///--------------------------------------
#pragma mark - Properties
///--------------------------------------
@property (nonatomic, assign, readonly) CGFloat height;
@property (nullable, nonatomic, strong, readonly) id background;
@property (nullable, nonatomic, strong, readonly) FBNCardTextContent *content;
@property (nonatomic, assign, readonly) FBNCardContentVerticalAlignment contentVerticalAlignment;
///--------------------------------------
#pragma mark - Init
///--------------------------------------
+ (void)loadFromDictionary:(nullable NSDictionary *)dictionary
assetsController:(FBNAssetsController *)controller
completion:(void (^)(FBNCardHeroConfiguration *_Nullable configuration))completion;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Models/Hero/FBNCardHeroConfiguration.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNCardHeroConfiguration.h"
#import "FBNAssetContentCache.h"
#import "FBNAssetsController.h"
#import "FBNCardViewUtilities.h"
const CGFloat FBNCardHeroHeightUnspecified = CGFLOAT_MAX;
NS_ASSUME_NONNULL_BEGIN
@implementation FBNCardHeroConfiguration
///--------------------------------------
#pragma mark - Init
///--------------------------------------
- (instancetype)initFromDictionary:(NSDictionary *)dictionary
withBackground:(nullable id )background {
self = [super init];
if (!self) return self;
NSNumber *height = dictionary[@"height"] ?: @(FBNCardHeroHeightUnspecified);
_height = FBNCGFloatFromNumber(height);
_background = background;
_content = [FBNCardTextContent contentFromDictionary:dictionary[@"content"]];
_contentVerticalAlignment = FBNCardContentVerticalAlignmentFromString(dictionary[@"contentAlign"]);
return self;
}
+ (void)loadFromDictionary:(nullable NSDictionary *)dictionary
assetsController:(FBNAssetsController *)controller
completion:(void (^)(FBNCardHeroConfiguration * _Nullable))completion {
if (!dictionary) {
completion(nil);
return;
}
[controller loadAssetFromDictionary:dictionary[@"background"] completion:^(id _Nullable asset) {
FBNCardHeroConfiguration *configuration = [[self alloc] initFromDictionary:dictionary withBackground:asset];
completion(configuration);
}];
}
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Utilities/FBNCardError.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
@interface FBNCardError : NSError
+ (instancetype)invalidRemoteNotificationPayloadError;
@end
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Utilities/FBNCardError.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNCardError.h"
#import "FBNConstants.h"
@implementation FBNCardError
+ (instancetype)invalidRemoteNotificationPayloadError {
return [self errorWithDomain:FBNotificationsErrorDomain
code:FBNotificationsErrorInvalidPayload
userInfo:@{ NSLocalizedDescriptionKey : @"Invalid remote notification payload for presenting push card." }];
}
@end
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Utilities/FBNCardHash.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
extern NSString *FBMD5HashFromData(NSData *data);
extern NSString *FBMD5HashFromString(NSString *string);
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Utilities/FBNCardHash.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNCardHash.h"
#import
NSString *FBMD5HashFromData(NSData *data) {
unsigned char md[CC_MD5_DIGEST_LENGTH];
// NOTE: `__block` variables of a struct type seem to be bugged. The compiler emits instructions to read
// from the stack past where they're supposed to exist. This fixes that, by only using a traditional pointer.
CC_MD5_CTX ctx_val = { 0 };
CC_MD5_CTX *ctx_ptr = &ctx_val;
CC_MD5_Init(ctx_ptr);
[data enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) {
CC_MD5_Update(ctx_ptr , bytes, (CC_LONG)byteRange.length);
}];
CC_MD5_Final(md, ctx_ptr);
NSString *string = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
md[0], md[1],
md[2], md[3],
md[4], md[5],
md[6], md[7],
md[8], md[9],
md[10], md[11],
md[12], md[13],
md[14], md[15]];
return string;
}
NSString *FBMD5HashFromString(NSString *string) {
return FBMD5HashFromData([string dataUsingEncoding:NSUTF8StringEncoding]);
}
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Utilities/FBNCardPayload.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
NS_ASSUME_NONNULL_BEGIN
typedef NSDictionary FBNCardPayload;
extern FBNCardPayload *_Nullable FBNCardPayloadFromRemoteNotificationPayload(NSDictionary *payload);
extern BOOL FBNCardPayloadIsCompatibleWithCurrentVersion(FBNCardPayload *payload, NSString *frameworkVersion);
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Utilities/FBNCardPayload.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNCardPayload.h"
///--------------------------------------
#pragma mark - Version
///--------------------------------------
typedef struct {
uint32_t major;
uint32_t minor;
uint32_t patch;
} FBNCardPayloadVersion;
static const FBNCardPayloadVersion FBNCardPayloadVersionInvalid = { .major = -1, .minor = -1, .patch = -1 };
BOOL FBNCardPayloadVersionIsValid(FBNCardPayloadVersion version) {
return (version.major != FBNCardPayloadVersionInvalid.major &&
version.minor != FBNCardPayloadVersionInvalid.minor &&
version.patch != FBNCardPayloadVersionInvalid.patch);
}
FBNCardPayloadVersion FBNCardPayloadVersionFromString(NSString *string) {
FBNCardPayloadVersion version = {0, 0, 0};
int tokens = sscanf([string UTF8String], "%u.%u.%u", &version.major, &version.minor, &version.patch);
if (tokens < 2) {
return FBNCardPayloadVersionInvalid;
}
return version;
}
///--------------------------------------
#pragma mark - Public
///--------------------------------------
FBNCardPayload *_Nullable FBNCardPayloadFromRemoteNotificationPayload(NSDictionary *payload) {
return payload[@"fb_push_card"];
}
BOOL FBNCardPayloadIsCompatibleWithCurrentVersion(FBNCardPayload *payload, NSString *frameworkVersionString) {
FBNCardPayloadVersion payloadVersion = FBNCardPayloadVersionFromString(payload[@"version"]);
FBNCardPayloadVersion frameworkVersion = FBNCardPayloadVersionFromString(frameworkVersionString);
// Check for both versions to be valid
if (!FBNCardPayloadVersionIsValid(payloadVersion) ||
!FBNCardPayloadVersionIsValid(frameworkVersion)) {
return NO;
}
// Not forward/backward compatible on major
if (payloadVersion.major != frameworkVersion.major) {
return NO;
}
// Not forward compatible on minor
if (payloadVersion.minor > frameworkVersion.minor) {
return NO;
}
// Don't care about patch
return YES;
}
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Utilities/FBNCardViewUtilities.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
NS_ASSUME_NONNULL_BEGIN
///--------------------------------------
#pragma mark - CGFloat
///--------------------------------------
extern CGFloat FBNCGFloatFromNumber(NSNumber *_Nullable number);
extern CGFloat FBNCGFloatRound(CGFloat number, NSRoundingMode roundingMode);
///--------------------------------------
#pragma mark - CGRect
///--------------------------------------
extern CGRect FBNRectMakeWithOriginSize(CGPoint origin, CGSize size);
extern CGRect FBNRectMakeWithSizeCenteredInRect(CGSize size, CGRect rect);
///--------------------------------------
#pragma mark - CGSize
///--------------------------------------
extern CGSize FBNSizeMin(CGSize size1, CGSize size2);
extern CGSize FBNSizeMax(CGSize size1, CGSize size2);
extern CGFloat FBNAspectFillScaleThatFits(CGSize size, CGSize fitSize);
///--------------------------------------
#pragma mark - Screen Scaling
///--------------------------------------
extern CGRect FBNRectAdjustToScreenScale(CGRect rect, NSRoundingMode roundingMode);
extern CGSize FBNSizeAdjustToScreenScale(CGSize size, NSRoundingMode roundingMode);
extern CGFloat FBNFloatAdjustToScreenScale(CGFloat value, NSRoundingMode roundingMode);
///--------------------------------------
#pragma mark - Top Most View Controller
///--------------------------------------
extern UIViewController *_Nullable FBNApplicationTopMostViewController();
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Utilities/FBNCardViewUtilities.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "FBNCardViewUtilities.h"
///--------------------------------------
#pragma mark - CGFloat
///--------------------------------------
CGFloat FBNCGFloatFromNumber(NSNumber *_Nullable number) {
#if CGFLOAT_IS_DOUBLE
return number.doubleValue;
#else
return number.floatValue;
#endif
}
CGFloat FBNCGFloatRound(CGFloat number, NSRoundingMode roundingMode) {
switch (roundingMode) {
case NSRoundPlain:
case NSRoundBankers:
#if CGFLOAT_IS_DOUBLE
number = round(number);
#else
number = roundf(number);
#endif
case NSRoundDown:
#if CGFLOAT_IS_DOUBLE
number = floor(number);
#else
number = floorf(number);
#endif
case NSRoundUp:
#if CGFLOAT_IS_DOUBLE
number = ceil(number);
#else
number = ceilf(number);
#endif
default: break;
}
return number;
}
///--------------------------------------
#pragma mark - CGRect
///--------------------------------------
CGRect FBNRectMakeWithOriginSize(CGPoint origin, CGSize size) {
return CGRectMake(origin.x, origin.y, size.width, size.height);
}
CGRect FBNRectMakeWithSizeCenteredInRect(CGSize size, CGRect rect) {
CGPoint center = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
CGPoint origin = CGPointMake(center.x - size.width / 2.0f, center.y - size.height / 2.0f);
rect = FBNRectMakeWithOriginSize(origin, size);
return FBNRectAdjustToScreenScale(rect, NSRoundPlain);
}
///--------------------------------------
#pragma mark - CGSize
///--------------------------------------
CGSize FBNSizeMin(CGSize size1, CGSize size2) {
CGSize size = CGSizeZero;
size.width = (float)fmin(size1.width, size2.width);
size.height = (float)fmin(size1.height, size2.height);
return size;
}
CGSize FBNSizeMax(CGSize size1, CGSize size2) {
CGSize size = CGSizeZero;
size.width = (float)fmax(size1.width, size2.width);
size.height = (float)fmax(size1.height, size2.height);
return size;
}
CGFloat FBNAspectFillScaleThatFits(CGSize size, CGSize fitSize) {
return MIN(fitSize.width / size.width, fitSize.height / size.height);
}
///--------------------------------------
#pragma mark - Screen Scaling
///--------------------------------------
CGRect FBNRectAdjustToScreenScale(CGRect rect, NSRoundingMode roundingMode) {
rect.origin.x = FBNFloatAdjustToScreenScale(rect.origin.x, roundingMode);
rect.origin.y = FBNFloatAdjustToScreenScale(rect.origin.y, roundingMode);
rect.size = FBNSizeAdjustToScreenScale(rect.size, roundingMode);
return rect;
}
CGSize FBNSizeAdjustToScreenScale(CGSize size, NSRoundingMode roundingMode) {
size.width = FBNFloatAdjustToScreenScale(size.width, roundingMode);
size.height = FBNFloatAdjustToScreenScale(size.height, roundingMode);
return size;
}
CGFloat FBNFloatAdjustToScreenScale(CGFloat value, NSRoundingMode roundingMode) {
const CGFloat scale = [UIScreen mainScreen].scale;
value = value * scale;
value = FBNCGFloatRound(value, roundingMode);
value /= scale;
return value;
}
UIViewController *_Nullable FBNApplicationTopMostViewController() {
UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
UIViewController *viewController = keyWindow.rootViewController;
while (viewController.presentedViewController && !viewController.presentedViewController.isBeingDismissed) {
viewController = viewController.presentedViewController;
}
return viewController;
}
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Utilities/FBNContentSizeProvider.h
================================================
//
// FBNContentSizeProvider.h
// FBNotifications
//
// Created by Nikita Lutsenko on 5/20/16.
// Copyright © 2016 Facebook Inc. All rights reserved.
//
#import
#import
@protocol FBNContentSizeProvider
- (CGSize)contentSizeThatFitsParentContainerSize:(CGSize)fitSize;
@end
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Views/Components/FBNCardDismissButton.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As withany software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
@interface FBNCardDismissButton : UIButton
@property (nonatomic, strong) UIColor *imageColor;
@end
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Views/Components/FBNCardDismissButton.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNCardDismissButton.h"
#import "FBNCardViewUtilities.h"
static const CGFloat FBNCardDismissButtonEdgeLength = 16.0;
static const CGFloat FBNCardDismissButtonLineWidth = 2.0;
@implementation FBNCardDismissButton
///--------------------------------------
#pragma mark - Accessors
///--------------------------------------
- (void)setImageColor:(UIColor *)imageColor {
if (self.imageColor != imageColor) {
_imageColor = imageColor;
[self setImage:[[self class] _imageWithColor:imageColor] forState:UIControlStateNormal];
}
}
///--------------------------------------
#pragma mark - UIView
///--------------------------------------
- (CGSize)sizeThatFits:(CGSize)boundingSize {
CGSize size = CGSizeZero;
size.width = MIN(FBNCardDismissButtonEdgeLength, boundingSize.width);
size.height = MIN(FBNCardDismissButtonEdgeLength, boundingSize.height);
return size;
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
CGFloat edgeInset = CGRectGetWidth(self.bounds) - 44.0f;
CGRect bigBounds = CGRectInset(self.bounds, edgeInset, edgeInset);
return CGRectContainsPoint(bigBounds, point);
}
///--------------------------------------
#pragma mark - Default
///--------------------------------------
+ (UIImage *)_imageWithColor:(UIColor *)color {
CGRect imageRect = FBNRectMakeWithOriginSize(CGPointZero, CGSizeMake(FBNCardDismissButtonEdgeLength, FBNCardDismissButtonEdgeLength));
UIGraphicsBeginImageContextWithOptions(imageRect.size, NO, 0.0f);
[color setStroke];
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointZero];
[path addLineToPoint:CGPointMake(CGRectGetMaxX(imageRect), CGRectGetMaxY(imageRect))];
[path moveToPoint:CGPointMake(CGRectGetMaxX(imageRect), CGRectGetMinY(imageRect))];
[path addLineToPoint:CGPointMake(CGRectGetMinX(imageRect), CGRectGetMaxY(imageRect))];
path.lineWidth = FBNCardDismissButtonLineWidth;
[path stroke];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
@end
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Views/Components/FBNCardLabel.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
@class FBNCardTextContent;
@interface FBNCardLabel : UILabel
+ (instancetype)labelFromTextContent:(FBNCardTextContent *)content;
@end
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Views/Components/FBNCardLabel.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNCardLabel.h"
#import "FBNCardTextContent.h"
#import "FBNCardViewUtilities.h"
@implementation FBNCardLabel
///--------------------------------------
#pragma mark - Init
///--------------------------------------
+ (instancetype)labelFromTextContent:(FBNCardTextContent *)content {
FBNCardLabel *label = [[self alloc] initWithFrame:CGRectZero];
label.textColor = content.textColor;
label.textAlignment = content.textAlignment;
label.attributedText = content.attributedText;
label.numberOfLines = 0;
label.lineBreakMode = NSLineBreakByWordWrapping;
return label;
}
///--------------------------------------
#pragma mark - UIView
///--------------------------------------
- (CGSize)sizeThatFits:(CGSize)size {
// This is required to accommodate for UILabel behavior, where it returns size bigger than the fit size.
return FBNSizeMin([super sizeThatFits:size], size);
}
@end
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Views/FBNCardActionButton.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
#import "FBNCardButtonAction.h"
NS_ASSUME_NONNULL_BEGIN
@class FBNCardActionConfiguration;
@interface FBNCardActionButton : UIButton
@property (nonatomic, strong, readonly) FBNCardActionConfiguration *configuration;
@property (nonatomic, assign, readonly) FBNCardButtonAction action;
+ (instancetype)buttonFromConfiguration:(FBNCardActionConfiguration *)configuration
withCornerRadius:(CGFloat)cornerRadius
action:(FBNCardButtonAction)action;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Views/FBNCardActionButton.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNCardActionButton.h"
#import "FBNCardActionConfiguration.h"
#import "FBNCardBackground.h"
#import "FBNCardTextContent.h"
@implementation FBNCardActionButton
- (instancetype)initWithConfiguration:(FBNCardActionConfiguration *)configuration
cornerRadius:(CGFloat)cornerRadius
action:(FBNCardButtonAction)action {
self = [super initWithFrame:CGRectZero];
if (!self) return self;
_configuration = configuration;
_action = action;
self.clipsToBounds = YES;
self.layer.cornerRadius = cornerRadius;
[self setAttributedTitle:configuration.content.attributedText forState:UIControlStateNormal];
if (configuration.backgroundColor) {
[self setBackgroundImage:FBNCardBackgroundImageWithColor(configuration.backgroundColor) forState:UIControlStateNormal];
}
if (configuration.borderColor) {
self.layer.borderColor = configuration.borderColor.CGColor;
self.layer.borderWidth = configuration.borderWidth;
}
return self;
}
+ (instancetype)buttonFromConfiguration:(FBNCardActionConfiguration *)configuration
withCornerRadius:(CGFloat)cornerRadius
action:(FBNCardButtonAction)action {
return [[self alloc] initWithConfiguration:configuration cornerRadius:cornerRadius action:action];
}
@end
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Views/FBNCardActionsViewController.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
#import "FBNContentSizeProvider.h"
#import "FBNCardButtonAction.h"
NS_ASSUME_NONNULL_BEGIN
@class FBNAssetsController;
@class FBNCardActionsConfiguration;
@class FBNCardActionsViewController;
@protocol FBNCardActionsViewControllerDelegate
- (void)actionsViewController:(FBNCardActionsViewController *)viewController
didPerformButtonAction:(FBNCardButtonAction)action
withOpenURL:(nullable NSURL *)url;
@end
@interface FBNCardActionsViewController : UIViewController
@property (nonatomic, weak) id delegate;
- (instancetype)initWithAssetsController:(FBNAssetsController *)assetsController
configuration:(FBNCardActionsConfiguration *)configuration;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Views/FBNCardActionsViewController.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNCardActionsViewController.h"
#import "FBNAssetsController.h"
#import "FBNCardActionsConfiguration.h"
#import "FBNCardActionConfiguration.h"
#import "FBNCardActionButton.h"
#import "FBNCardViewUtilities.h"
NS_ASSUME_NONNULL_BEGIN
@interface FBNCardActionsViewController ()
@property (nonatomic, strong, readonly) FBNAssetsController *assetsController;
@property (nonatomic, strong, readonly) FBNCardActionsConfiguration *configuration;
@property (nullable, nonatomic, strong) UIViewController *backgroundViewController;
@property (nullable, nonatomic, copy) NSArray *actionButtons;
@end
@implementation FBNCardActionsViewController
///--------------------------------------
#pragma mark - Init/Dealloc
///--------------------------------------
- (instancetype)initWithAssetsController:(FBNAssetsController *)assetsController
configuration:(FBNCardActionsConfiguration *)configuration {
self = [super init];
if (!self) return self;
_assetsController = assetsController;
_configuration = configuration;
return self;
}
- (void)dealloc {
for (FBNCardActionButton *button in self.actionButtons) {
[button removeTarget:nil action:NULL forControlEvents:UIControlEventAllEvents];
}
}
///--------------------------------------
#pragma mark - View
///--------------------------------------
- (void)viewDidLoad {
[super viewDidLoad];
id background = self.configuration.background;
if (background) {
self.backgroundViewController = [self.assetsController viewControllerForAsset:background];
[self addChildViewController:self.backgroundViewController];
[self.view addSubview:self.backgroundViewController.view];
[self.backgroundViewController didMoveToParentViewController:self];
}
self.actionButtons = [[self class] _actionButtonsFromConfiguration:self.configuration];
for (FBNCardActionButton *button in self.actionButtons) {
[button addTarget:self action:@selector(_buttonAction:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
}
}
///--------------------------------------
#pragma mark - Layout
///--------------------------------------
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
const CGRect bounds = self.view.bounds;
self.backgroundViewController.view.frame = bounds;
CGSize buttonSize = { .width = 0.0f, .height = self.configuration.height };
switch (self.configuration.layoutStyle) {
case FBNCardActionsLayoutStyleHorizontal: {
if (self.configuration.style == FBNCardActionsStyleAttached) {
buttonSize.width = CGRectGetWidth(bounds) - self.configuration.contentInset * (self.actionButtons.count + 1); // Left + Right + Inter-button
} else {
buttonSize.width = CGRectGetWidth(bounds) - self.configuration.contentInset * (self.actionButtons.count - 1); // Inter-button only
}
buttonSize.width /= self.actionButtons.count;
} break;
case FBNCardActionsLayoutStyleVertical: {
if (self.configuration.style == FBNCardActionsStyleAttached) {
buttonSize.width = CGRectGetWidth(bounds) - (self.configuration.contentInset * 2); // Left + Right
} else {
buttonSize.width = CGRectGetWidth(bounds);
}
} break;
default:break;
}
CGPoint buttonOrigin = { .x = 0.0f, .y = self.configuration.topInset };
CGRect buttonFrame = FBNRectAdjustToScreenScale(FBNRectMakeWithOriginSize(buttonOrigin, buttonSize), NSRoundUp);
if (self.configuration.style == FBNCardActionsStyleAttached) {
buttonFrame.origin.x = self.configuration.contentInset;
}
for (FBNCardActionButton *button in self.actionButtons) {
button.frame = FBNRectAdjustToScreenScale(buttonFrame, NSRoundUp);
switch (self.configuration.layoutStyle) {
case FBNCardActionsLayoutStyleHorizontal: {
buttonFrame.origin.x = CGRectGetMaxX(buttonFrame) + self.configuration.contentInset; // Previous button + Inset
} break;
case FBNCardActionsLayoutStyleVertical: {
buttonFrame.origin.y = CGRectGetMaxY(buttonFrame) + self.configuration.contentInset; // Previous button + Inset
} break;
default:break;
}
}
}
///--------------------------------------
#pragma mark - FBNContentSizeProvider
///--------------------------------------
- (CGSize)contentSizeThatFitsParentContainerSize:(CGSize)fitSize {
CGSize size = CGSizeMake(fitSize.width, self.configuration.topInset);
switch (self.configuration.layoutStyle) {
case FBNCardActionsLayoutStyleVertical: {
size.height += self.configuration.height * self.actionButtons.count; // All the buttons
size.height += self.configuration.contentInset * self.actionButtons.count; // Inter-button + Bottom
} break;
case FBNCardActionsLayoutStyleHorizontal: {
size.height += self.configuration.height; // Single button
size.height += self.configuration.contentInset; // Bottom inset
} break;
default: break;
}
return FBNSizeAdjustToScreenScale(size, NSRoundUp);
}
///--------------------------------------
#pragma mark - Buttons
///--------------------------------------
+ (NSArray *)_actionButtonsFromConfiguration:(FBNCardActionsConfiguration *)configuration {
NSMutableArray *actionButtons = [NSMutableArray arrayWithCapacity:configuration.actions.count];
FBNCardButtonAction buttonAction = FBNCardButtonActionPrimary;
for (FBNCardActionConfiguration *actionConfiguration in configuration.actions) {
FBNCardActionButton *button = nil;
if (actionConfiguration.actionURL != nil) {
button = [FBNCardActionButton buttonFromConfiguration:actionConfiguration
withCornerRadius:configuration.cornerRadius
action:buttonAction];
if (buttonAction == FBNCardButtonActionPrimary) {
buttonAction = FBNCardButtonActionSecondary;
}
} else {
button = [FBNCardActionButton buttonFromConfiguration:actionConfiguration
withCornerRadius:configuration.cornerRadius
action:FBNCardButtonActionDismiss];
}
[actionButtons addObject:button];
}
return actionButtons;
}
- (void)_buttonAction:(FBNCardActionButton *)button {
[self.delegate actionsViewController:self didPerformButtonAction:button.action withOpenURL:button.configuration.actionURL];
}
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Views/FBNCardBodyViewController.h
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import
#import "FBNContentSizeProvider.h"
@class FBNAssetsController;
@class FBNCardBodyConfiguration;
NS_ASSUME_NONNULL_BEGIN
@interface FBNCardBodyViewController : UIViewController
- (instancetype)initWithAssetsController:(FBNAssetsController *)controller
configuration:(FBNCardBodyConfiguration *)configuration
contentInset:(CGFloat)contentInset;
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;
- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: iOS/FBNotifications/FBNotifications/Internal/Views/FBNCardBodyViewController.m
================================================
// Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "FBNCardBodyViewController.h"
#import "FBNAssetsController.h"
#import "FBNCardBodyConfiguration.h"
#import "FBNCardTextContent.h"
#import "FBNCardLabel.h"
#import "FBNCardViewUtilities.h"
NS_ASSUME_NONNULL_BEGIN
@interface FBNCardBodyViewController ()
@property (nonatomic, strong, readonly) FBNAssetsController *assetsController;
@property (nonatomic, strong, readonly) FBNCardBodyConfiguration *configuration;
@property (nonatomic, assign, readonly) CGFloat contentInset;
@property (nullable, nonatomic, strong) UIViewController