├── .github
└── workflows
│ └── deploy.yaml
├── .gitignore
├── .metadata
├── .vscode
└── launch.json
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── RELEASE_NOTES.md
├── android
├── .gitignore
├── app
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src
│ │ ├── debug
│ │ └── AndroidManifest.xml
│ │ ├── main
│ │ ├── AndroidManifest.sample.xml
│ │ ├── ic_launcher-playstore.png
│ │ ├── kotlin
│ │ │ └── com
│ │ │ │ └── example
│ │ │ │ └── aninforme
│ │ │ │ └── MainActivity.kt
│ │ └── res
│ │ │ ├── drawable-anydpi-v24
│ │ │ └── notification_icon.xml
│ │ │ ├── drawable-anydpi
│ │ │ └── app_icon.xml
│ │ │ ├── drawable-hdpi
│ │ │ ├── app_icon.png
│ │ │ └── notification_icon.png
│ │ │ ├── drawable-mdpi
│ │ │ ├── app_icon.png
│ │ │ └── notification_icon.png
│ │ │ ├── drawable-v21
│ │ │ └── launch_background.xml
│ │ │ ├── drawable-xhdpi
│ │ │ ├── app_icon.png
│ │ │ └── notification_icon.png
│ │ │ ├── drawable-xxhdpi
│ │ │ ├── app_icon.png
│ │ │ └── notification_icon.png
│ │ │ ├── drawable-xxxhdpi
│ │ │ └── app_icon.png
│ │ │ ├── drawable
│ │ │ └── launch_background.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ │ ├── mipmap-hdpi
│ │ │ └── launcher_icon.png
│ │ │ ├── mipmap-mdpi
│ │ │ └── launcher_icon.png
│ │ │ ├── mipmap-xhdpi
│ │ │ └── launcher_icon.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ └── launcher_icon.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ └── launcher_icon.png
│ │ │ ├── values-night
│ │ │ └── styles.xml
│ │ │ └── values
│ │ │ ├── ic_launcher_background.xml
│ │ │ └── styles.xml
│ │ └── profile
│ │ └── AndroidManifest.xml
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
├── settings.gradle
└── settings_aar.gradle
├── assets
├── google_sign_in.png
├── q.png
└── sources-icons
│ ├── ann.png
│ ├── livechart.png
│ ├── mal.png
│ └── reddit.png
├── build_script.dart
├── ios
├── .gitignore
├── Flutter
│ ├── AppFrameworkInfo.plist
│ ├── Debug.xcconfig
│ └── Release.xcconfig
├── Runner.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── WorkspaceSettings.xcsettings
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── WorkspaceSettings.xcsettings
└── Runner
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── Icon-App-1024x1024@1x.png
│ │ ├── Icon-App-20x20@1x.png
│ │ ├── Icon-App-20x20@2x.png
│ │ ├── Icon-App-20x20@3x.png
│ │ ├── Icon-App-29x29@1x.png
│ │ ├── Icon-App-29x29@2x.png
│ │ ├── Icon-App-29x29@3x.png
│ │ ├── Icon-App-40x40@1x.png
│ │ ├── Icon-App-40x40@2x.png
│ │ ├── Icon-App-40x40@3x.png
│ │ ├── Icon-App-60x60@2x.png
│ │ ├── Icon-App-60x60@3x.png
│ │ ├── Icon-App-76x76@1x.png
│ │ ├── Icon-App-76x76@2x.png
│ │ └── Icon-App-83.5x83.5@2x.png
│ └── LaunchImage.imageset
│ │ ├── Contents.json
│ │ ├── LaunchImage.png
│ │ ├── LaunchImage@2x.png
│ │ ├── LaunchImage@3x.png
│ │ └── README.md
│ ├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
│ ├── Info.plist
│ └── Runner-Bridging-Header.h
├── lib
├── main.dart
└── src
│ ├── components
│ ├── admob
│ │ ├── admob.controller.dart
│ │ ├── admob.model.dart
│ │ └── index.dart
│ ├── animelist
│ │ ├── animelist.controller.dart
│ │ ├── animelist.event.dart
│ │ ├── animelist.model.dart
│ │ └── index.dart
│ ├── cloud-backup
│ │ ├── cloud-backup.controller.dart
│ │ ├── cloud-backup.events.dart
│ │ ├── cloud-backup.model.dart
│ │ └── index.dart
│ ├── feed
│ │ ├── feed.controller.dart
│ │ ├── feed.model.dart
│ │ └── index.dart
│ ├── filter
│ │ ├── filter.controller.dart
│ │ ├── filter.model.dart
│ │ ├── filter.types.dart
│ │ └── index.dart
│ ├── google-flow
│ │ ├── google-flow.controller.dart
│ │ ├── google-flow.model.dart
│ │ └── index.dart
│ ├── index.dart
│ ├── integration
│ │ ├── index.dart
│ │ ├── integration.controller.dart
│ │ ├── integration.events.dart
│ │ └── integration.model.dart
│ ├── notification
│ │ ├── index.dart
│ │ ├── notification.controller.dart
│ │ └── notification.model.dart
│ ├── sources
│ │ ├── index.dart
│ │ ├── source.news.dart
│ │ ├── sources.controller.dart
│ │ └── sources.model.dart
│ ├── supporter-subscription
│ │ ├── index.dart
│ │ ├── supporter-subscription.controller.dart
│ │ └── supporter-subscription.model.dart
│ └── topic
│ │ ├── index.dart
│ │ ├── topic.controller.dart
│ │ └── topic.model.dart
│ ├── core
│ ├── admob_units.dart
│ ├── api_key.sample.dart
│ ├── config.dart
│ ├── controllers.dart
│ ├── in-app-purchase.sample.dart
│ ├── index.dart
│ ├── initializer.dart
│ ├── mal.client.sample.dart
│ ├── persistence.dart
│ ├── services.dart
│ └── version.dart
│ ├── data
│ ├── backup.dart
│ ├── feed.response.dart
│ ├── firebase.topics.dart
│ ├── index.dart
│ ├── jwt-token.profile.dart
│ ├── mal-token.dart
│ ├── mal-user.animelist.dart
│ ├── mal-user.animeupdate.dart
│ ├── mal-user.profile.dart
│ └── response.all_anime.dart
│ ├── misc
│ ├── date.dart
│ ├── feed-icons.dart
│ ├── index.dart
│ └── keys.dart
│ ├── notification
│ ├── app_lifecycle_handler.dart
│ ├── fcm_handler.dart
│ ├── index.dart
│ ├── notification.firebase.dart
│ └── notification.local.dart
│ ├── services
│ ├── concretes
│ │ ├── api.service.dart
│ │ ├── google-api.service.dart
│ │ ├── index.dart
│ │ └── mal.service.dart
│ ├── fcm.service.dart
│ ├── index.dart
│ ├── interface
│ │ ├── api.interface.dart
│ │ ├── google-api.interface.dart
│ │ ├── index.dart
│ │ └── mal.interface.dart
│ └── mocks
│ │ ├── api.mock-service.dart
│ │ ├── google-api.mock-service.dart
│ │ ├── index.dart
│ │ └── mal.mock-service.dart
│ └── widgets
│ ├── app.dart
│ ├── async-loading.dart
│ ├── badge.dart
│ ├── button.dart
│ ├── colors.dart
│ ├── empty.dart
│ ├── icon_button.dart
│ ├── index.dart
│ ├── inputs
│ ├── checkbox.dart
│ ├── custom.switch.dart
│ └── index.dart
│ ├── listing
│ ├── ad.feed_tab.dart
│ ├── ad.library_tab.dart
│ ├── ad.sources_tab.dart
│ ├── anime.item-integration.dart
│ ├── anime.item.dart
│ ├── anime.list.dart
│ ├── feed.item.dart
│ ├── index.dart
│ ├── menu-item.dart
│ └── news-source.item.dart
│ ├── loader.dart
│ ├── menu_actions
│ ├── filter_bottom_sheet
│ │ ├── bottom_menu.display.dart
│ │ ├── bottom_menu.order.dart
│ │ ├── bottom_menu.sort.dart
│ │ ├── bottom_menu.status.dart
│ │ ├── bottom_sheet.dart
│ │ └── index.dart
│ ├── index.dart
│ ├── more_page_menu
│ │ ├── index.dart
│ │ ├── menu.backup-restore.dart
│ │ ├── menu.github.dart
│ │ ├── menu.licenses.dart
│ │ ├── menu.mal-integration.dart
│ │ └── menu.support-dev.dart
│ ├── news_guide
│ │ ├── guide.item.dart
│ │ ├── index.dart
│ │ └── sources_guide_prompt.dart
│ └── refresh_anime_list.dart
│ ├── pages
│ ├── core.dashboard.dart
│ ├── index.dart
│ ├── page.feed.dart
│ ├── page.library.dart
│ ├── page.more.dart
│ └── page.sources.dart
│ ├── refreshing.dart
│ ├── search.dart
│ ├── shadow.dart
│ ├── syncing
│ ├── cloud-backup-restore.dart
│ ├── index.dart
│ ├── integration.mal.dart
│ └── mal-updater.dart
│ ├── tabview.dart
│ ├── thumbnail.dart
│ └── toast.dart
├── pubspec.lock
├── pubspec.yaml
└── test
└── interface_test.dart
/.github/workflows/deploy.yaml:
--------------------------------------------------------------------------------
1 | name: deploy
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*'
7 |
8 | jobs:
9 | build:
10 | if: github.event.base_ref == 'refs/heads/release' # if tag is created in release branch
11 | name: Build APK
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v1
15 | - uses: actions/setup-java@v1
16 | with:
17 | java-version: '12.x'
18 | - run: echo $ANDROID_MANIFEST | base64 -d > android/app/src/main/AndroidManifest.xml
19 | env:
20 | ANDROID_MANIFEST: ${{ secrets.ANDROID_MANIFEST }}
21 | - run: echo $GOOGLE_SERVICES | base64 -d > android/app/google-services.json
22 | env:
23 | GOOGLE_SERVICES: ${{ secrets.GOOGLE_SERVICES }}
24 | - run: echo $SIGNING_KEY | base64 -d > android/app/key.jks
25 | env:
26 | SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
27 | - run: echo $SIGNING_PROPERTIES | base64 -d > android/key.properties
28 | env:
29 | SIGNING_PROPERTIES: ${{ secrets.SIGNING_PROPERTIES }}
30 | - run: echo $ADMOB_UNITS_CONFIG | base64 -d > lib/src/core/admob_units.dart
31 | env:
32 | ADMOB_UNITS_CONFIG: ${{ secrets.ADMOB_UNITS_CONFIG }}
33 | - run: echo $GOOGLE_PLAY_SERVICE_ACCOUNT | base64 -d > service_account.json
34 | env:
35 | GOOGLE_PLAY_SERVICE_ACCOUNT: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT }}
36 | - run: echo $IN_APP_PURCHASE_CONFIG | base64 -d > lib/src/core/in-app-purchase.dart
37 | env:
38 | IN_APP_PURCHASE_CONFIG: ${{ secrets.IN_APP_PURCHASE_CONFIG }}
39 | - run: echo $MAL_CLIENT_CONFIG | base64 -d > lib/src/core/mal.client.dart
40 | env:
41 | MAL_CLIENT_CONFIG: ${{ secrets.MAL_CLIENT_CONFIG }}
42 | - run: echo $SERVICES_DART_CODE | base64 -d > lib/src/core/services.dart
43 | env:
44 | SERVICES_DART_CODE: ${{ secrets.SERVICES_DART_CODE }}
45 | - run: echo $API_KEY | base64 -d > lib/src/core/api_key.dart
46 | env:
47 | API_KEY: ${{ secrets.API_KEY }}
48 | - uses: subosito/flutter-action@v1
49 | with:
50 | flutter-version: '2.10.4'
51 | - run: flutter pub get
52 | - run: flutter test
53 | - run: flutter build apk --release --build-name=${GITHUB_REF#refs/*/} --build-number=$GITHUB_RUN_NUMBER --obfuscate --split-debug-info=build/app/outputs/mapping/
54 | - run: flutter build appbundle --release --build-name=${GITHUB_REF#refs/*/} --build-number=$GITHUB_RUN_NUMBER --obfuscate --split-debug-info=build/app/outputs/mapping/
55 | - run: dart build_script.dart
56 | - run: mv "build/app/outputs/apk/release/app-release.apk" "build/app/outputs/apk/release/quantz.${GITHUB_REF#refs/*/}.apk"
57 | - name: Upload APK/s
58 | uses: ncipollo/release-action@v1.8.3
59 | with:
60 | artifacts: "build/app/outputs/apk/release/*.apk"
61 | bodyFile: RELEASE_NOTES.md
62 | token: ${{ secrets.GITHUB_TOKEN }}
63 | allowUpdates: true
64 | prerelease: ${{ secrets.IS_PRE_RELEASE == 'true' }}
65 | - name: Upload .aab to Google Play
66 | uses: r0adkll/upload-google-play@v1.0.15
67 | with:
68 | serviceAccountJson: service_account.json
69 | packageName: dev.xamantra.quantz
70 | releaseFiles: build/app/outputs/bundle/release/app-release.aab
71 | track: production
72 | whatsNewDirectory: release_notes/
73 | mappingFile: build/app/outputs/mapping/release/mapping.txt
74 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # The .vscode folder contains launch configuration and tasks you configure in
19 | # VS Code which you may wish to be included in version control, so this line
20 | # is commented out by default.
21 | #.vscode/
22 |
23 | # Flutter/Dart/Pub related
24 | **/doc/api/
25 | **/ios/Flutter/.last_build_id
26 | .dart_tool/
27 | .flutter-plugins
28 | .flutter-plugins-dependencies
29 | .packages
30 | .pub-cache/
31 | .pub/
32 | /build/
33 |
34 | # Web related
35 | lib/generated_plugin_registrant.dart
36 |
37 | # Symbolication related
38 | app.*.symbols
39 |
40 | # Obfuscation related
41 | app.*.map.json
42 |
43 | # Android Studio will place build artifacts here
44 | /android/app/debug
45 | /android/app/profile
46 | /android/app/release
47 |
48 | # credentials
49 | google-services.json
50 | key.properties
51 | key.jks
52 | .secrets
53 | *.ignore.*
54 | android/app/src/main/AndroidManifest.xml
55 | ./personal/
56 | pepk.jar
57 | private_key.pepk
58 | quantz_key.zip
59 | lib/src/core/admob_units.prod.dart
60 | lib/src/core/in-app-purchase.dart
61 | android/app/google-services.prod.json
62 | android/app/google-services.dev.json
63 | lib/src/core/mal.client.dart
64 | android/app/google-services.prev.json
65 | lib/src/core/api_key.dart
66 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: c5a4b4029c0798f37c4a39b479d7cb75daa7b05c
8 | channel: stable
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Run - Debug",
9 | "request": "launch",
10 | "type": "dart",
11 | "flutterMode": "debug",
12 | "args": [
13 | "-v"
14 | ]
15 | },
16 | {
17 | "name": "Run - Release",
18 | "request": "launch",
19 | "type": "dart",
20 | "flutterMode": "release",
21 | "args": [
22 | "-v"
23 | ]
24 | },
25 | {
26 | "name": "Run - Profile",
27 | "request": "launch",
28 | "type": "dart",
29 | "flutterMode": "profile",
30 | "args": [
31 | "-v"
32 | ]
33 | }
34 | ]
35 | }
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | A lot of files in this project are not commited into the repo and you have to set them up properly first.
2 |
3 | - `android/app/src/main/AndroidManifest.xml` - rename the sample file provided in the same path.
4 | - `android/app/google-services.json` - create this from Firebase console. This is for push notification and google sign-in.
5 | - `android/app/key.jks` - apk signing key, [you can create this by yourself](https://developer.android.com/studio/publish/app-signing).
6 | - `android/key.properties` - credentials for the key above. [guide here](https://developer.android.com/studio/publish/app-signing#secure-shared-keystore).
7 | - `lib/src/core/in-app-purchase.dart` - rename the sample file provided in the same path.
8 | - `lib/src/core/mal.client.dart` - rename the sample file provided in the same path.
9 | - `lib/src/core/api_key.dart` - rename the sample file provided in the same path.
10 |
11 | Once you have all the files set up. You can try to run `flutter run` or `flutter build`
12 |
13 | **NOTE**: This flutter app has not been set up for iOS yet.
14 |
15 | # Backend API
16 | The backend for this app is currently closed-source. I provided mock data using mock services in the code. You can test the functions through them instead.
17 |
18 | I will also try to update the mock data from time to time.
19 |
20 | # Push Notifications
21 | You can send push notification through FCM api.
22 |
23 | # Contributions
24 | Since the backend is closed-source, the type of contribution you'll probably be able to do are mostly frontend stuffs. Like design improvements or user-experience.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Quantz
5 | Push Notification service for anime episodes and news.
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | # Features
19 | - **Sub and dub** - get notified with latest anime episodes on the internet.
20 | - **Ongoing and upcoming** - get notified with latest episode for ongoing series or first episodes of upcoming shows.
21 | - **Chinese series** - this app also gets you notified with them too.
22 | - **News** - anime and manga news updates.
23 | - **Follow/unfollow** - follow or unfollow specific series or news site to get notified.
24 | - **MyAnimeList integration** - if you are a MAL user, this app will make it convenient for you to track episodes.
25 |
26 | # Why this app?
27 | There are two types of episode updates. **Japan TV schedule** and **actual episode uploads** on the internet. This app do the *latter*.
28 | - **Japan TV schedule** - most apps out there do this one. I've been there. I used them and they're not really helpful. I get an episode notification but it's Japan TV schedule and not actual availability on the internet.
29 | - **Actual episodes uploads** - with this app, you get updates based on actual episodes being uploaded on the internet.
30 |
31 | The target users of this app are those anime fans not living in Japan. And that is most of us, I'm pretty sure :)
32 |
33 | This app is inspired by [r/anime Subreddit's Episode BOT](https://www.reddit.com/user/AutoLovepon/) - this bot gets update from legal sites and anime torrent sites.
34 |
35 | # Disclaimer
36 | - This app is not a streaming service. You can't watch anime in here. This is just for quick updates.
37 | - The app does not tell you what website you can find an episode. But instead you can browse list of legal streaming sites from the app.
38 | - This app's server gets an update from a certain pirate site for episode updates **but the app itself does not pirate any content**.
39 | - Only the mobile app is open-source (for now). The backend server is currently closed-source.
40 |
41 | # Develop
42 | Visit the [contributing guide](https://github.com/xamantra/quantz-app/blob/release/CONTRIBUTING.md) to set up the project.
43 |
44 | This app is made with [Flutter](https://flutter.dev/).
45 |
46 | # Support
47 | If you're using the app and liking it so far. Consider supporting the developer by donating from the mobile app through google play in-app purchase. It's a $1 per month subscription.
48 |
49 | By donating, you'll receive push notifications 15 minutes earlier. Non-supporter have 15 minutes delay for push notifications.
50 |
51 | There's also a posibility for me to build and publish an **iOS** version if donations go well. This app's source code is cross-platform.
52 |
--------------------------------------------------------------------------------
/RELEASE_NOTES.md:
--------------------------------------------------------------------------------
1 | - Fixed infinite loading issue when the app is opened for the first time.
2 | - Fixed text overflow with anime title on MAL updater dialog.
3 | - Fixed issue with MAL updater for dub entries.
4 | - Added sub or dub indicator in MAL updater dialog.
5 | - Fixed MAL syncing bug.
6 | - Improve loading speed in splash screen.
7 | - Fixed issue with news feed not properly caching.
8 | - Improved user experience.
9 | - Fixed an issue with ads.
10 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | # key.properties
12 |
--------------------------------------------------------------------------------
/android/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | ## Gson rules
2 | # Gson uses generic type information stored in a class file when working with fields. Proguard
3 | # removes such information by default, so configure it to keep all of it.
4 | -keepattributes Signature
5 |
6 | # For using GSON @Expose annotation
7 | -keepattributes *Annotation*
8 |
9 | # Gson specific classes
10 | -dontwarn sun.misc.**
11 | #-keep class com.google.gson.stream.** { *; }
12 |
13 | # Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory,
14 | # JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
15 | -keep class * extends com.google.gson.TypeAdapter
16 | -keep class * implements com.google.gson.TypeAdapterFactory
17 | -keep class * implements com.google.gson.JsonSerializer
18 | -keep class * implements com.google.gson.JsonDeserializer
19 |
20 | # Prevent R8 from leaving Data object members always null
21 | -keepclassmembers,allowobfuscation class * {
22 | @com.google.gson.annotations.SerializedName ;
23 | }
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.sample.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
9 |
16 |
20 |
24 |
29 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
47 |
50 |
51 |
52 |
54 |
57 |
60 |
63 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/android/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/android/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/example/aninforme/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package dev.xamantra.quantz
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-anydpi-v24/notification_icon.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
12 |
13 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-anydpi/app_icon.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
11 |
12 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/app_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/android/app/src/main/res/drawable-hdpi/app_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/notification_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/android/app/src/main/res/drawable-hdpi/notification_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/app_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/android/app/src/main/res/drawable-mdpi/app_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/notification_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/android/app/src/main/res/drawable-mdpi/notification_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/app_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/android/app/src/main/res/drawable-xhdpi/app_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/notification_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/android/app/src/main/res/drawable-xhdpi/notification_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/app_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/android/app/src/main/res/drawable-xxhdpi/app_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/notification_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/android/app/src/main/res/drawable-xxhdpi/notification_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/app_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/android/app/src/main/res/drawable-xxxhdpi/app_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/android/app/src/main/res/mipmap-hdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/android/app/src/main/res/mipmap-mdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #4D79CC
4 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.6.10'
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:4.1.3'
10 | classpath 'com.google.gms:google-services:4.3.8'
11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | google()
18 | mavenCentral()
19 | }
20 | }
21 |
22 | rootProject.buildDir = '../build'
23 | subprojects {
24 | project.buildDir = "${rootProject.buildDir}/${project.name}"
25 | }
26 | subprojects {
27 | project.evaluationDependsOn(':app')
28 | }
29 |
30 | task clean(type: Delete) {
31 | delete rootProject.buildDir
32 | }
33 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jun 23 08:50:38 CEST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
7 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
4 | def properties = new Properties()
5 |
6 | assert localPropertiesFile.exists()
7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
8 |
9 | def flutterSdkPath = properties.getProperty("flutter.sdk")
10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
12 |
--------------------------------------------------------------------------------
/android/settings_aar.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/assets/google_sign_in.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/assets/google_sign_in.png
--------------------------------------------------------------------------------
/assets/q.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/assets/q.png
--------------------------------------------------------------------------------
/assets/sources-icons/ann.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/assets/sources-icons/ann.png
--------------------------------------------------------------------------------
/assets/sources-icons/livechart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/assets/sources-icons/livechart.png
--------------------------------------------------------------------------------
/assets/sources-icons/mal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/assets/sources-icons/mal.png
--------------------------------------------------------------------------------
/assets/sources-icons/reddit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/assets/sources-icons/reddit.png
--------------------------------------------------------------------------------
/build_script.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | void main() async {
4 | await copyReleaseNotesForGooglePlay();
5 | }
6 |
7 | const googlePlayReleaseNotesFolder = './release_notes/';
8 | const googlePlayReleaseNotesFileName = 'whatsnew-en-US';
9 | const githubReleaseNotesFileName = './RELEASE_NOTES.md';
10 |
11 | Future copyReleaseNotesForGooglePlay() async {
12 | final contents = await File(githubReleaseNotesFileName).readAsString();
13 | await Directory(googlePlayReleaseNotesFolder).create(recursive: true);
14 | await File(googlePlayReleaseNotesFolder + googlePlayReleaseNotesFileName).writeAsString(contents);
15 | }
16 |
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | *.mode1v3
2 | *.mode2v3
3 | *.moved-aside
4 | *.pbxuser
5 | *.perspectivev3
6 | **/*sync/
7 | .sconsign.dblite
8 | .tags*
9 | **/.vagrant/
10 | **/DerivedData/
11 | Icon?
12 | **/Pods/
13 | **/.symlinks/
14 | profile
15 | xcuserdata
16 | **/.generated/
17 | Flutter/App.framework
18 | Flutter/Flutter.framework
19 | Flutter/Flutter.podspec
20 | Flutter/Generated.xcconfig
21 | Flutter/app.flx
22 | Flutter/app.zip
23 | Flutter/flutter_assets/
24 | Flutter/flutter_export_environment.sh
25 | ServiceDefinitions.json
26 | Runner/GeneratedPluginRegistrant.*
27 |
28 | # Exceptions to above rules.
29 | !default.mode1v3
30 | !default.mode2v3
31 | !default.pbxuser
32 | !default.perspectivev3
33 |
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 8.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | quantz
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | $(FLUTTER_BUILD_NAME)
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(FLUTTER_BUILD_NUMBER)
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UISupportedInterfaceOrientations
30 |
31 | UIInterfaceOrientationPortrait
32 | UIInterfaceOrientationLandscapeLeft
33 | UIInterfaceOrientationLandscapeRight
34 |
35 | UISupportedInterfaceOrientations~ipad
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationPortraitUpsideDown
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | UIViewControllerBasedStatusBarAppearance
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:momentum/momentum.dart';
3 |
4 | import 'src/core/index.dart';
5 | import 'src/widgets/index.dart';
6 |
7 | void main() {
8 | WidgetsFlutterBinding.ensureInitialized();
9 | runApp(momentum());
10 | }
11 |
12 | Momentum momentum() {
13 | return Momentum(
14 | child: MyApp(),
15 | key: UniqueKey(),
16 | appLoader: Loader(),
17 | controllers: controllers(),
18 | services: services(),
19 | initializer: initializer,
20 | persistSave: persistSave,
21 | persistGet: persistGet,
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/lib/src/components/admob/admob.model.dart:
--------------------------------------------------------------------------------
1 | import 'package:google_mobile_ads/google_mobile_ads.dart';
2 | import 'package:momentum/momentum.dart';
3 |
4 | import 'index.dart';
5 |
6 | class AdmobModel extends MomentumModel {
7 | AdmobModel(
8 | AdmobController controller, {
9 | required this.libraryTabAd,
10 | required this.showLibraryTabAd,
11 | required this.sourcesTabAd,
12 | required this.showSourcesTabAd,
13 | required this.feedTabAd,
14 | required this.showFeedTabAd,
15 | required this.lastTimeUserClickedAnAd,
16 | }) : super(controller);
17 |
18 | final BannerAd libraryTabAd;
19 | final bool showLibraryTabAd;
20 |
21 | final BannerAd sourcesTabAd;
22 | final bool showSourcesTabAd;
23 |
24 | final BannerAd feedTabAd;
25 | final bool showFeedTabAd;
26 |
27 | final int lastTimeUserClickedAnAd;
28 |
29 | @override
30 | void update({
31 | BannerAd? libraryTabAd,
32 | bool? showLibraryTabAd,
33 | BannerAd? sourcesTabAd,
34 | bool? showSourcesTabAd,
35 | BannerAd? feedTabAd,
36 | bool? showFeedTabAd,
37 | int? lastTimeUserClickedAnAd,
38 | }) {
39 | AdmobModel(
40 | controller,
41 | libraryTabAd: libraryTabAd ?? this.libraryTabAd,
42 | showLibraryTabAd: showLibraryTabAd ?? this.showLibraryTabAd,
43 | sourcesTabAd: sourcesTabAd ?? this.sourcesTabAd,
44 | showSourcesTabAd: showSourcesTabAd ?? this.showSourcesTabAd,
45 | feedTabAd: feedTabAd ?? this.feedTabAd,
46 | showFeedTabAd: showFeedTabAd ?? this.showFeedTabAd,
47 | lastTimeUserClickedAnAd: lastTimeUserClickedAnAd ?? this.lastTimeUserClickedAnAd,
48 | ).updateMomentum();
49 | }
50 |
51 | Map toJson() {
52 | return {
53 | "lastTimeUserClickedAnAd": lastTimeUserClickedAnAd,
54 | };
55 | }
56 |
57 | AdmobModel? fromJson(Map? json) {
58 | if (json == null) return null;
59 | return AdmobModel(
60 | controller,
61 | libraryTabAd: libraryTabAd,
62 | showLibraryTabAd: showLibraryTabAd,
63 | sourcesTabAd: sourcesTabAd,
64 | showSourcesTabAd: showSourcesTabAd,
65 | feedTabAd: feedTabAd,
66 | showFeedTabAd: showFeedTabAd,
67 | lastTimeUserClickedAnAd: json['lastTimeUserClickedAnAd'] ?? 0,
68 | );
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/lib/src/components/admob/index.dart:
--------------------------------------------------------------------------------
1 | export 'admob.controller.dart';
2 | export 'admob.model.dart';
3 |
--------------------------------------------------------------------------------
/lib/src/components/animelist/animelist.event.dart:
--------------------------------------------------------------------------------
1 | class AnimelistEvent {
2 | AnimelistEvent(this.followingCount);
3 |
4 | final int followingCount;
5 | }
6 |
--------------------------------------------------------------------------------
/lib/src/components/animelist/index.dart:
--------------------------------------------------------------------------------
1 | export 'animelist.controller.dart';
2 | export 'animelist.event.dart';
3 | export 'animelist.model.dart';
4 | export 'index.dart';
5 |
--------------------------------------------------------------------------------
/lib/src/components/cloud-backup/cloud-backup.controller.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:momentum/momentum.dart';
4 |
5 | import '../../data/index.dart';
6 | import '../../misc/index.dart';
7 | import '../../services/interface/api.interface.dart';
8 | import '../../services/interface/google-api.interface.dart';
9 | import '../../widgets/index.dart';
10 | import '../animelist/index.dart';
11 | import '../sources/index.dart';
12 | import 'index.dart';
13 |
14 | class CloudBackupController extends MomentumController {
15 | @override
16 | CloudBackupModel init() {
17 | return CloudBackupModel(
18 | this,
19 | latestBackupInfo: CloudBackup(),
20 | loading: false,
21 | );
22 | }
23 |
24 | ApiInterface get api => service(runtimeType: false);
25 | GoogleApiInterface get google => service(runtimeType: false);
26 |
27 | Future initialize() async {
28 | if (model.signedIn) {
29 | model.update(loading: true);
30 | final latestBackupInfo = await api.fetchBackup(token: model.token, includeData: false);
31 | model.update(latestBackupInfo: latestBackupInfo, loading: false);
32 | }
33 | }
34 |
35 | Future triggerCloudBackupPrompt() async {
36 | model.update(loading: true);
37 | final signedIn = await google.isSignedIn();
38 | if (signedIn) {
39 | sendEvent(CloudbackupEvents.alreadySignedIn);
40 | }
41 | model.update(loading: false);
42 | return signedIn;
43 | }
44 |
45 | Future startNewBackup() async {
46 | if (model.signedIn) {
47 | final animeListState = controller().model.toBackup();
48 | final sourcesState = controller().model.toJson();
49 |
50 | final data = BackupData(
51 | animeListState: animeListState,
52 | sourcesState: sourcesState,
53 | ).toRawJson();
54 |
55 | final result = await api.newBackup(token: model.token, data: data);
56 | model.update(latestBackupInfo: result);
57 | } else {
58 | showToast('Please sign in with google first', error: true);
59 | }
60 | }
61 |
62 | Future restoreFromLatest() async {
63 | if (model.signedIn) {
64 | try {
65 | final backupData = await api.fetchBackup(token: model.token);
66 |
67 | if (backupData.data.isNotEmpty) {
68 | /* Parsing */
69 | final json = jsonDecode(backupData.data);
70 | final animeListState = jsonEncode(json[ANIMELIST_STATE_KEY]);
71 | final sourcesState = jsonEncode(json[SOURCES_STATE_KEY]);
72 | /* Parsing */
73 |
74 | /* Restoration */
75 | await controller().restoreFromBackup(sourcesState);
76 | await controller().restoreFromBackup(animeListState);
77 | model.update(lastRestore: DateTime.now());
78 | /* Restoration */
79 | }
80 | } catch (e) {
81 | showToast(e.toString(), error: true);
82 | }
83 | } else {
84 | showToast('Please sign in with google first', error: true);
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/lib/src/components/cloud-backup/cloud-backup.events.dart:
--------------------------------------------------------------------------------
1 | enum CloudbackupEvents {
2 | alreadySignedIn,
3 | startNewBackup,
4 | restoreFromLatest,
5 | logoutGoogle,
6 | none,
7 | }
8 |
--------------------------------------------------------------------------------
/lib/src/components/cloud-backup/cloud-backup.model.dart:
--------------------------------------------------------------------------------
1 | import 'package:momentum/momentum.dart';
2 | import '../google-flow/google-flow.controller.dart';
3 |
4 | import '../../data/index.dart';
5 | import 'index.dart';
6 |
7 | class CloudBackupModel extends MomentumModel {
8 | CloudBackupModel(
9 | CloudBackupController controller, {
10 | required this.latestBackupInfo,
11 | required this.loading,
12 | this.lastRestore,
13 | }) : super(controller);
14 |
15 | final CloudBackup latestBackupInfo;
16 | final DateTime? lastRestore;
17 | final bool loading;
18 |
19 | bool get hasLatestBackup => latestBackupInfo.updatedAt != null;
20 | bool get hasLastRestored => lastRestore != null;
21 |
22 | bool get signedIn => controller.controller().model.signedIn;
23 | String get token => controller.controller().model.token;
24 |
25 | @override
26 | void update({
27 | CloudBackup? latestBackupInfo,
28 | DateTime? lastRestore,
29 | bool? loading,
30 | }) {
31 | CloudBackupModel(
32 | controller,
33 | latestBackupInfo: latestBackupInfo ?? this.latestBackupInfo,
34 | lastRestore: lastRestore ?? this.lastRestore,
35 | loading: loading ?? this.loading,
36 | ).updateMomentum();
37 | }
38 |
39 | void modifyLastRestore({
40 | DateTime? lastRestore,
41 | }) {
42 | CloudBackupModel(
43 | controller,
44 | latestBackupInfo: this.latestBackupInfo,
45 | lastRestore: lastRestore,
46 | loading: false,
47 | ).updateMomentum();
48 | }
49 |
50 | Map toJson() {
51 | return {
52 | 'lastRestore': lastRestore?.millisecondsSinceEpoch,
53 | };
54 | }
55 |
56 | CloudBackupModel? fromJson(Map? map) {
57 | if (map == null) return null;
58 | return CloudBackupModel(
59 | controller,
60 | latestBackupInfo: CloudBackup(),
61 | lastRestore: map['lastRestore'] == null ? null : DateTime.fromMillisecondsSinceEpoch(map['lastRestore']),
62 | loading: false,
63 | );
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/lib/src/components/cloud-backup/index.dart:
--------------------------------------------------------------------------------
1 | export 'cloud-backup.controller.dart';
2 | export 'cloud-backup.events.dart';
3 | export 'cloud-backup.model.dart';
4 | export 'index.dart';
5 |
--------------------------------------------------------------------------------
/lib/src/components/feed/feed.controller.dart:
--------------------------------------------------------------------------------
1 | import 'package:momentum/momentum.dart';
2 | import '../../services/interface/api.interface.dart';
3 |
4 | import '../../data/feed.response.dart';
5 | import 'index.dart';
6 |
7 | class FeedController extends MomentumController {
8 | @override
9 | FeedModel init() {
10 | return FeedModel(
11 | this,
12 | feed: QuantzFeed(),
13 | loading: false,
14 | );
15 | }
16 |
17 | ApiInterface get api => service(runtimeType: false);
18 |
19 | void bootstrap() async {
20 | loadInitial();
21 | }
22 |
23 | Future loadInitial({bool refresh = false}) async {
24 | if (!refresh) model.update(loading: true);
25 |
26 | final feed = await api.getLatestFeed();
27 | var feedItems = feed.items;
28 | if (feedItems.isNotEmpty) {
29 | feedItems.sort((a, b) => b.utcTimestampSeconds.compareTo(a.utcTimestampSeconds));
30 | model.update(feed: feed.copyWith(items: feedItems), loading: false);
31 | } else {
32 | model.update(loading: false);
33 | }
34 | }
35 |
36 | Future loadMore() async {
37 | final feed = await api.getLatestFeed(page: model.feed.page + 1);
38 | var feedItems = List.from(model.feed.items)..addAll(feed.items);
39 | if (feedItems.isNotEmpty) {
40 | feedItems = feedItems.toSet().toList();
41 | feedItems.sort((a, b) => b.utcTimestampSeconds.compareTo(a.utcTimestampSeconds));
42 | model.update(feed: feed.copyWith(items: feedItems));
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/lib/src/components/feed/feed.model.dart:
--------------------------------------------------------------------------------
1 | import 'package:momentum/momentum.dart';
2 |
3 | import '../../data/feed.response.dart';
4 | import 'index.dart';
5 |
6 | class FeedModel extends MomentumModel {
7 | FeedModel(
8 | FeedController controller, {
9 | required this.feed,
10 | required this.loading,
11 | }) : super(controller);
12 |
13 | final QuantzFeed feed;
14 | final bool loading;
15 |
16 | @override
17 | void update({
18 | QuantzFeed? feed,
19 | bool? loading,
20 | }) {
21 | FeedModel(
22 | controller,
23 | feed: feed ?? this.feed,
24 | loading: loading ?? this.loading,
25 | ).updateMomentum();
26 | }
27 |
28 | Map toJson() {
29 | return {
30 | "feed": feed.toJson(),
31 | "loading": false,
32 | };
33 | }
34 |
35 | FeedModel? fromJson(Map? map) {
36 | if (map == null) return null;
37 |
38 | return FeedModel(
39 | controller,
40 | feed: QuantzFeed.fromJson(map['feed']),
41 | loading: false,
42 | );
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/lib/src/components/feed/index.dart:
--------------------------------------------------------------------------------
1 | export 'feed.controller.dart';
2 | export 'feed.model.dart';
3 |
--------------------------------------------------------------------------------
/lib/src/components/filter/filter.controller.dart:
--------------------------------------------------------------------------------
1 | import 'package:momentum/momentum.dart';
2 |
3 | import '../../data/index.dart';
4 | import '../animelist/index.dart';
5 | import 'index.dart';
6 |
7 | class FilterController extends MomentumController {
8 | @override
9 | FilterModel init() {
10 | return FilterModel(
11 | this,
12 | displayTitle: DisplayTitle.defaultTitle,
13 | sortBy: SortBy.desc,
14 | orderBy: OrderBy.episodeRelease,
15 | showOngoing: true,
16 | showUpcoming: true,
17 | );
18 | }
19 |
20 | AnimelistController get animeListCtrl => controller();
21 |
22 | void setDisplayTitle(DisplayTitle displayTitle) {
23 | model.update(displayTitle: displayTitle);
24 | animeListCtrl.flagEntries();
25 | animeListCtrl.arrangeList();
26 | animeListCtrl.separateList();
27 | }
28 |
29 | void setOrderBy(OrderBy orderBy) {
30 | model.update(orderBy: orderBy);
31 | animeListCtrl.flagEntries();
32 | animeListCtrl.arrangeList();
33 | animeListCtrl.separateList();
34 | }
35 |
36 | void setSortBy(SortBy sortBy) {
37 | model.update(sortBy: sortBy);
38 | animeListCtrl.arrangeList();
39 | animeListCtrl.separateList();
40 | }
41 |
42 | int compare(AnimeEntry a, AnimeEntry b) {
43 | switch (model.sortBy) {
44 | case SortBy.asc:
45 | return _compareOrder(a, b);
46 | case SortBy.desc:
47 | return _compareOrder(b, a);
48 | }
49 | }
50 |
51 | int _compareOrder(AnimeEntry x, AnimeEntry y) {
52 | switch (model.orderBy) {
53 | case OrderBy.title:
54 | return _compareTitle(x, y);
55 | case OrderBy.episodeCount:
56 | return x.latestEpisode.compareTo(y.latestEpisode);
57 | case OrderBy.episodeRelease:
58 | return x.episodeTimestamp.compareTo(y.episodeTimestamp);
59 | case OrderBy.popularity:
60 | return x.malTotalUsers.compareTo(y.malTotalUsers);
61 | case OrderBy.score:
62 | return x.malScore.compareTo(y.malScore);
63 | }
64 | }
65 |
66 | int _compareTitle(AnimeEntry x, AnimeEntry y) {
67 | switch (model.displayTitle) {
68 | case DisplayTitle.defaultTitle:
69 | return x.title.compareTo(y.title);
70 | case DisplayTitle.english:
71 | return x.malTitleEnglish.compareTo(y.malTitleEnglish);
72 | case DisplayTitle.japanese:
73 | return x.malTitleJapanese.compareTo(y.malTitleJapanese);
74 | }
75 | }
76 |
77 | void toggleOngoing() {
78 | model.update(showOngoing: !model.showOngoing);
79 | animeListCtrl.arrangeList();
80 | animeListCtrl.separateList();
81 | }
82 |
83 | void toggleUpcoming() {
84 | model.update(showUpcoming: !model.showUpcoming);
85 | animeListCtrl.arrangeList();
86 | animeListCtrl.separateList();
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/lib/src/components/filter/filter.model.dart:
--------------------------------------------------------------------------------
1 | import 'package:momentum/momentum.dart';
2 |
3 | import 'index.dart';
4 |
5 | class FilterModel extends MomentumModel {
6 | FilterModel(
7 | FilterController controller, {
8 | required this.displayTitle,
9 | required this.sortBy,
10 | required this.orderBy,
11 | required this.showOngoing,
12 | required this.showUpcoming,
13 | }) : super(controller);
14 |
15 | final DisplayTitle displayTitle;
16 | final SortBy sortBy;
17 | final OrderBy orderBy;
18 |
19 | final bool showOngoing;
20 | final bool showUpcoming;
21 |
22 | @override
23 | void update({
24 | DisplayTitle? displayTitle,
25 | SortBy? sortBy,
26 | OrderBy? orderBy,
27 | bool? showOngoing,
28 | bool? showUpcoming,
29 | }) {
30 | FilterModel(
31 | controller,
32 | displayTitle: displayTitle ?? this.displayTitle,
33 | sortBy: sortBy ?? this.sortBy,
34 | orderBy: orderBy ?? this.orderBy,
35 | showOngoing: showOngoing ?? this.showOngoing,
36 | showUpcoming: showUpcoming ?? this.showUpcoming,
37 | ).updateMomentum();
38 | }
39 |
40 | Map toJson() {
41 | return {
42 | 'displayTitle': DisplayTitle.values.indexOf(displayTitle),
43 | 'sortBy': SortBy.values.indexOf(SortBy.desc),
44 | 'orderBy': OrderBy.values.indexOf(OrderBy.episodeRelease),
45 | 'showOngoing': true,
46 | 'showUpcoming': true,
47 | };
48 | }
49 |
50 | FilterModel? fromJson(Map? map) {
51 | if (map == null) {
52 | return null;
53 | }
54 | return FilterModel(
55 | controller,
56 | displayTitle: DisplayTitle.values[map['displayTitle']],
57 | sortBy: SortBy.values[map['sortBy']],
58 | orderBy: OrderBy.values[map['orderBy']],
59 | showOngoing: true,
60 | showUpcoming: true,
61 | );
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/lib/src/components/filter/filter.types.dart:
--------------------------------------------------------------------------------
1 | enum DisplayTitle {
2 | defaultTitle,
3 | english,
4 | japanese,
5 | }
6 |
7 | String displayTitleLabel(DisplayTitle displayTitle) {
8 | switch (displayTitle) {
9 | case DisplayTitle.defaultTitle:
10 | return 'Default';
11 | case DisplayTitle.english:
12 | return 'English';
13 | case DisplayTitle.japanese:
14 | return 'Japanese';
15 | }
16 | }
17 |
18 | enum OrderBy {
19 | title,
20 | episodeCount,
21 | episodeRelease,
22 | popularity,
23 | score,
24 | }
25 |
26 | String orderByLabel(OrderBy orderBy) {
27 | switch (orderBy) {
28 | case OrderBy.title:
29 | return 'Title';
30 | case OrderBy.episodeCount:
31 | return 'Episode Number';
32 | case OrderBy.episodeRelease:
33 | return 'Episode Release';
34 | case OrderBy.popularity:
35 | return 'Popularity';
36 | case OrderBy.score:
37 | return 'Score';
38 | }
39 | }
40 |
41 | enum SortBy {
42 | asc,
43 | desc,
44 | }
45 |
46 | String sortByLabel(SortBy sortBy) {
47 | switch (sortBy) {
48 | case SortBy.asc:
49 | return 'Ascending';
50 | case SortBy.desc:
51 | return 'Descending';
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/lib/src/components/filter/index.dart:
--------------------------------------------------------------------------------
1 | export 'filter.controller.dart';
2 | export 'filter.model.dart';
3 | export 'filter.types.dart';
4 | export 'index.dart';
5 |
--------------------------------------------------------------------------------
/lib/src/components/google-flow/google-flow.model.dart:
--------------------------------------------------------------------------------
1 | import 'package:momentum/momentum.dart';
2 |
3 | import '../../data/index.dart';
4 | import 'index.dart';
5 |
6 | class GoogleFlowModel extends MomentumModel {
7 | GoogleFlowModel(
8 | GoogleFlowController controller, {
9 | required this.token,
10 | required this.profile,
11 | }) : super(controller);
12 |
13 | final String token;
14 | final JwtTokenProfile profile;
15 |
16 | bool get signedIn => token.isNotEmpty;
17 | String get emailObscure {
18 | final e = profile.email;
19 | if (e.isNotEmpty) {
20 | final username = e.substring(0, e.indexOf('@'));
21 | final domain = e.replaceAll('$username@', '');
22 | final firstLetter = username[0];
23 | final lastLetter = username[username.length - 1];
24 | return '$firstLetter********$lastLetter@$domain';
25 | }
26 | return '';
27 | }
28 |
29 | @override
30 | void update({
31 | String? token,
32 | JwtTokenProfile? profile,
33 | }) {
34 | GoogleFlowModel(
35 | controller,
36 | token: token ?? this.token,
37 | profile: profile ?? this.profile,
38 | ).updateMomentum();
39 | }
40 |
41 | Map toJson() {
42 | return {
43 | 'token': token,
44 | };
45 | }
46 |
47 | GoogleFlowModel? fromJson(Map? map) {
48 | if (map == null) return null;
49 | return GoogleFlowModel(
50 | controller,
51 | token: map['token'],
52 | profile: JwtTokenProfile(),
53 | );
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/lib/src/components/google-flow/index.dart:
--------------------------------------------------------------------------------
1 | export 'google-flow.controller.dart';
2 | export 'google-flow.model.dart';
3 |
--------------------------------------------------------------------------------
/lib/src/components/index.dart:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quantz-dev/quantz-app/690abc2d76edebd9e2cb3792707e27a8e5ebac87/lib/src/components/index.dart
--------------------------------------------------------------------------------
/lib/src/components/integration/index.dart:
--------------------------------------------------------------------------------
1 | export 'integration.controller.dart';
2 | export 'integration.events.dart';
3 | export 'integration.model.dart';
4 | export 'index.dart';
5 |
--------------------------------------------------------------------------------
/lib/src/components/integration/integration.events.dart:
--------------------------------------------------------------------------------
1 | enum IntegrationEvents {
2 | done,
3 | }
4 |
--------------------------------------------------------------------------------
/lib/src/components/integration/integration.model.dart:
--------------------------------------------------------------------------------
1 | import 'package:momentum/momentum.dart';
2 |
3 | import '../../data/index.dart';
4 | import '../../data/mal-user.animelist.dart';
5 | import 'index.dart';
6 |
7 | class IntegrationModel extends MomentumModel {
8 | IntegrationModel(
9 | IntegrationController controller, {
10 | required this.loading,
11 | required this.malList,
12 | required this.toFollow,
13 | required this.toUnfollow,
14 | required this.malUsername,
15 | required this.syncSub,
16 | required this.syncDub,
17 | required this.malUserAnimeListCache,
18 | required this.statProgress,
19 | required this.statToImport,
20 | }) : super(controller);
21 |
22 | final bool loading;
23 | final List malList;
24 | final List toFollow;
25 | final List toUnfollow;
26 | final String malUsername;
27 | final bool syncSub;
28 | final bool syncDub;
29 |
30 | final List malUserAnimeListCache;
31 |
32 | final int statProgress;
33 | final int statToImport;
34 |
35 | bool get loggedIn => controller.mal.loggedIn;
36 |
37 | @override
38 | void update({
39 | bool? loading,
40 | List? malList,
41 | List? toFollow,
42 | List? toUnfollow,
43 | String? malUsername,
44 | bool? syncSub,
45 | bool? syncDub,
46 | int? statProgress,
47 | int? statToImport,
48 | List? malUserAnimeListCache,
49 | }) {
50 | IntegrationModel(
51 | controller,
52 | loading: loading ?? this.loading,
53 | malList: malList ?? this.malList,
54 | toFollow: toFollow ?? this.toFollow,
55 | toUnfollow: toUnfollow ?? this.toUnfollow,
56 | malUsername: malUsername ?? this.malUsername,
57 | syncSub: syncSub ?? this.syncSub,
58 | syncDub: syncDub ?? this.syncDub,
59 | malUserAnimeListCache: malUserAnimeListCache ?? this.malUserAnimeListCache,
60 | statProgress: statProgress ?? this.statProgress,
61 | statToImport: statToImport ?? this.statToImport,
62 | ).updateMomentum();
63 | }
64 |
65 | Map toJson() {
66 | return {
67 | 'syncSub': syncSub,
68 | 'syncDub': syncDub,
69 | 'malUsername': malUsername,
70 | 'malUserAnimeListCache': malUserAnimeListCache.map((e) => e.toJson()).toList(),
71 | };
72 | }
73 |
74 | IntegrationModel? fromJson(Map? map) {
75 | if (map == null) return null;
76 | return IntegrationModel(
77 | controller,
78 | loading: false,
79 | malList: [],
80 | toFollow: [],
81 | toUnfollow: [],
82 | malUsername: map['malUsername'] ?? '',
83 | syncSub: map['syncSub'],
84 | syncDub: map['syncDub'],
85 | malUserAnimeListCache: map['malUserAnimeListCache'] == null ? [] : List.from((map['malUserAnimeListCache'] as List).map((e) => MalUserAnimeItem.fromJson(e))),
86 | statProgress: statProgress,
87 | statToImport: statToImport,
88 | );
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/lib/src/components/notification/index.dart:
--------------------------------------------------------------------------------
1 | export 'notification.controller.dart';
2 | export 'notification.model.dart';
3 |
--------------------------------------------------------------------------------
/lib/src/components/notification/notification.controller.dart:
--------------------------------------------------------------------------------
1 | import 'package:firebase_messaging/firebase_messaging.dart';
2 | import 'package:flutter/services.dart';
3 | import 'package:momentum/momentum.dart';
4 | import 'package:url_launcher/url_launcher.dart';
5 |
6 | import '../../notification/index.dart';
7 | import '../../services/index.dart';
8 | import '../../widgets/index.dart';
9 | import 'index.dart';
10 |
11 | class NotificationController extends MomentumController {
12 | @override
13 | NotificationModel init() {
14 | return NotificationModel(
15 | this,
16 | loading: false,
17 | permalinks: [],
18 | );
19 | }
20 |
21 | FirebaseMessaging? _messaging;
22 | FirebaseMessaging get messaging => _messaging!;
23 |
24 | @override
25 | void onReady() async {
26 | await waitForFirebaseInit();
27 | _messaging = FirebaseMessaging.instance;
28 | var message = await _messaging!.getInitialMessage();
29 | if (message != null) {
30 | service().setMessage(message);
31 | }
32 | openNotification();
33 | }
34 |
35 | void markPermalinkAsVisited(String permalink) {
36 | var links = List.from(model.permalinks);
37 | links.add(permalink); // mark the permalink as visited.
38 | links = links.toSet().toList();
39 | model.update(permalinks: links);
40 | }
41 |
42 | bool isVisited(String permalink) {
43 | return model.permalinks.any((x) => x == permalink);
44 | }
45 |
46 | /// Try to open permalink if there's a pending notification click result
47 | Future openNotification() async {
48 | model.update(loading: true);
49 | try {
50 | var message = service().message;
51 | try {
52 | if (message != null) {
53 | try {
54 | var permalink = message.data['permalink'] as String;
55 | await _openPermalink(permalink);
56 | } catch (e) {
57 | showToast(e.toString(), error: true);
58 | }
59 | }
60 | } catch (e) {
61 | showToast(e.toString(), error: true);
62 | }
63 | } catch (e, trace) {
64 | print(trace);
65 | showToast(e.toString(), error: true);
66 | }
67 | model.update(loading: false);
68 | }
69 |
70 | Future _openPermalink(String permalink) async {
71 | try {
72 | final visited = isVisited(permalink);
73 | if (visited) {
74 | service().clear();
75 | return;
76 | }
77 | await launch(permalink);
78 | markPermalinkAsVisited(permalink);
79 | SystemChannels.platform.invokeMethod('SystemNavigator.pop');
80 | } catch (e) {
81 | showToast('Unable to open notification.', error: true);
82 | }
83 | service().clear();
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/lib/src/components/notification/notification.model.dart:
--------------------------------------------------------------------------------
1 | import 'package:momentum/momentum.dart';
2 |
3 | import 'index.dart';
4 |
5 | class NotificationModel extends MomentumModel {
6 | NotificationModel(
7 | NotificationController controller, {
8 | required this.loading,
9 | required this.permalinks,
10 | }) : super(controller);
11 |
12 | final bool loading;
13 | final List permalinks;
14 |
15 | @override
16 | void update({
17 | bool? loading,
18 | List? permalinks,
19 | }) {
20 | NotificationModel(
21 | controller,
22 | loading: loading ?? this.loading,
23 | permalinks: permalinks ?? this.permalinks,
24 | ).updateMomentum();
25 | }
26 |
27 | Map toJson() {
28 | return {
29 | 'permalinks': permalinks,
30 | };
31 | }
32 |
33 | NotificationModel? fromJson(Map? json) {
34 | if (json == null) return null;
35 |
36 | return NotificationModel(
37 | controller,
38 | loading: false,
39 | permalinks: List.from(json['permalinks']),
40 | );
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/lib/src/components/sources/index.dart:
--------------------------------------------------------------------------------
1 | export 'index.dart';
2 | export 'sources.controller.dart';
3 | export 'sources.model.dart';
4 | export 'source.news.dart';
5 |
--------------------------------------------------------------------------------
/lib/src/components/sources/source.news.dart:
--------------------------------------------------------------------------------
1 | class NewsSource {
2 | const NewsSource({
3 | required this.name,
4 | required this.iconAssetPath,
5 | required this.firebaseTopic,
6 | required this.following,
7 | required this.links,
8 | });
9 |
10 | final String name;
11 | final String iconAssetPath;
12 | final String firebaseTopic;
13 | final bool following;
14 | final List links;
15 |
16 | Map toMap() {
17 | return {
18 | 'name': name,
19 | 'iconAssetPath': iconAssetPath,
20 | 'firebaseTopic': firebaseTopic,
21 | 'following': following,
22 | 'links': links.map((x) => x.toMap()).toList(),
23 | };
24 | }
25 |
26 | factory NewsSource.fromMap(Map map) {
27 | return NewsSource(
28 | name: map['name'],
29 | iconAssetPath: map['iconAssetPath'],
30 | firebaseTopic: map['firebaseTopic'],
31 | following: map['following'],
32 | links: List.from(map['links']?.map((x) => NewsSourceLink.fromMap(x))),
33 | );
34 | }
35 |
36 | NewsSource copyWith({
37 | String? name,
38 | String? iconAssetPath,
39 | String? firebaseTopic,
40 | bool? following,
41 | List? links,
42 | }) {
43 | return NewsSource(
44 | name: name ?? this.name,
45 | iconAssetPath: iconAssetPath ?? this.iconAssetPath,
46 | firebaseTopic: firebaseTopic ?? this.firebaseTopic,
47 | following: following ?? this.following,
48 | links: links ?? this.links,
49 | );
50 | }
51 | }
52 |
53 | class NewsSourceLink {
54 | const NewsSourceLink(this.name, this.url);
55 |
56 | final String name;
57 | final String url;
58 |
59 | Map toMap() {
60 | return {
61 | 'name': name,
62 | 'url': url,
63 | };
64 | }
65 |
66 | factory NewsSourceLink.fromMap(Map map) {
67 | return NewsSourceLink(
68 | map['name'],
69 | map['url'],
70 | );
71 | }
72 | }
73 |
74 | const sourcesList = [
75 | const NewsSource(
76 | name: 'AnimeNewsNetwork',
77 | iconAssetPath: 'assets/sources-icons/ann.png',
78 | firebaseTopic: 'anime_news_network',
79 | following: false,
80 | links: [
81 | const NewsSourceLink('Homepage', 'https://www.animenewsnetwork.com/'),
82 | ],
83 | ),
84 | const NewsSource(
85 | name: 'Livechart Headlines',
86 | iconAssetPath: 'assets/sources-icons/livechart.png',
87 | firebaseTopic: 'livechart_headlines',
88 | following: false,
89 | links: [
90 | const NewsSourceLink('Recent Anime Headlines', 'https://www.livechart.me/headlines'),
91 | ],
92 | ),
93 | const NewsSource(
94 | name: 'MyAnimeList',
95 | iconAssetPath: 'assets/sources-icons/mal.png',
96 | firebaseTopic: 'my_anime_list',
97 | following: false,
98 | links: [
99 | const NewsSourceLink('All News', 'https://myanimelist.net/news'),
100 | const NewsSourceLink('New Anime', 'https://myanimelist.net/news/tag/new_anime'),
101 | ],
102 | ),
103 | const NewsSource(
104 | name: 'r/anime - Subreddit',
105 | iconAssetPath: 'assets/sources-icons/reddit.png',
106 | firebaseTopic: 'r_anime_feed',
107 | following: false,
108 | links: [
109 | const NewsSourceLink('News', 'https://www.reddit.com/r/anime?f=flair_name:"News"'),
110 | const NewsSourceLink('Official Media', 'https://www.reddit.com/r/anime?f=flair_name:"Official Media"'),
111 | ],
112 | ),
113 | ];
114 |
--------------------------------------------------------------------------------
/lib/src/components/sources/sources.controller.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:firebase_messaging/firebase_messaging.dart';
4 | import 'package:momentum/momentum.dart';
5 |
6 | import '../../misc/index.dart';
7 | import '../../notification/index.dart';
8 | import 'index.dart';
9 |
10 | class SourcesController extends MomentumController {
11 | @override
12 | SourcesModel init() {
13 | return SourcesModel(
14 | this,
15 | initialized: false,
16 | loading: false,
17 | sourcesSubscriptionList: sourcesList,
18 | );
19 | }
20 |
21 | String get persistenceKey => SOURCES_STATE_KEY;
22 |
23 | FirebaseMessaging? _messaging;
24 | FirebaseMessaging get messaging => _messaging!;
25 |
26 | @override
27 | void onReady() async {
28 | await waitForFirebaseInit();
29 | _messaging = FirebaseMessaging.instance;
30 | if (!model.initialized) {
31 | model.update(loading: true);
32 | await toggleNews(model.sourcesSubscriptionList[0], true);
33 | model.update(loading: false, initialized: true);
34 | }
35 | syncNewsSources();
36 | }
37 |
38 | void syncNewsSources() {
39 | var list = List.from(model.sourcesSubscriptionList);
40 | for (var i = 0; i < sourcesList.length; i++) {
41 | var exist = list.any((x) => x.name == sourcesList[i].name);
42 | if (!exist) {
43 | list.insert(i, sourcesList[i]);
44 | } else {
45 | var current = list.firstWhere((x) => x.name == sourcesList[i].name);
46 | list.replaceRange(i, i + 1, [sourcesList[i].copyWith(following: current.following)]);
47 | }
48 | }
49 | model.update(sourcesSubscriptionList: list);
50 | }
51 |
52 | Future toggleNews(NewsSource source, bool state) async {
53 | model.update(loading: true);
54 | try {
55 | var list = List.from(model.sourcesSubscriptionList);
56 | var updated = source.copyWith(following: state);
57 | var index = list.indexWhere((x) => x.firebaseTopic == source.firebaseTopic);
58 | if (state) {
59 | await messaging.subscribeToTopic(source.firebaseTopic);
60 | } else {
61 | await messaging.unsubscribeFromTopic(source.firebaseTopic);
62 | }
63 | list.removeAt(index);
64 | list.insert(index, updated);
65 | model.update(sourcesSubscriptionList: list);
66 | } catch (e) {
67 | print(e);
68 | }
69 | model.update(loading: false);
70 | }
71 |
72 | Future restoreFromBackup(String source) async {
73 | var json = jsonDecode(source);
74 | var backup = model.fromJson(json);
75 | final sources = backup?.sourcesSubscriptionList ?? [];
76 | for (var item in sources) {
77 | await toggleNews(item, item.following);
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/lib/src/components/sources/sources.model.dart:
--------------------------------------------------------------------------------
1 | import 'package:momentum/momentum.dart';
2 |
3 | import 'index.dart';
4 |
5 | class SourcesModel extends MomentumModel {
6 | SourcesModel(
7 | SourcesController controller, {
8 | required this.initialized,
9 | required this.loading,
10 | required this.sourcesSubscriptionList,
11 | }) : super(controller);
12 |
13 | final bool initialized;
14 | final bool loading;
15 | final List sourcesSubscriptionList;
16 |
17 | @override
18 | void update({
19 | bool? initialized,
20 | bool? loading,
21 | List? sourcesSubscriptionList,
22 | }) {
23 | SourcesModel(
24 | controller,
25 | initialized: initialized ?? this.initialized,
26 | loading: loading ?? this.loading,
27 | sourcesSubscriptionList: sourcesSubscriptionList ?? this.sourcesSubscriptionList,
28 | ).updateMomentum();
29 | }
30 |
31 | Map toJson() {
32 | return {
33 | 'initialized': initialized,
34 | 'sourcesSubscriptionList': sourcesSubscriptionList.map((x) => x.toMap()).toList(),
35 | };
36 | }
37 |
38 | SourcesModel? fromJson(Map? json) {
39 | if (json == null) return null;
40 | return SourcesModel(
41 | controller,
42 | initialized: json['initialized'],
43 | loading: false,
44 | sourcesSubscriptionList: List.from(json['sourcesSubscriptionList']?.map((x) => NewsSource.fromMap(x))),
45 | );
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/lib/src/components/supporter-subscription/index.dart:
--------------------------------------------------------------------------------
1 | export 'supporter-subscription.controller.dart';
2 | export 'supporter-subscription.model.dart';
3 |
--------------------------------------------------------------------------------
/lib/src/components/supporter-subscription/supporter-subscription.model.dart:
--------------------------------------------------------------------------------
1 | import 'package:momentum/momentum.dart';
2 |
3 | import 'index.dart';
4 |
5 | class SupporterSubscriptionModel extends MomentumModel {
6 | SupporterSubscriptionModel(
7 | SupporterSubscriptionController controller, {
8 | required this.loading,
9 | required this.purchaseIsPending,
10 | required this.purchaseError,
11 | required this.subscriptionActive,
12 | required this.storeIsAvailable,
13 | }) : super(controller);
14 |
15 | final bool loading;
16 | final bool purchaseIsPending;
17 | final String purchaseError;
18 | final bool subscriptionActive;
19 | final bool storeIsAvailable;
20 |
21 | @override
22 | void update({
23 | bool? loading,
24 | bool? purchaseIsPending,
25 | String? purchaseError,
26 | bool? subscriptionActive,
27 | bool? storeIsAvailable,
28 | }) {
29 | SupporterSubscriptionModel(
30 | controller,
31 | loading: loading ?? this.loading,
32 | purchaseIsPending: purchaseIsPending ?? this.purchaseIsPending,
33 | purchaseError: purchaseError ?? this.purchaseError,
34 | subscriptionActive: subscriptionActive ?? this.subscriptionActive,
35 | storeIsAvailable: storeIsAvailable ?? this.storeIsAvailable,
36 | ).updateMomentum();
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/lib/src/components/topic/index.dart:
--------------------------------------------------------------------------------
1 | export 'topic.controller.dart';
2 | export 'topic.model.dart';
3 |
--------------------------------------------------------------------------------
/lib/src/components/topic/topic.controller.dart:
--------------------------------------------------------------------------------
1 | import 'package:momentum/momentum.dart';
2 |
3 | import '../../data/index.dart';
4 | import '../../services/interface/google-api.interface.dart';
5 | import 'index.dart';
6 |
7 | class TopicController extends MomentumController {
8 | @override
9 | TopicModel init() {
10 | return TopicModel(
11 | this,
12 | loading: false,
13 | firebaseSubscription: FirebaseSubscription(),
14 | );
15 | }
16 |
17 | Future loadSubscription() async {
18 | final api = service(runtimeType: false);
19 | model.update(loading: true);
20 | var firebaseSubscription = await api.getFirebaseSubscription();
21 | model.update(loading: false, firebaseSubscription: firebaseSubscription);
22 | return firebaseSubscription;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lib/src/components/topic/topic.model.dart:
--------------------------------------------------------------------------------
1 | import 'package:momentum/momentum.dart';
2 |
3 | import '../../data/index.dart';
4 | import 'index.dart';
5 |
6 | class TopicModel extends MomentumModel {
7 | TopicModel(
8 | TopicController controller, {
9 | required this.loading,
10 | required this.firebaseSubscription,
11 | }) : super(controller);
12 |
13 | final bool loading;
14 | final FirebaseSubscription firebaseSubscription;
15 |
16 | @override
17 | void update({
18 | bool? loading,
19 | FirebaseSubscription? firebaseSubscription,
20 | }) {
21 | TopicModel(
22 | controller,
23 | loading: loading ?? this.loading,
24 | firebaseSubscription: firebaseSubscription ?? this.firebaseSubscription,
25 | ).updateMomentum();
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/lib/src/core/admob_units.dart:
--------------------------------------------------------------------------------
1 | const AD_UNIT_LIBRARY = 'ca-app-pub-3940256099942544/6300978111';
2 | const AD_UNIT_SOURCES = 'ca-app-pub-3940256099942544/6300978111';
3 | const AD_UNIT_FEED = 'ca-app-pub-3940256099942544/6300978111';
4 |
--------------------------------------------------------------------------------
/lib/src/core/api_key.sample.dart:
--------------------------------------------------------------------------------
1 | /// This is to close the quantz's api from public access.
2 | ///
3 | /// I'm planning to open it to public when considerable amount of support or donations is reached.
4 | const api_key = '________________';
--------------------------------------------------------------------------------
/lib/src/core/config.dart:
--------------------------------------------------------------------------------
1 | const api = 'https://api.quantz.app';
2 |
--------------------------------------------------------------------------------
/lib/src/core/controllers.dart:
--------------------------------------------------------------------------------
1 | import 'package:momentum/momentum.dart';
2 |
3 | import '../components/admob/index.dart';
4 | import '../components/animelist/index.dart';
5 | import '../components/cloud-backup/index.dart';
6 | import '../components/feed/index.dart';
7 | import '../components/filter/index.dart';
8 | import '../components/google-flow/index.dart';
9 | import '../components/integration/index.dart';
10 | import '../components/sources/index.dart';
11 | import '../components/notification/index.dart';
12 | import '../components/supporter-subscription/index.dart';
13 | import '../components/topic/index.dart';
14 |
15 | final notificationController = NotificationController();
16 |
17 | List controllers() {
18 | return [
19 | AnimelistController(),
20 | notificationController,
21 | IntegrationController(),
22 | SourcesController(),
23 | TopicController(),
24 | FilterController()..config(maxTimeTravelSteps: 2),
25 | FeedController(),
26 | CloudBackupController(),
27 | AdmobController(),
28 | SupporterSubscriptionController(),
29 | GoogleFlowController()..config(lazy: false),
30 | ];
31 | }
32 |
--------------------------------------------------------------------------------
/lib/src/core/in-app-purchase.sample.dart:
--------------------------------------------------------------------------------
1 | const SUPPORTER_PRODUCT_ID = 'GOOGLE_PLAY_PRODUCT_ID_HERE';
2 |
--------------------------------------------------------------------------------
/lib/src/core/index.dart:
--------------------------------------------------------------------------------
1 | export 'config.dart';
2 | export 'controllers.dart';
3 | export 'index.dart';
4 | export 'initializer.dart';
5 | export 'persistence.dart';
6 | export 'admob_units.dart';
7 | export 'services.dart';
8 |
--------------------------------------------------------------------------------
/lib/src/core/initializer.dart:
--------------------------------------------------------------------------------
1 | // import 'package:flutter/foundation.dart';
2 | // import 'package:in_app_purchase_android/in_app_purchase_android.dart';
3 |
4 | import '../notification/index.dart';
5 | import 'index.dart';
6 | import 'version.dart';
7 |
8 | Future initializer() async {
9 | initFirebaseNotification();
10 |
11 | await Future.wait([
12 | initSharedPreferences(),
13 | initLocalNotification(),
14 | checkAppVersion(),
15 | ]);
16 |
17 | // if (defaultTargetPlatform == TargetPlatform.android) {
18 | // InAppPurchaseAndroidPlatformAddition.enablePendingPurchases();
19 | // }
20 |
21 | listenToAppLifecycle();
22 | listenToFCM();
23 | }
24 |
--------------------------------------------------------------------------------
/lib/src/core/mal.client.sample.dart:
--------------------------------------------------------------------------------
1 | const MAL_CLIENT_ID = 'PASTE_YOUR_MAL_CLIENT_ID_HERE';
2 | const MAL_REDIRECT_URI = 'PASTE_YOUR_REDIRECT_URI_HERE';
3 | const MAL_AUTH = 'PASTE_YOUR_POSTMAN_GENERATED_BASIC_AUTH_HERE';
4 |
--------------------------------------------------------------------------------
/lib/src/core/persistence.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/widgets.dart';
2 | import 'package:shared_preferences/shared_preferences.dart';
3 |
4 | SharedPreferences? _sharedPreferences;
5 | SharedPreferences get sharedPreferences => _sharedPreferences!;
6 |
7 | Future initSharedPreferences() async {
8 | if (_sharedPreferences == null) _sharedPreferences = await SharedPreferences.getInstance();
9 | }
10 |
11 | Future persistSave(BuildContext? context, String key, String? value) async {
12 | var result = await sharedPreferences.setString(key, value ?? '');
13 | return result;
14 | }
15 |
16 | Future persistGet(
17 | BuildContext? context,
18 | String key,
19 | ) async {
20 | var result = sharedPreferences.getString(key);
21 | return result;
22 | }
23 |
--------------------------------------------------------------------------------
/lib/src/core/services.dart:
--------------------------------------------------------------------------------
1 | import 'package:momentum/momentum.dart';
2 |
3 | import '../services/index.dart';
4 |
5 | var fcmService = FcmService();
6 |
7 | List services() {
8 | return [
9 | fcmService,
10 | // ApiService(),
11 | // GoogleApiService(),
12 | // MalService(),
13 | ApiMockService(),
14 | GoogleApiMockService(),
15 | MalMockService(),
16 | ];
17 | }
18 |
--------------------------------------------------------------------------------
/lib/src/core/version.dart:
--------------------------------------------------------------------------------
1 | import 'package:package_info_plus/package_info_plus.dart';
2 |
3 | String _version = '';
4 | String get appVersion => _version;
5 |
6 | Future checkAppVersion() async {
7 | PackageInfo packageInfo = await PackageInfo.fromPlatform();
8 | _version = packageInfo.version;
9 | }
10 |
--------------------------------------------------------------------------------
/lib/src/data/backup.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import '../misc/index.dart';
4 |
5 | class BackupData {
6 | final Map animeListState;
7 | final Map sourcesState;
8 |
9 | BackupData({
10 | required this.animeListState,
11 | required this.sourcesState,
12 | });
13 |
14 | Map toJson() {
15 | return {
16 | ANIMELIST_STATE_KEY: animeListState,
17 | SOURCES_STATE_KEY: sourcesState,
18 | };
19 | }
20 |
21 | factory BackupData.fromJson(Map map) {
22 | return BackupData(
23 | animeListState: Map.from(map[ANIMELIST_STATE_KEY]),
24 | sourcesState: Map.from(map[SOURCES_STATE_KEY]),
25 | );
26 | }
27 |
28 | String toRawJson() => json.encode(toJson());
29 |
30 | factory BackupData.fromRawJson(String source) => BackupData.fromJson(json.decode(source));
31 | }
32 |
33 | class CloudBackup {
34 | CloudBackup({
35 | this.id = '',
36 | this.userId = '',
37 | this.data = '',
38 | this.createdAt,
39 | this.updatedAt,
40 | this.v = -1,
41 | });
42 |
43 | final String id;
44 | final String userId;
45 | final String data;
46 | final DateTime? createdAt;
47 | final DateTime? updatedAt;
48 | final int v;
49 |
50 | CloudBackup copyWith({
51 | String? id,
52 | String? userId,
53 | String? data,
54 | DateTime? createdAt,
55 | DateTime? updatedAt,
56 | int? v,
57 | }) =>
58 | CloudBackup(
59 | id: id ?? this.id,
60 | userId: userId ?? this.userId,
61 | data: data ?? this.data,
62 | createdAt: createdAt ?? this.createdAt,
63 | updatedAt: updatedAt ?? this.updatedAt,
64 | v: v ?? this.v,
65 | );
66 |
67 | factory CloudBackup.fromRawJson(String str) => CloudBackup.fromJson(json.decode(str));
68 |
69 | String toRawJson() => json.encode(toJson());
70 |
71 | factory CloudBackup.fromJson(Map json) => CloudBackup(
72 | id: json["_id"] == null ? '' : json["_id"],
73 | userId: json["user_id"] == '' ? null : json["user_id"],
74 | data: json["data"] == null ? '' : json["data"],
75 | createdAt: json["createdAt"] == null ? null : DateTime.parse(json["createdAt"]),
76 | updatedAt: json["updatedAt"] == null ? null : DateTime.parse(json["updatedAt"]),
77 | v: json["__v"] == null ? -1 : json["__v"],
78 | );
79 |
80 | Map toJson() => {
81 | "_id": id,
82 | "user_id": userId,
83 | "data": data,
84 | "createdAt": createdAt?.toIso8601String(),
85 | "updatedAt": updatedAt?.toIso8601String(),
86 | "__v": v,
87 | };
88 | }
89 |
--------------------------------------------------------------------------------
/lib/src/data/feed.response.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:equatable/equatable.dart';
4 | import 'package:timeago/timeago.dart' as timeago;
5 |
6 | import '../misc/feed-icons.dart';
7 |
8 | class QuantzFeed {
9 | QuantzFeed({
10 | this.page = 0,
11 | this.count = 0,
12 | this.items = const [],
13 | });
14 |
15 | final int page;
16 | final int count;
17 | final List items;
18 |
19 | QuantzFeed copyWith({
20 | int? page,
21 | int? count,
22 | List? items,
23 | }) =>
24 | QuantzFeed(
25 | page: page ?? this.page,
26 | count: count ?? this.count,
27 | items: items ?? this.items,
28 | );
29 |
30 | factory QuantzFeed.fromRawJson(String str) => QuantzFeed.fromJson(json.decode(str));
31 |
32 | String toRawJson() => json.encode(toJson());
33 |
34 | factory QuantzFeed.fromJson(Map json) => QuantzFeed(
35 | page: json["page"] == null ? 0 : json["page"],
36 | count: json["count"] == null ? 0 : json["count"],
37 | items: json["feed"] == null ? [] : List.from(json["feed"].map((x) => QuantzFeedItem.fromJson(x))),
38 | );
39 |
40 | Map toJson() => {
41 | "page": page,
42 | "count": count,
43 | "feed": items.map((x) => x.toJson()).toList(),
44 | };
45 | }
46 |
47 | class QuantzFeedItem extends Equatable {
48 | QuantzFeedItem({
49 | this.title = '',
50 | this.permalink = '',
51 | this.utcTimestampSeconds = 0,
52 | this.provider = '',
53 | this.sourceDomain = '',
54 | });
55 |
56 | final String title;
57 | final String permalink;
58 | final int utcTimestampSeconds;
59 | final String provider;
60 | final String sourceDomain;
61 |
62 | /// For UI to indicate when did the news got detected by Quantz.
63 | String get ago => timeago.format(DateTime.fromMillisecondsSinceEpoch(utcTimestampSeconds * 1000));
64 | String get sourceImage => feedIconsMap[provider] ?? '';
65 | int get tooltipDuration => title.length * 70; // 100ms per character
66 |
67 | QuantzFeedItem copyWith({
68 | String? title,
69 | String? permalink,
70 | int? utcTimestampSeconds,
71 | String? provider,
72 | String? sourceDomain,
73 | }) =>
74 | QuantzFeedItem(
75 | title: title ?? this.title,
76 | permalink: permalink ?? this.permalink,
77 | utcTimestampSeconds: utcTimestampSeconds ?? this.utcTimestampSeconds,
78 | provider: provider ?? this.provider,
79 | sourceDomain: sourceDomain ?? this.sourceDomain,
80 | );
81 |
82 | factory QuantzFeedItem.fromRawJson(String str) => QuantzFeedItem.fromJson(json.decode(str));
83 |
84 | String toRawJson() => json.encode(toJson());
85 |
86 | factory QuantzFeedItem.fromJson(Map json) => QuantzFeedItem(
87 | title: json["title"] == null ? '' : json["title"],
88 | permalink: json["permalink"] == null ? '' : json["permalink"],
89 | utcTimestampSeconds: json["utcTimestampSeconds"] == null ? 0 : json["utcTimestampSeconds"],
90 | provider: json["provider"] == null ? '' : json["provider"],
91 | sourceDomain: json["sourceDomain"] == null ? '' : json["sourceDomain"],
92 | );
93 |
94 | Map toJson() => {
95 | "title": title,
96 | "permalink": permalink,
97 | "utcTimestampSeconds": utcTimestampSeconds,
98 | "provider": provider,
99 | "sourceDomain": sourceDomain,
100 | };
101 |
102 | List