diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..dd84ea7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..bbcbbe7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0360f7c..4b81514 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,26 +1,40 @@ name: build + +# This workflow is triggered on pushes to the repository. + on: - pull_request: - paths: - - "**" push: - branches: - - master - + +# on: push # Default will running for every branch. + jobs: - android: - runs-on: windows-latest - timeout-minutes: 30 + build: + # This job will run on ubuntu virtual machine + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: subosito/flutter-action@v1.3.0 - with: - flutter-version: '1.21.0-9.0.pre' - channel: 'dev' - - name: Install Dependencies - run: flutter packages get - - name: Build example - run: | - cd example - flutter build apk + + # Setup Java environment in order to build the Android app. + - uses: actions/checkout@v2 + - uses: actions/setup-java@v1 + with: + java-version: '12.x' + + # Setup the flutter environment. + - uses: subosito/flutter-action@v1 + with: + channel: 'stable' # 'dev', 'alpha', default to: 'stable' + # flutter-version: '1.12.x' # you can also specify exact version of flutter + - run : cd example + # Get flutter dependencies. + - run: flutter pub get + + # Build apk. + - run: flutter build apk + + # Upload generated apk to the artifacts. + - uses: actions/upload-artifact@v1 + with: + name: release-apk + path: build/app/outputs/apk/release/app-release.apk + diff --git a/.idea/libraries/Dart_SDK.xml b/.idea/libraries/Dart_SDK.xml index 312f576..449c2cb 100644 --- a/.idea/libraries/Dart_SDK.xml +++ b/.idea/libraries/Dart_SDK.xml @@ -1,17 +1,27 @@ - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/.idea/misc.xml b/.idea/misc.xml index ac5bd6f..7a8e207 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,6 @@ - + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 438939d..a18bdb7 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -1,34 +1,75 @@ + + - - - - - - - + + + + + + + + + + + + + { + "customColor": "", + "associatedIndex": 0 +} - - - - + + + + + + @@ -40,12 +81,19 @@ - - - - - + + + \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index c2c91f4..03078de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ +## 2.1.0 +* rethink entire plugin + +## 2.0.0 +* revert to hover standart +* change sendUssd() to startTransaction() +## 1.0.0a +* change from hover standard to hover noSms +* You can now customize the hover the by passing branding or drawable to the HoverUssd Contruction +* You can also provide the theme of hover ussd +* +## 0.0.2+2 +* update readme +## 0.0.2+1 +* improve code ## 0.0.2 * improve performance * update readme ## 0.0.1 -* Initial release +* Initial release \ No newline at end of file diff --git a/README.md b/README.md index 11c552f..4a21f72 100644 --- a/README.md +++ b/README.md @@ -6,81 +6,416 @@ [![Flutter Website](https://img.shields.io/badge/flutter-website-deepskyblue.svg)](https://flutter.dev/docs/development/data-and-backend/state-mgmt/options#bloc--rx) [![License: MIT](https://img.shields.io/badge/license-MIT-purple.svg)](https://opensource.org/licenses/MIT) -drawing + -A flutter plugin to make payments by usehover.com ussd gateway using Android Intent and receiving the transaction information back in response. +© image by Francis Mwakitumbula + + +A flutter plugin implemanting usehover.com ussd gateway sdk using Android Intent and receiving the transaction information back in response. **android only** ## Getting Started -* Adding The hover api key refert to documentation at [docs.usehover.com](https://docs.usehover.com/) +## Hover USSD Plugin + +The Hover USSD plugin provides a simple and easy-to-use interface for integrating Hover USSD services into your Flutter applications. It allows you to start USSD transactions, listen to transaction states, and retrieve available actions from the Hover API. + +## Usage + +1. **Installation** + + Add `hover_ussd` to your `pubspec.yaml` file: + + ```yaml + dependencies: + hover_ussd: ^latest_version + ``` + + Then, run: + + ```sh + flutter pub get + ``` + +2. **Initialization** + + Initialize HoverUssd with your Hover API key at [docs.usehover.com](https://docs.usehover.com/), branding, and logo information: + + ```dart + final HoverUssd _hoverUssd = HoverUssd(); + await _hoverUssd.initialize( + apiKey: "YOUR_API_KEY", + branding: "Your App Name", + logo: "ic_launcher", + notificationLogo: "ic_launcher", + ); + ``` + +3. **Start a Transaction** + + Start a USSD transaction with the provided parameters: + + ```dart + await _hoverUssd.startTransaction( + actionId: "ACTION_ID", + extras: {}, + theme: "myhoverTheme", //located in android/app/main/values/styles.xml + header: "Hover Ussd Example", + showUserStepDescriptions: true, + ); + ``` + +4. **Permissions Check** + + Check if the app has all the necessary permissions: + + ```dart + bool hasPermissions = await _hoverUssd.hasAllPermissions(); + ``` + +5. **Overlay and Accessibility Check** + + Check if the app has overlay and accessibility permissions: + + ```dart + bool isOverlayEnabled = await _hoverUssd.isOverlayEnabled(); + bool isAccessibilityEnabled = await _hoverUssd.isAccessibilityEnabled(); + ``` + +6. **Retrieve Actions** + + Retrieve a list of available actions: + + ```dart + List actions = await _hoverUssd.getAllActions(); + ``` + +7. **Refresh Actions** + + Refresh actions to get the latest updates: + + ```dart + await _hoverUssd.refreshActions(); + ``` + +8. **Listen to Transaction States** + + Get a stream of transaction states: + + ```dart + Stream transactionStream = _hoverUssd.getUssdTransactionState(); + ``` + +9. **Listen to Download Action States** + + Get a stream of download action states: + + ```dart + Stream downloadStream = _hoverUssd.getDownloadActionState(); + ``` + +## Customization + + +* ### To use your own theme style + +#### add your style to android/app/main/values/styles.xml ```xml - + + + + + + + + + + + + + + ``` -## Usage -* Initialize the plugin in main method + +* ### Customize an hover session ```dart -void main() { - WidgetsFlutterBinding.ensureInitialized(); - HoverUssd().initialize(); - runApp(MyApp()); -} + await _hoverUssd.startTransaction( + actionId: "ACTION_ID", + extras: {}, + theme: "myhoverTheme", // as in android/app/main/values/styles.xml + header: "Hover Ussd Example"// the title of your hover session, + showUserStepDescriptions: true, + ); ``` -* Start a transaction -```dart -import 'package:hover_ussd/hover_ussd.dart'; -... -final HoverUssd _hoverUssd = HoverUssd(); - -///Begin transaction -void send(){ - ///First param @String [action_id] - ///Second param @ Map [step_variables] - _hoverUssd.sendUssd("c6e45e62", {"price": "4000"}); + +* ### Add a custom permissions activity + +```dart + + final String activityName = "com.example.yourApp.custompermissionsActivity"; + _hoverUssd.setPermissionsActivity(activityName: activityName); + + +``` + +### Example + +```dart +class MyHomePage extends StatefulWidget { + const MyHomePage({super.key}); + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + late final HoverUssd _hoverUssd = HoverUssd(); + // Create an instance of HoverUssd you can pass it to a provider, + // or use get_it to make it available to the entire app + + late StreamSubscription _transactionListening; + late StreamSubscription _actionDownloadListening; + bool _hasPermissions = false; + bool _isOverlayEnabled = false; + bool _isAccessibilityEnabled = false; + + @override + void initState() { + _transactionListening = + _hoverUssd.getUssdTransactionState().listen((event) { + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar(content: Text(event.toMap().toString()))); + }); + + _actionDownloadListening = + _hoverUssd.getDownloadActionState().listen((event) { + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar(content: Text(event.toString()))); + }); + _checkAccessibility(); + _checkPermissions(); + _checkOverlay(); + + //initialize hover after the downlad listener is set + + _actionDownloadListening.onData((event) { + _initHover(); + }); + + super.initState(); + } + + @override + void dispose() { + _transactionListening.cancel(); + _actionDownloadListening.cancel(); + super.dispose(); + } + //check for permissions + + Future _checkPermissions() async { + _hasPermissions = await _hoverUssd.hasAllPermissions(); + setState(() {}); + } + + Future _checkAccessibility() async { + _isAccessibilityEnabled = await _hoverUssd.isAccessibilityEnabled(); + setState(() {}); + } + + Future _checkOverlay() async { + _isOverlayEnabled = await _hoverUssd.isOverlayEnabled(); + setState(() {}); + } + + Future _initHover() async { + try { + await _hoverUssd.initialize( + apiKey: "15ccc2bd81801d8c5fbfd5847d3b4e77", + branding: "My Hover App", + logo: "ic_launcher", + notificationLogo: "ic_launcher", + ); + } catch (e) { + print(e); + } + } + + Future _getAndGoToActions() async { + final actions = await _hoverUssd.getAllActions(); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => HoverActionListPage( + hoverActions: actions, + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Hover Ussd Example'), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: () { + _hoverUssd.startTransaction( + actionId: "c6e45e62", + extras: {"price": "4000"}, + theme: "HoverTheme", + header: "Hover Ussd Example", + showUserStepDescriptions: true, + ); + }, + child: const Text("Start Transaction"), + ), + ElevatedButton( + onPressed: _getAndGoToActions, + child: const Text("Get Actions"), + ), + + //refresh actions + ElevatedButton( + onPressed: () { + _hoverUssd.refreshActions(); + }, + child: const Text("Refresh Actions"), + ), + Text( + _hasPermissions + ? "Permissions Granted" + : "Permissions Not Granted", + style: TextStyle( + color: _hasPermissions ? Colors.green : Colors.red, + fontWeight: FontWeight.bold, + ), + ), + Text( + _isAccessibilityEnabled + ? "Accessibility Granted" + : "Accessibility Not Granted", + style: TextStyle( + color: _isAccessibilityEnabled ? Colors.green : Colors.red, + fontWeight: FontWeight.bold, + ), + ), + Text( + _isOverlayEnabled ? "Overlay Granted" : "Overlay Not Granted", + style: TextStyle( + color: _isOverlayEnabled ? Colors.green : Colors.red, + fontWeight: FontWeight.bold, + ), + ), + + StreamBuilder( + stream: _hoverUssd.getDownloadActionState(), + builder: (context, snapshot) { + final state = snapshot.data; + + String statusText; + Color textColor; + + if (state is ActionDownloaded) { + statusText = "Actions Downloaded"; + textColor = Colors.green; + } else if (state is ActionDownloadFailed) { + statusText = "Actions Not Downloaded"; + textColor = Colors.red; + } else if (state is ActionDownloading) { + statusText = "Actions Downloading"; + textColor = + Colors.blue; // Adjust color as per your preference + } else { + statusText = "Action Download Status: Unknown"; + textColor = Colors.grey; + } + + return Text( + statusText, + style: + TextStyle(color: textColor, fontWeight: FontWeight.bold), + ); + }, + ), + StreamBuilder( + stream: _hoverUssd.getUssdTransactionState(), + builder: (BuildContext context, snapshot) { + if (snapshot.data is SmsParsed) { + return Text("Sms parsed : \n${snapshot.data!.toMap()}"); + } + if (snapshot.data is UssdSucceeded) { + return Text("Ussd Succeded : \n${snapshot.data!.toMap()}"); + } + if (snapshot.data is UssdLoading) { + return const Text("loading..."); + } + if (snapshot.data is UssdFailed) { + return Text("Ussd Failed : \n${snapshot.data!.toMap()}"); + } + if (snapshot.data is EmptyState) { + return const Text("Empty State"); + } + return const Text("No state"); + }, + ), + ], + ), + ), + ); + } } -///Listen for transaction status - _hoverUssd.onTransactiontateChanged.listen((event) { - // Do something with new state - if (event == TransactionState.succesfull) { - print("succesfull"); - } else if (event == TransactionState.waiting) { - print("pending"); - } else if (event == TransactionState.failed) { - print('failed'); - } - }); -///You can listen with StreamBuilder to update ui - StreamBuilder( - stream: _hoverUssd.onTransactiontateChanged, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.data == TransactionState.succesfull) { - return Text("succesfull"); - } else if (snapshot.data == TransactionState.waiting) { - return Text("pending"); - } else if (snapshot.data == TransactionState.failed) { - return Text("failed"); - } - return Text("no transaction"); - }, -); ``` -## Features - - [x] start a transaction - - [x] listen for result - - [ ] customization - - [ ] translation - + + ## Important - - * **support only basic feature** - * **always in developpement** - * **this isn't a officialy plugin** + * **This is a unofficial plugin** +## Credit +* Thanks to the authors of useHover android sdk, this work based of it ## Maintainers -- [Lucdotdev](https://twitter.com/lucdotdev) - +- [lucdotdev](mailto:lucdotdev@gmail.com) \ No newline at end of file diff --git a/android/.gitignore b/android/.gitignore index c6cbe56..b84dbef 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -6,3 +6,6 @@ .DS_Store /build /captures +.cxx + +/tempsLibs diff --git a/android/.idea/.gitignore b/android/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/android/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/android/.idea/.name b/android/.idea/.name new file mode 100644 index 0000000..fd21e78 --- /dev/null +++ b/android/.idea/.name @@ -0,0 +1 @@ +hover_ussd \ No newline at end of file diff --git a/android/.idea/codeStyles/Project.xml b/android/.idea/codeStyles/Project.xml deleted file mode 100644 index 681f41a..0000000 --- a/android/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - - -
- - - - xmlns:android - - ^$ - - - -
-
- - - - xmlns:.* - - ^$ - - - BY_NAME - -
-
- - - - .*:id - - http://schemas.android.com/apk/res/android - - - -
-
- - - - .*:name - - http://schemas.android.com/apk/res/android - - - -
-
- - - - name - - ^$ - - - -
-
- - - - style - - ^$ - - - -
-
- - - - .* - - ^$ - - - BY_NAME - -
-
- - - - .* - - http://schemas.android.com/apk/res/android - - - ANDROID_ATTRIBUTE_ORDER - -
-
- - - - .* - - .* - - - BY_NAME - -
-
-
-
-
-
\ No newline at end of file diff --git a/android/.idea/compiler.xml b/android/.idea/compiler.xml new file mode 100644 index 0000000..b589d56 --- /dev/null +++ b/android/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/android/.idea/gradle.xml b/android/.idea/gradle.xml index 3e3960b..aeac74f 100644 --- a/android/.idea/gradle.xml +++ b/android/.idea/gradle.xml @@ -1,4 +1,18 @@ + + + \ No newline at end of file diff --git a/android/.idea/migrations.xml b/android/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/android/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/android/.idea/misc.xml b/android/.idea/misc.xml new file mode 100644 index 0000000..8978d23 --- /dev/null +++ b/android/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/android/.idea/vcs.xml b/android/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/android/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index e7a8d2f..4ce7165 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,47 +1,50 @@ group 'com.lucdotdev.hover_ussd' -version '1.0' +version '2.1.0' buildscript { repositories { google() - jcenter() - maven { url "http://maven.usehover.com/releases" } + mavenCentral() + maven { url "https://maven.usehover.com/snapshots" } + } dependencies { - classpath 'com.android.tools.build:gradle:4.0.1' + classpath 'com.android.tools.build:gradle:7.3.0' } - } - - rootProject.allprojects { - gradle.projectsEvaluated { - tasks.withType(JavaCompile) { - options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" - } - } repositories { google() - jcenter() - maven { url "http://maven.usehover.com/releases" } + mavenCentral() + maven { url "https://maven.usehover.com/snapshots" } + } } apply plugin: 'com.android.library' android { - compileSdkVersion 29 + if (project.android.hasProperty("namespace")) { + namespace 'com.lucdotdev.hover_ussd' + } - defaultConfig { - minSdkVersion 18 + compileSdkVersion 33 + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 } - lintOptions { - disable 'InvalidPackage' + + defaultConfig { + minSdkVersion 19 } - dependencies { - implementation 'com.hover:android-sdk:1.6.3' + dependencies { + testImplementation 'junit:junit:4.13.2' + testImplementation 'org.mockito:mockito-core:5.0.0' + implementation 'com.hover:android-sdk:2.0.0-beta04-noSMS' } + } diff --git a/android/gradle.properties b/android/gradle.properties deleted file mode 100644 index 38c8d45..0000000 --- a/android/gradle.properties +++ /dev/null @@ -1,4 +0,0 @@ -org.gradle.jvmargs=-Xmx1536M -android.enableR8=true -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..ccebba7 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 01a286e..42defcc 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip diff --git a/android/gradlew b/android/gradlew new file mode 100644 index 0000000..79a61d4 --- /dev/null +++ b/android/gradlew @@ -0,0 +1,244 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +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" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat new file mode 100644 index 0000000..93e3f59 --- /dev/null +++ b/android/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@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 + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@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="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +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 execute + +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 + +: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 %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 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! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index 594fba6..8bd0db9 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1,16 +1,4 @@ + package="com.lucdotdev.hover_ussd"> - - - - - - - diff --git a/android/src/main/java/com/lucdotdev/hover_ussd/HoverUssdApi.java b/android/src/main/java/com/lucdotdev/hover_ussd/HoverUssdApi.java index 634259e..e7bb5cd 100644 --- a/android/src/main/java/com/lucdotdev/hover_ussd/HoverUssdApi.java +++ b/android/src/main/java/com/lucdotdev/hover_ussd/HoverUssdApi.java @@ -1,55 +1,160 @@ package com.lucdotdev.hover_ussd; +import android.Manifest; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; +import android.os.Build; +import androidx.core.content.ContextCompat; -import androidx.localbroadcastmanager.content.LocalBroadcastManager; - +import com.hover.sdk.actions.HoverAction; +import com.hover.sdk.api.Hover; import com.hover.sdk.api.HoverParameters; +import com.hover.sdk.database.HoverRoomDatabase; +import com.hover.sdk.transactions.Transaction; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; -import io.flutter.Log; - public class HoverUssdApi { + private final Activity activity; + private final Context context; - private Activity activity; - - public HoverUssdApi(Activity activity) { + public HoverUssdApi(Activity activity, Context context) { this.activity = activity; + this.context = context; } - ///This Start Listennig SMS before the begin of Transaction - /// - /// + public void initialize(String apiKey, String branding, String logo, String notificationLogo, Hover.DownloadListener downloadListener) { + Hover.initialize(context, apiKey, false, downloadListener); + int logoResourceId = getResourceId(logo == null ? "ic_launcher" : logo); + int notificationLogoResourceId = getResourceId(notificationLogo == null ? "ic_launcher" : notificationLogo); + Hover.setBranding(branding == null ? "Hover Ussd Plugin" : branding, logoResourceId, notificationLogoResourceId, context); + } + public boolean hasAllPerms() { + return Hover.hasAllPerms(context); + } + public boolean hasAccessibilityPermission() { + return Hover.isAccessibilityEnabled(context); + } - public void sendUssd(String action_id, HashMap extra, HoverUssdSmsReceiver smsReceiver) { + public boolean hasOverlayPermission() { + return Hover.isOverlayEnabled(context); + } - LocalBroadcastManager.getInstance(activity).registerReceiver(smsReceiver, new IntentFilter("com.lucdotdev.hover_ussd.SMS_MISS")); + public boolean hasContactPermission() { + return Build.VERSION.SDK_INT < 23 || hasPermission(new String[]{Manifest.permission.READ_CONTACTS}, context); + } - ///Initialize @HoverBuilder - final HoverParameters.Builder builder = new HoverParameters.Builder(activity).request(action_id); + public boolean hasWritePermission() { + return Build.VERSION.SDK_INT < 23 || hasPermission(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, context); + } + + public boolean hasSmsPermission() { + return Build.VERSION.SDK_INT < 23 || hasPermission(new String[]{Manifest.permission.RECEIVE_SMS, Manifest.permission.READ_SMS}, context); + } - ///If there are action with variables - /// + public boolean hasPhonePermission() { + return Build.VERSION.SDK_INT < 23 || hasPermission(new String[]{Manifest.permission.READ_PHONE_STATE}, context); + } + + private static boolean hasPermission(String[] permissions, Context context) { + if (context == null) return false; + for (String permission : permissions) { + if (ContextCompat.checkSelfPermission(context, permission) != android.content.pm.PackageManager.PERMISSION_GRANTED) { + return false; + } + } + return true; + } + + public ArrayList> getAllActions() { + List actions = HoverRoomDatabase.getInstance(context).actionDao().getAll(); + ArrayList> mapActions = new ArrayList<>(); + + for (HoverAction action : actions) { + Map mapAction = HoverUssdObjectToMap.convertHoverActionToMap(action); + mapActions.add(mapAction); + } + + return mapActions; + } + + public void refreshActions(Hover.DownloadListener actionDownloadListener) { + Hover.updateActionConfigs(actionDownloadListener, context); + } + + public ArrayList> getAllTransaction() { + List transactions = HoverRoomDatabase.getInstance(context).transactionDao().getAll(); + ArrayList> mapTransactions = new ArrayList<>(); + + for (Transaction transaction : transactions) { + Map mapTransaction = HoverUssdObjectToMap.convertTransactionToMap(transaction); + mapTransactions.add(mapTransaction); + } + + return mapTransactions; + } + + private int getResourceId(String resourceName) { + try { + return context.getResources().getIdentifier(resourceName, "mipmap", activity.getPackageName()); + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } + public void setPermissionsActivity(String activityString){ + Hover.setPermissionActivity(activityString, context); + } + + public void sendUssd(String action_id, + HashMap extra, + String theme, + String header, + String initialProcessingMessage, + boolean showUserStepDescriptions, + int finalMsgDisplayTime, + BroadcastReceiver transactionStateReceiver) { + + final HoverParameters.Builder builder = new HoverParameters.Builder(context); + + builder.request(action_id); + + if (extra != null) { if (!extra.isEmpty()) { for (Map.Entry entry : extra.entrySet()) { builder.extra(entry.getKey(), entry.getValue()); } } + } - Intent buildIntent = builder.buildIntent(); - activity.startActivityForResult(buildIntent, 0); + if (theme != null) { + int id = context.getResources().getIdentifier(theme, "style", context.getPackageName()); + builder.style(id); + } + if (header != null) { + builder.setHeader(header); + } + if (initialProcessingMessage != null) { + builder.initialProcessingMessage(initialProcessingMessage); } + builder.showUserStepDescriptions(showUserStepDescriptions); + + if (finalMsgDisplayTime != 0) { + builder.finalMsgDisplayTime(finalMsgDisplayTime); + } + Intent buildIntent = builder.buildIntent(); + activity.startActivityForResult(buildIntent, 0); } +} diff --git a/android/src/main/java/com/lucdotdev/hover_ussd/HoverUssdObjectToMap.java b/android/src/main/java/com/lucdotdev/hover_ussd/HoverUssdObjectToMap.java new file mode 100644 index 0000000..0c29f48 --- /dev/null +++ b/android/src/main/java/com/lucdotdev/hover_ussd/HoverUssdObjectToMap.java @@ -0,0 +1,70 @@ +package com.lucdotdev.hover_ussd; + +import com.hover.sdk.actions.HoverAction; +import com.hover.sdk.transactions.Transaction; + +import java.util.HashMap; +import java.util.Map; + +public class HoverUssdObjectToMap { + + public static Map convertHoverActionToMap(HoverAction hoverAction) { + Map map = new HashMap<>(); + map.put("id", hoverAction.id); + map.put("public_id", hoverAction.public_id); + map.put("name", hoverAction.name); + map.put("channel_id", hoverAction.channel_id); + map.put("network_name", hoverAction.network_name); + map.put("country_alpha2", hoverAction.country_alpha2); + map.put("root_code", hoverAction.root_code); + map.put("transport_type", hoverAction.transport_type); + map.put("transaction_type", hoverAction.transaction_type); + map.put("from_institution_id", hoverAction.from_institution_id); + map.put("from_institution_name", hoverAction.from_institution_name); + map.put("from_institution_logo", hoverAction.from_institution_logo); + map.put("to_institution_id", hoverAction.to_institution_id); + map.put("to_institution_name", hoverAction.to_institution_name); + map.put("to_institution_logo", hoverAction.to_institution_logo); + map.put("to_country_alpha2", hoverAction.to_country_alpha2); + map.put("created_timestamp", hoverAction.created_timestamp); + map.put("updated_timestamp", hoverAction.updated_timestamp); + map.put("bounty_amount", hoverAction.bounty_amount); + map.put("bounty_is_open", hoverAction.bounty_is_open); + map.put("is_ready", hoverAction.is_ready); + map.put("bonus_percent", hoverAction.bonus_percent); + map.put("bonus_message", hoverAction.bonus_message); + + // Ignoring 'parsers' field as it's not included in the map + + return map; + } + + public static Map convertTransactionToMap(Transaction transaction) { + Map map = new HashMap<>(); + + map.put("id", transaction.id); + map.put("channelId", transaction.channelId); + map.put("statusId", transaction.statusId); + map.put("env", transaction.env); + map.put("actionId", transaction.actionId); + map.put("actionName", transaction.actionName); + map.put("uuid", transaction.uuid); + map.put("status", transaction.status); + map.put("category", transaction.category); + map.put("userMessage", transaction.userMessage); + map.put("networkHni", transaction.networkHni); + map.put("myType", transaction.myType); + map.put("toInstitutionName", transaction.toInstitutionName); + map.put("fromInstitutionName", transaction.fromInstitutionName); + map.put("configVersion", transaction.configVersion); + map.put("sdkVersion", transaction.sdkVersion); + map.put("reqTimestamp", transaction.reqTimestamp); + map.put("updatedTimestamp", transaction.updatedTimestamp); + + + + return map; + } + +} + diff --git a/android/src/main/java/com/lucdotdev/hover_ussd/HoverUssdPlugin.java b/android/src/main/java/com/lucdotdev/hover_ussd/HoverUssdPlugin.java index 03287b7..372fdd5 100644 --- a/android/src/main/java/com/lucdotdev/hover_ussd/HoverUssdPlugin.java +++ b/android/src/main/java/com/lucdotdev/hover_ussd/HoverUssdPlugin.java @@ -1,136 +1,300 @@ package com.lucdotdev.hover_ussd; + import android.app.Activity; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.widget.ImageView; -import android.widget.Toast; - import androidx.annotation.NonNull; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import com.hover.sdk.actions.HoverAction; import com.hover.sdk.api.Hover; +import java.util.ArrayList; import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import io.flutter.Log; 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.EventChannel; 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 io.flutter.embedding.engine.plugins.activity.ActivityAware; import io.flutter.plugin.common.PluginRegistry; -/** HoverUssdPlugin */ -public class HoverUssdPlugin implements FlutterPlugin, MethodCallHandler, ActivityAware ,PluginRegistry.ActivityResultListener, EventChannel.StreamHandler, HoverUssdSmsReceiver.HoverUssdReceiverInterface { - - - private MethodChannel channel; - private Activity activity; - - - private HoverUssdApi hoverUssdApi; - private EventChannel eventChannel; - private EventChannel.EventSink eventSink; - private HoverUssdSmsReceiver hoverUssdSmsReceiver; - - - - - - @Override - public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { - - channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "hover_ussd"); - eventChannel = new EventChannel(flutterPluginBinding.getBinaryMessenger(), "transaction_event"); - eventChannel.setStreamHandler(this); - channel.setMethodCallHandler(this); - hoverUssdSmsReceiver = new HoverUssdSmsReceiver(this); - - } - - @Override - public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { - if (call.method.equals("hoverStartTransaction")) { - - hoverUssdApi = new HoverUssdApi(activity); - hoverUssdApi.sendUssd((String) call.argument("action_id"), (HashMap) call.argument("extras"), hoverUssdSmsReceiver); - - - } else if(call.method.equals("hoverInitial")) { - Hover.initialize(activity.getApplicationContext()); - } else { - result.notImplemented(); +/** + * HoverUssdPlugin + */ + + +public class HoverUssdPlugin implements FlutterPlugin, ActivityAware, MethodChannel.MethodCallHandler, PluginRegistry.ActivityResultListener { + + private Activity activity; + private EventChannel.EventSink transactionEventSink; + private EventChannel.EventSink actionDownloadEventSink; + private BroadcastReceiver smsReceiver; + private BroadcastReceiver transactionStateReceiver; + private HoverUssdApi hoverUssdApi; + private Context context; + private static final String TAG = "HOVER_USSD_PLUGIN"; + + @Override + public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { + MethodChannel channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "HoverUssdChannel"); + EventChannel transactionEventChannel = new EventChannel(flutterPluginBinding.getBinaryMessenger(), "TransactionEvent"); + EventChannel actionDownloadEventChannel = new EventChannel(flutterPluginBinding.getBinaryMessenger(), "ActionDownloadEvent"); + + context = flutterPluginBinding.getApplicationContext(); + + actionDownloadEventChannel.setStreamHandler(new EventChannel.StreamHandler() { + @Override + public void onListen(Object arguments, EventChannel.EventSink events) { + actionDownloadEventSink = events; + } + + @Override + public void onCancel(Object arguments) { + } + }); + + transactionEventChannel.setStreamHandler(new EventChannel.StreamHandler() { + @Override + public void onListen(Object arguments, EventChannel.EventSink events) { + transactionEventSink = events; + } + + @Override + public void onCancel(Object arguments) { + } + }); + + channel.setMethodCallHandler(this); } - } - @Override - public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { - channel.setMethodCallHandler(null); - eventChannel.setStreamHandler(null); - } - - @Override - public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { - activity = binding.getActivity(); - binding.addActivityResultListener(this); - - } - - @Override - public void onDetachedFromActivityForConfigChanges() { - activity = null; - } - - @Override - public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { - activity = binding.getActivity(); - } - - @Override - public void onDetachedFromActivity() { - activity = null; - ///this help us to destroy the smsReceiver - // hoverUssdApi.destroySmsReceiver(); + @Override + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { + } - } + @Override + public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { + activity = binding.getActivity(); + binding.addActivityResultListener(this); + + smsReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + handleSmsReceived(intent); + } + }; + LocalBroadcastManager.getInstance(activity).registerReceiver(smsReceiver, new IntentFilter(activity.getPackageName() + ".SMS_MISS")); + } + @Override + public void onDetachedFromActivityForConfigChanges() { + activity = null; + } - @Override - public boolean onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == 0 && resultCode == Activity.RESULT_OK) { + @Override + public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { + activity = binding.getActivity(); + binding.addActivityResultListener(this); + } - Toast.makeText(activity, "Please wait for confirmation", Toast.LENGTH_LONG).show(); - eventSink.success("pending"); + @Override + public void onDetachedFromActivity() { + LocalBroadcastManager.getInstance(activity).unregisterReceiver(smsReceiver); + if (transactionStateReceiver != null) { + LocalBroadcastManager.getInstance(activity.getApplication()).unregisterReceiver(transactionStateReceiver); + } + activity = null; + } - return true; + @Override + public boolean onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == 0) { + if (resultCode == Activity.RESULT_OK && data != null) { + handleUssdSuccess(data); + } else if (resultCode == Activity.RESULT_CANCELED && data != null) { + handleUssdFailed(data); + } + return true; + } + return false; + } - } else if (requestCode == 0 && resultCode == Activity.RESULT_CANCELED) { + @Override + public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + hoverUssdApi = new HoverUssdApi(activity, context); + switch (call.method) { + case "Initialize": + initializeHover(call); + break; + case "HasAllPermissions": + result.success(hoverUssdApi.hasAllPerms()); + break; + case "SetPermissionsActivity": + hoverUssdApi.setPermissionsActivity((String) call.argument("activityName")); + result.success(true); + break; + case "HasAccessibilityPermission": + result.success(hoverUssdApi.hasAccessibilityPermission()); + break; + + case "HasSmsPermission": + result.success(hoverUssdApi.hasSmsPermission()); + break; + + case "HasPhonePermission": + result.success(hoverUssdApi.hasPhonePermission()); + break; + case "HasContactsPermission": + result.success(hoverUssdApi.hasContactPermission()); + break; + + case "HasWritePermission": + result.success(hoverUssdApi.hasWritePermission()); + break; + + case "HasOverlayPermission": + result.success(hoverUssdApi.hasOverlayPermission()); + break; + case "getAllActions": + result.success(hoverUssdApi.getAllActions()); + break; + case "getAllTransactions": + result.success(hoverUssdApi.getAllTransaction()); + break; + case "refreshActions": + refreshActions(); + break; + case "HoverStartATransaction": + startHoverTransaction(call, result); + break; + default: + result.notImplemented(); + } + hoverUssdApi = null; + } - Toast.makeText(activity, "Error: " + data.getStringExtra("error"), Toast.LENGTH_LONG).show(); + private void initializeHover(MethodCall call) { + hoverUssdApi.initialize(call.argument("apiKey"), call.argument("branding"), call.argument("logo"), call.argument("notificationLogo"), new Hover.DownloadListener() { + @Override + public void onError(String s) { + Map result = new HashMap<>(); + result.put("state", "actionDownloadFailed"); + result.put("error", s); + actionDownloadEventSink.success(result); + } + + @Override + public void onSuccess(ArrayList arrayList) { + Map result = new HashMap<>(); + result.put("state", "actionDownloaded"); + result.put("isDownloaded", true); + actionDownloadEventSink.success(result); + } + }); + } - eventSink.success("failed"); + private void refreshActions() { + hoverUssdApi.refreshActions(new Hover.DownloadListener() { + @Override + public void onError(String s) { + Map result = new HashMap<>(); + result.put("state", "actionDownloadFailed"); + result.put("error", s); + actionDownloadEventSink.success(result); + } + + @Override + public void onSuccess(ArrayList arrayList) { + Map result = new HashMap<>(); + result.put("state", "actionDownloaded"); + result.put("isDownloaded", true); + actionDownloadEventSink.success(result); + } + }); + } - return true; + private void startHoverTransaction(MethodCall call, MethodChannel.Result result) { + Map resultJson = new HashMap<>(); + resultJson.put("state", "ussdLoading"); + transactionEventSink.success(resultJson); + + transactionStateReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent i) { + transactionEventSink.success(i.getExtras()); + Log.i(TAG, "Received pending transaction created broadcast"); + Log.i(TAG, "uuid: " + i.getStringExtra("uuid")); + } + }; + + try { + hoverUssdApi.sendUssd( + Objects.requireNonNull(call.argument("actionId")), + call.argument("extras") != null ? call.argument("extras") : new HashMap(), + call.argument("theme"), + call.argument("header"), + call.argument("initialProcessingMessage"), + false, + call.argument("finalMsgDisplayTime"), + transactionStateReceiver + ); + result.success(true); + } catch (Exception e) { + Map resultError = new HashMap<>(); + resultError.put("state", "ussdFailed"); + resultError.put("errorMessage", e.getMessage()); + transactionEventSink.success(resultError); + } } - return false; - } - @Override - public void onListen(Object arguments, EventChannel.EventSink events) { - eventSink = events; - } + private void handleSmsReceived(Intent intent) { + Map result = new HashMap<>(); + result.put("state", "smsParsed"); + result.put("action_id", intentNullAwareString(intent, "action_id")); + result.put("response_message", intentNullAwareString(intent, "response_message")); + result.put("status", intentNullAwareString(intent, "status")); + result.put("status_meaning", intentNullAwareString(intent, "status_meaning")); + result.put("status_description", intentNullAwareString(intent, "status_description")); + result.put("uuid", intentNullAwareString(intent, "uuid")); + result.put("im_hni", intentNullAwareString(intent, "im_hni")); + result.put("environment", intent.getIntExtra("environment", 0)); + result.put("request_timestamp", intent.getIntExtra("request_timestamp", 0)); + result.put("response_timestamp", intent.getIntExtra("response_timestamp", 0)); + result.put("matched_parser_id", intentNullAwareString(intent, "matched_parser_id")); + result.put("messagetype", intentNullAwareString(intent, "messagetype")); + result.put("message_sender", intentNullAwareString(intent, "message_sender")); + result.put("regex", intentNullAwareString(intent, "regex")); + transactionEventSink.success(result); + } - @Override - public void onCancel(Object arguments) { + private void handleUssdSuccess(Intent data) { + String uuid = data.hasExtra("uuid") ? data.getStringExtra("uuid") : ""; + Map result = new HashMap<>(); + result.put("state", "ussdSucceeded"); + result.put("uuid", uuid); + if (data.hasExtra("session_messages")) { + String[] sessionMessages = data.getStringArrayExtra("session_messages"); + result.put("ussdSessionMessages", sessionMessages); + } + transactionEventSink.success(result); + } - } + private void handleUssdFailed(Intent data) { + Map result = new HashMap<>(); + result.put("state", "ussdFailed"); + result.put("errorMessage", data.getStringExtra("error")); + transactionEventSink.success(result); + } - @Override - public void onRecevedData(String msg) { - eventSink.success(msg); - } -} + private String intentNullAwareString(Intent intent, String name) { + return intent.hasExtra(name) ? intent.getStringExtra(name) : ""; + } +} \ No newline at end of file diff --git a/android/src/main/java/com/lucdotdev/hover_ussd/HoverUssdSmsReceiver.java b/android/src/main/java/com/lucdotdev/hover_ussd/HoverUssdSmsReceiver.java deleted file mode 100644 index 4aa6aa7..0000000 --- a/android/src/main/java/com/lucdotdev/hover_ussd/HoverUssdSmsReceiver.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.lucdotdev.hover_ussd; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.util.Log; - - -import com.hover.sdk.transactions.Transaction; - -import io.flutter.plugin.common.EventChannel; - -public final class HoverUssdSmsReceiver extends BroadcastReceiver{ - private HoverUssdReceiverInterface hoverUssdReceiverInterface; - - public HoverUssdSmsReceiver(HoverUssdReceiverInterface hoverUssdReceiverInterface){ - this.hoverUssdReceiverInterface = hoverUssdReceiverInterface; - } - @Override - public void onReceive(Context context, Intent intent) { - String t = Transaction.SUCCEEDED; - hoverUssdReceiverInterface.onRecevedData(t); - } - - public interface HoverUssdReceiverInterface{ - void onRecevedData(String msg) ; - } -} diff --git a/android/src/test/java/com/lucdotdev/hover_ussd/HoverUssdPluginTest.java b/android/src/test/java/com/lucdotdev/hover_ussd/HoverUssdPluginTest.java new file mode 100644 index 0000000..a5b0c88 --- /dev/null +++ b/android/src/test/java/com/lucdotdev/hover_ussd/HoverUssdPluginTest.java @@ -0,0 +1,18 @@ +package android.src.test.java.com.lucdotdev.hover_ussd; + +import org.junit.Test; + +/** + * This demonstrates a simple unit test of the Java 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. + */ + +public class HoverUssdPluginTest { + @Test + public void onMethodCall_getPlatformVersion_returnsExpectedValue() { + + } +} diff --git a/example/.gitignore b/example/.gitignore index 9d532b1..24476c5 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -8,6 +8,7 @@ .buildlog/ .history .svn/ +migrate_working_dir/ # IntelliJ related *.iml @@ -31,11 +32,13 @@ .pub/ /build/ -# Web related -lib/generated_plugin_registrant.dart - # Symbolication related app.*.symbols # Obfuscation related app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/example/.metadata b/example/.metadata index d06ed58..b48f68e 100644 --- a/example/.metadata +++ b/example/.metadata @@ -4,7 +4,42 @@ # This file should be version controlled and should not be manually edited. version: - revision: bf9f3a3dcfea3022f9cf2dfc3ab10b120b48b19d - channel: master + revision: "2f708eb8396e362e280fac22cf171c2cb467343c" + channel: "stable" project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 2f708eb8396e362e280fac22cf171c2cb467343c + base_revision: 2f708eb8396e362e280fac22cf171c2cb467343c + - platform: android + create_revision: 2f708eb8396e362e280fac22cf171c2cb467343c + base_revision: 2f708eb8396e362e280fac22cf171c2cb467343c + - platform: ios + create_revision: 2f708eb8396e362e280fac22cf171c2cb467343c + base_revision: 2f708eb8396e362e280fac22cf171c2cb467343c + - platform: linux + create_revision: 2f708eb8396e362e280fac22cf171c2cb467343c + base_revision: 2f708eb8396e362e280fac22cf171c2cb467343c + - platform: macos + create_revision: 2f708eb8396e362e280fac22cf171c2cb467343c + base_revision: 2f708eb8396e362e280fac22cf171c2cb467343c + - platform: web + create_revision: 2f708eb8396e362e280fac22cf171c2cb467343c + base_revision: 2f708eb8396e362e280fac22cf171c2cb467343c + - platform: windows + create_revision: 2f708eb8396e362e280fac22cf171c2cb467343c + base_revision: 2f708eb8396e362e280fac22cf171c2cb467343c + + # 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 b2749d4..ba937ff 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/app/build.gradle b/example/android/app/build.gradle index 6fadaac..3e8bb68 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,21 +22,31 @@ if (flutterVersionName == null) { flutterVersionName = '1.0' } -apply plugin: 'com.android.application' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - android { - compileSdkVersion 29 + namespace "com.example.example" + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } - lintOptions { - disable 'InvalidPackage' + 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.lucdotdev.hover_ussd_example" - minSdkVersion 18 - targetSdkVersion 29 + applicationId "com.example.example" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName } @@ -47,8 +58,13 @@ android { signingConfig signingConfigs.debug } } + } flutter { source '../..' } + +dependencies { + +} diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml index 97e0455..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 3ace8bc..1c44ba2 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -1,17 +1,12 @@ - - - + + - - @@ -44,8 +30,5 @@ - diff --git a/example/android/app/src/main/java/com/lucdotdev/hover_ussd_example/MainActivity.java b/example/android/app/src/main/java/com/lucdotdev/hover_ussd_example/MainActivity.java deleted file mode 100644 index 6692794..0000000 --- a/example/android/app/src/main/java/com/lucdotdev/hover_ussd_example/MainActivity.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.lucdotdev.hover_ussd_example; - -import io.flutter.embedding.android.FlutterActivity; - -public class MainActivity extends FlutterActivity { -} diff --git a/example/android/app/src/main/java/io/flutter/app/FlutterMultiDexApplication.java b/example/android/app/src/main/java/io/flutter/app/FlutterMultiDexApplication.java new file mode 100644 index 0000000..752fc18 --- /dev/null +++ b/example/android/app/src/main/java/io/flutter/app/FlutterMultiDexApplication.java @@ -0,0 +1,25 @@ +// Generated file. +// +// If you wish to remove Flutter's multidex support, delete this entire file. +// +// Modifications to this file should be done in a copy under a different name +// as this file may be regenerated. + +package io.flutter.app; + +import android.app.Application; +import android.content.Context; +import androidx.annotation.CallSuper; +import androidx.multidex.MultiDex; + +/** + * Extension of {@link android.app.Application}, adding multidex support. + */ +public class FlutterMultiDexApplication extends Application { + @Override + @CallSuper + protected void attachBaseContext(Context base) { + super.attachBaseContext(base); + MultiDex.install(this); + } +} diff --git a/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt new file mode 100644 index 0000000..e793a00 --- /dev/null +++ b/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/example/android/app/src/main/res/drawable-v21/launch_background.xml b/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example/android/app/src/main/res/values-night/styles.xml b/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/example/android/app/src/main/res/values/styles.xml b/example/android/app/src/main/res/values/styles.xml index 1f83a33..d1001e6 100644 --- a/example/android/app/src/main/res/values/styles.xml +++ b/example/android/app/src/main/res/values/styles.xml @@ -1,18 +1,386 @@ - - - - + + + + + #FF4C00 + #F1F1F4 + #292E35 + #1E232A + #8AFF4C00 + #CC3E02 + #FFFFFF + #F1F1F4 + #54575D + #FFCA00 + #374B5F + #28aae1 + #5f8cb4 + #EB7D23 + #CD2328 + #F5C346 + #F0F0F0 + #0A9A8D + #4285F4 + #263238 + #202020 + #737373 + #0A9A8D + #000000 + #5f8cb4 + #D0021B + #FF999999 + #11122C + #4A4A4A + #80000000 + #80FFFFFF + #00FFFFFF + #C8616161 + #DEDEDE + #808080 + #BDBDBD + #FFFFFF + #34383F + #23FFFFFF + 13dp + 5dp + 24dp + 128dp + 55dp + 21dp + 150dp + 8dp + 16dp + 16sp + 4dp + 32dp + 19sp + This is used to read and interact with USSD menus to provide a simplified and more accessible experience. + + Connect with Mobile Money and Telecom services on your + behalf + buy airtime + check balance + Fetch accounts + https://usehover.com + Open Settings + To prevent Accessibility from automatically turning off, go + to Settings and disable battery optimization for this app. + + Find and Tap %1$s then turn it ON + Turn on Accessibility in the next screen to run USSD sessions]]> + Step 1 + Touch this button + Step 2 + Switch ON + Find \"%1$s\" in the list and turn it ON + Accessibility + ERROR CODE 5000: Accessibility settings could not be found + Services + WARNING: Accessibility got timeout. Shutting down + e3b51457136ad623213ade89a42a288f + Accessibility + All + Removed screen blocker + Pressed cancel + Matched custom stop event, ending at step %1$s + Showing confirm transaction details + Initializing USSD services + Denied + Exception + Execution failed. Action: %1$s, AccessNode: %2$s + Transacting failed + Failure + False Start + Finished + Finished due to SMS match + Granted + Intializing + Interrupt + Keystore + Failed to generate keystore key for PIN + Reacting to Dual SIM + Matched + Mocking + Showing go online to check Hover API key + Showing enter a correct SIM + Parser + Permission + Could not decrypt PIN + Could not encrypt PIN + Pressed submit PIN + Could not find PIN + Showing PIN entry screen + Could not generate PrivateKeyEntry after 4 attempts + Reacting + Requesting Accessiblity + Requesting Phone and SMS + Requesting Overlay + Hover Session + Added screen blocker + Step not found, skipping + Starting + STK not found + Transacting succeeded + App Name + Back + back icon + backspace + https://www.usehover.com/sdk/ + We need this permission to read your SIM card and SMS responses to USSD requests + battery icon + Cancel + channel_actions + Choose SIM + Close + Transaction complete + Details fetched + Confirm + Please turn on mobile data or Wi-Fi to complete this + request. + Continue + Debug environment active. You will see USSD menus flash on screen. See Android Studio logcat for messages. + Hover Menu Service + Communicating with USSD service… + 8 + Enable Accessibility + %1$s + Enter PIN + invalid-mmi-code + ERROR CODE 7001: Unknown error, please try again. It may an issue with your device, network, or SIM card(s). If the problem persists please contact Hover. + Please check your PIN and try again. + 5 + 4 + hand icon + Internet Required + ERROR CODE 2001: There was a problem processing the actions\' configuration, please contact Hover for help. + ERROR CODE 1004: Network problem while attempting actions download. Trying again. + Successfully saved %1$d action(s) + Successfully imported %1$d action(s) + ERROR CODE 1005: This version of Android is not supported + ERROR CODE 2003: Internet problem, trying again. + API key is valid + Confirmed request. + WARNING: Failed to load cursor + Downloading actions + ERROR CODE 1002: Could not validate API Key, see web dashboard for details. Is it defined as metadata inside your application tag in your Manifest. Does it match the key and package name defined in your web dashboard, and is your billing up to date? + Accessibility service threw exception. + WARNING: Failed parser match. + Importing cached actions from json file + Initiating request: %1$s %2$s + ERROR CODE 1013: Environment must be TEST_ENV, DEBUG_ENV, or PROD_ENV + ERROR CODE 6001: Received invalid Intent + ERROR CODE 1011: You have selected a sim with HNI %1$s, but the action\'s only valid choices are %2$s + ERROR 5010: Problem with device keystore. Please contact Hover with your device model if the problem continues. + ERROR CODE 1008: Action not found. If you have updated your actions in the Hover dashboard you must ensure that they are updated in your client which happens when `%1$s.initialize()` is called. + ERROR CODE 1006: You must specify an Action ID + ERROR CODE 1012: Missing parameter. You must specify: %1$s + ERROR CODE 1001: Could not find API Key. Is it defined as metadata inside your application tag in your Manifest? + ERROR CODE 1000: Could not get package name. Have you defined an applicationID in your build.gradle? + ERROR CODE 1003: Please call %1$s.initialize() before using this function + Parser %1$d matched message: %2$s. + ERROR 5012: Could not decrypt PIN. Please contact Hover with your device model if the problem continues. + ERROR 5011: Could not encrypt PIN. Please contact Hover with your device model if the problem continues. + Problem with PIN + Parser matched an SMS, ending session. + Finished carrying out action, cleaning up. + Received response from operator, carrying out action. + Successfully ran action. + Request valid, confirming any variables or PIN with user. + SMS HIT. + WARNING: SMS miss. Check that sender match is case sensitive and regex matches whole message. + Received SMS from %1$s: %2$s + SMS sender matched a parser. + Timeout triggered + ERROR 3001: Timeout reached, please check your USSD network connection and your action configurations. + Manual environment active. This session will be recorded. + sms_logs + network icon + next icon + 9 + Off + OK + 1 + Allow display over other apps + Transacting + Show progress updates by turning switch ON in the next screen.]]> + Next, tap the switch ON + ACCESSIBILITY + DISPLAY_OVERLAY + READ_PHONE_STATE + READ_SMS + permission icon + Permissions needed + We need this permission to read your SIM card and dial USSD numbers on your behalf + phone border icon + PIN character + Please wait + custom_actions + Loading, one moment… + Processing response SMS, one moment… + response icon + ERROR 6003: You must specify a list of actions + ERROR 1007: No actions found. Do you have some in your account, and have you called %1$s.initialize()? + ERROR 6005: Bad package name format. Expecting format: com.example.ActivityName or something similar + ERROR 6004: Invalid request: When you specify multiple action Ids they must be for different networks/SIM cards + ERROR CODE 1010: Key value pair was not valid + ERROR CODE 4002: User has not granted %1$s permission(s) + ERROR CODE 5003: Could not use USSD API + ERROR CODE 5001: Could not find Phone Dialer + ERROR CODE 6002: Please ensure you have READ_PHONE_STATE permission + ERROR CODE 5002: Could not find SIM Toolkit for correct SIM + ERROR CODE 5013: Could not use PIN. Please contact Hover with your device model if the problem continues. + ERROR CODE 4003: User has no SIM cards that work with the specified actions + ERROR CODE 4001: Request canceled by user + Private & Secure + https://19a2e15db8bf433fbab4458997b26eee@o44209.ingest.sentry.io/95446 + https://%1$s@sentry.io/95446?stacktrace.app.packages=com.hover.sdk&release=%2$s + USSD Session + Settings + 7 + SIM + 6 + parsing-for-variables + …still waiting… + forced-stopped-before-end + Test environment active. No actual transactions will take place. See Android Studio logcat for messages. + 3 + toggle icon + validate-token + transactions/batch + 2 + unknown + Communication error, please try again. + %1$s%2$s + Use Accessibility Service + By continuing, you allow this app to have the permission(s) above in order to serve you a fully functional app experience + Show Drawing Overlay + %1$s would like to: + Read SIM and make phone calls + Read SMS messages + wifi icon + likely-wrong-pin-detection + SIM not present + Please insert a %1$s SIM. + 0 + buy goods + money received + move money + To %1$s + On network + Someone else + pay bill + Secure with %1$s + %1$s never saves your PIN. We never see your PIN. Your PIN never leaves your device. When you put your PIN in the app, we encrypt the PIN on your device with Android Key Store, which is trusted by thousands of banks all over the world, and delete it as soon as your bank or mobile money provider has it. + Visit our FAQ page + To learn more about security in %1$s: + Myself + send money + https://www.stax.me/faq + use USSD + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml index 97e0455..399f698 100644 --- a/example/android/app/src/profile/AndroidManifest.xml +++ b/example/android/app/src/profile/AndroidManifest.xml @@ -1,6 +1,6 @@ - - diff --git a/example/android/build.gradle b/example/android/build.gradle index e0d7ae2..c586514 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,18 +1,25 @@ buildscript { + ext.kotlin_version = '1.7.10' repositories { google() - jcenter() + mavenCentral() + maven { url "https://maven.usehover.com/snapshots" } } + dependencies { - classpath 'com.android.tools.build:gradle:3.5.0' + + classpath 'com.android.tools.build:gradle:7.3.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } } allprojects { repositories { google() - jcenter() + mavenCentral() + maven { url "https://maven.usehover.com/snapshots" } } } @@ -24,6 +31,6 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/example/android/gradle.properties b/example/android/gradle.properties index 38c8d45..94adc3a 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -1,4 +1,3 @@ org.gradle.jvmargs=-Xmx1536M -android.enableR8=true android.useAndroidX=true android.enableJetifier=true diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index 296b146..3c472b9 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/example/android/settings.gradle b/example/android/settings.gradle index 44e62bc..55c4ca8 100644 --- a/example/android/settings.gradle +++ b/example/android/settings.gradle @@ -1,11 +1,20 @@ -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + } + settings.ext.flutterSdkPath = flutterSdkPath() -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() + includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + plugins { + id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false + } +} -def flutterSdkPath = properties.getProperty("flutter.sdk") -assert flutterSdkPath != null, "flutter.sdk not set in local.properties" -apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" +include ":app" + +apply from: "${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/example/integration_test/plugin_integration_test.dart b/example/integration_test/plugin_integration_test.dart new file mode 100644 index 0000000..94da938 --- /dev/null +++ b/example/integration_test/plugin_integration_test.dart @@ -0,0 +1,25 @@ +// This is a basic Flutter integration test. +// +// Since integration tests run in a full Flutter application, they can interact +// with the host side of a plugin implementation, unlike Dart unit tests. +// +// For more information about Flutter integration tests, please see +// https://docs.flutter.dev/cookbook/testing/integration/introduction + + +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import 'package:hover_ussd/hover_ussd.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('getPlatformVersion test', (WidgetTester tester) async { + final HoverUssd plugin = HoverUssd(); + + // The version string depends on the host platform running the test, so + // just assert that some non-empty string is returned. + + }); +} diff --git a/example/ios/.gitignore b/example/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/example/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..9625e10 --- /dev/null +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 11.0 + + diff --git a/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/example/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/example/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..75c0e50 --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,614 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807E294A63A400263BE5 /* Frameworks */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1430; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AE0B7B92F70575B8D7E0D07E /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 89B67EB44CE7B6631473024E /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 640959BDD8F10B91D80A66BE /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..87131a0 --- /dev/null +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@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) + } +} diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..7353c41 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..6ed2d93 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cd7b00 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..fe73094 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..321773c Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..502f463 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..e9f5fea Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..84ac32a Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..8953cba Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..0467bf1 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/ios/Runner/Base.lproj/Main.storyboard b/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist new file mode 100644 index 0000000..5458fc4 --- /dev/null +++ b/example/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Example + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/example/ios/Runner/Runner-Bridging-Header.h b/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/example/ios/RunnerTests/RunnerTests.swift b/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/example/lib/main.dart b/example/lib/main.dart index d2ae52f..36536d1 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,54 +1,275 @@ -import 'package:flutter/material.dart'; +import 'dart:async'; +import 'package:flutter/material.dart'; import 'package:hover_ussd/hover_ussd.dart'; void main() { - WidgetsFlutterBinding.ensureInitialized(); - HoverUssd().initialize(); - runApp(MyApp()); + runApp(const MyApp()); } -class MyApp extends StatefulWidget { +class MyApp extends StatelessWidget { + const MyApp({super.key}); + @override - _MyAppState createState() => _MyAppState(); + Widget build(BuildContext context) { + return MaterialApp( + title: 'Hover USSD Example', + theme: ThemeData( + primarySwatch: Colors.blue, + visualDensity: VisualDensity.adaptivePlatformDensity, + ), + home: const MyHomePage(), + ); + } } -class _MyAppState extends State { - final HoverUssd _hoverUssd = HoverUssd(); +class MyHomePage extends StatefulWidget { + const MyHomePage({super.key}); @override - Widget build(BuildContext context) { - return MaterialApp( - home: Scaffold( - appBar: AppBar( - title: const Text('Plugin example app'), + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + late final HoverUssd _hoverUssd = HoverUssd(); + late StreamSubscription _transactionListening; + late StreamSubscription _actionDownloadListening; + bool _hasPermissions = false; + bool _isOverlayEnabled = false; + bool _isAccessibilityEnabled = false; + + final String activityName = "com.hover.sdk.permissions.PermissionActivity"; + + @override + void initState() { + _transactionListening = + _hoverUssd.getUssdTransactionState().listen((event) { + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar(content: Text(event.toMap().toString()))); + }); + + _actionDownloadListening = + _hoverUssd.getDownloadActionState().listen((event) { + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar(content: Text(event.toString()))); + }); + _checkAccessibility(); + _checkPermissions(); + _checkOverlay(); + _actionDownloadListening.onData((event) { + _initHover(); + }); + + super.initState(); + } + + @override + void dispose() { + _transactionListening.cancel(); + _actionDownloadListening.cancel(); + super.dispose(); + } + //check for permissions + + Future _checkPermissions() async { + _hasPermissions = await _hoverUssd.hasAllPermissions(); + setState(() {}); + } + + Future _checkAccessibility() async { + _isAccessibilityEnabled = await _hoverUssd.hasSmsPermission(); + setState(() {}); + } + + Future _checkOverlay() async { + _isOverlayEnabled = await _hoverUssd.hasOverlayPermission(); + setState(() {}); + } + + Future _initHover() async { + try { + await _hoverUssd.initialize( + apiKey: "15ccc2bd81801d8c5fbfd5847d3b4e77", + branding: "My Hover App", + logo: "ic_launcher", + notificationLogo: "ic_launcher", + ); + + _hoverUssd.setPermissionsActivity(activityName: activityName); + } catch (e) { + print(e); + } + } + + Future _getAndGoToActions() async { + final actions = await _hoverUssd.getAllActions(); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => HoverActionListPage( + hoverActions: actions, ), - body: Center( - child: Row( - children: [ - FlatButton( - onPressed: () { - _hoverUssd.sendUssd("c6e45e62", {"price": "4000"}); - }, - child: Text("Start Trasaction"), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Hover Ussd Example'), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: () { + _hoverUssd.startTransaction( + actionId: "c6e45e62", + extras: {"price": "4000"}, + theme: "HoverTheme", + header: "Hover Ussd Example", + showUserStepDescriptions: true, + ); + }, + child: const Text("Start Transaction"), + ), + ElevatedButton( + onPressed: _getAndGoToActions, + child: const Text("Get Actions"), + ), + + //refresh actions + ElevatedButton( + onPressed: () { + _hoverUssd.refreshActions(); + }, + child: const Text("Refresh Actions"), + ), + Text( + _hasPermissions + ? "Permissions Granted" + : "Permissions Not Granted", + style: TextStyle( + color: _hasPermissions ? Colors.green : Colors.red, + fontWeight: FontWeight.bold, + ), + ), + Text( + _isAccessibilityEnabled + ? "Accessibility Granted" + : "Accessibility Not Granted", + style: TextStyle( + color: _isAccessibilityEnabled ? Colors.green : Colors.red, + fontWeight: FontWeight.bold, ), - StreamBuilder( - stream: _hoverUssd.onTransactiontateChanged, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.data == TransactionState.succesfull) { - return Text("succesfull"); - } else if (snapshot.data == TransactionState.waiting) { - return Text("pending"); - } else if (snapshot.data == TransactionState.failed) { - return Text("failed"); - } - return Text("no transaction"); - }, + ), + Text( + _isOverlayEnabled ? "Overlay Granted" : "Overlay Not Granted", + style: TextStyle( + color: _isOverlayEnabled ? Colors.green : Colors.red, + fontWeight: FontWeight.bold, ), - ], - ), + ), + + StreamBuilder( + stream: _hoverUssd.getDownloadActionState(), + builder: (context, snapshot) { + final state = snapshot.data; + + String statusText; + Color textColor; + + if (state is ActionDownloaded) { + statusText = "Actions Downloaded"; + textColor = Colors.green; + } else if (state is ActionDownloadFailed) { + statusText = "Actions Not Downloaded"; + textColor = Colors.red; + } else if (state is ActionDownloading) { + statusText = "Actions Downloading"; + textColor = + Colors.blue; // Adjust color as per your preference + } else { + statusText = "Action Download Status: Unknown"; + textColor = Colors.grey; + } + + return Text( + statusText, + style: + TextStyle(color: textColor, fontWeight: FontWeight.bold), + ); + }, + ), + StreamBuilder( + stream: _hoverUssd.getUssdTransactionState(), + builder: (BuildContext context, snapshot) { + if (snapshot.data is SmsParsed) { + return Text("Sms parsed : \n${snapshot.data!.toMap()}"); + } + if (snapshot.data is UssdSucceeded) { + return Text("Ussd Succeded : \n${snapshot.data!.toMap()}"); + } + if (snapshot.data is UssdLoading) { + return const Text("loading..."); + } + if (snapshot.data is UssdFailed) { + return Text("Ussd Failed : \n${snapshot.data!.toMap()}"); + } + if (snapshot.data is EmptyState) { + return const Text("Empty State"); + } + return const Text("No state"); + }, + ), + ], ), ), ); } } + +class HoverActionListPage extends StatefulWidget { + final List hoverActions; + + const HoverActionListPage({Key? key, required this.hoverActions}) + : super(key: key); + + @override + State createState() => _HoverActionListPageState(); +} + +class _HoverActionListPageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Hover Action List'), + ), + body: ListView.builder( + itemCount: widget.hoverActions.length, + itemBuilder: (BuildContext context, int index) { + final hoverAction = widget.hoverActions[index]; + return ListTile( + title: Text(hoverAction.name ?? ''), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Network: ${hoverAction.networkName ?? ''}'), + Text('Country: ${hoverAction.countryAlpha2 ?? ''}'), + Text('Transaction Type: ${hoverAction.transactionType ?? ''}'), + // Add more details as needed + ], + ), + onTap: () { + // Handle onTap if needed + }, + ); + }, + ), + ); + } +} diff --git a/example/pubspec.lock b/example/pubspec.lock index 3514234..6dc82ec 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -5,96 +5,194 @@ packages: dependency: transitive description: name: async - url: "https://pub.dartlang.org" + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" source: hosted - version: "2.5.0-nullsafety" + version: "2.11.0" boolean_selector: dependency: transitive description: name: boolean_selector - url: "https://pub.dartlang.org" + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" source: hosted - version: "2.1.0-nullsafety" + version: "2.1.1" characters: dependency: transitive description: name: characters - url: "https://pub.dartlang.org" + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" source: hosted - version: "1.1.0-nullsafety.2" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0-nullsafety" + version: "1.3.0" clock: dependency: transitive description: name: clock - url: "https://pub.dartlang.org" + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" source: hosted - version: "1.1.0-nullsafety" + version: "1.1.1" collection: dependency: transitive description: name: collection - url: "https://pub.dartlang.org" + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" source: hosted - version: "1.15.0-nullsafety.2" + version: "1.18.0" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - url: "https://pub.dartlang.org" + sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d + url: "https://pub.dev" source: hosted - version: "0.1.3" + version: "1.0.6" fake_async: dependency: transitive description: name: fake_async - url: "https://pub.dartlang.org" + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" source: hosted - version: "1.1.0-nullsafety" + version: "1.3.1" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_driver: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + url: "https://pub.dev" + source: hosted + version: "2.0.3" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + fuchsia_remote_debug_protocol: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" hover_ussd: dependency: "direct main" description: path: ".." relative: true source: path - version: "0.0.2" + version: "2.0.0" + integration_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + url: "https://pub.dev" + source: hosted + version: "10.0.0" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" matcher: dependency: transitive description: name: matcher - url: "https://pub.dartlang.org" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + url: "https://pub.dev" source: hosted - version: "0.12.10-nullsafety" + version: "0.8.0" meta: dependency: transitive description: name: meta - url: "https://pub.dartlang.org" + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + url: "https://pub.dev" source: hosted - version: "1.3.0-nullsafety.2" + version: "1.11.0" path: dependency: transitive description: name: path - url: "https://pub.dartlang.org" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + platform: + dependency: transitive + description: + name: platform + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + url: "https://pub.dev" + source: hosted + version: "3.1.4" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" source: hosted - version: "1.8.0-nullsafety" + version: "2.1.8" + process: + dependency: transitive + description: + name: process + sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" + url: "https://pub.dev" + source: hosted + version: "5.0.2" sky_engine: dependency: transitive description: flutter @@ -104,58 +202,82 @@ packages: dependency: transitive description: name: source_span - url: "https://pub.dartlang.org" + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" source: hosted - version: "1.8.0-nullsafety" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.dartlang.org" + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" source: hosted - version: "1.10.0-nullsafety" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.dartlang.org" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" source: hosted - version: "2.1.0-nullsafety" + version: "2.1.2" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.dartlang.org" + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" source: hosted - version: "1.1.0-nullsafety" + version: "1.2.0" + sync_http: + dependency: transitive + description: + name: sync_http + sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" + url: "https://pub.dev" + source: hosted + version: "0.3.1" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.dartlang.org" + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" source: hosted - version: "1.2.0-nullsafety" + version: "1.2.1" test_api: dependency: transitive description: name: test_api - url: "https://pub.dartlang.org" + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + url: "https://pub.dev" source: hosted - version: "0.2.19-nullsafety" - typed_data: + version: "0.6.1" + vector_math: dependency: transitive description: - name: typed_data - url: "https://pub.dartlang.org" + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" source: hosted - version: "1.3.0-nullsafety.2" - vector_math: + version: "2.1.4" + vm_service: dependency: transitive description: - name: vector_math - url: "https://pub.dartlang.org" + name: vm_service + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + url: "https://pub.dev" + source: hosted + version: "13.0.0" + webdriver: + dependency: transitive + description: + name: webdriver + sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e" + url: "https://pub.dev" source: hosted - version: "2.1.0-nullsafety.2" + version: "3.0.3" sdks: - dart: ">=2.10.0-0.0.dev <2.10.0" - flutter: ">=1.20.0 <2.0.0" + dart: ">=3.2.0-0 <4.0.0" + flutter: ">=3.3.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 52b2b49..a5d7044 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,13 +1,18 @@ name: hover_ussd_example description: Demonstrates how to use the hover_ussd plugin. - # The following line prevents the package from being accidentally published to -# pub.dev using `pub publish`. This is preferred for private packages. +# pub.dev using `flutter pub publish`. This is preferred for private packages. publish_to: 'none' # Remove this line if you wish to publish to pub.dev environment: - sdk: ">=2.7.0 <3.0.0" + sdk: '>=3.1.3 <4.0.0' +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. dependencies: flutter: sdk: flutter @@ -22,16 +27,25 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^0.1.3 + cupertino_icons: ^1.0.2 dev_dependencies: + integration_test: + sdk: flutter flutter_test: sdk: flutter + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^2.0.0 + # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec -# The following section is specific to Flutter. +# The following section is specific to Flutter packages. flutter: # The following line ensures that the Material Icons font is @@ -45,7 +59,7 @@ flutter: # - images/a_dot_ham.jpeg # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. + # https://flutter.dev/assets-and-images/#resolution-aware # For details regarding adding assets from package dependencies, see # https://flutter.dev/assets-and-images/#from-packages diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart index 737318f..8d49d7f 100644 --- a/example/test/widget_test.dart +++ b/example/test/widget_test.dart @@ -1,12 +1,26 @@ // This is a basic Flutter widget test. // // To perform an interaction with a widget in your test, use the WidgetTester -// utility that Flutter provides. For example, you can send tap and scroll +// utility in the flutter_test package. For example, you can send tap and scroll // gestures. You can also use WidgetTester to find child widgets in the widget // tree, read text, and verify that the values of widget properties are correct. import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; import 'package:hover_ussd_example/main.dart'; -void main() {} +void main() { + testWidgets('Verify Platform version', (WidgetTester tester) async { + // Build our app and trigger a frame. + + // Verify that platform version is retrieved. + expect( + find.byWidgetPredicate( + (Widget widget) => widget is Text && + widget.data!.startsWith('Running on:'), + ), + findsOneWidget, + ); + }); +} diff --git a/hover_ussd.iml b/hover_ussd.iml index 73e7ebd..ca846be 100644 --- a/hover_ussd.iml +++ b/hover_ussd.iml @@ -11,7 +11,17 @@ + + + + + + + + + + diff --git a/lib/hover_ussd.dart b/lib/hover_ussd.dart index a7d640b..f089c51 100644 --- a/lib/hover_ussd.dart +++ b/lib/hover_ussd.dart @@ -1,58 +1,5 @@ -import 'dart:async'; - -import 'package:flutter/services.dart'; -import 'package:meta/meta.dart'; - -enum TransactionState { succesfull, waiting, failed } - -class HoverUssd { - final MethodChannel _methodChannel; - final EventChannel _eventChannel; - - factory HoverUssd() { - if (_instance == null) { - final MethodChannel methodChannel = const MethodChannel('hover_ussd'); - final EventChannel eventChannel = const EventChannel('transaction_event'); - _instance = HoverUssd.private(methodChannel, eventChannel); - } - return _instance; - } - - static HoverUssd _instance; - - @visibleForTesting - HoverUssd.private(this._methodChannel, this._eventChannel); - - Stream _onTransactionStateChanged; - - Future sendUssd(String actionId, Map extras) async => - await _methodChannel.invokeMethod( - "hoverStartTransaction", {"action_id": actionId, "extras": extras}); - - Stream get onTransactiontateChanged { - if (_onTransactionStateChanged == null) { - _onTransactionStateChanged = _eventChannel - .receiveBroadcastStream() - .map((dynamic event) => _parseTransactionState(event)); - } - return _onTransactionStateChanged; - } - - void initialize() async { - await _methodChannel.invokeMethod("hoverInitial"); - } - - TransactionState _parseTransactionState(String state) { - switch (state) { - case "succeeded": - return TransactionState.succesfull; - break; - case "pending": - return TransactionState.waiting; - case "failed": - return TransactionState.failed; - default: - throw ArgumentError('$state'); - } - } -} +export 'hover_ussd_plugin.dart'; +export 'models/transaction.dart'; +export 'models/transaction_state.dart'; +export 'models/hover_action.dart'; +export 'models/download_action_state.dart'; \ No newline at end of file diff --git a/lib/hover_ussd_plugin.dart b/lib/hover_ussd_plugin.dart new file mode 100644 index 0000000..fa9e4b6 --- /dev/null +++ b/lib/hover_ussd_plugin.dart @@ -0,0 +1,212 @@ +import 'dart:async'; + +import 'package:flutter/services.dart'; +import 'package:hover_ussd/hover_ussd.dart'; + +class HoverUssd { + static const MethodChannel _methodChannel = MethodChannel('HoverUssdChannel'); + static const EventChannel _transactionEventChannel = + EventChannel('TransactionEvent'); + static const EventChannel _downloadEventChannel = + EventChannel('ActionDownloadEvent'); + + Stream? _onTransactionStateChanged; + + /// Initialize HoverUssd with branding and logo information. + /// + /// [apiKey] is the Hover API key. + /// [branding] is the name of the app. + /// [logo] is the id of the app's logo in ressource. + /// [notificationLogo] is the id of the app's logo in ressource. + /// + Future initialize({ + required String apiKey, + String? branding, + String? logo, + String? notificationLogo, + }) async { + await _methodChannel.invokeMethod("Initialize", { + "branding": branding ?? "Flutter App", + "logo": logo ?? "ic_launcher", + "notificationLogo": notificationLogo ?? "ic_launcher", + "apiKey": apiKey, + }); + } + + /// Start a transaction with the provided parameters. + /// [actionId] is the id of the action to be performed. + /// [extras] is a map of extra parameters to be sent to the action. + /// [theme] is the theme of the transaction. + /// [header] is the header of the transaction. + /// [initialProcessingMessage] is the message to be displayed while the transaction is being processed. + /// [finalMsgDisplayTime] is the time to display the final message. + /// [showUserStepDescriptions] is a flag to show the user step descriptions. + /// + Future startTransaction({ + required String actionId, + Map? extras, + String? theme, + String? header, + String? initialProcessingMessage, + int finalMsgDisplayTime = 5000, + bool? showUserStepDescriptions, + }) async { + await _methodChannel.invokeMethod("HoverStartATransaction", { + "actionId": actionId, + "extras": extras ?? {}, + "theme": theme, + "header": header, + "initialProcessingMessage": initialProcessingMessage, + "finalMsgDisplayTime": finalMsgDisplayTime, + }); + } + + /// Check if the app has all the necessary permissions. + Future hasAllPermissions() async { + return await _methodChannel.invokeMethod("HasAllPermissions"); + } + + // Check accessibility permission + Future hasAccessibilityPermission() async { + return await _methodChannel.invokeMethod('HasAccessibilityPermission'); + } + + // Check SMS permission + Future hasSmsPermission() async { + return await _methodChannel.invokeMethod('HasSmsPermission'); + } + + // Check phone permission + Future hasPhonePermission() async { + return await _methodChannel.invokeMethod('HasPhonePermission'); + } + + // Check contacts permission + Future hasContactsPermission() async { + return await _methodChannel.invokeMethod('HasContactsPermission'); + } + + // Check write permission + Future hasWritePermission() async { + return await _methodChannel.invokeMethod('HasWritePermission'); + } + + // Check overlay permission + Future hasOverlayPermission() async { + return await _methodChannel.invokeMethod('HasOverlayPermission'); + } + + /// this set custom permissions activity for the app + /// [activityName] is the name of the custom activity to be set ex: 'com.example.example.CustomPermissionsActivity' + Future setPermissionsActivity({required String activityName}) async { + _methodChannel.invokeMethod("SetPermissionsActivity"); + } + + /// Check if the app has downloaded actions. + Future hasActionsDownloaded() async { + return await _methodChannel.invokeMethod("HasActionsDownloaded"); + } + + /// Retrieve a list of available actions. + Future> getAllActions() async { + try { + final result = await _methodChannel.invokeListMethod("getAllActions"); + if (result != null) { + List actions = result + .map((item) => HoverAction.fromMap(item.cast())) + .toList(); + return actions; + } else { + return []; + } + } catch (e) { + print('Error retrieving actions: $e'); + return []; + } + } + + /// refresh actions + Future refreshActions() async { + await _methodChannel.invokeMethod("refreshActions"); + } + + Future> getAllTransactions() async { + try { + final result = + await _methodChannel.invokeListMethod("getAllTransactions"); + if (result != null) { + final List> resultList = + List>.from(result); + List transactions = + resultList.map((item) => Transaction.fromMap(item)).toList(); + return transactions; + } else { + return []; + } + } catch (e) { + print('Error retrieving transactions: $e'); + return []; + } + } + + /// Get the stream of transaction states. + Stream getUssdTransactionState() { + if (_onTransactionStateChanged == null) { + _onTransactionStateChanged ??= _transactionEventChannel + .receiveBroadcastStream() + .map((dynamic event) => _parseTransactionState( + event['state'], + Map.from(event), + )) + .asBroadcastStream(); + } + + return _onTransactionStateChanged!; + } + + Stream getDownloadActionState() { + return _downloadEventChannel + .receiveBroadcastStream() + .map((dynamic event) => _parseDownloadActionState( + event['state'], + Map.from(event), + )) + .asBroadcastStream(); + } + + DonwloadActionState _parseDownloadActionState( + String state, + Map result, + ) { + switch (state) { + case "actionDownloaded": + return ActionDownloaded.fromMap(result); + case "actionDownloadFailed": + return ActionDownloadFailed.fromMap(result); + case "actionDownloading": + return ActionDownloading(); + default: + return EmptyDownloadState(); + } + } + + /// Parse transaction state from received event. + TransactionState _parseTransactionState( + String state, + Map result, + ) { + print("State: $state"); + switch (state) { + case "smsParsed": + return SmsParsed.fromMap(result); + case "ussdSucceeded": + return UssdSucceeded.fromMap(result); + case "ussdFailed": + return UssdFailed.fromMap(result); + case "ussdLoading": + return UssdLoading(); + default: + return EmptyState(); + } + } +} diff --git a/lib/models/download_action_state.dart b/lib/models/download_action_state.dart new file mode 100644 index 0000000..ff101ad --- /dev/null +++ b/lib/models/download_action_state.dart @@ -0,0 +1,59 @@ +abstract class DonwloadActionState { + const DonwloadActionState(); + Map toMap(); +} + +class ActionDownloadFailed extends DonwloadActionState { + final String error; + + ActionDownloadFailed(this.error); + + @override + Map toMap() { + return { + "state": "actionDownloadFailed", + "error": error, + }; + } + + factory ActionDownloadFailed.fromMap(Map map) { + return ActionDownloadFailed(map['error']); + } +} + +class ActionDownloading extends DonwloadActionState { + @override + Map toMap() { + return { + "state": "actionDownloading", + }; + } +} + +class ActionDownloaded extends DonwloadActionState { + final bool isDownloaded; + + ActionDownloaded(this.isDownloaded); + + @override + Map toMap() { + return { + "state": "actionDownloaded", + "isDownloaded": isDownloaded, + }; + } + + factory ActionDownloaded.fromMap(Map map) { + return ActionDownloaded(map['isDownloaded']); + } + +} + +class EmptyDownloadState extends DonwloadActionState { + @override + Map toMap() { + return { + "state": "empty", + }; + } +} \ No newline at end of file diff --git a/lib/models/hover_action.dart b/lib/models/hover_action.dart new file mode 100644 index 0000000..049ddc9 --- /dev/null +++ b/lib/models/hover_action.dart @@ -0,0 +1,119 @@ +class HoverAction { + int? id; + String? publicId; + String? name; + int? channelId; + String? networkName; + String? countryAlpha2; + String? rootCode; + String? transportType; + String? transactionType; + int? fromInstitutionId; + String? fromInstitutionName; + String? fromInstitutionLogo; + int? toInstitutionId; + String? toInstitutionName; + String? toInstitutionLogo; + String? toCountryAlpha2; + int? createdTimestamp; + int? updatedTimestamp; + int? bountyAmount; + bool? bountyIsOpen; + bool? isReady; + int? bonusPercent; + String? bonusMessage; + List? hniList; + List? customSteps; + List? tagsList; + Map? requiredParams; + Map? outputParams; + + HoverAction({ + this.id, + this.publicId, + this.name, + this.channelId, + this.networkName, + this.countryAlpha2, + this.rootCode, + this.transportType, + this.transactionType, + this.fromInstitutionId, + this.fromInstitutionName, + this.fromInstitutionLogo, + this.toInstitutionId, + this.toInstitutionName, + this.toInstitutionLogo, + this.toCountryAlpha2, + this.createdTimestamp, + this.updatedTimestamp, + this.bountyAmount, + this.bountyIsOpen, + this.isReady, + this.bonusPercent, + this.bonusMessage, + this.hniList, + this.customSteps, + this.tagsList, + this.requiredParams, + this.outputParams, + }); + + factory HoverAction.fromMap(Map json) { + return HoverAction( + id: json['id'], + publicId: json['public_id'], + name: json['name'], + channelId: json['channel_id'], + networkName: json['network_name'], + countryAlpha2: json['country_alpha2'], + rootCode: json['root_code'], + transportType: json['transport_type'], + transactionType: json['transaction_type'], + fromInstitutionId: json['from_institution_id'], + fromInstitutionName: json['from_institution_name'], + fromInstitutionLogo: json['from_institution_logo'], + toInstitutionId: json['to_institution_id'], + toInstitutionName: json['to_institution_name'], + toInstitutionLogo: json['to_institution_logo'], + toCountryAlpha2: json['to_country_alpha2'], + createdTimestamp: json['created_timestamp'], + updatedTimestamp: json['updated_timestamp'], + bountyAmount: json['bounty_amount'], + bountyIsOpen: json['bounty_is_open'], + isReady: json['is_ready'], + bonusPercent: json['bonus_percent'], + bonusMessage: json['bonus_message'], + requiredParams: json['required_params'], + outputParams: json['output_params'], + ); + } + + Map toMap() { + return { + 'id': id, + 'public_id': publicId, + 'name': name, + 'channel_id': channelId, + 'network_name': networkName, + 'country_alpha2': countryAlpha2, + 'root_code': rootCode, + 'transport_type': transportType, + 'transaction_type': transactionType, + 'from_institution_id': fromInstitutionId, + 'from_institution_name': fromInstitutionName, + 'from_institution_logo': fromInstitutionLogo, + 'to_institution_id': toInstitutionId, + 'to_institution_name': toInstitutionName, + 'to_institution_logo': toInstitutionLogo, + 'to_country_alpha2': toCountryAlpha2, + 'created_timestamp': createdTimestamp, + 'updated_timestamp': updatedTimestamp, + 'bounty_amount': bountyAmount, + 'bounty_is_open': bountyIsOpen, + 'is_ready': isReady, + 'bonus_percent': bonusPercent, + 'bonus_message': bonusMessage, + }; + } +} diff --git a/lib/models/transaction.dart b/lib/models/transaction.dart new file mode 100644 index 0000000..18b9486 --- /dev/null +++ b/lib/models/transaction.dart @@ -0,0 +1,62 @@ +class Transaction { + final String? id; + final String? actionId; + final String? uuid; + final String? status; + final String? category; + final String? userMessage; + final String? networkHni; + final String? inputExtras; + final String? parsedVariables; + final List? ussdMessages; + final List? enteredValues; + final List? smsHits; + final List? smsMisses; + final List? logMessages; + final List? matchedParsers; + final int? reqTimestamp; + final int? updatedTimestamp; + + Transaction( + {this.uuid, + this.id, + this.actionId, + this.status, + this.category, + this.userMessage, + this.networkHni, + this.inputExtras, + this.parsedVariables, + this.ussdMessages, + this.enteredValues, + this.smsHits, + this.smsMisses, + this.logMessages, + this.matchedParsers, + this.reqTimestamp, + this.updatedTimestamp}); + + factory Transaction.fromMap(Map map) { + return Transaction( + uuid: map['uuid'], + id: map['id'], + actionId: map['actionId'], + status: map['status'], + category: map['category'], + userMessage: map['userMessage'], + networkHni: map['networkHni'], + inputExtras: map['inputExtras'], + parsedVariables: map['parsedVariables'], + ussdMessages: map['ussdMessages'], + enteredValues: map['enteredValues'], + smsHits: map['smsHits'], + smsMisses: map['smsMisses'], + logMessages: map['logMessages'], + matchedParsers: map['matchedParsers'], + reqTimestamp: map['reqTimestamp'], + updatedTimestamp: map['updatedTimestamp'], + ); + } +} + + diff --git a/lib/models/transaction_state.dart b/lib/models/transaction_state.dart new file mode 100644 index 0000000..6915873 --- /dev/null +++ b/lib/models/transaction_state.dart @@ -0,0 +1,191 @@ +abstract class TransactionState { + TransactionState(); + + Map toMap(); +} + +/// when the message(sms) is successfully parsed +class SmsParsed extends TransactionState { + /// Unique Identifier for the transaction + final String? uuid; + + /// The action id from out supported operators page + final String? actionId; + + /// Full message used for parsing + final String? responseMessage; + + /// “pending”, “failed” or “succeeded” + final String? status; + + /// What you specified for the latest matched parser or one of the default failed cases above + final String? statusMeaning; + + /// Message you specified for the latest matched parser + final String? statusDescription; + + /// Unique identifier for the parser which matched, causing this transaction to update + final int? matchedParserId; + + /// “ussd” or “sms” + final String? messagetype; + + /// If SMS, the sender id from the parser form, null if USSD + final String? messageSender; + + /// Regular expression you specified in the parser form + final String? regex; + + /// The Home Network Identifier (MCC + MNC) of the SIM used + final String? simHni; + + /// 0 for normal, 1 for debug, 2 for test + final int? environment; + + /// Time user initiated transaction (Unix time) + final int? requestTimestamp; + + /// Time at which the transaction last updated (SMS arrival or USSD finished) + final int? updateTimestamp; + + /// (depreciated) Same as updated_timestamp + final int? responseTimestamp; + + /// A HashMap object of all the extras you passed in using .extra(key, value) + final Map? inputExtras; + + /// A HashMap object of all named groups parsed out of the response message based on your regex + final Map? parsedVariables; + + /// Array of all USSD session messages in order encountered + final List? sessionMessages; + + SmsParsed( + {this.uuid, + this.actionId, + this.responseMessage, + this.status, + this.statusMeaning, + this.statusDescription, + this.matchedParserId, + this.messagetype, + this.messageSender, + this.regex, + this.simHni, + this.environment, + this.requestTimestamp, + this.updateTimestamp, + this.responseTimestamp, + this.inputExtras, + this.parsedVariables, + this.sessionMessages}); + @override + Map toMap() { + return { + 'uuid': uuid, + 'action_id': actionId, + 'response_message': responseMessage, + 'status': status, + 'status_meaning': statusMeaning, + 'status_description': statusDescription, + 'matched_parser_id': matchedParserId, + 'messagetype': messagetype, + 'message_sender': messageSender, + 'regex': regex, + 'sim_hni': simHni, + 'environment': environment, + 'request_timestamp': requestTimestamp, + 'update_timestamp': updateTimestamp, + 'response_timestamp': responseTimestamp, + 'input_extras': inputExtras, + 'parsed_variables': parsedVariables, + 'session_messages': sessionMessages, + }; + } + + factory SmsParsed.fromMap(Map json) { + return SmsParsed( + uuid: json['uuid'] as String?, + sessionMessages: json['session_messages'] as List?, + inputExtras: json['input_extras'] as Map?, + parsedVariables: json['parsed_variables'] as Map?, + responseTimestamp: json['response_timestamp'] as int?, + updateTimestamp: json['update_timestamp'] as int?, + requestTimestamp: json['request_timestamp'] as int?, + environment: json['environment'] as int?, + simHni: json['sim_hni'] as String?, + regex: json['regex'] as String?, + messageSender: json['message_sender'] as String?, + messagetype: json['messagetype'] as String?, + matchedParserId: json['matched_parser_id'] as int?, + statusDescription: json['status_description'] as String?, + statusMeaning: json['status_meaning'] as String?, + actionId: json['action_id'] as String?, + responseMessage: json['response_message'] as String?, + status: json['status'] as String?, + ); + } +} + +/// when ussd session run succesfully +class UssdSucceeded extends TransactionState { + /// Unique Identifier for the transaction + final String? uuid; + + /// The action id from out supported operators page + final String? actionId; + + /// Full message used for parsing + final String? responseMessage; + + UssdSucceeded({this.uuid, this.actionId, this.responseMessage}); + @override + Map toMap() { + return { + 'uuid': uuid, + 'action_id': actionId, + 'response_message': responseMessage, + }; + } + + factory UssdSucceeded.fromMap(Map json) { + return UssdSucceeded( + uuid: json['uuid'] as String?, + actionId: json['action_id'] as String?, + responseMessage: json['response_message'] as String?, + ); + } +} + +/// when the ussd code failed; this can be caused by user +/// dissmiss or request refuse +class UssdFailed extends TransactionState { + /// error message if ussd call failed + final String? errorMessage; + + UssdFailed({this.errorMessage}); + + factory UssdFailed.fromMap(Map json) { + return UssdFailed(errorMessage: json["errorMessage"]); + } + + @override + Map toMap() { + return {"errorMessage": errorMessage}; + } +} + +class UssdLoading extends TransactionState { + @override + Map toMap() { + return {}; + } +} + + +class EmptyState extends TransactionState { + @override + Map toMap() { + return {}; + } +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 42e0dd6..3bdcb83 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,82 +5,140 @@ packages: dependency: transitive description: name: async - url: "https://pub.dartlang.org" + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" source: hosted - version: "2.5.0-nullsafety" + version: "2.11.0" boolean_selector: dependency: transitive description: name: boolean_selector - url: "https://pub.dartlang.org" + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" source: hosted - version: "2.1.0-nullsafety" + version: "2.1.1" characters: dependency: transitive description: name: characters - url: "https://pub.dartlang.org" + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" source: hosted - version: "1.1.0-nullsafety.2" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0-nullsafety" + version: "1.3.0" clock: dependency: transitive description: name: clock - url: "https://pub.dartlang.org" + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" source: hosted - version: "1.1.0-nullsafety" + version: "1.1.1" collection: dependency: transitive description: name: collection - url: "https://pub.dartlang.org" + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" source: hosted - version: "1.15.0-nullsafety.2" + version: "1.18.0" fake_async: dependency: transitive description: name: fake_async - url: "https://pub.dartlang.org" + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" source: hosted - version: "1.1.0-nullsafety" + version: "1.3.1" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + url: "https://pub.dev" + source: hosted + version: "2.0.3" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + url: "https://pub.dev" + source: hosted + version: "10.0.0" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" matcher: dependency: transitive description: name: matcher - url: "https://pub.dartlang.org" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + url: "https://pub.dev" source: hosted - version: "0.12.10-nullsafety" + version: "0.8.0" meta: - dependency: "direct main" + dependency: transitive description: name: meta - url: "https://pub.dartlang.org" + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + url: "https://pub.dev" source: hosted - version: "1.3.0-nullsafety.2" + version: "1.11.0" path: dependency: transitive description: name: path - url: "https://pub.dartlang.org" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" source: hosted - version: "1.8.0-nullsafety" + version: "1.9.0" + plugin_platform_interface: + dependency: "direct main" + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" sky_engine: dependency: transitive description: flutter @@ -90,58 +148,66 @@ packages: dependency: transitive description: name: source_span - url: "https://pub.dartlang.org" + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" source: hosted - version: "1.8.0-nullsafety" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.dartlang.org" + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" source: hosted - version: "1.10.0-nullsafety" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.dartlang.org" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" source: hosted - version: "2.1.0-nullsafety" + version: "2.1.2" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.dartlang.org" + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" source: hosted - version: "1.1.0-nullsafety" + version: "1.2.0" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.dartlang.org" + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" source: hosted - version: "1.2.0-nullsafety" + version: "1.2.1" test_api: dependency: transitive description: name: test_api - url: "https://pub.dartlang.org" + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + url: "https://pub.dev" source: hosted - version: "0.2.19-nullsafety" - typed_data: + version: "0.6.1" + vector_math: dependency: transitive description: - name: typed_data - url: "https://pub.dartlang.org" + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" source: hosted - version: "1.3.0-nullsafety.2" - vector_math: + version: "2.1.4" + vm_service: dependency: transitive description: - name: vector_math - url: "https://pub.dartlang.org" + name: vm_service + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + url: "https://pub.dev" source: hosted - version: "2.1.0-nullsafety.2" + version: "13.0.0" sdks: - dart: ">=2.10.0-0.0.dev <2.10.0" - flutter: ">=1.20.0 <2.0.0" + dart: ">=3.2.0-0 <4.0.0" + flutter: ">=3.3.0" diff --git a/pubspec.yaml b/pubspec.yaml index c5ec574..132ff3b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,31 +1,39 @@ name: hover_ussd description: A flutter plugin to make payments by usehover.com ussd gateway using Android Intent and receiving the transaction information back in response. -version: 0.0.2 -author: lucdotdev +version: 2.0.0 + + homepage: https://github.com/lucdotdev/hover_ussd environment: - sdk: ">=2.7.0 <3.0.0" - flutter: ">=1.20.0 <2.0.0" + sdk: '>=3.1.3 <4.0.0' + flutter: '>=3.3.0' dependencies: flutter: sdk: flutter - meta: ^1.1.8 + plugin_platform_interface: ^2.0.2 dev_dependencies: flutter_test: sdk: flutter + flutter_lints: ^2.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec -# The following section is specific to Flutter. +# The following section is specific to Flutter packages. flutter: # This section identifies this Flutter project as a plugin project. - # The 'pluginClass' and Android 'package' identifiers should not ordinarily - # be modified. They are used by the tooling to maintain consistency when + # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) + # which should be registered in the plugin registry. This is required for + # using method channels. + # The Android 'package' specifies package in which the registered class is. + # This is required for using method channels on Android. + # The 'ffiPlugin' specifies that native code should be built and bundled. + # This is required for using `dart:ffi`. + # All these are used by the tooling to maintain consistency when # adding or updating assets for this project. plugin: platforms: @@ -42,7 +50,7 @@ flutter: # https://flutter.dev/assets-and-images/#from-packages # # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. + # https://flutter.dev/assets-and-images/#resolution-aware # To add custom fonts to your plugin package, add a fonts section here, # in this "flutter" section. Each entry in this list should have a @@ -63,3 +71,4 @@ flutter: # # For details regarding fonts in packages, see # https://flutter.dev/custom-fonts/#from-packages + diff --git a/test/hover_ussd_test.dart b/test/hover_ussd_test.dart index 99b96ec..ffb39c8 100644 --- a/test/hover_ussd_test.dart +++ b/test/hover_ussd_test.dart @@ -1,19 +1,19 @@ -import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:hover_ussd/hover_ussd.dart'; -void main() { - const MethodChannel channel = MethodChannel('hover_ussd'); +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; - TestWidgetsFlutterBinding.ensureInitialized(); +class MockHoverUssdPlatform + with MockPlatformInterfaceMixin + { + +} + +void main() { - setUp(() { - channel.setMockMethodCallHandler((MethodCall methodCall) async { - return '42'; - }); - }); - tearDown(() { - channel.setMockMethodCallHandler(null); + test('getPlatformVersion', () async { + HoverUssd hoverUssdPlugin = HoverUssd(); +; }); }