mirror of
https://github.com/zoriya/Aeris.git
synced 2025-12-06 06:36:12 +00:00
Merge branch 'master' of github.com:AnonymusRaccoon/Aeris into multilng-about-json
This commit is contained in:
@@ -4,6 +4,8 @@ POSTGRES_PORT=
|
|||||||
POSTGRES_DB=
|
POSTGRES_DB=
|
||||||
POSTGRES_HOST=
|
POSTGRES_HOST=
|
||||||
WORKER_API_KEY=
|
WORKER_API_KEY=
|
||||||
|
|
||||||
|
HOSTNAME=aeris.com
|
||||||
WORKER_API_URL=
|
WORKER_API_URL=
|
||||||
WORKER_URL=
|
WORKER_URL=
|
||||||
DISCORD_CLIENT_ID=
|
DISCORD_CLIENT_ID=
|
||||||
@@ -18,3 +20,4 @@ SPOTIFY_CLIENT_ID=
|
|||||||
SPOTIFY_SECRET=
|
SPOTIFY_SECRET=
|
||||||
ANILIST_SECRET=
|
ANILIST_SECRET=
|
||||||
ANILIST_CLIENT_ID=
|
ANILIST_CLIENT_ID=
|
||||||
|
BACK_URL=
|
||||||
|
|||||||
1
.github/workflows/build.yml
vendored
1
.github/workflows/build.yml
vendored
@@ -20,7 +20,6 @@ jobs:
|
|||||||
- name: Run Docker
|
- name: Run Docker
|
||||||
run: docker run -v $PWD:/dist aeris_mobile_build
|
run: docker run -v $PWD:/dist aeris_mobile_build
|
||||||
- name: Upload build artifact
|
- name: Upload build artifact
|
||||||
if: github.ref == 'refs/head/master'
|
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: aeris_apk
|
name: aeris_apk
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"actions": [],
|
"actions": [],
|
||||||
"reactions": [
|
"reactions": [
|
||||||
{
|
{
|
||||||
"name": "UpdateAbout",
|
"name": "Anilist_UpdateAbout",
|
||||||
"description": {
|
"description": {
|
||||||
"en": "Update the about you section.",
|
"en": "Update the about you section.",
|
||||||
"fr": "Mets à jour votre section \"À propos de moi\"."
|
"fr": "Mets à jour votre section \"À propos de moi\"."
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
"returns": []
|
"returns": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "ToggleFavourite",
|
"name": "Anilist_ToggleFavourite",
|
||||||
"description": {
|
"description": {
|
||||||
"en": "Add or remove an anime from your favorite.",
|
"en": "Add or remove an anime from your favorite.",
|
||||||
"fr": "Ajoute ou retire un animé de vos favoris."
|
"fr": "Ajoute ou retire un animé de vos favoris."
|
||||||
|
|||||||
@@ -69,6 +69,7 @@
|
|||||||
],
|
],
|
||||||
"reactions": [
|
"reactions": [
|
||||||
{
|
{
|
||||||
|
<<<<<<< HEAD
|
||||||
"name": "PlayTrack",
|
"name": "PlayTrack",
|
||||||
"description": {
|
"description": {
|
||||||
"en": "Play a track",
|
"en": "Play a track",
|
||||||
@@ -78,6 +79,10 @@
|
|||||||
"en": "Play a track",
|
"en": "Play a track",
|
||||||
"fr": "Joue une musique"
|
"fr": "Joue une musique"
|
||||||
},
|
},
|
||||||
|
=======
|
||||||
|
"name": "Spotify_PlayTrack",
|
||||||
|
"description": "Play a track",
|
||||||
|
>>>>>>> 26566154676c8e279c3615522129e61b851c75a9
|
||||||
"params": [
|
"params": [
|
||||||
{
|
{
|
||||||
"name": "artist",
|
"name": "artist",
|
||||||
@@ -134,6 +139,7 @@
|
|||||||
"returns": []
|
"returns": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
<<<<<<< HEAD
|
||||||
"name": "AddTrackToLibrary",
|
"name": "AddTrackToLibrary",
|
||||||
"description": {
|
"description": {
|
||||||
"en": "Add a track to library",
|
"en": "Add a track to library",
|
||||||
@@ -143,6 +149,10 @@
|
|||||||
"en": "Add a song to library",
|
"en": "Add a song to library",
|
||||||
"fr": "Ajoute une musique à votre librarie"
|
"fr": "Ajoute une musique à votre librarie"
|
||||||
},
|
},
|
||||||
|
=======
|
||||||
|
"name": "Spotify_AddTrackToLibrary",
|
||||||
|
"description": "Add a track to library",
|
||||||
|
>>>>>>> 26566154676c8e279c3615522129e61b851c75a9
|
||||||
"params": [
|
"params": [
|
||||||
{
|
{
|
||||||
"name": "artist",
|
"name": "artist",
|
||||||
|
|||||||
@@ -40,34 +40,34 @@ urlHandler :: Service -> Maybe String -> AppM NoContent
|
|||||||
urlHandler _ Nothing = throwError err400
|
urlHandler _ Nothing = throwError err400
|
||||||
urlHandler Anilist (Just r) = do
|
urlHandler Anilist (Just r) = do
|
||||||
clientId <- liftIO $ envAsString "ANILIST_CLIENT_ID" ""
|
clientId <- liftIO $ envAsString "ANILIST_CLIENT_ID" ""
|
||||||
backRedirect <- liftIO $ envAsString "BACK_REDIRECT_URL" ""
|
backRedirect <- liftIO $ envAsString "BACK_URL" ""
|
||||||
throwError $ err302 { errHeaders =
|
throwError $ err302 { errHeaders =
|
||||||
[("Location", B8.pack $ "https://anilist.co/api/v2/oauth/authorize?client_id=" ++ clientId ++ "&response_type=code&redirect_uri=" ++ backRedirect ++ "&state=" ++ r)] }
|
[("Location", B8.pack $ "https://anilist.co/api/v2/oauth/authorize?client_id=" ++ clientId ++ "&response_type=code&redirect_uri=" ++ backRedirect ++ "auth/redirect" ++ "&state=" ++ r)] }
|
||||||
urlHandler Discord (Just r) = do
|
urlHandler Discord (Just r) = do
|
||||||
clientId <- liftIO $ envAsString "DISCORD_CLIENT_ID" ""
|
clientId <- liftIO $ envAsString "DISCORD_CLIENT_ID" ""
|
||||||
backRedirect <- liftIO $ envAsString "BACK_REDIRECT_URL" ""
|
backRedirect <- liftIO $ envAsString "BACK_URL" ""
|
||||||
throwError $ err302 { errHeaders =
|
throwError $ err302 { errHeaders =
|
||||||
[("Location", B8.pack $ "https://discord.com/api/oauth2/authorize?response_type=code&scope=identify&client_id=" ++ clientId ++ "&response_type=code&redirect_uri=" ++ backRedirect ++ "&state=" ++ r)] }
|
[("Location", B8.pack $ "https://discord.com/api/oauth2/authorize?response_type=code&scope=identify%20guilds%20messages.read%20activities.write%20webhook.incoming&client_id=" ++ clientId ++ "&response_type=code&redirect_uri=" ++ backRedirect ++ "auth/redirect" ++ "&state=" ++ r)] }
|
||||||
urlHandler Google (Just r) = do
|
urlHandler Google (Just r) = do
|
||||||
clientId <- liftIO $ envAsString "GOOGLE_CLIENT_ID" ""
|
clientId <- liftIO $ envAsString "GOOGLE_CLIENT_ID" ""
|
||||||
backRedirect <- liftIO $ envAsString "BACK_REDIRECT_URL" ""
|
backRedirect <- liftIO $ envAsString "BACK_URL" ""
|
||||||
throwError $ err302 { errHeaders =
|
throwError $ err302 { errHeaders =
|
||||||
[("Location", B8.pack $ "https://accounts.google.com/o/oauth2/v2/auth?scope=https://www.googleapis.com/auth/youtube.force-ssl&access_type=offline&prompt=consent&include_granted_scopes=true&response_type=code&client_id=" ++ clientId ++ "&redirect_uri=" ++ backRedirect ++ "&state=" ++ r)] }
|
[("Location", B8.pack $ "https://accounts.google.com/o/oauth2/v2/auth?scope=https://www.googleapis.com/auth/youtube.force-ssl&access_type=offline&prompt=consent&include_granted_scopes=true&response_type=code&client_id=" ++ clientId ++ "&redirect_uri=" ++ backRedirect ++ "auth/redirect" ++ "&state=" ++ r)] }
|
||||||
urlHandler Twitter (Just r) = do
|
urlHandler Twitter (Just r) = do
|
||||||
clientId <- liftIO $ envAsString "TWITTER_CLIENT_ID" ""
|
clientId <- liftIO $ envAsString "TWITTER_CLIENT_ID" ""
|
||||||
backRedirect <- liftIO $ envAsString "BACK_REDIRECT_URL" ""
|
backRedirect <- liftIO $ envAsString "BACK_URL" ""
|
||||||
throwError $ err302 { errHeaders =
|
throwError $ err302 { errHeaders =
|
||||||
[("Location", B8.pack $ "https://twitter.com/i/oauth2/authorize?response_type=code&scope=like.write like.read follows.read follows.write offline.access tweet.read tweet.write&code_challenge=challenge&code_challenge_method=plain&client_id=" ++ clientId ++ "&redirect_uri=" ++ backRedirect ++ "&state=" ++ r)] }
|
[("Location", B8.pack $ "https://twitter.com/i/oauth2/authorize?response_type=code&scope=like.write like.read follows.read follows.write offline.access tweet.read tweet.write&code_challenge=challenge&code_challenge_method=plain&client_id=" ++ clientId ++ "&redirect_uri=" ++ backRedirect ++ "auth/redirect" ++ "&state=" ++ r)] }
|
||||||
urlHandler Spotify (Just r) = do
|
urlHandler Spotify (Just r) = do
|
||||||
clientId <- liftIO $ envAsString "SPOTIFY_CLIENT_ID" ""
|
clientId <- liftIO $ envAsString "SPOTIFY_CLIENT_ID" ""
|
||||||
backRedirect <- liftIO $ envAsString "BACK_REDIRECT_URL" ""
|
backRedirect <- liftIO $ envAsString "BACK_URL" ""
|
||||||
throwError $ err302 { errHeaders =
|
throwError $ err302 { errHeaders =
|
||||||
[("Location", B8.pack $ "https://accounts.spotify.com/authorize?response_type=code&scope=user-library-read&client_id=" ++ clientId ++ "&redirect_uri=" ++ backRedirect ++ "&state=" ++ r)] }
|
[("Location", B8.pack $ "https://accounts.spotify.com/authorize?response_type=code&scope=user-library-read&client_id=" ++ clientId ++ "&redirect_uri=" ++ backRedirect ++ "auth/redirect" ++ "&state=" ++ r)] }
|
||||||
urlHandler Github (Just r) = do
|
urlHandler Github (Just r) = do
|
||||||
clientId <- liftIO $ envAsString "GITHUB_CLIENT_ID" ""
|
clientId <- liftIO $ envAsString "GITHUB_CLIENT_ID" ""
|
||||||
backRedirect <- liftIO $ envAsString "BACK_REDIRECT_URL" ""
|
backRedirect <- liftIO $ envAsString "BACK_URL" ""
|
||||||
throwError $ err302 { errHeaders =
|
throwError $ err302 { errHeaders =
|
||||||
[("Location", B8.pack $ "https://github.com/login/oauth/authorize?response_type=code&scope=repo&client_id=" ++ clientId ++ "&redirect_uri=" ++ backRedirect ++ "&state=" ++ r)] }
|
[("Location", B8.pack $ "https://github.com/login/oauth/authorize?response_type=code&scope=repo&client_id=" ++ clientId ++ "&redirect_uri=" ++ backRedirect ++ "auth/redirect" ++ "&state=" ++ r)] }
|
||||||
|
|
||||||
servicesHandler :: AuthRes -> AppM [String]
|
servicesHandler :: AuthRes -> AppM [String]
|
||||||
servicesHandler (Authenticated (User uid name slug)) = do
|
servicesHandler (Authenticated (User uid name slug)) = do
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- "db"
|
- "db"
|
||||||
environment:
|
environment:
|
||||||
|
- BACK_URL=${BACK_URL}
|
||||||
- POSTGRES_USER=${POSTGRES_USER}
|
- POSTGRES_USER=${POSTGRES_USER}
|
||||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||||
- POSTGRES_HOST=${POSTGRES_HOST}
|
- POSTGRES_HOST=${POSTGRES_HOST}
|
||||||
@@ -59,7 +60,6 @@ services:
|
|||||||
- ANILIST_SECRET=${ANILIST_SECRET}
|
- ANILIST_SECRET=${ANILIST_SECRET}
|
||||||
- WORKER_API_URL=${WORKER_API_URL}
|
- WORKER_API_URL=${WORKER_API_URL}
|
||||||
- WORKER_API_KEY=${WORKER_API_KEY}
|
- WORKER_API_KEY=${WORKER_API_KEY}
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
apk:
|
apk:
|
||||||
cache:
|
cache:
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ services:
|
|||||||
- POSTGRES_PORT=${POSTGRES_PORT}
|
- POSTGRES_PORT=${POSTGRES_PORT}
|
||||||
- WORKER_API_KEY=${WORKER_API_KEY}
|
- WORKER_API_KEY=${WORKER_API_KEY}
|
||||||
- WORKER_URL=${WORKER_URL}
|
- WORKER_URL=${WORKER_URL}
|
||||||
|
- BACK_URL=${BACK_URL}
|
||||||
- DISCORD_CLIENT_ID=${DISCORD_CLIENT_ID}
|
- DISCORD_CLIENT_ID=${DISCORD_CLIENT_ID}
|
||||||
- DISCORD_SECRET=${DISCORD_SECRET}
|
- DISCORD_SECRET=${DISCORD_SECRET}
|
||||||
- GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID}
|
- GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID}
|
||||||
|
|||||||
@@ -21,5 +21,5 @@ RUN flutter gen-l10n
|
|||||||
RUN flutter pub run flutter_launcher_icons:main
|
RUN flutter pub run flutter_launcher_icons:main
|
||||||
# Generate native splashscreen
|
# Generate native splashscreen
|
||||||
RUN flutter pub run flutter_native_splash:create
|
RUN flutter pub run flutter_native_splash:create
|
||||||
RUN flutter build apk lib/src/main.dart
|
RUN flutter build apk lib/main.dart
|
||||||
CMD cp ./build/app/outputs/flutter-apk/app-release.apk /dist/aeris_android.apk
|
CMD cp ./build/app/outputs/flutter-apk/app-release.apk /dist/aeris_android.apk
|
||||||
|
|||||||
@@ -1,7 +1,45 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.aeris.mobile">
|
package="com.aeris.mobile">
|
||||||
<!-- Flutter needs it to communicate with the running application
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
to allow setting breakpoints, to provide hot reload, etc.
|
<application
|
||||||
-->
|
android:label="Aeris"
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
android:name="${applicationName}"
|
||||||
|
android:icon="@mipmap/ic_launcher">
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:theme="@style/LaunchTheme"
|
||||||
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
|
android:hardwareAccelerated="true"
|
||||||
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
|
the Android process has started. This theme is visible to the user
|
||||||
|
while the Flutter UI initializes. After that, this theme continues
|
||||||
|
to determine the Window background behind the Flutter UI. -->
|
||||||
|
<meta-data
|
||||||
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
|
android:resource="@style/NormalTheme"
|
||||||
|
/>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
</intent-filter>
|
||||||
|
<!-- Deep linking -->
|
||||||
|
<meta-data android:name="flutter_deeplinking_enabled" android:value="true" />
|
||||||
|
<intent-filter android:autoVerify="true">
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="http" android:host="arthichaud.me" />
|
||||||
|
<data android:scheme="https" android:host="arthichaud.me"/>
|
||||||
|
<data android:scheme="aeris" android:host="arthichaud.me"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<!-- Don't delete the meta-data below.
|
||||||
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
|
<meta-data
|
||||||
|
android:name="flutterEmbedding"
|
||||||
|
android:value="2" />
|
||||||
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -25,6 +25,16 @@
|
|||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<!-- Deep linking -->
|
||||||
|
<meta-data android:name="flutter_deeplinking_enabled" android:value="true" />
|
||||||
|
<intent-filter android:autoVerify="true">
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="http" android:host="arthichaud.me" />
|
||||||
|
<data android:scheme="https" android:host="arthichaud.me"/>
|
||||||
|
<data android:scheme="aeris" android:host="arthichaud.me"/>
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<!-- Don't delete the meta-data below.
|
<!-- Don't delete the meta-data below.
|
||||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
PODS:
|
PODS:
|
||||||
- Flutter (1.0.0)
|
- Flutter (1.0.0)
|
||||||
|
- flutter_keyboard_visibility (0.0.1):
|
||||||
|
- Flutter
|
||||||
- FMDB (2.7.5):
|
- FMDB (2.7.5):
|
||||||
- FMDB/standard (= 2.7.5)
|
- FMDB/standard (= 2.7.5)
|
||||||
- FMDB/standard (2.7.5)
|
- FMDB/standard (2.7.5)
|
||||||
- path_provider_ios (0.0.1):
|
- path_provider_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- shared_preferences_ios (0.0.1):
|
||||||
|
- Flutter
|
||||||
- sqflite (0.0.2):
|
- sqflite (0.0.2):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FMDB (>= 2.7.5)
|
- FMDB (>= 2.7.5)
|
||||||
@@ -13,7 +17,9 @@ PODS:
|
|||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
|
- flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`)
|
||||||
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
|
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
|
||||||
|
- shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`)
|
||||||
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
||||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
|
|
||||||
@@ -24,8 +30,12 @@ SPEC REPOS:
|
|||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
Flutter:
|
Flutter:
|
||||||
:path: Flutter
|
:path: Flutter
|
||||||
|
flutter_keyboard_visibility:
|
||||||
|
:path: ".symlinks/plugins/flutter_keyboard_visibility/ios"
|
||||||
path_provider_ios:
|
path_provider_ios:
|
||||||
:path: ".symlinks/plugins/path_provider_ios/ios"
|
:path: ".symlinks/plugins/path_provider_ios/ios"
|
||||||
|
shared_preferences_ios:
|
||||||
|
:path: ".symlinks/plugins/shared_preferences_ios/ios"
|
||||||
sqflite:
|
sqflite:
|
||||||
:path: ".symlinks/plugins/sqflite/ios"
|
:path: ".symlinks/plugins/sqflite/ios"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
@@ -33,8 +43,10 @@ EXTERNAL SOURCES:
|
|||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
|
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
|
||||||
|
flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069
|
||||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||||
path_provider_ios: 7d7ce634493af4477d156294792024ec3485acd5
|
path_provider_ios: 7d7ce634493af4477d156294792024ec3485acd5
|
||||||
|
shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad
|
||||||
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
|
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
|
||||||
url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de
|
url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
0B9CFEF16F77B4F2467EF56A /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
0B9CFEF16F77B4F2467EF56A /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
0C6C3D7227D0D7C100B12C20 /* RunnerDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerDebug.entitlements; sourceTree = "<group>"; };
|
||||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||||
@@ -99,6 +100,7 @@
|
|||||||
97C146F01CF9000F007C117D /* Runner */ = {
|
97C146F01CF9000F007C117D /* Runner */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
0C6C3D7227D0D7C100B12C20 /* RunnerDebug.entitlements */,
|
||||||
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||||
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||||
@@ -217,7 +219,7 @@
|
|||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n";
|
||||||
};
|
};
|
||||||
755B6DB6A94E09EAD68F291E /* [CP] Embed Pods Frameworks */ = {
|
755B6DB6A94E09EAD68F291E /* [CP] Embed Pods Frameworks */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
@@ -248,7 +250,7 @@
|
|||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n";
|
||||||
};
|
};
|
||||||
/* End PBXShellScriptBuildPhase section */
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
@@ -476,6 +478,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
DEVELOPMENT_TEAM = HJ45QP4WWR;
|
DEVELOPMENT_TEAM = HJ45QP4WWR;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"filename" : "icon.jpg",
|
"filename" : "BrandingImage.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "icon-1.jpg",
|
"filename" : "BrandingImage@2x.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "icon-2.jpg",
|
"filename" : "BrandingImage@3x.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "3x"
|
"scale" : "3x"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,5 +47,20 @@
|
|||||||
</array>
|
</array>
|
||||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||||
<false/>
|
<false/>
|
||||||
|
<key>FlutterDeepLinkingEnabled</key>
|
||||||
|
<true/>
|
||||||
|
<key>CFBundleURLTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
<key>CFBundleURLName</key>
|
||||||
|
<string>arthichaud.me</string>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>aeris</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -45,5 +45,20 @@
|
|||||||
<false/>
|
<false/>
|
||||||
<key>UIStatusBarHidden</key>
|
<key>UIStatusBarHidden</key>
|
||||||
<false/>
|
<false/>
|
||||||
|
<key>FlutterDeepLinkingEnabled</key>
|
||||||
|
<true/>
|
||||||
|
<key>CFBundleURLTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
<key>CFBundleURLName</key>
|
||||||
|
<string>arthichaud.me</string>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>aeris</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
8
mobile/ios/Runner/RunnerDebug.entitlements
Normal file
8
mobile/ios/Runner/RunnerDebug.entitlements
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.developer.default-data-protection</key>
|
||||||
|
<string>NSFileProtectionComplete</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -36,5 +36,12 @@
|
|||||||
"pipelineFormMisingAction": "You must select at least a trigger and a reaction",
|
"pipelineFormMisingAction": "You must select at least a trigger and a reaction",
|
||||||
"logoutWarningMessage": "You are about to logout, are you sure?",
|
"logoutWarningMessage": "You are about to logout, are you sure?",
|
||||||
"warning": "Warning",
|
"warning": "Warning",
|
||||||
"cancel": "Cancel"
|
"cancel": "Cancel",
|
||||||
|
"errorOnSignup": "An error occured while signing you up, please try again",
|
||||||
|
"loading": "Loading...",
|
||||||
|
"invalidUrl": "Invalid URL",
|
||||||
|
"tryToConnect": "Try to connect",
|
||||||
|
"routeToApi": "Route to API",
|
||||||
|
"setupAPIRoute": "Setup API Route",
|
||||||
|
"paramInheritTip": "To inherit parameters from previous actions, type '{' in the text field and tap on the choosen parameter"
|
||||||
}
|
}
|
||||||
@@ -6,8 +6,8 @@
|
|||||||
"today": "Aujourd'hui",
|
"today": "Aujourd'hui",
|
||||||
"nameOfThePipeline": "Nom de la pipeline",
|
"nameOfThePipeline": "Nom de la pipeline",
|
||||||
"addReaction": "Ajouter une Reaction",
|
"addReaction": "Ajouter une Reaction",
|
||||||
"addTrigger": "Ajouter un Déclancheur",
|
"addTrigger": "Ajouter un déclencheur",
|
||||||
"setupTrigger": "Gérer une Déclancheur",
|
"setupTrigger": "Gérer un déclencheur",
|
||||||
"setupReaction": "Gérer une Réaction",
|
"setupReaction": "Gérer une Réaction",
|
||||||
"action": "Action",
|
"action": "Action",
|
||||||
"reactions": "Réactions",
|
"reactions": "Réactions",
|
||||||
@@ -33,8 +33,15 @@
|
|||||||
"dangerZone": "Zone dangereuse",
|
"dangerZone": "Zone dangereuse",
|
||||||
"createNewPipeline": "Créer une nouvelle pipeline",
|
"createNewPipeline": "Créer une nouvelle pipeline",
|
||||||
"save": "Enregistrer",
|
"save": "Enregistrer",
|
||||||
"pipelineFormMisingAction": "Vous devez selectionner au moins un déclancheur et une réaction",
|
"pipelineFormMisingAction": "Vous devez selectionner au moins un déclencheur et une réaction",
|
||||||
"logoutWarningMessage": "Êtes-vous sûr(e) de voulour vous déconnecter d'Aeris?",
|
"logoutWarningMessage": "Êtes-vous sûr(e) de voulour vous déconnecter d'Aeris?",
|
||||||
"warning": "Attention",
|
"warning": "Attention",
|
||||||
"cancel": "Annuler"
|
"cancel": "Annuler",
|
||||||
|
"errorOnSignup": "Une erreur est survenue, veuillez réessayer",
|
||||||
|
"loading": "Chargement...",
|
||||||
|
"invalidUrl": "URL invalide",
|
||||||
|
"tryToConnect": "Tester la connection",
|
||||||
|
"routeToApi": "Route de l'API",
|
||||||
|
"setupAPIRoute": "Choisir la route de l'API",
|
||||||
|
"paramInheritTip": "Afin d'hériter de variables venant d'actions précedentes, entrez '{' dans un champ et choisissez la valeur"
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
import 'package:aeris/src/aeris_api.dart';
|
import 'package:aeris/src/aeris_api.dart';
|
||||||
|
import 'package:aeris/src/providers/action_catalogue_provider.dart';
|
||||||
|
import 'package:aeris/src/views/authorization_page.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:form_builder_validators/localization/l10n.dart';
|
import 'package:form_builder_validators/localization/l10n.dart';
|
||||||
import 'package:aeris/src/providers/pipelines_provider.dart';
|
import 'package:aeris/src/providers/pipelines_provider.dart';
|
||||||
import 'package:aeris/src/providers/user_services_provider.dart';
|
import 'package:aeris/src/providers/services_provider.dart';
|
||||||
import 'package:aeris/src/views/startup_page.dart';
|
import 'package:aeris/src/views/startup_page.dart';
|
||||||
import 'package:aeris/src/views/login_page.dart';
|
import 'package:aeris/src/views/login_page.dart';
|
||||||
import 'package:aeris/src/views/home_page.dart';
|
import 'package:aeris/src/views/home_page.dart';
|
||||||
@@ -11,13 +13,19 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
void main() {
|
void main() async {
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
GetIt.I.registerSingleton<SharedPreferences>(prefs);
|
||||||
AerisAPI interface = AerisAPI();
|
AerisAPI interface = AerisAPI();
|
||||||
GetIt.I.registerSingleton<AerisAPI>(interface);
|
GetIt.I.registerSingleton<AerisAPI>(interface);
|
||||||
|
await interface.restoreConnection();
|
||||||
runApp(MultiProvider(providers: [
|
runApp(MultiProvider(providers: [
|
||||||
ChangeNotifierProvider(create: (_) => PipelineProvider()),
|
ChangeNotifierProvider(create: (_) => PipelineProvider()),
|
||||||
ChangeNotifierProvider(create: (_) => UserServiceProvider())
|
ChangeNotifierProvider(create: (_) => ServiceProvider()),
|
||||||
|
ChangeNotifierProvider(create: (_) => ActionCatalogueProvider(), lazy: false)
|
||||||
], child: const Aeris()));
|
], child: const Aeris()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,22 +56,23 @@ class Aeris extends StatelessWidget {
|
|||||||
'/login': () => const LoginPage(),
|
'/login': () => const LoginPage(),
|
||||||
'/home': () => const HomePage(),
|
'/home': () => const HomePage(),
|
||||||
};
|
};
|
||||||
|
|
||||||
return PageRouteBuilder(
|
return PageRouteBuilder(
|
||||||
opaque: false,
|
opaque: false,
|
||||||
settings: settings,
|
settings: settings,
|
||||||
pageBuilder: (_, __, ___) => routes[settings.name].call(),
|
pageBuilder: (_, __, ___) {
|
||||||
|
if (settings.name!.startsWith('/authorization')) {
|
||||||
|
return const AuthorizationPage();
|
||||||
|
}
|
||||||
|
return routes[settings.name].call();
|
||||||
|
},
|
||||||
transitionDuration: const Duration(milliseconds: 350),
|
transitionDuration: const Duration(milliseconds: 350),
|
||||||
transitionsBuilder: (context, animation, secondaryAnimation, child) =>
|
transitionsBuilder: (context, animation, secondaryAnimation,
|
||||||
SlideTransition(
|
child) =>
|
||||||
child: child,
|
SlideTransition(
|
||||||
position: animation.drive(
|
child: child,
|
||||||
Tween(
|
position: animation.drive(Tween(
|
||||||
begin: const Offset(1.0, 0.0),
|
begin: const Offset(1.0, 0.0), end: Offset.zero))));
|
||||||
end: Offset.zero
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,127 +1,249 @@
|
|||||||
|
// ignore_for_file: unused_import
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:aeris/src/models/action.dart';
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:aeris/main.dart';
|
||||||
|
import 'package:aeris/src/models/action.dart' as aeris;
|
||||||
|
import 'package:aeris/src/models/action_parameter.dart';
|
||||||
import 'package:aeris/src/models/action_template.dart';
|
import 'package:aeris/src/models/action_template.dart';
|
||||||
import 'package:aeris/src/models/pipeline.dart';
|
import 'package:aeris/src/models/pipeline.dart';
|
||||||
import 'package:aeris/src/models/reaction.dart';
|
import 'package:aeris/src/models/reaction.dart';
|
||||||
import 'package:aeris/src/models/service.dart';
|
import 'package:aeris/src/models/service.dart';
|
||||||
import 'package:aeris/src/models/trigger.dart';
|
import 'package:aeris/src/models/trigger.dart';
|
||||||
|
import 'package:aeris/src/providers/action_catalogue_provider.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get_it/get_it.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
extension IsOk on http.Response {
|
||||||
|
bool get ok => (statusCode ~/ 100) == 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Requests types supported by Aeris API
|
||||||
|
enum AerisAPIRequestType { get, post, put, delete }
|
||||||
|
|
||||||
/// Call to interact with Aeris' Back end
|
/// Call to interact with Aeris' Back end
|
||||||
class AerisAPI {
|
class AerisAPI {
|
||||||
///TODO set status based on stored credentials
|
/// Get Connection state
|
||||||
bool connected = true;
|
bool _connected = false;
|
||||||
late List<Pipeline> fakeAPI;
|
bool get isConnected => _connected;
|
||||||
|
|
||||||
|
/// JWT token used to request API
|
||||||
|
late String _jwt;
|
||||||
|
|
||||||
|
late final String deepLinkRoute;
|
||||||
|
|
||||||
|
String _baseRoute =
|
||||||
|
GetIt.I<SharedPreferences>().getString('api') ?? "http://localhost:8080";
|
||||||
|
String get baseRoute => _baseRoute;
|
||||||
|
set baseRoute(value) => _baseRoute = value;
|
||||||
|
|
||||||
AerisAPI() {
|
AerisAPI() {
|
||||||
var trigger1 = Trigger(
|
var scheme = "http";
|
||||||
service: const Service.spotify(),
|
if (Platform.isIOS) {
|
||||||
name: "Play song",
|
scheme = "aeris";
|
||||||
last: DateTime.now());
|
}
|
||||||
var trigger3 = Trigger(
|
deepLinkRoute = "$scheme://arthichaud.me";
|
||||||
service: const Service.discord(),
|
|
||||||
name: "Send a message",
|
|
||||||
last: DateTime.now());
|
|
||||||
var trigger2 = Trigger(
|
|
||||||
service: const Service.spotify(),
|
|
||||||
name: "Play song",
|
|
||||||
last: DateTime.parse("2022-01-01"));
|
|
||||||
var reaction = Reaction(
|
|
||||||
service: const Service.twitter(), parameters: {}, name: "Post a tweet");
|
|
||||||
var reaction2 = Reaction(
|
|
||||||
service: const Service.gmail(), parameters: {}, name: "Do smth");
|
|
||||||
var reaction1 = Reaction(
|
|
||||||
service: const Service.youtube(), parameters: {}, name: "Do smth youtube");
|
|
||||||
var pipeline1 = Pipeline(
|
|
||||||
id: 10,
|
|
||||||
name: "My Action",
|
|
||||||
triggerCount: 1,
|
|
||||||
enabled: true,
|
|
||||||
trigger: trigger1,
|
|
||||||
reactions: [reaction]);
|
|
||||||
var pipeline2 = Pipeline(
|
|
||||||
id: 10,
|
|
||||||
name: "My very long action Action",
|
|
||||||
triggerCount: 10,
|
|
||||||
enabled: true,
|
|
||||||
trigger: trigger2,
|
|
||||||
reactions: [reaction, reaction1, reaction2]);
|
|
||||||
var pipeline3 = Pipeline(
|
|
||||||
id: 10,
|
|
||||||
name: "Disabled",
|
|
||||||
triggerCount: 3,
|
|
||||||
enabled: false,
|
|
||||||
trigger: trigger3,
|
|
||||||
reactions: [reaction]);
|
|
||||||
fakeAPI = [
|
|
||||||
pipeline3,
|
|
||||||
pipeline2,
|
|
||||||
pipeline1,
|
|
||||||
pipeline3,
|
|
||||||
pipeline2,
|
|
||||||
pipeline1,
|
|
||||||
pipeline3,
|
|
||||||
pipeline2,
|
|
||||||
pipeline1
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds new pipeline to API
|
/// Name of the file that contains the JWT used for Aeris' API requestd
|
||||||
Future<void> createPipeline(Pipeline newPipeline) async {
|
static const String jwtFile = 'aeris_jwt.txt';
|
||||||
///TODO Send Pipeline to API
|
|
||||||
fakeAPI.add(newPipeline);
|
///ROUTES
|
||||||
await Future.delayed(const Duration(seconds: 2));
|
/// Registers new user in the database and connects it. Returns false if register failed
|
||||||
return;
|
Future<bool> signUpUser(String username, String password) async {
|
||||||
|
http.Response response =
|
||||||
|
await _requestAPI('/auth/signup', AerisAPIRequestType.post, {
|
||||||
|
'username': username,
|
||||||
|
'password': password,
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return createConnection(username, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// On success, sets API as connected to given user. Returns false if connection false
|
||||||
|
Future<bool> createConnection(String username, String password) async {
|
||||||
|
http.Response response =
|
||||||
|
await _requestAPI('/auth/login', AerisAPIRequestType.post, {
|
||||||
|
'username': username,
|
||||||
|
'password': password,
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final String jwt = jsonDecode(response.body)['jwt'];
|
||||||
|
await GetIt.I<SharedPreferences>().setString('jwt', jwt);
|
||||||
|
_connected = true;
|
||||||
|
_jwt = jwt;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create an API connection using previously created credentials
|
||||||
|
Future<void> restoreConnection() async {
|
||||||
|
try {
|
||||||
|
final cred = GetIt.I<SharedPreferences>().getString('jwt');
|
||||||
|
if (cred == "" || cred == null) {
|
||||||
|
throw Exception("Empty creds");
|
||||||
|
}
|
||||||
|
_jwt = cred;
|
||||||
|
_connected = true;
|
||||||
|
} catch (e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete JWT file and disconnect from API
|
||||||
|
Future<void> stopConnection() async {
|
||||||
|
await GetIt.I<SharedPreferences>().remove('jwt');
|
||||||
|
_connected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
///Get /about.json
|
||||||
|
Future<Map<String, dynamic>> getAbout() async {
|
||||||
|
var res = await _requestAPI('/about.json', AerisAPIRequestType.get, null);
|
||||||
|
if (!res.ok) return {};
|
||||||
|
return jsonDecode(res.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds new pipeline to API, returns false if post failed
|
||||||
|
Future<bool> createPipeline(Pipeline newPipeline) async {
|
||||||
|
var res = await _requestAPI(
|
||||||
|
'/workflow', AerisAPIRequestType.post, newPipeline.toJSON());
|
||||||
|
newPipeline.id = Pipeline.fromJSON(jsonDecode(res.body)).id;
|
||||||
|
return res.ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes pipeline from API
|
/// Removes pipeline from API
|
||||||
Future<void> removePipeline(Pipeline pipeline) async {
|
Future<bool> removePipeline(Pipeline pipeline) async {
|
||||||
///TODO Send delete request to API
|
var res = await _requestAPI(
|
||||||
fakeAPI.remove(pipeline);
|
'/workflow/${pipeline.id}', AerisAPIRequestType.delete, null);
|
||||||
await Future.delayed(const Duration(seconds: 2));
|
return res.ok;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> editPipeline(Pipeline updatedPipeline) async {
|
String getServiceAuthURL(Service service) {
|
||||||
///TODO Send update request to API
|
final serviceName = service == const Service.youtube()
|
||||||
for (var pipeline in fakeAPI) {
|
? "google"
|
||||||
if (pipeline.id == updatedPipeline.id) {
|
: service.name.toLowerCase();
|
||||||
///TODO Call Api
|
return "$baseRoute/auth/$serviceName/url?redirect_uri=$deepLinkRoute/authorization/$serviceName";
|
||||||
break;
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await Future.delayed(const Duration(seconds: 2));
|
/// Send PUT request to update Pipeline, returns false if failed
|
||||||
return;
|
Future<bool> editPipeline(Pipeline updatedPipeline) async {
|
||||||
|
var res = await _requestAPI('/workflow/${updatedPipeline.id}',
|
||||||
|
AerisAPIRequestType.put, updatedPipeline.toJSON());
|
||||||
|
return res.ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetches the Pipelines from the API
|
/// Fetches the Pipelines from the API
|
||||||
Future<List<Pipeline>> getPipelines() async {
|
Future<List<Pipeline>> getPipelines() async {
|
||||||
/// TODO Fetch the API
|
var res = await _requestAPI('/workflows', AerisAPIRequestType.get, null);
|
||||||
await Future.delayed(const Duration(seconds: 2));
|
if (res.ok == false) return [];
|
||||||
return fakeAPI;
|
final List body = jsonDecode(res.body);
|
||||||
|
|
||||||
|
return body.map((e) => Pipeline.fromJSON(Map.from(e))).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch the services the user is authenticated to
|
||||||
|
Future<List<Service>> getConnectedService() async {
|
||||||
|
var res =
|
||||||
|
await _requestAPI('/auth/services', AerisAPIRequestType.get, null);
|
||||||
|
if (!res.ok) return [];
|
||||||
|
return (jsonDecode(res.body) as List)
|
||||||
|
.map((e) => Service.factory(e.toString()))
|
||||||
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Disconnects the user from the service
|
/// Disconnects the user from the service
|
||||||
Future<void> disconnectService(Service service) async {
|
Future<bool> disconnectService(Service service) async {
|
||||||
///TODO disconnect service from user
|
var res = await _requestAPI('/auth/${service.name.toLowerCase()}',
|
||||||
await Future.delayed(const Duration(seconds: 2));
|
AerisAPIRequestType.delete, null);
|
||||||
return;
|
return res.ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<ActionTemplate>> getActionsFor(
|
/// Connects the user from the service
|
||||||
Service service, Action action) async {
|
Future<bool> connectService(Service service, String code) async {
|
||||||
await Future.delayed(const Duration(seconds: 3));
|
var res = await _requestAPI(
|
||||||
|
'/auth/${service.name.toLowerCase()}?code=$code&redirect_uri=$deepLinkRoute/authorization/${service.name.toLowerCase()}',
|
||||||
|
AerisAPIRequestType.get,
|
||||||
|
null);
|
||||||
|
return res.ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ActionTemplate> getActionsFor(Service service, aeris.Action action) {
|
||||||
|
final catalogue =
|
||||||
|
Aeris.materialKey.currentContext?.read<ActionCatalogueProvider>();
|
||||||
if (action is Trigger) {
|
if (action is Trigger) {
|
||||||
///TODO get triggers
|
return catalogue!.triggerTemplates[service]!;
|
||||||
} else if (action is Reaction) {
|
}
|
||||||
///TODO get reactions
|
return catalogue!.reactionTemplates[service]!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encodes Uri for request
|
||||||
|
Uri _encoreUri(String route) {
|
||||||
|
return Uri.parse('$_baseRoute$route');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calls API using a HTTP request type, a route and body
|
||||||
|
Future<http.Response> _requestAPI(
|
||||||
|
String route, AerisAPIRequestType requestType, Object? body) async {
|
||||||
|
final Map<String, String> header = {
|
||||||
|
'Content-type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
};
|
||||||
|
if (_connected) {
|
||||||
|
header.addAll({'Authorization': 'Bearer $_jwt'});
|
||||||
|
}
|
||||||
|
const duration = Duration(seconds: 3);
|
||||||
|
try {
|
||||||
|
switch (requestType) {
|
||||||
|
case AerisAPIRequestType.delete:
|
||||||
|
return await http
|
||||||
|
.delete(_encoreUri(route),
|
||||||
|
body: jsonEncode(body), headers: header)
|
||||||
|
.timeout(
|
||||||
|
duration,
|
||||||
|
onTimeout: () {
|
||||||
|
return http.Response('Error', 408);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
case AerisAPIRequestType.get:
|
||||||
|
return await http.get(_encoreUri(route), headers: header).timeout(
|
||||||
|
duration,
|
||||||
|
onTimeout: () {
|
||||||
|
return http.Response('Error', 408);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
case AerisAPIRequestType.post:
|
||||||
|
return await http
|
||||||
|
.post(_encoreUri(route), body: jsonEncode(body), headers: header)
|
||||||
|
.timeout(
|
||||||
|
duration,
|
||||||
|
onTimeout: () {
|
||||||
|
return http.Response('Error', 408);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
case AerisAPIRequestType.put:
|
||||||
|
return await http
|
||||||
|
.put(_encoreUri(route), body: jsonEncode(body), headers: header)
|
||||||
|
.timeout(
|
||||||
|
duration,
|
||||||
|
onTimeout: () {
|
||||||
|
return http.Response('Error', 408);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return http.Response('{}', 400);
|
||||||
}
|
}
|
||||||
return [
|
|
||||||
for (int i = 0; i <= 10; i++)
|
|
||||||
ActionTemplate(
|
|
||||||
service: service,
|
|
||||||
name: "action$i",
|
|
||||||
parameters: {'key1': 'value1', 'key2': 'value2'})
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import 'package:aeris/src/models/action_parameter.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:aeris/src/models/service.dart';
|
import 'package:aeris/src/models/service.dart';
|
||||||
|
import 'package:recase/recase.dart';
|
||||||
|
|
||||||
///Base class for reactions and trigger
|
///Base class for reactions and trigger
|
||||||
abstract class Action {
|
abstract class Action {
|
||||||
@@ -10,10 +12,27 @@ abstract class Action {
|
|||||||
String name;
|
String name;
|
||||||
|
|
||||||
///Action's parameters
|
///Action's parameters
|
||||||
Map<String, Object> parameters;
|
List<ActionParameter> parameters;
|
||||||
|
|
||||||
|
/// Description of the action (used in catalogue)
|
||||||
|
String? description;
|
||||||
|
|
||||||
Action(
|
Action(
|
||||||
{Key? key,
|
{Key? key,
|
||||||
required this.service,
|
required this.service,
|
||||||
required this.name,
|
required this.name,
|
||||||
this.parameters = const {}});
|
this.description,
|
||||||
|
this.parameters = const []});
|
||||||
|
|
||||||
|
static Service parseServiceInName(String rType) {
|
||||||
|
var snake = rType.split('_');
|
||||||
|
var service = snake.removeAt(0);
|
||||||
|
return Service.factory(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
String displayName() {
|
||||||
|
var words = name.split('_');
|
||||||
|
words.removeAt(0);
|
||||||
|
return ReCase(words.join()).titleCase;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
25
mobile/lib/src/models/action_parameter.dart
Normal file
25
mobile/lib/src/models/action_parameter.dart
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Object representation of an action's parameter
|
||||||
|
class ActionParameter {
|
||||||
|
/// Name of the action parameter
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
/// Description of theparameter
|
||||||
|
final String description;
|
||||||
|
|
||||||
|
/// Value of the pamrameter
|
||||||
|
Object? value;
|
||||||
|
|
||||||
|
ActionParameter(
|
||||||
|
{Key? key, required this.name, this.description = "", this.value});
|
||||||
|
|
||||||
|
MapEntry<String, dynamic> toJson() => MapEntry(name, value);
|
||||||
|
|
||||||
|
static List<ActionParameter> fromJSON(Map<String, dynamic> params) {
|
||||||
|
List<ActionParameter> actionParameters = [];
|
||||||
|
params.forEach((key, value) =>
|
||||||
|
actionParameters.add(ActionParameter(name: key, value: value)));
|
||||||
|
return actionParameters;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,20 @@
|
|||||||
import 'package:aeris/src/models/action.dart';
|
import 'package:aeris/src/models/action.dart';
|
||||||
|
import 'package:aeris/src/models/action_parameter.dart';
|
||||||
import 'package:aeris/src/models/service.dart';
|
import 'package:aeris/src/models/service.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
/// Template for actions, for forms
|
/// Template for actions, for forms
|
||||||
class ActionTemplate extends Action {
|
class ActionTemplate extends Action {
|
||||||
|
|
||||||
|
///List of values returned by the action
|
||||||
|
final List<ActionParameter> returnedValues;
|
||||||
|
|
||||||
ActionTemplate(
|
ActionTemplate(
|
||||||
{Key? key,
|
{Key? key,
|
||||||
required Service service,
|
required Service service,
|
||||||
required String name,
|
required String name,
|
||||||
Map<String, Object> parameters = const {}})
|
required String description,
|
||||||
: super(service: service, name: name, parameters: parameters);
|
this.returnedValues = const [],
|
||||||
|
List<ActionParameter> parameters = const []})
|
||||||
|
: super(service: service, name: name, parameters: parameters, description: description);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import 'package:aeris/src/models/trigger.dart';
|
|||||||
/// Object representation of a pipeline
|
/// Object representation of a pipeline
|
||||||
class Pipeline {
|
class Pipeline {
|
||||||
///Unique identifier
|
///Unique identifier
|
||||||
final int id;
|
int id;
|
||||||
|
|
||||||
/// Name of the pipeline, defined by the user
|
/// Name of the pipeline, defined by the user
|
||||||
final String name;
|
final String name;
|
||||||
@@ -16,7 +16,6 @@ class Pipeline {
|
|||||||
/// Is the pipeline enabled
|
/// Is the pipeline enabled
|
||||||
bool enabled;
|
bool enabled;
|
||||||
|
|
||||||
|
|
||||||
///The pipeline's reactions
|
///The pipeline's reactions
|
||||||
final List<Reaction> reactions;
|
final List<Reaction> reactions;
|
||||||
|
|
||||||
@@ -29,4 +28,34 @@ class Pipeline {
|
|||||||
required this.enabled,
|
required this.enabled,
|
||||||
required this.trigger,
|
required this.trigger,
|
||||||
required this.reactions});
|
required this.reactions});
|
||||||
|
|
||||||
|
/// Unserialize Pipeline from JSON
|
||||||
|
static Pipeline fromJSON(Map<String, dynamic> data) {
|
||||||
|
var action = data['action'] as Map<String, dynamic>;
|
||||||
|
var reactions = data['reactions'] as List<dynamic>;
|
||||||
|
|
||||||
|
return Pipeline(
|
||||||
|
name: action['name'] as String,
|
||||||
|
enabled: action['enabled'] as bool,
|
||||||
|
id: action['id'] as int,
|
||||||
|
triggerCount: action['triggerCount'] as int,
|
||||||
|
trigger: Trigger.fromJSON(action),
|
||||||
|
reactions: reactions
|
||||||
|
.map<Reaction>((e) => Reaction.fromJSON(e))
|
||||||
|
.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize Pipeline into JSON
|
||||||
|
Object toJSON() => {
|
||||||
|
"action": {
|
||||||
|
"id": id,
|
||||||
|
"name": name,
|
||||||
|
"pType": trigger.name,
|
||||||
|
"pParams": { for (var e in trigger.parameters) e.name : e.value }, ///Serialize
|
||||||
|
"enabled": enabled,
|
||||||
|
"lastTrigger": trigger.last?.toIso8601String(),
|
||||||
|
"triggerCount": triggerCount
|
||||||
|
},
|
||||||
|
'reactions': reactions.map((e) => e.toJSON()).toList()
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// ignore_for_file: hash_and_equals
|
// ignore_for_file: hash_and_equals
|
||||||
|
|
||||||
import 'package:aeris/src/models/action.dart' as aeris_action;
|
import 'package:aeris/src/models/action.dart' as aeris_action;
|
||||||
|
import 'package:aeris/src/models/action_parameter.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:aeris/src/models/service.dart';
|
import 'package:aeris/src/models/service.dart';
|
||||||
|
|
||||||
@@ -10,20 +11,33 @@ class Reaction extends aeris_action.Action {
|
|||||||
{Key? key,
|
{Key? key,
|
||||||
required Service service,
|
required Service service,
|
||||||
required String name,
|
required String name,
|
||||||
Map<String, Object> parameters = const {}})
|
List<ActionParameter> parameters = const []})
|
||||||
: super(service: service, name: name, parameters: parameters);
|
: super(service: service, name: name, parameters: parameters);
|
||||||
|
|
||||||
/// Template trigger, used as an 'empty' trigger
|
/// Template trigger, used as an 'empty' trigger
|
||||||
Reaction.template()
|
Reaction.template()
|
||||||
: super(service: Service.all()[0], name: '', parameters: {});
|
: super(service: Service.all()[0], name: '', parameters: []);
|
||||||
|
|
||||||
|
static Reaction fromJSON(Object reaction) {
|
||||||
|
var reactionJSON = reaction as Map<String, dynamic>;
|
||||||
|
var service = aeris_action.Action.parseServiceInName(reactionJSON['rType'] as String);
|
||||||
|
return Reaction(
|
||||||
|
service: service,
|
||||||
|
name: reactionJSON['rType'] as String,
|
||||||
|
parameters: ActionParameter.fromJSON((reactionJSON['rParams'] as Map<String, dynamic>)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize Reaction to JSON
|
||||||
|
Object toJSON() => {
|
||||||
|
"rType": name,
|
||||||
|
"rParams": { for (var e in parameters) e.name : e.value }
|
||||||
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
Reaction otherReaction = other as Reaction;
|
Reaction otherReaction = other as Reaction;
|
||||||
return service.name == otherReaction.service.name &&
|
return service.name == otherReaction.service.name &&
|
||||||
name == otherReaction.name &&
|
name == otherReaction.name &&
|
||||||
parameters.values.toString() ==
|
parameters.map((e) => e.name).toString() == other.parameters.map((e) => e.name).toString();
|
||||||
otherReaction.parameters.values.toString() &&
|
|
||||||
parameters.keys.toString() == otherReaction.parameters.keys.toString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
// Class for a service (Youtube, Gmail, ...)
|
// Class for a service (Youtube, Gmail, ...)
|
||||||
|
import 'package:aeris/src/aeris_api.dart';
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'dart:core';
|
||||||
|
|
||||||
|
import 'package:get_it/get_it.dart';
|
||||||
|
|
||||||
/// Data class used to store data about a service (logo, url, name)
|
/// Data class used to store data about a service (logo, url, name)
|
||||||
class Service {
|
class Service {
|
||||||
@@ -22,16 +26,19 @@ class Service {
|
|||||||
height: logoSize,
|
height: logoSize,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
/// Get full url for OAuth2
|
||||||
|
String get authUrl => GetIt.I<AerisAPI>().getServiceAuthURL(this);
|
||||||
|
|
||||||
const Service.spotify()
|
const Service.spotify()
|
||||||
: name = "Spotify",
|
: name = "Spotify",
|
||||||
url = "https://www.spotify.com",
|
url = "https://www.spotify.com",
|
||||||
logoUrl =
|
logoUrl =
|
||||||
"https://www.presse-citron.net/app/uploads/2020/06/spotify-une-.jpg";
|
"https://www.presse-citron.net/app/uploads/2020/06/spotify-une-.jpg";
|
||||||
const Service.gmail()
|
const Service.anilist()
|
||||||
: name = "Gmail",
|
: name = "Anilist",
|
||||||
url = "https://mail.google.com/",
|
url = "https://anilist.co",
|
||||||
logoUrl =
|
logoUrl =
|
||||||
"https://play-lh.googleusercontent.com/KSuaRLiI_FlDP8cM4MzJ23ml3og5Hxb9AapaGTMZ2GgR103mvJ3AAnoOFz1yheeQBBI";
|
"https://anilist.co/img/icons/android-chrome-512x512.png";
|
||||||
const Service.discord()
|
const Service.discord()
|
||||||
: name = "Discord",
|
: name = "Discord",
|
||||||
url = "https://discord.com/app",
|
url = "https://discord.com/app",
|
||||||
@@ -43,7 +50,7 @@ class Service {
|
|||||||
logoUrl =
|
logoUrl =
|
||||||
"https://f.hellowork.com/blogdumoderateur/2019/11/twitter-logo-1200x1200.jpg";
|
"https://f.hellowork.com/blogdumoderateur/2019/11/twitter-logo-1200x1200.jpg";
|
||||||
const Service.github()
|
const Service.github()
|
||||||
: name = "GitHub",
|
: name = "Github",
|
||||||
url = "https://github.com/",
|
url = "https://github.com/",
|
||||||
logoUrl = "https://avatars.githubusercontent.com/u/9919?s=280&v=4";
|
logoUrl = "https://avatars.githubusercontent.com/u/9919?s=280&v=4";
|
||||||
const Service.youtube()
|
const Service.youtube()
|
||||||
@@ -57,13 +64,23 @@ class Service {
|
|||||||
logoUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/a/af/Cle.png/1024px-Cle.png";
|
logoUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/a/af/Cle.png/1024px-Cle.png";
|
||||||
|
|
||||||
/// Returns a list of all the available services
|
/// Returns a list of all the available services
|
||||||
static all() => const [
|
static List<Service> all() => const [
|
||||||
Service.discord(),
|
Service.discord(),
|
||||||
Service.github(),
|
Service.github(),
|
||||||
Service.gmail(),
|
Service.anilist(),
|
||||||
Service.youtube(),
|
Service.youtube(),
|
||||||
Service.twitter(),
|
Service.twitter(),
|
||||||
Service.spotify(),
|
Service.spotify(),
|
||||||
Service.utils(),
|
Service.utils(),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/// Construct a service based on a lowercase string, the name of the service
|
||||||
|
static Service factory(String name) {
|
||||||
|
if (name.toLowerCase() == "git") return const Service.github();
|
||||||
|
if (name.toLowerCase() == "ani") return const Service.anilist();
|
||||||
|
for (Service service in Service.all()) {
|
||||||
|
if (service.name.toLowerCase() == name.toLowerCase()) return service;
|
||||||
|
}
|
||||||
|
throw Exception("Unknown service");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
// ignore_for_file: hash_and_equals
|
// ignore_for_file: hash_and_equals
|
||||||
|
|
||||||
|
import 'package:aeris/src/models/action_parameter.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:aeris/src/main.dart';
|
import 'package:aeris/main.dart';
|
||||||
import 'package:aeris/src/models/service.dart';
|
import 'package:aeris/src/models/service.dart';
|
||||||
import 'package:aeris/src/models/action.dart' as aeris_action;
|
import 'package:aeris/src/models/action.dart' as aeris_action;
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
@@ -14,11 +15,27 @@ class Trigger extends aeris_action.Action {
|
|||||||
{Key? key,
|
{Key? key,
|
||||||
required Service service,
|
required Service service,
|
||||||
required String name,
|
required String name,
|
||||||
Map<String, Object> parameters = const {},
|
List<ActionParameter> parameters = const [],
|
||||||
this.last})
|
this.last})
|
||||||
: super(service: service, name: name, parameters: parameters);
|
: super(service: service, name: name, parameters: parameters);
|
||||||
|
|
||||||
///TODO Constructor from DB 'Type' field
|
/// Unserialize
|
||||||
|
static Trigger fromJSON(Object action) {
|
||||||
|
var triggerJSON = action as Map<String, dynamic>;
|
||||||
|
Service service =
|
||||||
|
aeris_action.Action.parseServiceInName(triggerJSON['pType'] as String);
|
||||||
|
var lastTriggerField = action['lastTrigger'];
|
||||||
|
DateTime? last = lastTriggerField == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(lastTriggerField as String);
|
||||||
|
|
||||||
|
return Trigger(
|
||||||
|
service: service,
|
||||||
|
name: triggerJSON['pType'] as String,
|
||||||
|
last: last,
|
||||||
|
parameters: ActionParameter.fromJSON((triggerJSON['pParams'] as Map<String, dynamic>))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
String lastToString() {
|
String lastToString() {
|
||||||
var context = AppLocalizations.of(Aeris.materialKey.currentContext!);
|
var context = AppLocalizations.of(Aeris.materialKey.currentContext!);
|
||||||
@@ -32,7 +49,7 @@ class Trigger extends aeris_action.Action {
|
|||||||
|
|
||||||
/// Template trigger, used as an 'empty' trigger
|
/// Template trigger, used as an 'empty' trigger
|
||||||
Trigger.template({Key? key, this.last})
|
Trigger.template({Key? key, this.last})
|
||||||
: super(service: const Service.twitter(), name: '', parameters: {});
|
: super(service: Service.all()[0], name: '', parameters: []);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
// ignore: avoid_renaming_method_parameters
|
// ignore: avoid_renaming_method_parameters
|
||||||
@@ -41,7 +58,7 @@ class Trigger extends aeris_action.Action {
|
|||||||
return service.name == other.service.name &&
|
return service.name == other.service.name &&
|
||||||
name == other.name &&
|
name == other.name &&
|
||||||
last == other.last &&
|
last == other.last &&
|
||||||
parameters.values.toString() == other.parameters.values.toString() &&
|
parameters.map((e) => e.name).toString() ==
|
||||||
parameters.keys.toString() == other.parameters.keys.toString();
|
other.parameters.map((e) => e.name).toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
// Class for a service related to a user (Youtube, Gmail, ...)
|
|
||||||
import 'package:aeris/src/models/service.dart';
|
|
||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
|
|
||||||
class UserService {
|
|
||||||
/// Service name related to the user
|
|
||||||
final Service serviceProvider;
|
|
||||||
|
|
||||||
/// Id of an user for this service
|
|
||||||
final String serviceAccountId;
|
|
||||||
|
|
||||||
/// Account Username
|
|
||||||
final String accountUsername;
|
|
||||||
|
|
||||||
/// Account Slug for this Service
|
|
||||||
final String accountSlug;
|
|
||||||
|
|
||||||
/// Account External Token for this Service
|
|
||||||
final String userExternalToken;
|
|
||||||
|
|
||||||
UserService(
|
|
||||||
{Key? key,
|
|
||||||
required this.serviceAccountId,
|
|
||||||
required this.accountUsername,
|
|
||||||
required this.accountSlug,
|
|
||||||
required this.userExternalToken,
|
|
||||||
required this.serviceProvider});
|
|
||||||
}
|
|
||||||
73
mobile/lib/src/providers/action_catalogue_provider.dart
Normal file
73
mobile/lib/src/providers/action_catalogue_provider.dart
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import 'package:aeris/src/models/action_parameter.dart';
|
||||||
|
import 'package:aeris/src/models/action_template.dart';
|
||||||
|
import 'package:aeris/src/models/service.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:aeris/src/aeris_api.dart';
|
||||||
|
import 'package:get_it/get_it.dart';
|
||||||
|
|
||||||
|
/// Provider class for Action listed in /about.json
|
||||||
|
class ActionCatalogueProvider extends ChangeNotifier {
|
||||||
|
/// Tells if the provers has loaded data at least once
|
||||||
|
final Map<Service, List<ActionTemplate>> _triggerTemplates = {};
|
||||||
|
final Map<Service, List<ActionTemplate>> _reactionTemplates = {};
|
||||||
|
Map<Service, List<ActionTemplate>> get triggerTemplates => _triggerTemplates;
|
||||||
|
Map<Service, List<ActionTemplate>> get reactionTemplates =>
|
||||||
|
_reactionTemplates;
|
||||||
|
|
||||||
|
String removeServiceFromAName(String aName) {
|
||||||
|
var words = aName.split('_');
|
||||||
|
words.removeAt(0);
|
||||||
|
return words.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
void reloadCatalogue() {
|
||||||
|
_triggerTemplates.clear();
|
||||||
|
_reactionTemplates.clear();
|
||||||
|
Service.all().forEach((element) {
|
||||||
|
_triggerTemplates.putIfAbsent(element, () => []);
|
||||||
|
_reactionTemplates.putIfAbsent(element, () => []);
|
||||||
|
});
|
||||||
|
GetIt.I<AerisAPI>().getAbout().then((about) {
|
||||||
|
if (about.isEmpty || about == null) return;
|
||||||
|
final services = (about['server'] as Map<String, dynamic>)['services'] as List<dynamic>;
|
||||||
|
for (var serviceContent in services) {
|
||||||
|
Service service = Service.factory(serviceContent['name']);
|
||||||
|
for (var action in (serviceContent['actions'] as List)) {
|
||||||
|
_triggerTemplates[service]!.add(
|
||||||
|
ActionTemplate(
|
||||||
|
name: action['name'],
|
||||||
|
service: service,
|
||||||
|
description: action['description'],
|
||||||
|
parameters: (action['params'] as List).map(
|
||||||
|
(e) => ActionParameter(name: e['name'], description: e['description'])
|
||||||
|
).toList(),
|
||||||
|
returnedValues: (action['returns'] as List).map(
|
||||||
|
(e) => ActionParameter(name: e['name'], description: e['description'])
|
||||||
|
).toList(),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (var reaction in serviceContent['reactions']) {
|
||||||
|
_reactionTemplates[service]!.add(
|
||||||
|
ActionTemplate(
|
||||||
|
name: reaction['name'],
|
||||||
|
service: service,
|
||||||
|
description: reaction['description'],
|
||||||
|
parameters: (reaction['params'] as List).map(
|
||||||
|
(e) => ActionParameter(name: e['name'], description: e['description'])
|
||||||
|
).toList(),
|
||||||
|
returnedValues: (reaction['returns'] as List).map(
|
||||||
|
(e) => ActionParameter(name: e['name'], description: e['description'])
|
||||||
|
).toList(),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
notifyListeners();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionCatalogueProvider() {
|
||||||
|
reloadCatalogue();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,8 @@ class PipelineProvider extends ChangeNotifier {
|
|||||||
late PipelineCollection _pipelineCollection;
|
late PipelineCollection _pipelineCollection;
|
||||||
|
|
||||||
/// Tells if the provers has loaded data at least once
|
/// Tells if the provers has loaded data at least once
|
||||||
bool initialized = false;
|
bool _initialized = false;
|
||||||
|
bool get initialized => _initialized;
|
||||||
|
|
||||||
PipelineProvider() {
|
PipelineProvider() {
|
||||||
_pipelineCollection = PipelineCollection(
|
_pipelineCollection = PipelineCollection(
|
||||||
@@ -23,17 +24,17 @@ class PipelineProvider extends ChangeNotifier {
|
|||||||
/// Fetches the pipelines from API and put them in the collection
|
/// Fetches the pipelines from API and put them in the collection
|
||||||
Future<void> fetchPipelines() {
|
Future<void> fetchPipelines() {
|
||||||
return GetIt.I<AerisAPI>().getPipelines().then((pipelines) {
|
return GetIt.I<AerisAPI>().getPipelines().then((pipelines) {
|
||||||
|
_initialized = true;
|
||||||
_pipelineCollection.pipelines = pipelines;
|
_pipelineCollection.pipelines = pipelines;
|
||||||
sortPipelines();
|
sortPipelines();
|
||||||
initialized = true;
|
notifyListeners();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a pipeline in the Provider
|
/// Adds a pipeline in the Provider
|
||||||
addPipeline(Pipeline newPipeline) {
|
addPipeline(Pipeline newPipeline) async {
|
||||||
initialized = true;
|
await GetIt.I<AerisAPI>().createPipeline(newPipeline);
|
||||||
_pipelineCollection.pipelines.add(newPipeline);
|
_pipelineCollection.pipelines.add(newPipeline);
|
||||||
GetIt.I<AerisAPI>().createPipeline(newPipeline);
|
|
||||||
sortPipelines();
|
sortPipelines();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|||||||
41
mobile/lib/src/providers/services_provider.dart
Normal file
41
mobile/lib/src/providers/services_provider.dart
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import 'package:aeris/src/aeris_api.dart';
|
||||||
|
import 'package:aeris/src/models/service.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:get_it/get_it.dart';
|
||||||
|
|
||||||
|
/// Provider used to store every Service the User is authenticated to
|
||||||
|
class ServiceProvider extends ChangeNotifier {
|
||||||
|
/// List of [Service] related to the user
|
||||||
|
List<Service> _connectedServices = [];
|
||||||
|
List<Service> get connectedServices => _connectedServices;
|
||||||
|
|
||||||
|
/// Get the services the user is not connected to
|
||||||
|
List<Service> get availableServices => Service.all()
|
||||||
|
.where((element) => !_connectedServices.contains(element))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
ServiceProvider() {
|
||||||
|
refreshServices();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a service into the Provider
|
||||||
|
addService(Service service, String code) async {
|
||||||
|
_connectedServices.add(service);
|
||||||
|
GetIt.I<AerisAPI>()
|
||||||
|
.connectService(service, code)
|
||||||
|
.then((value) => notifyListeners());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Refresh services from API
|
||||||
|
refreshServices() async {
|
||||||
|
_connectedServices = await GetIt.I<AerisAPI>().getConnectedService();
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes a service from the Provider, and calls API
|
||||||
|
removeService(Service service) async {
|
||||||
|
_connectedServices.remove(service);
|
||||||
|
notifyListeners();
|
||||||
|
await GetIt.I<AerisAPI>().disconnectService(service);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
import 'package:aeris/src/models/user_service.dart';
|
|
||||||
import 'package:aeris/src/models/service.dart';
|
|
||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
|
|
||||||
/// Provider used to store every Service related to the User
|
|
||||||
class UserServiceProvider extends ChangeNotifier {
|
|
||||||
/// List of [Service] related to the user
|
|
||||||
List<UserService> userServices = [];
|
|
||||||
|
|
||||||
/// Adds a service into the Provider
|
|
||||||
addServiceForUser(UserService newService) {
|
|
||||||
userServices.add(newService);
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new service related to the user
|
|
||||||
createUserService(Service serviceToSet,
|
|
||||||
{String accountId = "",
|
|
||||||
String accUsername = "",
|
|
||||||
String accountSlug = "",
|
|
||||||
String externalToken = ""}) {
|
|
||||||
UserService newService = UserService(
|
|
||||||
serviceAccountId: accountId,
|
|
||||||
accountUsername: accUsername,
|
|
||||||
accountSlug: accountSlug,
|
|
||||||
userExternalToken: externalToken,
|
|
||||||
serviceProvider: serviceToSet);
|
|
||||||
userServices.add(newService);
|
|
||||||
// notifyListeners(); /// TODO Get the notifyListeners method back.
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets a list of service into the Provider
|
|
||||||
setServiceForUser(List<UserService> newServices) {
|
|
||||||
userServices = [];
|
|
||||||
userServices = newServices;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Modifies a service given as argument
|
|
||||||
modifyService(UserService toModify, String serviceAccountId,
|
|
||||||
String accountUsername, String accountSlug, String userExternalToken) {
|
|
||||||
for (int i = 0; i < userServices.length; i++) {
|
|
||||||
if (userServices[i].serviceProvider.name ==
|
|
||||||
toModify.serviceProvider.name &&
|
|
||||||
userServices[i].serviceAccountId == toModify.serviceAccountId &&
|
|
||||||
userServices[i].userExternalToken == toModify.userExternalToken) {
|
|
||||||
UserService newService = UserService(
|
|
||||||
serviceProvider: userServices[i].serviceProvider,
|
|
||||||
serviceAccountId: serviceAccountId,
|
|
||||||
accountUsername: accountUsername,
|
|
||||||
accountSlug: accountSlug,
|
|
||||||
userExternalToken: userExternalToken);
|
|
||||||
userServices[i] = newService;
|
|
||||||
notifyListeners();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Removes a service from the Provider
|
|
||||||
removeService(UserService toRemove) {
|
|
||||||
for (UserService uService in userServices) {
|
|
||||||
if (uService.serviceProvider.name == toRemove.serviceProvider.name &&
|
|
||||||
uService.serviceAccountId == toRemove.serviceAccountId &&
|
|
||||||
uService.userExternalToken == toRemove.userExternalToken) {
|
|
||||||
userServices.remove(uService);
|
|
||||||
notifyListeners();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clears Provider from data
|
|
||||||
clearProvider() {
|
|
||||||
userServices.clear();
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
28
mobile/lib/src/views/authorization_page.dart
Normal file
28
mobile/lib/src/views/authorization_page.dart
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import 'package:aeris/src/models/service.dart';
|
||||||
|
import 'package:aeris/src/providers/services_provider.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:loading_indicator/loading_indicator.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class AuthorizationPage extends StatelessWidget {
|
||||||
|
const AuthorizationPage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final route = ModalRoute.of(context)!.settings.name!;
|
||||||
|
final code = Uri.parse(route).queryParameters['code']!;
|
||||||
|
final serviceName = Uri.parse(route).pathSegments.last;
|
||||||
|
final service = Service.factory(serviceName);
|
||||||
|
|
||||||
|
Provider.of<ServiceProvider>(context, listen: false).addService(service, code).then((_) {
|
||||||
|
Provider.of<ServiceProvider>(context, listen: false).notifyListeners();
|
||||||
|
Navigator.pop(context);
|
||||||
|
});
|
||||||
|
return Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: LoadingIndicator(
|
||||||
|
indicatorType: Indicator.ballClipRotateMultiple,
|
||||||
|
colors: [Theme.of(context).colorScheme.secondary],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -79,15 +79,19 @@ class _CreatePipelinePageState extends State<CreatePipelinePage> {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
showAerisCardPage(
|
showAerisCardPage(
|
||||||
context,
|
context,
|
||||||
(_) =>
|
(_) => SetupActionPage(
|
||||||
SetupActionPage(action: trigger))
|
action: trigger,
|
||||||
|
parentReactions: reactions
|
||||||
|
))
|
||||||
.then((_) => setState(() {}));
|
.then((_) => setState(() {}));
|
||||||
})
|
})
|
||||||
: ActionCard(
|
: ActionCard(
|
||||||
leading: trigger.service.getLogo(logoSize: 50),
|
leading: trigger.service.getLogo(logoSize: 50),
|
||||||
title: trigger.name,
|
title: trigger.displayName(),
|
||||||
trailing: ActionCardPopupMenu(
|
trailing: ActionCardPopupMenu(
|
||||||
deletable: false,
|
deletable: false,
|
||||||
|
parentReactions: reactions,
|
||||||
|
parentTrigger: trigger,
|
||||||
action: trigger,
|
action: trigger,
|
||||||
then: () => setState(() {})),
|
then: () => setState(() {})),
|
||||||
),
|
),
|
||||||
@@ -104,8 +108,10 @@ class _CreatePipelinePageState extends State<CreatePipelinePage> {
|
|||||||
itemBuilder: (reaction) => ActionCard(
|
itemBuilder: (reaction) => ActionCard(
|
||||||
key: ValueKey(reactions.indexOf(reaction)),
|
key: ValueKey(reactions.indexOf(reaction)),
|
||||||
leading: reaction.service.getLogo(logoSize: 50),
|
leading: reaction.service.getLogo(logoSize: 50),
|
||||||
title: reaction.name,
|
title: reaction.displayName(),
|
||||||
trailing: ActionCardPopupMenu(
|
trailing: ActionCardPopupMenu(
|
||||||
|
parentTrigger: trigger == Trigger.template() ? null : trigger,
|
||||||
|
parentReactions: reactions,
|
||||||
deletable: reactions.length > 1,
|
deletable: reactions.length > 1,
|
||||||
action: reaction,
|
action: reaction,
|
||||||
then: () => setState(() {}),
|
then: () => setState(() {}),
|
||||||
@@ -128,7 +134,10 @@ class _CreatePipelinePageState extends State<CreatePipelinePage> {
|
|||||||
showAerisCardPage(
|
showAerisCardPage(
|
||||||
context,
|
context,
|
||||||
(_) => SetupActionPage(
|
(_) => SetupActionPage(
|
||||||
action: newreact))
|
action: newreact,
|
||||||
|
parentReactions: reactions,
|
||||||
|
parentTrigger: trigger == Trigger.template() ? null : trigger,
|
||||||
|
))
|
||||||
.then((_) => setState(() {
|
.then((_) => setState(() {
|
||||||
if (newreact != Reaction.template()) {
|
if (newreact != Reaction.template()) {
|
||||||
reactions.add(newreact);
|
reactions.add(newreact);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:aeris/src/aeris_api.dart';
|
||||||
import 'package:aeris/src/views/create_pipeline_page.dart';
|
import 'package:aeris/src/views/create_pipeline_page.dart';
|
||||||
import 'package:aeris/src/views/service_page.dart';
|
import 'package:aeris/src/views/service_page.dart';
|
||||||
import 'package:aeris/src/widgets/aeris_card_page.dart';
|
import 'package:aeris/src/widgets/aeris_card_page.dart';
|
||||||
@@ -8,6 +9,7 @@ import 'package:aeris/src/widgets/aeris_page.dart';
|
|||||||
import 'package:aeris/src/widgets/clickable_card.dart';
|
import 'package:aeris/src/widgets/clickable_card.dart';
|
||||||
import 'package:aeris/src/widgets/home_page_sort_menu.dart';
|
import 'package:aeris/src/widgets/home_page_sort_menu.dart';
|
||||||
import 'package:aeris/src/widgets/pipeline_card.dart';
|
import 'package:aeris/src/widgets/pipeline_card.dart';
|
||||||
|
import 'package:get_it/get_it.dart';
|
||||||
import 'package:liquid_pull_to_refresh/liquid_pull_to_refresh.dart';
|
import 'package:liquid_pull_to_refresh/liquid_pull_to_refresh.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:skeleton_loader/skeleton_loader.dart';
|
import 'package:skeleton_loader/skeleton_loader.dart';
|
||||||
@@ -28,58 +30,69 @@ class _HomePageState extends State<HomePage> {
|
|||||||
|
|
||||||
Widget serviceActionButtons = IconButton(
|
Widget serviceActionButtons = IconButton(
|
||||||
icon: const Icon(Icons.electrical_services),
|
icon: const Icon(Icons.electrical_services),
|
||||||
onPressed: () => showAerisCardPage(context, (context) => const ServicePage())
|
onPressed: () =>
|
||||||
);
|
showAerisCardPage(context, (context) => const ServicePage()));
|
||||||
Widget logoutActionButton = IconButton(
|
Widget logoutActionButton = IconButton(
|
||||||
icon: const Icon(Icons.logout),
|
icon: const Icon(Icons.logout),
|
||||||
onPressed: () => showDialog<String>(
|
onPressed: () => showDialog<String>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) => WarningDialog(
|
builder: (BuildContext context) => WarningDialog(
|
||||||
message: AppLocalizations.of(context).logoutWarningMessage,
|
message: AppLocalizations.of(context).logoutWarningMessage,
|
||||||
onAccept: () => Navigator.of(context).popAndPushNamed('/'), //TODO logout
|
onAccept: () {
|
||||||
warnedAction: AppLocalizations.of(context).logout
|
GetIt.I<AerisAPI>().stopConnection();
|
||||||
)
|
Navigator.of(context).popAndPushNamed('/');
|
||||||
),
|
},
|
||||||
|
warnedAction: AppLocalizations.of(context).logout)),
|
||||||
);
|
);
|
||||||
return Consumer<PipelineProvider>(
|
return Consumer<PipelineProvider>(
|
||||||
builder: (context, provider, _) => AerisPage(
|
builder: (context, provider, _) => AerisPage(
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
onPressed: () => showAerisCardPage(context, (_) => const CreatePipelinePage()),
|
onPressed: () => showAerisCardPage(
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
context, (_) => const CreatePipelinePage()),
|
||||||
elevation: 10,
|
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||||
child: const Icon(Icons.add),
|
elevation: 10,
|
||||||
),
|
child: const Icon(Icons.add),
|
||||||
actions: [
|
),
|
||||||
HomePageSortMenu(
|
actions: [
|
||||||
collectionProvider: provider,
|
HomePageSortMenu(
|
||||||
),
|
collectionProvider: provider,
|
||||||
serviceActionButtons,
|
),
|
||||||
logoutActionButton
|
serviceActionButtons,
|
||||||
],
|
logoutActionButton
|
||||||
body: provider.initialized == false
|
],
|
||||||
? ListView(physics: const BouncingScrollPhysics(),
|
body: provider.initialized == false
|
||||||
padding: const EdgeInsets.only(bottom: 20, top: 20, left: 10, right: 10),
|
? ListView(
|
||||||
children: [SkeletonLoader(
|
physics: const BouncingScrollPhysics(),
|
||||||
builder: ClickableCard(onTap:(){}, body: const SizedBox(height: 80)),
|
padding: const EdgeInsets.only(
|
||||||
items: 10,
|
bottom: 20, top: 20, left: 10, right: 10),
|
||||||
highlightColor: Theme.of(context).colorScheme.secondary
|
children: [
|
||||||
)])
|
SkeletonLoader(
|
||||||
: LiquidPullToRefresh(
|
builder: ClickableCard(
|
||||||
borderWidth: 2,
|
onTap: () {},
|
||||||
animSpeedFactor: 3,
|
body: const SizedBox(height: 80)),
|
||||||
color: Colors.transparent,
|
items: 10,
|
||||||
showChildOpacityTransition: false,
|
highlightColor:
|
||||||
onRefresh: () => provider.fetchPipelines()
|
Theme.of(context).colorScheme.secondary)
|
||||||
.then((_) => setState(() {})), // refresh callback
|
])
|
||||||
child: ListView.builder(
|
: LiquidPullToRefresh(
|
||||||
physics: const BouncingScrollPhysics(),
|
borderWidth: 2,
|
||||||
padding: const EdgeInsets.only(bottom: 20, top: 20, left: 10, right: 10),
|
animSpeedFactor: 3,
|
||||||
controller: listController,
|
color: Colors.transparent,
|
||||||
itemCount: provider.pipelineCount,
|
showChildOpacityTransition: false,
|
||||||
itemBuilder: (BuildContext context, int index) =>
|
onRefresh: () => provider
|
||||||
PipelineCard(pipeline: provider.getPipelineAt(index),
|
.fetchPipelines()
|
||||||
),
|
.then((_) => setState(() {})), // refresh callback
|
||||||
)),
|
child: ListView.builder(
|
||||||
));
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
bottom: 20, top: 20, left: 10, right: 10),
|
||||||
|
controller: listController,
|
||||||
|
itemCount: provider.pipelineCount,
|
||||||
|
itemBuilder: (BuildContext context, int index) =>
|
||||||
|
PipelineCard(
|
||||||
|
pipeline: provider.getPipelineAt(index),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
import 'package:aeris/src/main.dart';
|
import 'package:aeris/src/aeris_api.dart';
|
||||||
|
import 'package:aeris/main.dart';
|
||||||
import 'package:aeris/src/widgets/aeris_page.dart';
|
import 'package:aeris/src/widgets/aeris_page.dart';
|
||||||
import 'package:flutter_login/flutter_login.dart';
|
import 'package:flutter_login/flutter_login.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
import 'package:get_it/get_it.dart';
|
||||||
const users = {
|
|
||||||
'dribbble@gmail.com': '12345',
|
|
||||||
'hunter@gmail.com': 'hunter',
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Login Page Widget
|
/// Login Page Widget
|
||||||
class LoginPage extends StatelessWidget {
|
class LoginPage extends StatelessWidget {
|
||||||
@@ -17,39 +14,24 @@ class LoginPage extends StatelessWidget {
|
|||||||
Duration get loginDuration => const Duration(milliseconds: 2500);
|
Duration get loginDuration => const Duration(milliseconds: 2500);
|
||||||
|
|
||||||
/// Called when user clicks on [FlutterLogin] widget 'login' button
|
/// Called when user clicks on [FlutterLogin] widget 'login' button
|
||||||
Future<String?> _authUser(LoginData data) {
|
Future<String?> _authUser(LoginData data) async {
|
||||||
debugPrint('Name: ${data.name}, Password: ${data.password}');
|
bool connected =
|
||||||
return Future.delayed(loginDuration).then((_) {
|
await GetIt.I<AerisAPI>().createConnection(data.name, data.password);
|
||||||
if (!users.containsKey(data.name)) {
|
if (!connected) {
|
||||||
return AppLocalizations.of(Aeris.materialKey.currentContext!)
|
return AppLocalizations.of(Aeris.materialKey.currentContext!)
|
||||||
.usernameOrPasswordIncorrect;
|
.usernameOrPasswordIncorrect;
|
||||||
}
|
}
|
||||||
if (users[data.name] != data.password) {
|
return null;
|
||||||
return AppLocalizations.of(Aeris.materialKey.currentContext!)
|
|
||||||
.usernameOrPasswordIncorrect;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Opens signup page of [FlutterLogin] widget
|
/// Opens signup page of [FlutterLogin] widget
|
||||||
Future<String?> _signupUser(SignupData data) {
|
Future<String?> _signupUser(SignupData data) async {
|
||||||
debugPrint('Signup Name: ${data.name}, Password: ${data.password}');
|
bool connected =
|
||||||
return Future.delayed(loginDuration).then((_) {
|
await GetIt.I<AerisAPI>().signUpUser(data.name!, data.password!);
|
||||||
return null;
|
if (connected == false) {
|
||||||
});
|
return AppLocalizations.of(Aeris.materialKey.currentContext!).errorOnSignup;
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
/// Opens user password recovery page
|
|
||||||
Future<String?> _recoverPassword(String name) {
|
|
||||||
debugPrint('Name: $name');
|
|
||||||
return Future.delayed(loginDuration).then((_) {
|
|
||||||
if (!users.containsKey(name)) {
|
|
||||||
return AppLocalizations.of(Aeris.materialKey.currentContext!)
|
|
||||||
.userDoesNotExist;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -59,16 +41,21 @@ class LoginPage extends StatelessWidget {
|
|||||||
body: FlutterLogin(
|
body: FlutterLogin(
|
||||||
disableCustomPageTransformer: true,
|
disableCustomPageTransformer: true,
|
||||||
logo: const AssetImage("assets/logo.png"),
|
logo: const AssetImage("assets/logo.png"),
|
||||||
onRecoverPassword: _recoverPassword,
|
hideForgotPasswordButton: true,
|
||||||
|
onRecoverPassword: (_) => null,
|
||||||
theme: LoginTheme(
|
theme: LoginTheme(
|
||||||
pageColorLight: Colors.transparent,
|
pageColorLight: Colors.transparent,
|
||||||
pageColorDark: Colors.transparent,
|
pageColorDark: Colors.transparent,
|
||||||
primaryColor: Theme.of(context).colorScheme.primary),
|
primaryColor: Theme.of(context).colorScheme.primary),
|
||||||
onLogin: _authUser,
|
onLogin: _authUser,
|
||||||
onSignup: _signupUser,
|
onSignup: _signupUser,
|
||||||
|
userType: LoginUserType.name,
|
||||||
|
userValidator: (input) {
|
||||||
|
if (input == null || input.trim().length < 4) return "Must be at least 4 chars long";
|
||||||
|
return null;
|
||||||
|
},
|
||||||
onSubmitAnimationCompleted: () {
|
onSubmitAnimationCompleted: () {
|
||||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
Navigator.of(context).pushNamedAndRemoveUntil('/home', (route) => false);
|
||||||
Navigator.of(context).popAndPushNamed("/home");
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,7 +95,12 @@ class _PipelineDetailPageState extends State<PipelineDetailPage> {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
Reaction newreaction = Reaction.template();
|
Reaction newreaction = Reaction.template();
|
||||||
showAerisCardPage(
|
showAerisCardPage(
|
||||||
context, (_) => SetupActionPage(action: newreaction))
|
context, (_) => SetupActionPage(
|
||||||
|
action: newreaction,
|
||||||
|
parentTrigger: pipeline.trigger,
|
||||||
|
parentReactions: pipeline.reactions,
|
||||||
|
)
|
||||||
|
)
|
||||||
.then((r) {
|
.then((r) {
|
||||||
if (newreaction != Reaction.template()) {
|
if (newreaction != Reaction.template()) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -135,9 +140,11 @@ class _PipelineDetailPageState extends State<PipelineDetailPage> {
|
|||||||
style: const TextStyle(fontWeight: FontWeight.w500)),
|
style: const TextStyle(fontWeight: FontWeight.w500)),
|
||||||
ActionCard(
|
ActionCard(
|
||||||
leading: pipeline.trigger.service.getLogo(logoSize: 50),
|
leading: pipeline.trigger.service.getLogo(logoSize: 50),
|
||||||
title: pipeline.trigger.name,
|
title: pipeline.trigger.displayName(),
|
||||||
trailing: ActionCardPopupMenu(
|
trailing: ActionCardPopupMenu(
|
||||||
deletable: false,
|
deletable: false,
|
||||||
|
parentTrigger: pipeline.trigger,
|
||||||
|
parentReactions: pipeline.reactions,
|
||||||
action: pipeline.trigger,
|
action: pipeline.trigger,
|
||||||
then: () {
|
then: () {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
@@ -152,8 +159,10 @@ class _PipelineDetailPageState extends State<PipelineDetailPage> {
|
|||||||
itemBuilder: (reaction) => ActionCard(
|
itemBuilder: (reaction) => ActionCard(
|
||||||
key: ValueKey(pipeline.reactions.indexOf(reaction)),
|
key: ValueKey(pipeline.reactions.indexOf(reaction)),
|
||||||
leading: reaction.service.getLogo(logoSize: 50),
|
leading: reaction.service.getLogo(logoSize: 50),
|
||||||
title: reaction.name,
|
title: reaction.displayName(),
|
||||||
trailing: ActionCardPopupMenu(
|
trailing: ActionCardPopupMenu(
|
||||||
|
parentTrigger: pipeline.trigger,
|
||||||
|
parentReactions: pipeline.reactions,
|
||||||
deletable: pipeline.reactions.length > 1,
|
deletable: pipeline.reactions.length > 1,
|
||||||
action: reaction,
|
action: reaction,
|
||||||
then: () {
|
then: () {
|
||||||
|
|||||||
@@ -1,41 +1,36 @@
|
|||||||
import 'package:aeris/src/aeris_api.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:aeris/src/models/pipeline.dart';
|
|
||||||
import 'package:aeris/src/models/reaction.dart';
|
|
||||||
import 'package:aeris/src/models/service.dart';
|
import 'package:aeris/src/models/service.dart';
|
||||||
import 'package:aeris/src/providers/pipelines_provider.dart';
|
import 'package:aeris/src/providers/pipelines_provider.dart';
|
||||||
import 'package:aeris/src/providers/user_services_provider.dart';
|
import 'package:aeris/src/providers/services_provider.dart';
|
||||||
import 'package:aeris/src/widgets/action_card.dart';
|
import 'package:aeris/src/widgets/action_card.dart';
|
||||||
import 'package:aeris/src/widgets/aeris_card_page.dart';
|
import 'package:aeris/src/widgets/aeris_card_page.dart';
|
||||||
import 'package:aeris/src/widgets/warning_dialog.dart';
|
import 'package:aeris/src/widgets/warning_dialog.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
///Page listing connected & available services
|
///Page listing connected & available services
|
||||||
class ServicePage extends StatelessWidget {
|
class ServicePage extends StatelessWidget {
|
||||||
const ServicePage({Key? key}) : super(key: key);
|
const ServicePage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
List<Widget> getServiceGroup(String groupName, Icon trailingIcon,
|
List<Widget> getServiceGroup(List<Service> services, String groupName, Icon trailingIcon,
|
||||||
void Function(Service) onTap, BuildContext context) {
|
void Function(Service) onTap, BuildContext context) {
|
||||||
UserServiceProvider uServicesProvider =
|
if (services.isEmpty) return [];
|
||||||
Provider.of<UserServiceProvider>(context);
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
Text(
|
Text(
|
||||||
"$groupName:",
|
"$groupName:",
|
||||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
for (var service in uServicesProvider.userServices)
|
for (var service in services)
|
||||||
ActionCard(
|
ActionCard(
|
||||||
leading: service.serviceProvider.getLogo(logoSize: 50),
|
leading: service.getLogo(logoSize: 50),
|
||||||
title: service.serviceProvider.name,
|
title: service.name,
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
splashColor: trailingIcon.color!.withAlpha(100),
|
splashColor: trailingIcon.color!.withAlpha(100),
|
||||||
splashRadius: 20,
|
splashRadius: 20,
|
||||||
icon: trailingIcon,
|
icon: trailingIcon,
|
||||||
onPressed: () => onTap(service.serviceProvider),
|
onPressed: () => onTap(service),
|
||||||
)),
|
)),
|
||||||
const SizedBox(height: 30),
|
const SizedBox(height: 30),
|
||||||
];
|
];
|
||||||
@@ -43,69 +38,45 @@ class ServicePage extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
List<Service> services = const [
|
return Consumer<ServiceProvider>(
|
||||||
Service.discord(),
|
builder: (context, serviceProvider, _) => Consumer<PipelineProvider>(
|
||||||
Service.gmail(),
|
builder: (context, pipelineProvider, _) => AerisCardPage(
|
||||||
Service.github(),
|
|
||||||
Service.youtube(),
|
|
||||||
Service.twitter(),
|
|
||||||
Service.spotify()
|
|
||||||
];
|
|
||||||
UserServiceProvider uServiceProvider =
|
|
||||||
Provider.of<UserServiceProvider>(context, listen: false);
|
|
||||||
uServiceProvider.clearProvider();
|
|
||||||
for (var service in services) {
|
|
||||||
uServiceProvider.createUserService(service);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Consumer<PipelineProvider>(
|
|
||||||
builder: (context, provider, _) => AerisCardPage(
|
|
||||||
body: Column(
|
body: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
...[
|
...[
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: Text(AppLocalizations.of(context).services, style: const TextStyle(fontSize: 25)),
|
child: Text(AppLocalizations.of(context).services,
|
||||||
),
|
style: const TextStyle(fontSize: 25)),
|
||||||
const SizedBox(height: 60)
|
),
|
||||||
],
|
const SizedBox(height: 60)
|
||||||
...getServiceGroup(
|
|
||||||
AppLocalizations.of(context).connected,
|
|
||||||
const Icon(Icons.delete, color: Colors.red),
|
|
||||||
(Service service) => showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) => WarningDialog(
|
|
||||||
message: AppLocalizations.of(context)
|
|
||||||
.disconnectServiceWarningMessage,
|
|
||||||
onAccept: () => {
|
|
||||||
provider.removePipelinesWhere((Pipeline pipeline) {
|
|
||||||
if (pipeline.trigger.service == service) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (pipeline.reactions
|
|
||||||
.where((Reaction react) =>
|
|
||||||
react.service == service)
|
|
||||||
.isNotEmpty) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}),
|
|
||||||
GetIt.I<AerisAPI>().disconnectService(service)
|
|
||||||
},
|
|
||||||
warnedAction:
|
|
||||||
AppLocalizations.of(context).disconnect)),
|
|
||||||
context),
|
|
||||||
...getServiceGroup(
|
|
||||||
AppLocalizations.of(context).available,
|
|
||||||
const Icon(Icons.connect_without_contact,
|
|
||||||
color: Colors.green),
|
|
||||||
(Service service) =>
|
|
||||||
print("Connected") /* TODO open page to connect service*/,
|
|
||||||
context),
|
|
||||||
],
|
],
|
||||||
),
|
...getServiceGroup(
|
||||||
|
serviceProvider.connectedServices,
|
||||||
|
AppLocalizations.of(context).connected,
|
||||||
|
const Icon(Icons.delete, color: Colors.red),
|
||||||
|
(Service service) => showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) => WarningDialog(
|
||||||
|
message: AppLocalizations.of(context)
|
||||||
|
.disconnectServiceWarningMessage,
|
||||||
|
onAccept: () => serviceProvider
|
||||||
|
.removeService(service)
|
||||||
|
.then((_) => pipelineProvider.fetchPipelines()),
|
||||||
|
warnedAction: AppLocalizations.of(context).disconnect)),
|
||||||
|
context),
|
||||||
|
...getServiceGroup(
|
||||||
|
serviceProvider.availableServices,
|
||||||
|
AppLocalizations.of(context).available,
|
||||||
|
const Icon(Icons.connect_without_contact, color: Colors.green),
|
||||||
|
(Service service) {
|
||||||
|
launch(Uri.parse(service.authUrl).toString(), forceSafariVC: false);
|
||||||
|
},
|
||||||
|
context),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import 'package:aeris/src/models/action_parameter.dart';
|
||||||
import 'package:aeris/src/models/action_template.dart';
|
import 'package:aeris/src/models/action_template.dart';
|
||||||
import 'package:aeris/src/aeris_api.dart';
|
import 'package:aeris/src/aeris_api.dart';
|
||||||
|
import 'package:aeris/src/models/reaction.dart';
|
||||||
import 'package:aeris/src/models/trigger.dart';
|
import 'package:aeris/src/models/trigger.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:aeris/src/models/action.dart' as aeris;
|
import 'package:aeris/src/models/action.dart' as aeris;
|
||||||
@@ -13,10 +15,15 @@ import 'package:skeleton_loader/skeleton_loader.dart';
|
|||||||
|
|
||||||
///Page to setup an action
|
///Page to setup an action
|
||||||
class SetupActionPage extends StatefulWidget {
|
class SetupActionPage extends StatefulWidget {
|
||||||
const SetupActionPage({Key? key, required this.action}) : super(key: key);
|
const SetupActionPage({Key? key, required this.action, required this.parentReactions, this.parentTrigger}) : super(key: key);
|
||||||
|
|
||||||
/// Action to setup
|
/// Action to setup
|
||||||
final aeris.Action action;
|
final aeris.Action action;
|
||||||
|
/// Trigger of Parent of the action to setup
|
||||||
|
final Trigger? parentTrigger;
|
||||||
|
|
||||||
|
/// reactions of Parent of the action to setup
|
||||||
|
final List<Reaction> parentReactions;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SetupActionPage> createState() => _SetupActionPageState();
|
State<SetupActionPage> createState() => _SetupActionPageState();
|
||||||
@@ -30,9 +37,7 @@ class _SetupActionPageState extends State<SetupActionPage> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
serviceState = widget.action.service;
|
serviceState = widget.action.service;
|
||||||
GetIt.I<AerisAPI>().getActionsFor(serviceState!, widget.action).then((actions) => setState(() {
|
availableActions = GetIt.I<AerisAPI>().getActionsFor(serviceState!, widget.action);
|
||||||
availableActions = actions;
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -43,12 +48,9 @@ class _SetupActionPageState extends State<SetupActionPage> {
|
|||||||
elevation: 8,
|
elevation: 8,
|
||||||
underline: Container(),
|
underline: Container(),
|
||||||
onChanged: (service) {
|
onChanged: (service) {
|
||||||
GetIt.I<AerisAPI>().getActionsFor(service!, widget.action).then((actions) => setState(() {
|
|
||||||
availableActions = actions;
|
|
||||||
}));
|
|
||||||
setState(() {
|
setState(() {
|
||||||
serviceState = service;
|
serviceState = service;
|
||||||
availableActions = [];
|
availableActions = GetIt.I<AerisAPI>().getActionsFor(service!, widget.action);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
items: Service.all().map<DropdownMenuItem<Service>>((Service service) {
|
items: Service.all().map<DropdownMenuItem<Service>>((Service service) {
|
||||||
@@ -66,6 +68,9 @@ class _SetupActionPageState extends State<SetupActionPage> {
|
|||||||
}).toList(),
|
}).toList(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
var cardShape = const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(10)));
|
||||||
|
|
||||||
return AerisCardPage(
|
return AerisCardPage(
|
||||||
body: Padding(
|
body: Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 20, left: 10, right: 10),
|
padding: const EdgeInsets.only(bottom: 20, left: 10, right: 10),
|
||||||
@@ -98,41 +103,54 @@ class _SetupActionPageState extends State<SetupActionPage> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 30),
|
const SizedBox(height: 20),
|
||||||
|
Text(AppLocalizations.of(context).paramInheritTip),
|
||||||
|
const SizedBox(height: 20),
|
||||||
if (availableActions == null)
|
if (availableActions == null)
|
||||||
SkeletonLoader(
|
SkeletonLoader(
|
||||||
builder: const Card(child: SizedBox(height: 40), elevation: 5),
|
builder: Card(shape: cardShape, child: const SizedBox(height: 40), elevation: 5),
|
||||||
items: 10,
|
items: 15,
|
||||||
highlightColor: Theme.of(context).colorScheme.secondary
|
highlightColor: Theme.of(context).colorScheme.secondary
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
...[for (aeris.Action availableAction in availableActions!)
|
...[for (ActionTemplate availableAction in availableActions!)
|
||||||
Card(
|
Card(
|
||||||
elevation: 5,
|
elevation: 5,
|
||||||
child: ExpandablePanel(
|
shape: cardShape,
|
||||||
|
child: ExpandableNotifier(
|
||||||
|
child: ScrollOnExpand(child: ExpandablePanel(
|
||||||
header: Padding(
|
header: Padding(
|
||||||
padding:
|
padding:
|
||||||
const EdgeInsets.only(left: 30, top: 20, bottom: 20),
|
const EdgeInsets.only(left: 30, top: 20, bottom: 20),
|
||||||
child: Text(availableAction.name,
|
child: Text(availableAction.displayName(),
|
||||||
style: const TextStyle(fontSize: 15))),
|
style: const TextStyle(fontSize: 15))),
|
||||||
collapsed: Container(),
|
collapsed: Container(),
|
||||||
expanded: Padding(
|
expanded: Padding(
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
child: ActionForm(
|
child: ActionForm(
|
||||||
|
reactionsCandidates: widget.parentReactions,
|
||||||
|
triggerCandidate: widget.parentTrigger,
|
||||||
|
candidate: widget.action,
|
||||||
|
key: Key("${availableAction.name}${availableAction.description}${availableAction.service}"),
|
||||||
|
description: availableAction.description!,
|
||||||
name: availableAction.name,
|
name: availableAction.name,
|
||||||
parametersNames:
|
parameters: availableAction.parameters.map((param) {
|
||||||
availableAction.parameters.keys.toList(),
|
if (widget.action.service.name == serviceState!.name && widget.action.name == availableAction.name) {
|
||||||
initValues: widget.action.name == availableAction.name
|
var previousParams = widget.action.parameters.where((element) => element.name == param.name);
|
||||||
&& availableAction.service.name == widget.action.service.name
|
if (previousParams.isNotEmpty) {
|
||||||
? widget.action.parameters : const {},
|
param.value = previousParams.first.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return param;
|
||||||
|
}).toList(),
|
||||||
onValidate: (parameters) {
|
onValidate: (parameters) {
|
||||||
widget.action.service = serviceState!;
|
widget.action.service = serviceState!;
|
||||||
widget.action.parameters = parameters;
|
widget.action.parameters = ActionParameter.fromJSON(parameters);
|
||||||
widget.action.name = availableAction.name;
|
widget.action.name = availableAction.name;
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
}),
|
}),
|
||||||
)),
|
)),
|
||||||
),
|
))),
|
||||||
const SizedBox(height: 10)
|
const SizedBox(height: 10)
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:aeris/src/aeris_api.dart';
|
import 'package:aeris/src/aeris_api.dart';
|
||||||
|
import 'package:aeris/src/widgets/setup_api_route.dart';
|
||||||
import 'package:flutter_fadein/flutter_fadein.dart';
|
import 'package:flutter_fadein/flutter_fadein.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
@@ -15,11 +16,30 @@ class StartupPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _StartupPageState extends State<StartupPage> {
|
class _StartupPageState extends State<StartupPage> {
|
||||||
|
bool connected = false;
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
GetIt.I<AerisAPI>().getAbout().then((value) {
|
||||||
|
setState(() {
|
||||||
|
connected = value.isNotEmpty;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
bool isConnected = GetIt.I<AerisAPI>().connected;
|
bool isConnected = GetIt.I<AerisAPI>().isConnected;
|
||||||
return AerisPage(
|
return AerisPage(
|
||||||
displayAppbar: false,
|
displayAppbar: false,
|
||||||
|
floatingActionButton: SetupAPIRouteButton(
|
||||||
|
connected: connected,
|
||||||
|
onSetup: () => GetIt.I<AerisAPI>().getAbout().then((value) {
|
||||||
|
setState(() {
|
||||||
|
connected = value.isNotEmpty;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
),
|
||||||
body: Column(
|
body: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
@@ -48,13 +68,13 @@ class _StartupPageState extends State<StartupPage> {
|
|||||||
textStyle: const TextStyle(fontSize: 20),
|
textStyle: const TextStyle(fontSize: 20),
|
||||||
primary: Theme.of(context).colorScheme.secondary,
|
primary: Theme.of(context).colorScheme.secondary,
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: connected ? () {
|
||||||
if (isConnected) {
|
if (isConnected) {
|
||||||
Navigator.of(context).popAndPushNamed('/home');
|
Navigator.of(context).pushNamedAndRemoveUntil('/home', (route) => false);
|
||||||
} else {
|
} else {
|
||||||
Navigator.of(context).pushNamed('/login');
|
Navigator.of(context).pushNamed('/login');
|
||||||
}
|
}
|
||||||
},
|
} : null,
|
||||||
child: Tooltip(
|
child: Tooltip(
|
||||||
message: 'Connexion',
|
message: 'Connexion',
|
||||||
child: Text(AppLocalizations.of(context).connect)
|
child: Text(AppLocalizations.of(context).connect)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'package:aeris/src/models/reaction.dart';
|
||||||
|
import 'package:aeris/src/models/trigger.dart';
|
||||||
import 'package:aeris/src/widgets/aeris_card_page.dart';
|
import 'package:aeris/src/widgets/aeris_card_page.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:aeris/src/views/setup_action_page.dart';
|
import 'package:aeris/src/views/setup_action_page.dart';
|
||||||
@@ -11,6 +13,8 @@ class ActionCardPopupMenu extends StatelessWidget {
|
|||||||
ActionCardPopupMenu({
|
ActionCardPopupMenu({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.action,
|
required this.action,
|
||||||
|
this.parentTrigger,
|
||||||
|
required this.parentReactions,
|
||||||
required this.then,
|
required this.then,
|
||||||
required this.deletable,
|
required this.deletable,
|
||||||
this.onDelete,
|
this.onDelete,
|
||||||
@@ -22,6 +26,10 @@ class ActionCardPopupMenu extends StatelessWidget {
|
|||||||
|
|
||||||
/// Selected Action
|
/// Selected Action
|
||||||
final aeris.Action action;
|
final aeris.Action action;
|
||||||
|
/// Trigger of the Parent of the action
|
||||||
|
final Trigger? parentTrigger;
|
||||||
|
/// Trigger of the Parent of the action
|
||||||
|
final List<Reaction> parentReactions;
|
||||||
|
|
||||||
/// Function to trigger once the Edit menu is closed
|
/// Function to trigger once the Edit menu is closed
|
||||||
final void Function() then;
|
final void Function() then;
|
||||||
@@ -46,15 +54,18 @@ class ActionCardPopupMenu extends StatelessWidget {
|
|||||||
icon: Icons.settings,
|
icon: Icons.settings,
|
||||||
title: AppLocalizations.of(context).modify,
|
title: AppLocalizations.of(context).modify,
|
||||||
value: () => showAerisCardPage(
|
value: () => showAerisCardPage(
|
||||||
context, (_) => SetupActionPage(action: action)).then((_) => then())
|
context, (_) => SetupActionPage(
|
||||||
),
|
action: action,
|
||||||
|
parentTrigger: parentTrigger,
|
||||||
|
parentReactions: parentReactions,
|
||||||
|
))
|
||||||
|
.then((_) => then())),
|
||||||
AerisPopupMenuItem(
|
AerisPopupMenuItem(
|
||||||
context: context,
|
context: context,
|
||||||
icon: Icons.delete,
|
icon: Icons.delete,
|
||||||
title: AppLocalizations.of(context).delete,
|
title: AppLocalizations.of(context).delete,
|
||||||
value: onDelete,
|
value: onDelete,
|
||||||
enabled: deletable
|
enabled: deletable),
|
||||||
),
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,43 @@
|
|||||||
|
import 'package:aeris/src/models/action_parameter.dart';
|
||||||
|
import 'package:aeris/src/models/action_template.dart';
|
||||||
|
import 'package:aeris/src/models/reaction.dart';
|
||||||
|
import 'package:aeris/src/models/action.dart' as aeris;
|
||||||
|
import 'package:aeris/src/models/trigger.dart';
|
||||||
|
import 'package:aeris/src/providers/action_catalogue_provider.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
|
||||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:recase/recase.dart';
|
||||||
|
import 'package:tuple/tuple.dart';
|
||||||
|
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
||||||
|
|
||||||
|
class Suggestion extends Tuple3<int, ActionParameter, ActionTemplate> {
|
||||||
|
Suggestion(int item1, ActionParameter item2, ActionTemplate item3) : super(item1, item2, item3);
|
||||||
|
|
||||||
|
// Overriding show method
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return "${item2.name}@$item1";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Form for an action
|
/// Form for an action
|
||||||
class ActionForm extends StatefulWidget {
|
class ActionForm extends StatefulWidget {
|
||||||
/// Name of the action
|
/// Name of the action
|
||||||
final String name;
|
final String name;
|
||||||
/// Names of the parameters
|
/// List of parameters, 'values' are used as default values
|
||||||
final List<String> parametersNames;
|
final List<ActionParameter> parameters;
|
||||||
/// Initial values of the fields
|
/// What the action does
|
||||||
final Map<String, Object> initValues;
|
final String description;
|
||||||
|
|
||||||
|
/// The Action that will be eventually filled by the form
|
||||||
|
final aeris.Action candidate;
|
||||||
|
/// The trigger candidate in the parent form page
|
||||||
|
final Trigger? triggerCandidate;
|
||||||
|
/// The trigger candidate in the parent form page
|
||||||
|
final List<Reaction> reactionsCandidates;
|
||||||
|
|
||||||
/// On validate callback
|
/// On validate callback
|
||||||
final void Function(Map<String, String>) onValidate;
|
final void Function(Map<String, String>) onValidate;
|
||||||
@@ -18,9 +45,13 @@ class ActionForm extends StatefulWidget {
|
|||||||
const ActionForm(
|
const ActionForm(
|
||||||
{Key? key,
|
{Key? key,
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.parametersNames,
|
required this.description,
|
||||||
|
required this.parameters,
|
||||||
required this.onValidate,
|
required this.onValidate,
|
||||||
this.initValues = const {}})
|
required this.candidate,
|
||||||
|
this.triggerCandidate,
|
||||||
|
required this.reactionsCandidates,
|
||||||
|
})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -28,32 +59,91 @@ class ActionForm extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ActionFormState extends State<ActionForm> {
|
class _ActionFormState extends State<ActionForm> {
|
||||||
final _formKey = GlobalKey<FormBuilderState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
|
List<Suggestion> getSuggestions(String pattern, ActionCatalogueProvider catalogue) {
|
||||||
|
List<Suggestion> suggestions = [];
|
||||||
|
if (pattern.endsWith("{") == false) return suggestions;
|
||||||
|
if (widget.candidate is Trigger) return suggestions;
|
||||||
|
if (widget.triggerCandidate != null) {
|
||||||
|
Trigger trigger = widget.triggerCandidate!;
|
||||||
|
var triggerTemplate = catalogue.triggerTemplates[trigger.service]!.firstWhere(
|
||||||
|
(element) => element.name == trigger.name
|
||||||
|
);
|
||||||
|
for (var parameter in triggerTemplate.returnedValues) {
|
||||||
|
suggestions.add(Suggestion(0, parameter, triggerTemplate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int index = 1;
|
||||||
|
int indexOfCandidate = widget.reactionsCandidates.indexOf(widget.candidate as Reaction);
|
||||||
|
for (var reactionCandidate in widget.reactionsCandidates) {
|
||||||
|
if (index == indexOfCandidate + 1) break;
|
||||||
|
var reactionTemplate = catalogue.reactionTemplates[reactionCandidate.service]!.firstWhere(
|
||||||
|
(element) => element.name == reactionCandidate.name
|
||||||
|
);
|
||||||
|
for (var parameter in reactionTemplate.returnedValues) {
|
||||||
|
suggestions.add(Suggestion(index, parameter, reactionTemplate));
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
return suggestions;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String> values = {};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return FormBuilder(
|
return Consumer<ActionCatalogueProvider>(
|
||||||
|
builder: (__, catalogue, _) => Form(
|
||||||
key: _formKey,
|
key: _formKey,
|
||||||
child: Column(
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
...widget.parametersNames.map((name) => FormBuilderTextField(
|
Text(widget.description, textAlign: TextAlign.left, style: TextStyle(color: Theme.of(context).colorScheme.onSurface)),
|
||||||
initialValue: (widget.initValues.containsKey(name)) ? widget.initValues[name] as String : null,
|
...widget.parameters.map((param) {
|
||||||
name: name,
|
final textEditingController = TextEditingController(text: values[param.name] ?? param.value?.toString());
|
||||||
decoration: InputDecoration(
|
return TypeAheadFormField<Suggestion>(
|
||||||
labelText: name,
|
key: Key(param.description),
|
||||||
),
|
textFieldConfiguration: TextFieldConfiguration(
|
||||||
validator: FormBuilderValidators.compose([
|
autofocus: true,
|
||||||
FormBuilderValidators.required(context),
|
controller: textEditingController,
|
||||||
]),
|
enableSuggestions: widget.candidate is Reaction,
|
||||||
keyboardType: (widget.initValues.containsKey(name)) && widget.initValues[name] is int ? TextInputType.number : null,
|
decoration: InputDecoration(
|
||||||
)),
|
labelText: ReCase(param.name).titleCase,
|
||||||
|
helperText: param.description
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onSaved: (value) {
|
||||||
|
values[param.name] = value!;
|
||||||
|
},
|
||||||
|
suggestionsBoxDecoration: const SuggestionsBoxDecoration(
|
||||||
|
elevation: 6,
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(10))
|
||||||
|
),
|
||||||
|
validator: FormBuilderValidators.compose([
|
||||||
|
FormBuilderValidators.required(context),
|
||||||
|
]),
|
||||||
|
hideOnEmpty: true,
|
||||||
|
suggestionsCallback: (suggestion) => getSuggestions(suggestion, catalogue),
|
||||||
|
onSuggestionSelected: (suggestion) {
|
||||||
|
textEditingController.text += suggestion.toString();
|
||||||
|
textEditingController.text += "}";
|
||||||
|
values[param.name] = textEditingController.text;
|
||||||
|
},
|
||||||
|
itemBuilder: (context, suggestion) => ListTile(
|
||||||
|
isThreeLine: true,
|
||||||
|
dense: true,
|
||||||
|
leading: suggestion.item3.service.getLogo(logoSize: 30),
|
||||||
|
title: Text(suggestion.item2.name),
|
||||||
|
subtitle: Text("${suggestion.item2.description}, from '${suggestion.item3.displayName()}'")
|
||||||
|
));}),
|
||||||
...[
|
...[
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
child: Text(AppLocalizations.of(context).save),
|
child: Text(AppLocalizations.of(context).save),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_formKey.currentState!.save();
|
|
||||||
if (_formKey.currentState!.validate()) {
|
if (_formKey.currentState!.validate()) {
|
||||||
widget.onValidate(_formKey.currentState!.value.map((key, value) => MapEntry(key, value)));
|
_formKey.currentState!.save();
|
||||||
|
widget.onValidate(values);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -61,6 +151,6 @@ class _ActionFormState extends State<ActionForm> {
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ class HomePageSortMenu extends StatelessWidget {
|
|||||||
value: !split),
|
value: !split),
|
||||||
],
|
],
|
||||||
onSelected: (sortingMethod) {
|
onSelected: (sortingMethod) {
|
||||||
/// TODO: not clean
|
|
||||||
if (sortingMethod is bool) {
|
if (sortingMethod is bool) {
|
||||||
collectionProvider.splitDisabled = sortingMethod;
|
collectionProvider.splitDisabled = sortingMethod;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ import 'package:flutter/widgets.dart';
|
|||||||
import 'package:reorderables/reorderables.dart';
|
import 'package:reorderables/reorderables.dart';
|
||||||
|
|
||||||
class ReorderableReactionCardsList extends StatefulWidget {
|
class ReorderableReactionCardsList extends StatefulWidget {
|
||||||
ReorderableReactionCardsList(
|
const ReorderableReactionCardsList(
|
||||||
{Key? key,
|
{Key? key,
|
||||||
required this.onReorder,
|
required this.onReorder,
|
||||||
required this.reactionList,
|
required this.reactionList,
|
||||||
required this.itemBuilder})
|
required this.itemBuilder})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
List<Reaction> reactionList;
|
final List<Reaction> reactionList;
|
||||||
|
|
||||||
// Callback when a list has been reordered
|
// Callback when a list has been reordered
|
||||||
final void Function() onReorder;
|
final void Function() onReorder;
|
||||||
|
|||||||
118
mobile/lib/src/widgets/setup_api_route.dart
Normal file
118
mobile/lib/src/widgets/setup_api_route.dart
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
import 'package:aeris/src/aeris_api.dart';
|
||||||
|
import 'package:aeris/src/providers/action_catalogue_provider.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||||
|
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||||
|
import 'package:get_it/get_it.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
/// Floating Action button to access the setup API route modal
|
||||||
|
class SetupAPIRouteButton extends StatefulWidget {
|
||||||
|
///Can the app access the api with the current baseRoute?
|
||||||
|
bool connected;
|
||||||
|
void Function() onSetup;
|
||||||
|
SetupAPIRouteButton(
|
||||||
|
{Key? key, required this.connected, required this.onSetup})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SetupAPIRouteButton> createState() => _SetupAPIRouteButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SetupAPIRouteButtonState extends State<SetupAPIRouteButton> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FloatingActionButton(
|
||||||
|
onPressed: () => showDialog(
|
||||||
|
context: context, builder: (_) => const SetupAPIRouteModal())
|
||||||
|
.then((_) => widget.onSetup()),
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||||
|
elevation: 10,
|
||||||
|
child: Icon(widget.connected == true
|
||||||
|
? Icons.wifi
|
||||||
|
: Icons.signal_cellular_connected_no_internet_0_bar_sharp),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Modal to setup route to connect to api
|
||||||
|
class SetupAPIRouteModal extends StatefulWidget {
|
||||||
|
const SetupAPIRouteModal({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SetupAPIRouteModal> createState() => _SetupAPIRouteModalState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SetupAPIRouteModalState extends State<SetupAPIRouteModal> {
|
||||||
|
bool? connected;
|
||||||
|
final _formKey = GlobalKey<FormBuilderState>();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
GetIt.I<AerisAPI>().getAbout().then((value) {
|
||||||
|
setState(() {
|
||||||
|
connected = value.isNotEmpty;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text(AppLocalizations.of(context).setupAPIRoute),
|
||||||
|
content: FormBuilder(
|
||||||
|
key: _formKey,
|
||||||
|
child: FormBuilderTextField(
|
||||||
|
initialValue: GetIt.I<AerisAPI>().baseRoute,
|
||||||
|
name: "route",
|
||||||
|
validator: FormBuilderValidators.required(context),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: AppLocalizations.of(context).routeToApi,
|
||||||
|
helperText: "Ex: http://host:port")),
|
||||||
|
),
|
||||||
|
actionsAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
actions: [
|
||||||
|
ElevatedButton(
|
||||||
|
child: Text(AppLocalizations.of(context).tryToConnect),
|
||||||
|
onPressed: () {
|
||||||
|
_formKey.currentState!.save();
|
||||||
|
if (_formKey.currentState!.validate()) {
|
||||||
|
var route = _formKey.currentState!.value['route'];
|
||||||
|
if (Uri.tryParse(route) == null) {
|
||||||
|
setState(() => connected = false);
|
||||||
|
} else {
|
||||||
|
final oldRoute = GetIt.I<AerisAPI>().baseRoute;
|
||||||
|
GetIt.I<AerisAPI>().baseRoute = route;
|
||||||
|
setState(() {
|
||||||
|
connected = null;
|
||||||
|
});
|
||||||
|
GetIt.I<AerisAPI>().getAbout().then((value) {
|
||||||
|
setState(() {
|
||||||
|
connected = value.isNotEmpty;
|
||||||
|
});
|
||||||
|
}, onError: (_) => GetIt.I<AerisAPI>().baseRoute = oldRoute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
child: Text(connected == null
|
||||||
|
? AppLocalizations.of(context).loading
|
||||||
|
: connected == true
|
||||||
|
? AppLocalizations.of(context).save
|
||||||
|
: AppLocalizations.of(context).invalidUrl),
|
||||||
|
onPressed: connected == true
|
||||||
|
? () {
|
||||||
|
GetIt.I<SharedPreferences>()
|
||||||
|
.setString('api', GetIt.I<AerisAPI>().baseRoute);
|
||||||
|
Provider.of<ActionCatalogueProvider>(context, listen: false).reloadCatalogue();
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -181,6 +181,27 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.1.0"
|
version: "7.1.0"
|
||||||
|
flutter_keyboard_visibility:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_keyboard_visibility
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "5.2.0"
|
||||||
|
flutter_keyboard_visibility_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_keyboard_visibility_platform_interface
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
|
flutter_keyboard_visibility_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_keyboard_visibility_web
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
flutter_launcher_icons:
|
flutter_launcher_icons:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@@ -233,6 +254,13 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_typeahead:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_typeahead
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.4"
|
||||||
flutter_web_plugins:
|
flutter_web_plugins:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -260,7 +288,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "7.2.0"
|
version: "7.2.0"
|
||||||
http:
|
http:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: http
|
name: http
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
@@ -308,6 +336,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.1"
|
version: "3.0.1"
|
||||||
|
loading_indicator:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: loading_indicator
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.3"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -358,7 +393,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.8.0"
|
version: "1.8.0"
|
||||||
path_provider:
|
path_provider:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: path_provider
|
name: path_provider
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
@@ -434,6 +469,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.2"
|
||||||
|
positioned_tap_detector_2:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: positioned_tap_detector_2
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.4"
|
||||||
process:
|
process:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -476,6 +518,62 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.27.3"
|
version: "0.27.3"
|
||||||
|
shared_preferences:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: shared_preferences
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.13"
|
||||||
|
shared_preferences_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_android
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.11"
|
||||||
|
shared_preferences_ios:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_ios
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
|
shared_preferences_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_linux
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
|
shared_preferences_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_macos
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.3"
|
||||||
|
shared_preferences_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_platform_interface
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
|
shared_preferences_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_web
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.3"
|
||||||
|
shared_preferences_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_windows
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
shimmer:
|
shimmer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -483,6 +581,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "2.0.0"
|
||||||
|
simple_autocomplete_formfield:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: simple_autocomplete_formfield
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.0"
|
||||||
skeleton_loader:
|
skeleton_loader:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -558,6 +663,20 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.8"
|
version: "0.4.8"
|
||||||
|
textfield_state:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: textfield_state
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.0"
|
||||||
|
tuple:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: tuple
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -53,6 +53,14 @@ dependencies:
|
|||||||
skeleton_loader: ^2.0.0+4
|
skeleton_loader: ^2.0.0+4
|
||||||
drag_and_drop_lists: ^0.3.2+2
|
drag_and_drop_lists: ^0.3.2+2
|
||||||
reorderables: ^0.4.2
|
reorderables: ^0.4.2
|
||||||
|
path_provider: ^2.0.9
|
||||||
|
http: ^0.13.4
|
||||||
|
tuple: ^2.0.0
|
||||||
|
loading_indicator: ^3.0.3
|
||||||
|
shared_preferences: ^2.0.13
|
||||||
|
flutter_typeahead: ^3.2.4
|
||||||
|
simple_autocomplete_formfield: ^0.3.0
|
||||||
|
positioned_tap_detector_2: ^1.0.4
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_launcher_icons: "^0.9.2"
|
flutter_launcher_icons: "^0.9.2"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:aeris/src/main.dart';
|
import 'package:aeris/main.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||||
|
|||||||
Reference in New Issue
Block a user