diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 8bc077d..9387d93 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -12,9 +12,16 @@ assignees: '' Before creating an issue, make sure to check our documentation and other issues for possible fixes to your problem * https://pub.dev/documentation/snapkit/latest/ - * https://github.com/TimmyRB/snapkit/wiki + * https://github.com/TimmyRB/snapkit/README.md --> +## Versions + + - SnapKit: + - Snapchat: + - iOS: + - Android: + ## Steps to Reproduce diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index 6859606..bf06e4c 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -19,10 +19,10 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-java@v1 with: - java-version: '12.x' + java-version: '17.x' - uses: subosito/flutter-action@v1 with: - flutter-version: '2.0.5' + flutter-version: '3.16.5' - name: Get Dependancies run: flutter pub get diff --git a/.github/workflows/build-ios.yml b/.github/workflows/build-ios.yml index 78b406f..84d9ea5 100644 --- a/.github/workflows/build-ios.yml +++ b/.github/workflows/build-ios.yml @@ -19,10 +19,10 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-java@v1 with: - java-version: '12.x' + java-version: '17.x' - uses: subosito/flutter-action@v1 with: - flutter-version: '2.0.5' + flutter-version: '3.16.5' - name: Get Dependancies run: flutter pub get diff --git a/.github/workflows/code-analysis.yml b/.github/workflows/code-analysis.yml index f39f289..4472bce 100644 --- a/.github/workflows/code-analysis.yml +++ b/.github/workflows/code-analysis.yml @@ -19,10 +19,10 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-java@v1 with: - java-version: '12.x' + java-version: '17.x' - uses: subosito/flutter-action@v1 with: - flutter-version: '2.0.5' + flutter-version: '3.16.5' - name: Get Dependancies run: flutter pub get diff --git a/.idea/libraries/Dart_SDK.xml b/.idea/libraries/Dart_SDK.xml deleted file mode 100644 index f47eb81..0000000 --- a/.idea/libraries/Dart_SDK.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index e15c872..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/.idea/runConfigurations/example_lib_main_dart.xml b/.idea/runConfigurations/example_lib_main_dart.xml deleted file mode 100644 index 5fd9159..0000000 --- a/.idea/runConfigurations/example_lib_main_dart.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index b2558cb..0000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.metadata b/.metadata index 3596a29..22257e4 100644 --- a/.metadata +++ b/.metadata @@ -4,7 +4,30 @@ # This file should be version controlled and should not be manually edited. version: - revision: 48c9d3e0e19e8fec84f1d316ce0559f26ca7277d - channel: beta + revision: "78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9" + channel: "stable" project_type: plugin + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + - platform: android + create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + - platform: ios + create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/CHANGELOG.md b/CHANGELOG.md index 34b7811..fdb762d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,4 +44,19 @@ * Fixed an issue where a user not sharing their Bitmoji would cause an exception * Fixed Videos not working on Android Clients * Fixed an issue where some Videos wouldn't work despite meeting Snapchat's Video requirements -* Bug fixes & Code improvements \ No newline at end of file +* Bug fixes & Code improvements + +## 3.0.0 + +* Restructured the project +* Upgraded SnapSDK to 2.1.0 for Android +* Upgraded SnapSDK to 2.5.0 for iOS +* Split LoginKit & CreativeKit into their own classes +* Classes now have static references to an Instance allow calls across pages without having to pass instances around +* The current user is now saved on an instance of LoginKit allowing access across your entire app +* Added OIDC to the current user's data +* Added a caller for the access token +* Added lots of error checking in platform code and more verbose errors +* Fixed issue where videos wouldn't send on Android +* Removed deprecated Verify Phone Number + diff --git a/README.md b/README.md index 6443e0f..94b45e8 100644 --- a/README.md +++ b/README.md @@ -1,111 +1,337 @@ # Snapkit [![Pub Package](https://img.shields.io/pub/v/snapkit.svg)](https://pub.dev/packages/snapkit) -[![Code Analysis](https://github.com/TimmyRB/snapkit/actions/workflows/code-analysis.yml/badge.svg)](https://github.com/TimmyRB/snapkit/actions/workflows/code-analysis.yml) -[![Android Builds](https://github.com/TimmyRB/snapkit/actions/workflows/build-android.yml/badge.svg)](https://github.com/TimmyRB/snapkit/actions/workflows/build-android.yml) -[![iOS Builds](https://github.com/TimmyRB/snapkit/actions/workflows/build-ios.yml/badge.svg)](https://github.com/TimmyRB/snapkit/actions/workflows/build-ios.yml) +[![Code Analysis](https://github.com/TimmyRB/snapkit/actions/workflows/code-analysis.yml/badge.svg)](https://github.com/TimmyRB/snapkit/actions/workflows/code-analysis.yml) +[![Android Builds](https://github.com/TimmyRB/snapkit/actions/workflows/build-android.yml/badge.svg)](https://github.com/TimmyRB/snapkit/actions/workflows/build-android.yml) +[![iOS Builds](https://github.com/TimmyRB/snapkit/actions/workflows/build-ios.yml/badge.svg)](https://github.com/TimmyRB/snapkit/actions/workflows/build-ios.yml) -A plugin that allows developers like you to integrate with Snapchat (using [SnapKit](https://kit.snapchat.com)) into your Flutter applications! +A plugin that allows developers like you to integrate with Snapchat (using [Snapchat's Native SnapKit](https://kit.snapchat.com)) in your Flutter applications! -## Getting Started +Contents: -Follow the [Wiki](https://github.com/TimmyRB/snapkit/wiki) for steps on how to get setup in an existing project or just copy the [example](example) project into a directory of your choosing and rename it. + - [What's New](#✨-whats-new) + - [Installation](#🛠️-installation) + - [Upgrading](#upgrading-from-older-versions) + - [iOS Setup](#-ios-setup) + - [Android Setup](#🤖-android-setup) + - [Usage](#✏️-usage) + - [LoginKit](#loginkit) + - [CreativeKit](#creativekit) -## Usage +## ✨ What's new -### Create new Instance -```dart -Snapkit snapkit = new Snapkit(); +This flutter plugin has now been updated to 3.0.0 and contains breaking changes from any project using versions <= 2.0.0. This plugin now uses the following versions for Snapchat's native SDKs. + +``` +iOS: ~2.5.0 +Android: ~2.1.0 ``` -### AuthState Stream -```dart -snapkit.onAuthStateChanged.listen((SnapchatUser? user) { - // Do something with the returned SnapchatUser or null here -}); +## 🛠️ Installation + +Add it to your project +``` +flutter pub add snapkit ``` -### AuthState Class +Import it ```dart -class MyAppState extends State implements SnapchatAuthStateListener { +import 'package:snapkit/snapkit.dart'; +``` - snapkit.addAuthStateListener(this); +The following setup instructions assume you have created an app on the [Snapchat Developer Portal](https://devportal.snap.com/manage/) and have enabled 'Login Kit', 'Bitmoji Kit' & 'Creative Kit' in your app's settings. Make sure to setup a redirect URI in the 'Login Kit' settings and add your Snapchat username as a demo user in the general tab. - @override - void onLogin(SnapchatUser user) { - // Do something with the returned SnapchatUser here - } +### Upgrading from older versions - @override - void onLogout() { - // Do something on logout - } +On iOS the Installation is the same as before, however on Android you will need to modify and remove a few lines if you're upgrading from < 3.0.0. +Firstly, remove this line from your `app/android/build.grade` + +```groovy +maven { + url "https://storage.googleapis.com/snap-kit-build/maven" } ``` -### Login +Next, in your `app/android/app/build.grade`, remove these lines + +```groovy + implementation([ + 'com.snapchat.kit.sdk:creative:1.10.0', + 'com.snapchat.kit.sdk:login:1.10.0', + 'com.snapchat.kit.sdk:bitmoji:1.10.0', + 'com.snapchat.kit.sdk:core:1.10.0' +]) +``` + +Finally, in your `app/android/app/src/main/AndroidManifest.xml` + +Change the following +```xml +com.snapchat.kit.sdk.clientId → com.snap.kit.clientId +com.snapchat.kit.sdk.redirectUrl → com.snap.kit.redirectUrl +com.snapchat.kit.sdk.scopes → com.snap.kit.scopes +``` + +And remove these lines +```xml + + + +``` + +###  iOS Setup + +Add the following to your `Info.plist` in `app/ios/Runner/`. Make sure to replace `YOUR_CLIENT_ID_HERE`, `YOUR_REDIRECT_URL_HERE` & `YOUR_URL_SCHEME_HERE` with the correct information from your [Snapchat Developer Portal](https://devportal.snap.com/manage/) + +```plist +SCSDKClientId +YOUR_CLIENT_ID_HERE +SCSDKRedirectUrl +YOUR_REDIRECT_URL_HERE +SCSDKScopes + + https://auth.snapchat.com/oauth2/api/user.display_name + https://auth.snapchat.com/oauth2/api/user.bitmoji.avatar + +LSApplicationQueriesSchemes + + snapchat + bitmoji-sdk + itms-apps + +CFBundleURLSchemes + + YOUR_URL_SCHEME_HERE + +``` + +Your redirect url should be in a similar format and must match one of the redirect URIs you should've made in the Login Kit settings on the [Snapchat Developer Portal](https://devportal.snap.com/manage/) +``` +myapp://snapkit/oauth2 +``` + +Your url scheme is the text that comes before `://` in your redirect url, so if you are using the redirect url above, your url scheme would be +``` +myapp +``` + +Next in XCode, open `Runner.xcworkspace` from `app/ios/`, then in `Runner → Targets → Runner → Info` scroll to the bottom of the `Info` tab, expand `URL Types` and press the add + button. Set the identifer to be your application's bundle identifier e.g. `com.example.app` and set URL schemes to be the url scheme you determined above, e.g. `myapp`. + +Finally, add the following to your `AppDelegate.swift` file in `app/ios/Runner/` in order to be able to use the redirect url you just created + +```swift +import UIKit +import Flutter +import SCSDKLoginKit // Add this import + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + // Add this function + override func application( + _ app: UIApplication, + open url: URL, + options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { + return SCSDKLoginClient.application(app, open: url, options: options) + } +} +``` + +### 🤖 Android Setup + +Add the following to your `AndroidMainfest.xml` in `app/android/app/src/main` Make sure to replace `YOUR_CLIENT_ID_HERE`, `YOUR_SCHEME`, `YOUR_HOST` & `YOUR_PATH` with the correct information from your [Snapchat Developer Portal](https://devportal.snap.com/manage/) + +`YOUR_SCHEME://YOUR_HOST/YOUR_PATH` Is your redirect URL from the Developer Portal that you should've made when enabling LoginKit. You will need to split up these redirect url segements in the `SnapKitActivity` block. + +```xml + + + + + + + + + + + + + + + + + + + + + + + +... +``` + +Create a file named `arrays.xml` in `app/android/app/src/main/res/values` with the following content +```xml + + + + https://auth.snapchat.com/oauth2/api/user.bitmoji.avatar + https://auth.snapchat.com/oauth2/api/user.display_name + https://auth.snapchat.com/oauth2/api/user.external_id + + +``` + +Create another file named `file_paths.xml` in `app/android/app/src/main/res/xml` with the following +```xml + + + + +``` + + +## ✏️ Usage + +Determine if Snapchat is installed on a user's device ```dart -await snapkit.login(); +await SnapKit.I.isSnapchatInstalled() → bool +``` + +Check the version of the native SnapSDK running +```dart +await SnapKit.I.getSnapSDKVersion() → String +``` -// or +### LoginKit -snapkit.login().then(user => {}); +This Kit is used for authenticating with the user's Snapchat account. Using this Kit allows you to get a user's External ID, OIDC, Display Name, Bitmoji URL & Access Token. + +Check if a user is already logged in +```dart +await LoginKit.I.isLoggedIn() → bool ``` -### Logout +Start the login flow ```dart -await snapkit.logout(); +await LoginKit.I.login() +``` -// or +Logging in automatically fetchs the user's data from Snapchat, however if you need to refresh it for whatever reason you can do so manually +```dart +await LoginKit.I.getCurrentUser() +``` -snapkit.logout().then(() => {}); +Since only one user can be authenticated with Snapchat at a time on a device, the current user and their data is found here +```dart +LoginKit.I.currentUser → SnapchatUser +``` + +Retrieve the access token like this +```dart +await LoginKit.I.getAccessToken() → String? ``` -### Verify a Phone Number -Returns a `bool` if Snapchat has verified the phone number, throws -an error if there was a problem. Always returns `false` on Android +Logout the user and unlink from the current session ```dart -snapkit.verifyPhoneNumber('US', '1231234567') - .then(isVerified {}) - .catchError((error, StackTrace stacktrace) {}) +await LoginKit.I.logout() ``` -## Share to Snapchat +### CreativeKit + +This Kit is used for sharing photos, videos, stickers and more to Snapchat from inside your app. This Kit does **not** require the user be authenticated with LoginKit. + +#### Media Size and Length Restrictions: + +Shared media must be 300 MB or smaller.\ +Videos must be 60 seconds or shorter.\ +Videos that are longer than 10 seconds are split up into multiple Snaps of 10 seconds or less. + +#### Suggested Media Parameters: + +Aspect Ratio: 9:16\ +Preferred Image File Types: .jpg or .png\ +Preferred Video File Types: .mp4 or .mov\ +Dimensions: 1080px x 1920px\ +Video Bitrate: 1080p at 8mbps or 720p at 5mbps + +Create a Sticker +```dart +var sticker = CreativeKitSticker( + AssetImage('assets/image.png'), + size: StickerSize(32, 32), + offset: StickerOffset(0.5, 0.5), + rotation: StickerRotation(30), + ); +``` -### Share to LIVE +Share to the Snapchat Camera ```dart -snapkit.share(SnapchatMediaType.NONE, - sticker: SnapchatSticker?, - caption: String?, - attachmentUrl: String? -); +CreativeKit.I.shareToCamera( + sticker: sticker, + caption: 'This is Awesome!', + link: Uri.parse('https://jacobbrasil.com/'), +) ``` -### Share with Background Photo +Share with a background Photo ```dart -snapkit.share(SnapchatMediaType.PHOTO, - image: ImageProvider, - sticker: SnapchatSticker?, - caption: String?, - attachmentUrl: String? -); +CreativeKit.I.shareWithPhoto( + AssetImage('assets/image.png'), + sticker: sticker, + caption: 'This is Awesome!', + link: Uri.parse('https://jacobbrasil.com/'), +) ``` -### Share with Background Video -Currently unavailable on Android +Share with a background Video that's available locally ```dart -snapkit.share(SnapchatMediaType.VIDEO, - videoUrl: String, - sticker: SnapchatSticker?, - caption: String?, - attachmentUrl: String? -); +CreativeKit.I.shareWithVideo( + DefaultAssetBundle.of(context).load('assets/video.mp4'), + sticker: sticker, + caption: 'This is Awesome!', + link: Uri.parse('https://jacobbrasil.com/'), +) ``` -### SnapchatSticker +Share with a background Video that's available online ```dart -new SnapchatSticker( - image: ImageProvider -); +CreativeKit.I.shareWithRemoteVideo( + Uri.parse('https://link.to/video.mp4'), + sticker: sticker, + caption: 'This is Awesome!', + link: Uri.parse('https://jacobbrasil.com/'), +) ``` diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..a5744c1 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/android/.classpath b/android/.classpath deleted file mode 100644 index 4a04201..0000000 --- a/android/.classpath +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/android/.gitignore b/android/.gitignore index c6cbe56..161bdcd 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -6,3 +6,4 @@ .DS_Store /build /captures +.cxx diff --git a/android/.settings/org.eclipse.buildship.core.prefs b/android/.settings/org.eclipse.buildship.core.prefs deleted file mode 100644 index 4b1bb56..0000000 --- a/android/.settings/org.eclipse.buildship.core.prefs +++ /dev/null @@ -1,13 +0,0 @@ -arguments= -auto.sync=false -build.scans.enabled=false -connection.gradle.distribution=GRADLE_DISTRIBUTION(VERSION(6.8)) -connection.project.dir=../example/android -eclipse.preferences.version=1 -gradle.user.home= -java.home=/Library/Java/JavaVirtualMachines/adoptopenjdk-11.jdk/Contents/Home -jvm.arguments= -offline.mode=false -override.workspace.settings=true -show.console.view=true -show.executions.view=true diff --git a/android/bin/.gitignore b/android/bin/.gitignore new file mode 100644 index 0000000..161bdcd --- /dev/null +++ b/android/bin/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.cxx diff --git a/android/.project b/android/bin/.project similarity index 87% rename from android/.project rename to android/bin/.project index a5e9298..39b047f 100644 --- a/android/.project +++ b/android/bin/.project @@ -22,12 +22,12 @@ - 1613022023091 + 1706223300483 30 org.eclipse.core.resources.regexFilterMatcher - node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ diff --git a/android/bin/build.gradle b/android/bin/build.gradle new file mode 100644 index 0000000..ba25be6 --- /dev/null +++ b/android/bin/build.gradle @@ -0,0 +1,72 @@ +group 'com.jacobbrasil.snapkit' +version '1.0-SNAPSHOT' + +buildscript { + ext.kotlin_version = '1.7.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.3.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + if (project.android.hasProperty("namespace")) { + namespace 'com.jacobbrasil.snapkit' + } + + compileSdkVersion 33 + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + test.java.srcDirs += 'src/test/kotlin' + } + + defaultConfig { + minSdkVersion 19 + } + + dependencies { + testImplementation 'org.jetbrains.kotlin:kotlin-test' + testImplementation 'org.mockito:mockito-core:5.0.0' + implementation([ + 'com.snap.loginkit:loginkit:2.0.0', // for Login Kit + 'com.snap.creativekit:creativekit:2.0.0', // for Creative Kit + ]) + } + + testOptions { + unitTests.all { + useJUnitPlatform() + + testLogging { + events "passed", "skipped", "failed", "standardOut", "standardError" + outputs.upToDateWhen {false} + showStandardStreams = true + } + } + } +} diff --git a/android/bin/settings.gradle b/android/bin/settings.gradle new file mode 100644 index 0000000..5f554cc --- /dev/null +++ b/android/bin/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'snapkit' diff --git a/android/bin/src/main/AndroidManifest.xml b/android/bin/src/main/AndroidManifest.xml new file mode 100644 index 0000000..e1bae7f --- /dev/null +++ b/android/bin/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/android/bin/src/main/kotlin/com/jacobbrasil/snapkit/SnapkitPlugin.kt b/android/bin/src/main/kotlin/com/jacobbrasil/snapkit/SnapkitPlugin.kt new file mode 100644 index 0000000..cbc0e2b --- /dev/null +++ b/android/bin/src/main/kotlin/com/jacobbrasil/snapkit/SnapkitPlugin.kt @@ -0,0 +1,35 @@ +package com.jacobbrasil.snapkit + +import androidx.annotation.NonNull + +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import io.flutter.plugin.common.MethodChannel.Result + +/** SnapkitPlugin */ +class SnapkitPlugin: FlutterPlugin, MethodCallHandler { + /// The MethodChannel that will the communication between Flutter and native Android + /// + /// This local reference serves to register the plugin with the Flutter Engine and unregister it + /// when the Flutter Engine is detached from the Activity + private lateinit var channel : MethodChannel + + override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + channel = MethodChannel(flutterPluginBinding.binaryMessenger, "snapkit") + channel.setMethodCallHandler(this) + } + + override fun onMethodCall(call: MethodCall, result: Result) { + if (call.method == "getPlatformVersion") { + result.success("Android ${android.os.Build.VERSION.RELEASE}") + } else { + result.notImplemented() + } + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + channel.setMethodCallHandler(null) + } +} diff --git a/android/bin/src/test/kotlin/com/jacobbrasil/snapkit/SnapkitPluginTest.kt b/android/bin/src/test/kotlin/com/jacobbrasil/snapkit/SnapkitPluginTest.kt new file mode 100644 index 0000000..d1bcbe8 --- /dev/null +++ b/android/bin/src/test/kotlin/com/jacobbrasil/snapkit/SnapkitPluginTest.kt @@ -0,0 +1,27 @@ +package com.jacobbrasil.snapkit + +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import kotlin.test.Test +import org.mockito.Mockito + +/* + * This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation. + * + * Once you have built the plugin's example app, you can run these tests from the command + * line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or + * you can run them directly from IDEs that support JUnit such as Android Studio. + */ + +internal class SnapkitPluginTest { + @Test + fun onMethodCall_getPlatformVersion_returnsExpectedValue() { + val plugin = SnapkitPlugin() + + val call = MethodCall("getPlatformVersion", null) + val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java) + plugin.onMethodCall(call, mockResult) + + Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE) + } +} diff --git a/android/build.gradle b/android/build.gradle index c94748f..9520b07 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,42 +1,72 @@ group 'com.jacobbrasil.snapkit' -version '1.0' +version '1.0-SNAPSHOT' buildscript { + ext.kotlin_version = '1.7.10' repositories { google() - jcenter() + mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.0' + classpath 'com.android.tools.build:gradle:7.3.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } -rootProject.allprojects { +allprojects { repositories { google() - jcenter() - maven { - url "https://storage.googleapis.com/snap-kit-build/maven" - } + mavenCentral() } } apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' android { - compileSdkVersion 30 + if (project.android.hasProperty("namespace")) { + namespace 'com.jacobbrasil.snapkit' + } + + compileSdk 33 + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + test.java.srcDirs += 'src/test/kotlin' + } defaultConfig { - minSdkVersion 19 + minSdkVersion 33 } -} -dependencies { - implementation([ - 'com.snapchat.kit.sdk:creative:1.10.0', - 'com.snapchat.kit.sdk:login:1.10.0', - 'com.snapchat.kit.sdk:bitmoji:1.10.0', - 'com.snapchat.kit.sdk:core:1.10.0' - ]) + dependencies { + testImplementation 'org.jetbrains.kotlin:kotlin-test' + testImplementation 'org.mockito:mockito-core:5.0.0' + implementation([ + 'com.snap.loginkit:loginkit:2.1.0', // for Login Kit + 'com.snap.creativekit:creativekit:2.1.0', // for Creative Kit + ]) + } + + testOptions { + unitTests.all { + useJUnitPlatform() + + testLogging { + events "passed", "skipped", "failed", "standardOut", "standardError" + outputs.upToDateWhen {false} + showStandardStreams = true + } + } + } } diff --git a/android/gradle.properties b/android/gradle.properties deleted file mode 100644 index 94adc3a..0000000 --- a/android/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -org.gradle.jvmargs=-Xmx1536M -android.useAndroidX=true -android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..7454180 Binary files /dev/null and b/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 3c9d085..0e9a610 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index e1bae7f..2be0412 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1,3 +1,6 @@ + + + diff --git a/android/src/main/java/com/jacobbrasil/snapkit/SnapkitPlugin.java b/android/src/main/java/com/jacobbrasil/snapkit/SnapkitPlugin.java deleted file mode 100644 index 7159187..0000000 --- a/android/src/main/java/com/jacobbrasil/snapkit/SnapkitPlugin.java +++ /dev/null @@ -1,231 +0,0 @@ -package com.jacobbrasil.snapkit; - -import android.app.Activity; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.snapchat.kit.sdk.SnapCreative; -import com.snapchat.kit.sdk.SnapLogin; -import com.snapchat.kit.sdk.core.controller.LoginStateController.OnLoginStateChangedListener; -import com.snapchat.kit.sdk.creative.api.SnapCreativeKitApi; -import com.snapchat.kit.sdk.creative.exceptions.SnapMediaSizeException; -import com.snapchat.kit.sdk.creative.exceptions.SnapStickerSizeException; -import com.snapchat.kit.sdk.creative.exceptions.SnapVideoLengthException; -import com.snapchat.kit.sdk.creative.media.SnapMediaFactory; -import com.snapchat.kit.sdk.creative.media.SnapPhotoFile; -import com.snapchat.kit.sdk.creative.media.SnapSticker; -import com.snapchat.kit.sdk.creative.media.SnapVideoFile; -import com.snapchat.kit.sdk.creative.models.SnapContent; -import com.snapchat.kit.sdk.creative.models.SnapLiveCameraContent; -import com.snapchat.kit.sdk.creative.models.SnapPhotoContent; -import com.snapchat.kit.sdk.creative.models.SnapVideoContent; -import com.snapchat.kit.sdk.login.models.MeData; -import com.snapchat.kit.sdk.login.models.UserDataResponse; -import com.snapchat.kit.sdk.login.networking.FetchUserDataCallback; -import com.snapchat.kit.sdk.util.SnapUtils; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import io.flutter.embedding.engine.plugins.FlutterPlugin; -import io.flutter.embedding.engine.plugins.activity.ActivityAware; -import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; -import io.flutter.plugin.common.MethodCall; -import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.MethodChannel.MethodCallHandler; -import io.flutter.plugin.common.MethodChannel.Result; - -/** - * SnapkitPlugin - */ -public class SnapkitPlugin implements FlutterPlugin, MethodCallHandler, ActivityAware, OnLoginStateChangedListener { - /// The MethodChannel that will the communication between Flutter and native Android - /// - /// This local reference serves to register the plugin with the Flutter Engine and unregister it - /// when the Flutter Engine is detached from the Activity - private MethodChannel channel; - private Activity _activity; - private MethodChannel.Result _result; - private SnapCreativeKitApi creativeKitApi; - private SnapMediaFactory mediaFactory; - - @Override - public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { - channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "snapkit"); - channel.setMethodCallHandler(this); - } - - @Override - public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) { - switch (call.method) { - case "callLogin": - SnapLogin.getLoginStateController(_activity).addOnLoginStateChangedListener(this); - SnapLogin.getAuthTokenManager(_activity).startTokenGrant(); - _result = result; - break; - case "getUser": - String query = "{me{externalId, displayName, bitmoji{selfie}}}"; - SnapLogin.fetchUserData(_activity, query, null, new FetchUserDataCallback() { - @Override - public void onSuccess(@Nullable UserDataResponse userDataResponse) { - if (userDataResponse == null || userDataResponse.getData() == null) { - return; - } - - MeData meData = userDataResponse.getData().getMe(); - if (meData == null) { - result.error("GetUserError", "Returned MeData was null", null); - return; - } - - List res = new ArrayList(); - res.add(meData.getExternalId()); - res.add(meData.getDisplayName()); - res.add(meData.getBitmojiData().getSelfie()); - - result.success(res); - } - - @Override - public void onFailure(boolean isNetworkError, int statusCode) { - if (isNetworkError) { - result.error("NetworkGetUserError", "Network Error", statusCode); - } else { - result.error("UnknownGetUserError", "Unknown Error", statusCode); - } - } - }); - break; - case "sendMedia": - if (creativeKitApi == null) creativeKitApi = SnapCreative.getApi(_activity); - if (mediaFactory == null) mediaFactory = SnapCreative.getMediaFactory(_activity); - - SnapContent content; - switch ((String)call.argument("mediaType")) { - case "PHOTO": - try { - SnapPhotoFile photoFile = mediaFactory.getSnapPhotoFromFile(new File((String) call.argument("imagePath"))); - content = new SnapPhotoContent(photoFile); - } catch (SnapMediaSizeException e) { - result.error("SendMediaError", "Could not create SnapPhotoFile", e); - return; - } catch (NullPointerException e) { - result.error("SendMediaError", "Could not find Image file", e); - return; - } - - break; - case "VIDEO": - try { - SnapVideoFile videoFile = mediaFactory.getSnapVideoFromFile(new File((String) call.argument("videoPath"))); - content = new SnapVideoContent(videoFile); - } catch (SnapMediaSizeException | SnapVideoLengthException e) { - result.error("SendMediaError", "Could not create SnapVideoFile", e); - return; - } catch (NullPointerException e) { - result.error("SendMediaError", "Could not find Video file", e); - return; - } - - break; - default: - content = new SnapLiveCameraContent(); - break; - } - - content.setCaptionText((String)call.argument("caption")); - content.setAttachmentUrl((String)call.argument("attachmentUrl")); - - if (call.argument("sticker") != null) { - Map stickerMap = (Map) call.argument("sticker"); - SnapSticker sticker = null; - try { - sticker = mediaFactory.getSnapStickerFromFile(new File((String) stickerMap.get("imagePath"))); - } catch (SnapStickerSizeException e) { - result.error("SendMediaError", "Could not create SnapSticker", e); - return; - } catch (NullPointerException e) { - result.error("SendMediaError", "Could not find Sticker file", e); - return; - } - - if (sticker != null) { - sticker.setWidthDp(Float.parseFloat(stickerMap.get("width").toString())); - sticker.setHeightDp(Float.parseFloat(stickerMap.get("height").toString())); - - sticker.setPosX(Float.parseFloat(stickerMap.get("offsetX").toString())); - sticker.setPosY(Float.parseFloat(stickerMap.get("offsetY").toString())); - - sticker.setRotationDegreesClockwise(Float.parseFloat(stickerMap.get("rotation").toString())); - - content.setSnapSticker(sticker); - } - } - - creativeKitApi.send(content); - break; - case "verifyNumber": - List res = new ArrayList(); - res.add(""); - res.add(""); - result.success(res); - break; - case "callLogout": - SnapLogin.getAuthTokenManager(_activity).clearToken(); - _result = result; - break; - case "isInstalled": - result.success(SnapUtils.isSnapchatInstalled(_activity.getPackageManager(), "com.snapchat.android")); - break; - case "getPlatformVersion": - result.success("Android " + android.os.Build.VERSION.RELEASE); - break; - default: - result.notImplemented(); - break; - } - } - - @Override - public void onLoginSucceeded() { - _result.success("Login Success"); - } - - @Override - public void onLoginFailed() { - _result.error("LoginError", "Error Logging In", null); - } - - @Override - public void onLogout() { - _result.success("Logout Success"); - } - - @Override - public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { - channel.setMethodCallHandler(null); - } - - @Override - public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { - _activity = binding.getActivity(); - } - - @Override - public void onDetachedFromActivityForConfigChanges() { - - } - - @Override - public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { - - } - - @Override - public void onDetachedFromActivity() { - - } -} diff --git a/android/src/main/kotlin/com/jacobbrasil/snapkit/SnapkitPlugin.kt b/android/src/main/kotlin/com/jacobbrasil/snapkit/SnapkitPlugin.kt new file mode 100644 index 0000000..07158d5 --- /dev/null +++ b/android/src/main/kotlin/com/jacobbrasil/snapkit/SnapkitPlugin.kt @@ -0,0 +1,316 @@ +package com.jacobbrasil.snapkit + +import android.app.Activity +import android.content.pm.PackageManager +import android.content.pm.PackageManager.PackageInfoFlags +import android.util.Log +import com.snap.creativekit.SnapCreative +import com.snap.creativekit.api.SnapCreativeKitApi +import com.snap.creativekit.api.SnapCreativeKitCompletionCallback +import com.snap.creativekit.api.SnapCreativeKitSendError +import com.snap.creativekit.exceptions.SnapMediaSizeException +import com.snap.creativekit.exceptions.SnapStickerSizeException +import com.snap.creativekit.exceptions.SnapVideoLengthException +import com.snap.creativekit.media.SnapMediaFactory +import com.snap.creativekit.models.SnapContent +import com.snap.creativekit.models.SnapLiveCameraContent +import com.snap.creativekit.models.SnapPhotoContent +import com.snap.creativekit.models.SnapVideoContent +import com.snap.loginkit.AccessTokenResultCallback +import com.snap.loginkit.BitmojiQuery +import com.snap.loginkit.LoginResultCallback +import com.snap.loginkit.SnapLoginProvider +import com.snap.loginkit.UserDataQuery +import com.snap.loginkit.UserDataResultCallback +import com.snap.loginkit.exceptions.AccessTokenException +import com.snap.loginkit.exceptions.LoginException +import com.snap.loginkit.exceptions.UserDataException +import com.snap.loginkit.models.UserDataResult +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.embedding.engine.plugins.activity.ActivityAware +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import io.flutter.plugin.common.MethodChannel.Result +import java.io.File +import java.util.Objects + + +/** SnapkitPlugin */ +class SnapkitPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { + /// The MethodChannel that will the communication between Flutter and native Android + /// + /// This local reference serves to register the plugin with the Flutter Engine and unregister it + /// when the Flutter Engine is detached from the Activity + private lateinit var channel : MethodChannel + + private var _activity : Activity? = null + + private var _snapApi : SnapCreativeKitApi? = null + private var _snapMediaFactory : SnapMediaFactory? = null + + private fun requireActivity(): Activity { + return Objects.requireNonNull(_activity, "Snapkit plugin is not attached to an activity.") + } + + private fun getSnapApi(): SnapCreativeKitApi { + if (_snapApi == null) { + _snapApi = SnapCreative.getApi(requireActivity()) + } + + return _snapApi!! + } + + private fun getSnapMediaFactory(): SnapMediaFactory { + if (_snapMediaFactory == null) { + _snapMediaFactory = SnapCreative.getMediaFactory(requireActivity()) + } + + return _snapMediaFactory!! + } + + override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + channel = MethodChannel(flutterPluginBinding.binaryMessenger, "snapkit") + channel.setMethodCallHandler(this) + } + + override fun onMethodCall(call: MethodCall, result: Result) { + when (call.method) { + "sdkVersion" -> { + result.success(SnapLoginProvider.getVersion()) + } + "isSnapchatInstalled" -> { + try { + val pm: PackageManager = requireActivity().packageManager + pm.getPackageInfo("com.snapchat.android", PackageInfoFlags.of(0)) + result.success(true) + } catch (e: PackageManager.NameNotFoundException) { + result.success(false) + } + } + "isLoggedIn" -> { + result.success(SnapLoginProvider.get(requireActivity()).isUserLoggedIn) + } + "login" -> { + Log.d("SNAPKIT", "Pre-Login Flow: " + requireActivity()) + SnapLoginProvider.get(requireActivity()).startTokenGrant(object: LoginResultCallback { + override fun onStart() { + Log.d("SNAPKIT", "Login Flow Started") + } + + override fun onSuccess(accessToken: String) { + result.success("Login Success") + } + + override fun onFailure(exception: LoginException) { + result.error("LoginError", exception.localizedMessage, null) + } + }) + } + "getCurrentUser" -> { + val bitmojiQuery = BitmojiQuery.newBuilder().withAvatarId().withTwoDAvatarUrl().build() + val userDataQuery = UserDataQuery.newBuilder().withExternalId().withIdToken().withDisplayName().withBitmoji(bitmojiQuery).build() + + SnapLoginProvider.get(requireActivity()).fetchUserData(userDataQuery, object: UserDataResultCallback { + override fun onSuccess(userDataResult: UserDataResult) { + if (userDataResult.data?.meData == null) { + result.error("GetUserError", "User data was null", null) + return + } + + val meData = userDataResult.data!!.meData!! + val map: HashMap = HashMap() + map["externalId"] = meData.externalId + map["openIdToken"] = meData.idToken + map["displayName"] = meData.displayName + map["bitmoji2DAvatarUrl"] = meData.bitmojiData?.twoDAvatarUrl + map["bitmojiAvatarId"] = meData.bitmojiData?.avatarId + map["errors"] = null + + result.success(map) + } + + override fun onFailure(exception: UserDataException) { + result.error("GetUserError", exception.localizedMessage, exception) + } + }) + } + "getAccessToken" -> { + SnapLoginProvider.get(requireActivity()).fetchAccessToken(object: AccessTokenResultCallback { + override fun onSuccess(token: String) { + result.success(token) + } + + override fun onFailure(e: AccessTokenException) { + result.error("GetAccessTokenError", e.localizedMessage, e) + } + + }) + } + "logout" -> { + SnapLoginProvider.get(requireActivity()).clearToken() + result.success("Logout Success") + } + "shareToCamera" -> { + if (call.arguments !is Map<*, *>) { + return + } + + val args = call.arguments as Map<*, *> + + try { + val content = handleCommonShare(args, SnapLiveCameraContent()) + + getSnapApi().sendWithCompletionHandler(content, object: SnapCreativeKitCompletionCallback { + override fun onSendSuccess() { + result.success("ShareToCamera Success") + } + + override fun onSendFailed(e: SnapCreativeKitSendError?) { + result.error("ShareToCameraError", e?.name, e) + } + }) + } catch (e: SnapStickerSizeException) { + result.error("ShareToCameraError", e.localizedMessage, e) + } catch (e: SnapKitException) { + result.error("ShareToCameraError", e.localizedMessage, "Error caused by handleCommonShare") + } + } + "shareWithPhoto" -> { + if (call.arguments !is Map<*, *>) { + return + } + + val args = call.arguments as Map<*, *> + + try { + val photo = File(args["photoPath"].toString()) + + if (!photo.exists()) { + result.error("ShareWithPhotoError","Photo could not be found in filesystem", null) + } + + val content = handleCommonShare(args, SnapPhotoContent(getSnapMediaFactory().getSnapPhotoFromFile(photo))) + + getSnapApi().sendWithCompletionHandler(content, object: SnapCreativeKitCompletionCallback { + override fun onSendSuccess() { + result.success("ShareWithPhoto Success") + } + + override fun onSendFailed(e: SnapCreativeKitSendError?) { + result.error("ShareWithPhotoError", e?.name, e) + } + }) + } catch (e: SnapMediaSizeException) { + result.error("ShareWithPhotoError", e.localizedMessage, e) + } catch (e: SnapStickerSizeException) { + result.error("ShareWithPhotoError", e.localizedMessage, e) + } catch (e: SnapKitException) { + result.error("ShareWithPhotoError", e.localizedMessage, "Error caused by handleCommonShare") + } + } + "shareWithVideo" -> { + if (call.arguments !is Map<*, *>) { + return + } + + val args = call.arguments as Map<*, *> + + try { + val video = File(args["videoPath"].toString()) + + if (!video.exists()) { + result.error("ShareWithVideoError","Video could not be found in filesystem", null) + } + + val content = handleCommonShare(args, SnapVideoContent(getSnapMediaFactory().getSnapVideoFromFile(video))) + + getSnapApi().sendWithCompletionHandler(content, object: SnapCreativeKitCompletionCallback { + override fun onSendSuccess() { + result.success("ShareWithVideo Success") + } + + override fun onSendFailed(e: SnapCreativeKitSendError?) { + result.error("ShareWithVideoError", e?.name, e) + } + }) + } catch (e: SnapMediaSizeException) { + result.error("ShareWithVideoError", e.localizedMessage, e) + } catch (e: SnapVideoLengthException) { + result.error("ShareWithVideoError", e.localizedMessage, e) + } catch (e: SnapStickerSizeException) { + result.error("ShareWithVideoError", e.localizedMessage, e) + } catch (e: SnapKitException) { + result.error("ShareWithVideoError", e.localizedMessage, "Error caused by handleCommonShare") + } + } + else -> { + result.notImplemented() + } + } + } + + private fun handleCommonShare(args: Map<*, *>, content: SnapContent): SnapContent { + content.captionText = args["caption"].toString() + content.attachmentUrl = args["link"].toString() + + if (args["sticker"] is Map<*, *>) { + val sticker = args["sticker"] as Map<*, *> + + val image = File(sticker["imagePath"].toString()) + + if (!image.exists()) { + throw SnapKitException("Image could not be found in filesystem") + } + + try { + val snapSticker = getSnapMediaFactory().getSnapStickerFromFile(image) + + if (sticker["size"] is Map<*, *>) { + val size = sticker["size"] as Map<*, *> + snapSticker.setWidthDp((size["width"] as Double).toFloat()) + snapSticker.setHeightDp((size["height"] as Double).toFloat()) + } else { + snapSticker.setWidthDp(64f) + snapSticker.setHeightDp(64f) + } + + if (sticker["offset"] is Map<*, *>) { + val offset = sticker["offset"] as Map<*, *> + snapSticker.setPosX((offset["x"] as Double).toFloat()) + snapSticker.setPosY((offset["y"] as Double).toFloat()) + } + + if (sticker["rotation"] is Map<*, *>) { + val rotation = sticker["rotation"] as Map<*, *> + snapSticker.setRotationDegreesClockwise((rotation["angle"] as Double).toFloat()) + } + + content.snapSticker = snapSticker + } catch (e: SnapStickerSizeException) { + throw e + } + } + + return content + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + channel.setMethodCallHandler(null) + } + + override fun onAttachedToActivity(binding: ActivityPluginBinding) { + _activity = binding.activity + } + + override fun onDetachedFromActivity() { + _activity = null + } + + override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { } + + override fun onDetachedFromActivityForConfigChanges() { } +} + +class SnapKitException(message: String): Exception(message) diff --git a/android/src/test/kotlin/com/jacobbrasil/snapkit/SnapkitPluginTest.kt b/android/src/test/kotlin/com/jacobbrasil/snapkit/SnapkitPluginTest.kt new file mode 100644 index 0000000..d1bcbe8 --- /dev/null +++ b/android/src/test/kotlin/com/jacobbrasil/snapkit/SnapkitPluginTest.kt @@ -0,0 +1,27 @@ +package com.jacobbrasil.snapkit + +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import kotlin.test.Test +import org.mockito.Mockito + +/* + * This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation. + * + * Once you have built the plugin's example app, you can run these tests from the command + * line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or + * you can run them directly from IDEs that support JUnit such as Android Studio. + */ + +internal class SnapkitPluginTest { + @Test + fun onMethodCall_getPlatformVersion_returnsExpectedValue() { + val plugin = SnapkitPlugin() + + val call = MethodCall("getPlatformVersion", null) + val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java) + plugin.onMethodCall(call, mockResult) + + Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE) + } +} diff --git a/example/.gitignore b/example/.gitignore index 0fa6b67..29a3a50 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -8,6 +8,7 @@ .buildlog/ .history .svn/ +migrate_working_dir/ # IntelliJ related *.iml @@ -26,14 +27,10 @@ .dart_tool/ .flutter-plugins .flutter-plugins-dependencies -.packages .pub-cache/ .pub/ /build/ -# Web related -lib/generated_plugin_registrant.dart - # Symbolication related app.*.symbols diff --git a/example/.metadata b/example/.metadata index 9f8c408..25d90ec 100644 --- a/example/.metadata +++ b/example/.metadata @@ -1,10 +1,30 @@ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # -# This file should be version controlled and should not be manually edited. +# This file should be version controlled. version: - revision: 48c9d3e0e19e8fec84f1d316ce0559f26ca7277d - channel: beta + revision: 796c8ef79279f9c774545b3771238c3098dbefab + channel: stable project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 796c8ef79279f9c774545b3771238c3098dbefab + base_revision: 796c8ef79279f9c774545b3771238c3098dbefab + - platform: android + create_revision: 796c8ef79279f9c774545b3771238c3098dbefab + base_revision: 796c8ef79279f9c774545b3771238c3098dbefab + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/example/README.md b/example/README.md index 35fd043..eea7e0a 100644 --- a/example/README.md +++ b/example/README.md @@ -8,9 +8,9 @@ This project is a starting point for a Flutter application. A few resources to get you started if this is your first Flutter project: -- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) -For help getting started with Flutter, view our -[online documentation](https://flutter.dev/docs), which offers tutorials, +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, samples, guidance on mobile development, and a full API reference. diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/example/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/example/android/.gitignore b/example/android/.gitignore index 0a741cb..6f56801 100644 --- a/example/android/.gitignore +++ b/example/android/.gitignore @@ -9,3 +9,5 @@ GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app key.properties +**/*.keystore +**/*.jks diff --git a/example/android/.project b/example/android/.project deleted file mode 100644 index 2c241ce..0000000 --- a/example/android/.project +++ /dev/null @@ -1,28 +0,0 @@ - - - android - Project android created by Buildship. - - - - - org.eclipse.buildship.core.gradleprojectbuilder - - - - - - org.eclipse.buildship.core.gradleprojectnature - - - - 1613022023065 - - 30 - - org.eclipse.core.resources.regexFilterMatcher - node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ - - - - diff --git a/example/android/.settings/org.eclipse.buildship.core.prefs b/example/android/.settings/org.eclipse.buildship.core.prefs deleted file mode 100644 index 9bb9226..0000000 --- a/example/android/.settings/org.eclipse.buildship.core.prefs +++ /dev/null @@ -1,13 +0,0 @@ -arguments= -auto.sync=false -build.scans.enabled=false -connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) -connection.project.dir= -eclipse.preferences.version=1 -gradle.user.home= -java.home=/Library/Java/JavaVirtualMachines/jdk-11.0.12.jdk/Contents/Home -jvm.arguments= -offline.mode=false -override.workspace.settings=true -show.console.view=true -show.executions.view=true diff --git a/example/android/app/.classpath b/example/android/app/.classpath deleted file mode 100644 index 4a04201..0000000 --- a/example/android/app/.classpath +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/example/android/app/.project b/example/android/app/.project deleted file mode 100644 index 86579a0..0000000 --- a/example/android/app/.project +++ /dev/null @@ -1,34 +0,0 @@ - - - app - Project app created by Buildship. - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.buildship.core.gradleprojectbuilder - - - - - - org.eclipse.jdt.core.javanature - org.eclipse.buildship.core.gradleprojectnature - - - - 1613022023076 - - 30 - - org.eclipse.core.resources.regexFilterMatcher - node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ - - - - diff --git a/example/android/app/.settings/org.eclipse.buildship.core.prefs b/example/android/app/.settings/org.eclipse.buildship.core.prefs deleted file mode 100644 index b1886ad..0000000 --- a/example/android/app/.settings/org.eclipse.buildship.core.prefs +++ /dev/null @@ -1,2 +0,0 @@ -connection.project.dir=.. -eclipse.preferences.version=1 diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 0080986..28d0f61 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -1,3 +1,9 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" +} + def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { @@ -6,11 +12,6 @@ if (localPropertiesFile.exists()) { } } -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' @@ -21,22 +22,31 @@ if (flutterVersionName == null) { flutterVersionName = '1.0' } -apply plugin: 'com.android.application' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - android { - compileSdkVersion 30 + namespace "com.jacobbrasil.snapkit_example" + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.jacobbrasil.snapkit_example" - minSdkVersion 19 - targetSdkVersion 30 + // Snap Kit requires SDK version 19 or later, but `flutter.minSdkVersion` currently is 16. + minSdkVersion 33 + targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName - ndk { - abiFilters 'armeabi-v7a', 'arm64-v8a' - } } buildTypes { @@ -46,15 +56,6 @@ android { signingConfig signingConfigs.debug } } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - dependenciesInfo { - includeInBundle true - includeInApk true - } - buildToolsVersion '29.0.2' } flutter { @@ -62,10 +63,5 @@ flutter { } dependencies { - implementation([ - 'com.snapchat.kit.sdk:creative:1.10.0', - 'com.snapchat.kit.sdk:login:1.10.0', - 'com.snapchat.kit.sdk:bitmoji:1.10.0', - 'com.snapchat.kit.sdk:core:1.10.0' - ]) + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml index f562f73..399f698 100644 --- a/example/android/app/src/debug/AndroidManifest.xml +++ b/example/android/app/src/debug/AndroidManifest.xml @@ -1,6 +1,6 @@ - - diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 85fb440..e7620c5 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -1,51 +1,54 @@ - - + + + - - - - - + + + - - - - - - + + + + + + + + - - - - + - - - - + + + - - @@ -80,9 +74,4 @@ android:value="2" /> - - - - - diff --git a/example/android/app/src/main/java/com/jacobbrasil/snapkit_example/MainActivity.java b/example/android/app/src/main/java/com/jacobbrasil/snapkit_example/MainActivity.java deleted file mode 100644 index 588615a..0000000 --- a/example/android/app/src/main/java/com/jacobbrasil/snapkit_example/MainActivity.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.jacobbrasil.snapkit_example; - -import io.flutter.embedding.android.FlutterActivity; - -public class MainActivity extends FlutterActivity { -} diff --git a/example/android/app/src/main/kotlin/com/jacobbrasil/snapkit_example/MainActivity.kt b/example/android/app/src/main/kotlin/com/jacobbrasil/snapkit_example/MainActivity.kt new file mode 100644 index 0000000..e651ba9 --- /dev/null +++ b/example/android/app/src/main/kotlin/com/jacobbrasil/snapkit_example/MainActivity.kt @@ -0,0 +1,6 @@ +package com.jacobbrasil.snapkit_example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/example/android/app/src/main/res/values-night/styles.xml b/example/android/app/src/main/res/values-night/styles.xml index 449a9f9..06952be 100644 --- a/example/android/app/src/main/res/values-night/styles.xml +++ b/example/android/app/src/main/res/values-night/styles.xml @@ -3,14 +3,14 @@